[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\nopen_collective: evershopcommerce\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG]\"\nlabels: ''\nassignees: treoden\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Background (please complete the following information):**\n - NodeJS Version\n - Postgres Version\n - EverShop Version\n - OS: [e.g. Window, Ubuntu, Mac-OS]\n - Browser [e.g. Chrome, Safari]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[FEATURE REQUEST]\"\nlabels: ''\nassignees: treoden\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## PR Checklist\nPlease check if your PR fulfills the following requirements:\n\n- [ ] Tests for the changes have been added (for bug fixes / features)\n\n## PR Type\nWhat kind of change does this PR introduce?\n\n<!-- Please check the one that applies to this PR using \"x\". -->\n- [ ] Bugfix\n- [ ] Feature\n- [ ] Code style update (formatting, local variables)\n- [ ] Refactoring (no functional changes, no api changes)\n- [ ] Build related changes\n- [ ] CI related changes\n- [ ] Other... Please describe:\n\n## What is the current behavior?\n<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->\n\nIssue Number: N/A\n\n\n## What is the new behavior?\n\n\n## Does this PR introduce a breaking change?\n- [ ] Yes\n- [ ] No\n\n<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->\n\n\n## Other information\n"
  },
  {
    "path": ".github/workflows/build_test.yml",
    "content": "name: Github build\n\non: [pull_request]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node: [ '20', '22']\n    name: Node ${{ matrix.node }}\n\n    steps:\n      - uses: actions/checkout@v2\n      - name: Setup node js\n        uses: actions/setup-node@v2\n        with:\n          node-version: ${{ matrix.node }}\n      - run: npm install -g npm@9\n      - run: npm install\n      - run: npm run compile\n      - run: npm run compile:db\n      - run: npm run test"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.env\ndist\n!packages/create-evershop-app/sample/extensions/sample/dist\n!packages/create-evershop-app/sample/themes/sample/dist\n/extensions/*\n!extensions/agegate\n!extensions/azure_file_storage\n!extensions/google_login\n!extensions/s3_file_storage\n!extensions/resend\n!extensions/sendgrid\n!extensions/product_review\n/themes\n.idea/*\n.evershop\n.vscode\nnpm-debug.log\n/package-log.json\n/media\n/public\n.DS_Store\n.log\ncoverage\ncypress\n.turbo\n/config\n/packages/evershop/config\n.prettierignore\n.prettierrc\nmysqlToPostgres.js\n/docs\nextensions"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\n#FORCE_COLOR=1 npm run lint"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\r\n\r\n## Our Pledge\r\n\r\nWe as members, contributors, and leaders pledge to make participation in our\r\ncommunity a harassment-free experience for everyone, regardless of age, body\r\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\r\nidentity and expression, level of experience, education, socio-economic status,\r\nnationality, personal appearance, race, religion, or sexual identity\r\nand orientation.\r\n\r\nWe pledge to act and interact in ways that contribute to an open, welcoming,\r\ndiverse, inclusive, and healthy community.\r\n\r\n## Our Standards\r\n\r\nExamples of behavior that contributes to a positive environment for our\r\ncommunity include:\r\n\r\n* Demonstrating empathy and kindness toward other people\r\n* Being respectful of differing opinions, viewpoints, and experiences\r\n* Giving and gracefully accepting constructive feedback\r\n* Accepting responsibility and apologizing to those affected by our mistakes,\r\n  and learning from the experience\r\n* Focusing on what is best not just for us as individuals, but for the\r\n  overall community\r\n\r\nExamples of unacceptable behavior include:\r\n\r\n* The use of sexualized language or imagery, and sexual attention or\r\n  advances of any kind\r\n* Trolling, insulting or derogatory comments, and personal or political attacks\r\n* Public or private harassment\r\n* Publishing others' private information, such as a physical or email\r\n  address, without their explicit permission\r\n* Other conduct which could reasonably be considered inappropriate in a\r\n  professional setting\r\n\r\n## Enforcement Responsibilities\r\n\r\nCommunity leaders are responsible for clarifying and enforcing our standards of\r\nacceptable behavior and will take appropriate and fair corrective action in\r\nresponse to any behavior that they deem inappropriate, threatening, offensive,\r\nor harmful.\r\n\r\nCommunity leaders have the right and responsibility to remove, edit, or reject\r\ncomments, commits, code, wiki edits, issues, and other contributions that are\r\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\r\ndecisions when appropriate.\r\n\r\n## Scope\r\n\r\nThis Code of Conduct applies within all community spaces, and also applies when\r\nan individual is officially representing the community in public spaces.\r\nExamples of representing our community include using an official e-mail address,\r\nposting via an official social media account, or acting as an appointed\r\nrepresentative at an online or offline event.\r\n\r\n## Enforcement\r\n\r\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\r\nreported to the community leaders responsible for enforcement at\r\n[support@evershop.io](mailto:support@evershop.io).\r\nAll complaints will be reviewed and investigated promptly and fairly.\r\n\r\nAll community leaders are obligated to respect the privacy and security of the\r\nreporter of any incident.\r\n\r\n## Enforcement Guidelines\r\n\r\nCommunity leaders will follow these Community Impact Guidelines in determining\r\nthe consequences for any action they deem in violation of this Code of Conduct:\r\n\r\n### 1. Correction\r\n\r\n**Community Impact**: Use of inappropriate language or other behavior deemed\r\nunprofessional or unwelcome in the community.\r\n\r\n**Consequence**: A private, written warning from community leaders, providing\r\nclarity around the nature of the violation and an explanation of why the\r\nbehavior was inappropriate. A public apology may be requested.\r\n\r\n### 2. Warning\r\n\r\n**Community Impact**: A violation through a single incident or series\r\nof actions.\r\n\r\n**Consequence**: A warning with consequences for continued behavior. No\r\ninteraction with the people involved, including unsolicited interaction with\r\nthose enforcing the Code of Conduct, for a specified period of time. This\r\nincludes avoiding interactions in community spaces as well as external channels\r\nlike social media. Violating these terms may lead to a temporary or\r\npermanent ban.\r\n\r\n### 3. Temporary Ban\r\n\r\n**Community Impact**: A serious violation of community standards, including\r\nsustained inappropriate behavior.\r\n\r\n**Consequence**: A temporary ban from any sort of interaction or public\r\ncommunication with the community for a specified period of time. No public or\r\nprivate interaction with the people involved, including unsolicited interaction\r\nwith those enforcing the Code of Conduct, is allowed during this period.\r\nViolating these terms may lead to a permanent ban.\r\n\r\n### 4. Permanent Ban\r\n\r\n**Community Impact**: Demonstrating a pattern of violation of community\r\nstandards, including sustained inappropriate behavior,  harassment of an\r\nindividual, or aggression toward or disparagement of classes of individuals.\r\n\r\n**Consequence**: A permanent ban from any sort of public interaction within\r\nthe community.\r\n\r\n## Attribution\r\n\r\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\r\nversion 2.0, available at\r\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\r\n\r\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\r\nenforcement ladder](https://github.com/mozilla/diversity).\r\n\r\n[homepage]: https://www.contributor-covenant.org\r\n\r\nFor answers to common questions about this code of conduct, see the FAQ at\r\nhttps://www.contributor-covenant.org/faq. Translations are available at\r\nhttps://www.contributor-covenant.org/translations.\r\n\r\n# License\r\n\r\nEverShop is licensed under the GNU General Public License v3.0"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to EverShop\n\nWe love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:\n\n- Reporting a bug\n- Discussing the current state of the code\n- Submitting a fix\n- Proposing new features\n\n---\n\n- Read about our [Code Of Conduct](https://github.com/evershopcommerce/evershop/blob/main/CODE_OF_CONDUCT.md).\n\n## Developing\n\nTo develop locally:\n\n1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your\n   own GitHub account and then\n   [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device.\n\n   ```sh\n   git clone https://github.com/evershopcommerce/evershop.git\n   ```\n\n2. Create a new branch:\n   ```\n   git checkout -b MY_BRANCH_NAME\n   ```\n3. Install the dependencies with:\n   ```\n   npm install\n   ```\n4. Create a Postgres database:\n   ```\n   // EverShop use Postgres for database storage\n   ```\n5. Run installation command to create a database schema:\n   ```\n   npm run setup\n   ```\n6. Start development server:\n   ```\n   npm run dev\n   ```\n7. Building\n\nYou can build with:\n\n```bash\nnpm run build\n```\n\n8. Testing the production build\n```bash\nnpm run start\n```\n\n9. Running tests\n\nRun the [Jest](https://jestjs.io/) unit testing\n```sh\nnpm run test\n```\n\n10. Running linting\n```sh\nnpm run lint\n```\n\n11. Issue that [pull request!](https://github.com/github/docs/blob/main/CONTRIBUTING.md) to the `dev` branch.\n\n## Any contributions you make will be under the GNU General Public License v3.0 Software License\nIn short, when you submit code changes, your submissions are understood to be under the same [GNU General Public License v3.0](https://github.com/evershopcommerce/evershop/blob/main/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern.\n\n## Report bugs using Github's [issues](https://github.com/evershopcommerce/evershop/issues)\nWe use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy!\n\n## Write bug reports with detail, background, and sample code\n**Great Bug Reports** tend to have:\n\n- A quick summary and/or background\n  - What EverShop version you are using\n  - What NodeJs version you are using\n  - What OS system you are using\n  - What Postgres version you are using\n- Steps to reproduce\n  - Be specific!\n  - Give sample code if you can\n- What you expected would happen\n- What actually happens\n- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)\n\n## License\nBy contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0 License.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:18-alpine\nWORKDIR /app\nRUN npm install -g npm@9\nCOPY package*.json .\nCOPY packages ./packages\nCOPY themes ./themes\nCOPY extensions ./extensions\nCOPY public ./public\nCOPY media ./media\nCOPY config ./config\nCOPY translations ./translations\nRUN npm install\nRUN npm run build\n\nEXPOSE 80\nCMD [\"npm\", \"run\", \"start\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>."
  },
  {
    "path": "README.md",
    "content": "<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>\n<p align=\"center\">\n<img width=\"60\" height=\"68\" alt=\"EverShop Logo\" src=\"https://raw.githubusercontent.com/evershopcommerce/evershop/dev/.github/images/logo-green.png\"/>\n</p>\n<p align=\"center\">\n  <h1 align=\"center\">EverShop</h1>\n</p>\n<h4 align=\"center\">\n    <a href=\"https://evershop.io/docs/development/getting-started/introduction\">Documentation</a> |\n    <a href=\"https://demo.evershop.io/\">Demo</a>\n</h4>\n\n<p align=\"center\">\n  <img src=\"https://github.com/evershopcommerce/evershop/actions/workflows/build_test.yml/badge.svg\" alt=\"Github Action\"> <a href=\"https://twitter.com/evershopjs\"><img alt=\"Twitter Follow\" src=\"https://img.shields.io/twitter/follow/evershopjs?style=social\"></a> <a href=\"https://discord.gg/GSzt7dt7RM\"><img src=\"https://img.shields.io/discord/757179260417867879?label=discord\" alt=\"Discord\"></a> <a href=\"https://opensource.org/licenses/GPL-3.0\"><img src=\"https://img.shields.io/badge/License-GPLv3-blue.svg\" alt=\"License\"></a>\n</p>\n\n<p align=\"center\">\n<img alt=\"EverShop\" width=\"950\" src=\"https://raw.githubusercontent.com/evershopcommerce/evershop/dev/.github/images/banner.png\"/>\n</p>\n\n## Introduction\n\nEverShop is a modern, TypeScript-first eCommerce platform built with GraphQL and React. Designed for developers, it offers essential commerce features in a modular, fully customizable architecture—perfect for building tailored shopping experiences with confidence and speed.\n\n## Installation Using Docker\n\n\nYou can get started with EverShop in minutes by using the Docker image. The Docker image is a great way to get started with EverShop without having to worry about installing dependencies or configuring your environment.\n\n```bash\ncurl -sSL https://raw.githubusercontent.com/evershopcommerce/evershop/main/docker-compose.yml > docker-compose.yml\ndocker compose up -d\n```\n\nFor the full installation guide, please refer to our [Installation guide](https://evershop.io/docs/development/getting-started/installation-guide).\n\n## Documentation\n\n- [Installation guide](https://evershop.io/docs/development/getting-started/installation-guide).\n\n- [Extension development](https://evershop.io/docs/development/module/create-your-first-extension).\n\n- [Theme development](https://evershop.io/docs/development/theme/theme-overview).\n\n\n## Demo\n\nExplore our demo store.\n\n<p align=\"left\">\n  <a href=\"https://demo.evershop.io/admin\" target=\"_blank\">\n    <img alt=\"evershop-backend-demo\" height=\"35\" alt=\"EverShop Admin Demo\" src=\"https://raw.githubusercontent.com/evershopcommerce/evershop/dev/.github/images/evershop-demo-back.png\"/>\n  </a>\n  <a href=\"https://demo.evershop.io/\" target=\"_blank\">\n    <img alt=\"evershop-store-demo\" height=\"35\" alt=\"EverShop Store Demo\" src=\"https://raw.githubusercontent.com/evershopcommerce/evershop/dev/.github/images/evershop-demo-front.png\"/>\n  </a>\n</p>\n<b>Demo user:</b>\n\nEmail: demo@evershop.io<br/>\nPassword: 123456\n\n## Support\n\nIf you like my work, feel free to:\n\n- ⭐ this repository. It helps.\n- [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)][tweet] about EverShop. Thank you!\n\n[tweet]: https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Fevershopcommerce%2Fevershop&text=Awesome%20React%20Ecommerce%20Project&hashtags=react,ecommerce,expressjs,graphql\n\n## Contributing\n\nEverShop is an open-source project. We are committed to a fully transparent development process and appreciate highly any contributions. Whether you are helping us fix bugs, proposing new features, improving our documentation or spreading the word - we would love to have you as part of the EverShop community.\n\n### Ask a question about EverShop\n\nYou can ask questions, and participate in discussions about EverShop-related topics in the EverShop Discord channel.\n\n<a href=\"https://discord.gg/GSzt7dt7RM\"><img src=\"https://raw.githubusercontent.com/evershopcommerce/evershop/dev/.github/images/discord_banner_github.svg\" /></a>\n\n### Create a bug report\n\nIf you see an error message or run into an issue, please [create bug report](https://github.com/evershopcommerce/evershop/issues/new). This effort is valued and it will help all EverShop users.\n\n\n### Submit a feature request\n\nIf you have an idea, or you're missing a capability that would make development easier and more robust, please [Submit feature request](https://github.com/evershopcommerce/evershop/issues/new).\n\nIf a similar feature request already exists, don't forget to leave a \"+1\".\nIf you add some more information such as your thoughts and vision about the feature, your comments will be embraced warmly :)\n\n\nPlease refer to our [Contribution Guidelines](./CONTRIBUTING.md) and [Code of Conduct](./CODE_OF_CONDUCT.md).\n\n## 🚀 The Future of EverShop\n\nEverShop is seeing rapid organic growth and strong adoption from the developer community. We are now scaling our operations and building **EverShop Cloud**.\n\nIf you are a strategic investor interested in the future of Node.js commerce and our mission to set a new standard for modern eCommerce, we’d love to share our vision and roadmap with you.\n\n📩 **Get in touch:** support@evershop.io\n\n## License\n\n[GPL-3.0 License](https://github.com/evershopcommerce/evershop/blob/main/LICENSE)\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: '3.8'\n\nservices:\n  app:\n    image: evershop/evershop:latest\n    restart: always\n    environment:\n      DB_HOST: database\n      DB_PORT: 5432\n      DB_PASSWORD: postgres\n      DB_USER: postgres\n      DB_NAME: postgres\n    networks:\n      - myevershop\n    depends_on:\n      - database\n    ports:\n      - 3000:3000\n  \n  #The postgres database: \n  database:\n    image: postgres:16\n    restart: unless-stopped\n    volumes:\n      - postgres-data:/var/lib/postgresql/data\n    environment:\n      POSTGRES_PASSWORD: postgres\n      POSTGRES_USER: postgres\n      POSTGRES_DB: postgres\n    ports:\n      - \"5432:5432\"\n    networks:\n      - myevershop\n\nnetworks:\n  myevershop:\n    name: MyEverShop\n    driver: bridge\n\nvolumes:\n  postgres-data:\n"
  },
  {
    "path": "eslint.config.js",
    "content": "// eslint.config.js\nimport eslintPluginTypescript from '@typescript-eslint/eslint-plugin';\nimport typescriptParser from '@typescript-eslint/parser';\nimport pluginImport from 'eslint-plugin-import';\nimport jsxA11y from 'eslint-plugin-jsx-a11y';\nimport pluginReact from 'eslint-plugin-react';\n\nexport default [\n  {\n      ignores: [\n      \"/node_modules/\",\n      \"**/*test.js\",\n      \"**/tests/**\",\n      \"**/create-evershop-app/**\",\n      \"**/.evershop/**\",\n      \"/.vscode/**\",\n      \"/.git/**\",\n      \"/.idea/**\",\n      \"**/extensions/**\",\n      \"**/public/**\",\n      \"**/themes/**\",\n      \"**/media/**\",\n      \"**/dist/**\",\n      \"**/packages/*/dist/**\",\n      \"**/packages/evershop/dist/**\",\n      \"**/packages/postgres-query-builder/dist/**\",\n      \"**/packages/product_review/**\",\n      \"**/packages/resend/**\"\n    ]},\n  pluginReact.configs.flat.recommended,\n  pluginReact.configs.flat['jsx-runtime'],\n  jsxA11y.flatConfigs.recommended,\n  {\n    files: [\"**/*.ts\", \"**/*.tsx\", \"**/*.d.ts\"],\n    plugins: {\n      \"@typescript-eslint\": eslintPluginTypescript\n    },\n    languageOptions: {\n      parser: typescriptParser,\n      parserOptions: {\n        ecmaVersion: 'latest',\n        sourceType: 'module'\n      }\n    }\n  },\n  {\n    plugins: {\n      \"import\": pluginImport\n    },\n    languageOptions: {\n      ecmaVersion: 'latest',\n      sourceType: 'module',\n      parserOptions : {\n        ecmaFeatures: {\n          jsx: true\n        }\n      }\n    },\n    rules: {\n      semi: \"off\",\n      \"prefer-const\": \"error\",\n      \"import/no-dynamic-require\": 0,\n      \"no-else-return\": \"off\",\n      \"import/prefer-default-export\": 0,\n      \"jsx-a11y/anchor-is-valid\": 0,\n      \"import/no-extraneous-dependencies\": \"off\",\n      \"import/no-unresolved\": \"off\",\n      \"camelcase\": \"off\",\n      \"no-multi-assign\": \"off\",\n      \"no-template-curly-in-string\": \"off\",\n      \"react/no-array-index-key\": \"off\",\n      \"react/no-unstable-nested-components\": \"off\",\n      \"no-continue\": \"off\",\n      \"no-await-in-loop\": \"off\",\n      \"no-use-before-define\" : \"off\",\n      \"global-require\": \"off\",\n      \"import/extensions\": \"off\",\n      \"no-shadow\": \"off\",\n      \"no-lonely-if\": \"warn\",\n      \"no-console\": \"error\",\n      \"no-useless-return\": \"off\",\n      \"react/display-name\": \"off\",\n      \"jsx-a11y/label-has-associated-control\": \"off\",\n      \"jsx-a11y/role-supports-aria-props\": \"warn\",\n      \"jsx-a11y/no-noninteractive-element-interactions\": \"warn\",\n      // Add import sorting rules\n      \"import/order\": [\"warn\", {\n        \"groups\": [\"builtin\", \"external\", \"internal\", \"parent\", \"sibling\", \"index\"],\n        \"newlines-between\": \"never\",\n        \"alphabetize\": {\n          \"order\": \"asc\",\n          \"caseInsensitive\": true\n        }\n      }]\n    },\n    settings: {\n      react: {\n        version: \"detect\"\n      }\n    }\n  }\n]"
  },
  {
    "path": "jest.config.js",
    "content": "export default {\n  testEnvironment: \"node\",\n  moduleNameMapper: {\n    '^@evershop/postgres-query-builder$': '<rootDir>/packages/postgres-query-builder/dist/index.js',\n    '^@evershop/postgres-query-builder/(.*)$': '<rootDir>/packages/postgres-query-builder/dist/$1',\n    '^(\\\\.{1,2}/.*)\\\\.js$': '$1'\n  },\n  transformIgnorePatterns: [\n    \"/node_modules/(?!(@evershop)/)\"\n  ],\n  testMatch: [\"**/dist/**/tests/**/unit/**/*.test.[jt]s\"],\n  modulePathIgnorePatterns: [\"<rootDir>/packages/evershop/src/\"]\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"evershop\",\n  \"version\": \"2.1.0\",\n  \"type\": \"module\",\n  \"description\": \"A shopping cart platform with Express, React and Postgres\",\n  \"workspaces\": [\n    \"packages/*\",\n    \"extensions/*\"\n  ],\n  \"scripts\": {\n    \"dev\": \"node ./packages/evershop/dist/bin/dev/index.js\",\n    \"start\": \"node ./packages/evershop/dist/bin/start/index.js\",\n    \"build\": \"node ./packages/evershop/dist/bin/build/index.js\",\n    \"build-fast\": \"evershop build -- --skip-minify\",\n    \"setup\": \"evershop install\",\n    \"theme:active\": \"evershop theme:active\",\n    \"theme:twizz\": \"evershop theme:twizz\",\n    \"theme:create\": \"evershop theme:create\",\n    \"compile\": \"rimraf ./packages/evershop/dist && cd ./packages/evershop && swc ./src/ -d dist/ --config-file .swcrc --copy-files --strip-leading-paths\",\n    \"compile:db\": \"rimraf ./packages/postgres-query-builder/dist && cd ./packages/postgres-query-builder && swc ./src/ -d dist/ --config-file .swcrc --copy-files --strip-leading-paths\",\n    \"compile:tsc\": \"rimraf ./packages/evershop/dist && cd ./packages/evershop && tsc && copyfiles -u 1 \\\"src/**/*.{graphql,scss,css,json}\\\" dist\",\n    \"start:debug\": \"node ./packages/evershop/dist/bin/start/index.js --debug\",\n    \"test\": \"ALLOW_CONFIG_MUTATIONS=true NODE_OPTIONS=--experimental-vm-modules node_modules/jest/bin/jest.js\",\n    \"lint\": \"eslint --fix --ext .js,.jsx,.ts,.tsx ./packages\",\n    \"prepare\": \"husky install\"\n  },\n  \"author\": \"The Nguyen (https://evershop.io)\",\n  \"license\": \"GNU GENERAL PUBLIC LICENSE 3.0\",\n  \"devDependencies\": {\n    \"@parcel/watcher\": \"^2.5.1\",\n    \"@swc/cli\": \"^0.7.7\",\n    \"@swc/core\": \"^1.11.29\",\n    \"@types/jest\": \"^29.5.14\",\n    \"@types/jsonwebtoken\": \"^9.0.10\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.32.0\",\n    \"@typescript-eslint/parser\": \"^8.32.0\",\n    \"copyfiles\": \"^2.4.1\",\n    \"cypress\": \"^13.15.1\",\n    \"eslint\": \"^9.24.0\",\n    \"eslint-plugin-import\": \"^2.31.0\",\n    \"eslint-plugin-jsx-a11y\": \"^6.10.2\",\n    \"eslint-plugin-react\": \"^7.37.5\",\n    \"execa\": \"^9.6.0\",\n    \"husky\": \"^8.0.3\",\n    \"jest\": \"^29.7.0\",\n    \"prettier\": \"2.8.4\",\n    \"reflect-metadata\": \"^0.1.13\",\n    \"rimraf\": \"^6.0.1\",\n    \"swc-minify-webpack-plugin\": \"^2.1.3\",\n    \"tailwindcss\": \"^4.1.18\",\n    \"typescript\": \"^5.8.3\",\n    \"webpack-bundle-analyzer\": \"^4.10.2\"\n  },\n  \"dependencies\": {\n    \"@sendgrid/mail\": \"^8.1.6\",\n    \"@types/react-slick\": \"^0.23.13\",\n    \"@types/uuid\": \"^10.0.0\",\n    \"uuid\": \"^13.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/create-evershop-app/README.md",
    "content": "# create-evershop-app\n\nThis package includes the global command for [Create EverShop App](https://evershop.io/).<br> Please refer to its documentation:\n\n- [Getting Started](https://evershop.io/docs/development/getting-started/introduction) – How to create a new app.\n- [Development Guide](https://evershop.io/docs/development/) – How to develop an ecommerce web app with EverShop.\n"
  },
  {
    "path": "packages/create-evershop-app/createEverShopApp.js",
    "content": "const https = require('https');\nconst chalk = require('chalk');\nconst commander = require('commander');\nconst dns = require('dns');\nconst { execSync } = require('child_process');\nconst fs = require('fs-extra');\nconst os = require('os');\nconst path = require('path');\nconst semver = require('semver');\nconst spawn = require('cross-spawn');\nconst url = require('url');\nconst validateProjectName = require('validate-npm-package-name');\nconst { mkdir } = require('fs/promises');\nconst packageJson = require('./package.json');\n\nfunction isUsingYarn() {\n  return (process.env.npm_config_user_agent || '').indexOf('yarn') === 0;\n}\n\nlet projectName;\n\nfunction init() {\n  const program = new commander.Command(packageJson.name)\n    .version(packageJson.version)\n    .arguments('[project-directory]')\n    .usage(`${chalk.green('<project-directory>')} [options]`)\n    .action((name) => {\n      projectName = name;\n    })\n    .option('--verbose', 'Print additional logs')\n    .option('--info', 'Print environment debug info')\n    .on('--help', () => {\n      console.log(\n        `    Only ${chalk.green('<project-directory>')} is required.`\n      );\n      console.log();\n      console.log(\n        `    If you have any problems, do not hesitate to file an issue:`\n      );\n      console.log(\n        `      ${chalk.cyan(\n          'https://github.com/evershop/create-evershop-app/issues/new'\n        )}`\n      );\n      console.log();\n    })\n    .parse(process.argv);\n  const options = program.opts();\n\n  if (typeof projectName === 'undefined') {\n    console.error('Please specify the project directory:');\n    console.log(\n      `  ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`\n    );\n    console.log();\n    console.log('For example:');\n    console.log(\n      `  ${chalk.cyan(program.name())} ${chalk.green('my-evershop-app')}`\n    );\n    console.log();\n    console.log(\n      `Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`\n    );\n    process.exit(1);\n  }\n\n  // We first check the registry directly via the API, and if that fails, we try\n  // the slower `npm view [package] version` command.\n  //\n  // This is important for users in environments where direct access to npm is\n  // blocked by a firewall, and packages are provided exclusively via a private\n  // registry.\n  checkForLatestVersion()\n    .catch(() => {\n      try {\n        return execSync('npm view create-evershop-app version')\n          .toString()\n          .trim();\n      } catch (e) {\n        return null;\n      }\n    })\n    .then((latest) => {\n      if (latest && semver.lt(packageJson.version, latest)) {\n        console.log();\n        console.error(\n          chalk.yellow(\n            `You are running \\`create-evershop-app\\` ${packageJson.version}, which is behind the latest release (${latest}).\\n\\n` +\n              'We recommend always using the latest version of create-evershop-app if possible.'\n          )\n        );\n        console.log();\n        console.log(\n          'The latest instructions for creating a new app can be found here:\\n' +\n            'https://evershop.io/docs/development/getting-started/installation-guide/'\n        );\n        console.log();\n      } else {\n        const useYarn = isUsingYarn();\n\n        createApp(projectName, options.verbose, useYarn);\n      }\n    });\n}\n\nfunction createApp(name, verbose, useYarn) {\n  const root = path.resolve(name);\n  const appName = path.basename(root);\n\n  checkAppName(appName);\n  fs.ensureDirSync(name);\n  if (!isSafeToCreateProjectIn(root, name)) {\n    process.exit(1);\n  }\n  console.log();\n\n  console.log(`Creating a new EverShop app in ${chalk.green(root)}.`);\n  console.log();\n\n  const packageJson = {\n    name: appName,\n    version: '0.1.0',\n    type: 'module',\n    private: true,\n    scripts: {\n      setup: 'evershop install',\n      start: 'evershop start',\n      build: 'evershop build',\n      dev: 'evershop dev'\n    }\n  };\n  fs.writeFileSync(\n    path.join(root, 'package.json'),\n    JSON.stringify(packageJson, null, 2) + os.EOL\n  );\n\n  const originalDirectory = process.cwd();\n  process.chdir(root);\n  if (!useYarn && !checkThatNpmCanReadCwd()) {\n    process.exit(1);\n  }\n\n  if (!useYarn) {\n    const npmInfo = checkNpmVersion();\n    if (!npmInfo.hasMinNpm) {\n      if (npmInfo.npmVersion) {\n        console.log(\n          chalk.yellow(\n            `Please update to npm 7 or higher for a workspaces feature.\\n`\n          )\n        );\n      }\n    }\n  }\n\n  run(root, appName, verbose, originalDirectory, useYarn);\n}\n\nfunction install(root, useYarn, dependencies, verbose, isOnline) {\n  return new Promise((resolve, reject) => {\n    let command;\n    let args;\n    if (useYarn) {\n      command = 'yarnpkg';\n      args = ['add', '--exact'];\n      if (!isOnline) {\n        args.push('--offline');\n      }\n      [].push.apply(args, dependencies);\n\n      // Explicitly set cwd() to work around issues like\n      // https://github.com/facebook/create-react-app/issues/3326.\n      // Unfortunately we can only do this for Yarn because npm support for\n      // equivalent --prefix flag doesn't help with this issue.\n      // This is why for npm, we run checkThatNpmCanReadCwd() early instead.\n      args.push('--cwd');\n      args.push(root);\n\n      if (!isOnline) {\n        console.log(chalk.yellow('You appear to be offline.'));\n        console.log(chalk.yellow('Falling back to the local Yarn cache.'));\n        console.log();\n      }\n    } else {\n      command = 'npm';\n      args = [\n        'install',\n        '--no-audit', // https://github.com/facebook/create-evershop-app/issues/11174\n        '--save',\n        '--save-exact',\n        '--loglevel',\n        'error'\n      ].concat(dependencies);\n    }\n\n    if (verbose) {\n      args.push('--verbose');\n    }\n\n    const child = spawn(command, args, { stdio: 'inherit' });\n    child.on('close', (code) => {\n      if (code !== 0) {\n        reject({\n          command: `${command} ${args.join(' ')}`\n        });\n        return;\n      }\n      resolve();\n    });\n  });\n}\n\nfunction installDevDependencies(\n  root,\n  useYarn,\n  devDependencies,\n  verbose,\n  isOnline\n) {\n  console.log(`Installing some development dependencies...`);\n  return new Promise((resolve, reject) => {\n    let command;\n    let args;\n    if (useYarn) {\n      command = 'yarnpkg';\n      args = ['add', '--exact'];\n      if (!isOnline) {\n        args.push('--offline');\n      }\n      [].push.apply(args, devDependencies);\n      args.push('--dev');\n      // Explicitly set cwd() to work around issues like\n      // https://github.com/facebook/create-react-app/issues/3326.\n      // Unfortunately we can only do this for Yarn because npm support for\n      // equivalent --prefix flag doesn't help with this issue.\n      // This is why for npm, we run checkThatNpmCanReadCwd() early instead.\n      args.push('--cwd');\n      args.push(root);\n\n      if (!isOnline) {\n        console.log(chalk.yellow('You appear to be offline.'));\n        console.log(chalk.yellow('Falling back to the local Yarn cache.'));\n        console.log();\n      }\n    } else {\n      command = 'npm';\n      args = [\n        'install',\n        '--no-audit', // https://github.com/facebook/create-evershop-app/issues/11174\n        '--save',\n        '--save-exact',\n        '--loglevel',\n        'error'\n      ].concat(devDependencies);\n      args.push('--save-dev');\n    }\n\n    if (verbose) {\n      args.push('--verbose');\n    }\n\n    const child = spawn(command, args, { stdio: 'inherit' });\n    child.on('close', (code) => {\n      if (code !== 0) {\n        reject({\n          command: `${command} ${args.join(' ')}`\n        });\n        return;\n      }\n      resolve();\n    });\n  });\n}\n\nfunction run(root, appName, verbose, originalDirectory, useYarn) {\n  console.log(`Installing ${chalk.cyan('@evershop/evershop')}`);\n  checkIfOnline(useYarn)\n    .then((isOnline) => ({\n      isOnline\n    }))\n    .then(({ isOnline }) => {\n      const allDependencies = ['@evershop/evershop'];\n      return install(root, useYarn, allDependencies, verbose, isOnline).then(\n        async () => {\n          await installDevDependencies(\n            root,\n            useYarn,\n            [\n              '@parcel/watcher',\n              '@types/config',\n              '@types/express',\n              '@types/node',\n              '@types/pg',\n              '@types/react',\n              'execa',\n              'typescript'\n            ],\n            verbose,\n            isOnline\n          );\n          await createConfigFile(root);\n          await createSampleExtension(root);\n          await createSampleTheme(root);\n          await setUpEverShop(root);\n        }\n      );\n    })\n    .catch((reason) => {\n      console.log(reason);\n      console.log();\n      console.log('Aborting installation.');\n      if (reason.command) {\n        console.log(`  ${chalk.cyan(reason.command)} has failed.`);\n      } else {\n        console.log(chalk.red('Unexpected error. Please report it as a bug:'));\n        console.log(reason);\n      }\n      console.log();\n\n      // On 'exit' we will delete these files from target directory.\n      const knownGeneratedFiles = [\n        'package.json',\n        'node_modules',\n        'package-lock.json'\n      ];\n      const currentFiles = fs.readdirSync(path.join(root));\n      currentFiles.forEach((file) => {\n        knownGeneratedFiles.forEach((fileToMatch) => {\n          // This removes all knownGeneratedFiles.\n          if (file === fileToMatch) {\n            console.log(`Deleting generated file... ${chalk.cyan(file)}`);\n            fs.removeSync(path.join(root, file));\n          }\n        });\n      });\n      const remainingFiles = fs.readdirSync(path.join(root));\n      if (!remainingFiles.length) {\n        // Delete target folder if empty\n        console.log(\n          `Deleting ${chalk.cyan(`${appName}/`)} from ${chalk.cyan(\n            path.resolve(root, '..')\n          )}`\n        );\n        process.chdir(path.resolve(root, '..'));\n        fs.removeSync(path.join(root));\n      }\n      console.log('Done.');\n      process.exit(1);\n    });\n}\n\nfunction checkNpmVersion() {\n  let hasMinNpm = true;\n  let npmVersion = null;\n  try {\n    npmVersion = execSync('npm --version').toString().trim();\n    hasMinNpm = semver.gte(npmVersion, '7.0.0');\n  } catch (err) {\n    // ignore\n  }\n  return {\n    hasMinNpm,\n    npmVersion\n  };\n}\n\nfunction checkAppName(appName) {\n  const validationResult = validateProjectName(appName);\n  if (appName === 'dist') {\n    console.error(\n      chalk.red(\n        `Cannot create a project named ${chalk.green(\n          `\"${appName}\"`\n        )} because it is reserved for the distribution files.\\n` +\n          `Please choose a different project name.`\n      )\n    );\n    process.exit(1);\n  }\n  if (!validationResult.validForNewPackages) {\n    console.error(\n      chalk.red(\n        `Cannot create a project named ${chalk.green(\n          `\"${appName}\"`\n        )} because of npm naming restrictions:\\n`\n      )\n    );\n    [\n      ...(validationResult.errors || []),\n      ...(validationResult.warnings || [])\n    ].forEach((error) => {\n      console.error(chalk.red(`  * ${error}`));\n    });\n    console.error(chalk.red('\\nPlease choose a different project name.'));\n    process.exit(1);\n  }\n\n  // TODO: there should be a single place that holds the dependencies\n  const dependencies = ['react', 'react-dom', 'react-scripts'].sort();\n  if (dependencies.includes(appName)) {\n    console.error(\n      chalk.red(\n        `Cannot create a project named ${chalk.green(\n          `\"${appName}\"`\n        )} because a dependency with the same name exists.\\n` +\n          `Due to the way npm works, the following names are not allowed:\\n\\n`\n      ) +\n        chalk.cyan(dependencies.map((depName) => `  ${depName}`).join('\\n')) +\n        chalk.red('\\n\\nPlease choose a different project name.')\n    );\n    process.exit(1);\n  }\n}\n\n// If project only contains files generated by GH, it’s safe.\n// Also, if project contains remnant error logs from a previous\n// installation, lets remove them now.\n// We also special case IJ-based products .idea because it integrates with CRA:\n// https://github.com/facebook/create-evershop-app/pull/368#issuecomment-243446094\nfunction isSafeToCreateProjectIn(root, name) {\n  const validFiles = [\n    '.DS_Store',\n    '.git',\n    '.gitattributes',\n    '.gitignore',\n    '.gitlab-ci.yml',\n    '.hg',\n    '.hgcheck',\n    '.hgignore',\n    '.idea',\n    '.npmignore',\n    '.travis.yml',\n    'docs',\n    'LICENSE',\n    'README.md',\n    'mkdocs.yml',\n    'Thumbs.db'\n  ];\n  // These files should be allowed to remain on a failed install, but then\n  // silently removed during the next create.\n  const errorLogFilePatterns = [\n    'npm-debug.log',\n    'yarn-error.log',\n    'yarn-debug.log'\n  ];\n  const isErrorLog = (file) =>\n    errorLogFilePatterns.some((pattern) => file.startsWith(pattern));\n\n  const conflicts = fs\n    .readdirSync(root)\n    .filter((file) => !validFiles.includes(file))\n    // IntelliJ IDEA creates module files before CRA is launched\n    .filter((file) => !/\\.iml$/.test(file))\n    // Don't treat log files from previous installation as conflicts\n    .filter((file) => !isErrorLog(file));\n\n  if (conflicts.length > 0) {\n    console.log(\n      `The directory ${chalk.green(name)} contains files that could conflict:`\n    );\n    console.log();\n    for (const file of conflicts) {\n      try {\n        const stats = fs.lstatSync(path.join(root, file));\n        if (stats.isDirectory()) {\n          console.log(`  ${chalk.blue(`${file}/`)}`);\n        } else {\n          console.log(`  ${file}`);\n        }\n      } catch (e) {\n        console.log(`  ${file}`);\n      }\n    }\n    console.log();\n    console.log(\n      'Either try using a new directory name, or remove the files listed above.'\n    );\n\n    return false;\n  }\n\n  // Remove any log files from a previous installation.\n  fs.readdirSync(root).forEach((file) => {\n    if (isErrorLog(file)) {\n      fs.removeSync(path.join(root, file));\n    }\n  });\n  return true;\n}\n\nfunction getProxy() {\n  if (process.env.https_proxy) {\n    return process.env.https_proxy;\n  } else {\n    try {\n      // Trying to read https-proxy from .npmrc\n      const httpsProxy = execSync('npm config get https-proxy')\n        .toString()\n        .trim();\n      return httpsProxy !== 'null' ? httpsProxy : undefined;\n    } catch (e) {}\n  }\n}\n\n// See https://github.com/facebook/create-evershop-app/pull/3355\nfunction checkThatNpmCanReadCwd() {\n  const cwd = process.cwd();\n  let childOutput = null;\n  try {\n    // Note: intentionally using spawn over exec since\n    // the problem doesn't reproduce otherwise.\n    // `npm config list` is the only reliable way I could find\n    // to reproduce the wrong path. Just printing process.cwd()\n    // in a Node process was not enough.\n    childOutput = spawn.sync('npm', ['config', 'list']).output.join('');\n  } catch (err) {\n    // Something went wrong spawning node.\n    // Not great, but it means we can't do this check.\n    // We might fail later on, but let's continue.\n    return true;\n  }\n  if (typeof childOutput !== 'string') {\n    return true;\n  }\n  const lines = childOutput.split('\\n');\n  // `npm config list` output includes the following line:\n  // \"; cwd = C:\\path\\to\\current\\dir\" (unquoted)\n  // I couldn't find an easier way to get it.\n  const prefix = '; cwd = ';\n  const line = lines.find((line) => line.startsWith(prefix));\n  if (typeof line !== 'string') {\n    // Fail gracefully. They could remove it.\n    return true;\n  }\n  const npmCWD = line.substring(prefix.length);\n  if (npmCWD === cwd) {\n    return true;\n  }\n  console.error(\n    chalk.red(\n      `Could not start an npm process in the right directory.\\n\\n` +\n        `The current directory is: ${chalk.bold(cwd)}\\n` +\n        `However, a newly started npm process runs in: ${chalk.bold(\n          npmCWD\n        )}\\n\\n` +\n        `This is probably caused by a misconfigured system terminal shell.`\n    )\n  );\n  if (process.platform === 'win32') {\n    console.error(\n      `${chalk.red(\n        `On Windows, this can usually be fixed by running:\\n\\n`\n      )}  ${chalk.cyan(\n        'reg'\n      )} delete \"HKCU\\\\Software\\\\Microsoft\\\\Command Processor\" /v AutoRun /f\\n` +\n        `  ${chalk.cyan(\n          'reg'\n        )} delete \"HKLM\\\\Software\\\\Microsoft\\\\Command Processor\" /v AutoRun /f\\n\\n${chalk.red(\n          `Try to run the above two lines in the terminal.\\n`\n        )}${chalk.red(\n          `To learn more about this problem, read: https://blogs.msdn.microsoft.com/oldnewthing/20071121-00/?p=24433/`\n        )}`\n    );\n  }\n  return false;\n}\n\nfunction checkIfOnline(useYarn) {\n  if (!useYarn) {\n    // Don't ping the Yarn registry.\n    // We'll just assume the best case.\n    return Promise.resolve(true);\n  }\n\n  return new Promise((resolve) => {\n    dns.lookup('registry.yarnpkg.com', (err) => {\n      let proxy;\n      if (err != null && (proxy = getProxy())) {\n        // If a proxy is defined, we likely can't resolve external hostnames.\n        // Try to resolve the proxy name as an indication of a connection.\n        dns.lookup(url.parse(proxy).hostname, (proxyErr) => {\n          resolve(proxyErr == null);\n        });\n      } else {\n        resolve(err == null);\n      }\n    });\n  });\n}\n\nasync function setUpEverShop(projectDir) {\n  // Use spawn to run 'npm run setup' command from the project directory\n  await new Promise((resolve, reject) => {\n    const child = spawn('npm', ['run', 'setup'], {\n      cwd: projectDir,\n      stdio: 'inherit'\n    });\n    child.on('close', (code) => {\n      if (code !== 0) {\n        reject({\n          command: 'npm run setup'\n        });\n        return;\n      }\n      resolve();\n    });\n  });\n}\n\nasync function createConfigFile(projectDir) {\n  console.log(\n    `Creating ${chalk.cyan('config/default.json')} in ${chalk.green(\n      projectDir\n    )}`\n  );\n  const config = {\n    shop: {\n      language: 'en',\n      currency: 'USD'\n    },\n    system: {\n      extensions: [\n        {\n          name: 'sample',\n          resolve: 'extensions/sample',\n          enabled: true\n        }\n      ],\n      theme: 'sample'\n    }\n  };\n  await mkdir(path.resolve(projectDir, 'config'), { recursive: true });\n  fs.writeFileSync(\n    path.join(projectDir, 'config', 'default.json'),\n    JSON.stringify(config, null, 2) + os.EOL\n  );\n}\n\nasync function createSampleExtension(projectDir) {\n  console.log(\n    `Creating ${chalk.cyan('extensions/sample')} in ${chalk.green(projectDir)}`\n  );\n  // Copy the extensions folder from the package to the project directory\n  const sourceDir = path.resolve(__dirname, 'sample/extensions');\n  const targetDir = path.resolve(projectDir, 'extensions');\n  await fs.copy(sourceDir, targetDir);\n}\n\nasync function createSampleTheme(projectDir) {\n  console.log(\n    `Creating ${chalk.cyan('themes/sample')} in ${chalk.green(projectDir)}`\n  );\n  // Copy the themes folder from the package to the project directory\n  const sourceDir = path.resolve(__dirname, 'sample/themes');\n  const targetDir = path.resolve(projectDir, 'themes');\n  await fs.copy(sourceDir, targetDir);\n}\n\nfunction checkForLatestVersion() {\n  return new Promise((resolve, reject) => {\n    https\n      .get(\n        'https://registry.npmjs.org/-/package/create-evershop-app/dist-tags',\n        (res) => {\n          if (res.statusCode === 200) {\n            let body = '';\n            res.on('data', (data) => (body += data));\n            res.on('end', () => {\n              resolve(JSON.parse(body).latest);\n            });\n          } else {\n            reject();\n          }\n        }\n      )\n      .on('error', () => {\n        reject();\n      });\n  });\n}\n\nmodule.exports = {\n  init\n};\n"
  },
  {
    "path": "packages/create-evershop-app/index.js",
    "content": "#!/usr/bin/env node\n\nconst currentNodeVersion = process.versions.node;\nconst semver = currentNodeVersion.split('.');\nconst major = semver[0];\n\nif (major < 14) {\n  console.error(\n    `You are running Node ${currentNodeVersion}.\\n` +\n      `Create React App requires Node 14 or higher. \\n` +\n      `Please update your version of Node.`\n  );\n  process.exit(1);\n}\n\nconst { init } = require('./createEverShopApp');\n\ninit();\n"
  },
  {
    "path": "packages/create-evershop-app/package.json",
    "content": "{\n  \"name\": \"create-evershop-app\",\n  \"version\": \"2.3.0\",\n  \"description\": \"Create EverShop App\",\n  \"main\": \"index.js\",\n  \"files\": [\n    \"index.js\",\n    \"createEverShopApp.js\",\n    \"sample\"\n  ],\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"bin\": {\n    \"create-evershop-app\": \"./index.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/evershopcommerce/evershop.git\"\n  },\n  \"author\": \"The Nguyen\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/evershopcommerce/evershop/issues\"\n  },\n  \"homepage\": \"https://github.com/evershopcommerce/evershop#readme\",\n  \"dependencies\": {\n    \"boxen\": \"^5.1.2\",\n    \"chalk\": \"^4.1.2\",\n    \"commander\": \"^9.4.1\",\n    \"cross-spawn\": \"^7.0.3\",\n    \"fs-extra\": \"^10.0.0\",\n    \"semver\": \"^7.6.3\",\n    \"validate-npm-package-name\": \"^4.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/create-evershop-app/sample/themes/sample/dist/pages/all/EveryWhere.d.ts",
    "content": "import React from 'react';\nexport default function EveryWhere(): React.JSX.Element;\nexport declare const layout: {\n    areaId: string;\n    sortOrder: number;\n};\n"
  },
  {
    "path": "packages/create-evershop-app/sample/themes/sample/dist/pages/all/EveryWhere.js",
    "content": "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n    return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.layout = void 0;\nexports.default = EveryWhere;\nconst react_1 = __importDefault(require(\"react\"));\nfunction EveryWhere() {\n    return (react_1.default.createElement(\"div\", { className: \"container mx-auto px-4 py-8 bg-gray-100 rounded-lg shadow-md mt-10\" },\n        react_1.default.createElement(\"h1\", { className: \"font-bold text-center mb-6\" }, \"Everywhere\"),\n        react_1.default.createElement(\"p\", { className: \"text-gray-700 text-center\" }, \"This component is rendered on every page of the store front.\"),\n        react_1.default.createElement(\"p\", { className: \"text-gray-700 text-center\" },\n            \"You can modify this component at\",\n            ' ',\n            react_1.default.createElement(\"code\", null, \"`themes/sample/src/pages/all/EveryWhere.tsx`\")),\n        react_1.default.createElement(\"p\", { className: \" text-gray-700 text-center\" }, \"You can also remove this by disabling the theme `sample`.\")));\n}\nexports.layout = {\n    areaId: 'content',\n    sortOrder: 20\n};\n//# sourceMappingURL=EveryWhere.js.map"
  },
  {
    "path": "packages/create-evershop-app/sample/themes/sample/dist/pages/all/EveryWhere.js.map",
    "content": "{\"version\":3,\"file\":\"EveryWhere.js\",\"sourceRoot\":\"\",\"sources\":[\"../../../src/pages/all/EveryWhere.tsx\"],\"names\":[],\"mappings\":\";;;;;;AAEA,6BAgBC;AAlBD,kDAA0B;AAE1B,SAAwB,UAAU;IAChC,OAAO,CACL,uCAAK,SAAS,EAAC,oEAAoE;QACjF,sCAAI,SAAS,EAAC,4BAA4B,iBAAgB;QAC1D,qCAAG,SAAS,EAAC,2BAA2B,mEAEpC;QACJ,qCAAG,SAAS,EAAC,2BAA2B;;YACL,GAAG;YACpC,2FAAyD,CACvD;QACJ,qCAAG,SAAS,EAAC,4BAA4B,gEAErC,CACA,CACP,CAAC;AACJ,CAAC;AAEY,QAAA,MAAM,GAAG;IACpB,MAAM,EAAE,SAAS;IACjB,SAAS,EAAE,EAAE;CACd,CAAC\"}"
  },
  {
    "path": "packages/create-evershop-app/sample/themes/sample/dist/pages/homepage/OnlyHomePage.d.ts",
    "content": "import React from 'react';\nexport default function OnlyHomePage(): React.JSX.Element;\nexport declare const layout: {\n    areaId: string;\n    sortOrder: number;\n};\n"
  },
  {
    "path": "packages/create-evershop-app/sample/themes/sample/dist/pages/homepage/OnlyHomePage.js",
    "content": "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n    return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.layout = void 0;\nexports.default = OnlyHomePage;\nconst react_1 = __importDefault(require(\"react\"));\nfunction OnlyHomePage() {\n    return (react_1.default.createElement(\"div\", { className: \"container mx-auto px-4 py-8 bg-gray-100 rounded-lg shadow-md mt-10\" },\n        react_1.default.createElement(\"h1\", { className: \"font-bold text-center mb-6\" }, \"Home Page Only\"),\n        react_1.default.createElement(\"p\", { className: \" text-gray-700 text-center\" }, \"This component is only rendered on the home page.\"),\n        react_1.default.createElement(\"p\", { className: \" text-gray-700 text-center\" },\n            \"You can modify this component at\",\n            ' ',\n            react_1.default.createElement(\"code\", null, \"`themes/sample/src/pages/homepage/OnlyHomePage.tsx`\")),\n        react_1.default.createElement(\"p\", { className: \" text-gray-700 text-center\" }, \"You can also remove this by disabling the theme `sample`.\")));\n}\nexports.layout = {\n    areaId: 'content',\n    sortOrder: 10\n};\n//# sourceMappingURL=OnlyHomePage.js.map"
  },
  {
    "path": "packages/create-evershop-app/sample/themes/sample/dist/pages/homepage/OnlyHomePage.js.map",
    "content": "{\"version\":3,\"file\":\"OnlyHomePage.js\",\"sourceRoot\":\"\",\"sources\":[\"../../../src/pages/homepage/OnlyHomePage.tsx\"],\"names\":[],\"mappings\":\";;;;;;AAEA,+BAgBC;AAlBD,kDAA0B;AAE1B,SAAwB,YAAY;IAClC,OAAO,CACL,uCAAK,SAAS,EAAC,oEAAoE;QACjF,sCAAI,SAAS,EAAC,4BAA4B,qBAAoB;QAC9D,qCAAG,SAAS,EAAC,4BAA4B,wDAErC;QACJ,qCAAG,SAAS,EAAC,4BAA4B;;YACN,GAAG;YACpC,kGAAgE,CAC9D;QACJ,qCAAG,SAAS,EAAC,4BAA4B,gEAErC,CACA,CACP,CAAC;AACJ,CAAC;AAEY,QAAA,MAAM,GAAG;IACpB,MAAM,EAAE,SAAS;IACjB,SAAS,EAAE,EAAE;CACd,CAAC\"}"
  },
  {
    "path": "packages/create-evershop-app/sample/themes/sample/package.json",
    "content": "{\n  \"name\": \"sample-evershop-theme\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"tsc\": \"tsc\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"description\": \"\"\n}\n"
  },
  {
    "path": "packages/create-evershop-app/sample/themes/sample/src/pages/all/EveryWhere.tsx",
    "content": "import React from 'react';\n\nexport default function EveryWhere() {\n  return (\n    <div className=\"container mx-auto px-4 py-8 bg-gray-100 rounded-lg shadow-md mt-10\">\n      <h1 className=\"font-bold text-center mb-6\">Everywhere</h1>\n      <p className=\"text-gray-700 text-center\">\n        This component is rendered on every page of the store front.\n      </p>\n      <p className=\"text-gray-700 text-center\">\n        You can modify this component at{' '}\n        <code>`themes/sample/src/pages/all/EveryWhere.tsx`</code>\n      </p>\n      <p className=\" text-gray-700 text-center\">\n        You can also remove this by disabling the theme `sample`.\n      </p>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 20\n};\n"
  },
  {
    "path": "packages/create-evershop-app/sample/themes/sample/src/pages/homepage/OnlyHomePage.tsx",
    "content": "import React from 'react';\n\nexport default function OnlyHomePage() {\n  return (\n    <div className=\"container mx-auto px-4 py-8 bg-gray-100 rounded-lg shadow-md mt-10\">\n      <h1 className=\"font-bold text-center mb-6\">Home Page Only</h1>\n      <p className=\" text-gray-700 text-center\">\n        This component is only rendered on the home page.\n      </p>\n      <p className=\" text-gray-700 text-center\">\n        You can modify this component at{' '}\n        <code>`themes/sample/src/pages/homepage/OnlyHomePage.tsx`</code>\n      </p>\n      <p className=\" text-gray-700 text-center\">\n        You can also remove this by disabling the theme `sample`.\n      </p>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/create-evershop-app/sample/themes/sample/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"NodeNext\",\n    \"target\": \"ES2018\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"sourceMap\": true,\n    \"allowJs\": true,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"outDir\": \"./dist\",\n    \"resolveJsonModule\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"allowArbitraryExtensions\": true,\n    \"strictNullChecks\": true,\n    \"isolatedModules\": false,\n    \"baseUrl\": \".\",\n    \"rootDir\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/evershop/.swcrc",
    "content": "{\n  \"$schema\": \"https://swc.rs/schema.json\",\n  \"jsc\": {\n    \"parser\": {\n      \"syntax\": \"typescript\",\n      \"tsx\": true,\n      \"dynamicImport\": true,\n      \"privateMethod\": false,\n      \"functionBind\": true,\n      \"exportDefaultFrom\": true,\n      \"exportNamespaceFrom\": false,\n      \"decorators\": true,\n      \"decoratorsBeforeExport\": false,\n      \"topLevelAwait\": true,\n      \"importMeta\": true,\n      \"importAs\": true,\n      \"preserveAllComments\": false\n    },\n    \"target\": \"es2022\",\n    \"experimental\": { \"keepImportAssertions\": true },\n    \"loose\": false,\n    \"keepClassNames\": false\n  },\n  \"module\": {\n    \"type\": \"es6\"\n  }\n}\n"
  },
  {
    "path": "packages/evershop/README.md",
    "content": "<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>\n<p align=\"center\">\n<img width=\"60\" height=\"68\" alt=\"EverShop Logo\" src=\"https://raw.githubusercontent.com/evershopcommerce/evershop/dev/.github/images/logo-green.png\"/>\n</p>\n<p align=\"center\">\n  <h1 align=\"center\">EverShop</h1>\n</p>\n<h4 align=\"center\">\n    <a href=\"https://evershop.io/docs/development/getting-started/introduction\">Documentation</a> |\n    <a href=\"https://demo.evershop.io/\">Demo</a>\n</h4>\n\n<p align=\"center\">\n  <img src=\"https://github.com/evershopcommerce/evershop/actions/workflows/build_test.yml/badge.svg\" alt=\"Github Action\">\n  <a href=\"https://twitter.com/evershopjs\">\n    <img alt=\"Twitter Follow\" src=\"https://img.shields.io/twitter/follow/evershopjs?style=social\">\n  </a>\n  <a href=\"https://discord.gg/GSzt7dt7RM\">\n    <img src=\"https://img.shields.io/discord/757179260417867879?label=discord\" alt=\"Discord\">\n  </a>\n  <a href=\"https://opensource.org/licenses/GPL-3.0\">\n    <img src=\"https://img.shields.io/badge/License-GPLv3-blue.svg\" alt=\"License\">\n  </a>\n</p>\n\n<p align=\"center\">\n<img alt=\"EverShop\" width=\"950\" src=\"https://raw.githubusercontent.com/evershopcommerce/evershop/dev/.github/images/banner.png\"/>\n</p>\n\n## Introduction\n\nEverShop is a modern, TypeScript-first eCommerce platform built with GraphQL and React. Designed for developers, it offers essential commerce features in a modular, fully customizable architecture—perfect for building tailored shopping experiences with confidence and speed.\n\n## Installation Using Docker\n\nYou can get started with EverShop in minutes by using the Docker image. The Docker image is a great way to get started with EverShop without having to worry about installing dependencies or configuring your environment.\n\n```bash\ncurl -sSL https://raw.githubusercontent.com/evershopcommerce/evershop/main/docker-compose.yml > docker-compose.yml\ndocker-compose up -d\n```\n\nFor the full installation guide, please refer to our [Installation guide](https://evershop.io/docs/development/getting-started/installation-guide).\n\n## Documentation\n\n- [Installation guide](https://evershop.io/docs/development/getting-started/installation-guide).\n\n- [Extension development](https://evershop.io/docs/development/module/create-your-first-extension).\n\n- [Theme development](https://evershop.io/docs/development/theme/theme-overview).\n\n## Demo\n\nExplore our demo store.\n\n<p align=\"left\">\n  <a href=\"https://demo.evershop.io/admin\" target=\"_blank\">\n    <img alt=\"evershop-backend-demo\" height=\"35\" alt=\"EverShop Admin Demo\" src=\"https://raw.githubusercontent.com/evershopcommerce/evershop/dev/.github/images/evershop-demo-back.png\"/>\n  </a>\n  <a href=\"https://demo.evershop.io/\" target=\"_blank\">\n    <img alt=\"evershop-store-demo\" height=\"35\" alt=\"EverShop Store Demo\" src=\"https://raw.githubusercontent.com/evershopcommerce/evershop/dev/.github/images/evershop-demo-front.png\"/>\n  </a>\n</p>\n<b>Demo user:</b>\n\nEmail: demo@evershop.io<br/>\nPassword: 123456\n\n## Support\n\nIf you like my work, feel free to:\n\n- ⭐ this repository. It helps.\n- [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)][tweet] about EverShop. Thank you!\n\n[tweet]: https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Fevershopcommerce%2Fevershop&text=Awesome%20React%20Ecommerce%20Project&hashtags=react,ecommerce,expressjs,graphql\n\n## Contributing\n\nEverShop is an open-source project. We are committed to a fully transparent development process and appreciate highly any contributions. Whether you are helping us fix bugs, proposing new features, improving our documentation or spreading the word - we would love to have you as part of the EverShop community.\n\n### Ask a question about EverShop\n\nYou can ask questions, and participate in discussions about EverShop-related topics in the EverShop Discord channel.\n\n<a href=\"https://discord.gg/GSzt7dt7RM\"><img src=\"https://raw.githubusercontent.com/evershopcommerce/evershop/dev/.github/images/discord_banner_github.svg\" /></a>\n\n### Create a bug report\n\nIf you see an error message or run into an issue, please [create bug report](https://github.com/evershopcommerce/evershop/issues/new). This effort is valued and it will help all EverShop users.\n\n### Submit a feature request\n\nIf you have an idea, or you're missing a capability that would make development easier and more robust, please [Submit feature request](https://github.com/evershopcommerce/evershop/issues/new).\n\nIf a similar feature request already exists, don't forget to leave a \"+1\".\nIf you add some more information such as your thoughts and vision about the feature, your comments will be embraced warmly :)\n\nPlease refer to our [Contribution Guidelines](./CONTRIBUTING.md) and [Code of Conduct](./CODE_OF_CONDUCT.md).\n\n## License\n\n[GPL-3.0 License](https://github.com/evershopcommerce/evershop/blob/main/LICENSE)\n"
  },
  {
    "path": "packages/evershop/package.json",
    "content": "{\n  \"name\": \"@evershop/evershop\",\n  \"version\": \"2.1.1\",\n  \"type\": \"module\",\n  \"description\": \"The React Ecommerce platform. Built with Typescript, React and Postgres. Open-source and free. Fast and customizable.\",\n  \"files\": [\n    \"dist\",\n    \"src\",\n    \".swcrc\"\n  ],\n  \"bin\": {\n    \"evershop\": \"./dist/bin/evershop.js\"\n  },\n  \"exports\": {\n    \"./types/*\": {\n      \"types\": \"./dist/types/*.d.ts\"\n    },\n    \"./lib/helpers\": {\n      \"import\": \"./dist/lib/helpers.js\",\n      \"types\": \"./dist/lib/helpers.d.ts\"\n    },\n    \"./lib/mail/*\": {\n      \"import\": \"./dist/lib/mail/*.js\",\n      \"types\": \"./dist/lib/mail/*.d.ts\"\n    },\n    \"./lib/util/*\": {\n      \"import\": \"./dist/lib/util/*.js\",\n      \"types\": \"./dist/lib/util/*.d.ts\"\n    },\n    \"./lib/event\": {\n      \"import\": \"./dist/lib/event/emitter.js\",\n      \"types\": \"./dist/lib/event/emitter.d.ts\"\n    },\n    \"./lib/event/subscriber\": {\n      \"import\": \"./dist/lib/event/subscriber.js\",\n      \"types\": \"./dist/lib/event/subscriber.d.ts\"\n    },\n    \"./lib/postgres\": {\n      \"import\": \"./dist/lib/postgres/connection.js\",\n      \"types\": \"./dist/lib/postgres/connection.d.ts\"\n    },\n    \"./lib/locale/*\": {\n      \"import\": \"./dist/lib/locale/*.js\",\n      \"types\": \"./dist/lib/locale/*.d.ts\"\n    },\n    \"./lib/log\": {\n      \"import\": \"./dist/lib/log/logger.js\",\n      \"types\": \"./dist/lib/log/logger.d.ts\"\n    },\n    \"./lib/router\": {\n      \"import\": \"./dist/lib/router/index.js\",\n      \"types\": \"./dist/lib/router/index.d.ts\"\n    },\n    \"./lib/widget\": {\n      \"import\": \"./dist/lib/widget/widgetManager.js\",\n      \"types\": \"./dist/lib/widget/widgetManager.d.ts\"\n    },\n    \"./lib/cronjob\": {\n      \"import\": \"./dist/lib/cronjob/jobManager.js\",\n      \"types\": \"./dist/lib/cronjob/jobManager.d.ts\"\n    },\n    \"./lib/middleware/delegate\": {\n      \"import\": \"./dist/lib/middleware/delegate.js\",\n      \"types\": \"./dist/lib/middleware/delegate.d.ts\"\n    },\n    \"./components/common/*\": {\n      \"import\": \"./dist/components/common/*.js\",\n      \"types\": \"./dist/components/common/*.d.ts\"\n    },\n    \"./components/admin/*\": {\n      \"import\": \"./dist/components/admin/*.js\",\n      \"types\": \"./dist/components/admin/*.d.ts\"\n    },\n    \"./components/frontStore/*\": {\n      \"import\": \"./dist/components/frontStore/*.js\",\n      \"types\": \"./dist/components/frontStore/*.d.ts\"\n    },\n    \"./graphql/services\": {\n      \"import\": \"./dist/modules/graphql/services/index.js\",\n      \"types\": \"./dist/modules/graphql/services/index.d.ts\"\n    },\n    \"./catalog/services\": {\n      \"import\": \"./dist/modules/catalog/services/index.js\",\n      \"types\": \"./dist/modules/catalog/services/index.d.ts\"\n    },\n    \"./customer/services\": {\n      \"import\": \"./dist/modules/customer/services/index.js\",\n      \"types\": \"./dist/modules/customer/services/index.d.ts\"\n    },\n    \"./setting/services\": {\n      \"import\": \"./dist/modules/setting/services/index.js\",\n      \"types\": \"./dist/modules/setting/services/index.d.ts\"\n    },\n    \"./checkout/services\": {\n      \"import\": \"./dist/modules/checkout/services/index.js\",\n      \"types\": \"./dist/modules/checkout/services/index.d.ts\"\n    },\n    \"./oms/services\": {\n      \"import\": \"./dist/modules/oms/services/index.js\",\n      \"types\": \"./dist/modules/oms/services/index.d.ts\"\n    },\n    \"./cms/services\": {\n      \"import\": \"./dist/modules/cms/services/index.js\",\n      \"types\": \"./dist/modules/cms/services/index.d.ts\"\n    },\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"prepack\": \"rimraf dist && tsc && copyfiles -u 1 \\\"src/**/*.{graphql,scss,css,json}\\\" dist\",\n    \"dev\": \"node ./dist/bin/dev/index.js\",\n    \"start\": \"node ./dist/bin/start/index.js\",\n    \"build\": \"node ./dist/bin/build/index.js\",\n    \"build-fast\": \"evershop build -- --skip-minify\",\n    \"user:create\": \"evershop user:create\",\n    \"user:changePassword\": \"evershop user:changePassword\",\n    \"test\": \"jest\"\n  },\n  \"author\": \"The Nguyen (https://evershop.io)\",\n  \"license\": \"GNU GENERAL PUBLIC LICENSE 3.0\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/evershopcommerce/evershop.git\"\n  },\n  \"keywords\": [\n    \"ecommerce\",\n    \"shopping cart\",\n    \"cart\"\n  ],\n  \"bugs\": {\n    \"url\": \"https://github.com/evershopcommerce/evershop/issues\"\n  },\n  \"homepage\": \"http://evershop.io/\",\n  \"dependencies\": {\n    \"@base-ui/react\": \"^1.1.0\",\n    \"@ckeditor/ckeditor5-build-classic\": \"^36.0.1\",\n    \"@ckeditor/ckeditor5-react\": \"^5.1.0\",\n    \"@dnd-kit/core\": \"^6.3.1\",\n    \"@dnd-kit/sortable\": \"^10.0.0\",\n    \"@editorjs/editorjs\": \"^2.30.8\",\n    \"@editorjs/header\": \"^2.8.7\",\n    \"@editorjs/list\": \"^1.10.0\",\n    \"@editorjs/quote\": \"^2.6.0\",\n    \"@editorjs/raw\": \"^2.5.0\",\n    \"@evershop/editorjs-image\": \"^1.1.0\",\n    \"@evershop/postgres-query-builder\": \"^2.0.1\",\n    \"@graphql-tools/load-files\": \"^6.6.1\",\n    \"@graphql-tools/merge\": \"^8.4.2\",\n    \"@graphql-tools/schema\": \"^9.0.19\",\n    \"@hapi/topo\": \"^5.0.0\",\n    \"@pmmmwh/react-refresh-webpack-plugin\": \"^0.6.0\",\n    \"@stripe/react-stripe-js\": \"^1.5.0\",\n    \"@stripe/stripe-js\": \"^1.18.0\",\n    \"@swc/cli\": \"^0.7.7\",\n    \"@swc/core\": \"^1.11.29\",\n    \"@tailwindcss/postcss\": \"^4.1.18\",\n    \"@tailwindcss/typography\": \"^0.5.13\",\n    \"ajv\": \"^8.12.0\",\n    \"ajv-errors\": \"^3.0.0\",\n    \"ajv-formats\": \"^2.1.1\",\n    \"autoprefixer\": \"^10.4.13\",\n    \"axios\": \"^1.13.2\",\n    \"bcryptjs\": \"^2.4.3\",\n    \"body-parser\": \"^1.20.0\",\n    \"boxen\": \"^5.1.2\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clean-css\": \"^5.3.1\",\n    \"clsx\": \"^2.1.1\",\n    \"config\": \"^3.3.6\",\n    \"connect-pg-simple\": \"^9.0.0\",\n    \"cookie-parser\": \"^1.4.6\",\n    \"cross-spawn\": \"^7.0.6\",\n    \"css-loader\": \"^6.7.1\",\n    \"csv-parser\": \"^3.0.0\",\n    \"dayjs\": \"^1.10.6\",\n    \"debug\": \"^4.3.2\",\n    \"dotenv\": \"^16.3.1\",\n    \"enquirer\": \"^2.3.6\",\n    \"execa\": \"^9.6.0\",\n    \"express\": \"^4.21.2\",\n    \"express-session\": \"^1.17.3\",\n    \"fast-glob\": \"^3.3.3\",\n    \"flatpickr\": \"^4.6.9\",\n    \"graphql\": \"^16.6.0\",\n    \"graphql-tag\": \"^2.12.6\",\n    \"graphql-type-json\": \"^0.3.2\",\n    \"handlebars\": \"^4.7.8\",\n    \"html-entities\": \"^2.3.3\",\n    \"html-webpack-plugin\": \"^5.5.0\",\n    \"immer\": \"^10.1.1\",\n    \"jsesc\": \"^3.0.2\",\n    \"json5\": \"^2.2.1\",\n    \"jsonwebtoken\": \"^9.0.2\",\n    \"kleur\": \"3.0.3\",\n    \"lodash.isequalwith\": \"^4.4.0\",\n    \"lucide-react\": \"^0.562.0\",\n    \"luxon\": \"^2.0.2\",\n    \"mini-css-extract-plugin\": \"^2.6.1\",\n    \"minimatch\": \"^10.2.3\",\n    \"multer\": \"^2.1.1\",\n    \"node-cron\": \"^3.0.3\",\n    \"ora\": \"^5.4.1\",\n    \"pg\": \"^8.16.3\",\n    \"postcss\": \"^8.4.18\",\n    \"postcss-loader\": \"^8.2.0\",\n    \"prop-types\": \"^15.8.1\",\n    \"react\": \"^17.0.1\",\n    \"react-dom\": \"^17.0.1\",\n    \"react-fast-compare\": \"^3.2.0\",\n    \"react-hook-form\": \"^7.61.1\",\n    \"react-refresh\": \"^0.14.0\",\n    \"react-select\": \"^5.4.0\",\n    \"react-slick\": \"^0.31.0\",\n    \"react-toastify\": \"^6.2.0\",\n    \"recharts\": \"^2.0.9\",\n    \"sanitize-html\": \"^2.17.0\",\n    \"sass\": \"^1.53.0\",\n    \"sass-loader\": \"^13.0.2\",\n    \"semver\": \"^7.6.3\",\n    \"serve-static\": \"^1.15.0\",\n    \"session-file-store\": \"^1.5.0\",\n    \"sharp\": \"^0.33.5\",\n    \"slick-carousel\": \"^1.8.1\",\n    \"stripe\": \"^8.176.0\",\n    \"style-loader\": \"^3.3.1\",\n    \"swc-minify-webpack-plugin\": \"^2.1.3\",\n    \"tailwind-merge\": \"^3.4.0\",\n    \"tailwindcss\": \"^4.1.18\",\n    \"touch\": \"^3.1.1\",\n    \"tw-animate-css\": \"^1.4.0\",\n    \"uniqid\": \"^5.3.0\",\n    \"urql\": \"^3.0.3\",\n    \"uuid\": \"^9.0.0\",\n    \"webpack\": \"^5.72.1\",\n    \"webpack-dev-middleware\": \"^7.4.2\",\n    \"webpack-hot-middleware\": \"^2.26.1\",\n    \"webpackbar\": \"^5.0.2\",\n    \"winston\": \"^3.3.3\",\n    \"yargs\": \"^17.7.2\",\n    \"zero-decimal-currencies\": \"^1.2.0\"\n  },\n  \"devDependencies\": {\n    \"@parcel/watcher\": \"^2.5.1\",\n    \"@paypal/paypal-js\": \"^8.4.2\",\n    \"@types/config\": \"^3.3.5\",\n    \"@types/express\": \"^5.0.1\",\n    \"@types/express-session\": \"^1.18.2\",\n    \"@types/multer\": \"^2.0.0\",\n    \"@types/node\": \"^22.14.1\",\n    \"@types/pg\": \"^8.15.2\",\n    \"@types/react\": \"^19.1.2\",\n    \"@types/sanitize-html\": \"^2.16.0\",\n    \"copyfiles\": \"^2.4.1\",\n    \"typescript\": \"^5.8.3\"\n  }\n}\n"
  },
  {
    "path": "packages/evershop/scripts/postpack.js",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport packageJson from '../package.json' with { type: 'json' };\n// Get the current version of the package from the nearest package.json file\nconst { version } = packageJson;\n// Get the --pack-destination from the command line arguments\n// Create a package.json file in the packDestination directory with dependencies is the package itself\nfs.writeFileSync(\n  path.resolve(process.env.npm_config_pack_destination, 'package.json'),\n  JSON.stringify(\n    {\n      name: packageJson.name,\n      version,\n      dependencies: {\n        '@evershop/evershop': `file:./evershop-evershop-${version}.tgz`\n      },\n      scripts: {\n        setup: 'evershop install',\n        start: 'evershop start',\n        'start:debug': 'evershop start:debug',\n        build: 'evershop build',\n        dev: 'evershop dev',\n        'user:create': 'evershop user:create'\n      }\n    },\n    null,\n    2\n  )\n);\n"
  },
  {
    "path": "packages/evershop/scripts/postpublish.js",
    "content": "import fs from 'fs';\nimport path from 'path';\n\nfunction getFileRecursive(dir, files) {\n  const list = fs.readdirSync(dir);\n  list.forEach((file) => {\n    const filePath = path.join(dir, file);\n    const stat = fs.statSync(filePath);\n    if (stat.isDirectory()) {\n      getFileRecursive(filePath, files);\n    } else {\n      files.push(filePath);\n    }\n  });\n}\n\nconst files = [];\n\ngetFileRecursive(path.resolve(__dirname, './bin/serve'), files);\n\nfiles.forEach((file) => {\n  const source = fs.readFileSync(file, { encoding: 'utf8', flag: 'r' });\n  const result = source.replace(/\\.\\.\\/dist/g, '../src');\n  fs.writeFileSync(file, result, 'utf8');\n});\n"
  },
  {
    "path": "packages/evershop/scripts/prepublish.js",
    "content": "import fs from 'fs';\nimport path from 'path';\n\nfs.copyFile(\n  path.resolve(__dirname, '../../README.md'),\n  path.resolve(__dirname, './README.md'),\n  (err) => {\n    if (err) throw err;\n  }\n);\n"
  },
  {
    "path": "packages/evershop/src/bin/build/client/index.js",
    "content": "import webpack from 'webpack';\nimport { error } from '../../../src/lib/log/logger';\nimport { createConfigClient } from '../../../src/lib/webpack/prod/createConfigClient';\n\nexport async function buildClient(routes) {\n  const config = createConfigClient(routes);\n  const compiler = webpack(config);\n\n  return new Promise((resolve, reject) => {\n    compiler.run((err, stats) => {\n      if (err || stats.hasErrors()) {\n        error(\n          stats.toString({\n            errorDetails: true,\n            warnings: true\n          })\n        );\n        reject(err);\n      }\n      resolve(stats);\n    });\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/build/complie.js",
    "content": "import pkg from 'webpack';\nimport { error } from '../../lib/log/logger.js';\nimport { createConfigClient } from '../../lib/webpack/prod/createConfigClient.js';\nimport { createConfigServer } from '../../lib/webpack/prod/createConfigServer.js';\n\nconst { webpack } = pkg;\nexport async function compile(routes) {\n  const config = [createConfigClient(routes), createConfigServer(routes)];\n  const compiler = webpack(config);\n  return new Promise((resolve, reject) => {\n    compiler.run((err, stats) => {\n      if (err || stats.hasErrors()) {\n        if (err) {\n          error(err);\n        }\n        error(\n          stats.toString({\n            errorDetails: true,\n            warnings: true\n          })\n        );\n        reject(err);\n      }\n      resolve(stats);\n    });\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/build/index.js",
    "content": "import { existsSync, mkdirSync, rmSync } from 'fs';\nimport path from 'path';\nimport config from 'config';\nimport { CONSTANTS } from '../../lib/helpers.js';\nimport { error } from '../../lib/log/logger.js';\nimport { loadModuleRoutes } from '../../lib/router/loadModuleRoutes.js';\nimport { getRoutes } from '../../lib/router/Router.js';\nimport { lockHooks } from '../../lib/util/hookable.js';\nimport { lockRegistry } from '../../lib/util/registry.js';\nimport { validateConfiguration } from '../../lib/util/validateConfiguration.js';\nimport { isBuildRequired } from '../../lib/webpack/isBuildRequired.js';\nimport { getEnabledExtensions } from '../extension/index.js';\nimport { loadBootstrapScript } from '../lib/bootstrap/bootstrap.js';\nimport { buildEntry } from '../lib/buildEntry.js';\nimport { getCoreModules } from '../lib/loadModules.js';\nimport { compile } from './complie.js';\nimport './initEnvBuild.js';\n\n/* Loading modules and initilize routes, components */\nconst modules = [...getCoreModules(), ...getEnabledExtensions()];\n\n/** Loading routes  */\nmodules.forEach((module) => {\n  try {\n    // Load routes\n    loadModuleRoutes(module.path);\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n});\n\n/** Clean up the build directory */\nif (existsSync(path.resolve(CONSTANTS.BUILDPATH))) {\n  // Delete directory recursively\n  rmSync(path.resolve(CONSTANTS.BUILDPATH), { recursive: true });\n  mkdirSync(path.resolve(CONSTANTS.BUILDPATH));\n} else {\n  mkdirSync(path.resolve(CONSTANTS.BUILDPATH), { recursive: true });\n}\nexport default async function build() {\n  /** Loading bootstrap script from modules */\n  try {\n    for (const module of modules) {\n      await loadBootstrapScript(module, {\n        command: 'build',\n        env: 'production',\n        process: 'main'\n      });\n    }\n    lockHooks();\n    lockRegistry();\n    // Get the configuration (nodeconfig)\n    validateConfiguration(config);\n  } catch (e) {\n    error(e);\n    process.exit(1);\n  }\n  process.env.ALLOW_CONFIG_MUTATIONS = false;\n\n  const routes = getRoutes();\n  await buildEntry(routes.filter((r) => isBuildRequired(r)));\n\n  /** Build  */\n  await compile(routes);\n}\n\nprocess.on('uncaughtException', function (exception) {\n  import('../../lib/log/logger.js').then((module) => {\n    module.error(exception);\n  });\n});\nprocess.on('unhandledRejection', (reason, p) => {\n  import('../../lib/log/logger.js').then((module) => {\n    module.error(`Unhandled Rejection: ${reason} at: ${p}`);\n  });\n});\n\nbuild();\n"
  },
  {
    "path": "packages/evershop/src/bin/build/initEnvBuild.ts",
    "content": "import 'dotenv/config';\nprocess.env.NODE_ENV = 'production';\nprocess.env.ALLOW_CONFIG_MUTATIONS = 'true';\n"
  },
  {
    "path": "packages/evershop/src/bin/build/server/index.js",
    "content": "import pkg from 'webpack';\nimport { error } from '../../../src/lib/log/logger.js';\nimport { createConfigServer } from '../../../src/lib/webpack/prod/createConfigServer.js';\n\nconst { webpack } = pkg;\nexport const buildServer = async function buildServer(routes) {\n  const config = createConfigServer(routes);\n  const compiler = webpack(config);\n\n  return new Promise((resolve, reject) => {\n    compiler.run((err, stats) => {\n      if (err || stats.hasErrors()) {\n        error(\n          stats.toString({\n            errorDetails: true,\n            warnings: true\n          })\n        );\n        reject(err);\n      }\n      resolve(stats);\n    });\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/bin/build/server/useDDL.js",
    "content": "import { existsSync, rmdirSync } from 'fs';\nimport { mkdir, writeFile } from 'fs/promises';\nimport path from 'path';\nimport { inspect } from 'util';\nimport boxen from 'boxen';\nimport { green, red } from 'kleur';\nimport ora from 'ora';\nimport pkg from 'webpack';\nimport { getComponentsByRoute } from '../../../src/lib/componee/getComponentByRoute.js';\nimport { CONSTANTS } from '../../../src/lib/helpers.js';\nimport { info } from '../../../src/lib/log/logger.js';\nimport { getRoutes } from '../../../src/lib/router/routes.js';\n// Run building vendor first\nimport { createVendorConfig } from '../../../src/lib/webpack/configProvider.js';\nimport { loadModuleComponents } from '../../serve/loadModuleComponents.js';\nimport { loadModuleRoutes } from '../../serve/loadModuleRoutes.js';\nimport { loadModules } from '../../serve/loadModules.js';\n\nconst { webpack } = pkg;\nconst modules = loadModules(path.resolve(__dirname, '../../../src', 'modules'));\n\nconst spinner = ora({\n  text: green('Starting server build'),\n  spinner: 'dots12'\n}).start();\nspinner.start();\n\n// Initilizing routes\nmodules.forEach((module) => {\n  try {\n    // Load routes\n    loadModuleRoutes(module.path);\n  } catch (e) {\n    spinner.fail(`${red(e.stack)}\\n`);\n    process.exit(0);\n  }\n});\n\n// Initializing components\nmodules.forEach((module) => {\n  try {\n    // Load components\n    loadModuleComponents(module.path);\n  } catch (e) {\n    spinner.fail(`${red(e.stack)}\\n`);\n    process.exit(0);\n  }\n});\n\nconst routes = getRoutes();\n\n// Collect all \"controller\" route\nconst controllers = routes.filter((r) => r.isApi === false);\n\nconst promises = [];\nconst total = controllers.length - 1;\nlet completed = 0;\n\nspinner.text = `Start building ☕☕☕☕☕\\n${Array(total).fill('▒').join('')}`;\n\nif (existsSync(path.resolve(CONSTANTS.ROOTPATH, './.evershop/build'))) {\n  rmdirSync(path.resolve(CONSTANTS.ROOTPATH, './.evershop/build'), {\n    recursive: true\n  });\n}\nconst start = Date.now();\n\nconst vendorComplier = webpack(createVendorConfig(webpack));\nconst webpackVendorPromise = new Promise((resolve, reject) => {\n  vendorComplier.run((err, stats) => {\n    if (err) {\n      reject(err);\n    } else if (stats.hasErrors()) {\n      reject(\n        new Error(\n          stats.toString({\n            errorDetails: true,\n            warnings: true\n          })\n        )\n      );\n    } else {\n      resolve(stats);\n    }\n  });\n});\n\nwebpackVendorPromise.then(async () => {\n  controllers.forEach((route) => {\n    const buildFunc = async function () {\n      const components = getComponentsByRoute(route.id);\n\n      if (!components) {\n        return;\n      }\n      Object.keys(components).forEach((area) => {\n        Object.keys(components[area]).forEach((id) => {\n          components[area][\n            id\n          ].component = `---require(\"${components[area][id].source}\")---`;\n          delete components[area][id].source;\n        });\n      });\n\n      const buildPath =\n        route.isAdmin === true\n          ? `./admin/${route.id}`\n          : `./frontStore/${route.id}`;\n      let content = `var components = module.exports = exports = ${inspect(\n        components,\n        { depth: 5 }\n      )\n        .replace(/'---/g, '')\n        .replace(/---'/g, '')}`;\n      content += '\\r\\n';\n      await mkdir(\n        path.resolve(CONSTANTS.ROOTPATH, './.evershop/build', buildPath),\n        { recursive: true }\n      );\n      await writeFile(\n        path.resolve(\n          CONSTANTS.ROOTPATH,\n          '.evershop/build',\n          buildPath,\n          'components.js'\n        ),\n        content\n      );\n      const name =\n        route.isAdmin === true ? `admin/${route.id}` : `frontStore/${route.id}`;\n      const entry = {};\n      entry[name] = [\n        path.resolve(\n          CONSTANTS.ROOTPATH,\n          '.evershop',\n          'build',\n          buildPath,\n          'components.js'\n        ),\n        path.resolve(\n          CONSTANTS.LIBPATH,\n          '../components/common/react/server',\n          'render.js'\n        )\n      ];\n      const compiler = webpack({\n        mode: 'production', // \"production\" | \"development\" | \"none\"\n        module: {\n          rules: [\n            {\n              test: /\\/views|components|context\\/(.*).js?$/,\n              // test: /\\.js?$/,\n              exclude: /(bower_components)/,\n              use: {\n                loader: 'babel-loader?cacheDirectory',\n                options: {\n                  sourceType: 'unambiguous',\n                  cacheDirectory: true,\n                  presets: [\n                    [\n                      '@babel/preset-env',\n                      {\n                        exclude: [\n                          '@babel/plugin-transform-regenerator',\n                          '@babel/plugin-transform-async-to-generator'\n                        ]\n                      }\n                    ],\n                    '@babel/preset-react'\n                  ]\n                }\n              }\n            },\n            {\n              test: /getComponents\\.js/,\n              use: [\n                {\n                  loader: path.resolve(\n                    CONSTANTS.LIBPATH,\n                    'webpack/getComponentLoader.js'\n                  ),\n                  options: {\n                    componentsPath: path.resolve(\n                      CONSTANTS.ROOTPATH,\n                      './.evershop/build',\n                      buildPath,\n                      'components.js'\n                    )\n                  }\n                }\n              ]\n            }\n          ]\n        },\n        // name: 'main',\n        target: 'node12.18',\n        entry,\n        output: {\n          path: path.resolve(\n            CONSTANTS.ROOTPATH,\n            './.evershop/build',\n            buildPath,\n            'server'\n          ),\n          libraryTarget: 'commonjs2',\n          globalObject: 'this',\n          filename: 'index.js'\n        },\n        resolve: {\n          alias: {\n            react: path.resolve(CONSTANTS.NODEMODULEPATH, 'react')\n          }\n        },\n        plugins: [\n          new webpack.DllReferencePlugin({\n            manifest: path.resolve(\n              CONSTANTS.ROOTPATH,\n              './.evershop/build/vendor-manifest.json'\n            )\n          })\n        ]\n      });\n\n      const webpackPromise = new Promise((resolve, reject) => {\n        compiler.run((err, stats) => {\n          if (err) {\n            reject(err);\n          } else if (stats.hasErrors()) {\n            reject(\n              new Error(\n                stats.toString({\n                  errorDetails: true,\n                  warnings: true\n                })\n              )\n            );\n          } else {\n            resolve(stats);\n          }\n        });\n      });\n\n      await webpackPromise;\n      completed += 1;\n      spinner.text = `Start building ☕☕☕☕☕\\n${Array(completed)\n        .fill(green('█'))\n        .concat(total - completed > 0 ? Array(total - completed).fill('▒') : [])\n        .join('')}`;\n    };\n    promises.push(buildFunc());\n  });\n\n  await Promise.all(promises)\n    .then(() => {\n      spinner.succeed(\n        green('Building completed!!!\\n') +\n          boxen(green('Please run \"npm run start\" to start your website'), {\n            title: 'EverShop',\n            titleAlignment: 'center',\n            padding: 1,\n            margin: 1,\n            borderColor: 'green'\n          })\n      );\n      const end = Date.now();\n      info(`Execution time: ${end - start} ms`);\n\n      process.exit(0);\n    })\n    .catch((e) => {\n      spinner.fail(`${red(e)}\\n`);\n      process.exit(0);\n    });\n});\n"
  },
  {
    "path": "packages/evershop/src/bin/build/server/useVendorChunk.js",
    "content": "import { existsSync, rmSync } from 'fs';\nimport path from 'path';\nimport { green, red } from 'kleur';\nimport ora from 'ora';\nimport webpack from 'webpack';\nimport { CONSTANTS } from '../../../src/lib/helpers.js';\nimport { getRoutes } from '../../../src/lib/router/routes.js';\nimport { createConfig } from '../../../src/lib/webpack/createConfig.js';\nimport { loadModuleComponents } from '../../serve/loadModuleComponents.js';\nimport { loadModuleRoutes } from '../../serve/loadModuleRoutes.js';\nimport { loadModules } from '../../serve/loadModules.js';\nimport { createComponents } from '../createComponents.js';\n\n(async () => {\n  const start = Date.now();\n  const modules = loadModules(\n    path.resolve(__dirname, '../../../src', 'modules')\n  );\n  const spinner = ora({\n    text: green('Starting server build'),\n    spinner: 'dots12'\n  }).start();\n  spinner.start();\n\n  /** Initilizing routes */\n  modules.forEach((module) => {\n    try {\n      // Load routes\n      loadModuleRoutes(module.path);\n    } catch (e) {\n      spinner.fail(`${red(e.stack)}\\n`);\n      process.exit(0);\n    }\n  });\n\n  /** Initializing components */\n  modules.forEach((module) => {\n    try {\n      // Load components\n      loadModuleComponents(module.path);\n    } catch (e) {\n      spinner.fail(`${red(e.stack)}\\n`);\n      process.exit(0);\n    }\n  });\n\n  /** Get list of routes */\n  const routes = getRoutes();\n\n  /** Collect all 'controller' routes */\n  const controllers = routes.filter((r) => r.isApi === false);\n\n  /** Clean up the build directory */\n  if (existsSync(CONSTANTS.BUILDPATH)) {\n    rmSync(CONSTANTS.BUILDPATH, { recursive: true });\n  }\n\n  /** Create components.js file for each route */\n  await createComponents(controllers);\n\n  /** Create the webpack complier object */\n  const compiler = webpack(createConfig(true, controllers));\n\n  /** Run the build */\n  await new Promise((resolve, reject) => {\n    compiler.run((err, stats) => {\n      if (err) {\n        reject(err);\n      } else if (stats.hasErrors()) {\n        reject(\n          new Error(\n            stats.toString({\n              errorDetails: true,\n              warnings: true\n            })\n          )\n        );\n      } else {\n        resolve(stats);\n      }\n    });\n  });\n\n  const end = Date.now();\n  spinner.succeed(`${green('Server build completed in')} ${end - start}ms`);\n  process.exit(0);\n})();\n"
  },
  {
    "path": "packages/evershop/src/bin/dev/compileTs.js",
    "content": "import path from 'path';\nimport { compileSwc } from '../lib/watch/compileSwc.js';\nimport { getSrcPaths } from '../lib/watch/getSrcPaths.js';\n\nasync function compileTs() {\n  const srcPaths = getSrcPaths();\n  const events = srcPaths.map((srcPath) => {\n    return {\n      srcPath: srcPath,\n      distPath: path.resolve(srcPath, '..', 'dist')\n    };\n  });\n  await Promise.all(\n    events.map((event) => {\n      return compileSwc(event.srcPath, event.distPath);\n    })\n  );\n}\n\nexport { compileTs };\n"
  },
  {
    "path": "packages/evershop/src/bin/dev/enableWatcher.js",
    "content": "import { subscribe } from '@parcel/watcher';\nimport { CONSTANTS } from '../../lib/helpers.js';\nimport { watchHandler } from '../lib/watch/watchHandler.js';\n\nexport default async function enableWatcher() {\n  const watcherInstance = await subscribe(\n    CONSTANTS.ROOTPATH,\n    (err, events) => {\n      if (err) {\n        return;\n      }\n      watchHandler(events);\n    },\n    {\n      ignore: [\n        '**/node_modules/**',\n        '**/dist/**',\n        '**/build/**',\n        '**/.git/**',\n        '**/.cache/**',\n        '**/.next/**',\n        '**/.nuxt/**',\n        '**/.vscode/**'\n      ]\n    }\n  );\n\n  process.on('SIGINT', () => {\n    watcherInstance.unsubscribe();\n    process.exit(0);\n  });\n  process.on('SIGTERM', () => {\n    watcherInstance.unsubscribe();\n  });\n  process.on('exit', () => {\n    watcherInstance.unsubscribe();\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/dev/hooks.js",
    "content": "import { isBuiltin } from 'node:module';\nimport path, { dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nlet broadcastChannel;\n\nexport function initialize(data) {\n  broadcastChannel = data.broadcastChannel;\n}\n\nexport function resolve(specifier, context, nextResolve) {\n  if (\n    isBuiltin(specifier) ||\n    specifier.includes('?t=') ||\n    context.parentURL === undefined\n  ) {\n    return nextResolve(specifier, context);\n  } else {\n    const modulePath = !specifier.startsWith('file:')\n      ? path.resolve(dirname(fileURLToPath(context.parentURL)), specifier)\n      : fileURLToPath(specifier);\n    if (modulePath.includes('node_modules')) {\n      return nextResolve(specifier, context);\n    } else {\n      broadcastChannel.postMessage({\n        path: modulePath,\n      });\n      return nextResolve(specifier, context);\n    }\n  }\n}"
  },
  {
    "path": "packages/evershop/src/bin/dev/index.ts",
    "content": "import path from 'path';\nimport { fileURLToPath } from 'url';\nimport spawn from 'cross-spawn';\nimport { debug, error } from '../../lib/log/logger.js';\n\nfunction startDev() {\n  const __filename = fileURLToPath(import.meta.url);\n  const __dirname = path.dirname(__filename);\n\n  const args = [path.resolve(__dirname, 'init.js')];\n  const appProcess = spawn('node', args, {\n    stdio: ['inherit', 'inherit', 'inherit', 'ipc'],\n    env: {\n      ...process.env,\n      ALLOW_CONFIG_MUTATIONS: true\n    }\n  });\n\n  appProcess.on('error', (err) => {\n    error(`Error spawning processor: ${err}`);\n  });\n\n  appProcess.on('message', (message) => {\n    debug('Restarting the development server');\n    if (message === 'RESTART_ME') {\n      if (appProcess && appProcess.pid) {\n        appProcess.removeAllListeners();\n        appProcess.kill('SIGTERM');\n      }\n      startDev();\n    }\n  });\n\n  return appProcess;\n}\n\nconst childProcess = startDev();\nprocess.on('exit', () => {\n  // Cleanup child processes on exit\n  if (childProcess && childProcess.pid) {\n    childProcess.kill();\n  }\n});\n"
  },
  {
    "path": "packages/evershop/src/bin/dev/init.ts",
    "content": "import './register.js';\nimport './initEnvDev.js';\nimport { debug, error } from '../../lib/log/logger.js';\nimport { start } from '../lib/startUp.js';\nimport { compileTs } from './compileTs.js';\nimport enableWatcher from './enableWatcher.js';\n\nawait compileTs();\nenableWatcher();\nstart({\n  command: 'dev',\n  env: 'development',\n  process: 'main'\n});\n\nprocess.on('SIGTERM', async () => {\n  debug('Received SIGTERM, shutting down the main process...');\n  try {\n    process.exit(0);\n  } catch (err) {\n    error('Error during shutdown the main process:');\n    error(err);\n    process.exit(1);\n  }\n});\n\nprocess.on('uncaughtException', function (exception) {\n  import('../../lib/log/logger.js').then((module) => {\n    module.error(exception);\n  });\n});\nprocess.on('unhandledRejection', (reason, p) => {\n  import('../../lib/log/logger.js').then((module) => {\n    module.error(`Unhandled Rejection: ${reason} at: ${p}`);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/bin/dev/initEnvDev.ts",
    "content": "import 'dotenv/config';\nprocess.env.NODE_ENV = 'development';\nprocess.env.ALLOW_CONFIG_MUTATIONS = 'true';\n"
  },
  {
    "path": "packages/evershop/src/bin/dev/register.js",
    "content": "import { register } from 'node:module';\nimport { MessageChannel } from 'node:worker_threads';\nexport const maps = new Map();\nconst { port1: listenChannel, port2: broadcastChannel } = new MessageChannel();\nlistenChannel.on('message', (message) => {\n  maps.set(message.path, true);\n});\n\nregister('./hooks.js', {\n  parentURL: import.meta.url,\n  data: { broadcastChannel },\n  transferList: [broadcastChannel],\n});\n\nexport function has(pathName) {\n  return maps.has(pathName);\n}"
  },
  {
    "path": "packages/evershop/src/bin/evershop.js",
    "content": "#!/usr/bin/env node\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\n\nconst { argv } = yargs(hideBin(process.argv));\nconst command = argv._[0];\ntry {\n  if (command === 'build') {\n    await import('./build/index.js');\n  } else if (command === 'dev') {\n    await import('./dev/index.js');\n  } else if (command === 'start') {\n    await import('./start/index.js');\n  } else if (command === 'install') {\n    await import('./install/index.js');\n  } else if (command === 'user:create') {\n    await import('./user/create.js');\n  } else if (command === 'user:changePassword') {\n    await import('./user/changePassword.js');\n  } else if (command === 'theme:active') {\n    await import('./theme/active.js');\n  } else if (command === 'theme:twizz') {\n    await import('./theme/twizz.js');\n  } else if (command === 'theme:create') {\n    await import('./theme/create.js');\n  } else if (command === 'seed') {\n    await import('./seed/index.js');\n  } else {\n    throw new Error('Invalid command');\n  }\n} catch (e) {\n  import('../lib/log/logger.js').then((module) => {\n    module.error(e);\n  });\n}\nprocess.on('uncaughtException', function (exception) {\n  import('../lib/log/logger.js').then((module) => {\n    module.error(exception);\n  });\n});\nprocess.on('unhandledRejection', (reason, p) => {\n  import('../lib/log/logger.js').then((module) => {\n    module.error(`Unhandled Rejection: ${reason} at: ${p}`);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/bin/extension/index.ts",
    "content": "import { existsSync } from 'fs';\nimport { resolve } from 'path';\nimport { CONSTANTS } from '../../lib/helpers.js';\nimport { error, warning } from '../../lib/log/logger.js';\nimport { getConfig } from '../../lib/util/getConfig.js';\nimport { isDevelopmentMode } from '../../lib/util/isDevelopmentMode.js';\nimport { isProductionMode } from '../../lib/util/isProductionMode.js';\nimport { Extension } from '../../types/extension.js';\nimport { getCoreModules } from '../lib/loadModules.js';\n\nlet extensions: Extension[] | undefined = undefined;\n\nfunction loadExtensions(): Extension[] {\n  const coreModules = getCoreModules();\n  const list = getConfig('system.extensions', []) as Extension[];\n  const extensions: Extension[] = [];\n  list.forEach((extension) => {\n    if (\n      coreModules.find((module) => module.name === extension.name) ||\n      extensions.find((e) => e.name === extension.name)\n    ) {\n      throw new Error(\n        `Extension ${extension.name} is invalid. extension name must be unique.`\n      );\n    }\n    if (extension.enabled !== true) {\n      warning(`Extension ${extension.name} is not enabled. Skipping.`);\n      return;\n    }\n    if (!existsSync(extension.resolve)) {\n      warning(\n        `Extension ${extension.name} has resolve path ${extension.resolve} which does not exist. Skipping.`\n      );\n      return;\n    }\n    if (isProductionMode() || extension.resolve.includes('node_modules')) {\n      // Make sure the folder has 'dist' subdirectory\n      if (!existsSync(resolve(extension.resolve, 'dist'))) {\n        error(\n          `Extension '${\n            extension.name\n          }' must have a 'dist' directory at ${resolve(\n            extension.resolve,\n            'dist'\n          )}. This is required for production mode.`\n        );\n        process.exit(1);\n      } else {\n        extensions.push({\n          ...extension,\n          path: resolve(CONSTANTS.ROOTPATH, extension.resolve, 'dist')\n        });\n      }\n    }\n    if (isDevelopmentMode() && !extension.resolve.includes('node_modules')) {\n      // Make sure the folder has 'src' subdirectory\n      if (!existsSync(resolve(extension.resolve, 'src'))) {\n        error(\n          `Extension '${\n            extension.name\n          }' must have a 'src' directory at ${resolve(\n            extension.resolve,\n            'src'\n          )}`\n        );\n        process.exit(1);\n      } else {\n        extensions.push({\n          ...extension,\n          srcPath: resolve(extension.resolve, 'src'),\n          path: resolve(extension.resolve, 'dist')\n        });\n      }\n    }\n  });\n\n  // Sort the extensions by priority, smaller number means higher priority\n  extensions.sort((a, b) => a.priority - b.priority);\n  return extensions;\n}\n\nexport function getEnabledExtensions() {\n  if (extensions === undefined) {\n    extensions = loadExtensions();\n  }\n  return extensions;\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/install/createMigrationTable.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport async function createMigrationTable(connection) {\n  await execute(\n    connection,\n    `CREATE TABLE IF NOT EXISTS \"migration\"  (\n        \"migration_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n        \"module\" varchar NOT NULL,\n        \"version\" varchar NOT NULL,\n        \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n        \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n        CONSTRAINT \"MODULE_UNIQUE\" UNIQUE (\"module\")\n        )`\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/install/index.js",
    "content": "import { mkdir, writeFile } from 'fs/promises';\nimport path from 'path';\nimport {\n  commit,\n  execute,\n  insertOnUpdate,\n  rollback,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport boxen from 'boxen';\nimport enquirer from 'enquirer';\nimport kleur from 'kleur';\nimport ora from 'ora';\nimport { Pool } from 'pg';\nimport { CONSTANTS } from '../../lib/helpers.js';\nimport { error, success } from '../../lib/log/logger.js';\nimport { hashPassword } from '../../lib/util/passwordHelper.js';\nimport { migrate } from '../lib/bootstrap/migrate.js';\nimport { getCoreModules } from '../lib/loadModules.js';\n\n// The installation command will create a .env file in the root directory of the project.\n// If you are using docker, do not run this command. Instead, you should set the environment variables in the docker-compose.yml file and run `npm run start`\n// This command means for the developer who want to install the system on their local machine.\nasync function install() {\n  // Check if the env for database is set\n  if (process.env.DB_HOST) {\n    error(\n      'We found that you have already set the environment variables for the database. Look like you have already installed the system. Run `npm run build` and `npm run start` to launch your store.'\n    );\n    process.exit(0);\n  }\n\n  var db;\n\n  var adminUser;\n\n  // eslint-disable-next-line no-console\n  console.log(\n    kleur.green(\n      boxen('Welcome to EverShop - The open-source e-commerce platform', {\n        title: 'EverShop',\n        titleAlignment: 'center',\n        padding: 1,\n        margin: 1,\n        borderColor: 'green'\n      })\n    )\n  );\n\n  const dbQuestions = [\n    {\n      type: 'input',\n      name: 'databaseHost',\n      message: 'Postgres Database Host (localhost)',\n      initial: process.env.DB_HOST || 'localhost',\n      skip: !!process.env.DB_HOST\n    },\n    {\n      type: 'input',\n      name: 'databasePort',\n      message: 'Postgres Database Port (5432)',\n      initial: process.env.DB_PORT || 5432,\n      skip: !!process.env.DB_PORT\n    },\n    {\n      type: 'input',\n      name: 'databaseName',\n      message: 'Postgres Database Name (evershop)',\n      initial: process.env.DB_NAME || 'evershop',\n      skip: !!process.env.DB_NAME\n    },\n    {\n      type: 'input',\n      name: 'databaseUser',\n      message: 'Postgres Database User (postgres)',\n      initial: process.env.DB_USER || 'postgres',\n      skip: !!process.env.DB_USER\n    },\n    {\n      type: 'input',\n      name: 'databasePassword',\n      message: 'PostgreSQL Database Password (<empty>)',\n      initial: process.env.DB_PASSWORD || '',\n      skip: !!process.env.DB_PASSWORD\n    }\n  ];\n\n  try {\n    db = await enquirer.prompt(dbQuestions);\n  } catch (e) {\n    process.exit(0);\n  }\n\n  const baseDBSetting = {\n    host: db.databaseHost,\n    port: db.databasePort,\n    user: db.databaseUser,\n    password: db.databasePassword,\n    database: db.databaseName,\n    max: 10,\n    idleTimeoutMillis: 30000\n  };\n\n  // We will try with SSL option enabled first\n  let pool = new Pool({ ...baseDBSetting, ssl: true });\n  let sslMode;\n\n  // Test the secure connection\n  try {\n    await pool.query(`SELECT 1`);\n    sslMode = 'require';\n  } catch (e) {\n    if (e.message.includes('does not support SSL')) {\n      // If the database does not support SSL, we will try to connect without SSL\n      pool = new Pool({ ...baseDBSetting, ssl: false });\n      sslMode = 'disable';\n    } else if (e.message.includes('certificate')) {\n      error(\n        `Looks like your database server does not have a valid SSL certificate. Please turn off the SSL option in the database configuration, restart the database server and try again.`\n      );\n    } else {\n      error(e);\n      process.exit(0);\n    }\n  }\n\n  // Check postgres database version\n  try {\n    const { rows } = await execute(pool, `SHOW SERVER_VERSION;`);\n    if (rows[0].server_version < '13.0') {\n      error(\n        `Your database server version(${rows[0].server_version}) is not supported. Please upgrade to PostgreSQL version 13.0 or higher`\n      );\n      process.exit(0);\n    }\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n\n  const adminUserQuestions = [\n    {\n      type: 'input',\n      name: 'fullName',\n      message: 'Your full name',\n      initial: process.env.ADMIN_FULLNAME || '',\n      skip: !!process.env.ADMIN_FULLNAME\n    },\n    {\n      type: 'input',\n      name: 'email',\n      message: 'Your administrator user email',\n      initial: process.env.ADMIN_EMAIL || 'admin@admin.com',\n      skip: !!process.env.ADMIN_EMAIL,\n      validate: (value) => {\n        if (\n          !value.match(\n            /^(([^<>()[\\]\\\\.,;:\\s@\\\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\\\"]+)*)|(\\\".+\\\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/\n          )\n        ) {\n          return 'Invalid email';\n        }\n        return true;\n      }\n    },\n    {\n      type: 'password',\n      name: 'password',\n      message: 'Your administrator user password',\n      initial: process.env.ADMIN_PASSWORD || '123456',\n      skip: !!process.env.ADMIN_PASSWORD,\n      validate: (value) => {\n        if (value.length < 8) {\n          return 'Your password must be at least 8 characters.';\n        }\n        if (value.search(/[a-z]/i) < 0) {\n          return 'Your password must contain at least one letter.';\n        }\n        if (value.search(/[0-9]/) < 0) {\n          return 'Your password must contain at least one digit.';\n        }\n        return true;\n      }\n    }\n  ];\n\n  try {\n    adminUser = await enquirer.prompt(adminUserQuestions);\n  } catch (e) {\n    process.exit(0);\n  }\n\n  /* Start installation */\n  const messages = [];\n  messages.push(`\\n\\n${kleur.green('EverShop is being installed ☕ ☕ ☕')}`);\n  messages.push('Creating .env file');\n  const spinner = ora({\n    text: kleur.green(messages.join('\\n')),\n    spinner: 'dots12'\n  }).start();\n  spinner.start();\n\n  /** Create the .env file at the root folder with the database connection */\n  await writeFile(\n    path.resolve(CONSTANTS.ROOTPATH, '.env'),\n    `DB_HOST=\"${db.databaseHost}\"\nDB_PORT=\"${db.databasePort}\"\nDB_NAME=\"${db.databaseName}\"\nDB_USER=\"${db.databaseUser}\"\nDB_PASSWORD=\"${db.databasePassword}\"\nDB_SSLMODE=\"${sslMode}\"\n`\n  );\n\n  messages.pop();\n  messages.push(kleur.green('✔ Created .env file'));\n  spinner.text = messages.join('\\n');\n\n  // Create `media` folder\n  await mkdir(path.resolve(CONSTANTS.ROOTPATH, 'media'), { recursive: true });\n\n  // Create `public` folder\n  await mkdir(path.resolve(CONSTANTS.ROOTPATH, 'public'), { recursive: true });\n\n  // Start install database\n  messages.push(kleur.green('Setting up a database'));\n  spinner.text = messages.join('\\n');\n\n  const connection = await pool.connect();\n  await startTransaction(connection);\n  try {\n    // Create the admin user\n    const passwordHash = hashPassword(adminUser.password || '123456');\n    await execute(\n      connection,\n      `CREATE TABLE IF NOT EXISTS \"admin_user\" (\n        \"admin_user_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n        \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n        \"status\" boolean NOT NULL DEFAULT TRUE,\n        \"email\" varchar NOT NULL,\n        \"password\" varchar NOT NULL,\n        \"full_name\" varchar DEFAULT NULL,\n        \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n        \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n        CONSTRAINT \"ADMIN_USER_EMAIL_UNIQUE\" UNIQUE (\"email\"),\n        CONSTRAINT \"ADMIN_USER_UUID_UNIQUE\" UNIQUE (\"uuid\")\n      );`\n    );\n    await insertOnUpdate('admin_user', ['email'])\n      .given({\n        status: 1,\n        email: adminUser?.email || 'admin@evershop.io',\n        password: passwordHash,\n        full_name: adminUser?.fullName || 'Admin'\n      })\n      .execute(connection);\n\n    // Run module migrations\n    const coreModules = getCoreModules();\n    await migrate(coreModules, connection);\n    await commit(connection);\n  } catch (e) {\n    await rollback(connection);\n    error(e);\n    process.exit(0);\n  }\n  messages.pop();\n  messages.push(kleur.green('✔ Setup database'));\n  messages.push(kleur.green('✔ Create admin user'));\n  spinner.succeed(messages.join('\\n'));\n\n  // eslint-disable-next-line no-console\n  console.log(\n    boxen(\n      kleur.green(\n        'Installation completed!. Run `npm run build` and `npm run start` to launch your store'\n      ),\n      {\n        title: 'EverShop',\n        titleAlignment: 'center',\n        padding: 1,\n        margin: 1,\n        borderColor: 'green'\n      }\n    )\n  );\n  process.exit(0);\n}\n\n(async () => {\n  try {\n    await install();\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n})();\n"
  },
  {
    "path": "packages/evershop/src/bin/install/templates/config.json",
    "content": "{\n  \"shop\": {\n    \"currency\": \"USD\",\n    \"language\": \"en\",\n    \"weightUnit\": \"kg\",\n    \"timezone\": \"UTC\"\n  },\n  \"system\": {\n    \"database\": {\n      \"host\": \"localhost\",\n      \"port\": 5432,\n      \"database\": \"evershop\",\n      \"user\": \"admin\",\n      \"password\": \"123456\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/addDefaultMiddlewareFuncs.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport sessionStorage from 'connect-pg-simple';\nimport cookieParser from 'cookie-parser';\nimport session from 'express-session';\nimport pathToRegexp from 'path-to-regexp';\nimport { translate } from '../../lib/locale/translate/translate.js';\nimport { debug, warning } from '../../lib/log/logger.js';\nimport publicStatic from '../../lib/middlewares/publicStatic.js';\nimport themePublicStatic from '../../lib/middlewares/themePublicStatic.js';\nimport { pool } from '../../lib/postgres/connection.js';\nimport { getRoutes } from '../../lib/router/Router.js';\nimport { getConfig } from '../../lib/util/getConfig.js';\nimport isDevelopmentMode from '../../lib/util/isDevelopmentMode.js';\nimport isProductionMode from '../../lib/util/isProductionMode.js';\nimport { getAdminSessionCookieName } from '../../modules/auth/services/getAdminSessionCookieName.js';\nimport { getCookieSecret } from '../../modules/auth/services/getCookieSecret.js';\nimport { getFrontStoreSessionCookieName } from '../../modules/auth/services/getFrontStoreSessionCookieName.js';\nimport { setPageMetaInfo } from '../../modules/cms/services/pageMetaInfo.js';\nimport { getDevMiddleware, getHotMiddleware } from './devEnvHelper.js';\n\nexport function addDefaultMiddlewareFuncs(app) {\n  app.use((request, response, next) => {\n    response.debugMiddlewares = [];\n    next();\n    response.on('finish', () => {\n      // Console log the debug middlewares\n      let message = `[${request.method}] ${request.originalUrl}\\n`;\n      response.debugMiddlewares.forEach((m) => {\n        message += m.time\n          ? `-> Middleware ${m.id} - ${m.time} ms\\n`\n          : `-> Middleware ${m.id}\\n`;\n      });\n      // Skip logging if the request is for static files\n      if (\n        request.currentRoute?.id === 'staticAsset' ||\n        request.currentRoute?.id === 'adminStaticAsset'\n      ) {\n        return;\n      }\n      debug(message);\n    });\n  });\n  // Add public static middleware\n  app.use(publicStatic);\n  // Add theme public static middleware\n  app.use(themePublicStatic);\n\n  // Express session\n  const cookieSecret = getCookieSecret();\n  const sess = {\n    store:\n      process.env.NODE_ENV === 'test'\n        ? undefined\n        : new (sessionStorage(session))({\n            pool\n          }),\n    secret: cookieSecret,\n    cookie: {\n      maxAge: getConfig('system.session.maxAge', 24 * 60 * 60 * 1000)\n    },\n    resave: getConfig('system.session.resave', false),\n    saveUninitialized: getConfig('system.session.saveUninitialized', true)\n  } as session.SessionOptions;\n\n  if (isProductionMode()) {\n    app.set('trust proxy', 1);\n    sess.cookie!.secure = false;\n  }\n\n  const adminSessionMiddleware = session({\n    ...sess,\n    name: getAdminSessionCookieName()\n  });\n\n  const frontStoreSessionMiddleware = session({\n    ...sess,\n    name: getFrontStoreSessionCookieName()\n  });\n\n  // Cookie parser\n  app.use(cookieParser(cookieSecret));\n  app.use((request, response, next) => {\n    const routes = getRoutes();\n    const method = request.method.toUpperCase();\n    const requestPath = request.originalUrl.split('?')[0];\n    const matchedRoutes = routes.filter((r) => {\n      const regexp = pathToRegexp(r.path, []);\n      const match = regexp.exec(requestPath);\n      if (match && r.method.includes(method)) {\n        return true;\n      } else {\n        return false;\n      }\n    });\n    if (matchedRoutes.length > 1) {\n      warning(\n        `Multiple routes matched for ${requestPath}. Please check your routes: ${matchedRoutes\n          .map((r) => r.id)\n          .join(', ')}. Route ${matchedRoutes[0].id} will be used.`\n      );\n    }\n    if (matchedRoutes.length) {\n      request.currentRoute = matchedRoutes[0];\n      next();\n    } else {\n      next();\n    }\n  });\n  const sessionMiddleware = (request, response, next) => {\n    const { currentRoute } = request;\n    if (currentRoute?.isApi) {\n      // We don't need session for api routes. Restful api should be stateless\n      next();\n    } else if (currentRoute?.isAdmin) {\n      adminSessionMiddleware(request, response, next);\n    } else {\n      frontStoreSessionMiddleware(request, response, next);\n    }\n  };\n  app.use(sessionMiddleware);\n\n  app.use(async (request, response, next) => {\n    // Get the request path, remove '/' from both ends\n    const path = request.originalUrl.split('?')[0].replace(/^\\/|\\/$/g, '');\n    // If the current route is already set, or the path contains .hot-update.json, .hot-update.js skip this middleware\n    if (request.currentRoute || path.includes('.hot-update')) {\n      return next();\n    }\n    // Also skip if we are running in the test mode\n    if (process.env.NODE_ENV === 'test') {\n      return next();\n    }\n\n    // Find the matched rewrite rule base on the request path\n    const rewriteRule = await select()\n      .from('url_rewrite')\n      .where('request_path', '=', `/${path}`)\n      .load(pool);\n\n    if (rewriteRule) {\n      // Find the route\n      const routes = getRoutes();\n      const route = routes.find((r) => {\n        const regexp = pathToRegexp(r.path);\n        const match = regexp.exec(rewriteRule.target_path);\n        if (match) {\n          request.locals = request.locals || {};\n          request.locals.customParams = {};\n          const keys: any[] = [];\n          pathToRegexp(r.path, keys);\n          keys.forEach((key, index) => {\n            request.locals.customParams[key.name] = match[index + 1];\n          });\n          return true;\n        }\n        return false;\n      });\n      // Get the current http method\n      const method = request.method.toUpperCase();\n      // Check if the route supports the current http method\n      if (route && route.method.includes(method)) {\n        request.currentRoute = route;\n      }\n      return next();\n    } else {\n      return next();\n    }\n  });\n\n  if (isDevelopmentMode()) {\n    // Admin webpack dev middleware - only for /backend/* paths\n    app.use((request, response, next) => {\n      if (request.path.startsWith('/backend/')) {\n        const adminDevMiddleware = getDevMiddleware(true);\n        adminDevMiddleware.waitUntilValid(() => {\n          const { stats } = adminDevMiddleware.context;\n          if (stats) {\n            response.locals.jsonWebpackStats = stats.toJson();\n          }\n        });\n        adminDevMiddleware(request, response, next);\n      } else {\n        next();\n      }\n    });\n\n    app.use((request, response, next) => {\n      if (request.path.startsWith('/__webpack_hmr_admin')) {\n        const adminHotMiddleware = getHotMiddleware(true);\n        adminHotMiddleware(request, response, next);\n      } else {\n        next();\n      }\n    });\n\n    // Frontstore webpack dev middleware - for all other paths\n    app.use((request, response, next) => {\n      if (\n        !request.path.startsWith('/backend/') &&\n        !request.path.startsWith('/__webpack_hmr_admin')\n      ) {\n        const frontstoreDevMiddleware = getDevMiddleware(false);\n        frontstoreDevMiddleware.waitUntilValid(() => {\n          const { stats } = frontstoreDevMiddleware.context;\n          if (stats) {\n            response.locals.jsonWebpackStats = stats.toJson();\n          }\n        });\n        frontstoreDevMiddleware(request, response, next);\n      } else {\n        next();\n      }\n    });\n\n    app.use((request, response, next) => {\n      if (request.path.startsWith('/__webpack_hmr_frontstore')) {\n        const frontstoreHotMiddleware = getHotMiddleware(false);\n        frontstoreHotMiddleware(request, response, next);\n      } else {\n        next();\n      }\n    });\n  }\n  /** 404 Not Found handle */\n  app.use((request, response, next) => {\n    if (!request.currentRoute) {\n      response.status(404);\n      const routes = getRoutes();\n      request.currentRoute = routes.find((r) => r.id === 'notFound');\n      setPageMetaInfo(request, {\n        title: translate('Not found'),\n        description: translate('Not found')\n      });\n      next();\n    } else {\n      next();\n    }\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/app.js",
    "content": "import express from 'express';\nimport { error } from '../../lib/log/logger.js';\nimport { Handler } from '../../lib/middleware/Handler.js';\nimport { getModuleMiddlewares } from '../../lib/middleware/index.js';\nimport { loadModuleRoutes } from '../../lib/router/loadModuleRoutes.js';\nimport { getRoutes } from '../../lib/router/Router.js';\nimport { getEnabledExtensions } from '../extension/index.js';\nimport { addDefaultMiddlewareFuncs } from './addDefaultMiddlewareFuncs.js';\nimport { getCoreModules } from './loadModules.js';\n\nexport const createApp = () => {\n  /** Create express app */\n  const app = express();\n  // Enable trust proxy\n  app.enable('trust proxy');\n  /* Loading modules and initilize routes, components and services */\n  const modules = getCoreModules();\n\n  // Load routes and middleware functions\n  modules.forEach((module) => {\n    try {\n      // Load middleware functions\n      getModuleMiddlewares(module.path);\n      // Load routes\n      loadModuleRoutes(module.path);\n    } catch (e) {\n      error(e);\n      process.exit(0);\n    }\n  });\n\n  /** Load extensions */\n  const extensions = getEnabledExtensions();\n  extensions.forEach((extension) => {\n    try {\n      // Load middleware functions\n      getModuleMiddlewares(extension.path);\n      // Load routes\n      loadModuleRoutes(extension.path);\n    } catch (e) {\n      error(e);\n      process.exit(0);\n    }\n  });\n\n  // Adding default middlewares\n  addDefaultMiddlewareFuncs(app);\n  const routes = getRoutes();\n  routes.forEach((route) => {\n    // app.all(route.path, Handler.middleware());\n    route.method.forEach((method) => {\n      switch (method.toUpperCase()) {\n        case 'GET':\n          app.get(route.path, Handler.middleware());\n          break;\n        case 'POST':\n          app.post(route.path, Handler.middleware());\n          break;\n        case 'PUT':\n          app.put(route.path, Handler.middleware());\n          break;\n        case 'DELETE':\n          app.delete(route.path, Handler.middleware());\n          break;\n        case 'PATCH':\n          app.patch(route.path, Handler.middleware());\n          break;\n        default:\n          app.get(route.path, Handler.middleware());\n          break;\n      }\n    });\n  });\n  app.use(Handler.middleware());\n  return app;\n};\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/bootstrap/bootstrap.ts",
    "content": "import { existsSync } from 'fs';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\n\ninterface Module {\n  path: string;\n}\n\nexport type BootstrapContext = {\n  command?: string;\n  env?: 'production' | 'development' | 'test';\n  process?: 'main' | 'cronjob' | 'event';\n};\n\ntype BootstrapModule = {\n  default: (context: BootstrapContext) => Promise<void> | void;\n};\n\n/**\n * Loads and runs the bootstrap script from a module directory.\n */\nexport const loadBootstrapScript = async function loadBootstrapScript(\n  module: Module,\n  context: BootstrapContext = {}\n): Promise<void> {\n  const filePath = path.resolve(module.path, 'bootstrap.js');\n  if (!existsSync(filePath)) {\n    return;\n  }\n\n  // Convert path to a URL\n  const bootstrapPath = pathToFileURL(filePath).toString();\n  const bootstrap = (await import(bootstrapPath)) as BootstrapModule;\n\n  if (typeof bootstrap.default !== 'function') {\n    throw new Error(\n      'Bootstrap script must provide a default export as a function'\n    );\n  }\n\n  await bootstrap.default(context);\n};\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/bootstrap/migrate.js",
    "content": "import { existsSync, readdirSync } from 'fs';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\nimport {\n  commit,\n  insertOnUpdate,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport semver from 'semver';\nimport { error } from '../../../lib/log/logger.js';\nimport { getConnection, pool } from '../../../lib/postgres/connection.js';\nimport { createMigrationTable } from '../../install/createMigrationTable.js';\n\nasync function getCurrentInstalledVersion(module, connection = null) {\n  /** Check for current installed version */\n  const check = await select()\n    .from('migration')\n    .where('module', '=', module)\n    .load(connection || pool);\n  if (!check) {\n    return '0.0.1';\n  } else {\n    return check.version;\n  }\n}\n\nasync function migrateModule(module, connection = null) {\n  /** Check if the module has migration folder, if not ignore it */\n  if (!existsSync(path.resolve(module.path, 'migration'))) {\n    return;\n  }\n  const migrations = readdirSync(path.resolve(module.path, 'migration'), {\n    withFileTypes: true\n  })\n    .filter(\n      (dirent) =>\n        dirent.isFile() &&\n        dirent.name.match(/^Version-+([1-9].[0-9].[0-9])+.js$/)\n    )\n    .map((dirent) => dirent.name.replace('Version-', '').replace('.js', ''))\n    .sort((first, second) => semver.lt(first, second));\n\n  const currentInstalledVersion = await getCurrentInstalledVersion(\n    module.name,\n    connection\n  );\n\n  for (const version of migrations) {\n    /** If the version is lower or equal the installed version, ignore it */\n    if (semver.lte(version, currentInstalledVersion)) {\n      continue;\n    }\n    const migrationConnection = connection || (await getConnection());\n    if (!connection) {\n      await startTransaction(migrationConnection);\n    }\n\n    /** We expect the migration script to provide a function as a default export */\n    try {\n      const versionModule = await import(\n        pathToFileURL(\n          path.resolve(module.path, 'migration', `Version-${version}.js`)\n        )\n      );\n      await versionModule.default(migrationConnection);\n\n      await insertOnUpdate('migration', ['module'])\n        .given({\n          module: module.name,\n          version\n        })\n        .execute(migrationConnection, false);\n      if (!connection) {\n        await commit(migrationConnection);\n      }\n    } catch (e) {\n      if (!connection) {\n        await rollback(migrationConnection);\n      }\n      throw new Error(\n        `Migration failed for module ${module.name}, version ${version}\\n${e}`\n      );\n    }\n  }\n}\n\nexport async function migrate(modules, connection = null) {\n  try {\n    const psqlConnection = connection || (await getConnection());\n    // Create a migration table if not exists. This is for the first time installation\n    await createMigrationTable(psqlConnection);\n\n    for (const module of modules) {\n      await migrateModule(module, connection);\n    }\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/buildEntry.js",
    "content": "import fs from 'fs';\nimport { mkdir, writeFile } from 'fs/promises';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\nimport { inspect } from 'util';\nimport JSON5 from 'json5';\nimport { getComponentsByRoute } from '../../lib/componee/getComponentsByRoute.js';\nimport { CONSTANTS } from '../../lib/helpers.js';\nimport { error } from '../../lib/log/logger.js';\nimport { generateComponentKey } from '../../lib/util/keyGenerator.js';\nimport { getRouteBuildPath } from '../../lib/webpack/getRouteBuildPath.js';\nimport { parseGraphql } from '../../lib/webpack/util/parseGraphql.js';\nimport { getEnabledWidgets } from '../../lib/widget/widgetManager.js';\n/**\n * Only pass the page routes, not api routes\n */\nexport async function buildEntry(routes, clientOnly = false) {\n  const widgets = getEnabledWidgets();\n  await Promise.all(\n    routes.map(async (route) => {\n      const imports = [];\n      const subPath = getRouteBuildPath(route);\n      const components = getComponentsByRoute(route);\n      if (!components) {\n        return;\n      }\n      /** Build layout and query */\n      const areas = {};\n      components.forEach((module) => {\n        if (!fs.existsSync(module)) {\n          return;\n        }\n        const source = fs.readFileSync(module, 'utf8');\n        // Regex matching 'export const layout = { ... }'\n        const layoutRegex =\n          /export\\s+const\\s+layout\\s*=\\s*{\\s*areaId\\s*:\\s*['\"]([^'\"]+)['\"],\\s*sortOrder\\s*:\\s*(\\d+)\\s*,*\\s*}/;\n        const match = source.match(layoutRegex);\n        if (match) {\n          // Remove everything before '{' from the beginning of the match\n          const check = match[0]\n            .replace(/^[^{]*/, '')\n            .replace(/(['\"])?([a-zA-Z0-9_]+)(['\"])?:/g, '\"$2\": ');\n          try {\n            const layout = JSON5.parse(check);\n            const id = generateComponentKey(module);\n            const url = pathToFileURL(module).toString();\n            imports.push(`import ${id} from '${url}';`);\n            areas[layout.areaId] = areas[layout.areaId] || {};\n            areas[layout.areaId][id] = {\n              id,\n              sortOrder: layout.sortOrder,\n              component: { default: `---${id}---` }\n            };\n          } catch (e) {\n            error(`Error parsing layout from ${module}`);\n            error(e);\n          }\n        }\n      });\n\n      let contentClient = `\n      import React from 'react';\n      import ReactDOM from 'react-dom';\n      import { Area } from '@evershop/evershop/components/common';\n      import {${\n        route.isAdmin ? 'HydrateAdmin' : 'HydrateFrontStore'\n      }} from '@evershop/evershop/components/common';\n      `;\n      areas['*'] = areas['*'] || {};\n      widgets.forEach((widget) => {\n        const url = route.isAdmin\n          ? pathToFileURL(widget.settingComponent).toString()\n          : pathToFileURL(widget.component).toString();\n        const id = generateComponentKey(\n          route.isAdmin\n            ? `admin_widget_${widget.type}`\n            : `widget_${widget.type}`\n        );\n        imports.push(`import ${id} from '${url}';`);\n        areas['*'][id] = {\n          id,\n          sortOrder: widget.sortOrder || 0,\n          component: {\n            default: `---${id}---`\n          }\n        };\n      });\n      contentClient += '\\r\\n';\n      contentClient += imports.join('\\r\\n');\n      contentClient += '\\r\\n';\n      contentClient += `Area.defaultProps.components = ${inspect(areas, {\n        depth: 5\n      })\n        .replace(/\"---/g, '')\n        .replace(/---\"/g, '')\n        .replace(/'---/g, '')\n        .replace(/---'/g, '')} `;\n      contentClient += '\\r\\n';\n      contentClient += `ReactDOM.hydrate(\n        ${\n          route.isAdmin\n            ? 'React.createElement(HydrateAdmin, null)'\n            : 'React.createElement(HydrateFrontStore, null)'\n        },\n        document.getElementById('app')\n      );`;\n      if (!fs.existsSync(path.resolve(subPath, 'client'))) {\n        await mkdir(path.resolve(subPath, 'client'), { recursive: true });\n      }\n      await writeFile(\n        path.resolve(subPath, 'client', 'entry.js'),\n        contentClient\n      );\n\n      if (!clientOnly) {\n        /** Build query */\n        const query = `${JSON.stringify(parseGraphql(components))}`;\n\n        // Loop through the widgets config and add the query to the widgets\n        let contentServer = `import React from 'react'; `;\n        contentServer += '\\r\\n';\n        contentServer += `import ReactDOM from 'react-dom'; `;\n        contentServer += '\\r\\n';\n        contentServer += `import { Area } from '@evershop/evershop/components/common';`;\n        contentServer += '\\r\\n';\n        contentServer += `import { renderHtml } from '@evershop/evershop/components/common';\\r\\n`;\n        contentServer += imports.join('\\r\\n');\n        contentServer += '\\r\\n';\n        contentServer += `export default renderHtml;\\r\\n`;\n        contentServer += `Area.defaultProps.components = ${inspect(areas, {\n          depth: 5\n        })\n          .replace(/\"---/g, '')\n          .replace(/---\"/g, '')\n          .replace(/'---/g, '')\n          .replace(/---'/g, '')} `;\n\n        if (!fs.existsSync(path.resolve(subPath, 'server'))) {\n          await mkdir(path.resolve(subPath, 'server'), { recursive: true });\n        }\n        await writeFile(\n          path.resolve(subPath, 'server', 'entry.js'),\n          contentServer\n        );\n        await writeFile(\n          path.resolve(subPath, 'server', 'query.graphql'),\n          query\n        );\n      }\n    })\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/devEnvHelper.ts",
    "content": "import webpack from 'webpack';\nimport middleware from 'webpack-dev-middleware';\nimport webpackHotMiddleware from 'webpack-hot-middleware';\nimport { createConfigClient } from '../../lib/webpack/dev/createConfigClient.js';\n\ntype DevConfig = {\n  admin: {\n    compiler?: webpack.Compiler;\n    devMiddleware?: ReturnType<typeof middleware>;\n    hotMiddleware?: ReturnType<typeof webpackHotMiddleware>;\n  };\n  frontStore: {\n    compiler?: webpack.Compiler | null;\n    devMiddleware?: ReturnType<typeof middleware> | null;\n    hotMiddleware?: ReturnType<typeof webpackHotMiddleware> | null;\n  };\n};\n\nconst webpackConfig = {\n  admin: {},\n  frontStore: {}\n} as DevConfig;\n\nfunction getWebpackCompiler(isAdmin: boolean) {\n  const area = isAdmin ? 'admin' : 'frontStore';\n  if (!webpackConfig[area].compiler) {\n    webpackConfig[area].compiler = webpack(createConfigClient(isAdmin) as any);\n  }\n  return webpackConfig[area].compiler;\n}\n\nfunction getDevMiddleware(isAdmin: boolean) {\n  const area = isAdmin ? 'admin' : 'frontStore';\n  if (!webpackConfig[area].devMiddleware) {\n    const compiler = getWebpackCompiler(isAdmin);\n    const devMiddleware = middleware(compiler, {\n      serverSideRender: true,\n      publicPath: isAdmin ? '/backend/' : '/',\n      stats: 'none'\n    });\n    devMiddleware.context.logger.info = () => {};\n    webpackConfig[area].devMiddleware = devMiddleware;\n  }\n  return webpackConfig[area].devMiddleware;\n}\n\nfunction getHotMiddleware(isAdmin: boolean) {\n  const area = isAdmin ? 'admin' : 'frontStore';\n  if (!webpackConfig[area].hotMiddleware) {\n    const compiler = getWebpackCompiler(isAdmin);\n    const hotMiddleware = webpackHotMiddleware(compiler, {\n      path: isAdmin ? `/__webpack_hmr_admin` : `/__webpack_hmr_frontstore`\n    });\n    webpackConfig[area].hotMiddleware = hotMiddleware;\n  }\n  return webpackConfig[area].hotMiddleware;\n}\n\nexport { getWebpackCompiler, getDevMiddleware, getHotMiddleware };\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/loadModules.js",
    "content": "import { readdirSync } from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst coreModules = [\n  {\n    name: 'auth',\n    resolve: path.resolve(__dirname, '../../modules/auth'),\n    path: path.resolve(__dirname, '../../modules/auth')\n  },\n  {\n    name: 'base',\n    resolve: path.resolve(__dirname, '../../modules/base'),\n    path: path.resolve(__dirname, '../../modules/base')\n  },\n  {\n    name: 'catalog',\n    resolve: path.resolve(__dirname, '../../modules/catalog'),\n    path: path.resolve(__dirname, '../../modules/catalog')\n  },\n  {\n    name: 'checkout',\n    resolve: path.resolve(__dirname, '../../modules/checkout'),\n    path: path.resolve(__dirname, '../../modules/checkout')\n  },\n  {\n    name: 'cms',\n    resolve: path.resolve(__dirname, '../../modules/cms'),\n    path: path.resolve(__dirname, '../../modules/cms')\n  },\n  {\n    name: 'cod',\n    resolve: path.resolve(__dirname, '../../modules/cod'),\n    path: path.resolve(__dirname, '../../modules/cod')\n  },\n  {\n    name: 'customer',\n    resolve: path.resolve(__dirname, '../../modules/customer'),\n    path: path.resolve(__dirname, '../../modules/customer')\n  },\n  {\n    name: 'graphql',\n    resolve: path.resolve(__dirname, '../../modules/graphql'),\n    path: path.resolve(__dirname, '../../modules/graphql')\n  },\n  {\n    name: 'oms',\n    resolve: path.resolve(__dirname, '../../modules/oms'),\n    path: path.resolve(__dirname, '../../modules/oms')\n  },\n  {\n    name: 'paypal',\n    resolve: path.resolve(__dirname, '../../modules/paypal'),\n    path: path.resolve(__dirname, '../../modules/paypal')\n  },\n  {\n    name: 'promotion',\n    resolve: path.resolve(__dirname, '../../modules/promotion'),\n    path: path.resolve(__dirname, '../../modules/promotion')\n  },\n  {\n    name: 'setting',\n    resolve: path.resolve(__dirname, '../../modules/setting'),\n    path: path.resolve(__dirname, '../../modules/setting')\n  },\n  {\n    name: 'stripe',\n    resolve: path.resolve(__dirname, '../../modules/stripe'),\n    path: path.resolve(__dirname, '../../modules/stripe')\n  },\n  {\n    name: 'tax',\n    resolve: path.resolve(__dirname, '../../modules/tax'),\n    path: path.resolve(__dirname, '../../modules/tax')\n  }\n];\n\nexport function loadModule(path) {\n  return readdirSync(path, { withFileTypes: true })\n    .filter((dirent) => dirent.isDirectory())\n    .map((dirent) => ({\n      name: dirent.name,\n      path: path.resolve(path, dirent.name)\n    }));\n}\n\nexport function getCoreModules() {\n  return coreModules;\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/normalizePort.js",
    "content": "/**\n * Normalize a port into a number, string, or false.\n */\nexport function normalizePort() {\n  const port = parseInt(process.env.PORT, 10);\n\n  if (isNaN(port)) {\n    return 3000;\n  }\n\n  if (port >= 0) {\n    // port number\n    return port;\n  }\n\n  return 3000;\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/onError.js",
    "content": "import { error } from '../../lib/log/logger.js';\nimport { normalizePort } from './normalizePort.js';\n\nconst port = normalizePort();\n/**\n * Event listener for HTTP server \"err\" event.\n */\nexport function onError(err) {\n  if (err.syscall !== 'listen') {\n    throw err;\n  }\n\n  const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`;\n\n  // handle specific listen errors with friendly messages\n  switch (err.code) {\n    case 'EACCES':\n      error(`${bind} requires elevated privileges\\n`);\n      process.exit(1);\n      break;\n    case 'EADDRINUSE':\n      error(`${bind} is already in use\\n`);\n      process.exit(1);\n      break;\n    default:\n      throw err;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/onListening.js",
    "content": "import boxen from 'boxen';\nimport kleur from 'kleur';\nimport { normalizePort } from './normalizePort.js';\n\nconst port = normalizePort();\n/**\n * Event listener for HTTP server \"listening\" event.\n */\nexport function onListening() {\n  const message = boxen(\n    `Your website is running at \"http://localhost:${port}\"`,\n    {\n      title: 'EverShop',\n      titleAlignment: 'center',\n      padding: 1,\n      margin: 1,\n      borderColor: 'green'\n    }\n  );\n  // eslint-disable-next-line no-console\n  console.log(kleur.green(message));\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/prepare.js",
    "content": "import { getAdminRoutes } from '../../src/lib/router/Router.js';\n\nexport function prepare(app, middlewares, routes) {\n  const adminRoutes = getAdminRoutes();\n\n  middlewares.forEach((m) => {\n    if (m.routeId === null) {\n      app.use(m.middleware);\n    } else if (m.routeId === 'admin') {\n      adminRoutes.forEach((route) => {\n        if (route.id !== 'adminStaticAsset' || m.id === 'isAdmin') {\n          route.method.forEach((method) => {\n            switch (method.toUpperCase()) {\n              case 'GET':\n                app.get(route.path, m.middleware);\n                break;\n              case 'POST':\n                app.post(route.path, m.middleware);\n                break;\n              case 'PUT':\n                app.put(route.path, m.middleware);\n                break;\n              case 'DELETE':\n                app.delete(route.path, m.middleware);\n                break;\n              default:\n                app.get(route.path, m.middleware);\n                break;\n            }\n          });\n        }\n      });\n    } else if (m.routeId === 'frontStore') {\n      app.all('*', (request, response, next) => {\n        const route = request.currentRoute;\n        if (route.isAdmin === true || route.id === 'staticAsset') {\n          return next();\n        }\n        return m.middleware(request, response, next);\n      });\n    } else {\n      const route = routes.find((r) => r.id === m.routeId);\n      if (route !== undefined) {\n        route.method.forEach((method) => {\n          switch (method.toUpperCase()) {\n            case 'GET':\n              app.get(route.path, m.middleware);\n              break;\n            case 'POST':\n              app.post(route.path, m.middleware);\n              break;\n            case 'PUT':\n              app.put(route.path, m.middleware);\n              break;\n            case 'DELETE':\n              app.delete(route.path, m.middleware);\n              break;\n            default:\n              app.get(route.path, m.middleware);\n              break;\n          }\n        });\n      }\n    }\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/startCronProcess.ts",
    "content": "import path from 'path';\nimport { fileURLToPath } from 'url';\nimport spawn from 'cross-spawn';\nimport { error } from '../../lib/log/logger.js';\nimport isDevelopmentMode from '../../lib/util/isDevelopmentMode.js';\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nexport function startCronProcess(context) {\n  // Spawn the child process to manage scheduled jobs\n  const jobArgs = [path.resolve(__dirname, '../../lib/cronjob/cronjob.js')];\n  if (isDevelopmentMode() || process.argv.includes('--debug')) {\n    jobArgs.push('--debug');\n  }\n  const jobChild = spawn('node', jobArgs, {\n    stdio: 'inherit',\n    env: {\n      ...process.env,\n      bootstrapContext: JSON.stringify(context),\n      ALLOW_CONFIG_MUTATIONS: true\n    }\n  });\n  jobChild.on('error', (err) => {\n    error(`Error spawning job processor: ${err}`);\n  });\n  jobChild.unref();\n  return jobChild;\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/startSubscriberProcess.ts",
    "content": "import path from 'path';\nimport { fileURLToPath } from 'url';\nimport spawn from 'cross-spawn';\nimport { error } from '../../lib/log/logger.js';\nimport isDevelopmentMode from '../../lib/util/isDevelopmentMode.js';\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nexport function startSubscriberProcess(context) {\n  const args = [path.resolve(__dirname, '../../lib/event/event-manager.js')];\n  if (isDevelopmentMode() || process.argv.includes('--debug')) {\n    args.push('--debug');\n  }\n  const child = spawn('node', args, {\n    stdio: 'inherit',\n    env: {\n      ...process.env,\n      bootstrapContext: JSON.stringify(context),\n      ALLOW_CONFIG_MUTATIONS: true\n    }\n  });\n\n  child.on('error', (err) => {\n    error(`Error spawning event processor: ${err}`);\n  });\n\n  child.unref();\n  return child;\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/startUp.js",
    "content": "import http from 'http';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport config from 'config';\nimport spawn from 'cross-spawn';\nimport { error, debug } from '../../lib/log/logger.js';\nimport { Handler } from '../../lib/middleware/Handler.js';\nimport { lockHooks } from '../../lib/util/hookable.js';\nimport isDevelopmentMode from '../../lib/util/isDevelopmentMode.js';\nimport { lockRegistry } from '../../lib/util/registry.js';\nimport { validateConfiguration } from '../../lib/util/validateConfiguration.js';\nimport { getEnabledExtensions } from '../extension/index.js';\nimport { createApp } from './app.js';\nimport { loadBootstrapScript } from './bootstrap/bootstrap.js';\nimport { migrate } from './bootstrap/migrate.js';\nimport { getCoreModules } from './loadModules.js';\nimport { normalizePort } from './normalizePort.js';\nimport { onError } from './onError.js';\nimport { onListening } from './onListening.js';\nimport { startCronProcess } from './startCronProcess.js';\nimport { startSubscriberProcess } from './startSubscriberProcess.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nexport const start = async function start(context, cb) {\n  const app = createApp();\n  /** Create a http server */\n  const server = http.createServer(app);\n  const modules = [...getCoreModules(), ...getEnabledExtensions()];\n\n  /** Loading bootstrap script from modules */\n  try {\n    for (const module of modules) {\n      await loadBootstrapScript(module, context);\n    }\n    lockHooks();\n    lockRegistry();\n    // Get the configuration (nodeconfig)\n    validateConfiguration(config);\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n  process.env.ALLOW_CONFIG_MUTATIONS = false;\n\n  /** Migration */\n  try {\n    await migrate(modules);\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n\n  /**\n   * Get port from environment and store in Express.\n   */\n  const port = normalizePort();\n  app.set('port', port);\n\n  /** Start listening */\n  server.on('listening', () => {\n    onListening();\n    if (cb) {\n      cb();\n    }\n  });\n  server.on('error', onError);\n  server.listen(port);\n\n  // Spawn the child process to manage events\n  let subscriberChild = startSubscriberProcess(context);\n  let jobChild = startCronProcess(context);\n\n  process.on('exit', (code) => {\n    // Cleanup child processes on exit\n    if (subscriberChild && subscriberChild.pid) {\n      subscriberChild.kill('SIGTERM');\n    }\n    if (jobChild && jobChild.pid) {\n      jobChild.kill('SIGTERM');\n    }\n    if (code === 100) {\n      debug('Restarting the sever');\n      process.send('RESTART_ME');\n    }\n  });\n  process.on('RESTART_CRONJOB', () => {\n    debug('Restarting the cron job process');\n    jobChild.kill('SIGTERM');\n    jobChild = startCronProcess(context);\n  });\n\n  process.on('RESTART_SUBSCRIBER', () => {\n    debug('Restarting the subscriber process');\n    subscriberChild.kill('SIGTERM');\n    subscriberChild = startSubscriberProcess(context);\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/broadcast.js",
    "content": "import { getRoutes } from '../../../lib/router/Router.js';\n\nexport const broadcast = async () => {\n  const routes = getRoutes();\n  routes.forEach((route) => {\n    if (route.hotMiddleware) {\n      const { hotMiddleware } = route;\n      hotMiddleware.publish({\n        action: 'serverReloaded'\n      });\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/compileSwc.ts",
    "content": "import fs, { promises as fsp } from 'fs';\nimport type { PathLike } from 'fs';\nimport path from 'path';\nimport { execa } from 'execa';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { error, warning } from '../../../lib/log/logger.js';\n\nexport async function compileSwc(\n  srcPath: PathLike,\n  distPath: PathLike\n): Promise<void> {\n  // Check if the source is a file or directory\n  if (!fs.existsSync(srcPath)) {\n    warning(`Source path ${srcPath} does not exist.`);\n    return;\n    // Check if file extension is not either ts, js, tsx, or jsx\n  } else if (\n    fs.statSync(srcPath).isFile() &&\n    !['.ts', '.js', '.tsx', '.jsx'].includes(path.extname(srcPath as string))\n  ) {\n    // For this case, we just force copy the file to the dist directory\n    try {\n      const directory = path.dirname(distPath as string);\n      await fsp.mkdir(directory, { recursive: true });\n      await fsp.copyFile(srcPath as string, distPath as string);\n    } catch (err) {\n      error(`Error copying ${srcPath} to ${distPath}:`);\n      throw err;\n    }\n  } else {\n    let cliOptions;\n    const configFile = path.resolve(CONSTANTS.LIBPATH, '../../.swcrc');\n    if (fs.statSync(srcPath).isDirectory()) {\n      cliOptions = [\n        srcPath as string,\n        '-d',\n        distPath as string,\n        '--config-file',\n        configFile,\n        '--strip-leading-paths',\n        '--copy-files'\n      ];\n    } else {\n      cliOptions = [\n        srcPath as string,\n        '-o',\n        distPath as string,\n        '--config-file',\n        configFile,\n        '--strip-leading-paths'\n      ];\n    }\n\n    try {\n      // Delete the dist directory if it exists using rimraf\n      await fsp.rm(distPath as string, { recursive: true, force: true });\n      await execa('swc', cliOptions, {\n        cwd: path.resolve(srcPath as string, '..')\n      });\n    } catch (err) {\n      error(`Error compiling ${srcPath}:`);\n      throw err;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/effect.ts",
    "content": "import { existsSync } from 'fs';\nimport { basename, dirname } from 'path';\nimport { Application } from 'express';\nimport { minimatch } from 'minimatch';\nimport { has } from '../../../bin/dev/register.js';\nimport { getEnabledJobs } from '../../../lib/cronjob/jobManager.js';\nimport { debug, error } from '../../../lib/log/logger.js';\nimport { getRoute } from '../../../lib/router/Router.js';\nimport { broadcast } from './broadcast.js';\nimport { isRestartRequired } from './isRestartRequired.js';\nimport { isSrc } from './isSrc.js';\nimport { processors } from './processors/index.js';\nimport { Event } from './watchHandler.js';\n\nexport type Effect =\n  | 'restart'\n  | 'restart_cronjob'\n  | 'restart_event'\n  | 'add_middleware'\n  | 'remove_middleware'\n  | 'update_middleware'\n  | 'add_component'\n  | 'remove_component'\n  | 'update_component'\n  | 'add_api_route'\n  | 'remove_api_route'\n  | 'update_api_route'\n  | 'add_admin_route'\n  | 'remove_admin_route'\n  | 'update_admin_route'\n  | 'add_front_store_route'\n  | 'remove_front_store_route'\n  | 'update_front_store_route'\n  | 'update_graphql'\n  | 'unknown';\n\nfunction isValidRouteFolder(name: string): boolean {\n  const segments = name.split('+');\n  // Make sure all segment match this regex: /^[a-zA-Z]+$/\n  return segments.every((segment) => /^[a-zA-Z]+$/.test(segment));\n}\n\nexport function detectEffect(event: Event): Effect {\n  const jobs = getEnabledJobs();\n  if (isRestartRequired(event)) {\n    return 'restart'; // No specific effect, just a restart required\n  } else if (minimatch(event.path.toString(), '**/*/[A-Z]*.+(jsx|tsx)')) {\n    const routeFolder = basename(dirname(event.path.toString()));\n    if (!isValidRouteFolder(routeFolder)) {\n      return 'unknown'; // Not a valid route folder, skip\n    }\n    if (event.type === 'create') {\n      if (\n        minimatch(\n          event.path.toString(),\n          '**/pages/+(admin|frontStore)/[A-Z]*.+(jsx|tsx)'\n        )\n      ) {\n        return 'update_component';\n      } else {\n        return 'add_component';\n      }\n    } else if (event.type === 'delete') {\n      return 'remove_component';\n    } else {\n      return 'update_component';\n    }\n  } else if (minimatch(event.path.toString(), '**/+(api|admin|frontStore)/*')) {\n    const fileName = basename(event.path.toString());\n    if (!isValidRouteFolder(fileName)) {\n      return 'unknown'; // Not a valid route folder, skip\n    }\n    if (event.type === 'delete') {\n      const route = getRoute(fileName);\n      if (route) {\n        const routePath = route.path;\n        if (!existsSync(routePath)) {\n          // If the route file does not exist, it means the route folder is deleted. We can safely delete the route.\n          if (route.isApi) {\n            return 'remove_api_route';\n          } else if (route.isAdmin) {\n            return 'remove_admin_route';\n          } else {\n            return 'remove_front_store_route';\n          }\n        } else {\n          return 'remove_middleware'; // The route folder still exists, so we just need to remove the middleware\n        }\n      } else {\n        // This folder is not representing a route, so we just need to take care of midldleware functions\n        return 'remove_middleware';\n      }\n    } else {\n      return 'unknown';\n    }\n  } else if (\n    minimatch(\n      event.path.toString(),\n      '**/+(api|admin|frontStore)/*/[a-z[]*.+(js|ts)'\n    )\n  ) {\n    const routeFolder = basename(dirname(event.path.toString()));\n    if (!isValidRouteFolder(routeFolder)) {\n      return 'unknown'; // Not a valid route folder, skip\n    }\n    if (event.type === 'create') {\n      return 'add_middleware'; // This is a middleware file\n    } else if (event.type === 'delete') {\n      return 'remove_middleware';\n    } else {\n      return 'update_middleware';\n    }\n  } else if (minimatch(event.path.toString(), '**/api/*/route.json')) {\n    const routeFolder = basename(dirname(event.path.toString()));\n    if (!isValidRouteFolder(routeFolder)) {\n      return 'unknown'; // Not a valid route folder, skip\n    }\n    if (event.type === 'create') {\n      return 'add_api_route';\n    } else if (event.type === 'delete') {\n      return 'remove_api_route';\n    } else {\n      return 'update_api_route';\n    }\n  } else if (minimatch(event.path.toString(), '**/api/*/payloadSchema.json')) {\n    // This is a payload schema file for an API route\n    const routeFolder = basename(dirname(event.path.toString()));\n    if (!isValidRouteFolder(routeFolder)) {\n      return 'unknown'; // Not a valid route folder, skip\n    }\n    return 'update_api_route';\n  } else if (minimatch(event.path.toString(), '**/pages/admin/*/route.json')) {\n    const routeFolder = basename(dirname(event.path.toString()));\n    if (!isValidRouteFolder(routeFolder)) {\n      return 'unknown'; // Not a valid route folder, skip\n    }\n    if (event.type === 'create') {\n      return 'add_admin_route';\n    } else if (event.type === 'delete') {\n      return 'remove_admin_route';\n    } else {\n      return 'update_admin_route';\n    }\n  } else if (\n    minimatch(event.path.toString(), '**/pages/frontStore/*/route.json')\n  ) {\n    const routeFolder = basename(dirname(event.path.toString()));\n    if (!isValidRouteFolder(routeFolder)) {\n      return 'unknown'; // Not a valid route folder, skip\n    }\n    if (event.type === 'create') {\n      return 'add_front_store_route';\n    } else if (event.type === 'delete') {\n      return 'remove_front_store_route';\n    } else {\n      return 'update_front_store_route';\n    }\n  } else if (\n    minimatch(event.path.toString(), '**/*/*.graphql') ||\n    minimatch(event.path.toString(), '**/*/*.resolvers.+(ts|js)')\n  ) {\n    return 'update_graphql'; // GraphQL schema or resolvers file\n  } else if (minimatch(event.path.toString(), '**/subscribers/**/*.+(ts|js)')) {\n    return 'restart_event';\n  }\n  // Check if the file is a job file\n  else if (\n    event.path &&\n    jobs.some(\n      (job) =>\n        job.resolve ===\n        event.path.toString().replace('src', 'dist').replace(/\\.ts$/, '.js')\n    )\n  ) {\n    return 'restart_cronjob';\n  } else if (isSrc(event.path.toString())) {\n    const distPath = event.path\n      .toString()\n      .replace('src', 'dist')\n      .replace(/\\.ts$/, '.js');\n    if (has(distPath)) {\n      // This module is being used in the application, so we need to restart the process\n      return 'restart';\n    } else {\n      return 'unknown'; // This is a source file, but not used in the application\n    }\n  } else {\n    const distPath = event.path.toString();\n    if (has(distPath)) {\n      // This module is being used in the application, so we need to restart the process\n      return 'restart';\n    } else {\n      return 'unknown'; // This is a source file, but not used in the application\n    }\n  }\n}\n\nexport function applyEffects(events: Event[], app: Application) {\n  for (const event of events) {\n    if (!event.effect) {\n      continue; // Skip if no effect is detected\n    } else {\n      const processor = processors[event.effect];\n      if (processor) {\n        try {\n          debug(`Applying changes: ${event.effect} for ${event.path}`);\n          processor(app, event);\n        } catch (e) {\n          error(`Error applying changes for ${event.path}:`);\n          error(e);\n        }\n      } else {\n        debug(`No processor found for effect type: ${event.effect}`);\n      }\n    }\n  }\n  // Call broadcast to notify all clients about the changes if there are any known effects\n  if (\n    events.some(\n      (e) =>\n        e.effect &&\n        !['unknown', 'restart_cronjob', 'restart_event'].includes(e.effect) &&\n        !e.effect.includes('component')\n    )\n  ) {\n    debug('Broadcasting changes to all clients');\n    broadcast();\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/getDistPaths.ts",
    "content": "import { PathLike } from 'fs';\n\nexport function getDistPaths(): PathLike[] {\n  return ['dist', 'packages/evershop/dist', 'packages/agegate/dist'];\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/getRootPaths.ts",
    "content": "import { PathLike } from 'fs';\nimport path from 'path';\nimport type { Event } from './watchHandler.js';\n/**\n * Deduplicates a list of paths, keeping only the top-most created folders.\n * @param {Array<{ path: string, type: string }>} entries\n * @returns {Event[]} Top-level unique root folders\n */\nexport function getRootPaths(entries: Event[]): Event[] {\n  const sortedPaths = entries\n    .map((entry) => path.resolve(entry.path as string))\n    .sort();\n\n  const roots: Event[] = [];\n\n  for (const current of sortedPaths) {\n    if (!roots.some((root) => current.startsWith(root.path + path.sep))) {\n      roots.push({\n        path: current,\n        type: entries.find((entry) => entry.path === current)?.type || 'create'\n      });\n    }\n  }\n\n  return roots;\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/getSrcPaths.ts",
    "content": "import { PathLike } from 'fs';\nimport path from 'path';\nimport { getEnabledExtensions } from '../../../bin/extension/index.js';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { getEnabledTheme } from '../../../lib/util/getEnabledTheme.js';\n\nexport function getSrcPaths(): PathLike[] {\n  const extensions = getEnabledExtensions();\n  const theme = getEnabledTheme();\n  return extensions\n    .filter((ext) => ext.srcPath)\n    .map((ext) => ext.srcPath as PathLike)\n    .concat(\n      !CONSTANTS.MODULESPATH.includes('node_modules')\n        ? (path.resolve(\n            CONSTANTS.ROOTPATH,\n            'packages/evershop/src/'\n          ) as PathLike)\n        : []\n    )\n    .concat(theme?.srcPath ? (theme.srcPath as PathLike) : []);\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/isDist.js",
    "content": "import path from 'path';\nimport { getDistPaths } from './getDistPaths.js';\n\nexport function isDist(pathName) {\n  if (\n    getDistPaths().some((distPath) => pathName.startsWith(distPath + path.sep))\n  ) {\n    return true;\n  } else {\n    return false;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/isRestartRequired.ts",
    "content": "import path from 'path';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { isSrc } from './isSrc.js';\nimport { Event } from './watchHandler.js';\n\nexport function isRestartRequired(event: Event) {\n  if (isSrc(event.path)) {\n    return false;\n  } else if (event.path === path.resolve(CONSTANTS.ROOTPATH, '.env')) {\n    // If the .env file is changed, we need to restart the server\n    return true;\n  } else {\n    const configPath = path.resolve(CONSTANTS.ROOTPATH, 'config');\n    if (\n      event.path.toString().startsWith(configPath) &&\n      path.extname(event.path as string) === '.json'\n    ) {\n      // If a config JSON file is changed, we need to restart the server\n      return true;\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/isSrc.js",
    "content": "import path from 'path';\nimport { getSrcPaths } from './getSrcPaths.js';\n\nexport function isSrc(pathName) {\n  if (\n    getSrcPaths().some((srcPath) => pathName.startsWith(srcPath + path.sep))\n  ) {\n    return true;\n  } else {\n    return false;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/addAdminRoute.ts",
    "content": "import { Application } from 'express';\nimport { warning } from '../../../../lib/log/logger.js';\nimport { addRoute, hasRoute } from '../../../../lib/router/Router.js';\nimport { parseRoute } from '../../../../lib/router/scanForRoutes.js';\nimport { Event } from '../watchHandler.js';\n\nexport function addAdminRoute(app: Application, event: Event) {\n  try {\n    const jsonPath = event.path.toString();\n    const route = parseRoute(jsonPath, true, false);\n    if (hasRoute(route?.id)) {\n      warning(`Route ${route?.id} already exists. Skipping adding new route.`);\n    } else {\n      addRoute(route);\n    }\n  } catch (error) {\n    warning(\n      `Failed to add new route from ${event.path}: ${error.message}. Skipping.`\n    );\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/addApiRoute.ts",
    "content": "import { Application } from 'express';\nimport { warning } from '../../../../lib/log/logger.js';\nimport { Handler } from '../../../../lib/middleware/Handler.js';\nimport { addRoute, hasRoute } from '../../../../lib/router/Router.js';\nimport { parseRoute } from '../../../../lib/router/scanForRoutes.js';\nimport { Event } from '../watchHandler.js';\n\nexport function addApiRoute(app: Application, event: Event) {\n  try {\n    const jsonPath = event.path.toString();\n    const route = parseRoute(jsonPath, false, true);\n    if (!route || hasRoute(route?.id)) {\n      warning(`Route ${route?.id} already exists. Skipping adding new route.`);\n    } else {\n      addRoute(route);\n      for (const method of route.methods) {\n        switch (method.toUpperCase()) {\n          case 'GET':\n            app.get(route.path, Handler.middleware());\n            break;\n          case 'POST':\n            app.post(route.path, Handler.middleware());\n            break;\n          case 'PUT':\n            app.put(route.path, Handler.middleware());\n            break;\n          case 'DELETE':\n            app.delete(route.path, Handler.middleware());\n            break;\n          case 'PATCH':\n            app.patch(route.path, Handler.middleware());\n            break;\n          default:\n            app.get(route.path, Handler.middleware());\n            break;\n        }\n      }\n    }\n  } catch (error) {\n    warning(\n      `Failed to add new route from ${event.path}: ${error.message}. Skipping.`\n    );\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/addComponent.ts",
    "content": "import { Application } from 'express';\nimport { warning } from '../../../../lib/log/logger.js';\nimport { Event } from '../watchHandler.js';\nexport function addComponent(app: Application, event: Event) {\n  // Do nothing. Let ThemeWatcherPlugin handle this.\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/addFrontStoreRoute.ts",
    "content": "import { Application } from 'express';\nimport { warning } from '../../../../lib/log/logger.js';\nimport { addRoute, hasRoute } from '../../../../lib/router/Router.js';\nimport { parseRoute } from '../../../../lib/router/scanForRoutes.js';\nimport { Event } from '../watchHandler.js';\n\nexport function addFrontStoreRoute(app: Application, event: Event) {\n  try {\n    const jsonPath = event.path.toString();\n    const route = parseRoute(jsonPath, false, false);\n    if (hasRoute(route?.id)) {\n      warning(`Route ${route?.id} already exists. Skipping adding new route.`);\n    } else {\n      addRoute(route);\n    }\n  } catch (error) {\n    warning(\n      `Failed to add new route from ${event.path}: ${error.message}. Skipping.`\n    );\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/addMiddleware.ts",
    "content": "import { Application } from 'express';\nimport { warning } from '../../../../lib/log/logger.js';\nimport { Handler } from '../../../../lib/middleware/Handler.js';\nimport { Event } from '../watchHandler.js';\n\nexport function addMiddleware(app: Application, event: Event) {\n  try {\n    const filePath = event.jsPath?.toString();\n    Handler.addMiddlewareFromPath(filePath);\n  } catch (error) {\n    warning(\n      `Failed to add new middleware from ${event.jsPath}: ${error.message}. Skipping.`\n    );\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/deleteARoute.ts",
    "content": "import { basename, dirname } from 'path';\nimport { Application } from 'express';\nimport { warning } from '../../../../lib/log/logger.js';\nimport { deleteRoute, hasRoute } from '../../../../lib/router/Router.js';\nimport { Event } from '../watchHandler.js';\n\nexport function deleteARoute(app: Application, event: Event) {\n  try {\n    const jsonPath = event.path.toString();\n    const routeId = jsonPath.includes('route.json')\n      ? basename(dirname(jsonPath))\n      : basename(jsonPath);\n    if (hasRoute(routeId)) {\n      deleteRoute(routeId);\n    }\n  } catch (error) {\n    warning(\n      `Failed to delete route from ${event.path}: ${error.message}. Skipping.`\n    );\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/index.ts",
    "content": "import { Application } from 'express';\nimport { Effect } from '../effect.js';\nimport { addAdminRoute } from './addAdminRoute.js';\nimport { addApiRoute } from './addApiRoute.js';\nimport { addComponent } from './addComponent.js';\nimport { addFrontStoreRoute } from './addFrontStoreRoute.js';\nimport { addMiddleware } from './addMiddleware.js';\nimport { deleteARoute } from './deleteARoute.js';\nimport { removeMiddleware } from './removeMiddleware.js';\nimport { restartCronJob } from './restartCronJob.js';\nimport { restartSubscriber } from './restartSubscriber.js';\nimport { updateAdminRoute } from './updateAdminRoute.js';\nimport { updateApiRoute } from './updateApiRoute.js';\nimport { updateFrontStoreRoute } from './updateFrontStoreRoute.js';\n\nexport type Processor = {\n  [key in Effect]?: (app: Application, event: any) => void;\n};\n\nexport const processors: Processor = {\n  add_api_route: addApiRoute,\n  update_api_route: updateApiRoute,\n  add_front_store_route: addFrontStoreRoute,\n  update_front_store_route: updateFrontStoreRoute,\n  add_admin_route: addAdminRoute,\n  update_admin_route: updateAdminRoute,\n  remove_api_route: deleteARoute,\n  remove_admin_route: deleteARoute,\n  remove_front_store_route: deleteARoute,\n  add_middleware: addMiddleware,\n  remove_middleware: removeMiddleware,\n  update_middleware: () => {},\n  update_component: () => {\n    // No operation for update_component, as it is handled by the compiler}\n  },\n  remove_component: () => {\n    // No operation for update_component, as it is handled by the compiler}\n  },\n  add_component: addComponent,\n  update_graphql: () => {},\n  restart_cronjob: () => {\n    restartCronJob();\n  },\n  restart_event: () => {\n    restartSubscriber();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/removeMiddleware.ts",
    "content": "import { Application } from 'express';\nimport { warning } from '../../../../lib/log/logger.js';\nimport { Handler } from '../../../../lib/middleware/Handler.js';\nimport { Event } from '../watchHandler.js';\n\nexport function removeMiddleware(app: Application, event: Event) {\n  try {\n    const filePath = event.jsPath?.toString();\n    Handler.removeMiddlewares(filePath);\n  } catch (error) {\n    warning(\n      `Failed to remove middleware from ${event.jsPath}: ${error.message}. Skipping.`\n    );\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/restart.ts",
    "content": "export function restartProcess() {\n  process.exit(100);\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/restartCronJob.ts",
    "content": "export function restartCronJob() {\n  (process as NodeJS.EventEmitter).emit('RESTART_CRONJOB');\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/restartSubscriber.ts",
    "content": "export function restartSubscriber() {\n  (process as NodeJS.EventEmitter).emit('RESTART_SUBSCRIBER');\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/touch.js",
    "content": "import { resolve } from 'path';\nimport touch from 'touch';\nimport { CONSTANTS } from '../../../../lib/helpers.js';\n\nexport function justATouch(path) {\n  touch(\n    path ||\n      resolve(\n        CONSTANTS.MODULESPATH,\n        '../components/common/react/client/Index.js'\n      )\n  );\n}\n\nexport async function touchList(paths) {\n  await Promise.all(paths.map((p) => touch(p)));\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/updateAdminRoute.ts",
    "content": "import { dirname, join } from 'path';\nimport { Application } from 'express';\nimport { warning } from '../../../../lib/log/logger.js';\nimport { addRoute } from '../../../../lib/router/Router.js';\nimport { parseRoute } from '../../../../lib/router/scanForRoutes.js';\nimport { Event } from '../watchHandler.js';\n\nexport function updateAdminRoute(app: Application, event: Event) {\n  try {\n    const jsonPath = event.path.toString();\n    const route = parseRoute(\n      join(dirname(jsonPath), 'route.json'),\n      true,\n      false\n    );\n    addRoute(route);\n  } catch (error) {\n    warning(`Failed to update route from ${event.path}: ${error.message}`);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/updateApiRoute.ts",
    "content": "import { dirname, join } from 'path';\nimport { Application } from 'express';\nimport { warning } from '../../../../lib/log/logger.js';\nimport { addRoute } from '../../../../lib/router/Router.js';\nimport { parseRoute } from '../../../../lib/router/scanForRoutes.js';\nimport { Event } from '../watchHandler.js';\n\nexport function updateApiRoute(app: Application, event: Event) {\n  try {\n    const jsonPath = event.path.toString();\n    const route = parseRoute(\n      join(dirname(jsonPath), 'route.json'),\n      false,\n      true\n    );\n    addRoute(route);\n  } catch (error) {\n    warning(`Failed to update route from ${event.path}: ${error.message}`);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/processors/updateFrontStoreRoute.ts",
    "content": "import { dirname, join } from 'path';\nimport { Application } from 'express';\nimport { warning } from '../../../../lib/log/logger.js';\nimport { addRoute } from '../../../../lib/router/Router.js';\nimport { parseRoute } from '../../../../lib/router/scanForRoutes.js';\nimport { Event } from '../watchHandler.js';\n\nexport function updateFrontStoreRoute(app: Application, event: Event) {\n  try {\n    const jsonPath = event.path.toString();\n    const route = parseRoute(\n      join(dirname(jsonPath), 'route.json'),\n      false,\n      false\n    );\n    addRoute(route);\n  } catch (error) {\n    warning(`Failed to update route from ${event.path}: ${error.message}`);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/lib/watch/watchHandler.ts",
    "content": "import { PathLike, readdirSync, rmSync, statSync } from 'fs';\nimport path from 'path';\nimport { Application } from 'express';\nimport { error } from '../../../lib/log/logger.js';\nimport { compileSwc } from './compileSwc.js';\nimport { applyEffects, detectEffect, Effect } from './effect.js';\nimport { isDist } from './isDist.js';\nimport { isSrc } from './isSrc.js';\nimport { restartProcess } from './processors/restart.js';\n\nexport type Event = {\n  path: PathLike;\n  type: 'create' | 'update' | 'delete';\n  jsPath?: PathLike;\n  effect?: Effect;\n};\n\nexport async function watchHandler(events: Event[], app: Application) {\n  if (\n    events.length === 2 &&\n    events.some((e) => e.type === 'delete') &&\n    events.some((e) => e.type === 'create')\n  ) {\n    // Likely a rename\n    // Sort the event make sure the delete comes first\n    events.sort((a, b) => (a.type === 'delete' ? -1 : 1));\n    // Travel the create event and if this is a folder, we need to add create event for every sub-file\n    for (const event of events) {\n      if (event.type === 'create') {\n        // Check if the path is a directory\n        try {\n          const stats = statSync(event.path);\n          if (stats.isDirectory()) {\n            // If it's a directory, we need to add create events for every file in the directory\n            const files = readdirSync(event.path);\n            for (const file of files) {\n              const filePath = path.resolve(event.path as string, file);\n              events.push({\n                path: filePath,\n                type: 'create'\n              });\n            }\n          }\n        } catch (e) {\n          error(`Error reading directory ${event.path}:`);\n          error(e);\n        }\n      }\n    }\n  }\n\n  // Handle the watch event\n  for (const event of events) {\n    event.effect = detectEffect(event);\n    if (event.effect === 'restart') {\n      restartProcess();\n      break; // Exit the loop if a restart is required, no need to process further\n    }\n    if (isDist(event.path)) {\n      continue;\n    }\n    if (isSrc(event.path)) {\n      const distPath = event.path\n        .toString()\n        .replace('src', 'dist')\n        .replace(/\\.ts$/, '.js')\n        .replace(/\\.tsx$/, '.js')\n        .replace(/\\.jsx$/, '.js'); // Ensure the path ends with .js\n\n      event.jsPath = distPath; // Set the compiled JS path\n      if (event.type === 'delete') {\n        // Delete whatever is necessary in the dist folder\n        rmSync(distPath as string, { recursive: true, force: true });\n      } else {\n        // Run swc to compile the files\n        // Get the dist path from the event by replacing the first 'src' with 'dist' and ts to js if this is a ts file\n        await compileSwc(event.path, distPath);\n      }\n    }\n  }\n  applyEffects(events, app);\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/data/attributes.json",
    "content": "[\n  {\n    \"attribute_code\": \"color\",\n    \"attribute_name\": \"Color\",\n    \"type\": \"select\",\n    \"is_required\": 1,\n    \"display_on_frontend\": 1,\n    \"sort_order\": 10,\n    \"is_filterable\": 1,\n    \"groups\": [],\n    \"options\": [\n      { \"option_text\": \"Black\" },\n      { \"option_text\": \"White\" },\n      { \"option_text\": \"Red\" },\n      { \"option_text\": \"Blue\" },\n      { \"option_text\": \"Green\" },\n      { \"option_text\": \"Yellow\" },\n      { \"option_text\": \"Pink\" },\n      { \"option_text\": \"Gray\" },\n      { \"option_text\": \"Navy\" },\n      { \"option_text\": \"Beige\" }\n    ]\n  },\n  {\n    \"attribute_code\": \"size\",\n    \"attribute_name\": \"Size\",\n    \"type\": \"select\",\n    \"is_required\": 1,\n    \"display_on_frontend\": 1,\n    \"sort_order\": 20,\n    \"is_filterable\": 1,\n    \"groups\": [],\n    \"options\": [\n      { \"option_text\": \"XS\" },\n      { \"option_text\": \"S\" },\n      { \"option_text\": \"M\" },\n      { \"option_text\": \"L\" },\n      { \"option_text\": \"XL\" },\n      { \"option_text\": \"XXL\" }\n    ]\n  }\n]\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/data/categories.json",
    "content": "[\n  {\n    \"name\": \"Accessories\",\n    \"url_key\": \"accessories\",\n    \"description\": [\n      {\n        \"id\": \"r__accessories\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__accessories\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"acc_block_1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Complete your look with our stylish accessories\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"meta_title\": \"Fashion Accessories\",\n    \"meta_description\": \"Browse our collection of fashion accessories\",\n    \"meta_keywords\": \"accessories, fashion accessories, style\",\n    \"status\": 1,\n    \"include_in_nav\": 1\n  }\n]\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/data/collections.json",
    "content": "[\n  {\n    \"name\": \"Featured Products\",\n    \"code\": \"homepage\",\n    \"description\": [\n      {\n        \"id\": \"r__featured\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__featured\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"fp_block_1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Featured products displayed on the homepage\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ]\n  },\n  {\n    \"name\": \"Summer Collection\",\n    \"code\": \"summer-2024\",\n    \"description\": [\n      {\n        \"id\": \"r__summer\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__summer\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"sc_block_1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Hot picks for the summer season\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ]\n  },\n  {\n    \"name\": \"Winter Essentials\",\n    \"code\": \"winter-essentials\",\n    \"description\": [\n      {\n        \"id\": \"r__winter\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__winter\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"we_block_1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Stay warm and stylish this winter\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ]\n  },\n  {\n    \"name\": \"Trending Now\",\n    \"code\": \"trending\",\n    \"description\": [\n      {\n        \"id\": \"r__trending\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__trending\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"tn_block_1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"What's hot and trending right now\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/data/pages.json",
    "content": "[\n  {\n    \"status\": true,\n    \"url_key\": \"about-us\",\n    \"name\": \"About Us\",\n    \"content\": [\n      {\n        \"id\": \"r__about_us\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__about_us\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"about_us_h2\",\n                  \"type\": \"header\",\n                  \"data\": {\n                    \"text\": \"Welcome to Our Store\",\n                    \"level\": 2\n                  }\n                },\n                {\n                  \"id\": \"about_us_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"We are passionate about bringing you high-quality ceramic and stainless steel products that combine functionality with elegant design. Our carefully curated collection features items that enhance your daily life, from morning coffee to home organization.\"\n                  }\n                },\n                {\n                  \"id\": \"about_us_h2\",\n                  \"type\": \"header\",\n                  \"data\": {\n                    \"text\": \"Our Mission\",\n                    \"level\": 2\n                  }\n                },\n                {\n                  \"id\": \"about_us_img1\",\n                  \"type\": \"image\",\n                  \"data\": {\n                    \"file\": {\n                      \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/banner-one.jpg\",\n                      \"width\": 2400,\n                      \"height\": 1200\n                    },\n                    \"caption\": \"Our carefully curated collection\",\n                    \"withBorder\": false,\n                    \"withBackground\": false,\n                    \"stretched\": false\n                  }\n                },\n                {\n                  \"id\": \"about_us_p2\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"We believe that everyday objects should be both beautiful and practical. That's why we source products that are not only aesthetically pleasing but also durable and functional. Each item in our collection is selected with care to ensure it meets our high standards.\"\n                  }\n                },\n                {\n                  \"id\": \"about_us_h3\",\n                  \"type\": \"header\",\n                  \"data\": {\n                    \"text\": \"Quality You Can Trust\",\n                    \"level\": 2\n                  }\n                },\n                {\n                  \"id\": \"about_us_p3\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"All our products are made from premium materials - from food-safe ceramics to BPA-free stainless steel. We work directly with manufacturers who share our commitment to quality and sustainability. Whether you're looking for office supplies, drinkware, or home decor, you can trust that every item has been thoughtfully designed and rigorously tested.\"\n                  }\n                },\n                {\n                  \"id\": \"about_us_h4\",\n                  \"type\": \"header\",\n                  \"data\": {\n                    \"text\": \"Customer Satisfaction\",\n                    \"level\": 2\n                  }\n                },\n                {\n                  \"id\": \"about_us_p4\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Your satisfaction is our top priority. We offer fast shipping, easy returns, and dedicated customer support to ensure your shopping experience is seamless. If you have any questions about our products or need assistance, our team is always here to help.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"meta_title\": \"About Us - Learn More About Our Store\",\n    \"meta_keywords\": \"about us, our story, company information\",\n    \"meta_description\": \"Learn more about our mission to bring you high-quality ceramic and stainless steel products that combine functionality with elegant design.\"\n  }\n]\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/data/products.json",
    "content": "[\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"CUP-001-WHT\",\n    \"name\": \"Ceramic Coffee Cup - White\",\n    \"url_key\": \"ceramic-coffee-cup-white\",\n    \"price\": 15,\n    \"weight\": 300,\n    \"meta_title\": \"Ceramic Coffee Cup - White\",\n    \"meta_description\": \"Modern ceramic coffee cup in white color, perfect for your morning coffee\",\n    \"meta_keywords\": \"coffee cup, ceramic cup, white cup, drinkware\",\n    \"description\": [\n      {\n        \"id\": \"r__cup_white\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__cup_white\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"cup_white_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Start your day right with our elegant Ceramic Coffee Cup. Crafted from high-quality ceramic, this cup features a smooth finish and comfortable grip.\"\n                  }\n                },\n                {\n                  \"id\": \"cup_white_p2\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"The classic design makes it perfect for both home and office use. Holds 12oz of your favorite beverage and is both microwave and dishwasher safe.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 100,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"best-sellers\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"ceramic-coffee-cup\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/cup-white.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"White\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"CUP-001-BLK\",\n    \"name\": \"Ceramic Coffee Cup - Black\",\n    \"url_key\": \"ceramic-coffee-cup-black\",\n    \"price\": 15,\n    \"weight\": 300,\n    \"meta_title\": \"Ceramic Coffee Cup - Black\",\n    \"meta_description\": \"Modern ceramic coffee cup in black color, perfect for your morning coffee\",\n    \"meta_keywords\": \"coffee cup, ceramic cup, black cup, drinkware\",\n    \"description\": [\n      {\n        \"id\": \"r__cup_black\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__cup_black\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"cup_black_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Start your day right with our elegant Ceramic Coffee Cup. Crafted from high-quality ceramic, this cup features a smooth finish and comfortable grip.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 100,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"best-sellers\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"ceramic-coffee-cup\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/cup-black.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"Black\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"CUP-001-YEL\",\n    \"name\": \"Ceramic Coffee Cup - Yellow\",\n    \"url_key\": \"ceramic-coffee-cup-yellow\",\n    \"price\": 15,\n    \"weight\": 300,\n    \"meta_title\": \"Ceramic Coffee Cup - Yellow\",\n    \"meta_description\": \"Modern ceramic coffee cup in yellow color, perfect for your morning coffee\",\n    \"meta_keywords\": \"coffee cup, ceramic cup, yellow cup, drinkware\",\n    \"description\": [\n      {\n        \"id\": \"r__cup_yellow\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__cup_yellow\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"cup_yellow_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Start your day right with our elegant Ceramic Coffee Cup. Crafted from high-quality ceramic, this cup features a smooth finish and comfortable grip.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 100,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"best-sellers\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"ceramic-coffee-cup\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/cup-yellow.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"Yellow\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"PEN-002-WHT\",\n    \"name\": \"Desk Pen Holder - White\",\n    \"url_key\": \"desk-pen-holder-white\",\n    \"price\": 12,\n    \"weight\": 200,\n    \"meta_title\": \"Desk Pen Holder - White\",\n    \"meta_description\": \"Stylish desk pen holder to keep your workspace organized\",\n    \"meta_keywords\": \"pen holder, desk organizer, office supplies, white\",\n    \"description\": [\n      {\n        \"id\": \"r__pen_white\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__pen_white\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"pen_white_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Keep your desk tidy and organized with our modern Desk Pen Holder. Features multiple compartments for pens, pencils, scissors, and other office supplies.\"\n                  }\n                },\n                {\n                  \"id\": \"pen_white_p2\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Made from durable materials with a sleek finish that complements any workspace. Perfect for home office or corporate settings.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 150,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"new-arrivals\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"desk-pen-holder\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/pen-holder-white.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"White\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"PEN-002-BLK\",\n    \"name\": \"Desk Pen Holder - Black\",\n    \"url_key\": \"desk-pen-holder-black\",\n    \"price\": 12,\n    \"weight\": 200,\n    \"meta_title\": \"Desk Pen Holder - Black\",\n    \"meta_description\": \"Stylish desk pen holder to keep your workspace organized\",\n    \"meta_keywords\": \"pen holder, desk organizer, office supplies, black\",\n    \"description\": [\n      {\n        \"id\": \"r__pen_black\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__pen_black\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"pen_black_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Keep your desk tidy and organized with our modern Desk Pen Holder. Features multiple compartments for pens, pencils, scissors, and other office supplies.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 150,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"new-arrivals\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"desk-pen-holder\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/pen-holder-black.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"Black\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"PEN-002-YEL\",\n    \"name\": \"Desk Pen Holder - Yellow\",\n    \"url_key\": \"desk-pen-holder-yellow\",\n    \"price\": 12,\n    \"weight\": 200,\n    \"meta_title\": \"Desk Pen Holder - Yellow\",\n    \"meta_description\": \"Stylish desk pen holder to keep your workspace organized\",\n    \"meta_keywords\": \"pen holder, desk organizer, office supplies, yellow\",\n    \"description\": [\n      {\n        \"id\": \"r__pen_yellow\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__pen_yellow\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"pen_yellow_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Keep your desk tidy and organized with our modern Desk Pen Holder. Features multiple compartments for pens, pencils, scissors, and other office supplies.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 150,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"new-arrivals\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"desk-pen-holder\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/pen-holder-yellow.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"Yellow\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"BOWL-003-WHT\",\n    \"name\": \"Ceramic Candy Bowl - White\",\n    \"url_key\": \"ceramic-candy-bowl-white\",\n    \"price\": 18,\n    \"weight\": 400,\n    \"meta_title\": \"Ceramic Candy Bowl - White\",\n    \"meta_description\": \"Elegant ceramic bowl perfect for candy, snacks, or decorative use\",\n    \"meta_keywords\": \"candy bowl, ceramic bowl, serving bowl, white\",\n    \"description\": [\n      {\n        \"id\": \"r__bowl_white\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__bowl_white\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"bowl_white_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Add a touch of elegance to your table with our Ceramic Candy Bowl. Perfect for serving candy, nuts, or small snacks at parties and gatherings.\"\n                  }\n                },\n                {\n                  \"id\": \"bowl_white_p2\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"The smooth ceramic finish and timeless design make it both functional and decorative. Also great for holding keys, jewelry, or other small items.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 80,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"best-sellers\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"ceramic-candy-bowl\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/bowl-white.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"White\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"BOWL-003-BLK\",\n    \"name\": \"Ceramic Candy Bowl - Black\",\n    \"url_key\": \"ceramic-candy-bowl-black\",\n    \"price\": 18,\n    \"weight\": 400,\n    \"meta_title\": \"Ceramic Candy Bowl - Black\",\n    \"meta_description\": \"Elegant ceramic bowl perfect for candy, snacks, or decorative use\",\n    \"meta_keywords\": \"candy bowl, ceramic bowl, serving bowl, black\",\n    \"description\": [\n      {\n        \"id\": \"r__bowl_black\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__bowl_black\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"bowl_black_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Add a touch of elegance to your table with our Ceramic Candy Bowl. Perfect for serving candy, nuts, or small snacks at parties and gatherings.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 80,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"best-sellers\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"ceramic-candy-bowl\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/bowl-black.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"Black\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"BOWL-003-YEL\",\n    \"name\": \"Ceramic Candy Bowl - Yellow\",\n    \"url_key\": \"ceramic-candy-bowl-yellow\",\n    \"price\": 18,\n    \"weight\": 400,\n    \"meta_title\": \"Ceramic Candy Bowl - Yellow\",\n    \"meta_description\": \"Elegant ceramic bowl perfect for candy, snacks, or decorative use\",\n    \"meta_keywords\": \"candy bowl, ceramic bowl, serving bowl, yellow\",\n    \"description\": [\n      {\n        \"id\": \"r__bowl_yellow\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__bowl_yellow\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"bowl_yellow_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Add a touch of elegance to your table with our Ceramic Candy Bowl. Perfect for serving candy, nuts, or small snacks at parties and gatherings.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 80,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"best-sellers\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"ceramic-candy-bowl\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/bowl-yellow.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"Yellow\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"VASE-004-WHT\",\n    \"name\": \"Modern Ceramic Vase - White\",\n    \"url_key\": \"modern-ceramic-vase-white\",\n    \"price\": 25,\n    \"weight\": 500,\n    \"meta_title\": \"Modern Ceramic Vase - White\",\n    \"meta_description\": \"Contemporary ceramic vase perfect for fresh or dried flowers\",\n    \"meta_keywords\": \"vase, ceramic vase, flower vase, white, home decor\",\n    \"description\": [\n      {\n        \"id\": \"r__vase_white\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__vase_white\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"vase_white_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Elevate your home decor with our Modern Ceramic Vase. The sleek, contemporary design complements any interior style, from minimalist to traditional.\"\n                  }\n                },\n                {\n                  \"id\": \"vase_white_p2\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Perfect for displaying fresh flowers, dried arrangements, or as a standalone decorative piece. The sturdy ceramic construction ensures long-lasting beauty.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 60,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"new-arrivals\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"modern-ceramic-vase\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/vase-white.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"White\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"VASE-004-BLK\",\n    \"name\": \"Modern Ceramic Vase - Black\",\n    \"url_key\": \"modern-ceramic-vase-black\",\n    \"price\": 25,\n    \"weight\": 500,\n    \"meta_title\": \"Modern Ceramic Vase - Black\",\n    \"meta_description\": \"Contemporary ceramic vase perfect for fresh or dried flowers\",\n    \"meta_keywords\": \"vase, ceramic vase, flower vase, black, home decor\",\n    \"description\": [\n      {\n        \"id\": \"r__vase_black\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__vase_black\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"vase_black_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Elevate your home decor with our Modern Ceramic Vase. The sleek, contemporary design complements any interior style, from minimalist to traditional.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 60,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"new-arrivals\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"modern-ceramic-vase\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/vase-black.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"Black\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"VASE-004-YEL\",\n    \"name\": \"Modern Ceramic Vase - Green\",\n    \"url_key\": \"modern-ceramic-vase-green\",\n    \"price\": 25,\n    \"weight\": 500,\n    \"meta_title\": \"Modern Ceramic Vase - Green\",\n    \"meta_description\": \"Contemporary ceramic vase perfect for fresh or dried flowers\",\n    \"meta_keywords\": \"vase, ceramic vase, flower vase, green, home decor\",\n    \"description\": [\n      {\n        \"id\": \"r__vase_green\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__vase_green\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"vase_green_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Elevate your home decor with our Modern Ceramic Vase. The sleek, contemporary design complements any interior style, from minimalist to traditional.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 60,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"new-arrivals\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"modern-ceramic-vase\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/vase-green.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"Green\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"THERMO-005-WHT\",\n    \"name\": \"Stainless Steel Thermos - White\",\n    \"url_key\": \"stainless-steel-thermos-white\",\n    \"price\": 35,\n    \"weight\": 350,\n    \"meta_title\": \"Stainless Steel Thermos - White\",\n    \"meta_description\": \"Insulated stainless steel thermos keeps drinks hot or cold for hours\",\n    \"meta_keywords\": \"thermos, insulated bottle, water bottle, white, drinkware\",\n    \"description\": [\n      {\n        \"id\": \"r__thermo_white\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__thermo_white\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"thermo_white_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Keep your beverages at the perfect temperature with our Stainless Steel Thermos. Double-wall vacuum insulation keeps drinks hot for 12 hours or cold for 24 hours.\"\n                  }\n                },\n                {\n                  \"id\": \"thermo_white_p2\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"The leak-proof lid and durable stainless steel construction make it perfect for travel, work, or outdoor activities. BPA-free and easy to clean.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 120,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"best-sellers\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"stainless-steel-thermos\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/thermos-white.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"White\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"THERMO-005-BLK\",\n    \"name\": \"Stainless Steel Thermos - Black\",\n    \"url_key\": \"stainless-steel-thermos-black\",\n    \"price\": 35,\n    \"weight\": 350,\n    \"meta_title\": \"Stainless Steel Thermos - Black\",\n    \"meta_description\": \"Insulated stainless steel thermos keeps drinks hot or cold for hours\",\n    \"meta_keywords\": \"thermos, insulated bottle, water bottle, black, drinkware\",\n    \"description\": [\n      {\n        \"id\": \"r__thermo_black\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__thermo_black\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"thermo_black_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Keep your beverages at the perfect temperature with our Stainless Steel Thermos. Double-wall vacuum insulation keeps drinks hot for 12 hours or cold for 24 hours.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 120,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"best-sellers\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"stainless-steel-thermos\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/thermos-black.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"Black\"\n      }\n    ]\n  },\n  {\n    \"type\": \"simple\",\n    \"visibility\": true,\n    \"status\": true,\n    \"sku\": \"THERMO-005-YEL\",\n    \"name\": \"Stainless Steel Thermos - Yellow\",\n    \"url_key\": \"stainless-steel-thermos-yellow\",\n    \"price\": 35,\n    \"weight\": 350,\n    \"meta_title\": \"Stainless Steel Thermos - Yellow\",\n    \"meta_description\": \"Insulated stainless steel thermos keeps drinks hot or cold for hours\",\n    \"meta_keywords\": \"thermos, insulated bottle, water bottle, yellow, drinkware\",\n    \"description\": [\n      {\n        \"id\": \"r__thermo_yellow\",\n        \"columns\": [\n          {\n            \"size\": 1,\n            \"id\": \"c__thermo_yellow\",\n            \"data\": {\n              \"time\": 1729900000000,\n              \"blocks\": [\n                {\n                  \"id\": \"thermo_yellow_p1\",\n                  \"type\": \"paragraph\",\n                  \"data\": {\n                    \"text\": \"Keep your beverages at the perfect temperature with our Stainless Steel Thermos. Double-wall vacuum insulation keeps drinks hot for 12 hours or cold for 24 hours.\"\n                  }\n                }\n              ],\n              \"version\": \"2.31.0\"\n            }\n          }\n        ],\n        \"size\": 1,\n        \"className\": \"md:grid-cols-1\"\n      }\n    ],\n    \"qty\": 120,\n    \"manage_stock\": true,\n    \"stock_availability\": true,\n    \"collections\": [\"homepage\", \"best-sellers\"],\n    \"category\": \"accessories\",\n    \"variant_group\": \"stainless-steel-thermos\",\n    \"images\": [\n      {\n        \"url\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/thermos-yellow.jpg\",\n        \"isMain\": true\n      }\n    ],\n    \"attributes\": [\n      {\n        \"attribute_code\": \"color\",\n        \"value\": \"Yellow\"\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/data/widgets.json",
    "content": "[\n  {\n    \"name\": \"Main menu\",\n    \"type\": \"basic_menu\",\n    \"route\": [\"all\"],\n    \"area\": [\"headerMiddleLeft\"],\n    \"sort_order\": 1,\n    \"settings\": {\n      \"menus\": [\n        {\n          \"id\": \"hanhk3km0m8nt2b\",\n          \"url\": \"#\",\n          \"name\": \"Shop\",\n          \"type\": \"custom\",\n          \"uuid\": \"#\",\n          \"children\": [\n            {\n              \"id\": \"hanhk3km0m8nt2c\",\n              \"url\": \"/accessories\",\n              \"name\": \"Accessories\",\n              \"type\": \"custom\",\n              \"uuid\": \"/accessories\"\n            }\n          ]\n        },\n        {\n          \"id\": \"hanhk3km0m8nt2e\",\n          \"url\": \"/page/about-us\",\n          \"name\": \"About us\",\n          \"type\": \"custom\",\n          \"uuid\": \"/page/about-us\",\n          \"children\": []\n        }\n      ],\n      \"isMain\": \"1\",\n      \"className\": \"\"\n    },\n    \"status\": true\n  },\n  {\n    \"name\": \"Homepage Slideshow\",\n    \"type\": \"simple_slider\",\n    \"route\": [\"homepage\"],\n    \"area\": [\"content\"],\n    \"sort_order\": 5,\n    \"settings\": {\n      \"dots\": true,\n      \"arrows\": true,\n      \"slides\": [\n        {\n          \"id\": \"slide-1\",\n          \"image\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/banner-one.jpg\",\n          \"width\": 2400,\n          \"height\": 1200,\n          \"subText\": \"Discover our exquisite collection of ceramic and stainless steel products\",\n          \"headline\": \"Premium Quality Products\",\n          \"buttonLink\": \"/accessories\",\n          \"buttonText\": \"Shop Now\",\n          \"buttonColor\": \"#3a3a3a\"\n        },\n        {\n          \"id\": \"slide-2\",\n          \"image\": \"https://raw.githubusercontent.com/evershopcommerce/evershop/refs/heads/dev/seed/images/banner-two.jpg\",\n          \"width\": 2400,\n          \"height\": 1200,\n          \"subText\": \"Elegant designs that enhance your daily life, from morning coffee to home organization\",\n          \"headline\": \"Crafted With Care\",\n          \"buttonLink\": \"/accessories\",\n          \"buttonText\": \"View Collection\",\n          \"buttonColor\": \"#3a3a3a\"\n        }\n      ],\n      \"autoplay\": true,\n      \"fullWidth\": true,\n      \"heightType\": \"auto\",\n      \"autoplaySpeed\": 3000\n    },\n    \"status\": true\n  },\n  {\n    \"name\": \"Featured Products\",\n    \"type\": \"collection_products\",\n    \"route\": [\"homepage\"],\n    \"area\": [\"content\"],\n    \"sort_order\": 20,\n    \"settings\": {\n      \"count\": 4,\n      \"collection\": \"homepage\"\n    },\n    \"status\": true\n  }\n]\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/imageDownloader.ts",
    "content": "import { createWriteStream, existsSync, mkdirSync } from 'fs';\nimport http from 'http';\nimport https from 'https';\nimport { dirname } from 'path';\nimport { pipeline } from 'stream/promises';\nimport { info, warning } from '../../lib/log/logger.js';\n\n/**\n * Download an image from a URL and save it to a local file\n */\nexport async function downloadImage(\n  url: string,\n  outputPath: string\n): Promise<string> {\n  return new Promise((resolve, reject) => {\n    // Ensure directory exists\n    const dir = dirname(outputPath);\n    if (!existsSync(dir)) {\n      mkdirSync(dir, { recursive: true });\n    }\n\n    const client = url.startsWith('https') ? https : http;\n\n    const request = client.get(url, (response) => {\n      // Handle redirects\n      if (\n        response.statusCode === 301 ||\n        response.statusCode === 302 ||\n        response.statusCode === 307 ||\n        response.statusCode === 308\n      ) {\n        const redirectUrl = response.headers.location;\n        if (redirectUrl) {\n          info(`  → Following redirect to: ${redirectUrl}`);\n          downloadImage(redirectUrl, outputPath).then(resolve).catch(reject);\n          return;\n        }\n      }\n\n      if (response.statusCode !== 200) {\n        reject(\n          new Error(`Failed to download: HTTP ${response.statusCode} - ${url}`)\n        );\n        return;\n      }\n\n      const fileStream = createWriteStream(outputPath);\n\n      pipeline(response, fileStream)\n        .then(() => {\n          info(`  ✓ Downloaded: ${url} → ${outputPath}`);\n          resolve(outputPath);\n        })\n        .catch((err) => {\n          reject(new Error(`Failed to save file: ${err.message}`));\n        });\n    });\n\n    request.on('error', (err) => {\n      reject(new Error(`Download failed: ${err.message}`));\n    });\n\n    request.setTimeout(30000, () => {\n      request.destroy();\n      reject(new Error('Download timeout'));\n    });\n  });\n}\n\n/**\n * Generate a filename from URL\n */\nexport function getFilenameFromUrl(url: string): string {\n  try {\n    const urlObj = new URL(url);\n\n    // For Unsplash images, extract photo ID\n    if (urlObj.hostname.includes('unsplash.com')) {\n      const photoId = urlObj.pathname.split('/').pop() || 'image';\n      return `${photoId}.jpg`;\n    }\n\n    const pathname = urlObj.pathname;\n    const filename = pathname.split('/').pop() || 'image.jpg';\n\n    // Ensure it has an extension\n    if (!filename.includes('.')) {\n      return `${filename}.jpg`;\n    }\n\n    return filename;\n  } catch {\n    return `image-${Date.now()}.jpg`;\n  }\n}\n\n/**\n * Convert GitHub raw URL to a local media path\n */\nexport function convertToMediaPath(localPath: string): string {\n  // Convert absolute path to relative media path\n  // e.g., /path/to/media/widgets/slide-1.jpg -> /assets/widgets/slide-1.jpg\n  // or on Windows: C:\\path\\to\\media\\widgets\\slide-1.jpg -> /assets/widgets/slide-1.jpg\n\n  // Normalize to forward slashes for consistent matching\n  const normalizedPath = localPath.replace(/\\\\/g, '/');\n\n  const mediaMatch = normalizedPath.match(/media\\/(.+)$/);\n  if (mediaMatch) {\n    return `/assets/${mediaMatch[1]}`;\n  }\n  return localPath;\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/index.ts",
    "content": "/* eslint-disable no-console */\nimport './initEnvDev.js';\nimport 'dotenv/config';\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { error, success, info } from '../../lib/log/logger.js';\nimport { seedAttributeGroup, seedAttributes } from './seedAttributes.js';\nimport { seedCategories } from './seedCategories.js';\nimport { seedCollections } from './seedCollections.js';\nimport { seedPages } from './seedPages.js';\nimport { seedProducts } from './seedProducts.js';\nimport { seedWidgets } from './seedWidgets.js';\n\nconst { argv } = yargs(hideBin(process.argv))\n  .option('attributes', {\n    alias: 'a',\n    description: 'Seed product attributes',\n    type: 'boolean',\n    default: false\n  })\n  .option('categories', {\n    alias: 'c',\n    description: 'Seed categories',\n    type: 'boolean',\n    default: false\n  })\n  .option('collections', {\n    alias: 'col',\n    description: 'Seed collections',\n    type: 'boolean',\n    default: false\n  })\n  .option('products', {\n    alias: 'p',\n    description: 'Seed products',\n    type: 'boolean',\n    default: false\n  })\n  .option('widgets', {\n    alias: 'w',\n    description: 'Seed widgets',\n    type: 'boolean',\n    default: false\n  })\n  .option('pages', {\n    alias: 'pg',\n    description: 'Seed CMS pages',\n    type: 'boolean',\n    default: false\n  })\n  .option('all', {\n    description:\n      'Seed all demo data (attributes, categories, collections, products, widgets, pages)',\n    type: 'boolean',\n    default: false\n  })\n  .check((argv) => {\n    if (\n      !argv.attributes &&\n      !argv.categories &&\n      !argv.collections &&\n      !argv.products &&\n      !argv.widgets &&\n      !argv.pages &&\n      !argv.all\n    ) {\n      throw new Error(\n        'Please specify at least one option: --attributes, --categories, --collections, --products, --widgets, --pages, or --all'\n      );\n    }\n    return true;\n  })\n  .help();\n\ninterface SeedOptions {\n  attributes: boolean;\n  categories: boolean;\n  collections: boolean;\n  products: boolean;\n  widgets: boolean;\n  pages: boolean;\n  all: boolean;\n}\n\nasync function seed() {\n  const options = argv as unknown as SeedOptions;\n  let demoAttributeGroupId: number | null = null;\n\n  try {\n    info('Starting demo data seeding...\\n');\n\n    // Create attribute group first if we're seeding attributes or products\n    if (options.all || options.attributes || options.products) {\n      demoAttributeGroupId = await seedAttributeGroup();\n      console.log();\n    }\n\n    if (options.all || options.attributes) {\n      if (!demoAttributeGroupId) {\n        demoAttributeGroupId = await seedAttributeGroup();\n      }\n      await seedAttributes(demoAttributeGroupId);\n      console.log();\n    }\n\n    if (options.all || options.categories) {\n      await seedCategories();\n      console.log();\n    }\n\n    if (options.all || options.collections) {\n      await seedCollections();\n      console.log();\n    }\n\n    if (options.all || options.products) {\n      if (!demoAttributeGroupId) {\n        demoAttributeGroupId = await seedAttributeGroup();\n      }\n      await seedProducts(demoAttributeGroupId);\n      console.log();\n    }\n\n    if (options.all || options.widgets) {\n      await seedWidgets();\n      console.log();\n    }\n\n    if (options.all || options.pages) {\n      await seedPages();\n      console.log();\n    }\n\n    success('✓ Demo data seeding completed successfully!');\n    process.exit(0);\n  } catch (e: any) {\n    error(`Seeding failed: ${e.message}`);\n    process.exit(1);\n  }\n}\n\nseed();\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/initEnvDev.ts",
    "content": "import 'dotenv/config';\nprocess.env.NODE_ENV = 'development';\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/seedAttributes.ts",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { insert, select } from '@evershop/postgres-query-builder';\nimport { info, success, error } from '../../lib/log/logger.js';\nimport { pool } from '../../lib/postgres/connection.js';\nimport createProductAttribute from '../../modules/catalog/services/attribute/createProductAttribute.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Create or get the demo attribute group\n */\nexport async function seedAttributeGroup(): Promise<number> {\n  info('Creating demo attribute group...');\n\n  // Check if demo group already exists\n  const existingGroup = await select()\n    .from('attribute_group')\n    .where('group_name', '=', 'Demo Products')\n    .load(pool);\n\n  if (existingGroup) {\n    info('Demo attribute group already exists, reusing...');\n    return existingGroup.attribute_group_id;\n  }\n\n  // Create the demo attribute group\n  const result = await insert('attribute_group')\n    .given({\n      group_name: 'Demo Products'\n    })\n    .execute(pool);\n\n  success(`✓ Created attribute group: Demo Products (ID: ${result.insertId})`);\n  return result.insertId;\n}\n\n/**\n * Seed product attributes from JSON file\n */\nexport async function seedAttributes(\n  demoAttributeGroupId: number\n): Promise<void> {\n  info('Seeding attributes...');\n  const dataPath = path.join(__dirname, 'data', 'attributes.json');\n  const attributesData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));\n\n  for (const attributeData of attributesData) {\n    try {\n      // Check if attribute already exists\n      const existingAttribute = await select()\n        .from('attribute')\n        .where('attribute_code', '=', attributeData.attribute_code)\n        .load(pool);\n\n      if (existingAttribute) {\n        info(\n          `Attribute \"${attributeData.attribute_name}\" already exists, updating options...`\n        );\n\n        // If attribute has options (select/multiselect type), sync the options\n        if (attributeData.options && Array.isArray(attributeData.options)) {\n          for (const optionData of attributeData.options) {\n            // Check if option already exists\n            const existingOption = await select()\n              .from('attribute_option')\n              .where('attribute_id', '=', existingAttribute.attribute_id)\n              .and('option_text', '=', optionData.option_text)\n              .load(pool);\n\n            if (!existingOption) {\n              // Add new option - must include attribute_code\n              await insert('attribute_option')\n                .given({\n                  attribute_id: existingAttribute.attribute_id,\n                  attribute_code: existingAttribute.attribute_code,\n                  option_text: optionData.option_text\n                })\n                .execute(pool);\n              success(`  ✓ Added option: ${optionData.option_text}`);\n            } else {\n              info(`  → Option \"${optionData.option_text}\" already exists`);\n            }\n          }\n        }\n\n        // Ensure attribute is linked to demo group\n        const existingLink = await select()\n          .from('attribute_group_link')\n          .where('attribute_id', '=', existingAttribute.attribute_id)\n          .and('group_id', '=', demoAttributeGroupId)\n          .load(pool);\n\n        if (!existingLink) {\n          await insert('attribute_group_link')\n            .given({\n              attribute_id: existingAttribute.attribute_id,\n              group_id: demoAttributeGroupId\n            })\n            .execute(pool);\n          info(`  → Linked to Demo Products group`);\n        }\n\n        continue;\n      }\n\n      // Add the demo group if no groups specified\n      if (!attributeData.groups || attributeData.groups.length === 0) {\n        attributeData.groups = [demoAttributeGroupId];\n      }\n\n      await createProductAttribute(attributeData, {});\n      success(`✓ Created attribute: ${attributeData.attribute_name}`);\n    } catch (e: any) {\n      error(\n        `Failed to create attribute ${attributeData.attribute_name}: ${e.message}`\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/seedCategories.ts",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { select } from '@evershop/postgres-query-builder';\nimport { info, success, error } from '../../lib/log/logger.js';\nimport { pool } from '../../lib/postgres/connection.js';\nimport createCategory from '../../modules/catalog/services/category/createCategory.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Seed categories from JSON file\n */\nexport async function seedCategories(): Promise<void> {\n  info('Seeding categories...');\n  const dataPath = path.join(__dirname, 'data', 'categories.json');\n  const categoriesData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));\n\n  for (const categoryData of categoriesData) {\n    try {\n      // Check if category already exists\n      const existingCategory = await select()\n        .from('category_description')\n        .where('url_key', '=', categoryData.url_key)\n        .load(pool);\n\n      if (existingCategory) {\n        info(`Category \"${categoryData.name}\" already exists, skipping...`);\n        continue;\n      }\n\n      await createCategory(categoryData, {});\n      success(`✓ Created category: ${categoryData.name}`);\n    } catch (e: any) {\n      error(`Failed to create category ${categoryData.name}: ${e.message}`);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/seedCollections.ts",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { select } from '@evershop/postgres-query-builder';\nimport { info, success, error } from '../../lib/log/logger.js';\nimport { pool } from '../../lib/postgres/connection.js';\nimport createCollection from '../../modules/catalog/services/collection/createCollection.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Seed collections from JSON file\n */\nexport async function seedCollections(): Promise<void> {\n  info('Seeding collections...');\n  const dataPath = path.join(__dirname, 'data', 'collections.json');\n  const collectionsData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));\n\n  for (const collectionData of collectionsData) {\n    try {\n      // Check if collection already exists\n      const existingCollection = await select()\n        .from('collection')\n        .where('code', '=', collectionData.code)\n        .load(pool);\n\n      if (existingCollection) {\n        info(`Collection \"${collectionData.name}\" already exists, skipping...`);\n        continue;\n      }\n\n      await createCollection(collectionData, {});\n      success(`✓ Created collection: ${collectionData.name}`);\n    } catch (e: any) {\n      error(`Failed to create collection ${collectionData.name}: ${e.message}`);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/seedImages.ts",
    "content": "import { existsSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { insert, select } from '@evershop/postgres-query-builder';\nimport { CONSTANTS } from '../../lib/helpers.js';\nimport { info, success, warning, error } from '../../lib/log/logger.js';\nimport { pool } from '../../lib/postgres/connection.js';\nimport { downloadImage, getFilenameFromUrl } from './imageDownloader.js';\n\n/**\n * Seed product images by downloading from GitHub raw URLs\n */\nexport async function seedProductImages(\n  productId: number,\n  images: any[]\n): Promise<void> {\n  if (!images || images.length === 0) return;\n\n  for (let i = 0; i < images.length; i++) {\n    const imageData = images[i];\n    try {\n      let finalImageUrl = imageData.url;\n\n      // Download image if it's a remote URL\n      if (imageData.url && imageData.url.startsWith('http')) {\n        info(`  → Downloading image: ${imageData.url}`);\n\n        // Get filename from URL\n        const filename = getFilenameFromUrl(imageData.url);\n\n        // Create local path - organize by SKU\n        const subPath = `catalog/${\n          Math.floor(Math.random() * (9999 - 1000)) + 1000\n        }/${Math.floor(Math.random() * (9999 - 1000)) + 1000}`;\n        const mediaDir = join(CONSTANTS.ROOTPATH, 'media', subPath);\n\n        // Ensure directory exists\n        if (!existsSync(mediaDir)) {\n          mkdirSync(mediaDir, { recursive: true });\n        }\n\n        const localPath = join(mediaDir, filename);\n\n        try {\n          // Download image\n          await downloadImage(imageData.url, localPath);\n\n          // Convert to media URL\n          finalImageUrl = `/assets/${subPath}/${filename}`;\n          success(`  ✓ Downloaded and saved: ${mediaDir}`);\n          // Check if image record already exists\n          const existingImage = await select()\n            .from('product_image')\n            .where('product_image_product_id', '=', productId)\n            .and('origin_image', '=', finalImageUrl)\n            .load(pool);\n\n          if (!existingImage) {\n            // Save image URL to database\n            await insert('product_image')\n              .given({\n                product_image_product_id: productId,\n                origin_image: finalImageUrl,\n                is_main: imageData.isMain ? 1 : 0\n              })\n              .execute(pool);\n            info(`  ✓ Added image record to database`);\n          } else {\n            info(`  → Image already exists in database`);\n          }\n        } catch (downloadErr: any) {\n          error(`  ✗ Failed to download image: ${downloadErr.message}`);\n        }\n      }\n    } catch (e: any) {\n      warning(`  ⚠️  Failed to process image ${i + 1}: ${e.message}`);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/seedPages.ts",
    "content": "import { readFileSync } from 'fs';\nimport { join } from 'path';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { insert, select } from '@evershop/postgres-query-builder';\nimport { error, info, success } from '../../lib/log/logger.js';\nimport { getConnection } from '../../lib/postgres/connection.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\ninterface PageData {\n  status: boolean;\n  url_key: string;\n  name: string;\n  content: any[];\n  meta_title: string;\n  meta_keywords?: string;\n  meta_description?: string;\n}\n\n/**\n * Seed CMS pages from JSON file\n */\nexport async function seedPages(): Promise<void> {\n  try {\n    info('Seeding CMS pages...');\n\n    // Read pages data\n    const pagesPath = join(__dirname, 'data', 'pages.json');\n    const pagesData: PageData[] = JSON.parse(readFileSync(pagesPath, 'utf-8'));\n\n    const connection = await getConnection();\n    let created = 0;\n    let skipped = 0;\n\n    for (const pageData of pagesData) {\n      // Check if page already exists (by url_key)\n      const existing = await select()\n        .from('cms_page_description')\n        .where('url_key', '=', pageData.url_key)\n        .load(connection, false);\n\n      if (existing) {\n        info(`  ⊘ Page \"${pageData.url_key}\" already exists, skipping...`);\n        skipped++;\n        continue;\n      }\n\n      // Insert cms_page first\n      const page = await insert('cms_page')\n        .given({\n          status: pageData.status\n        })\n        .execute(connection, false);\n\n      // Insert cms_page_description\n      await insert('cms_page_description')\n        .given({\n          cms_page_description_cms_page_id: page.cms_page_id,\n          url_key: pageData.url_key,\n          name: pageData.name,\n          content: JSON.stringify(pageData.content),\n          meta_title: pageData.meta_title,\n          meta_keywords: pageData.meta_keywords || null,\n          meta_description: pageData.meta_description || null\n        })\n        .execute(connection);\n\n      success(`  ✓ Created page: ${pageData.name} (/${pageData.url_key})`);\n      created++;\n    }\n\n    success(\n      `✓ CMS pages seeding complete: ${created} created, ${skipped} skipped`\n    );\n  } catch (e: any) {\n    error(`Failed to seed pages: ${e.message}`);\n    throw e;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/seedProducts.ts",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { insert, select } from '@evershop/postgres-query-builder';\nimport { info, success, error, warning } from '../../lib/log/logger.js';\nimport { pool } from '../../lib/postgres/connection.js';\nimport createProduct from '../../modules/catalog/services/product/createProduct.js';\nimport { seedProductImages } from './seedImages.js';\nimport {\n  createVariantGroups,\n  resolveAttributeOptions\n} from './variantGroupHelpers.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Seed products from JSON file\n */\nexport async function seedProducts(\n  demoAttributeGroupId: number\n): Promise<void> {\n  info('Seeding products...');\n  const dataPath = path.join(__dirname, 'data', 'products.json');\n  const productsData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));\n\n  // Get color and size attribute IDs\n  const colorAttribute = await select()\n    .from('attribute')\n    .where('attribute_code', '=', 'color')\n    .load(pool);\n\n  const sizeAttribute = await select()\n    .from('attribute')\n    .where('attribute_code', '=', 'size')\n    .load(pool);\n\n  if (!colorAttribute || !sizeAttribute) {\n    error(\n      'Color and Size attributes must be seeded first. Run: npm run seed -- --attributes'\n    );\n    return;\n  }\n\n  // Create variant groups\n  const variantGroupIds = await createVariantGroups(\n    productsData,\n    demoAttributeGroupId,\n    colorAttribute.attribute_id\n  );\n\n  // Seed products\n  info('\\nSeeding products...');\n  for (const productData of productsData) {\n    try {\n      // Check if product already exists\n      const existingProduct = await select()\n        .from('product')\n        .where('sku', '=', productData.sku)\n        .load(pool);\n\n      if (existingProduct) {\n        info(\n          `Product \"${productData.name}\" (${productData.sku}) already exists, skipping...`\n        );\n        continue;\n      }\n\n      // Assign product to the demo attribute group\n      if (!productData.group_id) {\n        if (!demoAttributeGroupId) {\n          error('Demo attribute group ID is not set. This should not happen.');\n          continue;\n        }\n        productData.group_id = demoAttributeGroupId;\n      }\n\n      // Resolve category_id from the category field\n      if (productData.category) {\n        const categoryUrlKey = productData.category;\n        const categoryQuery = select('category.category_id').from(\n          'category_description'\n        );\n        categoryQuery\n          .leftJoin('category')\n          .on(\n            'category.category_id',\n            '=',\n            'category_description.category_description_category_id'\n          );\n\n        categoryQuery.where(\n          'category_description.url_key',\n          '=',\n          categoryUrlKey\n        );\n\n        const category = await categoryQuery.load(pool);\n\n        if (category && category.category_id) {\n          productData.category_id = category.category_id;\n        } else {\n          warning(\n            `  ⚠️  Category \"${categoryUrlKey}\" not found, product will have no category`\n          );\n        }\n\n        // Remove category field as it's not needed for product creation\n        delete productData.category;\n      }\n\n      // Save collections, images, and variant_group for later processing\n      const collections = productData.collections;\n      const images = productData.images;\n      const variantGroup = productData.variant_group;\n      delete productData.collections;\n      delete productData.images;\n      delete productData.variant_group;\n\n      // Set variant_group_id if this product belongs to a variant group\n      if (variantGroup && variantGroupIds.has(variantGroup)) {\n        productData.variant_group_id = variantGroupIds.get(variantGroup);\n        info(\n          `  → Assigning to variant group: ${variantGroup} (ID: ${productData.variant_group_id})`\n        );\n      }\n\n      // Convert attribute values to option IDs for select type attributes\n      if (productData.attributes && Array.isArray(productData.attributes)) {\n        productData.attributes = await resolveAttributeOptions(\n          productData.attributes\n        );\n      }\n\n      const product = await createProduct(productData, {});\n      success(`✓ Created product: ${productData.name} (${productData.sku})`);\n\n      // Process images\n      if (images && Array.isArray(images)) {\n        await seedProductImages(product.insertId, images);\n      }\n\n      // Assign product to collections if specified\n      if (collections && Array.isArray(collections)) {\n        for (const collectionCode of collections) {\n          const collection = await select()\n            .from('collection')\n            .where('code', '=', collectionCode)\n            .load(pool);\n\n          if (collection) {\n            await insert('product_collection')\n              .given({\n                collection_id: collection.collection_id,\n                product_id: product.insertId\n              })\n              .execute(pool);\n            info(`  → Assigned to collection: ${collectionCode}`);\n          }\n        }\n      }\n    } catch (e: any) {\n      error(`Failed to create product ${productData.name}: ${e.message}`);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/seedWidgets.ts",
    "content": "import { readFileSync, existsSync, mkdirSync } from 'fs';\nimport { join, resolve, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { insert, select } from '@evershop/postgres-query-builder';\nimport { CONSTANTS } from '../../lib/helpers.js';\nimport { error, info, success } from '../../lib/log/logger.js';\nimport { getConnection } from '../../lib/postgres/connection.js';\nimport {\n  downloadImage,\n  getFilenameFromUrl,\n  convertToMediaPath\n} from './imageDownloader.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\ninterface WidgetData {\n  name: string;\n  type: string;\n  status: 1 | 0;\n  area: string;\n  route: string[];\n  settings: Record<string, any>;\n  sort_order: number;\n}\n\ninterface SlideData {\n  id: string;\n  image: string;\n  width: number;\n  height: number;\n  headline?: string;\n  subheadline?: string;\n  buttonText?: string;\n  buttonUrl?: string;\n}\n\n/**\n * Download slideshow images and update URLs\n */\nasync function downloadSlideshowImages(\n  settings: Record<string, any>\n): Promise<Record<string, any>> {\n  if (settings.slides && Array.isArray(settings.slides)) {\n    const updatedSlides: SlideData[] = [];\n\n    for (const slide of settings.slides as SlideData[]) {\n      if (slide.image && slide.image.startsWith('http')) {\n        try {\n          info(`  → Downloading slide image: ${slide.image}`);\n\n          // Get filename from URL\n          const filename = getFilenameFromUrl(slide.image);\n          const slideId = slide.id || `slide-${Date.now()}`;\n\n          // Create local path\n          const mediaDir = join(\n            CONSTANTS.ROOTPATH,\n            'media',\n            'widgets',\n            slideId\n          );\n\n          // Ensure directory exists\n          if (!existsSync(mediaDir)) {\n            mkdirSync(mediaDir, { recursive: true });\n          }\n\n          const localPath = join(mediaDir, filename);\n\n          // Download image\n          await downloadImage(slide.image, localPath);\n\n          // Convert to media URL\n          const mediaUrl = convertToMediaPath(localPath);\n\n          // Update slide with local URL\n          updatedSlides.push({\n            ...slide,\n            image: mediaUrl\n          });\n        } catch (err) {\n          error(`  ✗ Failed to download slide image: ${err}`);\n          // Keep original URL on failure\n          updatedSlides.push(slide);\n        }\n      } else {\n        updatedSlides.push(slide);\n      }\n    }\n\n    return {\n      ...settings,\n      slides: updatedSlides\n    };\n  }\n\n  return settings;\n}\n\n/**\n * Seed widgets from JSON file\n */\nexport async function seedWidgets(): Promise<void> {\n  try {\n    info('Seeding widgets...');\n\n    // Read widgets data\n    const widgetsPath = join(__dirname, 'data', 'widgets.json');\n    const widgetsData: WidgetData[] = JSON.parse(\n      readFileSync(widgetsPath, 'utf-8')\n    );\n\n    const connection = await getConnection();\n    let created = 0;\n    let skipped = 0;\n\n    for (const widgetData of widgetsData) {\n      // Check if widget already exists (by name and type)\n      const existing = await select()\n        .from('widget')\n        .where('name', '=', widgetData.name)\n        .and('type', '=', widgetData.type)\n        .load(connection, false);\n\n      if (existing) {\n        info(`  ⊘ Widget \"${widgetData.name}\" already exists, skipping...`);\n        skipped++;\n        continue;\n      }\n\n      // Process settings - download slideshow images if needed\n      let processedSettings = widgetData.settings;\n      if (widgetData.type === 'simple_slider') {\n        info(`  → Processing slideshow images for: ${widgetData.name}`);\n        processedSettings = await downloadSlideshowImages(widgetData.settings);\n      }\n\n      // Insert widget\n      await insert('widget')\n        .given({\n          name: widgetData.name,\n          type: widgetData.type,\n          area: widgetData.area,\n          route: JSON.stringify(widgetData.route),\n          sort_order: widgetData.sort_order,\n          settings: JSON.stringify(processedSettings),\n          status: widgetData.status\n        })\n        .execute(connection, false);\n\n      success(`  ✓ Created widget: ${widgetData.name}`);\n      created++;\n    }\n\n    success(\n      `✓ Widget seeding complete: ${created} created, ${skipped} skipped`\n    );\n  } catch (e: any) {\n    error(`Failed to seed widgets: ${e.message}`);\n    throw e;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/seed/variantGroupHelpers.ts",
    "content": "import { insert, select } from '@evershop/postgres-query-builder';\nimport { v4 as uuidv4 } from 'uuid';\nimport { info, success, error } from '../../lib/log/logger.js';\nimport { pool } from '../../lib/postgres/connection.js';\n\n/**\n * Create variant groups for products\n */\nexport async function createVariantGroups(\n  productsData: any[],\n  demoAttributeGroupId: number,\n  colorAttributeId: number\n): Promise<Map<string, number>> {\n  const variantGroupIds = new Map<string, number>();\n\n  // Collect unique variant group names\n  const uniqueGroups = new Set<string>();\n  for (const productData of productsData) {\n    if (productData.variant_group) {\n      uniqueGroups.add(productData.variant_group);\n    }\n  }\n\n  // Create variant group records\n  info('Creating variant groups...');\n  for (const groupName of uniqueGroups) {\n    try {\n      // Generate a proper UUID compatible with PostgreSQL\n      const uuid = uuidv4();\n\n      // Create the variant group with attribute IDs and attribute_group_id\n      const result = await insert('variant_group')\n        .given({\n          uuid: uuid,\n          attribute_group_id: demoAttributeGroupId,\n          attribute_one: colorAttributeId,\n          attribute_two: null,\n          attribute_three: null,\n          attribute_four: null,\n          attribute_five: null,\n          visibility: 1\n        })\n        .execute(pool);\n\n      variantGroupIds.set(groupName, result.insertId);\n      success(\n        `✓ Created variant group: ${groupName} (ID: ${result.insertId}, UUID: ${uuid})`\n      );\n    } catch (e: any) {\n      error(`Failed to create variant group ${groupName}: ${e.message}`);\n    }\n  }\n\n  return variantGroupIds;\n}\n\n/**\n * Resolve attribute option IDs from text values\n */\nexport async function resolveAttributeOptions(\n  attributes: any[]\n): Promise<any[]> {\n  const validAttributes: any[] = [];\n\n  for (const attr of attributes) {\n    // Check the attribute type\n    const attribute = await select()\n      .from('attribute')\n      .where('attribute_code', '=', attr.attribute_code)\n      .load(pool);\n\n    if (\n      attribute &&\n      (attribute.type === 'select' || attribute.type === 'multiselect')\n    ) {\n      // Look up the option ID by option text\n      const option = await select()\n        .from('attribute_option')\n        .where('attribute_id', '=', attribute.attribute_id)\n        .and('option_text', '=', attr.value)\n        .load(pool);\n\n      if (option) {\n        // Replace the text value with the option ID\n        attr.value = option.attribute_option_id.toString();\n        validAttributes.push(attr);\n        info(\n          `  → Resolved ${attr.attribute_code}: \"${option.option_text}\" → ID ${option.attribute_option_id}`\n        );\n      } else {\n        error(\n          `  ✗ Option \"${attr.value}\" not found for attribute \"${attr.attribute_code}\" - skipping this attribute`\n        );\n        // Don't add this attribute to validAttributes\n      }\n    } else {\n      // Non-select attributes, add as-is\n      validAttributes.push(attr);\n    }\n  }\n\n  return validAttributes;\n}\n"
  },
  {
    "path": "packages/evershop/src/bin/start/index.ts",
    "content": "import './initEnvStart.js';\nimport { start } from '../lib/startUp.js';\n\nstart({\n  command: 'start',\n  env: 'production',\n  process: 'main'\n});\nprocess.on('uncaughtException', function (exception) {\n  import('../../lib/log/logger.js').then((module) => {\n    module.error(exception);\n  });\n});\nprocess.on('unhandledRejection', (reason, p) => {\n  import('../../lib/log/logger.js').then((module) => {\n    module.error(`Unhandled Rejection: ${reason} at: ${p}`);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/bin/start/initEnvStart.ts",
    "content": "import 'dotenv/config';\nprocess.env.NODE_ENV = 'production';\nprocess.env.ALLOW_CONFIG_MUTATIONS = 'true';\n"
  },
  {
    "path": "packages/evershop/src/bin/theme/active.ts",
    "content": "#!/usr/bin/env node\n/* eslint-disable no-console */\n\nimport { exec } from 'child_process';\nimport fs from 'fs/promises';\nimport path from 'path';\nimport boxen from 'boxen';\nimport enquirer from 'enquirer';\nimport kleur from 'kleur';\nimport ora from 'ora';\n\nconst { prompt } = enquirer;\nasync function selectTheme() {\n  const themesDir = path.join(process.cwd(), 'themes');\n  let themeNames: string[] = [];\n  try {\n    const files = await fs.readdir(themesDir, { withFileTypes: true });\n    themeNames = files\n      .filter((dirent) => dirent.isDirectory())\n      .map((dirent) => dirent.name);\n    if (themeNames.length === 0) {\n      console.error(kleur.red('No themes found in themes directory.'));\n      process.exit(1);\n    }\n  } catch (err) {\n    console.error(kleur.red('Error reading themes directory:'), err);\n    process.exit(1);\n  }\n  const response: any = await prompt({\n    type: 'select',\n    name: 'theme',\n    message: 'Select a theme to activate:',\n    choices: themeNames\n  });\n  return response.theme;\n}\n\nasync function updateConfig(theme: string) {\n  const configDir = path.join(process.cwd(), 'config');\n  const configPath = path.join(configDir, 'default.json');\n  try {\n    // Ensure config directory exists\n    try {\n      await fs.access(configDir);\n    } catch {\n      await fs.mkdir(configDir, { recursive: true });\n    }\n\n    // Read existing config or create new one\n    let config: any = {};\n    try {\n      const configData = await fs.readFile(configPath, 'utf8');\n      config = JSON.parse(configData);\n    } catch (err: any) {\n      // If file doesn't exist, start with empty config\n      if (err.code !== 'ENOENT') {\n        throw err;\n      }\n    }\n\n    // Update theme\n    config.system = config.system || {};\n    config.system.theme = theme;\n    await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');\n\n    console.log(\n      boxen(kleur.green(`Theme updated to \"${theme}\" in config/default.json`), {\n        padding: 1,\n        borderColor: 'green'\n      })\n    );\n  } catch (err) {\n    console.error(kleur.red('Error updating config:'), err);\n    process.exit(1);\n  }\n}\n\nasync function runBuild() {\n  const spinner = ora('Running build...').start();\n  return new Promise<void>((resolve, reject) => {\n    exec('npm run build', (error, stdout, stderr) => {\n      if (error) {\n        spinner.fail('Build failed');\n        console.error(stderr);\n        return reject(error);\n      } else {\n        spinner.succeed('Build completed successfully');\n        console.log(stdout);\n        return resolve();\n      }\n    });\n  });\n}\n\nasync function confirmBuild() {\n  const response: any = await prompt({\n    type: 'confirm',\n    name: 'runBuild',\n    initial: true,\n    message: 'Would you like to run \"npm run build\" now?'\n  });\n  return response.runBuild;\n}\n\nasync function activateTheme() {\n  const theme = await selectTheme();\n  await updateConfig(theme);\n  const shouldBuild = await confirmBuild();\n  if (shouldBuild) {\n    await runBuild();\n  } else {\n    console.log(\n      kleur.yellow('Remember to run \"npm run build\" later to apply changes.')\n    );\n  }\n}\n\nactivateTheme().catch((err) => {\n  console.error(kleur.red('An error occurred:'), err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "packages/evershop/src/bin/theme/create.ts",
    "content": "#!/usr/bin/env node\n/* eslint-disable no-console */\n\nimport fs from 'fs/promises';\nimport path from 'path';\nimport enquirer from 'enquirer';\nimport kleur from 'kleur';\n\nconst { prompt } = enquirer;\nfunction capitalize(str) {\n  if (!str) return '';\n  return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nasync function isRealDirectory(path) {\n  try {\n    const stats = await fs.lstat(path);\n    if (stats.isSymbolicLink()) {\n      return false;\n    }\n    return stats.isDirectory();\n  } catch (err) {\n    if (err.code === 'ENOENT') {\n      return false;\n    }\n    throw err;\n  }\n}\n\nasync function createTheme() {\n  const response: any = await prompt({\n    type: 'input',\n    name: 'name',\n    message: 'Enter new theme name (alphanumeric, dashes or underscores only):'\n  });\n  const name: string = response.name.trim();\n\n  // Validate name\n  if (!/^[A-Za-z0-9_-]+$/.test(name)) {\n    console.error(\n      kleur.red(\n        'Invalid theme name. Use only letters, numbers, dashes or underscores.'\n      )\n    );\n    process.exit(1);\n  }\n\n  const themeDir = path.join(process.cwd(), 'themes', name);\n  const pagesDir = path.join(themeDir, 'src', 'pages', 'homepage');\n  const componentFile = path.join(pagesDir, `${capitalize(name)}.tsx`);\n\n  // Prevent overwriting existing themes\n  try {\n    await fs.access(themeDir);\n    console.error(kleur.red(`Theme '${name}' already exists.`));\n    process.exit(1);\n  } catch (err: any) {\n    if (err.code !== 'ENOENT') {\n      console.error(kleur.red('Error checking theme existence:'), err);\n      process.exit(1);\n    }\n    // Directory does not exist, proceed\n  }\n\n  try {\n    // Create directories\n    await fs.mkdir(pagesDir, { recursive: true });\n\n    // Create package.json for the new theme\n    const packageJsonPath = path.join(themeDir, 'package.json');\n    const packageJsonContent = {\n      name,\n      version: '0.1.0',\n      type: 'module',\n      private: true,\n      scripts: {\n        build: 'tsc'\n      }\n    };\n    await fs.writeFile(\n      packageJsonPath,\n      JSON.stringify(packageJsonContent, null, 2),\n      'utf8'\n    );\n\n    // Create tsconfig.json for the new theme\n    const tsconfigPath = path.join(themeDir, 'tsconfig.json');\n    const tsconfigContent = {\n      compilerOptions: {\n        module: 'NodeNext',\n        target: 'ES2018',\n        lib: ['dom', 'dom.iterable', 'esnext'],\n        esModuleInterop: true,\n        forceConsistentCasingInFileNames: true,\n        skipLibCheck: true,\n        declaration: true,\n        sourceMap: true,\n        allowJs: true,\n        checkJs: false,\n        jsx: 'react',\n        outDir: './dist',\n        resolveJsonModule: true,\n        allowSyntheticDefaultImports: true,\n        allowArbitraryExtensions: true,\n        strictNullChecks: true,\n        baseUrl: '.',\n        rootDir: 'src',\n        paths: {\n          '@components/*': (await isRealDirectory(\n            path.join(process.cwd(), 'node_modules', '@evershop', 'evershop')\n          ))\n            ? [\n                './src/components/*',\n                '../../node_modules/@evershop/evershop/src/components/*'\n              ]\n            : ['./src/components/*', '../../packages/evershop/src/components/*']\n        }\n      },\n      include: ['src']\n    };\n    await fs.writeFile(\n      tsconfigPath,\n      JSON.stringify(tsconfigContent, null, 2),\n      'utf8'\n    );\n\n    // Create component file\n    const componentContent = `import React from 'react';\n\nconst ${capitalize(name)}: React.FC = () => {\n  return (\n    <div className=\"p-5 text-center text-2xl border border-dashed border-gray-300 my-5 mx-2 bg-blue-50 text-blue-800\">\n      <h3 className=\"mb-3\">\n        Welcome to the <span className=\"text-pink-500\">&#9829; </span>\n        ${name} <span className=\"text-pink-500\">&#9829; </span> theme!\n      </h3>\n      <code className=\"text-sm break-all\">\n        You can edit this file at:\n        ${componentFile}\n      </code>\n    </div>\n  );\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport default ${capitalize(name)};\n`;\n    await fs.writeFile(componentFile, componentContent, 'utf8');\n\n    console.log(kleur.green(`Theme '${name}' created.`));\n    console.log(kleur.blue(`Edit your new page at: ${componentFile}`));\n  } catch (err: any) {\n    console.error(kleur.red('Error creating theme:'), err);\n    process.exit(1);\n  }\n}\n\ncreateTheme().catch((err: any) => {\n  if (err) {\n    console.error(kleur.red('An unexpected error occurred:'), err);\n  }\n  process.exit(1);\n});\n"
  },
  {
    "path": "packages/evershop/src/bin/theme/twizz.ts",
    "content": "#!/usr/bin/env node\n/* eslint-disable no-console */\n\nimport fs from 'fs/promises';\nimport path from 'path';\nimport boxen from 'boxen';\nimport enquirer from 'enquirer';\nimport kleur from 'kleur';\nimport { getConfig } from '../../lib/util/getConfig.js';\n\nconst { prompt } = enquirer;\n\nfunction parseRelativeImports(content: string): string[] {\n  const relativeImports: string[] = [];\n\n  const importRegex =\n    /import\\s+(?:(?:\\{[^}]*\\}|\\*\\s+as\\s+\\w+|\\w+)(?:\\s*,\\s*(?:\\{[^}]*\\}|\\*\\s+as\\s+\\w+|\\w+))*\\s+from\\s+)?['\"`]([^'\"`]+)['\"`]/g;\n\n  let match;\n  while ((match = importRegex.exec(content)) !== null) {\n    const importPath = match[1];\n    // Check if it's a relative import (starts with ./ or ../)\n    if (importPath.startsWith('./') || importPath.startsWith('../')) {\n      relativeImports.push(importPath);\n    }\n  }\n\n  return relativeImports;\n}\n\nfunction resolveImportPath(\n  currentFilePath: string,\n  importPath: string\n): string {\n  const currentDir = path.dirname(currentFilePath);\n  const resolvedPath = path.resolve(currentDir, importPath);\n\n  const extensions = ['.tsx', '.jsx', '.ts', '.js'];\n\n  if (path.extname(resolvedPath)) {\n    return resolvedPath;\n  }\n\n  for (const ext of extensions) {\n    const pathWithExt = resolvedPath + ext;\n    try {\n      return pathWithExt;\n    } catch {\n      continue;\n    }\n  }\n\n  for (const ext of extensions) {\n    const indexPath = path.join(resolvedPath, `index${ext}`);\n    try {\n      return indexPath;\n    } catch {\n      continue;\n    }\n  }\n\n  return resolvedPath;\n}\n\n// Utility: Recursively find all dependencies of a file\nasync function findAllDependencies(\n  filePath: string,\n  visited: Set<string> = new Set(),\n  baseDir: string\n): Promise<string[]> {\n  if (visited.has(filePath)) {\n    return [];\n  }\n\n  visited.add(filePath);\n  const dependencies: string[] = [];\n\n  try {\n    const content = await fs.readFile(filePath, 'utf8');\n    const relativeImports = parseRelativeImports(content);\n\n    for (const importPath of relativeImports) {\n      const resolvedPath = resolveImportPath(filePath, importPath);\n\n      // Check if the resolved file exists and is within our base directory\n      try {\n        await fs.access(resolvedPath);\n\n        // Only include files that are within our component structure\n        if (resolvedPath.startsWith(baseDir)) {\n          dependencies.push(resolvedPath);\n\n          // Recursively find dependencies of this file\n          const nestedDeps = await findAllDependencies(\n            resolvedPath,\n            visited,\n            baseDir\n          );\n          dependencies.push(...nestedDeps);\n        }\n      } catch {\n        // File doesn't exist, try other extensions\n        const extensions = ['.tsx', '.jsx', '.ts', '.js'];\n        let found = false;\n\n        for (const ext of extensions) {\n          const pathWithExt = resolvedPath + ext;\n          try {\n            await fs.access(pathWithExt);\n            if (pathWithExt.startsWith(baseDir)) {\n              dependencies.push(pathWithExt);\n              const nestedDeps = await findAllDependencies(\n                pathWithExt,\n                visited,\n                baseDir\n              );\n              dependencies.push(...nestedDeps);\n              found = true;\n              break;\n            }\n          } catch {\n            continue;\n          }\n        }\n\n        // Try index files if still not found\n        if (!found) {\n          for (const ext of extensions) {\n            const indexPath = path.join(resolvedPath, `index${ext}`);\n            try {\n              await fs.access(indexPath);\n              if (indexPath.startsWith(baseDir)) {\n                dependencies.push(indexPath);\n                const nestedDeps = await findAllDependencies(\n                  indexPath,\n                  visited,\n                  baseDir\n                );\n                dependencies.push(...nestedDeps);\n                break;\n              }\n            } catch {\n              continue;\n            }\n          }\n        }\n      }\n    }\n  } catch (err) {}\n\n  return [...new Set(dependencies)];\n}\n\nasync function scanDirectory(dir: string): Promise<string[]> {\n  let results: string[] = [];\n  try {\n    const entries = await fs.readdir(dir, { withFileTypes: true });\n    for (const entry of entries) {\n      const fullPath = path.join(dir, entry.name);\n      if (entry.isDirectory()) {\n        results = results.concat(await scanDirectory(fullPath));\n      } else if (\n        entry.isFile() &&\n        (fullPath.endsWith('.jsx') || fullPath.endsWith('.tsx'))\n      ) {\n        results.push(fullPath);\n      }\n    }\n  } catch (err) {\n    // ignore errors if directory doesn't exist\n  }\n  return results;\n}\n\nasync function scanModulesFrontStore(): Promise<string[]> {\n  const evershopDir = path.join(\n    process.cwd(),\n    'node_modules',\n    '@evershop',\n    'evershop'\n  );\n\n  const modulesDir = (await isRealDirectory(evershopDir))\n    ? path.join(evershopDir, 'src', 'modules')\n    : path.join(process.cwd(), 'packages', 'evershop', 'src', 'modules');\n  let results: string[] = [];\n  try {\n    const modules = await fs.readdir(modulesDir, { withFileTypes: true });\n    for (const mod of modules) {\n      if (mod.isDirectory()) {\n        const frontStoreDir = path.join(\n          modulesDir,\n          mod.name,\n          'pages',\n          'frontStore'\n        );\n        const files = await scanDirectory(frontStoreDir);\n        results = results.concat(files);\n      }\n    }\n  } catch (err) {}\n  return results;\n}\n\nasync function isRealDirectory(path) {\n  try {\n    const stats = await fs.lstat(path);\n    if (stats.isSymbolicLink()) {\n      return false;\n    }\n    return stats.isDirectory();\n  } catch (err) {\n    if (err.code === 'ENOENT') {\n      return false;\n    }\n    throw err;\n  }\n}\n\nasync function getOverrideCandidates(): Promise<string[]> {\n  // Check if a folder @evershop/evershop exists in the node_modules\n  const evershopDir = path.join(\n    process.cwd(),\n    'node_modules',\n    '@evershop',\n    'evershop'\n  );\n  let commonDir, frontStoreDir;\n  if (await isRealDirectory(evershopDir)) {\n    commonDir = path.join(evershopDir, 'src', 'components', 'common');\n    frontStoreDir = path.join(evershopDir, 'src', 'components', 'frontStore');\n  } else {\n    commonDir = path.join(\n      process.cwd(),\n      'packages',\n      'evershop',\n      'src',\n      'components',\n      'common'\n    );\n    frontStoreDir = path.join(\n      process.cwd(),\n      'packages',\n      'evershop',\n      'src',\n      'components',\n      'frontStore'\n    );\n  }\n\n  const files1 = await scanDirectory(commonDir);\n  const files2 = await scanDirectory(frontStoreDir);\n  const files3 = await scanModulesFrontStore();\n  return [...files1, ...files2, ...files3];\n}\n\nfunction getCurrentTheme(): string {\n  const theme = getConfig('system.theme');\n  if (theme) {\n    return theme;\n  } else {\n    console.error(\n      kleur.red(\n        'No theme set in config/system.theme. Please set a theme before creating overrides.'\n      )\n    );\n    process.exit(1);\n  }\n}\n\n// Given an original file path and current theme, determine the destination override file path\nfunction getDestinationPath(originalPath: string, theme: string): string {\n  const themeDir = path.join(process.cwd(), 'themes', theme, 'src');\n  const componentsIdx = originalPath.indexOf(path.join('src', 'components'));\n  const modulesIdx = originalPath.indexOf(path.join('src', 'modules'));\n\n  if (componentsIdx !== -1) {\n    // For files under src/components, replicate structure under <theme>/components\n    const relativePath = originalPath.substring(\n      originalPath.indexOf('components')\n    );\n    return path.join(themeDir, relativePath);\n  } else if (modulesIdx !== -1) {\n    // For files under src/modules/*/pages/frontStore/*, map to <theme>/pages/*\n    const frontStoreMarker = path.join('pages', 'frontStore');\n    const markerIdx = originalPath.indexOf(frontStoreMarker);\n    if (markerIdx !== -1) {\n      const relativePath = originalPath.substring(\n        markerIdx + frontStoreMarker.length\n      );\n      // Ensure leading slash is removed\n      const cleanedRelative = relativePath.replace(\n        new RegExp(`^(\\\\${path.sep}|/)`),\n        ''\n      );\n      return path.join(themeDir, 'pages', cleanedRelative);\n    }\n  }\n  // Fallback: put in theme root\n  return path.join(themeDir, path.basename(originalPath));\n}\n\n// Ensure directory exists\nasync function ensureDir(dir: string): Promise<void> {\n  try {\n    await fs.mkdir(dir, { recursive: true });\n  } catch (err) {\n    // Ignore if exists\n  }\n}\n\nasync function createOverrideFile() {\n  const candidates = await getOverrideCandidates();\n  if (candidates.length === 0) {\n    console.error(kleur.red('No override candidates found.'));\n    process.exit(1);\n  }\n\n  // Updated prompt: use 'autocomplete' and removed unsupported 'limit' property\n  const relativeCandidates = candidates.map((filePath) =>\n    path.relative(process.cwd(), filePath)\n  );\n  const response: any = await prompt({\n    type: 'autocomplete',\n    name: 'file',\n    message: 'Select a file to override:',\n    initial: 0,\n    choices: relativeCandidates\n  });\n  const selectedRelative = response.file;\n  const selectedFile = path.join(process.cwd(), selectedRelative);\n\n  // Get current theme\n  const theme = getCurrentTheme();\n\n  // Determine the base directory for dependency tracking\n  const evershopDir = path.join(\n    process.cwd(),\n    'node_modules',\n    '@evershop',\n    'evershop'\n  );\n  const baseDir = (await isRealDirectory(evershopDir))\n    ? path.join(evershopDir, 'src')\n    : path.join(process.cwd(), 'packages', 'evershop', 'src');\n\n  // Find all dependencies of the selected file\n  console.log(kleur.yellow('Analyzing dependencies...'));\n  const dependencies = await findAllDependencies(\n    selectedFile,\n    new Set(),\n    baseDir\n  );\n  const allFiles = [selectedFile, ...dependencies];\n\n  console.log(kleur.cyan(`Found ${dependencies.length} dependencies:`));\n  dependencies.forEach((dep) => {\n    console.log(kleur.gray(`  ${path.relative(process.cwd(), dep)}`));\n  });\n\n  // Ask user if they want to copy dependencies\n  if (dependencies.length > 0) {\n    const confirmResponse: any = await prompt({\n      type: 'confirm',\n      name: 'copyDependencies',\n      message: `Copy ${dependencies.length} dependency files along with the main file?`,\n      initial: true\n    });\n\n    if (!confirmResponse.copyDependencies) {\n      // Only copy the main file\n      allFiles.splice(1); // Remove all dependencies, keep only the main file\n    }\n  }\n\n  // Copy all files (main + dependencies if confirmed)\n  const copiedFiles: string[] = [];\n\n  for (const filePath of allFiles) {\n    const destPath = getDestinationPath(filePath, theme);\n\n    // Read content from file\n    let content: string;\n    try {\n      content = await fs.readFile(filePath, 'utf8');\n    } catch (err) {\n      console.error(kleur.red(`Error reading file ${filePath}:`), err);\n      continue;\n    }\n\n    // Ensure destination directory exists\n    const destDir = path.dirname(destPath);\n    await ensureDir(destDir);\n\n    // Write content to new file\n    try {\n      await fs.writeFile(destPath, content, 'utf8');\n      copiedFiles.push(destPath);\n    } catch (err) {\n      console.error(kleur.red(`Error writing file ${destPath}:`), err);\n    }\n  }\n\n  // Display results\n  if (copiedFiles.length > 0) {\n    console.log(\n      boxen(\n        kleur.green(\n          `Successfully created ${copiedFiles.length} override file(s):\\n`\n        ) + copiedFiles.map((file) => kleur.white(`• ${file}`)).join('\\n'),\n        {\n          padding: 1,\n          borderColor: 'green'\n        }\n      )\n    );\n  } else {\n    console.error(kleur.red('No files were copied.'));\n    process.exit(1);\n  }\n}\n\ncreateOverrideFile().catch((err) => {\n  console.log(err);\n  if (!err) {\n    console.log(kleur.yellow('Command cancelled.'));\n    process.exit(0);\n  } else {\n    console.error(kleur.red('An unexpected error occurred:'), err);\n    process.exit(1);\n  }\n});\n"
  },
  {
    "path": "packages/evershop/src/bin/user/changePassword.js",
    "content": "import 'dotenv/config';\nimport { select, update } from '@evershop/postgres-query-builder';\nimport yargs from 'yargs';\nimport { error, success } from '../../lib/log/logger.js';\nimport { pool } from '../../lib/postgres/connection.js';\nimport { hashPassword } from '../../lib/util/passwordHelper.js';\n\nfunction isValidPassword(password) {\n  return password.length >= 8;\n}\n\nconst { argv } = yargs\n  .option('email', {\n    alias: 'e',\n    description: 'User email',\n    demandOption: true,\n    type: 'string',\n    validate: (email) => {\n      if (email.length === 0) {\n        throw new Error('Email is required');\n      }\n      return true;\n    }\n  })\n  .option('password', {\n    alias: 'p',\n    description: 'New password',\n    demandOption: true,\n    type: 'string'\n  })\n  .check((argv) => {\n    if (!isValidPassword(argv.password)) {\n      throw new Error(\n        'Invalid password. Password must be at least 8 characters long'\n      );\n    }\n    return true;\n  })\n  .help();\n\nasync function updatePassword() {\n  const { email, password } = argv;\n\n  try {\n    const user = await select()\n      .from('admin_user')\n      .where('email', '=', email)\n      .load(pool);\n\n    if (!user) {\n      throw new Error('User not found');\n    }\n    await update('admin_user')\n      .given({\n        password: hashPassword(password)\n      })\n      .where('admin_user_id', '=', user.admin_user_id)\n      .execute(pool);\n    success('Password is updated successfully');\n    process.exit(0);\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n}\n\nupdatePassword();\n"
  },
  {
    "path": "packages/evershop/src/bin/user/create.js",
    "content": "import 'dotenv/config';\nimport { insertOnUpdate } from '@evershop/postgres-query-builder';\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { error, success } from '../../lib/log/logger.js';\nimport { pool } from '../../lib/postgres/connection.js';\nimport { hashPassword } from '../../lib/util/passwordHelper.js';\n\nfunction isValidEmail(email) {\n  return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n}\n\nfunction isValidPassword(password) {\n  return password.length >= 8;\n}\n\nconst { argv } = yargs(hideBin(process.argv))\n  .option('name', {\n    alias: 'n',\n    description: 'Admin user full name',\n    demandOption: true,\n    type: 'string',\n    validate: (name) => {\n      if (name.length === 0) {\n        throw new Error('Full name is required');\n      }\n      return true;\n    }\n  })\n  .option('email', {\n    alias: 'e',\n    description: 'User email',\n    demandOption: true,\n    type: 'string',\n    validate: (email) => {\n      if (!isValidEmail(email)) {\n        throw new Error('Invalid email format');\n      }\n      return true;\n    }\n  })\n  .option('password', {\n    alias: 'p',\n    description: 'User password',\n    demandOption: true,\n    type: 'string'\n  })\n  .check((argv) => {\n    if (!isValidPassword(argv.password)) {\n      throw new Error(\n        'Invalid password. Password must be at least 8 characters long'\n      );\n    }\n    return true;\n  })\n  .help();\n\nasync function createAdminUser() {\n  const { name: full_name, email, password } = argv;\n  // Insert the admin user\n  try {\n    await insertOnUpdate('admin_user', ['email'])\n      .given({\n        full_name,\n        email,\n        password: hashPassword(password)\n      })\n      .execute(pool);\n    success('Admin user created successfully');\n    process.exit(0);\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n}\n\ncreateAdminUser();\n"
  },
  {
    "path": "packages/evershop/src/components/admin/AttributeGroupSelector.tsx",
    "content": "import { SimplePagination } from '@components/common/SimplePagination.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport { Skeleton } from '@components/common/ui/Skeleton.js';\nimport { Check } from 'lucide-react';\nimport React from 'react';\nimport { useQuery } from 'urql';\nimport { AtLeastOne } from '../../types/atLeastOne.js';\n\nconst SearchQuery = `\n  query Query ($filters: [FilterInput!]) {\n    attributeGroups(filters: $filters) {\n      items {\n        attributeGroupId\n        uuid\n        groupName\n      }\n      total\n    }\n  }\n`;\n\ninterface AttributeGroupIdentifier {\n  attributeGroupId?: string | number;\n  uuid?: string;\n}\n\nconst AttributeGroupListSkeleton: React.FC = () => {\n  const skeletonItems = Array(5).fill(0);\n\n  return (\n    <div className=\"attribute-group-list-skeleton space-y-2 divide-y\">\n      {skeletonItems.map((_, index) => (\n        <div\n          key={index}\n          className=\"attribute-group-skeleton-item border-border pb-2 flex justify-between items-center \"\n        >\n          <div className=\"flex items-center\">\n            <Skeleton className=\"h-5 w-30 rounded\"></Skeleton>\n          </div>\n          <div className=\"select-button\">\n            <Skeleton className=\"h-6 w-12 rounded\"></Skeleton>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n};\n\nconst isAttributeGroupSelected = (\n  attributeGroup: AttributeGroupIdentifier,\n  selectedAttributeGroups: AtLeastOne<AttributeGroupIdentifier>[]\n): boolean => {\n  return selectedAttributeGroups.some(\n    (selected) =>\n      (selected?.attributeGroupId &&\n        selected.attributeGroupId === attributeGroup.attributeGroupId) ||\n      (selected?.uuid && selected.uuid === attributeGroup.uuid)\n  );\n};\n\nconst AttributeGroupSelector: React.FC<{\n  onSelect: (id: string | number, uuid: string, name: string) => void;\n  onUnSelect: (id: string | number, uuid: string, name: string) => void;\n  selectedAttributeGroups: AtLeastOne<AttributeGroupIdentifier>[];\n}> = ({ onSelect, onUnSelect, selectedAttributeGroups }) => {\n  const [internalSelectedAttributeGroups, setInternalSelectedAttributeGroups] =\n    React.useState<AtLeastOne<AttributeGroupIdentifier>[]>(\n      selectedAttributeGroups || []\n    );\n  const [loading, setLoading] = React.useState<boolean>(false);\n  const limit = 10;\n  const [inputValue, setInputValue] = React.useState<string>('');\n  const [page, setPage] = React.useState(1);\n\n  const [result, reexecuteQuery] = useQuery({\n    query: SearchQuery,\n    variables: {\n      filters: inputValue\n        ? [\n            { key: 'name', operation: 'like', value: inputValue },\n            { key: 'page', operation: 'eq', value: page.toString() },\n            { key: 'limit', operation: 'eq', value: limit.toString() }\n          ]\n        : [\n            { key: 'limit', operation: 'eq', value: limit.toString() },\n            { key: 'page', operation: 'eq', value: page.toString() }\n          ]\n    },\n    pause: true\n  });\n\n  React.useEffect(() => {\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  }, [page]);\n\n  React.useEffect(() => {\n    const timer = setTimeout(() => {\n      setLoading(false);\n      if (inputValue !== '') {\n        reexecuteQuery({ requestPolicy: 'network-only' });\n      }\n    }, 1500);\n\n    return () => clearTimeout(timer);\n  }, [inputValue]);\n\n  const { data, fetching, error } = result as {\n    data: {\n      attributeGroups: {\n        items: Array<{\n          attributeGroupId: string | number;\n          uuid: string;\n          groupName: string;\n        }>;\n        total: number;\n      };\n    };\n    fetching: boolean;\n    error: Error | undefined;\n  };\n\n  if (error) {\n    return (\n      <p className=\"text-destructive\">\n        There was an error fetching attribute groups.\n        {error.message}\n      </p>\n    );\n  }\n\n  return (\n    <div>\n      <div>\n        <div className=\"mb-5\">\n          <Input\n            type=\"text\"\n            value={inputValue || ''}\n            placeholder=\"Search attribute groups\"\n            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n              setInputValue(e.target.value);\n              setLoading(true);\n            }}\n          />\n        </div>\n        {(fetching || loading) && <AttributeGroupListSkeleton />}\n        {!fetching && data && (\n          <div className=\"divide-y\">\n            {data.attributeGroups.items.length === 0 && (\n              <div className=\"p-2 border border-divider rounded flex justify-center items-center\">\n                {inputValue ? (\n                  <p>\n                    No attribute groups found for query &quot;{inputValue}\n                    &rdquo;\n                  </p>\n                ) : (\n                  <p>You have no attribute groups to display</p>\n                )}\n              </div>\n            )}\n            {data.attributeGroups.items.map((a) => (\n              <div\n                key={a.uuid}\n                className=\"grid grid-cols-8 gap-5 py-2 border-divider items-center\"\n              >\n                <div className=\"col-span-5\">\n                  <h3>{a.groupName}</h3>\n                </div>\n                <div className=\"col-span-3 text-right\">\n                  {!isAttributeGroupSelected(\n                    a,\n                    internalSelectedAttributeGroups\n                  ) && (\n                    <Button\n                      variant=\"outline\"\n                      onClick={async (e) => {\n                        e.preventDefault();\n                        setInternalSelectedAttributeGroups((prev) => [\n                          ...prev,\n                          {\n                            attributeGroupId: a.attributeGroupId,\n                            uuid: a.uuid,\n                            groupName: a.groupName\n                          }\n                        ]);\n                        onSelect(a.attributeGroupId, a.uuid, a.groupName);\n                      }}\n                    >\n                      Select\n                    </Button>\n                  )}\n                  {isAttributeGroupSelected(\n                    a,\n                    internalSelectedAttributeGroups\n                  ) && (\n                    <Button\n                      onClick={(e) => {\n                        e.preventDefault();\n                        setInternalSelectedAttributeGroups((prev) =>\n                          prev.filter(\n                            (c) =>\n                              c.attributeGroupId !== a.attributeGroupId &&\n                              c.uuid !== a.uuid\n                          )\n                        );\n                        onUnSelect(a.attributeGroupId, a.uuid, a.groupName);\n                      }}\n                    >\n                      <Check className=\"w-5 h-5\" />\n                    </Button>\n                  )}\n                </div>\n              </div>\n            ))}\n          </div>\n        )}\n      </div>\n      <div className=\"flex justify-between gap-5 mt-3\">\n        <SimplePagination\n          total={data?.attributeGroups.total || 0}\n          count={data?.attributeGroups?.items?.length || 0}\n          page={page}\n          hasNext={limit * page < data?.attributeGroups.total}\n          setPage={setPage}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport { AttributeGroupSelector };\n"
  },
  {
    "path": "packages/evershop/src/components/admin/CategorySelector.tsx",
    "content": "import { SimplePagination } from '@components/common/SimplePagination.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport { Skeleton } from '@components/common/ui/Skeleton.js';\nimport { Check } from 'lucide-react';\nimport React from 'react';\nimport { useQuery } from 'urql';\nimport { AtLeastOne } from '../../types/atLeastOne.js';\n\nconst SearchQuery = `\n  query Query ($filters: [FilterInput!]) {\n    categories(filters: $filters) {\n      items {\n        categoryId\n        uuid\n        name\n        path {\n          name\n        }\n      }\n      total\n    }\n  }\n`;\n\ninterface CategoryIdentifier {\n  categoryId?: string | number;\n  uuid?: string;\n}\n\nconst CategoryListSkeleton: React.FC = () => {\n  const skeletonItems = Array(5).fill(0);\n\n  return (\n    <div className=\"attribute-group-list-skeleton space-y-2 divide-y\">\n      {skeletonItems.map((_, index) => (\n        <div\n          key={index}\n          className=\"attribute-group-skeleton-item border-border pb-2 flex justify-between items-center \"\n        >\n          <div className=\"flex items-center\">\n            <Skeleton className=\"h-5 w-30 rounded\"></Skeleton>\n          </div>\n          <div className=\"select-button\">\n            <Skeleton className=\"h-6 w-12 rounded\"></Skeleton>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n};\n\nconst isCategorySelected = (\n  category: CategoryIdentifier,\n  selectedCategories: AtLeastOne<CategoryIdentifier>[]\n): boolean => {\n  return selectedCategories.some(\n    (selected) =>\n      (selected?.categoryId && selected.categoryId === category.categoryId) ||\n      (selected?.uuid && selected.uuid === category.uuid)\n  );\n};\n\nconst CategorySelector: React.FC<{\n  onSelect: (id: string | number, uuid: string, name: string) => void;\n  onUnSelect: (id: string | number, uuid: string, name: string) => void;\n  selectedCategories: AtLeastOne<CategoryIdentifier>[];\n}> = ({ onSelect, onUnSelect, selectedCategories }) => {\n  const [internalSelectedCategories, setInternalSelectedCategories] =\n    React.useState<AtLeastOne<CategoryIdentifier>[]>(selectedCategories || []);\n  const [loading, setLoading] = React.useState<boolean>(false);\n  const limit = 10;\n  const [inputValue, setInputValue] = React.useState<string>('');\n  const [page, setPage] = React.useState(1);\n\n  const [result, reexecuteQuery] = useQuery({\n    query: SearchQuery,\n    variables: {\n      filters: inputValue\n        ? [\n            { key: 'name', operation: 'like', value: inputValue },\n            { key: 'page', operation: 'eq', value: page.toString() },\n            { key: 'limit', operation: 'eq', value: limit.toString() }\n          ]\n        : [\n            { key: 'limit', operation: 'eq', value: limit.toString() },\n            { key: 'page', operation: 'eq', value: page.toString() }\n          ]\n    },\n    pause: true\n  });\n\n  React.useEffect(() => {\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  }, [page]);\n\n  React.useEffect(() => {\n    const timer = setTimeout(() => {\n      setLoading(false);\n      if (inputValue !== '') {\n        reexecuteQuery({ requestPolicy: 'network-only' });\n      }\n    }, 1500);\n\n    return () => clearTimeout(timer);\n  }, [inputValue]);\n\n  const { data, fetching, error } = result as {\n    data: {\n      categories: {\n        items: Array<{\n          categoryId: string | number;\n          uuid: string;\n          name: string;\n          path: Array<{ name: string }>;\n        }>;\n        total: number;\n      };\n    };\n    fetching: boolean;\n    error: Error | undefined;\n  };\n\n  if (error) {\n    return (\n      <p className=\"text-destructive\">\n        There was an error fetching categories.\n        {error.message}\n      </p>\n    );\n  }\n\n  return (\n    <div>\n      <div>\n        <div className=\"p-2\">\n          <Input\n            type=\"text\"\n            value={inputValue || ''}\n            placeholder=\"Search categories\"\n            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n              setInputValue(e.target.value);\n              setLoading(true);\n            }}\n          />\n        </div>\n        {(fetching || loading) && <CategoryListSkeleton />}\n        {!fetching && data && (\n          <div className=\"divide-y\">\n            {data.categories.items.length === 0 && (\n              <div className=\"p-2 border border-divider rounded flex justify-center items-center\">\n                {inputValue ? (\n                  <p>No categories found for query &quot;{inputValue}&rdquo;</p>\n                ) : (\n                  <p>You have no categories to display</p>\n                )}\n              </div>\n            )}\n            {data.categories.items.map((cat) => (\n              <div\n                key={cat.uuid}\n                className=\"grid grid-cols-8 gap-5 py-2 border-divider items-center\"\n              >\n                <div className=\"col-span-5\">\n                  <h3>\n                    {cat.path.map((item, index) => (\n                      <span key={item.name} className=\"text-gray-500\">\n                        {item.name}\n                        {index < cat.path.length - 1 && ' > '}\n                      </span>\n                    ))}\n                  </h3>\n                </div>\n                <div className=\"col-span-3 text-right\">\n                  {!isCategorySelected(cat, internalSelectedCategories) && (\n                    <Button\n                      variant={'outline'}\n                      onClick={async (e) => {\n                        e.preventDefault();\n                        setInternalSelectedCategories((prev) => [\n                          ...prev,\n                          {\n                            categoryId: cat.categoryId,\n                            uuid: cat.uuid,\n                            name: cat.name\n                          }\n                        ]);\n                        onSelect(cat.categoryId, cat.uuid, cat.name);\n                      }}\n                    >\n                      Select\n                    </Button>\n                  )}\n                  {isCategorySelected(cat, internalSelectedCategories) && (\n                    <Button\n                      variant={'default'}\n                      onClick={(e) => {\n                        e.preventDefault();\n                        setInternalSelectedCategories((prev) =>\n                          prev.filter(\n                            (c) =>\n                              c.categoryId !== cat.categoryId &&\n                              c.uuid !== cat.uuid\n                          )\n                        );\n                        onUnSelect(cat.categoryId, cat.uuid, cat.name);\n                      }}\n                    >\n                      <Check className=\"w-5 h-5\" />\n                    </Button>\n                  )}\n                </div>\n              </div>\n            ))}\n          </div>\n        )}\n      </div>\n      <div className=\"flex justify-between gap-5\">\n        <SimplePagination\n          total={data?.categories.total || 0}\n          count={data?.categories?.items?.length || 0}\n          page={page}\n          hasNext={limit * page < data?.categories.total}\n          setPage={setPage}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport { CategorySelector };\n"
  },
  {
    "path": "packages/evershop/src/components/admin/CategoryTree.scss",
    "content": ".category-tree-container {\n  background-color: #fff;\n  z-index: 100;\n  border-color: #c9cccf;\n  padding: 10px;\n  box-sizing: border-box;\n  max-height: 250px;\n  overflow-y: auto;\n  overflow-x: hidden;\n}\n\n.skeleton-wrapper-category-tree {\n  width: 100%;\n  display: flex;\n  justify-content: center;\n  flex-direction: column;\n  .skeleton:empty {\n    width: 100%;\n    height: 30px;\n    border-radius: 3px;\n    cursor: progress;\n    background: linear-gradient(0.25turn, transparent, #f7f6f6, transparent),\n      linear-gradient(#eee, #eee);\n    background-repeat: no-repeat;\n    animation: loading 1.5s infinite;\n  }\n  @keyframes loading {\n    to {\n      background-position: 315px 0, 0 0, 0 190px, 50px 195px;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/CategoryTree.tsx",
    "content": "import React from 'react';\nimport { useQuery } from 'urql';\nimport './CategoryTree.scss';\nimport RenderIfTrue from '@components/common/RenderIfTrue.jsx';\nimport { Folder, Minus, Plus } from 'lucide-react';\n\nexport interface CategoryTreeItem {\n  categoryId: number;\n  name: string;\n  hasChildren: boolean;\n  path: Array<{ name: string }>;\n  children?: Array<CategoryTreeItem>;\n}\n\nconst categoriesQuery = `\n  query Query ($filters: [FilterInput]) {\n    categories (filters: $filters) {\n      items {\n        categoryId,\n        name\n        hasChildren\n        path {\n          name\n        }\n      }\n    }\n  }\n`;\n\nconst childrenQuery = `\n  query Query ($filters: [FilterInput]) {\n    categories (filters: $filters) {\n      items {\n        categoryId,\n        name\n        path {\n          name\n        }\n        hasChildren\n      }\n    }\n  }\n`;\n\nconst Skeleton = () => (\n  <ul className=\"skeleton-wrapper-category-tree\">\n    <li className=\"skeleton mt-2\" />\n    <li className=\"skeleton mt-2\" />\n    <li className=\"skeleton mt-2\" />\n    <li className=\"skeleton mt-2\" />\n  </ul>\n);\n\nexport interface CategoryItemProps {\n  category: CategoryTreeItem;\n  selectedCategories?: CategoryTreeItem[];\n  onSelect: (category: CategoryTreeItem) => void;\n}\n\nfunction CategoryItem({\n  category,\n  selectedCategories,\n  onSelect\n}: CategoryItemProps) {\n  const [expanded, setExpanded] = React.useState(false);\n\n  const [result] = useQuery({\n    query: childrenQuery,\n    variables: {\n      filters: [{ key: 'parent', operation: 'eq', value: category.categoryId }]\n    },\n    pause: !expanded\n  });\n\n  const { data, fetching, error } = result;\n\n  if (error) {\n    return (\n      <li className=\"text-destructive\">\n        <span>{error.message}</span>\n      </li>\n    );\n  }\n  const className = selectedCategories?.find(\n    (item) => item.categoryId === category.categoryId\n  )\n    ? 'flex justify-start gap-2 items-center p-2 rounded-md bg-green-100 transition-colors duration-500'\n    : 'flex justify-start gap-2 items-center p-2 rounded-md hover:bg-gray-100 transition-colors duration-500';\n  return (\n    <li className=\"[&_ul]:pl-2\">\n      <div className={className}>\n        {category.hasChildren && (\n          <a\n            href=\"#\"\n            onClick={(e) => {\n              e.preventDefault();\n              setExpanded(!expanded);\n            }}\n          >\n            {expanded ? (\n              <Minus width={15} height={15} />\n            ) : (\n              <Plus width={15} height={15} />\n            )}\n          </a>\n        )}\n        {!category.hasChildren && (\n          <span>\n            <Minus width={15} height={15} />\n          </span>\n        )}\n        <a\n          href=\"#\"\n          onClick={(e) => {\n            e.preventDefault();\n            onSelect(category);\n          }}\n        >\n          <div className=\"flex gap-2 justify-start items-center cursor-pointer\">\n            <Folder width={20} height={20} />\n            <span className=\"text-sm\">{category.name}</span>\n          </div>\n        </a>\n      </div>\n      {data && data.categories.items.length > 0 && expanded && (\n        <div className=\"pb-2\">\n          <ul>\n            {data.categories.items.map((child) => (\n              <CategoryItem\n                key={child.value}\n                category={child}\n                selectedCategories={selectedCategories}\n                onSelect={onSelect}\n              />\n            ))}\n          </ul>\n        </div>\n      )}\n      <RenderIfTrue condition={fetching && expanded}>\n        <div className=\"pb-2\">\n          <Skeleton />\n        </div>\n      </RenderIfTrue>\n    </li>\n  );\n}\n\nCategoryItem.defaultProps = {\n  category: {},\n  selectedCategory: {}\n};\n\ninterface CategoryTreeProps {\n  selectedCategories?: CategoryTreeItem[];\n  onSelect: (category: CategoryTreeItem) => void;\n}\n\nfunction CategoryTree({ selectedCategories, onSelect }: CategoryTreeProps) {\n  const [result] = useQuery({\n    query: categoriesQuery,\n    variables: {\n      filters: [{ key: 'parent', operation: 'eq', value: null }]\n    }\n  });\n  const { data, fetching, error } = result;\n\n  if (fetching) {\n    return <Skeleton />;\n  }\n  if (error) {\n    return <p className=\"text-destructive\">{error.message}</p>;\n  }\n  if (!data || !data.categories || data.categories.items.length === 0) {\n    return <div className=\"text-gray-400 text-md\">There is no category</div>;\n  }\n\n  return (\n    <ul className=\"category-tree\">\n      {data.categories.items.map((category) => (\n        <CategoryItem\n          key={category.value}\n          category={category}\n          selectedCategories={selectedCategories}\n          onSelect={onSelect}\n        />\n      ))}\n    </ul>\n  );\n}\n\nCategoryTree.defaultProps = {\n  selectedCategories: []\n};\n\nexport { CategoryTree };\n"
  },
  {
    "path": "packages/evershop/src/components/admin/CollectionSelector.tsx",
    "content": "import { SimplePagination } from '@components/common/SimplePagination.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport { Skeleton } from '@components/common/ui/Skeleton.js';\nimport { Check } from 'lucide-react';\nimport React from 'react';\nimport { useQuery } from 'urql';\nimport { AtLeastOne } from '../../types/atLeastOne.js';\n\nconst SearchQuery = `\n  query Query ($filters: [FilterInput!]) {\n    collections(filters: $filters) {\n      items {\n        collectionId\n        uuid\n        name\n      }\n      total\n    }\n  }\n`;\n\ninterface CollectionIdentifier {\n  collectionId?: string | number;\n  uuid?: string;\n}\n\nconst CollectionListSkeleton: React.FC = () => {\n  const skeletonItems = Array(5).fill(0);\n\n  return (\n    <div className=\"attribute-group-list-skeleton space-y-2 divide-y\">\n      {skeletonItems.map((_, index) => (\n        <div\n          key={index}\n          className=\"attribute-group-skeleton-item border-border pb-2 flex justify-between items-center \"\n        >\n          <div className=\"flex items-center\">\n            <Skeleton className=\"h-5 w-30 rounded\"></Skeleton>\n          </div>\n          <div className=\"select-button\">\n            <Skeleton className=\"h-6 w-12 rounded\"></Skeleton>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n};\n\nconst isCollectionSelected = (\n  collection: CollectionIdentifier,\n  selectedCollections: AtLeastOne<CollectionIdentifier>[]\n): boolean => {\n  return selectedCollections.some(\n    (selected) =>\n      (selected?.collectionId &&\n        selected.collectionId === collection.collectionId) ||\n      (selected?.uuid && selected.uuid === collection.uuid)\n  );\n};\n\nconst CollectionSelector: React.FC<{\n  onSelect: (id: string | number, uuid: string, name: string) => void;\n  onUnSelect: (id: string | number, uuid: string, name: string) => void;\n  selectedCollections: AtLeastOne<CollectionIdentifier>[];\n}> = ({ onSelect, onUnSelect, selectedCollections }) => {\n  const [internalSelectedCollections, setInternalSelectedCollections] =\n    React.useState<AtLeastOne<CollectionIdentifier>[]>(\n      selectedCollections || []\n    );\n  const [loading, setLoading] = React.useState<boolean>(false);\n  const limit = 10;\n  const [inputValue, setInputValue] = React.useState<string>('');\n  const [page, setPage] = React.useState(1);\n\n  const [result, reexecuteQuery] = useQuery({\n    query: SearchQuery,\n    variables: {\n      filters: inputValue\n        ? [\n            { key: 'name', operation: 'like', value: inputValue },\n            { key: 'page', operation: 'eq', value: page.toString() },\n            { key: 'limit', operation: 'eq', value: limit.toString() }\n          ]\n        : [\n            { key: 'limit', operation: 'eq', value: limit.toString() },\n            { key: 'page', operation: 'eq', value: page.toString() }\n          ]\n    },\n    pause: true\n  });\n\n  React.useEffect(() => {\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  }, [page]);\n\n  React.useEffect(() => {\n    const timer = setTimeout(() => {\n      setLoading(false);\n      if (inputValue !== '') {\n        reexecuteQuery({ requestPolicy: 'network-only' });\n      }\n    }, 1500);\n\n    return () => clearTimeout(timer);\n  }, [inputValue]);\n\n  const { data, fetching, error } = result as {\n    data: {\n      collections: {\n        items: Array<{\n          collectionId: string | number;\n          uuid: string;\n          name: string;\n        }>;\n        total: number;\n      };\n    };\n    fetching: boolean;\n    error: Error | undefined;\n  };\n\n  if (error) {\n    return (\n      <p className=\"text-destructive\">\n        There was an error fetching collections.\n        {error.message}\n      </p>\n    );\n  }\n\n  return (\n    <div>\n      <div>\n        <div className=\"p-2\">\n          <Input\n            type=\"text\"\n            value={inputValue || ''}\n            placeholder=\"Search collections\"\n            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n              setInputValue(e.target.value);\n              setLoading(true);\n            }}\n          />\n        </div>\n        {(fetching || loading) && <CollectionListSkeleton />}\n        {!fetching && data && (\n          <div className=\"divide-y\">\n            {data.collections.items.length === 0 && (\n              <div className=\"p-2 border border-divider rounded flex justify-center items-center\">\n                {inputValue ? (\n                  <p>\n                    No collections found for query &quot;{inputValue}&rdquo;\n                  </p>\n                ) : (\n                  <p>You have no collections to display</p>\n                )}\n              </div>\n            )}\n            {data.collections.items.map((c) => (\n              <div\n                key={c.uuid}\n                className=\"grid grid-cols-8 gap-5 py-2 border-divider items-center\"\n              >\n                <div className=\"col-span-5\">\n                  <h3>{c.name}</h3>\n                </div>\n                <div className=\"col-span-3 text-right\">\n                  {!isCollectionSelected(c, internalSelectedCollections) && (\n                    <Button\n                      variant=\"outline\"\n                      onClick={async (e) => {\n                        e.preventDefault();\n                        setInternalSelectedCollections((prev) => [\n                          ...prev,\n                          {\n                            collectionId: c.collectionId,\n                            uuid: c.uuid,\n                            name: c.name\n                          }\n                        ]);\n                        onSelect(c.collectionId, c.uuid, c.name);\n                      }}\n                    >\n                      Select\n                    </Button>\n                  )}\n                  {isCollectionSelected(c, internalSelectedCollections) && (\n                    <Button\n                      variant=\"default\"\n                      onClick={(e) => {\n                        e.preventDefault();\n                        setInternalSelectedCollections((prev) =>\n                          prev.filter(\n                            (c) =>\n                              c.collectionId !== c.collectionId &&\n                              c.uuid !== c.uuid\n                          )\n                        );\n                        onUnSelect(c.collectionId, c.uuid, c.name);\n                      }}\n                    >\n                      <Check width={20} height={20} />\n                    </Button>\n                  )}\n                </div>\n              </div>\n            ))}\n          </div>\n        )}\n      </div>\n      <div className=\"flex justify-between gap-5\">\n        <SimplePagination\n          total={data?.collections.total || 0}\n          count={data?.collections?.items?.length || 0}\n          page={page}\n          hasNext={limit * page < data?.collections.total}\n          setPage={setPage}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport { CollectionSelector };\n"
  },
  {
    "path": "packages/evershop/src/components/admin/FileBrowser.scss",
    "content": "/* FILE BROWSER */\n.file-browser {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  background: #fff;\n  z-index: 1000;\n  padding: 20px;\n}\n.file-browser .loading {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  margin-top: -40px;\n  margin-left: -40px;\n}\n.file-browser img {\n  max-width: 100%;\n}\n.file-browser .image-item .inner {\n  padding: 3px;\n  box-sizing: border-box;\n  border: 1px solid #e1e1e1;\n  position: relative;\n}\n.file-browser .image-item .inner .select {\n  position: absolute;\n  bottom: 5px;\n  right: 5px;\n  font-size: 20px;\n}\n.image-tool__image-picture {\n  margin-top: 0 !important;\n  margin-bottom: 0 !important;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/FileBrowser.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\nimport './FileBrowser.scss';\nimport { useQuery } from 'urql';\nimport Spinner from '@components/admin/Spinner.js';\nimport { Input } from '@components/common/ui/Input.js';\n\nconst GetApisQuery = `\n  query Query ($filters: [FilterInput!]) {\n    browserApi: url(routeId: \"fileBrowser\", params: [{key: \"0\", value: \"\"}])\n    deleteApi: url(routeId: \"fileDelete\", params: [{key: \"0\", value: \"\"}])\n    uploadApi: url(routeId: \"imageUpload\", params: [{key: \"0\", value: \"\"}])\n    folderCreateApi: url(routeId: \"folderCreate\")\n  }\n`;\n\nexport interface File {\n  isSelected?: boolean;\n  name: string;\n  url: string;\n}\n\nconst File: React.FC<{\n  file: File;\n  select: (url: File) => void;\n}> = ({ file, select }) => {\n  const className = file.isSelected === true ? 'selected' : '';\n  return (\n    <div className={`col image-item ${className}`}>\n      <div className=\"inner\">\n        <a\n          href=\"#\"\n          onClick={(e) => {\n            e.preventDefault();\n            select(file);\n          }}\n        >\n          <img src={file.url} alt=\"\" />\n        </a>\n        {file.isSelected === true && (\n          <div className=\"select fill-current text-primary\">\n            <svg\n              style={{ width: '2rem' }}\n              xmlns=\"http://www.w3.org/2000/svg\"\n              className=\"h-4 w-4\"\n              fill=\"none\"\n              viewBox=\"0 0 24 24\"\n              stroke=\"currentColor\"\n            >\n              <path\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                strokeWidth={2}\n                d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n              />\n            </svg>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n\nconst FileBrowser: React.FC<{\n  onInsert: (url: string) => void;\n  isMultiple: boolean;\n  close: () => void;\n}> = ({ onInsert, isMultiple, close }) => {\n  const [error, setError] = React.useState<string>('');\n  const [loading, setLoading] = React.useState<boolean>(false);\n  const [folders, setFolders] = React.useState<string[]>([]);\n  const [files, setFiles] = React.useState<File[]>([]);\n  const [currentPath, setCurrentPath] = React.useState<\n    {\n      name: string;\n      index: number;\n    }[]\n  >([{ name: '', index: 0 }]);\n  const newFolderRefInput = React.useRef<HTMLInputElement>(null);\n  const browserApiRef = React.useRef<string>('');\n  const deleteApiRef = React.useRef<string>('');\n  const uploadApiRef = React.useRef<string>('');\n  const folderCreateApiRef = React.useRef<string>('');\n\n  const onSelectFolder = (e, f) => {\n    e.preventDefault();\n    setCurrentPath(\n      currentPath.concat({ name: f, index: currentPath.length + 1 })\n    );\n  };\n\n  const onSelectFolderFromBreadcrumb = (e, index) => {\n    e.preventDefault();\n    const newPath = [] as { name: string; index: number }[];\n    currentPath.forEach((f) => {\n      if (f.index <= index) newPath.push(f);\n    });\n    setCurrentPath(newPath);\n  };\n\n  const onSelectFile = (f) => {\n    if (isMultiple === false) {\n      setFiles(\n        files.map((file) => {\n          if (f.name === file.name) {\n            file.isSelected = !file.isSelected;\n          } else {\n            file.isSelected = false;\n          }\n          return file;\n        })\n      );\n    } else {\n      setFiles(\n        files.map((file) => {\n          if (f.name === file.name) {\n            file.isSelected = true;\n          } else {\n            file.isSelected = false;\n          }\n          return file;\n        })\n      );\n    }\n  };\n\n  const closeFileBrowser = (e) => {\n    e.preventDefault();\n    close();\n  };\n\n  const createFolder = (e, folder) => {\n    e.preventDefault();\n    if (!folder || !folder.trim()) {\n      setError('Invalid folder name');\n      return;\n    }\n    const path = currentPath.map((f) => f.name);\n    path.push(folder.trim());\n    setLoading(true);\n    fetch(folderCreateApiRef.current, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({ path: path.join('/') }),\n      credentials: 'same-origin'\n    })\n      .then((res) => res.json())\n      .then((response) => {\n        if (!response.error) {\n          // Get the first level folder, incase of recursive folder creation\n          const recursiveFolders = folder.split('/');\n          setFolders([...new Set(folders.concat(recursiveFolders[0]))]);\n        } else {\n          setError(response.error.message);\n        }\n      })\n      .catch((err) => setError(err.message))\n      .finally(() => setLoading(false));\n  };\n\n  const deleteFile = () => {\n    let file;\n    files.forEach((f) => {\n      if (f.isSelected === true) {\n        file = f;\n      }\n    });\n\n    if (!file) {\n      setError('No file selected');\n    } else {\n      const path = currentPath.map((f) => f.name);\n      path.push(file.name);\n      setLoading(true);\n      fetch(deleteApiRef.current + path.join('/'), {\n        method: 'DELETE'\n      })\n        .then((res) => res.json())\n        .then((response) => {\n          if (!response.error) {\n            setCurrentPath(currentPath.map((f) => f));\n          } else {\n            setError(response.error.message);\n          }\n        })\n        .catch((err) => setError(err.message))\n        .finally(() => setLoading(false));\n    }\n  };\n\n  const insertFile = () => {\n    let file;\n    files.forEach((f) => {\n      if (f.isSelected === true) {\n        file = f;\n      }\n    });\n\n    if (!file) {\n      setError('No file selected');\n    } else {\n      onInsert(file.url);\n    }\n  };\n\n  const onUpload = (e) => {\n    e.persist();\n    const formData = new FormData();\n    for (let i = 0; i < e.target.files.length; i += 1)\n      formData.append('images', e.target.files[i]);\n\n    const path = [] as string[];\n    currentPath.forEach((f) => {\n      path.push(f.name);\n    });\n\n    setLoading(true);\n    fetch(uploadApiRef.current + path.join('/'), {\n      method: 'POST',\n      body: formData\n    })\n      .then((res) => res.json())\n      .then((response) => {\n        if (!response.error) {\n          setCurrentPath(currentPath.map((f) => f));\n        } else {\n          setError(response.error.message);\n        }\n      })\n      .catch((err) => setError(err.message))\n      .finally(() => setLoading(false));\n  };\n\n  // Create a function to fetch files and folders to avoid code duplication\n  const fetchFilesAndFolders = React.useCallback(() => {\n    if (!browserApiRef.current) {\n      return;\n    }\n\n    const path = currentPath.map((f) => f.name);\n    setLoading(true);\n    fetch(browserApiRef.current + path.join('/'), {\n      method: 'GET'\n    })\n      .then((res) => res.json())\n      .then((response) => {\n        if (!response.error) {\n          setFolders(response.data.folders);\n          setFiles(response.data.files);\n        } else {\n          setError(response.error.message);\n        }\n      })\n      .catch((e) => setError(e.message))\n      .finally(() => setLoading(false));\n  }, [currentPath]);\n\n  const [result] = useQuery({\n    query: GetApisQuery\n  });\n  const { data, fetching, error: err } = result;\n\n  if (data) {\n    browserApiRef.current = data.browserApi;\n    deleteApiRef.current = data.deleteApi;\n    uploadApiRef.current = data.uploadApi;\n    folderCreateApiRef.current = data.folderCreateApi;\n  }\n\n  // Fetch files and folders when APIs are ready\n  React.useEffect(() => {\n    if (data) {\n      fetchFilesAndFolders();\n    }\n  }, [currentPath, fetchFilesAndFolders, data]);\n\n  if (err) {\n    return (\n      <p className=\"text-destructive\">\n        There was an error fetching file browser APIs.\n        {err.message}\n      </p>\n    );\n  }\n  if (fetching) {\n    return (\n      <div className=\"fixed top-0 left-0 bottom-0 right-0 flex justify-center\">\n        <Spinner width={30} height={30} />\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"file-browser\">\n      {loading === true && (\n        <div className=\"fixed top-0 left-0 bottom-0 right-0 flex justify-center\">\n          <Spinner width={30} height={30} />\n        </div>\n      )}\n      <div className=\"content\">\n        <div className=\"flex justify-end\">\n          <a\n            href=\"#\"\n            onClick={(e) => closeFileBrowser(e)}\n            className=\"text-interactive fill-current\"\n          >\n            <svg\n              style={{ width: '2rem' }}\n              xmlns=\"http://www.w3.org/2000/svg\"\n              className=\"h-4 w-4\"\n              fill=\"none\"\n              viewBox=\"0 0 24 24\"\n              stroke=\"currentColor\"\n            >\n              <path\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                strokeWidth={2}\n                d=\"M6 18L18 6M6 6l12 12\"\n              />\n            </svg>\n          </a>\n        </div>\n        <div>\n          <div className=\"grid grid-cols-4 gap-5\">\n            <div className=\"col-span-1\">\n              <div className=\"current-path mb-10\">\n                <div className=\"flex\">\n                  <div className=\"pr-2\">You are here:</div>\n                  <div>\n                    <a\n                      href=\"#\"\n                      onClick={(e) => onSelectFolderFromBreadcrumb(e, 0)}\n                      className=\"text-primary hover:underline\"\n                    >\n                      Root\n                    </a>\n                  </div>\n                  {currentPath\n                    .filter((f) => f.name !== '')\n                    .map((f, index) => (\n                      <div key={index}>\n                        <span>/</span>\n                        <a\n                          className=\"text-primary hover:underline\"\n                          href=\"#\"\n                          onClick={(e) =>\n                            onSelectFolderFromBreadcrumb(e, f.index)\n                          }\n                        >\n                          {f.name}\n                        </a>\n                      </div>\n                    ))}\n                </div>\n              </div>\n              <ul className=\"mt-4 mb-4\">\n                {folders.map((f, i) => (\n                  <li\n                    key={i}\n                    className=\"text-primary fill-current flex list-group-item\"\n                  >\n                    <svg\n                      style={{ width: '2rem', height: '2rem' }}\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                      className=\"h-4 w-4\"\n                      fill=\"none\"\n                      viewBox=\"0 0 24 24\"\n                      stroke=\"currentColor\"\n                    >\n                      <path\n                        strokeLinecap=\"round\"\n                        strokeLinejoin=\"round\"\n                        strokeWidth={2}\n                        d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"\n                      />\n                    </svg>\n                    <a\n                      className=\"pl-2 hover:underline\"\n                      href=\"#\"\n                      onClick={(e) => onSelectFolder(e, f)}\n                    >\n                      {f}\n                    </a>\n                  </li>\n                ))}\n                {folders.length === 0 && (\n                  <li className=\"list-group-item\">\n                    <span>There is no sub folder.</span>\n                  </li>\n                )}\n              </ul>\n              <div className=\"justify-start items-center gap-2 flex\">\n                <Input\n                  type=\"text\"\n                  placeholder=\"New folder\"\n                  ref={newFolderRefInput}\n                />\n                <Button\n                  onClick={(e) =>\n                    createFolder(e, newFolderRefInput.current?.value)\n                  }\n                  variant={'outline'}\n                >\n                  Create\n                </Button>\n              </div>\n            </div>\n            <div className=\"col-span-3\">\n              <div className=\"error text-destructive mb-5\">{error}</div>\n              <div className=\"tool-bar grid grid-cols-3 gap-2 mb-5\">\n                <Button\n                  variant=\"destructive\"\n                  title=\"Delete image\"\n                  onClick={() => deleteFile()}\n                >\n                  Delete\n                </Button>\n                <Button\n                  variant=\"default\"\n                  title=\"Insert image\"\n                  onClick={() => insertFile()}\n                >\n                  Insert\n                </Button>\n                <Button\n                  variant=\"outline\"\n                  onClick={() => {\n                    (\n                      document.getElementById(\n                        'upload-image'\n                      ) as HTMLInputElement\n                    ).click();\n                  }}\n                >\n                  Upload\n                </Button>\n                <label\n                  className=\"self-center\"\n                  id=\"upload-image-label\"\n                  htmlFor=\"upload-image\"\n                >\n                  <a className=\"invisible\">\n                    <input\n                      id=\"upload-image\"\n                      type=\"file\"\n                      multiple\n                      onChange={onUpload}\n                    />\n                  </a>\n                </label>\n              </div>\n              {files.length === 0 && <div>There is no file to display.</div>}\n              <div className=\"grid grid-cols-9 gap-2\">\n                {files.map((f) => (\n                  <File file={f} select={onSelectFile} key={f.name} />\n                ))}\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport { FileBrowser };\n"
  },
  {
    "path": "packages/evershop/src/components/admin/FormButtons.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\nimport { useFormContext } from 'react-hook-form';\n\nconst FormButtons: React.FC<{\n  formId: string;\n  cancelUrl: string;\n}> = ({ cancelUrl, formId }) => {\n  const {\n    formState: { isSubmitting }\n  } = useFormContext();\n\n  return (\n    <div className=\"form-submit-button flex border-t border-border mt-4 pt-4 justify-between\">\n      <Button\n        variant=\"destructive\"\n        onClick={() => {\n          window.location.href = cancelUrl;\n        }}\n      >\n        Cancel\n      </Button>\n      <Button\n        onClick={() => {\n          (document.getElementById(formId) as HTMLFormElement).dispatchEvent(\n            new Event('submit', { cancelable: true, bubbles: true })\n          );\n        }}\n        isLoading={isSubmitting}\n      >\n        Save\n      </Button>\n    </div>\n  );\n};\n\nexport { FormButtons };\n"
  },
  {
    "path": "packages/evershop/src/components/admin/ImageUploader.scss",
    "content": ".image-uploader-manager img {\n  max-width: 100%;\n}\n.image-uploader-manager .image-list {\n  grid-template-columns: repeat(4, 1fr);\n  display: grid;\n  grid-gap: 8px;\n  grid-auto-rows: 1fr;\n}\n\n/* Single image mode - responsive to parent */\n.image-uploader-manager .single-image-container {\n  position: relative;\n  width: 100%;\n  max-width: 100%;\n  margin: 0 auto;\n  aspect-ratio: 1 / 1; /* Maintain square aspect ratio */\n}\n\n.image-uploader-manager .single-image-container .image {\n  width: 100%;\n  height: 100%;\n  max-width: 100%;\n  position: absolute;\n  top: 0;\n  left: 0;\n}\n\n/* Position the remove icon above everything in single image mode */\n.image-uploader-manager .single-image-container .image .remove {\n  position: absolute;\n  top: 10px;\n  left: 10px;\n  z-index: 3; /* Higher than the Upload overlay */\n  background-color: rgba(255, 255, 255, 0.7); /* Semi-transparent background */\n  border-radius: 50%;\n  padding: 5px;\n}\n\n/* Additional styling for the remove button in single mode */\n.single-mode-remove {\n  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 30px;\n  height: 30px;\n}\n\n.image-uploader-manager .single-image-container .uploader {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  max-width: 100%;\n  background: transparent;\n  border: none;\n  opacity: 0;\n  transition: opacity 0.2s ease-in-out;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  z-index: 2;\n}\n\n/* For small containers, adjust icon sizes */\n@media (max-width: 400px) {\n  .image-uploader-manager\n    .single-image-container\n    .uploader\n    .uploader-icon\n    label\n    svg {\n    width: 30px !important;\n    height: 30px !important;\n  }\n\n  .single-mode-remove {\n    width: 25px;\n    height: 25px;\n  }\n}\n\n.image-uploader-manager .single-image-container .uploader:hover {\n  opacity: 1;\n  background: rgba(0, 0, 0, 0.5);\n}\n\n.image-uploader-manager .single-image-container .uploader .uploader-icon label {\n  color: white;\n  font-size: 30px;\n}\n\n/* When there's no image yet in single mode */\n.image-uploader-manager .single-image-container.no-image .uploader {\n  opacity: 1;\n  border-radius: var(--radius);\n  background: #f5f5f5;\n  border: 2px dashed var(--border);\n}\n\n.image-uploader-manager\n  .single-image-container.no-image\n  .uploader\n  .uploader-icon\n  label {\n  color: var(--primary);\n}\n\n.image-uploader-manager .image-list .image {\n  position: relative;\n  padding: 5px;\n  box-sizing: border-box;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  background: #fff;\n  max-width: 150px;\n}\n.image-uploader-manager .image-list .uploader {\n  position: relative;\n  padding: 5px;\n  box-sizing: border-box;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  border: 2px dashed var(--border);\n  border-radius: var(--radius);\n  background: #fff;\n  max-width: 150px;\n}\n.image-uploader-manager .image-list .grid-item {\n  display: flex;\n}\n/* Apply special styling for the first grid item */\n.image-uploader-manager .image-list .grid-item:first-child,\n.image-uploader-manager .image-list .grid-item.first-item {\n  grid-column: 1 / span 2;\n  grid-row: 1 / span 2;\n  max-width: 100%;\n}\n\n.image-uploader-manager .image-list .grid-item:first-child:after,\n.image-uploader-manager .image-list .grid-item.first-item:after {\n  content: '';\n  display: block;\n  padding-bottom: 100%;\n}\n\n.image-uploader-manager .image-list .image:first-child,\n.image-uploader-manager .image-list .image.first-item {\n  grid-row: 1 / span 2;\n}\n\n.image-uploader-manager .image-list .image:first-child img,\n.image-uploader-manager .image-list .image.first-item img {\n  max-width: 100%;\n}\n.image-uploader-manager .image-list {\n  outline: 0;\n}\n.image-uploader-manager .image-list .image .img {\n  width: 100%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n.image-uploader-manager .image-list .image .remove,\n.image-uploader-manager .image-list .image .zoom {\n  position: absolute;\n  top: 10px;\n}\n.image-uploader-manager .image-list .image .remove {\n  left: 10px;\n}\n.image-uploader-manager .image-list .image .zoom {\n  right: 10px;\n}\n.uploader .invisible {\n  position: absolute;\n  top: 0;\n  left: 0;\n}\n.uploader .uploader-icon label {\n  cursor: pointer;\n  font-size: 18px;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/ImageUploader.tsx",
    "content": "import React from 'react';\nimport { toast } from 'react-toastify';\nimport uniqid from 'uniqid';\nimport { useQuery } from 'urql';\nimport { get } from '../../lib/util/get.js';\nimport './ImageUploader.scss';\nimport Spinner from '@components/admin/Spinner.js';\nimport { ImageUploaderSkeleton } from './ImageUploaderSkeleton.js';\nimport {\n  DndContext,\n  closestCenter,\n  KeyboardSensor,\n  PointerSensor,\n  useSensor,\n  useSensors,\n  DragEndEvent\n} from '@dnd-kit/core';\nimport {\n  arrayMove,\n  SortableContext,\n  sortableKeyboardCoordinates,\n  useSortable\n} from '@dnd-kit/sortable';\nimport { CSS } from '@dnd-kit/utilities';\n\nexport interface Image {\n  uuid: string;\n  url: string;\n  path?: string;\n}\n\nconst Upload: React.FC<{\n  imageUploadUrl: string;\n  targetPath?: string;\n  onUpload: (images: Image[]) => void | Promise<void>;\n  isSingleMode?: boolean;\n}> = ({ imageUploadUrl, targetPath, onUpload, isSingleMode }) => {\n  const [uploading, setUploading] = React.useState(false);\n\n  const onChange = (e) => {\n    setUploading(true);\n    e.persist();\n    const formData = new FormData();\n    for (let i = 0; i < e.target.files.length; i += 1) {\n      formData.append('images', e.target.files[i]);\n    }\n    formData.append('targetPath', targetPath || '');\n    fetch(imageUploadUrl + (targetPath || ''), {\n      method: 'POST',\n      body: formData,\n      headers: {\n        'X-Requested-With': 'XMLHttpRequest'\n      }\n    })\n      .then((response) => {\n        const contentType = response.headers.get('content-type');\n        if (!contentType || !contentType.includes('application/json')) {\n          throw new TypeError('Something wrong. Please try again');\n        }\n\n        return response.json();\n      })\n      .then(async (response) => {\n        if (!response.error) {\n          await onUpload(\n            get(response, 'data.files', []).map((i) => ({\n              uuid: uniqid(),\n              url: i.url,\n              path: i.path\n            }))\n          );\n        } else {\n          toast.error(get(response, 'error.message', 'Failed!'));\n        }\n      })\n      .catch((error) => {\n        toast.error(error.message);\n      })\n      .finally(() => {\n        e.target.value = null;\n        setUploading(false);\n      });\n  };\n\n  const id = uniqid();\n  return (\n    <div className=\"uploader grid-item\">\n      <div className=\"uploader-icon text-primary w-full h-full\">\n        <label\n          htmlFor={id}\n          className=\"w-full h-full flex items-center justify-center cursor-pointer\"\n        >\n          {uploading ? (\n            <Spinner\n              width={isSingleMode ? 40 : 25}\n              height={isSingleMode ? 40 : 25}\n            />\n          ) : (\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              className=\"h-5 w-5\"\n              viewBox=\"0 0 20 20\"\n              fill=\"currentColor\"\n            >\n              <path\n                fillRule=\"evenodd\"\n                d=\"M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z\"\n                clipRule=\"evenodd\"\n              />\n            </svg>\n          )}\n        </label>\n      </div>\n      <div className=\"invisible\">\n        <input id={id} type=\"file\" multiple onChange={onChange} />\n      </div>\n    </div>\n  );\n};\n\nconst Image: React.FC<{\n  image: Image;\n  allowDelete?: boolean;\n  onDelete: (image) => void | Promise<void>;\n  isFirst?: boolean;\n  isSingleMode?: boolean;\n}> = ({ image, allowDelete, onDelete, isFirst, isSingleMode }) => {\n  const [deleting, setDeleting] = React.useState(false);\n  // Use ref to track if component is mounted\n  const isMounted = React.useRef(true);\n\n  // Set up effect for cleanup\n  React.useEffect(() => {\n    return () => {\n      // When component unmounts, set ref to false\n      isMounted.current = false;\n    };\n  }, []);\n\n  // Assign classes based on mode\n  const classes = isSingleMode\n    ? 'image border border-border rounded-lg'\n    : `image border border-border rounded-lg grid-item ${\n        isFirst ? 'first-item' : ''\n      }`;\n\n  return (\n    <div className={classes} id={image.uuid}>\n      <div className=\"img\">\n        <img src={image.url} alt=\"\" />\n      </div>\n      {allowDelete && (\n        <span\n          role=\"button\"\n          tabIndex={0}\n          className={`remove cursor-pointer text-destructive fill-current ${\n            isSingleMode ? 'single-mode-remove' : ''\n          }`}\n          onClick={async () => {\n            setDeleting(true);\n            await onDelete(image);\n            // Only update state if component is still mounted\n            if (isMounted.current) {\n              setDeleting(false);\n            }\n          }}\n          onKeyDown={() => {}}\n        >\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width={isSingleMode ? '20' : '16'}\n            height={isSingleMode ? '20' : '16'}\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            className=\"feather feather-trash-2\"\n          >\n            <polyline points=\"3 6 5 6 21 6\" />\n            <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" />\n            <line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\" />\n            <line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\" />\n          </svg>\n        </span>\n      )}\n      {deleting && (\n        <div className=\"remove\">\n          <Spinner width={15} height={15} />\n        </div>\n      )}\n    </div>\n  );\n};\n\nconst SortableImage: React.FC<{\n  image: Image;\n  allowDelete?: boolean;\n  onDelete: (image) => void | Promise<void>;\n  isFirst?: boolean;\n}> = (props) => {\n  const { attributes, listeners, setNodeRef, transform, transition } =\n    useSortable({\n      id: props.image.uuid\n    });\n\n  const style = {\n    transform: CSS.Transform.toString(transform),\n    transition\n  };\n\n  return (\n    <div\n      ref={setNodeRef}\n      style={style}\n      className={`grid-item ${props.isFirst ? 'first-item' : ''}`}\n      {...attributes}\n      {...listeners}\n    >\n      <Image {...props} />\n    </div>\n  );\n};\n\nconst GetUploadApiQuery = `\n  query Query ($filters: [FilterInput!]) {\n    imageUploadUrl: url(routeId: \"imageUpload\", params: [{key: \"0\", value: \"\"}])\n  }\n`;\n\nexport interface ImageUploaderProps {\n  currentImages?: Array<Image>;\n  isMultiple?: boolean;\n  allowDelete?: boolean;\n  onDelete?: (image: Image) => void | Promise<void>;\n  onUpload?: (images: Image[]) => void | Promise<void>;\n  targetPath?: string;\n  allowSwap?: boolean;\n  onSortEnd?: (oldIndex: number, newIndex: number) => void;\n}\ninterface ImagesProps extends ImageUploaderProps {\n  addImage: (imageArray: Image[]) => void;\n  imageUploadUrl: string;\n  onDelete: (image: Image) => void | Promise<void>;\n  onUpload: (images: Image[]) => void | Promise<void>;\n  targetPath?: string;\n  onSortEnd?: (oldIndex: number, newIndex: number) => void;\n}\nconst Images: React.FC<ImagesProps> = ({\n  allowDelete = true,\n  currentImages,\n  imageUploadUrl,\n  onDelete,\n  onUpload,\n  targetPath,\n  isMultiple,\n  allowSwap,\n  onSortEnd\n}) => {\n  const sensors = useSensors(\n    useSensor(PointerSensor, {\n      activationConstraint: {\n        distance: 8\n      }\n    }),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates\n    })\n  );\n\n  const handleDragEnd = (event: DragEndEvent) => {\n    const { active, over } = event;\n\n    if (active.id !== over?.id && onSortEnd && currentImages) {\n      const oldIndex = currentImages.findIndex((img) => img.uuid === active.id);\n      const newIndex = currentImages.findIndex((img) => img.uuid === over?.id);\n\n      if (oldIndex !== -1 && newIndex !== -1) {\n        onSortEnd(oldIndex, newIndex);\n      }\n    }\n  };\n\n  if (!isMultiple) {\n    const hasImage = currentImages && currentImages.length > 0;\n\n    return (\n      <div className={`single-image-container ${!hasImage ? 'no-image' : ''}`}>\n        {hasImage ? (\n          <Image\n            key={currentImages[0].uuid}\n            image={currentImages[0]}\n            onDelete={onDelete}\n            allowDelete={allowDelete}\n            isSingleMode={true}\n          />\n        ) : null}\n        <Upload\n          imageUploadUrl={imageUploadUrl}\n          targetPath={targetPath}\n          onUpload={onUpload}\n          isSingleMode={true}\n        />\n      </div>\n    );\n  } else if (allowSwap && currentImages && currentImages.length > 1) {\n    return (\n      <DndContext\n        sensors={sensors}\n        collisionDetection={closestCenter}\n        onDragEnd={handleDragEnd}\n      >\n        <SortableContext items={currentImages.map((img) => img.uuid)}>\n          {currentImages.map((image, index) => (\n            <SortableImage\n              key={image.uuid}\n              image={image}\n              onDelete={onDelete}\n              allowDelete={allowDelete}\n              isFirst={index === 0}\n            />\n          ))}\n        </SortableContext>\n        <Upload\n          imageUploadUrl={imageUploadUrl}\n          targetPath={targetPath}\n          onUpload={onUpload}\n        />\n      </DndContext>\n    );\n  }\n\n  // Multi-image mode without drag and drop\n  return (\n    <>\n      {(currentImages || []).map((image, index) => (\n        <Image\n          key={image.uuid}\n          image={image}\n          onDelete={onDelete}\n          allowDelete={allowDelete}\n          isFirst={index === 0}\n        />\n      ))}\n      <Upload\n        imageUploadUrl={imageUploadUrl}\n        targetPath={targetPath}\n        onUpload={onUpload}\n      />\n    </>\n  );\n};\n\nexport function ImageUploader({\n  currentImages = [],\n  isMultiple = true,\n  allowDelete = true,\n  onDelete,\n  onUpload,\n  allowSwap = true,\n  targetPath,\n  onSortEnd\n}: ImageUploaderProps) {\n  const [images, setImages] = React.useState<Image[]>(\n    currentImages.map((image) => ({\n      uuid: image.uuid,\n      url: image.url,\n      path: image.path\n    }))\n  );\n\n  const handleSortEnd = (oldIndex: number, newIndex: number) => {\n    setImages((items) => {\n      return arrayMove(items, oldIndex, newIndex);\n    });\n    if (onSortEnd) {\n      onSortEnd(oldIndex, newIndex);\n    }\n  };\n\n  const addImage = (imageArray: Image[]) => {\n    if (!isMultiple) {\n      // For single image mode, replace the current image\n      setImages(imageArray);\n    } else {\n      setImages(images.concat(imageArray));\n    }\n  };\n\n  const removeImage = (imageUuid) => {\n    setImages(images.filter((i) => i.uuid !== imageUuid));\n  };\n\n  const onDeleteFn = async (image: Image) => {\n    if (onDelete) {\n      await onDelete(image);\n    }\n    removeImage(image.uuid);\n  };\n\n  const onUploadFn = async (imageArray: Image[]) => {\n    if (onUpload) {\n      await onUpload(imageArray);\n    }\n    addImage(imageArray);\n  };\n\n  const [result] = useQuery({\n    query: GetUploadApiQuery\n  });\n  const { data, fetching, error } = result;\n\n  if (error) {\n    return (\n      <p className=\"text-destructive\">There was an error:{error.message}</p>\n    );\n  } else if (fetching) {\n    return <ImageUploaderSkeleton itemCount={isMultiple ? 5 : 1} />;\n  } else {\n    return (\n      <div className=\"image-uploader-manager\">\n        <div\n          id={'image-uploader-wrapper'}\n          className={isMultiple ? 'image-list' : ''}\n        >\n          <Images\n            currentImages={images}\n            addImage={addImage}\n            imageUploadUrl={data.imageUploadUrl}\n            targetPath={targetPath}\n            onDelete={onDeleteFn}\n            onUpload={onUploadFn}\n            allowDelete={allowDelete}\n            allowSwap={allowSwap && isMultiple}\n            onSortEnd={handleSortEnd}\n            isMultiple={isMultiple}\n          />\n        </div>\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/ImageUploaderSkeleton.tsx",
    "content": "import React from 'react';\n\ninterface ImageUploaderSkeletonProps {\n  itemCount?: number;\n}\n\nexport const ImageUploaderSkeleton: React.FC<ImageUploaderSkeletonProps> = ({\n  itemCount = 5\n}) => {\n  const items = Array(itemCount).fill(0);\n\n  if (itemCount === 1) {\n    return (\n      <div className=\"flex justify-center\">\n        <div\n          className=\"relative border border-dashed border-gray-300 rounded flex items-center justify-center bg-gray-50 animate-pulse\"\n          style={{ aspectRatio: '1/1', width: '300px', height: '300px' }}\n        >\n          <div className=\"absolute top-2 right-2\">\n            <div className=\"w-4 h-4 rounded-full bg-gray-200\"></div>\n          </div>\n          <svg\n            style={{ width: '30px', height: '30px' }}\n            xmlns=\"http://www.w3.org/2000/svg\"\n            className=\"h-5 w-5\"\n            viewBox=\"0 0 20 20\"\n            fill=\"#e5e7eb\"\n          >\n            <path\n              fillRule=\"evenodd\"\n              d=\"M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z\"\n              clipRule=\"evenodd\"\n            />\n          </svg>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"grid grid-cols-4 gap-2\">\n      <div\n        className=\"col-span-2 row-span-2 relative border border-dashed border-gray-300 rounded flex items-center justify-center bg-gray-50 animate-pulse\"\n        style={{ aspectRatio: '1/1', minHeight: '200px' }}\n      >\n        <svg\n          style={{ width: '30px', height: '30px' }}\n          xmlns=\"http://www.w3.org/2000/svg\"\n          className=\"h-5 w-5\"\n          viewBox=\"0 0 20 20\"\n          fill=\"#e5e7eb\"\n        >\n          <path\n            fillRule=\"evenodd\"\n            d=\"M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z\"\n            clipRule=\"evenodd\"\n          />\n        </svg>\n      </div>\n\n      {items.slice(1, itemCount).map((_, index) => (\n        <div\n          key={index}\n          className=\"relative border border-dashed border-gray-300 rounded flex items-center justify-center bg-gray-50 animate-pulse\"\n          style={{ aspectRatio: '1/1', minHeight: '100px' }}\n        >\n          <svg\n            style={{ width: '30px', height: '30px' }}\n            xmlns=\"http://www.w3.org/2000/svg\"\n            className=\"h-5 w-5\"\n            viewBox=\"0 0 20 20\"\n            fill=\"#e5e7eb\"\n          >\n            <path\n              fillRule=\"evenodd\"\n              d=\"M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z\"\n              clipRule=\"evenodd\"\n            />\n          </svg>\n        </div>\n      ))}\n      <div\n        className=\"border border-dashed border-gray-300 rounded flex items-center justify-center bg-gray-50\"\n        style={{ aspectRatio: '1/1', minHeight: '100px' }}\n      >\n        <svg\n          style={{ width: '30px', height: '30px' }}\n          xmlns=\"http://www.w3.org/2000/svg\"\n          className=\"h-5 w-5\"\n          viewBox=\"0 0 20 20\"\n          fill=\"#e5e7eb\"\n        >\n          <path\n            fillRule=\"evenodd\"\n            d=\"M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z\"\n            clipRule=\"evenodd\"\n          />\n        </svg>\n      </div>\n    </div>\n  );\n};\n\nexport default { ImageUploaderSkeleton };\n"
  },
  {
    "path": "packages/evershop/src/components/admin/NavigationItem.scss",
    "content": ".nav-item {\n  .menu-icon {\n    padding-right: 10px;\n    align-self: center;\n    svg {\n      width: 15px;\n      height: 15px;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/NavigationItem.tsx",
    "content": "import React from 'react';\nimport './NavigationItem.scss';\n\nexport interface NavigationItemProps {\n  Icon: React.ElementType;\n  url: string;\n  title: string;\n}\n\nexport function NavigationItem({ Icon, url, title }: NavigationItemProps) {\n  const [isActive, setIsActive] = React.useState(false);\n\n  React.useEffect(() => {\n    const checkActive = () => {\n      const currentUrl = window.location.href;\n      const currentUrlObj = new URL(currentUrl);\n      const menuUrlObj = new URL(url);\n\n      const currentPath = currentUrlObj.pathname;\n      const menuPath = menuUrlObj.pathname;\n\n      if (currentPath === menuPath) {\n        setIsActive(true);\n        return;\n      }\n\n      const menuSegments = menuPath.split('/').filter(Boolean);\n\n      if (menuSegments.length >= 2 && currentPath.startsWith(menuPath + '/')) {\n        const remainingPath = currentPath.substring(menuPath.length + 1);\n        const nextSegment = remainingPath.split('/')[0];\n\n        const actionWords = ['new', 'create', 'add'];\n        if (!actionWords.includes(nextSegment.toLowerCase())) {\n          setIsActive(true);\n          return;\n        }\n      }\n\n      setIsActive(false);\n    };\n\n    checkActive();\n  }, [url]);\n\n  return (\n    <li className={isActive ? 'active nav-item' : 'nav-item'}>\n      <a href={url} className=\"flex justify-left\">\n        <i className=\"menu-icon\">\n          <Icon />\n        </i>\n        {title}\n      </a>\n    </li>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/NavigationItemGroup.scss",
    "content": ".nav-item {\n  // Open, close transition\n  &.closed {\n    // Close transition\n    // Hide the children\n    .item-group {\n      display: none;\n    }\n  }\n  .item-group {\n    .nav-item {\n      > a {\n        padding-left: 1.25rem;\n      }\n    }\n  }\n}\n.root-label {\n  span:first-child {\n    padding-right: 10px;\n  }\n}\n.root-nav-item {\n  &:last-child {\n    position: fixed;\n    bottom: 0px;\n    left: 0px;\n    width: 200px;\n    background-color: #fff;\n    padding-bottom: 1.25rem;\n    padding-top: 1rem;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/NavigationItemGroup.tsx",
    "content": "import {\n  NavigationItem,\n  NavigationItemProps\n} from '@components/admin/NavigationItem.js';\nimport Area from '@components/common/Area.jsx';\nimport React from 'react';\nimport './NavigationItemGroup.scss';\n\ninterface NavigationItemGroupProps {\n  id: string;\n  name: string;\n  items: NavigationItemProps[];\n  Icon: React.ElementType | null;\n  url: string | null;\n}\n\nexport function NavigationItemGroup({\n  id,\n  name,\n  items = [],\n  Icon = null,\n  url = null\n}: NavigationItemGroupProps) {\n  return (\n    <li className=\"root-nav-item nav-item\">\n      <div className=\"flex justify-between items-center\">\n        <div className=\"root-label flex justify-between items-center\">\n          {Icon && (\n            <span>\n              <Icon />\n            </span>\n          )}\n          {!url && <span>{name}</span>}\n          {url && <a href={url}>{name}</a>}\n        </div>\n      </div>\n      <ul className=\"item-group\">\n        <Area\n          id={id}\n          noOuter\n          coreComponents={items.map((item) => ({\n            component: {\n              default: () => (\n                <NavigationItem\n                  Icon={item.Icon}\n                  url={item.url}\n                  title={item.title}\n                />\n              )\n            }\n          }))}\n        />\n      </ul>\n    </li>\n  );\n}\n\nNavigationItemGroup.defaultProps = {\n  items: [],\n  Icon: null,\n  url: null\n};\n"
  },
  {
    "path": "packages/evershop/src/components/admin/PageHeading.scss",
    "content": ".page-heading {\n  margin: 1rem auto 2rem;\n  h1 {\n    font-size: 1.25rem;\n    font-weight: 600;\n  }\n}\n.breadcrum-icon {\n  width: 2.125rem;\n  height: 2.125rem;\n  span {\n    width: 100%;\n    height: 100%;\n    svg {\n      fill: var(--icon);\n      width: 65%;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/PageHeading.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport React from 'react';\nimport './PageHeading.scss';\n\nfunction BackIcon({ backUrl }: { backUrl?: string }) {\n  if (!backUrl) return null;\n  return (\n    <a\n      href={backUrl}\n      className=\"breadcrum-icon border block border-border rounded mr-2\"\n    >\n      <span className=\"flex items-center justify-center\">\n        <svg\n          className=\"text-icon\"\n          viewBox=\"0 0 20 20\"\n          focusable=\"false\"\n          aria-hidden=\"true\"\n        >\n          <path d=\"M17 9H5.414l3.293-3.293a.999.999 0 1 0-1.414-1.414l-5 5a.999.999 0 0 0 0 1.414l5 5a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414L5.414 11H17a1 1 0 1 0 0-2z\" />\n        </svg>\n      </span>\n    </a>\n  );\n}\n\nBackIcon.defaultProps = {\n  backUrl: undefined\n};\n\nfunction Heading({ heading }: { heading: string }) {\n  return (\n    <div className=\"self-center\">\n      <h1 className=\"page-heading-title\">{heading}</h1>\n    </div>\n  );\n}\n\nexport interface PageHeadingProps {\n  backUrl?: string;\n  heading: string;\n}\n\nfunction PageHeading({ backUrl, heading }: PageHeadingProps) {\n  if (!heading) {\n    return null;\n  }\n\n  return (\n    <div className=\"page-heading flex justify-between items-center\">\n      <div className=\"flex justify-start space-x-2 items-center\">\n        <Area\n          id=\"pageHeadingLeft\"\n          noOuter\n          coreComponents={[\n            {\n              component: { default: BackIcon },\n              props: {\n                backUrl\n              },\n              sortOrder: 0,\n              id: 'breadcrumb'\n            },\n            {\n              component: { default: Heading },\n              props: {\n                heading\n              },\n              sortOrder: 0,\n              id: 'heading'\n            }\n          ]}\n        />\n      </div>\n      <div className=\"flex justify-end space-x-2 items-center\">\n        <Area id=\"pageHeadingRight\" noOuter coreComponents={[]} />\n      </div>\n    </div>\n  );\n}\n\nPageHeading.defaultProps = {\n  backUrl: undefined\n};\n\nexport { PageHeading };\n"
  },
  {
    "path": "packages/evershop/src/components/admin/ProductListSkeleton.tsx",
    "content": "import { Skeleton } from '@components/common/ui/Skeleton.js';\nimport React from 'react';\n\nexport const ProductListSkeleton: React.FC = () => {\n  const skeletonItems = Array(5).fill(0);\n\n  return (\n    <div className=\"attribute-group-list-skeleton space-y-2 divide-y\">\n      {skeletonItems.map((_, index) => (\n        <div\n          key={index}\n          className=\"attribute-group-skeleton-item border-border pb-2 flex justify-between items-center \"\n        >\n          <div className=\"flex items-center\">\n            <Skeleton className=\"h-5 w-30 rounded\"></Skeleton>\n          </div>\n          <div className=\"select-button\">\n            <Skeleton className=\"h-6 w-12 rounded\"></Skeleton>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/admin/ProductSelector.tsx",
    "content": "import { SimplePagination } from '@components/common/SimplePagination.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport { Check } from 'lucide-react';\nimport React from 'react';\nimport { toast } from 'react-toastify';\nimport { useQuery } from 'urql';\nimport { AtLeastOne } from '../../types/atLeastOne.js';\nimport { ProductListSkeleton } from './ProductListSkeleton.js';\n\nconst SearchQuery = `\n  query Query ($filters: [FilterInput!]) {\n    products(filters: $filters) {\n      items {\n        productId\n        uuid\n        sku\n        name\n        price {\n          regular {\n            text\n          }\n        }\n        image {\n          url\n        }\n      }\n      total\n    }\n  }\n`;\n\ntype ProductIdentifier = {\n  sku?: string;\n  uuid?: string;\n  productId?: string;\n};\n\nconst isProductSelected = (\n  product: ProductIdentifier,\n  selectedProducts: Array<AtLeastOne<ProductIdentifier>>\n): boolean => {\n  return selectedProducts.some(\n    (selected) =>\n      (selected?.sku && selected.sku === product.sku) ||\n      (selected?.uuid && selected.uuid === product.uuid) ||\n      (selected?.productId && selected.productId === product.productId)\n  );\n};\n\nconst ProductSelector: React.FC<{\n  onSelect: (\n    sku: string,\n    uuid: string,\n    productId: string\n  ) => Promise<void> | void;\n  onUnSelect?: (\n    sku: string,\n    uuid: string,\n    productId: string\n  ) => Promise<void> | void;\n  selectedProducts: Array<AtLeastOne<ProductIdentifier>>;\n}> = ({ onSelect, onUnSelect, selectedProducts }) => {\n  const limit = 10;\n  const [internalSelectedProducts, setSelectedProducts] = React.useState<\n    Array<AtLeastOne<ProductIdentifier>>\n  >(selectedProducts || []);\n  const [inputValue, setInputValue] = React.useState<string | null>(null);\n  const [loading, setLoading] = React.useState(false);\n  const [page, setPage] = React.useState<number>(1);\n\n  const [result, reexecuteQuery] = useQuery({\n    query: SearchQuery,\n    variables: {\n      filters: inputValue\n        ? [\n            { key: 'keyword', operation: 'eq', value: inputValue },\n            { key: 'page', operation: 'eq', value: page.toString() },\n            { key: 'limit', operation: 'eq', value: limit.toString() }\n          ]\n        : [\n            { key: 'limit', operation: 'eq', value: limit.toString() },\n            { key: 'page', operation: 'eq', value: page.toString() }\n          ]\n    },\n    pause: true\n  });\n\n  const selectProduct = async (\n    sku: string,\n    uuid: string,\n    productId: string\n  ) => {\n    setSelectedProducts((prev) => [...prev, { sku, uuid, productId }]);\n    try {\n      await onSelect(sku, uuid, productId);\n    } catch (e) {\n      toast.error(e.message);\n    }\n  };\n\n  const unSelectProduct = async (\n    sku: string,\n    uuid: string,\n    productId: string\n  ) => {\n    if (!onUnSelect) {\n      return;\n    }\n    setSelectedProducts((prev) =>\n      prev.filter((product) => product?.sku !== sku)\n    );\n    try {\n      await onUnSelect(sku, uuid, productId);\n    } catch (e) {\n      toast.error(e.message);\n    }\n  };\n\n  React.useEffect(() => {\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  }, [page]);\n\n  React.useEffect(() => {\n    const timer = setTimeout(() => {\n      setLoading(false);\n      if (inputValue !== null) {\n        reexecuteQuery({ requestPolicy: 'network-only' });\n      }\n    }, 1500);\n\n    return () => clearTimeout(timer);\n  }, [inputValue]);\n\n  const { data, fetching, error } = result;\n\n  if (error) {\n    return (\n      <p className=\"text-destructive\">\n        There was an error fetching products.\n        {error.message}\n      </p>\n    );\n  }\n\n  return (\n    <div>\n      <div className=\"p-2\">\n        <Input\n          type=\"text\"\n          value={inputValue || ''}\n          placeholder=\"Search products\"\n          onChange={(e) => {\n            setInputValue(e.target.value);\n            setLoading(true);\n          }}\n        />\n      </div>\n      {(fetching || loading) && <ProductListSkeleton />}\n      {!fetching && data && !loading && (\n        <div className=\"divide-y\">\n          {data.products.items.length === 0 && (\n            <div className=\"p-2 border border-divider rounded flex justify-center items-center\">\n              {inputValue ? (\n                <p>No products found for query &quot;{inputValue}&rdquo;</p>\n              ) : (\n                <p>You have no products to display</p>\n              )}\n            </div>\n          )}\n          {data.products.items.map((product) => (\n            <div\n              key={product.uuid}\n              className=\"grid grid-cols-8 gap-5 py-2 border-divider items-center\"\n            >\n              <div className=\"col-span-1\">\n                <div className=\"text-border border border-divider p-2 rounded flex justify-center w-10 h-10\">\n                  {product.image?.url && (\n                    <img src={product.image?.url} alt={product.name} />\n                  )}\n                  {!product.image?.url && (\n                    <svg\n                      className=\"self-center\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                      width=\"2rem\"\n                      fill=\"none\"\n                      viewBox=\"0 0 24 24\"\n                      stroke=\"currentColor\"\n                    >\n                      <path\n                        strokeLinecap=\"round\"\n                        strokeLinejoin=\"round\"\n                        strokeWidth={2}\n                        d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n                      />\n                    </svg>\n                  )}\n                </div>\n              </div>\n              <div className=\"col-span-5\">\n                <h3>{product.name}</h3>\n                <p>{product.sku}</p>\n              </div>\n              <div className=\"col-span-2 text-right\">\n                {!isProductSelected(product, internalSelectedProducts) && (\n                  <Button\n                    variant={'outline'}\n                    onClick={async (e) => {\n                      e.preventDefault();\n                      await selectProduct(\n                        product.sku,\n                        product.uuid,\n                        product.productId\n                      );\n                    }}\n                  >\n                    Select\n                  </Button>\n                )}\n                {isProductSelected(product, internalSelectedProducts) && (\n                  <Button\n                    onClick={(e) => {\n                      e.preventDefault();\n                      unSelectProduct(\n                        product.sku,\n                        product.uuid,\n                        product.productId\n                      );\n                    }}\n                  >\n                    <Check width={'1.2rem'} height={'1.2rem'} />\n                  </Button>\n                )}\n              </div>\n            </div>\n          ))}\n        </div>\n      )}\n      <div className=\"flex justify-between gap-5 pt-5\">\n        <SimplePagination\n          total={data?.products.total || 0}\n          count={data?.products?.items?.length || 0}\n          page={page}\n          hasNext={limit * page < data?.products.total}\n          setPage={setPage}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport { ProductSelector };\n"
  },
  {
    "path": "packages/evershop/src/components/admin/SettingMenu.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport React from 'react';\n\nexport function SettingMenu() {\n  return (\n    <div className=\"setting-page-menu space-y-3\">\n      <Area id=\"settingPageMenu\" noOuter coreComponents={[]} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/Spinner.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\n\nfunction Spinner({ width, height }) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      style={{ margin: 'auto' }}\n      width={width}\n      height={height}\n      display=\"block\"\n      preserveAspectRatio=\"xMidYMid\"\n      viewBox=\"0 0 100 100\"\n    >\n      <g transform=\"translate(50 50) scale(.7)\">\n        <circle r=\"50\" className=\"fill-primary\" />\n        <circle cy=\"-28\" r=\"15\" className=\"fill-secondary\">\n          <animateTransform\n            attributeName=\"transform\"\n            dur=\"1s\"\n            keyTimes=\"0;1\"\n            repeatCount=\"indefinite\"\n            type=\"rotate\"\n            values=\"0 0 0;360 0 0\"\n          />\n        </circle>\n      </g>\n    </svg>\n  );\n}\n\nSpinner.propTypes = {\n  width: PropTypes.number,\n  height: PropTypes.number\n};\n\nSpinner.defaultProps = {\n  width: 60,\n  height: 60\n};\n\nexport default Spinner;\n"
  },
  {
    "path": "packages/evershop/src/components/admin/Status.tsx",
    "content": "import { Badge } from '@components/common/ui/Badge.js';\nimport { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\nexport interface StatusProps {\n  status: number;\n}\nexport function Status({ status }: StatusProps) {\n  return (\n    <TableCell>\n      <div>\n        {status === 0 && <Badge variant=\"destructive\">Inactive</Badge>}\n        {status === 1 && <Badge variant=\"success\">Active</Badge>}\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/grid/GridPagination.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport {\n  Pagination,\n  PaginationContent,\n  PaginationEllipsis,\n  PaginationItem,\n  PaginationLink,\n  PaginationNext,\n  PaginationPrevious\n} from '@components/common/ui/Pagination.js';\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectTrigger,\n  SelectValue\n} from '@components/common/ui/Select.js';\nimport React from 'react';\n\nexport interface GridPaginationProps {\n  total: number;\n  limit: number;\n  page: number;\n}\n\nexport function GridPagination({ total, limit, page }: GridPaginationProps) {\n  const limitInput = React.useRef<HTMLInputElement>(null);\n\n  React.useEffect(() => {\n    if (limitInput.current) {\n      limitInput.current.value = limit.toString();\n    }\n  }, []);\n\n  const onKeyPress = (e: React.ChangeEvent<HTMLSelectElement>) => {\n    e.preventDefault();\n    let pageNumber = parseInt(e.target.value, 10);\n    if (page < 1) pageNumber = 1;\n    if (page > Math.ceil(total / limit)) pageNumber = Math.ceil(total / limit);\n    const url = new URL(window.location.href);\n    url.searchParams.set('page', pageNumber.toString());\n    window.location.href = url.href;\n  };\n\n  const onPrev = (e: React.MouseEvent) => {\n    e.preventDefault();\n    const prev = page - 1;\n    if (page === 1) return;\n    const url = new URL(window.location.href);\n    url.searchParams.set('page', prev.toString());\n    window.location.href = url.href;\n  };\n\n  const onNext = (e: React.MouseEvent) => {\n    e.preventDefault();\n    const next = page + 1;\n    if (page * limit >= total) return;\n    const url = new URL(window.location.href);\n    url.searchParams.set('page', next.toString());\n    window.location.href = url.href;\n  };\n\n  const onFirst = (e: React.MouseEvent) => {\n    e.preventDefault();\n    if (page === 1) return;\n    const url = new URL(window.location.href);\n    url.searchParams.delete('page');\n    window.location.href = url.href;\n  };\n\n  const onLast = (e: React.MouseEvent) => {\n    e.preventDefault();\n    if (page === Math.ceil(total / limit)) return;\n    const url = new URL(window.location.href);\n    url.searchParams.set('page', Math.ceil(total / limit).toString());\n    window.location.href = url.href;\n  };\n\n  return (\n    <div className=\"pagination flex w-full mt-3\">\n      <div className=\"flex justify-between w-full space-x-2\">\n        <ButtonGroup>\n          <Button variant={'outline'}>Show</Button>\n          <Select\n            value={limit.toString()}\n            onValueChange={(value) => {\n              const url = new URL(window.location.href);\n              url.searchParams.set('limit', value?.toString() || '10');\n              window.location.href = url.href;\n            }}\n          >\n            <SelectTrigger className=\"w-20\">\n              <SelectValue>{limit}</SelectValue>\n            </SelectTrigger>\n            <SelectContent>\n              <SelectGroup>\n                <SelectLabel>Limit</SelectLabel>\n                <SelectItem value=\"50\">50</SelectItem>\n                <SelectItem value=\"100\">100</SelectItem>\n                <SelectItem value=\"150\">150</SelectItem>\n                <SelectItem value=\"200\">200</SelectItem>\n              </SelectGroup>\n            </SelectContent>\n          </Select>\n        </ButtonGroup>\n        <div className=\"flex justify-end\">\n          <Pagination>\n            <PaginationContent>\n              <PaginationItem>\n                <PaginationPrevious\n                  onClick={(e) => {\n                    onPrev(e);\n                  }}\n                />\n              </PaginationItem>\n              {page > 1 && (\n                <PaginationItem>\n                  <PaginationLink\n                    isActive={page === 1}\n                    onClick={(e) => {\n                      onFirst(e);\n                    }}\n                  >\n                    1\n                  </PaginationLink>\n                </PaginationItem>\n              )}\n              {page >= 3 && (\n                <PaginationItem>\n                  <PaginationEllipsis />\n                </PaginationItem>\n              )}\n              <PaginationItem>\n                <PaginationLink isActive={true}>{page}</PaginationLink>\n              </PaginationItem>\n              {page < Math.ceil(total / limit) - 1 && (\n                <PaginationItem>\n                  <PaginationEllipsis />\n                </PaginationItem>\n              )}\n              {page * limit < total && (\n                <PaginationItem>\n                  <PaginationLink\n                    isActive={page === Math.ceil(total / limit)}\n                    onClick={(e) => {\n                      onLast(e);\n                    }}\n                  >\n                    {Math.ceil(total / limit)}\n                  </PaginationLink>\n                </PaginationItem>\n              )}\n              <PaginationNext onClick={onNext} />\n            </PaginationContent>\n          </Pagination>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/grid/Thumbnail.tsx",
    "content": "import { Image } from '@components/common/Image.js';\nimport { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\nexport interface ThumbnailProps {\n  src?: string;\n  name?: string;\n}\n\nexport function Thumbnail({ src, name }: ThumbnailProps) {\n  return (\n    <TableCell>\n      <div\n        className=\"grid-thumbnail text-border border border-divider p-2 rounded flex justify-center\"\n        style={{ width: '4rem', height: '4rem' }}\n      >\n        {src && (\n          <Image\n            className=\"self-center\"\n            src={src}\n            alt={name || ''}\n            width={100}\n            height={100}\n          />\n        )}\n        {!src && (\n          <svg\n            className=\"self-center\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"2rem\"\n            fill=\"none\"\n            viewBox=\"0 0 24 24\"\n            stroke=\"currentColor\"\n          >\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth={2}\n              d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n            />\n          </svg>\n        )}\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/grid/header/Dummy.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\nexport function DummyColumnHeader({ title }: { title: string }) {\n  return (\n    <TableCell>\n      <div className=\"font-medium uppercase text-xs\">\n        <span>{title}</span>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/admin/grid/header/Sortable.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\nfunction Up() {\n  return (\n    <svg\n      width=\"12\"\n      height=\"12\"\n      viewBox=\"0 0 17 23\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M1 8.5L8.5 1L16 8.5\"\n        stroke=\"black\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <path\n        d=\"M16 14L8.5 21.5L1 14\"\n        stroke=\"#e1e3e5\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction Down() {\n  return (\n    <svg\n      width=\"12\"\n      height=\"12\"\n      viewBox=\"0 0 17 23\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M1 8.5L8.5 1L16 8.5\"\n        stroke=\"#e1e3e5\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <path\n        d=\"M16 14L8.5 21.5L1 14\"\n        stroke=\"black\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction None() {\n  return (\n    <svg\n      width=\"12\"\n      height=\"12\"\n      viewBox=\"0 0 17 23\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M1 8.5L8.5 1L16 8.5\"\n        stroke=\"#e1e3e5\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <path\n        d=\"M16 14L8.5 21.5L1 14\"\n        stroke=\"#e1e3e5\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nexport interface SortableHeaderProps {\n  title: string;\n  name: string;\n  currentFilters: Array<{ key: string; value: string }>;\n}\n\nexport function SortableHeader({\n  title,\n  name,\n  currentFilters = []\n}: SortableHeaderProps) {\n  const [currentDirection] = React.useState(() => {\n    const currentOrderBy = currentFilters.find((filter) => filter.key === 'ob');\n    if (!currentOrderBy || currentOrderBy.value !== name) {\n      return null;\n    } else {\n      return (\n        currentFilters.find((filter) => filter.key === 'od')?.value || 'asc'\n      );\n    }\n  });\n  const onChange = () => {\n    const url = new URL(window.location.href);\n    url.searchParams.set('ob', name);\n    // Get the current direction by checking the currentFilters\n    const currentDirection = currentFilters.find(\n      (filter) => filter.key === 'od'\n    );\n    if (!currentDirection || currentDirection.value === 'asc') {\n      url.searchParams.set('od', 'desc');\n    } else {\n      url.searchParams.set('od', 'asc');\n    }\n    window.location.href = url.toString();\n  };\n\n  return (\n    <TableCell>\n      <div className=\"table-header flex justify-start gap-2 content-center\">\n        <div className=\"font-medium uppercase text-xs\">\n          <span>{title}</span>\n        </div>\n        <div className=\"sort flex items-center\">\n          <button type=\"button\" onClick={onChange}>\n            {currentDirection === 'asc' ? (\n              <Down />\n            ) : currentDirection === 'desc' ? (\n              <Up />\n            ) : (\n              <None />\n            )}\n          </button>\n        </div>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/Area.tsx",
    "content": "import { useAppState } from '@components/common/context/app.js';\nimport { generateComponentKey } from '@evershop/evershop/lib/util/keyGenerator';\nimport React, { useEffect, useState } from 'react';\nimport type { ElementType } from 'react';\n\ninterface Component {\n  id?: string;\n  sortOrder?: number;\n  props?: Record<string, any>;\n  component: {\n    default: React.ElementType | React.ReactNode;\n  };\n}\n\ntype AreaID = string;\ntype ComponentID = string;\n\ninterface Components {\n  [key: AreaID]: {\n    [key: ComponentID]: Component;\n  };\n}\n\ninterface AreaProps {\n  className?: string;\n  coreComponents?: Component[];\n  id: string;\n  noOuter?: boolean;\n  wrapper?: React.ReactNode | string;\n  wrapperProps?: Record<string, any>;\n  components?: Components;\n  [key: string]: unknown;\n}\n\ninterface Widget extends Component {\n  props: Record<string, any>;\n  type: string;\n  areaId: string[];\n}\n\nconst DEBUG_KEY = 'evershop_area_debug';\n\nlet toggleButtonMounted = false;\nlet debugStylesMounted = false;\n\nfunction injectDebugStyles() {\n  if (process.env.NODE_ENV !== 'development') return;\n  if (debugStylesMounted || typeof document === 'undefined') return;\n  debugStylesMounted = true;\n  const style = document.createElement('style');\n  style.id = 'evershop-debug-styles';\n  style.textContent = [\n    '.evershop-debug-area__badge { opacity: 0; transition: opacity 0.15s ease; }',\n    '.evershop-debug-area:hover > .evershop-debug-area__badge { opacity: 1; }'\n  ].join('\\n');\n  document.head.appendChild(style);\n}\n\nfunction injectToggleButton() {\n  if (process.env.NODE_ENV !== 'development') return;\n  if (toggleButtonMounted || typeof document === 'undefined') return;\n  toggleButtonMounted = true;\n\n  const btn = document.createElement('button');\n\n  const update = () => {\n    const active = localStorage.getItem(DEBUG_KEY) === '1';\n    btn.textContent = active ? 'Debug: ON' : 'Debug: OFF';\n    btn.style.background = active ? '#3b82f6' : '#6b7280';\n  };\n\n  Object.assign(btn.style, {\n    position: 'fixed',\n    bottom: '16px',\n    right: '16px',\n    zIndex: '99999',\n    padding: '6px 12px',\n    borderRadius: '6px',\n    border: 'none',\n    color: '#fff',\n    fontFamily: 'monospace',\n    fontSize: '12px',\n    cursor: 'pointer',\n    boxShadow: '0 2px 8px rgba(0,0,0,0.3)',\n    transition: 'background 0.2s'\n  });\n\n  btn.title = 'Toggle Area debug mode';\n  update();\n\n  btn.addEventListener('click', () => {\n    const next = localStorage.getItem(DEBUG_KEY) === '1' ? '0' : '1';\n    localStorage.setItem(DEBUG_KEY, next);\n    // Notify all tabs and same-page listeners\n    window.dispatchEvent(\n      new StorageEvent('storage', {\n        key: DEBUG_KEY,\n        newValue: next,\n        storageArea: localStorage\n      })\n    );\n    update();\n  });\n\n  document.body.appendChild(btn);\n}\n\nfunction useDebugMode(): boolean {\n  const [debug, setDebug] = useState(() => {\n    if (process.env.NODE_ENV !== 'development') return false;\n    try {\n      return localStorage.getItem(DEBUG_KEY) === '1';\n    } catch {\n      return false;\n    }\n  });\n\n  useEffect(() => {\n    if (process.env.NODE_ENV !== 'development') return;\n    injectToggleButton();\n    injectDebugStyles();\n\n    const handler = (e: StorageEvent) => {\n      if (e.key === DEBUG_KEY) {\n        setDebug(e.newValue === '1');\n      }\n    };\n    window.addEventListener('storage', handler);\n    return () => window.removeEventListener('storage', handler);\n  }, []);\n\n  return debug;\n}\n\nconst AREA_COLORS = [\n  '#3b82f6',\n  '#10b981',\n  '#f59e0b',\n  '#ef4444',\n  '#8b5cf6',\n  '#ec4899',\n  '#06b6d4',\n  '#84cc16',\n  '#f97316',\n  '#6366f1',\n  '#db2777',\n  '#14b8a6',\n  '#22c55e',\n  '#eab308',\n  '#f43f5e'\n];\n\n// Stable color per area ID\nfunction areaColor(id: string | undefined): string {\n  if (!id) return AREA_COLORS[0];\n  let hash = 0;\n  for (let i = 0; i < id.length; i++) {\n    hash = (hash << 5) - hash + id.charCodeAt(i);\n    hash |= 0;\n  }\n  return AREA_COLORS[Math.abs(hash) % AREA_COLORS.length];\n}\n\nfunction Area(props: AreaProps) {\n  const context = useAppState();\n  const debug = useDebugMode();\n  const {\n    id,\n    coreComponents,\n    wrapperProps,\n    noOuter,\n    wrapper,\n    className,\n    components\n  } = props;\n\n  const areaComponents = (() => {\n    const areaCoreComponents = coreComponents || [];\n    const widgets = context.widgets || [];\n    const wildCardWidgets = components?.['*'] || {};\n    const assignedWidgets: Component[] = [];\n\n    widgets.forEach((widget: Widget) => {\n      const adminKey = generateComponentKey(`admin_widget_${widget.type}`);\n      const frontKey = generateComponentKey(`widget_${widget.type}`);\n      const w = wildCardWidgets[adminKey] || wildCardWidgets[frontKey];\n      if (widget.areaId.includes(id) && w !== undefined) {\n        assignedWidgets.push({\n          id: widget.id,\n          sortOrder: widget.sortOrder,\n          props: widget.props,\n          component: w.component\n        });\n      }\n    });\n    const cs =\n      components?.[id] === undefined\n        ? areaCoreComponents.concat(assignedWidgets)\n        : areaCoreComponents\n            .concat(Object.values(components[id]))\n            .concat(assignedWidgets);\n    return cs.sort(\n      (obj1, obj2) => (obj1.sortOrder || 0) - (obj2.sortOrder || 0)\n    );\n  })();\n  const { propsMap } = context;\n  // In debug mode, always use a real wrapper element so borders/badges can render.\n  // noOuter is intentionally ignored when debug is active.\n  // The process.env.NODE_ENV guard lets Terser statically eliminate this in production.\n  const effectiveNoOuter =\n    process.env.NODE_ENV === 'development' && debug ? false : noOuter;\n\n  let WrapperComponent: ElementType = React.Fragment;\n  if (effectiveNoOuter !== true) {\n    if (wrapper !== undefined) {\n      WrapperComponent = wrapper as ElementType;\n    } else {\n      WrapperComponent = 'div';\n    }\n  }\n\n  let areaWrapperProps: Record<string, any> = {};\n  if (effectiveNoOuter === true) {\n    areaWrapperProps = {};\n  } else if (typeof wrapperProps === 'object' && wrapperProps !== null) {\n    areaWrapperProps = { className: className || '', ...wrapperProps };\n  } else {\n    areaWrapperProps = { className: className || '' };\n  }\n\n  const color =\n    process.env.NODE_ENV === 'development' && debug ? areaColor(id) : '';\n\n  if (\n    process.env.NODE_ENV === 'development' &&\n    debug &&\n    effectiveNoOuter !== true\n  ) {\n    const existingStyle = areaWrapperProps.style || {};\n    const existingClass = (areaWrapperProps.className || '') as string;\n    areaWrapperProps = {\n      ...areaWrapperProps,\n      className: `${existingClass} evershop-debug-area`.trim(),\n      style: {\n        ...existingStyle,\n        position: 'relative',\n        border: `2px dashed ${color}`,\n        padding: '5px',\n        boxSizing: 'border-box',\n        minHeight: '32px'\n      }\n    };\n  }\n\n  const renderedChildren = areaComponents.map((w, index) => {\n    const C = w.component.default;\n\n    const { id: componentId } = w;\n    const propsData = context.graphqlResponse;\n    const propKeys =\n      componentId !== undefined ? propsMap[componentId] || [] : [];\n\n    const componentProps = propKeys.reduce(\n      (acc: Record<string, any>, map: Record<string, any>) => {\n        const { origin, alias } = map;\n        acc[origin] = propsData[alias];\n        return acc;\n      },\n      {}\n    );\n    if (w.props) {\n      Object.assign(componentProps, w.props);\n    }\n\n    let rendered: React.ReactNode = null;\n\n    if (React.isValidElement(C)) {\n      rendered = <React.Fragment key={index}>{C}</React.Fragment>;\n    } else if (typeof C === 'string') {\n      rendered = <C key={index} {...componentProps} />;\n    } else if (typeof C === 'function') {\n      rendered = <C key={index} areaProps={props} {...componentProps} />;\n    }\n\n    if (!debug || rendered === null || process.env.NODE_ENV !== 'development') {\n      return rendered;\n    }\n\n    return (\n      <div\n        key={index}\n        className=\"evershop-debug-child\"\n        style={{\n          position: 'relative',\n          outline: `1px solid ${color}40`,\n          outlineOffset: '1px'\n        }}\n      >\n        <span\n          style={{\n            position: 'absolute',\n            top: 0,\n            right: 0,\n            zIndex: 9999,\n            background: `${color}cc`,\n            color: '#fff',\n            fontSize: '9px',\n            fontFamily: 'monospace',\n            padding: '1px 5px',\n            borderRadius: '0 0 0 4px',\n            lineHeight: '16px',\n            whiteSpace: 'nowrap',\n            pointerEvents: 'none'\n          }}\n        >\n          order: {w.sortOrder ?? 0}\n        </span>\n        {rendered}\n      </div>\n    );\n  });\n\n  if (process.env.NODE_ENV === 'development' && debug) {\n    return (\n      <WrapperComponent {...areaWrapperProps}>\n        <span\n          className=\"evershop-debug-area__badge\"\n          style={{\n            position: 'absolute',\n            top: 0,\n            left: 0,\n            zIndex: 9999,\n            background: color,\n            color: '#fff',\n            fontSize: '10px',\n            fontFamily: 'monospace',\n            padding: '1px 6px',\n            borderRadius: '0 0 4px 0',\n            lineHeight: '16px',\n            whiteSpace: 'nowrap',\n            cursor: 'default'\n          }}\n          title={`Area: #${id}`}\n        >\n          #{id}\n        </span>\n        {renderedChildren}\n      </WrapperComponent>\n    );\n  }\n\n  return (\n    <WrapperComponent {...areaWrapperProps}>\n      {renderedChildren}\n    </WrapperComponent>\n  );\n}\n\nArea.defaultProps = {\n  className: undefined,\n  coreComponents: [],\n  noOuter: false,\n  wrapper: 'div',\n  wrapperProps: {}\n};\n\nexport { Area };\nexport default Area;\n"
  },
  {
    "path": "packages/evershop/src/components/common/Editor.scss",
    "content": ".prose-base {\n  margin-top: 1rem;\n  margin-bottom: 1rem;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/Editor.tsx",
    "content": "import { getColumnClasses } from '@components/common/form/editor/GetColumnClasses.js';\nimport { getRowClasses } from '@components/common/form/editor/GetRowClasses.js';\nimport { Row } from '@components/common/form/Editor.js';\nimport { Image as ResponsiveImage } from '@components/common/Image.js';\nimport React from 'react';\nimport './Editor.scss';\n\nconst Paragraph: React.FC<{ data: { text: string } }> = ({ data }) => {\n  return <p dangerouslySetInnerHTML={{ __html: data.text }} />;\n};\n\nconst Header: React.FC<{ data: { level: number; text: string } }> = ({\n  data\n}) => {\n  const tagName = `h${data.level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';\n  return React.createElement(tagName, null, data.text);\n};\n\nconst List: React.FC<{ data: { items: string[] } }> = ({ data }) => {\n  return (\n    <ul>\n      {data.items.map((item, index) => (\n        <li key={index}>{item}</li>\n      ))}\n    </ul>\n  );\n};\n\nconst Quote: React.FC<{ data: { text: string; caption?: string } }> = ({\n  data\n}) => {\n  return (\n    <blockquote>\n      <p>&quot;{data.text}&quot;</p>\n      {data.caption && <cite>- {data.caption}</cite>}\n    </blockquote>\n  );\n};\n\nconst Image: React.FC<{\n  data: {\n    file: { url: string; width?: number; height?: number };\n    caption?: string;\n    withBorder?: boolean;\n    withBackground?: boolean;\n    stretched?: boolean;\n    link?: string;\n  };\n  columnSize: number;\n}> = ({ data, columnSize }) => {\n  const { file, caption, withBorder, withBackground, stretched, link } = data;\n\n  const imageStyles = {\n    border: withBorder ? '1px solid #ccc' : 'none',\n    backgroundColor: withBackground ? '#f9f9f9' : 'transparent',\n    width: stretched ? '100%' : 'auto',\n    display: 'block',\n    maxWidth: '100%',\n    margin: '0 auto'\n  };\n\n  const imageWidth = file.width || 800;\n  const imageHeight =\n    file.height || (file.width ? Math.round(file.width * 0.75) : 600);\n\n  // Calculate responsive sizes based on the columnSize prop\n  // columnSize represents the fraction of the row that this column occupies (e.g., 1/2, 1/3, 2/3, etc.)\n  let sizesValue: string;\n\n  sizesValue = '100vw'; // On mobile, always full viewport width\n\n  if (columnSize <= 0.25) {\n    sizesValue = '(max-width: 640px) 100vw, (max-width: 768px) 80vw, 25vw';\n  } else if (columnSize <= 0.34) {\n    sizesValue = '(max-width: 640px) 100vw, (max-width: 768px) 80vw, 33vw';\n  } else if (columnSize <= 0.5) {\n    sizesValue = '(max-width: 640px) 100vw, (max-width: 768px) 80vw, 50vw';\n  } else if (columnSize <= 0.67) {\n    sizesValue = '(max-width: 640px) 100vw, (max-width: 768px) 80vw, 67vw';\n  } else if (columnSize <= 0.75) {\n    sizesValue = '(max-width: 640px) 100vw, (max-width: 768px) 80vw, 75vw';\n  } else {\n    sizesValue = '(max-width: 640px) 100vw, 100vw';\n  }\n\n  const responsiveSizes = sizesValue;\n\n  const imageElement = (\n    <ResponsiveImage\n      src={file.url}\n      alt={caption || 'Image'}\n      width={imageWidth}\n      height={imageHeight}\n      sizes={responsiveSizes}\n      style={{ ...imageStyles }}\n    />\n  );\n\n  return (\n    <div className=\"editor-image-container\">\n      {link ? (\n        <a href={link} target=\"_blank\" rel=\"noopener noreferrer\">\n          {imageElement}\n        </a>\n      ) : (\n        imageElement\n      )}\n      {caption && (\n        <p style={{ textAlign: 'center', marginTop: '10px' }}>{caption}</p>\n      )}\n    </div>\n  );\n};\n\nconst RawHtml: React.FC<{ data: { html: string } }> = ({ data }) => {\n  return <div dangerouslySetInnerHTML={{ __html: data.html }} />;\n};\n\nconst RenderEditorJS: React.FC<{\n  blocks: Array<{ type: string; data: any }>;\n  columnSize: number; // Renamed from 'size' to 'columnSize' for clarity\n}> = ({ blocks, columnSize }) => {\n  return (\n    <div className=\"prose prose-base max-w-none text-base\">\n      {blocks.map((block, index) => {\n        switch (block.type) {\n          case 'paragraph':\n            return <Paragraph key={index} data={block.data} />;\n          case 'header':\n            return <Header key={index} data={block.data} />;\n          case 'list':\n            return <List key={index} data={block.data} />;\n          case 'image':\n            return (\n              <Image key={index} data={block.data} columnSize={columnSize} />\n            );\n          case 'quote':\n            return <Quote key={index} data={block.data} />;\n          case 'raw':\n            return <RawHtml key={index} data={block.data} />;\n          default:\n            return null;\n        }\n      })}\n    </div>\n  );\n};\n\ninterface EditorProps {\n  rows: Row[];\n}\n\nexport function Editor({ rows }: EditorProps) {\n  return (\n    <div className=\"editor__html space-y-6\">\n      {rows.map((row, index) => {\n        const rowClasses = getRowClasses(row.size);\n        return (\n          <div\n            className={`row__container grid ${rowClasses} grid-cols-1 gap-5`}\n            key={index}\n          >\n            {row.columns.map((column, index) => {\n              const columnClasses = getColumnClasses(column.size);\n              return (\n                <div\n                  className={`column__container ${columnClasses} col-span-1`}\n                  key={index}\n                >\n                  {column.data?.blocks && (\n                    <RenderEditorJS\n                      blocks={column.data?.blocks}\n                      columnSize={column.size / row.size}\n                    />\n                  )}\n                </div>\n              );\n            })}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/ExtendableTable.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport interface TableColumn<T = any> {\n  key: string;\n  header: {\n    label: React.ReactNode;\n    className?: string;\n  };\n  sortable?: boolean;\n  width?: string;\n  className?: string;\n  isRemoved?: boolean;\n  render?: (row: T, rowIndex?: number, loading?: boolean) => React.ReactNode;\n}\n\nexport interface TableContextValue<T = any> {\n  columns: TableColumn<T>[];\n  setColumns: React.Dispatch<React.SetStateAction<TableColumn<T>[]>>;\n  tableData: T[];\n  currentSort?: { key: string; direction: 'asc' | 'desc' };\n  addColumnBefore: (newColumn: TableColumn<T>, beforeColumnKey: string) => void;\n  addColumnAfter: (newColumn: TableColumn<T>, afterColumnKey: string) => void;\n  removeColumn: (key: string) => void;\n  tableName: string;\n}\n\ninterface TableProviderProps<T = any> {\n  children: React.ReactNode;\n  name: string;\n  initialColumns: TableColumn<T>[];\n  tableData: T[];\n  onSort?: (key: string, direction: 'asc' | 'desc') => void;\n  currentSort?: { key: string; direction: 'asc' | 'desc' };\n}\n\nconst TableContext = React.createContext<TableContextValue | null>(null);\n\nexport function useTableContext<T = any>(): TableContextValue<T> {\n  const context = React.useContext(TableContext);\n  if (!context) {\n    throw new Error('useTableContext must be used within a TableProvider');\n  }\n  return context as TableContextValue<T>;\n}\n\nexport function TableProvider<T = any>({\n  children,\n  name,\n  initialColumns,\n  tableData,\n  onSort,\n  currentSort\n}: TableProviderProps<T>) {\n  const [columns, setColumns] =\n    React.useState<TableColumn<T>[]>(initialColumns);\n\n  // Update columns when props change\n  React.useEffect(() => {\n    setColumns(initialColumns.map((col) => ({ ...col })));\n  }, [initialColumns]);\n\n  const addColumnBefore = React.useCallback(\n    (newColumn: TableColumn<T>, beforeColumnKey: string) => {\n      setColumns((cols) => {\n        // Find index of the column to insert before\n        const index = cols.findIndex((col) => col.key === beforeColumnKey);\n        // If found, insert before it (index), else add to the start\n        const position = index !== -1 ? index : 0;\n        return [...cols.slice(0, position), newColumn, ...cols.slice(position)];\n      });\n    },\n    []\n  );\n\n  const addColumnAfter = React.useCallback(\n    (newColumn: TableColumn<T>, afterColumnKey: string) => {\n      setColumns((cols) => {\n        // Find index of the column to insert after\n        const index = cols.findIndex((col) => col.key === afterColumnKey);\n        // If found, insert after it (index + 1), else add to the end\n        const position = index !== -1 ? index + 1 : cols.length;\n        return [...cols.slice(0, position), newColumn, ...cols.slice(position)];\n      });\n    },\n    []\n  );\n\n  const removeColumn = React.useCallback((key: string) => {\n    setColumns((cols) =>\n      cols.map((col) => (col.key === key ? { ...col, isRemoved: true } : col))\n    );\n  }, []);\n\n  const contextValue: TableContextValue<T> = {\n    columns,\n    setColumns,\n    tableData,\n    currentSort,\n    addColumnBefore,\n    addColumnAfter,\n    removeColumn,\n    tableName: name\n  };\n\n  return (\n    <TableContext.Provider value={contextValue as TableContextValue}>\n      {children}\n    </TableContext.Provider>\n  );\n}\n\ninterface ExtendableTableProps<T = any> {\n  name: string;\n  columns: TableColumn<T>[];\n  initialData: T[];\n  loading?: boolean;\n  noHeader?: boolean;\n  emptyMessage?: string;\n  onSort?: (key: string, direction: 'asc' | 'desc') => void;\n  currentSort?: { key: string; direction: 'asc' | 'desc' };\n  className?: string;\n}\n\nexport function ExtendableTable<T = any>({\n  name,\n  columns,\n  initialData,\n  loading = false,\n  noHeader = false,\n  emptyMessage = _('No data available'),\n  onSort,\n  currentSort,\n  className = ''\n}: ExtendableTableProps<T>) {\n  const handleSort = (key: string) => {\n    if (!onSort) return;\n\n    const direction =\n      currentSort?.key === key && currentSort.direction === 'asc'\n        ? 'desc'\n        : 'asc';\n    onSort(key, direction);\n  };\n\n  return (\n    <TableProvider\n      name={name}\n      initialColumns={columns}\n      tableData={initialData}\n      onSort={onSort}\n      currentSort={currentSort}\n    >\n      <Area id={name} />\n      <TableContent\n        loading={loading}\n        noHeader={noHeader}\n        onSort={onSort}\n        currentSort={currentSort}\n        emptyMessage={emptyMessage}\n        className={className}\n      />\n    </TableProvider>\n  );\n}\n\n// Separate component to use the context\nfunction TableContent<T = any>({\n  loading = false,\n  noHeader = false,\n  onSort,\n  currentSort,\n  emptyMessage,\n  className\n}: {\n  loading?: boolean;\n  noHeader?: boolean;\n  onSort?: (key: string, direction: 'asc' | 'desc') => void;\n  currentSort?: { key: string; direction: 'asc' | 'desc' };\n  emptyMessage: string;\n  className: string;\n}) {\n  const { columns, tableData } = useTableContext<T>();\n\n  const handleSort = (key: string) => {\n    if (!onSort) return;\n\n    const direction =\n      currentSort?.key === key && currentSort.direction === 'asc'\n        ? 'desc'\n        : 'asc';\n    onSort(key, direction);\n  };\n\n  return (\n    <>\n      <Table className={className}>\n        {!noHeader && (\n          <TableHeader>\n            <TableRow>\n              {columns\n                .filter((col) => !col.isRemoved)\n                .map((col) => (\n                  <TableHead\n                    key={col.key}\n                    className={`${col.header.className} ${\n                      col.sortable ? 'cursor-pointer' : ''\n                    }`}\n                    onClick={() => col.sortable && handleSort(col.key)}\n                    style={{ width: col.width }}\n                  >\n                    <span>{col.header.label}</span>\n                    {col.sortable && currentSort?.key === col.key && (\n                      <span className=\"text-blue-500\">\n                        {currentSort.direction === 'asc' ? '↑' : '↓'}\n                      </span>\n                    )}\n                  </TableHead>\n                ))}\n            </TableRow>\n          </TableHeader>\n        )}\n        <TableBody>\n          {tableData.length === 0 ? (\n            <TableRow>\n              <TableCell\n                colSpan={columns.filter((col) => !col.isRemoved).length}\n              >\n                {emptyMessage}\n              </TableCell>\n            </TableRow>\n          ) : (\n            tableData.map((row, rowIndex) => (\n              <TableRow key={rowIndex}>\n                {columns\n                  .filter((col) => !col.isRemoved)\n                  .map((col) => (\n                    <TableCell\n                      key={col.key}\n                      className={col.className}\n                      style={{ width: col.width }}\n                    >\n                      {col.render\n                        ? col.render(row, rowIndex, loading)\n                        : row[col.key]}\n                    </TableCell>\n                  ))}\n              </TableRow>\n            ))\n          )}\n        </TableBody>\n      </Table>\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/Image.tsx",
    "content": "import { parseImageSizes } from '@evershop/evershop/lib/util/parseImageSizes';\nimport React from 'react';\n\nexport type ImageProps = {\n  src: string;\n  width: number; // Intrinsic width of the image\n  height: number; // Intrinsic height of the image\n  alt: string;\n  quality?: number;\n  priority?: boolean;\n  sizes?: string;\n  loading?: 'eager' | 'lazy' | undefined;\n  decoding?: 'async' | 'auto' | 'sync' | undefined;\n  objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down' | 'unset';\n  style?: React.CSSProperties;\n} & React.ImgHTMLAttributes<HTMLImageElement>;\n\nexport function Image({\n  src,\n  width,\n  height,\n  alt,\n  quality = 75,\n  loading = 'eager',\n  decoding = 'async',\n  priority = false,\n  sizes = '100vw',\n  objectFit = 'unset',\n  ...props\n}: ImageProps): React.ReactElement | null {\n  const generateSrcSet = (): string => {\n    const imageSizes = parseImageSizes(sizes);\n    // Don't upscale beyond 3 times the original width, but be smarter about filtering\n    let filteredSizes = imageSizes.filter((size) => size <= width * 3);\n\n    if (filteredSizes.length < 2) {\n      // Add the original width\n      filteredSizes.push(width);\n\n      const smallerSizes = [\n        Math.round(width * 0.5), // 50% of original\n        Math.round(width * 0.75) // 75% of original\n      ].filter((size) => size >= 200 && !filteredSizes.includes(size)); // Don't go too small\n\n      filteredSizes = [...filteredSizes, ...smallerSizes];\n    }\n\n    if (!filteredSizes.includes(width)) {\n      filteredSizes.push(width);\n    }\n\n    filteredSizes = [...new Set(filteredSizes)].sort((a, b) => a - b);\n\n    return filteredSizes\n      .map((size) => {\n        // Construct the URL pointing to our image API\n        const url = `/images?src=${encodeURIComponent(\n          src\n        )}&w=${size}&q=${quality}`;\n        return `${url} ${size}w`;\n      })\n      .join(', ');\n  };\n\n  const srcset = generateSrcSet();\n  const fallbackSrc = `/images?src=${encodeURIComponent(\n    src\n  )}&w=${width}&q=${quality}`;\n\n  // Prepare the base style with responsive behavior\n  const baseStyle = {\n    // Modern responsive image approach\n    maxWidth: '100%', // Ensure image doesn't exceed its container\n    height: 'auto', // Maintain aspect ratio\n    objectFit: objectFit,\n    aspectRatio: `${width} / ${height}` // Maintain aspect ratio\n  };\n\n  return (\n    <img\n      {...props}\n      src={fallbackSrc}\n      srcSet={srcset}\n      sizes={sizes}\n      alt={alt}\n      // Set intrinsic dimensions to help browser calculate aspect ratio\n      width={width}\n      height={height}\n      style={{\n        ...baseStyle,\n        ...props.style\n      }}\n      loading={loading}\n      decoding={decoding}\n      itemProp={priority ? 'preload' : undefined}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/Link.tsx",
    "content": "/* eslint-disable no-console */\nimport React from 'react';\n\n/**\n * Valid values for the crossorigin attribute based on MDN specification\n */\nexport type CrossOrigin = 'anonymous' | 'use-credentials';\n\n/**\n * Valid values for the fetchpriority attribute\n */\nexport type FetchPriority = 'high' | 'low' | 'auto';\n\n/**\n * Valid values for the referrerpolicy attribute\n */\nexport type ReferrerPolicy =\n  | 'no-referrer'\n  | 'no-referrer-when-downgrade'\n  | 'origin'\n  | 'origin-when-cross-origin'\n  | 'same-origin'\n  | 'strict-origin'\n  | 'strict-origin-when-cross-origin'\n  | 'unsafe-url';\n\n/**\n * Valid values for the as attribute when used with preload/modulepreload\n */\nexport type AsType =\n  | 'audio'\n  | 'document'\n  | 'embed'\n  | 'fetch'\n  | 'font'\n  | 'image'\n  | 'object'\n  | 'script'\n  | 'style'\n  | 'track'\n  | 'video'\n  | 'worker';\n\n/**\n * Valid values for the blocking attribute\n */\nexport type BlockingType = 'render';\n\n/**\n * Complete props interface for the Link component\n */\nexport interface LinkProps\n  extends Omit<\n    React.LinkHTMLAttributes<HTMLLinkElement>,\n    'as' | 'crossOrigin' | 'fetchPriority' | 'referrerPolicy'\n  > {\n  href: string;\n  rel: string;\n  as?: AsType;\n  blocking?: BlockingType;\n  crossOrigin?: CrossOrigin;\n  disabled?: boolean;\n  fetchPriority?: FetchPriority;\n  hrefLang?: string;\n  imageSizes?: string;\n  imageSrcSet?: string;\n  integrity?: string;\n  media?: string;\n  referrerPolicy?: ReferrerPolicy;\n  sizes?: string;\n  title?: string;\n  type?: string;\n}\n\n/**\n * Validates that required props are present based on rel value\n */\nconst validateProps = (props: LinkProps): void => {\n  if (process.env.NODE_ENV === 'development') {\n    const { rel, as: asType, crossOrigin, href } = props;\n\n    // Check for 'as' attribute requirement with preload\n    if (rel === 'preload' && !asType) {\n      console.warn('Link: The \"as\" attribute is required when rel=\"preload\"');\n    }\n\n    // Check for crossOrigin requirement with certain as values\n    if (asType && ['fetch', 'font'].includes(asType) && !crossOrigin) {\n      console.warn(\n        `Link: The \"crossOrigin\" attribute is required when as=\"${asType}\"`\n      );\n    }\n\n    // Validate href is a proper URL format\n    if (href && !href.match(/^(https?:\\/\\/|\\/|\\.\\/|\\.\\.\\/|\\w+:)/)) {\n      console.warn(\n        `Link: Invalid href format \"${href}\". Expected a valid URL or path.`\n      );\n    }\n  }\n};\n\n/**\n * Link component that renders an HTML <link> element with comprehensive HTML5 support.\n *\n * This component supports all standard HTML5 link element attributes including:\n * - Resource linking (stylesheets, icons, etc.)\n * - Preloading resources with rel=\"preload\"\n * - Module preloading with rel=\"modulepreload\"\n * - CORS handling with crossOrigin\n * - Security features like Subresource Integrity\n * - Performance hints with fetchPriority\n * - Media queries for conditional loading\n * - All modern web standards compliance\n *\n * @example\n * // Basic stylesheet\n * <Link rel=\"stylesheet\" href=\"/styles.css\" />\n *\n * @example\n * // Preload font with CORS\n * <Link\n *   rel=\"preload\"\n *   as=\"font\"\n *   href=\"/fonts/main.woff2\"\n *   type=\"font/woff2\"\n *   crossOrigin=\"anonymous\"\n * />\n *\n * @example\n * // Responsive stylesheet with media query\n * <Link\n *   rel=\"stylesheet\"\n *   href=\"/mobile.css\"\n *   media=\"screen and (max-width: 600px)\"\n * />\n *\n * @example\n * // Icon with sizes\n * <Link\n *   rel=\"apple-touch-icon\"\n *   sizes=\"180x180\"\n *   href=\"/apple-touch-icon.png\"\n * />\n */\nexport function Link(props: LinkProps): React.ReactElement {\n  const {\n    href,\n    rel,\n    as: asType,\n    blocking,\n    crossOrigin,\n    disabled,\n    fetchPriority,\n    hrefLang,\n    imageSizes,\n    imageSrcSet,\n    integrity,\n    media,\n    referrerPolicy,\n    sizes,\n    title,\n    type,\n    ...otherProps\n  } = props;\n\n  // Validate props in development\n  validateProps(props);\n\n  // Build props object with only defined attributes\n  const linkProps: Record<string, any> = {\n    href,\n    rel,\n    ...otherProps\n  };\n\n  // Add optional attributes only if they are defined\n  if (asType !== undefined) linkProps.as = asType;\n  if (blocking !== undefined) linkProps.blocking = blocking;\n  if (crossOrigin !== undefined) linkProps.crossOrigin = crossOrigin;\n  if (disabled !== undefined) linkProps.disabled = disabled;\n  if (fetchPriority !== undefined) linkProps.fetchPriority = fetchPriority;\n  if (hrefLang !== undefined) linkProps.hrefLang = hrefLang;\n  if (imageSizes !== undefined) linkProps.imageSizes = imageSizes;\n  if (imageSrcSet !== undefined) linkProps.imageSrcSet = imageSrcSet;\n  if (integrity !== undefined) linkProps.integrity = integrity;\n  if (media !== undefined) linkProps.media = media;\n  if (referrerPolicy !== undefined) linkProps.referrerPolicy = referrerPolicy;\n  if (sizes !== undefined) linkProps.sizes = sizes;\n  if (title !== undefined) linkProps.title = title;\n  if (type !== undefined) linkProps.type = type;\n\n  return <link {...linkProps} />;\n}\n\n/**\n * Convenience component for stylesheet links\n */\nexport function Stylesheet({\n  href,\n  media,\n  title,\n  disabled,\n  integrity,\n  crossOrigin,\n  fetchPriority,\n  ...props\n}: Omit<LinkProps, 'rel'> & {\n  media?: string;\n  title?: string;\n  disabled?: boolean;\n}): React.ReactElement {\n  return (\n    <Link\n      rel=\"stylesheet\"\n      href={href}\n      media={media}\n      title={title}\n      disabled={disabled}\n      integrity={integrity}\n      crossOrigin={crossOrigin}\n      fetchPriority={fetchPriority}\n      {...props}\n    />\n  );\n}\n\n/**\n * Convenience component for favicon links\n */\nexport function Favicon({\n  href,\n  sizes,\n  type = 'image/x-icon',\n  ...props\n}: Omit<LinkProps, 'rel'> & {\n  sizes?: string;\n  type?: string;\n}): React.ReactElement {\n  return <Link rel=\"icon\" href={href} sizes={sizes} type={type} {...props} />;\n}\n\n/**\n * Convenience component for Apple Touch Icon links\n */\nexport function AppleTouchIcon({\n  href,\n  sizes,\n  type = 'image/png',\n  ...props\n}: Omit<LinkProps, 'rel'> & {\n  sizes?: string;\n  type?: string;\n}): React.ReactElement {\n  return (\n    <Link\n      rel=\"apple-touch-icon\"\n      href={href}\n      sizes={sizes}\n      type={type}\n      {...props}\n    />\n  );\n}\n\n/**\n * Convenience component for resource preloading\n */\nexport function Preload({\n  href,\n  as,\n  type,\n  crossOrigin,\n  integrity,\n  fetchPriority,\n  media,\n  imageSizes,\n  imageSrcSet,\n  ...props\n}: Omit<LinkProps, 'rel'> & {\n  as: AsType;\n  type?: string;\n  crossOrigin?: CrossOrigin;\n  integrity?: string;\n  fetchPriority?: FetchPriority;\n  media?: string;\n  imageSizes?: string;\n  imageSrcSet?: string;\n}): React.ReactElement {\n  return (\n    <Link\n      rel=\"preload\"\n      href={href}\n      as={as}\n      type={type}\n      crossOrigin={crossOrigin}\n      integrity={integrity}\n      fetchPriority={fetchPriority}\n      media={media}\n      imageSizes={imageSizes}\n      imageSrcSet={imageSrcSet}\n      {...props}\n    />\n  );\n}\n\n/**\n * Convenience component for module preloading\n */\nexport function ModulePreload({\n  href,\n  as,\n  integrity,\n  fetchPriority,\n  crossOrigin,\n  ...props\n}: Omit<LinkProps, 'rel'> & {\n  as?: AsType;\n  integrity?: string;\n  fetchPriority?: FetchPriority;\n  crossOrigin?: CrossOrigin;\n}): React.ReactElement {\n  return (\n    <Link\n      rel=\"modulepreload\"\n      href={href}\n      as={as}\n      integrity={integrity}\n      fetchPriority={fetchPriority}\n      crossOrigin={crossOrigin}\n      {...props}\n    />\n  );\n}\n\n/**\n * Convenience component for DNS prefetch\n */\nexport function DNSPrefetch({\n  href,\n  ...props\n}: Omit<LinkProps, 'rel'>): React.ReactElement {\n  return <Link rel=\"dns-prefetch\" href={href} {...props} />;\n}\n\n/**\n * Convenience component for preconnect\n */\nexport function Preconnect({\n  href,\n  crossOrigin,\n  ...props\n}: Omit<LinkProps, 'rel'> & { crossOrigin?: CrossOrigin }): React.ReactElement {\n  return (\n    <Link rel=\"preconnect\" href={href} crossOrigin={crossOrigin} {...props} />\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/LoadingBar.scss",
    "content": ".loading-bar {\n  background: #058c8c;\n  height: 4px;\n  display: block;\n  position: fixed;\n  top: 0;\n  left: 0;\n  -webkit-transition: width 1.5s;\n  -moz-transition: width 1.5s;\n  -o-transition: width 1.5s;\n  transition: width 1.5s;\n  width: 0%;\n  z-index: 1001;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/LoadingBar.tsx",
    "content": "import { useAppState } from '@components/common/context/app.js';\nimport React from 'react';\nimport './LoadingBar.scss';\n\nconst LoadingBar = function LoadingBar() {\n  const { fetching } = useAppState();\n  const [width, setWidth] = React.useState(0);\n  const widthRef = React.useRef(0);\n\n  React.useEffect(() => {\n    widthRef.current = width;\n    if (fetching === true) {\n      // Random number between 1 and 3\n      const step = Math.random() * (3 - 1) + 1;\n      // Random number between 85 and 95\n      const peak = Math.random() * (95 - 85) + 85;\n      if (widthRef.current < peak) {\n        const timer = setTimeout(() => setWidth(widthRef.current + step), 0);\n        return () => clearTimeout(timer);\n      }\n    } else if (widthRef.current === 100) {\n      setWidth(0);\n      widthRef.current = 0;\n    } else if (widthRef.current !== 0) {\n      setWidth(100);\n    }\n  });\n\n  return (\n    <div\n      className=\"loading-bar\"\n      style={{\n        width: `${width}%`,\n        display: fetching === true ? 'block' : 'none'\n      }}\n    />\n  );\n};\n\nexport { LoadingBar };\n"
  },
  {
    "path": "packages/evershop/src/components/common/Meta.tsx",
    "content": "/* eslint-disable no-console */\nimport React from 'react';\n\ninterface BaseMetaProps {\n  charset?: string;\n  content?: string;\n  httpEquiv?:\n    | 'content-type'\n    | 'default-style'\n    | 'refresh'\n    | 'x-ua-compatible'\n    | 'content-security-policy';\n  lang?: string;\n  scheme?: string;\n  media?: string;\n}\n\ninterface NameMetaProps extends BaseMetaProps {\n  name:\n    | 'description'\n    | 'keywords'\n    | 'author'\n    | 'viewport'\n    | 'robots'\n    | 'generator'\n    | 'theme-color'\n    | 'application-name'\n    | 'color-scheme'\n    | 'referrer'\n    | string;\n  property?: never;\n  itemProp?: never;\n}\n\ninterface PropertyMetaProps extends BaseMetaProps {\n  property: string;\n  name?: never;\n  itemProp?: never;\n}\n\ninterface ItemPropMetaProps extends BaseMetaProps {\n  itemProp: string;\n  itemType?: string;\n  itemId?: string;\n  name?: never;\n  property?: never;\n  httpEquiv?: never;\n}\n\ninterface HttpEquivMetaProps extends BaseMetaProps {\n  httpEquiv:\n    | 'content-type'\n    | 'default-style'\n    | 'refresh'\n    | 'x-ua-compatible'\n    | 'content-security-policy';\n  content: string;\n  name?: never;\n  property?: never;\n  itemProp?: never;\n}\n\ninterface CharsetOnlyProps {\n  charset: 'utf-8' | string;\n  content?: never;\n  name?: never;\n  property?: never;\n  itemProp?: never;\n  httpEquiv?: never;\n  lang?: never;\n  scheme?: never;\n  media?: never;\n}\n\ntype MetaProps =\n  | NameMetaProps\n  | PropertyMetaProps\n  | ItemPropMetaProps\n  | HttpEquivMetaProps\n  | CharsetOnlyProps;\n\nconst VALID_HTTP_EQUIV = [\n  'content-type',\n  'default-style',\n  'refresh',\n  'x-ua-compatible',\n  'content-security-policy'\n] as const;\n\nconst REQUIRED_CONTENT_ATTRIBUTES = [\n  'name',\n  'property',\n  'itemProp',\n  'httpEquiv'\n] as const;\n\nfunction validateMetaProps(props: any): { isValid: boolean; errors: string[] } {\n  const errors: string[] = [];\n\n  const hasIdentifier = [\n    'name',\n    'property',\n    'itemProp',\n    'httpEquiv',\n    'charset'\n  ].some((attr) => props[attr] !== undefined);\n\n  if (!hasIdentifier) {\n    errors.push(\n      'Meta tag must have at least one identifier attribute (name, property, itemProp, httpEquiv, or charset)'\n    );\n  }\n\n  if (props.charset && props.charset.toLowerCase() !== 'utf-8') {\n    errors.push('charset attribute must be \"utf-8\" for HTML5 documents');\n  }\n\n  if (props.itemProp && (props.name || props.httpEquiv || props.charset)) {\n    errors.push(\n      'itemProp attribute cannot be used with name, http-equiv, or charset attributes'\n    );\n  }\n\n  const needsContent = REQUIRED_CONTENT_ATTRIBUTES.some(\n    (attr) => props[attr] !== undefined\n  );\n  if (needsContent && !props.content) {\n    errors.push(\n      'Meta tag with name, property, itemProp, or httpEquiv must have content attribute'\n    );\n  }\n\n  if (props.media && props.name !== 'theme-color') {\n    errors.push('media attribute is only valid when name=\"theme-color\"');\n  }\n\n  if (props.httpEquiv && !VALID_HTTP_EQUIV.includes(props.httpEquiv)) {\n    errors.push(\n      `Invalid httpEquiv value: ${\n        props.httpEquiv\n      }. Valid values: ${VALID_HTTP_EQUIV.join(', ')}`\n    );\n  }\n\n  const identifierCount = ['name', 'property', 'itemProp'].filter(\n    (attr) => props[attr] !== undefined\n  ).length;\n  if (identifierCount > 1) {\n    errors.push(\n      'Meta tag cannot have multiple identifier attributes (name, property, itemProp)'\n    );\n  }\n\n  if (props.itemProp) {\n    if (props.itemType && !props.itemType.startsWith('http')) {\n      errors.push('itemType should be a valid URL (typically schema.org URL)');\n    }\n  }\n\n  return {\n    isValid: errors.length === 0,\n    errors\n  };\n}\n\nfunction sanitizeMetaProps(props: any): Record<string, string> {\n  const allowedAttributes = [\n    'charset',\n    'name',\n    'content',\n    'httpEquiv',\n    'property',\n    'itemProp',\n    'itemType',\n    'itemId',\n    'lang',\n    'scheme',\n    'media'\n  ];\n\n  return Object.keys(props)\n    .filter(\n      (key) =>\n        allowedAttributes.includes(key) &&\n        props[key] !== undefined &&\n        props[key] !== null\n    )\n    .reduce((obj, key) => {\n      obj[key] = String(props[key]).trim();\n      return obj;\n    }, {} as Record<string, string>);\n}\n\nexport function Meta(props: MetaProps) {\n  if (process.env.NODE_ENV === 'development') {\n    const validation = validateMetaProps(props);\n    if (!validation.isValid) {\n      console.error('Meta component validation errors:', validation.errors);\n      validation.errors.forEach((error) => console.error(`Meta: ${error}`));\n    }\n  }\n\n  const sanitizedProps = sanitizeMetaProps(props);\n\n  if (Object.keys(sanitizedProps).length === 0) {\n    if (process.env.NODE_ENV === 'development') {\n      console.warn('Meta component has no valid attributes, not rendering');\n    }\n    return null;\n  }\n\n  return <meta {...sanitizedProps} />;\n}\n\nexport function MetaCharset({ charset = 'utf-8' }: { charset?: string } = {}) {\n  return <Meta charset={charset} />;\n}\n\nexport function MetaDescription({ description }: { description: string }) {\n  return <Meta name=\"description\" content={description} />;\n}\n\nexport function MetaKeywords({ keywords }: { keywords: string | string[] }) {\n  const keywordString = Array.isArray(keywords)\n    ? keywords.join(', ')\n    : keywords;\n  return <Meta name=\"keywords\" content={keywordString} />;\n}\n\nexport function MetaAuthor({ author }: { author: string }) {\n  return <Meta name=\"author\" content={author} />;\n}\n\nexport function MetaThemeColor({\n  color,\n  media\n}: {\n  color: string;\n  media?: string;\n}) {\n  return <Meta name=\"theme-color\" content={color} media={media} />;\n}\n\nexport function MetaViewport({\n  width = 'device-width',\n  initialScale = 1,\n  maximumScale,\n  userScalable = true\n}: {\n  width?: string | number;\n  initialScale?: number;\n  maximumScale?: number;\n  userScalable?: boolean;\n}) {\n  const parts = [`width=${width}`, `initial-scale=${initialScale}`];\n\n  if (maximumScale !== undefined) {\n    parts.push(`maximum-scale=${maximumScale}`);\n  }\n\n  if (!userScalable) {\n    parts.push('user-scalable=no');\n  }\n\n  return <Meta name=\"viewport\" content={parts.join(', ')} />;\n}\n\nexport function MetaHttpEquiv({\n  httpEquiv,\n  content\n}: {\n  httpEquiv:\n    | 'content-type'\n    | 'default-style'\n    | 'refresh'\n    | 'x-ua-compatible'\n    | 'content-security-policy';\n  content: string;\n}) {\n  return <Meta httpEquiv={httpEquiv} content={content} />;\n}\n\nexport function MetaOpenGraph({\n  type,\n  title,\n  description,\n  image,\n  url,\n  siteName\n}: {\n  type?: 'website' | 'article' | 'product' | string;\n  title?: string;\n  description?: string;\n  image?: string;\n  url?: string;\n  siteName?: string;\n}) {\n  return (\n    <>\n      {type && <Meta property=\"og:type\" content={type} />}\n      {title && <Meta property=\"og:title\" content={title} />}\n      {description && <Meta property=\"og:description\" content={description} />}\n      {image && <Meta property=\"og:image\" content={image} />}\n      {url && <Meta property=\"og:url\" content={url} />}\n      {siteName && <Meta property=\"og:site_name\" content={siteName} />}\n    </>\n  );\n}\n\nexport function MetaTwitterCard({\n  card = 'summary',\n  site,\n  creator,\n  title,\n  description,\n  image\n}: {\n  card?: 'summary' | 'summary_large_image' | 'app' | 'player';\n  site?: string;\n  creator?: string;\n  title?: string;\n  description?: string;\n  image?: string;\n}) {\n  return (\n    <>\n      <Meta name=\"twitter:card\" content={card} />\n      {site && <Meta name=\"twitter:site\" content={site} />}\n      {creator && <Meta name=\"twitter:creator\" content={creator} />}\n      {title && <Meta name=\"twitter:title\" content={title} />}\n      {description && <Meta name=\"twitter:description\" content={description} />}\n      {image && <Meta name=\"twitter:image\" content={image} />}\n    </>\n  );\n}\n\nexport function MetaRobots({\n  index = true,\n  follow = true,\n  noarchive = false,\n  nosnippet = false\n}: {\n  index?: boolean;\n  follow?: boolean;\n  noarchive?: boolean;\n  nosnippet?: boolean;\n}) {\n  const directives = [\n    index ? 'index' : 'noindex',\n    follow ? 'follow' : 'nofollow'\n  ];\n\n  if (noarchive) directives.push('noarchive');\n  if (nosnippet) directives.push('nosnippet');\n\n  return <Meta name=\"robots\" content={directives.join(', ')} />;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/Notification.scss",
    "content": ".Toastify__toast-container {\n  z-index: 9999;\n  -webkit-transform: translate3d(0, 0, 9999px);\n  position: fixed;\n  padding: 4px;\n  width: 320px;\n  box-sizing: border-box;\n  color: #fff;\n  background-color: transparent;\n}\n.Toastify__toast-container--top-left {\n  top: 1em;\n  left: 1em;\n}\n.Toastify__toast-container--top-center {\n  top: 1em;\n  left: 50%;\n  transform: translateX(-50%);\n}\n.Toastify__toast-container--top-right {\n  top: 1em;\n  right: 1em;\n}\n.Toastify__toast-container--bottom-left {\n  bottom: 1em;\n  left: 1em;\n}\n.Toastify__toast-container--bottom-center {\n  bottom: 1em;\n  left: 50%;\n  transform: translateX(-50%);\n}\n.Toastify__toast-container--bottom-right {\n  bottom: 1em;\n  right: 1em;\n}\n\n@media only screen and (max-width: 480px) {\n  .Toastify__toast-container {\n    width: 100vw;\n    padding: 0;\n    left: 0;\n    margin: 0;\n  }\n  .Toastify__toast-container--top-left,\n  .Toastify__toast-container--top-center,\n  .Toastify__toast-container--top-right {\n    top: 0;\n    transform: translateX(0);\n  }\n  .Toastify__toast-container--bottom-left,\n  .Toastify__toast-container--bottom-center,\n  .Toastify__toast-container--bottom-right {\n    bottom: 0;\n    transform: translateX(0);\n  }\n  .Toastify__toast-container--rtl {\n    right: 0;\n    left: initial;\n  }\n}\n.Toastify__toast {\n  position: relative;\n  min-height: 64px;\n  box-sizing: border-box;\n  margin-bottom: 0.625rem;\n  padding: 8px;\n  border-radius: 5px;\n  display: -ms-flexbox;\n  display: flex;\n  -ms-flex-pack: justify;\n  justify-content: space-between;\n  max-height: 800px;\n  overflow: hidden;\n  font-family: sans-serif;\n  cursor: pointer;\n  direction: ltr;\n}\n.Toastify__toast--rtl {\n  direction: rtl;\n}\n.Toastify__toast--dark {\n  background: #121212;\n  color: #fff;\n}\n.Toastify__toast--default {\n  background: #fff;\n  color: #aaa;\n}\n.Toastify__toast--info {\n  background: #3498db;\n}\n.Toastify__toast--success {\n  background: var(--success);\n}\n.Toastify__toast--warning {\n  background: #f1c40f;\n}\n.Toastify__toast--error {\n  background: var(--critical);\n}\n.Toastify__toast-body {\n  margin: auto 0;\n  -ms-flex: 1 1 auto;\n  flex: 1 1 auto;\n}\n\n@media only screen and (max-width: 480px) {\n  .Toastify__toast {\n    margin-bottom: 0;\n  }\n}\n.Toastify__close-button {\n  color: #fff;\n  background: transparent;\n  outline: none;\n  border: none;\n  padding: 0;\n  cursor: pointer;\n  opacity: 0.7;\n  transition: 0.3s ease;\n  -ms-flex-item-align: start;\n  align-self: center;\n}\n.Toastify__close-button--default {\n  color: #000;\n  opacity: 0.3;\n}\n.Toastify__close-button > svg {\n  fill: currentColor;\n  height: 16px;\n  width: 14px;\n}\n.Toastify__close-button:hover,\n.Toastify__close-button:focus {\n  opacity: 1;\n}\n\n@keyframes Toastify__trackProgress {\n  0% {\n    transform: scaleX(1);\n  }\n  100% {\n    transform: scaleX(0);\n  }\n}\n.Toastify__progress-bar {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 5px;\n  z-index: 9999;\n  opacity: 0.7;\n  background-color: rgba(255, 255, 255, 0.7);\n  transform-origin: left;\n}\n.Toastify__progress-bar--animated {\n  animation: Toastify__trackProgress linear 1 forwards;\n}\n.Toastify__progress-bar--controlled {\n  transition: transform 0.2s;\n}\n.Toastify__progress-bar--rtl {\n  right: 0;\n  left: initial;\n  transform-origin: right;\n}\n.Toastify__progress-bar--default {\n  background: linear-gradient(\n    to right,\n    #4cd964,\n    #5ac8fa,\n    #007aff,\n    #34aadc,\n    #5856d6,\n    #ff2d55\n  );\n}\n.Toastify__progress-bar--dark {\n  background: #bb86fc;\n}\n@keyframes Toastify__bounceInRight {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  from {\n    opacity: 0;\n    transform: translate3d(3000px, 0, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(-25px, 0, 0);\n  }\n  75% {\n    transform: translate3d(10px, 0, 0);\n  }\n  90% {\n    transform: translate3d(-5px, 0, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n@keyframes Toastify__bounceOutRight {\n  20% {\n    opacity: 1;\n    transform: translate3d(-20px, 0, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(2000px, 0, 0);\n  }\n}\n@keyframes Toastify__bounceInLeft {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  0% {\n    opacity: 0;\n    transform: translate3d(-3000px, 0, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(25px, 0, 0);\n  }\n  75% {\n    transform: translate3d(-10px, 0, 0);\n  }\n  90% {\n    transform: translate3d(5px, 0, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n@keyframes Toastify__bounceOutLeft {\n  20% {\n    opacity: 1;\n    transform: translate3d(20px, 0, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(-2000px, 0, 0);\n  }\n}\n@keyframes Toastify__bounceInUp {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  from {\n    opacity: 0;\n    transform: translate3d(0, 3000px, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(0, -20px, 0);\n  }\n  75% {\n    transform: translate3d(0, 10px, 0);\n  }\n  90% {\n    transform: translate3d(0, -5px, 0);\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__bounceOutUp {\n  20% {\n    transform: translate3d(0, -10px, 0);\n  }\n  40%,\n  45% {\n    opacity: 1;\n    transform: translate3d(0, 20px, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(0, -2000px, 0);\n  }\n}\n@keyframes Toastify__bounceInDown {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  0% {\n    opacity: 0;\n    transform: translate3d(0, -3000px, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(0, 25px, 0);\n  }\n  75% {\n    transform: translate3d(0, -10px, 0);\n  }\n  90% {\n    transform: translate3d(0, 5px, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n@keyframes Toastify__bounceOutDown {\n  20% {\n    transform: translate3d(0, 10px, 0);\n  }\n  40%,\n  45% {\n    opacity: 1;\n    transform: translate3d(0, -20px, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(0, 2000px, 0);\n  }\n}\n.Toastify__bounce-enter--top-left,\n.Toastify__bounce-enter--bottom-left {\n  animation-name: Toastify__bounceInLeft;\n}\n.Toastify__bounce-enter--top-right,\n.Toastify__bounce-enter--bottom-right {\n  animation-name: Toastify__bounceInRight;\n}\n.Toastify__bounce-enter--top-center {\n  animation-name: Toastify__bounceInDown;\n}\n.Toastify__bounce-enter--bottom-center {\n  animation-name: Toastify__bounceInUp;\n}\n\n.Toastify__bounce-exit--top-left,\n.Toastify__bounce-exit--bottom-left {\n  animation-name: Toastify__bounceOutLeft;\n}\n.Toastify__bounce-exit--top-right,\n.Toastify__bounce-exit--bottom-right {\n  animation-name: Toastify__bounceOutRight;\n}\n.Toastify__bounce-exit--top-center {\n  animation-name: Toastify__bounceOutUp;\n}\n.Toastify__bounce-exit--bottom-center {\n  animation-name: Toastify__bounceOutDown;\n}\n\n@keyframes Toastify__zoomIn {\n  from {\n    opacity: 0;\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n  50% {\n    opacity: 1;\n  }\n}\n@keyframes Toastify__zoomOut {\n  from {\n    opacity: 1;\n  }\n  50% {\n    opacity: 0;\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n  to {\n    opacity: 0;\n  }\n}\n.Toastify__zoom-enter {\n  animation-name: Toastify__zoomIn;\n}\n\n.Toastify__zoom-exit {\n  animation-name: Toastify__zoomOut;\n}\n\n@keyframes Toastify__flipIn {\n  from {\n    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n    animation-timing-function: ease-in;\n    opacity: 0;\n  }\n  40% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n    animation-timing-function: ease-in;\n  }\n  60% {\n    transform: perspective(400px) rotate3d(1, 0, 0, 10deg);\n    opacity: 1;\n  }\n  80% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -5deg);\n  }\n  to {\n    transform: perspective(400px);\n  }\n}\n@keyframes Toastify__flipOut {\n  from {\n    transform: perspective(400px);\n  }\n  30% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n    opacity: 1;\n  }\n  to {\n    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n    opacity: 0;\n  }\n}\n.Toastify__flip-enter {\n  animation-name: Toastify__flipIn;\n}\n\n.Toastify__flip-exit {\n  animation-name: Toastify__flipOut;\n}\n\n@keyframes Toastify__slideInRight {\n  from {\n    transform: translate3d(110%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideInLeft {\n  from {\n    transform: translate3d(-110%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideInUp {\n  from {\n    transform: translate3d(0, 110%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideInDown {\n  from {\n    transform: translate3d(0, -110%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(110%, 0, 0);\n  }\n}\n@keyframes Toastify__slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-110%, 0, 0);\n  }\n}\n@keyframes Toastify__slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 500px, 0);\n  }\n}\n@keyframes Toastify__slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -500px, 0);\n  }\n}\n.Toastify__slide-enter--top-left,\n.Toastify__slide-enter--bottom-left {\n  animation-name: Toastify__slideInLeft;\n}\n.Toastify__slide-enter--top-right,\n.Toastify__slide-enter--bottom-right {\n  animation-name: Toastify__slideInRight;\n}\n.Toastify__slide-enter--top-center {\n  animation-name: Toastify__slideInDown;\n}\n.Toastify__slide-enter--bottom-center {\n  animation-name: Toastify__slideInUp;\n}\n\n.Toastify__slide-exit--top-left,\n.Toastify__slide-exit--bottom-left {\n  animation-name: Toastify__slideOutLeft;\n}\n.Toastify__slide-exit--top-right,\n.Toastify__slide-exit--bottom-right {\n  animation-name: Toastify__slideOutRight;\n}\n.Toastify__slide-exit--top-center {\n  animation-name: Toastify__slideOutUp;\n}\n.Toastify__slide-exit--bottom-center {\n  animation-name: Toastify__slideOutDown;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/Notification.tsx",
    "content": "import { useAppState } from '@components/common/context/app.js';\nimport { get } from '@evershop/evershop/lib/util/get';\nimport React from 'react';\nimport { toast, ToastContainer } from 'react-toastify';\nimport './Notification.scss';\n\nexport default function Notification() {\n  const notify = (type, message) => {\n    switch (type) {\n      case 'success':\n        toast.success(message);\n        break;\n      case 'error':\n        toast.error(message);\n        break;\n      case 'info':\n        toast.info(message);\n        break;\n      case 'warning':\n        toast.warning(message);\n        break;\n      default:\n        toast(message);\n    }\n  };\n  const context = useAppState();\n\n  React.useEffect(() => {\n    get(context, 'notifications', []).forEach((n) => notify(n.type, n.message));\n  }, []);\n\n  return (\n    <div>\n      <ToastContainer hideProgressBar autoClose={false} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/ProductNoThumbnail.tsx",
    "content": "import React from 'react';\n\nconst ProductNoThumbnail: React.FC<{\n  width?: number;\n  height?: number;\n  className?: string;\n}> = ({ width, height, className }) => {\n  return (\n    <svg\n      className={`max-w-full ${className}`}\n      width={width || 100}\n      height={height || 100}\n      viewBox=\"0 0 251 276\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M62.2402 34.2864L0.329313 68.5728L0.131725 137.524L0 206.538L62.3061 240.95C96.5546 259.858 124.81 275.363 125.139 275.363C125.468 275.363 142.527 266.035 163.142 254.69C183.691 243.282 211.748 227.841 225.448 220.277L250.278 206.538V191.789V176.978L248.829 177.735C247.973 178.176 219.915 193.617 186.457 212.147C152.933 230.677 125.205 245.677 124.81 245.614C124.349 245.488 102.219 233.387 75.5444 218.639L27.0037 191.853V137.65V83.447L48.9359 71.346C60.9229 64.7282 82.9211 52.6271 97.7401 44.4337C112.493 36.2402 124.876 29.5594 125.139 29.5594C125.402 29.5594 142.593 38.9504 163.339 50.4212L223.801 83.447L233.337 78.0776L250.278 68.5728L223.801 54.1397C202.857 42.2908 125.6 -0.0629802 124.941 4.62725e-05C124.546 4.62725e-05 96.2912 15.4415 62.2402 34.2864Z\"\n        fill=\"#BBBBBB\"\n      />\n      <path\n        d=\"M188.367 102.796C154.514 121.515 126.325 137.019 125.732 137.146C125.073 137.335 108.542 128.511 87.0045 116.662L49.397 95.8632V110.8L49.4628 125.675L86.0166 145.843C106.105 156.936 123.229 166.264 124.085 166.579C125.402 167.02 134.623 162.167 187.445 132.986C221.43 114.141 249.488 98.5734 249.817 98.3213C250.08 98.0691 250.212 91.3253 250.146 83.321L249.949 68.7618L188.367 102.796Z\"\n        fill=\"#BBBBBB\"\n      />\n      <path\n        d=\"M243.362 126.557C239.74 128.511 211.814 143.953 181.254 160.844C150.694 177.735 125.468 191.537 125.139 191.537C124.81 191.537 107.751 182.21 87.1363 170.865L49.7263 150.192L49.5288 164.688C49.397 175.781 49.5946 179.373 50.1874 179.941C51.4388 181.012 124.349 221.16 125.139 221.16C125.798 221.16 248.763 153.406 249.817 152.524C250.08 152.272 250.212 145.528 250.146 137.461L249.949 122.902L243.362 126.557Z\"\n        fill=\"#BBBBBB\"\n      />\n    </svg>\n  );\n};\n\nexport { ProductNoThumbnail };\n"
  },
  {
    "path": "packages/evershop/src/components/common/RenderIfTrue.tsx",
    "content": "import React from 'react';\n\ninterface RenderIfTrueProps {\n  condition: boolean;\n  children: React.ReactNode;\n}\n\nexport default function RenderIfTrue({\n  condition,\n  children\n}: RenderIfTrueProps) {\n  return condition === true ? children : null;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/Script.tsx",
    "content": "/* eslint-disable no-console */\nimport React from 'react';\n\ninterface BaseScriptProps {\n  src?: string;\n  async?: boolean;\n  defer?: boolean;\n  type?:\n    | 'text/javascript'\n    | 'module'\n    | 'importmap'\n    | 'speculationrules'\n    | 'application/json'\n    | 'application/ld+json'\n    | string;\n  crossOrigin?: 'anonymous' | 'use-credentials';\n  integrity?: string;\n  nonce?: string;\n  referrerPolicy?:\n    | 'no-referrer'\n    | 'no-referrer-when-downgrade'\n    | 'origin'\n    | 'origin-when-cross-origin'\n    | 'same-origin'\n    | 'strict-origin'\n    | 'strict-origin-when-cross-origin'\n    | 'unsafe-url';\n  noModule?: boolean;\n  fetchPriority?: 'high' | 'low' | 'auto';\n  blocking?: 'render';\n  attributionSrc?: boolean | string;\n  children?: React.ReactNode;\n}\n\ninterface ExternalScriptProps extends BaseScriptProps {\n  src: string;\n  children?: never;\n}\n\ninterface InlineScriptProps extends BaseScriptProps {\n  src?: never;\n  children: React.ReactNode;\n}\n\ntype ScriptProps = ExternalScriptProps | InlineScriptProps;\n\nconst VALID_REFERRER_POLICIES = [\n  'no-referrer',\n  'no-referrer-when-downgrade',\n  'origin',\n  'origin-when-cross-origin',\n  'same-origin',\n  'strict-origin',\n  'strict-origin-when-cross-origin',\n  'unsafe-url'\n] as const;\n\nconst VALID_CROSSORIGIN_VALUES = ['anonymous', 'use-credentials'] as const;\n\nfunction validateScriptProps(props: any): {\n  isValid: boolean;\n  errors: string[];\n} {\n  const errors: string[] = [];\n\n  if (!props.src && !props.children) {\n    errors.push('Script must have either src attribute or children content');\n  }\n\n  if (props.src && props.children) {\n    errors.push('Script cannot have both src attribute and children content');\n  }\n\n  if (props.async && props.defer) {\n    errors.push('Script cannot have both async and defer attributes');\n  }\n\n  if (!props.src && props.async) {\n    errors.push('async attribute has no effect on inline scripts');\n  }\n\n  if (!props.src && props.defer) {\n    errors.push('defer attribute has no effect on inline scripts');\n  }\n\n  if (\n    props.referrerPolicy &&\n    !VALID_REFERRER_POLICIES.includes(props.referrerPolicy)\n  ) {\n    errors.push(`Invalid referrerPolicy: ${props.referrerPolicy}`);\n  }\n\n  if (\n    props.crossOrigin &&\n    !VALID_CROSSORIGIN_VALUES.includes(props.crossOrigin)\n  ) {\n    errors.push(`Invalid crossOrigin: ${props.crossOrigin}`);\n  }\n\n  if (props.integrity && !props.src) {\n    errors.push('integrity attribute requires src attribute');\n  }\n\n  if (\n    props.fetchPriority &&\n    !['high', 'low', 'auto'].includes(props.fetchPriority)\n  ) {\n    errors.push(`Invalid fetchPriority: ${props.fetchPriority}`);\n  }\n\n  if (props.blocking && props.blocking !== 'render') {\n    errors.push('blocking attribute can only be \"render\"');\n  }\n\n  return {\n    isValid: errors.length === 0,\n    errors\n  };\n}\n\nfunction sanitizeScriptProps(props: any): Record<string, any> {\n  const allowedAttributes = [\n    'src',\n    'async',\n    'defer',\n    'type',\n    'crossOrigin',\n    'integrity',\n    'nonce',\n    'referrerPolicy',\n    'noModule',\n    'fetchPriority',\n    'blocking',\n    'attributionSrc'\n  ];\n\n  const sanitized = Object.keys(props)\n    .filter(\n      (key) =>\n        allowedAttributes.includes(key) &&\n        props[key] !== undefined &&\n        props[key] !== null\n    )\n    .reduce((obj, key) => {\n      if (typeof props[key] === 'boolean') {\n        if (props[key]) {\n          obj[key] =\n            key === 'attributionSrc' && props[key] === true ? '' : props[key];\n        }\n      } else {\n        obj[key] = String(props[key]).trim();\n      }\n      return obj;\n    }, {} as Record<string, any>);\n\n  return sanitized;\n}\n\nexport function Script(props: ScriptProps) {\n  if (process.env.NODE_ENV === 'development') {\n    const validation = validateScriptProps(props);\n    if (!validation.isValid) {\n      console.error('Script component validation errors:', validation.errors);\n      validation.errors.forEach((error) => console.error(`Script: ${error}`));\n    }\n  }\n\n  if (!props.src && !props.children) {\n    if (process.env.NODE_ENV === 'development') {\n      console.warn('Script component has no src or children, not rendering');\n    }\n    return null;\n  }\n\n  const sanitizedProps = sanitizeScriptProps(props);\n\n  if (props.src) {\n    return <script {...sanitizedProps} />;\n  }\n\n  return <script {...sanitizedProps}>{props.children}</script>;\n}\n\nexport function ScriptExternal({\n  src,\n  async = false,\n  defer = false,\n  crossOrigin,\n  integrity,\n  referrerPolicy,\n  fetchPriority = 'auto',\n  nonce\n}: {\n  src: string;\n  async?: boolean;\n  defer?: boolean;\n  crossOrigin?: 'anonymous' | 'use-credentials';\n  integrity?: string;\n  referrerPolicy?:\n    | 'no-referrer'\n    | 'no-referrer-when-downgrade'\n    | 'origin'\n    | 'origin-when-cross-origin'\n    | 'same-origin'\n    | 'strict-origin'\n    | 'strict-origin-when-cross-origin'\n    | 'unsafe-url';\n  fetchPriority?: 'high' | 'low' | 'auto';\n  nonce?: string;\n}) {\n  return (\n    <Script\n      src={src}\n      async={async}\n      defer={defer}\n      crossOrigin={crossOrigin}\n      integrity={integrity}\n      referrerPolicy={referrerPolicy}\n      fetchPriority={fetchPriority}\n      nonce={nonce}\n    />\n  );\n}\n\nexport function ScriptModule({\n  src,\n  children,\n  nonce,\n  crossOrigin,\n  integrity,\n  referrerPolicy\n}: {\n  src?: string;\n  children?: React.ReactNode;\n  nonce?: string;\n  crossOrigin?: 'anonymous' | 'use-credentials';\n  integrity?: string;\n  referrerPolicy?:\n    | 'no-referrer'\n    | 'no-referrer-when-downgrade'\n    | 'origin'\n    | 'origin-when-cross-origin'\n    | 'same-origin'\n    | 'strict-origin'\n    | 'strict-origin-when-cross-origin'\n    | 'unsafe-url';\n}) {\n  if (src && children) {\n    console.error('ScriptModule cannot have both src and children');\n    return null;\n  }\n\n  if (src) {\n    return (\n      <Script\n        src={src}\n        type=\"module\"\n        nonce={nonce}\n        crossOrigin={crossOrigin}\n        integrity={integrity}\n        referrerPolicy={referrerPolicy}\n      />\n    );\n  }\n\n  return (\n    <Script\n      type=\"module\"\n      nonce={nonce}\n      crossOrigin={crossOrigin}\n      integrity={integrity}\n      referrerPolicy={referrerPolicy}\n    >\n      {children}\n    </Script>\n  );\n}\n\nexport function ScriptInline({\n  children,\n  type = 'text/javascript',\n  nonce\n}: {\n  children: React.ReactNode;\n  type?: string;\n  nonce?: string;\n}) {\n  return (\n    <Script type={type} nonce={nonce}>\n      {children}\n    </Script>\n  );\n}\n\nexport function ScriptJSON({\n  id,\n  data,\n  nonce\n}: {\n  id?: string;\n  data: any;\n  nonce?: string;\n}) {\n  return (\n    <Script type=\"application/json\" nonce={nonce} {...(id && { id })}>\n      {JSON.stringify(data, null, 2)}\n    </Script>\n  );\n}\n\nexport function ScriptImportMap({\n  imports,\n  scopes,\n  nonce\n}: {\n  imports?: Record<string, string>;\n  scopes?: Record<string, Record<string, string>>;\n  nonce?: string;\n}) {\n  const importMap: any = {};\n  if (imports) importMap.imports = imports;\n  if (scopes) importMap.scopes = scopes;\n\n  return (\n    <Script type=\"importmap\" nonce={nonce}>\n      {JSON.stringify(importMap, null, 2)}\n    </Script>\n  );\n}\n\nexport function ScriptNoModule({\n  src,\n  children,\n  async = false,\n  defer = false\n}: {\n  src?: string;\n  children?: React.ReactNode;\n  async?: boolean;\n  defer?: boolean;\n}) {\n  if (src && children) {\n    console.error('ScriptNoModule cannot have both src and children');\n    return null;\n  }\n\n  if (src) {\n    return <Script src={src} noModule={true} async={async} defer={defer} />;\n  }\n\n  return (\n    <Script noModule={true} async={async} defer={defer}>\n      {children}\n    </Script>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/SimplePagination.tsx",
    "content": "import { ChevronLeft, ChevronRight } from 'lucide-react';\nimport React from 'react';\n\ninterface SimplePaginationProps {\n  total: number;\n  count: number;\n  page: number;\n  hasNext: boolean;\n  setPage: (page: number) => void;\n}\nexport function SimplePagination({\n  total,\n  count,\n  page,\n  hasNext,\n  setPage\n}: SimplePaginationProps) {\n  return (\n    <div className=\"simple__pagination flex gap-2 items-center\">\n      <div>\n        <span>\n          {count} of {total}\n        </span>\n      </div>\n      <div className=\"flex gap-2\">\n        {page > 1 && (\n          <a\n            className=\"hover:text-interactive border rounded p-1.25 border-divider\"\n            href=\"#\"\n            onClick={(e) => {\n              e.preventDefault();\n              setPage(page - 1);\n            }}\n          >\n            <ChevronLeft width={15} height={15} />\n          </a>\n        )}\n        {page === 1 && (\n          <span className=\"border rounded p-1.25 border-divider text-divider\">\n            <ChevronLeft width={15} height={15} />\n          </span>\n        )}\n        {hasNext && (\n          <a\n            className=\"hover:text-interactive border rounded p-1.25 border-divider\"\n            href=\"#\"\n            onClick={(e) => {\n              e.preventDefault();\n              setPage(page + 1);\n            }}\n          >\n            <ChevronRight width={15} height={15} />\n          </a>\n        )}\n        {!hasNext && (\n          <span className=\"border rounded p-1.25 border-divider text-divider\">\n            <ChevronRight width={15} height={15} />\n          </span>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/StaticImage.tsx",
    "content": "import { useAppState } from '@components/common/context/app.js';\nimport { Image, ImageProps } from '@components/common/Image.js';\nimport React, { useMemo } from 'react';\n\nexport interface StaticImageProps extends Omit<ImageProps, 'src'> {\n  subPath: string; // Path relative to the root public folder or the public folder of the active theme\n}\n\nexport const StaticImage: React.FC<StaticImageProps> = ({\n  subPath,\n  quality = 75,\n  ...props\n}) => {\n  const { config } = useAppState();\n  const baseUrl = config?.pageMeta?.baseUrl || '';\n\n  const imagePath = useMemo(() => {\n    const formattedSubPath = subPath.startsWith('/')\n      ? subPath.substring(1)\n      : subPath;\n\n    return `${baseUrl}/assets/${formattedSubPath}`;\n  }, [baseUrl, subPath]);\n\n  return <Image src={imagePath} quality={quality} {...props} />;\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/Title.tsx",
    "content": "/* eslint-disable no-console */\nimport React from 'react';\n\n/**\n * Props for the Title component\n */\nexport interface TitleProps\n  extends Omit<React.HTMLAttributes<HTMLTitleElement>, 'children'> {\n  /**\n   * The text content for the document title.\n   * Should be descriptive and unique for SEO purposes.\n   */\n  title: string;\n  /**\n   * Optional prefix to add to the title (e.g., site name).\n   * Will be separated from the main title with a separator.\n   */\n  prefix?: string;\n  /**\n   * Optional suffix to add to the title (e.g., site name).\n   * Will be separated from the main title with a separator.\n   */\n  suffix?: string;\n  /**\n   * Separator to use between title parts.\n   * @default \" - \"\n   */\n  separator?: string;\n  /**\n   * Maximum length for the title (SEO best practice: ~55-60 chars).\n   * If exceeded, will truncate and add ellipsis.\n   */\n  maxLength?: number;\n}\n\n/**\n * Validates title content for SEO and accessibility best practices\n */\nconst validateTitle = (title: string, maxLength?: number): void => {\n  if (process.env.NODE_ENV === 'development') {\n    // Check for empty title\n    if (!title || title.trim().length === 0) {\n      console.warn(\n        'Title: Empty title detected. This is bad for SEO and accessibility.'\n      );\n      return;\n    }\n\n    // Check for very short titles\n    if (title.length < 3) {\n      console.warn(\n        'Title: Very short title detected. Consider making it more descriptive.'\n      );\n    }\n\n    // Check for very long titles\n    const recommendedMaxLength = maxLength || 60;\n    if (title.length > recommendedMaxLength) {\n      console.warn(\n        `Title: Title exceeds recommended length of ${recommendedMaxLength} characters (${title.length}). May be truncated in search results.`\n      );\n    }\n\n    // Check for keyword stuffing patterns\n    const words = title.toLowerCase().split(/\\s+/);\n    const wordCount = words.reduce((acc, word) => {\n      acc[word] = (acc[word] || 0) + 1;\n      return acc;\n    }, {} as Record<string, number>);\n\n    const repeatedWords = Object.entries(wordCount).filter(\n      ([word, count]) => count > 2 && word.length > 3\n    );\n\n    if (repeatedWords.length > 0) {\n      console.warn(\n        'Title: Potential keyword stuffing detected. Repeated words:',\n        repeatedWords.map(([word]) => word).join(', ')\n      );\n    }\n\n    // Check for common bad patterns\n    if (title.includes('||') || title.includes('>>') || title.includes('<<')) {\n      console.warn(\n        'Title: Unusual separators detected. Consider using standard separators like \" - \" or \" | \".'\n      );\n    }\n\n    // Check if title starts/ends with separator characters\n    if (/^[-|•·]|\\s[-|•·]\\s*$/.test(title)) {\n      console.warn(\n        'Title: Title appears to start or end with separator characters.'\n      );\n    }\n  }\n};\n\n/**\n * Formats the complete title string with optional prefix/suffix\n */\nconst formatTitle = (\n  title: string,\n  prefix?: string,\n  suffix?: string,\n  separator: string = ' - ',\n  maxLength?: number\n): string => {\n  const parts: string[] = [];\n\n  if (prefix) parts.push(prefix);\n  parts.push(title);\n  if (suffix) parts.push(suffix);\n\n  let formattedTitle = parts.join(separator);\n\n  // Truncate if needed\n  if (maxLength && formattedTitle.length > maxLength) {\n    // Try to truncate at word boundaries\n    const truncated = formattedTitle.substring(0, maxLength - 3);\n    const lastSpace = truncated.lastIndexOf(' ');\n    if (lastSpace > formattedTitle.length * 0.7) {\n      formattedTitle = truncated.substring(0, lastSpace) + '...';\n    } else {\n      formattedTitle = truncated + '...';\n    }\n  }\n\n  return formattedTitle;\n};\n\n/**\n * Title component that renders an HTML <title> element with SEO and accessibility best practices.\n *\n * The title element is crucial for:\n * - SEO: Search engines use it as the clickable headline in search results\n * - Accessibility: Screen readers announce the page title when users navigate to the page\n * - User Experience: Displayed in browser tabs and bookmarks\n *\n * SEO Best Practices:\n * - Keep titles between 30-60 characters (55-60 optimal for Google)\n * - Make each page title unique within your site\n * - Put important keywords first\n * - Avoid keyword stuffing\n * - Use descriptive, readable titles that entice clicks\n *\n * @example\n * // Basic usage\n * <Title title=\"About Us\" />\n *\n * @example\n * // With site branding\n * <Title\n *   title=\"Product Details\"\n *   suffix=\"EverShop\"\n *   separator=\" | \"\n * />\n *\n * @example\n * // E-commerce product page\n * <Title\n *   title=\"iPhone 14 Pro Max - 256GB Space Black\"\n *   suffix=\"TechStore\"\n *   maxLength={60}\n * />\n *\n * @example\n * // Blog post\n * <Title\n *   title=\"10 Tips for Better React Performance\"\n *   suffix=\"Developer Blog\"\n * />\n *\n * @example\n * // Error page\n * <Title\n *   title=\"Page Not Found (404)\"\n *   suffix=\"EverShop\"\n * />\n */\nexport function Title({\n  title,\n  prefix,\n  suffix,\n  separator = ' - ',\n  maxLength,\n  ...otherProps\n}: TitleProps): React.ReactElement {\n  // Format the complete title\n  const formattedTitle = formatTitle(\n    title,\n    prefix,\n    suffix,\n    separator,\n    maxLength\n  );\n\n  // Validate in development\n  validateTitle(formattedTitle, maxLength);\n\n  return <title {...otherProps}>{formattedTitle}</title>;\n}\n\n/**\n * Convenience component for product page titles\n */\nexport function ProductTitle({\n  productName,\n  category,\n  brand,\n  siteName,\n  separator = ' - ',\n  maxLength = 60,\n  ...props\n}: Omit<TitleProps, 'title'> & {\n  productName: string;\n  category?: string;\n  brand?: string;\n  siteName?: string;\n}): React.ReactElement {\n  const titleParts: string[] = [productName];\n  if (category) titleParts.push(category);\n  if (brand) titleParts.push(brand);\n\n  const title = titleParts.join(' ');\n\n  return (\n    <Title\n      title={title}\n      suffix={siteName}\n      separator={separator}\n      maxLength={maxLength}\n      {...props}\n    />\n  );\n}\n\n/**\n * Convenience component for category/collection page titles\n */\nexport function CategoryTitle({\n  categoryName,\n  itemCount,\n  siteName,\n  separator = ' - ',\n  maxLength = 60,\n  ...props\n}: Omit<TitleProps, 'title'> & {\n  categoryName: string;\n  itemCount?: number;\n  siteName?: string;\n}): React.ReactElement {\n  let title = categoryName;\n  if (itemCount !== undefined) {\n    title += ` (${itemCount} items)`;\n  }\n\n  return (\n    <Title\n      title={title}\n      suffix={siteName}\n      separator={separator}\n      maxLength={maxLength}\n      {...props}\n    />\n  );\n}\n\n/**\n * Convenience component for error page titles\n */\nexport function ErrorTitle({\n  errorCode,\n  errorMessage,\n  siteName,\n  separator = ' - ',\n  ...props\n}: Omit<TitleProps, 'title'> & {\n  errorCode: number | string;\n  errorMessage?: string;\n  siteName?: string;\n}): React.ReactElement {\n  const title = errorMessage\n    ? `${errorMessage} (${errorCode})`\n    : `Error ${errorCode}`;\n\n  return (\n    <Title title={title} suffix={siteName} separator={separator} {...props} />\n  );\n}\n\n/**\n * Convenience component for search result page titles\n */\nexport function SearchTitle({\n  query,\n  resultCount,\n  siteName,\n  separator = ' - ',\n  maxLength = 60,\n  ...props\n}: Omit<TitleProps, 'title'> & {\n  query: string;\n  resultCount?: number;\n  siteName?: string;\n}): React.ReactElement {\n  let title = `Search: ${query}`;\n  if (resultCount !== undefined) {\n    title += ` (${resultCount} results)`;\n  }\n\n  return (\n    <Title\n      title={title}\n      suffix={siteName}\n      separator={separator}\n      maxLength={maxLength}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/context/app.tsx",
    "content": "import { produce } from 'immer';\nimport React, { useMemo } from 'react';\nimport {\n  AppContextDispatchValue,\n  AppStateContextValue\n} from '../../../types/appContext.js';\n\nconst AppStateContext = React.createContext<AppStateContextValue>(\n  {} as AppStateContextValue\n);\nconst AppContextDispatch = React.createContext<AppContextDispatchValue>(\n  {} as AppContextDispatchValue\n);\n\ninterface AppProviderProps {\n  value: AppStateContextValue;\n  children: React.ReactNode;\n}\n\nexport function AppProvider({ value, children }: AppProviderProps) {\n  const [data, setData] = React.useState<AppStateContextValue>(value);\n  const [fetching, setFetching] = React.useState<boolean>(false);\n\n  const fetchPageData = async (url: string | URL): Promise<void> => {\n    setFetching(true);\n    try {\n      const response = await fetch(url, {\n        method: 'GET',\n        headers: {\n          'Content-Type': 'application/json'\n        }\n      });\n      const dataResponse = await response.json();\n      // Update the entire context using immer\n      setData(\n        produce(data, (draft) => {\n          Object.assign(draft, dataResponse.eContext);\n          return draft;\n        })\n      );\n    } catch (error) {\n    } finally {\n      setFetching(false);\n    }\n  };\n\n  React.useEffect(() => {\n    window.onpopstate = async () => {\n      // Get the current url\n      const url = new URL(window.location.href, window.location.origin);\n      url.searchParams.append('ajax', 'true');\n      await fetchPageData(url.toString());\n    };\n  }, []);\n\n  const contextDispatchValue = useMemo<AppContextDispatchValue>(\n    () => ({ setData, fetchPageData }),\n    [setData, fetchPageData]\n  );\n\n  const contextValue = useMemo<AppStateContextValue>(\n    () => ({ ...data, fetching }),\n    [data, fetching]\n  );\n  return (\n    <AppContextDispatch.Provider value={contextDispatchValue}>\n      <AppStateContext.Provider value={contextValue}>\n        {children}\n      </AppStateContext.Provider>\n    </AppContextDispatch.Provider>\n  );\n}\n\nexport const useAppState = (): AppStateContextValue =>\n  React.useContext(AppStateContext);\nexport const useAppDispatch = (): AppContextDispatchValue =>\n  React.useContext(AppContextDispatch);\n"
  },
  {
    "path": "packages/evershop/src/components/common/customer/address/AddressSummary.jsx",
    "content": "/* eslint-disable react/prop-types */\nimport Area from '@components/common/Area';\nimport React from 'react';\n\nexport function AddressSummary({ address }) {\n  return (\n    <Area\n      id=\"addressSummary\"\n      className=\"address__summary\"\n      coreComponents={[\n        {\n          component: {\n            default: ({ fullName }) => (\n              <div className=\"full-name\">{fullName}</div>\n            )\n          },\n          props: {\n            fullName: address.fullName\n          },\n          sortOrder: 10,\n          id: 'fullName'\n        },\n        {\n          component: {\n            default: ({ address1 }) => (\n              <div className=\"address-one\">{address1}</div>\n            )\n          },\n          props: {\n            address1: address.address1\n          },\n          sortOrder: 20,\n          id: 'address1'\n        },\n        {\n          component: {\n            default: ({ city, province, postcode, country }) => (\n              <div className=\"city-province-postcode\">\n                <div>{`${postcode}, ${city}`}</div>\n                <div>\n                  {province && <span>{province.name}, </span>}{' '}\n                  <span>{country.name}</span>\n                </div>\n              </div>\n            )\n          },\n          props: {\n            city: address.city,\n            province: address.province,\n            postcode: address.postcode,\n            country: address.country\n          },\n          sortOrder: 40,\n          id: 'cityProvincePostcode'\n        },\n        {\n          component: {\n            default: ({ telephone }) => (\n              <div className=\"telephone\">{telephone}</div>\n            )\n          },\n          props: {\n            telephone: address.telephone\n          },\n          sortOrder: 60,\n          id: 'telephone'\n        }\n      ]}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/CheckboxField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport {\n  Field,\n  FieldError,\n  FieldLabel,\n  FieldLegend\n} from '@components/common/ui/Field.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface CheckboxOption {\n  value: string | number;\n  label: string;\n  disabled?: boolean;\n}\n\ninterface CheckboxFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<\n    React.InputHTMLAttributes<HTMLInputElement>,\n    'name' | 'type' | 'defaultValue'\n  > {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  options?: CheckboxOption[];\n  defaultValue?: boolean | (string | number)[];\n  direction?: 'horizontal' | 'vertical';\n  wrapperClassName?: string;\n}\n\nexport function CheckboxField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  wrapperClassName,\n  helperText,\n  required,\n  validation,\n  options,\n  defaultValue,\n  direction = 'vertical',\n  className,\n  disabled,\n  ...props\n}: CheckboxFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const validationRules = {\n    ...validation,\n    ...(required &&\n      !validation?.required && {\n        required: _('${field} is required', { field: label || name })\n      })\n  };\n  const containerClass =\n    direction === 'horizontal' ? 'checkbox-group horizontal' : 'checkbox-group';\n\n  if (!options || options.length === 0) {\n    return (\n      <Field\n        data-invalid={fieldError ? 'true' : 'false'}\n        className={wrapperClassName}\n      >\n        <div className=\"flex items-center gap-2\">\n          <Controller\n            name={name}\n            control={control}\n            rules={validationRules}\n            defaultValue={defaultValue as any}\n            render={({ field }) => (\n              <Checkbox\n                id={fieldId}\n                checked={!!field.value}\n                onCheckedChange={(checked) => field.onChange(checked)}\n                onBlur={field.onBlur}\n                disabled={disabled}\n                className={className}\n                aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n                aria-describedby={\n                  fieldError !== undefined\n                    ? `${fieldId}-error`\n                    : helperText\n                    ? `${fieldId}-helper`\n                    : undefined\n                }\n              />\n            )}\n          />\n          {label && (\n            <FieldLabel\n              htmlFor={fieldId}\n              className=\"text-sm font-normal cursor-pointer\"\n            >\n              {label}\n              {required && <span className=\"text-destructive\">*</span>}\n              {helperText && <Tooltip content={helperText} position=\"top\" />}\n            </FieldLabel>\n          )}\n        </div>\n\n        {fieldError && <FieldError>{fieldError}</FieldError>}\n      </Field>\n    );\n  }\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <fieldset>\n          <FieldLegend>\n            <>\n              {label}\n              {required && <span className=\"text-destructive\">*</span>}\n              {helperText && <Tooltip content={helperText} position=\"top\" />}\n            </>\n          </FieldLegend>\n\n          <Controller\n            name={name}\n            control={control}\n            rules={validationRules}\n            defaultValue={defaultValue as any}\n            render={({ field }) => (\n              <div className={containerClass}>\n                {options.map((option, index) => {\n                  const isChecked = Array.isArray(field.value)\n                    ? field.value.includes(option.value)\n                    : false;\n\n                  return (\n                    <div key={option.value} className=\"flex items-center gap-2\">\n                      <Checkbox\n                        id={`${fieldId}-${index}`}\n                        disabled={disabled || option.disabled}\n                        checked={isChecked}\n                        onCheckedChange={(checked) => {\n                          const currentValues = Array.isArray(field.value)\n                            ? field.value\n                            : [];\n                          if (checked) {\n                            field.onChange([...currentValues, option.value]);\n                          } else {\n                            field.onChange(\n                              currentValues.filter(\n                                (val) => val !== option.value\n                              )\n                            );\n                          }\n                        }}\n                        onBlur={field.onBlur}\n                        className={className}\n                        aria-invalid={fieldError ? 'true' : 'false'}\n                        aria-describedby={\n                          fieldError ? `${fieldId}-error` : undefined\n                        }\n                      />\n                      <Label\n                        htmlFor={`${fieldId}-${index}`}\n                        className={`text-sm cursor-pointer ${\n                          option.disabled ? 'opacity-50 cursor-not-allowed' : ''\n                        }`}\n                      >\n                        {option.label}\n                      </Label>\n                    </div>\n                  );\n                })}\n              </div>\n            )}\n          />\n        </fieldset>\n      )}\n\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/DateField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  InputGroup,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface DateFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'type'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  wrapperClassName?: string;\n}\n\nexport function DateField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  wrapperClassName,\n  helperText,\n  required,\n  validation,\n  className,\n  min,\n  max,\n  defaultValue,\n  ...props\n}: DateFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const { valueAsNumber, ...cleanValidation } = validation || {};\n  const validationRules = {\n    ...cleanValidation,\n    ...(required && {\n      required: _('${field} is required', { field: label || name })\n    }),\n    validate: {\n      ...validation?.validate,\n      minDate: (value) => {\n        if (!min || !value) return true;\n        return (\n          value >= min ||\n          _('Date must be after ${min}', { min: min.toString() })\n        );\n      },\n      maxDate: (value) => {\n        if (!max || !value) return true;\n        return (\n          value <= max ||\n          _('Date must be before ${max}', { max: max.toString() })\n        );\n      }\n    }\n  };\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n\n      <Controller\n        name={name}\n        control={control}\n        defaultValue={defaultValue as any}\n        rules={validationRules}\n        render={({ field }) => (\n          <InputGroup>\n            <InputGroupInput\n              {...field}\n              value={field.value ?? ''}\n              id={fieldId}\n              type=\"date\"\n              min={min}\n              max={max}\n              className={className}\n              aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n              aria-describedby={\n                fieldError !== undefined ? `${fieldId}-error` : undefined\n              }\n              {...props}\n            />\n          </InputGroup>\n        )}\n      />\n\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/DateTimeLocalField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  InputGroup,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface DateTimeLocalFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'type'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  wrapperClassName?: string;\n}\n\nexport function DateTimeLocalField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  wrapperClassName,\n  helperText,\n  required,\n  validation,\n  className,\n  min,\n  max,\n  step,\n  ...props\n}: DateTimeLocalFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const { valueAsNumber, valueAsDate, ...cleanValidation } = validation || {};\n  const validationRules = {\n    ...cleanValidation,\n    ...(required && {\n      required: _('${field} is required', { field: label || name })\n    }),\n    validate: {\n      ...validation?.validate,\n      minDateTime: (value) => {\n        if (!min || !value) return true;\n        return (\n          value >= min ||\n          _('Date and time must be after ${min}', { min: min.toString() })\n        );\n      },\n      maxDateTime: (value) => {\n        if (!max || !value) return true;\n        return (\n          value <= max ||\n          _('Date and time must be before ${max}', { max: max.toString() })\n        );\n      }\n    }\n  };\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n\n      <Controller\n        name={name}\n        control={control}\n        rules={validationRules}\n        render={({ field }) => (\n          <InputGroup>\n            <InputGroupInput\n              {...field}\n              value={field.value ?? ''}\n              id={fieldId}\n              type=\"datetime-local\"\n              min={min}\n              max={max}\n              step={step}\n              className={className}\n              aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n              aria-describedby={\n                fieldError !== undefined ? `${fieldId}-error` : undefined\n              }\n              {...props}\n            />\n          </InputGroup>\n        )}\n      />\n\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/Editor.scss",
    "content": "body .codex-editor {\n  z-index: 99;\n}\n\n#rows {\n  .row {\n    background-color: #fff;\n  }\n\n  .row__container {\n    &:hover,\n    &:focus-within {\n      z-index: 100 !important;\n    }\n  }\n\n  .drag__icon {\n    cursor: move;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: 4px;\n\n    &:hover {\n      background-color: #e0e0e0;\n    }\n\n    svg {\n      display: block; // Prevent extra space below svg\n    }\n  }\n}\n\n.item {\n  padding: 10px;\n  background: var(--divider);\n  border: 2px solid var(--border);\n}\n\n#rows .draggable-source--is-dragging {\n  background: var(--divider);\n  color: var(--divider);\n}\n\n#rows .draggable-source--is-dragging > * {\n  visibility: hidden;\n}\n\n.ce-rawtool__textarea {\n  z-index: 0;\n  min-height: 200px;\n  width: 100%;\n  padding: 12px;\n  border: 1px solid #e5e7eb;\n  border-radius: 6px;\n  font-family: 'Courier New', monospace;\n  font-size: 14px;\n  line-height: 1.6;\n  resize: vertical;\n\n  &:focus {\n    outline: none;\n    border-color: #3b82f6;\n    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n  }\n}\n\n.ce-rawtool {\n  margin: 0.5em 0;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/Editor.tsx",
    "content": "import { FileBrowser } from '@components/admin/FileBrowser.js';\nimport { getColumnClasses } from '@components/common/form/editor/GetColumnClasses.js';\nimport { getRowClasses } from '@components/common/form/editor/GetRowClasses.js';\nimport { RawToolWrapper } from '@components/common/form/editor/RawToolWrapper.js';\nimport { RowTemplates } from '@components/common/form/editor/RowTemplates.js';\nimport { Field, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  DndContext,\n  closestCenter,\n  KeyboardSensor,\n  PointerSensor,\n  useSensor,\n  useSensors\n} from '@dnd-kit/core';\nimport {\n  arrayMove,\n  SortableContext,\n  sortableKeyboardCoordinates,\n  useSortable,\n  verticalListSortingStrategy\n} from '@dnd-kit/sortable';\nimport { CircleX } from 'lucide-react';\nimport React from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport { v4 as uuidv4 } from 'uuid';\nimport './Editor.scss';\n\nasync function loadEditorJS(): Promise<any> {\n  const { default: EditorJS } = await import('@editorjs/editorjs');\n  return EditorJS;\n}\n\nasync function loadEditorJSImage(): Promise<any> {\n  const { default: ImageTool } = await import('@evershop/editorjs-image');\n  return ImageTool;\n}\n\nasync function loadEditorJSHeader(): Promise<any> {\n  const { default: Header } = await import('@editorjs/header');\n  return Header;\n}\n\nasync function loadEditorJSList(): Promise<any> {\n  const { default: List } = await import('@editorjs/list');\n  return List;\n}\n\nasync function loadEditorJSQuote(): Promise<any> {\n  const { default: Quote } = await import('@editorjs/quote');\n  return Quote;\n}\n\n// Using custom RawToolWrapper instead to fix backspace issues\n// async function loadEditorJSRaw(): Promise<any> {\n//   const { default: RawTool } = await import('@editorjs/raw');\n//   return RawTool;\n// }\n\nconst SortableRow: React.FC<{\n  row: Row;\n  removeRow: (rowId: string) => void;\n  children: React.ReactNode;\n}> = ({ row, removeRow, children }) => {\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    transform,\n    transition,\n    isDragging\n  } = useSortable({\n    id: row.id\n  });\n\n  const style = {\n    transform: transform ? `translateY(${transform.y}px)` : undefined,\n    transition,\n    opacity: isDragging ? 0.5 : 1,\n    position: 'relative'\n  } as React.CSSProperties;\n\n  return (\n    <div\n      className=\"border border-border row__container mt-3 first:mt-0 rounded-md\"\n      id={row.id}\n      ref={setNodeRef}\n      style={style}\n    >\n      <div className=\"config p-3 flex justify-between bg-muted items-center\">\n        <div className=\"drag__icon cursor-move\" {...attributes} {...listeners}>\n          <svg\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            fill=\"#949494\"\n            width={20}\n            height={20}\n          >\n            <g>\n              <path fill=\"none\" d=\"M0 0h24v24H0z\" />\n              <path\n                fillRule=\"nonzero\"\n                d=\"M14 6h2v2h5a1 1 0 0 1 1 1v7.5L16 13l.036 8.062 2.223-2.15L20.041 22H9a1 1 0 0 1-1-1v-5H6v-2h2V9a1 1 0 0 1 1-1h5V6zm8 11.338V21a1 1 0 0 1-.048.307l-1.96-3.394L22 17.338zM4 14v2H2v-2h2zm0-4v2H2v-2h2zm0-4v2H2V6h2zm0-4v2H2V2h2zm4 0v2H6V2h2zm4 0v2h-2V2h2zm4 0v2h-2V2h2z\"\n              />\n            </g>\n          </svg>\n        </div>\n        <div>\n          <a\n            href=\"#\"\n            onClick={(e) => {\n              e.preventDefault();\n              removeRow(row.id);\n            }}\n          >\n            <CircleX width={20} height={20} />\n          </a>\n        </div>\n      </div>\n      {children}\n    </div>\n  );\n};\n\nexport interface Row {\n  id: string;\n  size: number;\n  columns: {\n    id: string;\n    size: number;\n    data: any;\n  }[];\n}\n\nexport interface EditorProps {\n  name: string;\n  value?: Row[];\n  label?: string;\n}\n\nexport const Editor: React.FC<EditorProps> = ({ name, value = [], label }) => {\n  const [openFileBrowser, setOpenFileBrowser] = React.useState(false);\n  const [fileBrowser, setFileBrowser] = React.useState<{\n    onUpload: (fileUrl: string) => void;\n    onError: (error: string) => void;\n  } | null>(null);\n  const { register, setValue } = useFormContext();\n  const [rows, setRows] = React.useState(\n    value\n      ? value.map((row) => {\n          const rowId = `r__${uuidv4()}`;\n          return {\n            ...row,\n            className: getRowClasses(row.size),\n            id: row.id || rowId,\n            columns: row.columns.map((column) => {\n              const colId = `c__${uuidv4()}`;\n              return {\n                ...column,\n                className: getColumnClasses(column.size),\n                id: column.id || colId\n              };\n            })\n          };\n        })\n      : []\n  );\n  const editors = React.useRef({});\n\n  const sensors = useSensors(\n    useSensor(PointerSensor, {\n      activationConstraint: {\n        distance: 8\n      }\n    }),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates\n    })\n  );\n\n  const handleDragEnd = (event) => {\n    const { active, over } = event;\n\n    if (active && over && active.id !== over.id) {\n      setRows((items) => {\n        const oldIndex = items.findIndex((row) => row.id === active.id);\n        const newIndex = items.findIndex((row) => row.id === over.id);\n\n        if (oldIndex !== -1 && newIndex !== -1) {\n          return arrayMove(items, oldIndex, newIndex);\n        }\n        return items;\n      });\n    }\n  };\n\n  React.useEffect(() => {\n    const initEditors = async () => {\n      const EditorJS = await loadEditorJS();\n      const ImageTool = await loadEditorJSImage();\n      const Header = await loadEditorJSHeader();\n      const List = await loadEditorJSList();\n      const Quote = await loadEditorJSQuote();\n      // Using RawToolWrapper instead of loading from @editorjs/raw\n      setValue(name, rows);\n      rows.forEach((row) => {\n        row.columns.forEach((column) => {\n          if (!editors.current[column.id]) {\n            editors.current[column.id] = {};\n            editors.current[column.id].instance = new EditorJS({\n              holder: column.id,\n              placeholder: 'Type / to see the available blocks',\n              minHeight: 0,\n              tools: {\n                header: Header,\n                list: List,\n                raw: {\n                  class: RawToolWrapper,\n                  inlineToolbar: false\n                },\n                quote: Quote,\n                image: {\n                  class: ImageTool,\n                  config: {\n                    onSelectFile: (onUpload, onError) => {\n                      setFileBrowser({\n                        onUpload: (fileUrl) => {\n                          onUpload({\n                            success: 1,\n                            file: {\n                              url: fileUrl\n                            }\n                          });\n                        },\n                        onError\n                      });\n                      setOpenFileBrowser(true);\n                    }\n                  }\n                }\n              },\n              data: column.data,\n              onChange: (api) => {\n                api.saver.save().then((outputData) => {\n                  // Save outputData to the column and trigger re-render\n                  setRows((prevRows) => {\n                    const newRows = [...prevRows];\n                    const rowIdx = newRows.findIndex((r) => r.id === row.id);\n                    const columnIdx = newRows[rowIdx].columns.findIndex(\n                      (c) => c.id === column.id\n                    );\n                    newRows[rowIdx].columns[columnIdx].data = outputData;\n                    setValue(name, newRows);\n                    return newRows;\n                  });\n                });\n              }\n            });\n          }\n        });\n      });\n    };\n    initEditors();\n  }, [rows.length]);\n\n  const removeRow = (rowId) => {\n    setRows(rows.filter((i) => i.id !== rowId));\n  };\n\n  const addRow = (row) => {\n    setRows(rows.concat(row));\n  };\n\n  return (\n    <Field className=\"editor form-field-container\">\n      <FieldLabel htmlFor=\"description mt-4\">{label}</FieldLabel>\n      <div className=\"prose prose-xl max-w-none\">\n        <DndContext\n          sensors={sensors}\n          collisionDetection={closestCenter}\n          onDragEnd={handleDragEnd}\n        >\n          <SortableContext\n            items={rows.map((row) => row.id)}\n            strategy={verticalListSortingStrategy}\n          >\n            <div id=\"rows\">\n              {rows.map((row) => (\n                // Grid template columns based on the number of columns in the row\n                <SortableRow key={row.id} row={row} removeRow={removeRow}>\n                  <div\n                    className={`row grid p-5 divide-x divide-dashed ${row.className}`}\n                    style={{\n                      minHeight: '30px'\n                    }}\n                  >\n                    {row.columns.map((column) => (\n                      <div\n                        className={`column p-3 ${column.className}`}\n                        key={column.id}\n                      >\n                        <div id={column.id} />\n                      </div>\n                    ))}\n                  </div>\n                </SortableRow>\n              ))}\n            </div>\n          </SortableContext>\n        </DndContext>\n        <div className=\"flex justify-center\">\n          <div className=\"flex justify-center flex-col mt-5\">\n            <RowTemplates addRow={addRow} />\n          </div>\n        </div>\n      </div>\n      <input type=\"hidden\" {...register(name)} />\n      {openFileBrowser && (\n        <FileBrowser\n          onInsert={(url) => {\n            fileBrowser && fileBrowser.onUpload(url);\n            setOpenFileBrowser(false);\n          }}\n          close={() => setOpenFileBrowser(false)}\n          isMultiple={false}\n        />\n      )}\n    </Field>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/EmailField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface EmailFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'type'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  wrapperClassName?: string;\n  prefixIcon?: React.ReactNode;\n  suffixIcon?: React.ReactNode;\n}\n\nexport function EmailField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  helperText,\n  required,\n  validation,\n  wrapperClassName,\n  className,\n  defaultValue,\n  prefixIcon,\n  suffixIcon,\n  ...props\n}: EmailFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const validationRules = {\n    ...validation,\n    ...(required &&\n      !validation?.required && {\n        required: _('${field} is required', { field: label || name })\n      }),\n    pattern: validation?.pattern || {\n      value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i,\n      message: _('Please enter a valid email address')\n    }\n  };\n\n  const renderInput = () => (\n    <Controller\n      name={name}\n      control={control}\n      defaultValue={defaultValue as any}\n      rules={validationRules}\n      render={({ field }) => (\n        <InputGroupInput\n          {...field}\n          value={field.value ?? ''}\n          id={fieldId}\n          type=\"email\"\n          aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n          aria-describedby={\n            fieldError !== undefined ? `${fieldId}-error` : undefined\n          }\n          {...props}\n        />\n      )}\n    />\n  );\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n      <InputGroup>\n        {renderInput()}\n        {prefixIcon && (\n          <InputGroupAddon align={'inline-start'}>{prefixIcon}</InputGroupAddon>\n        )}\n        {suffixIcon && (\n          <InputGroupAddon align={'inline-end'}>{suffixIcon}</InputGroupAddon>\n        )}\n      </InputGroup>\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/FileField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport { InputGroupInput } from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface FileFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'type'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  maxSize?: number;\n  wrapperClassName?: string;\n}\n\nexport function FileField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  wrapperClassName,\n  helperText,\n  required,\n  validation,\n  maxSize,\n  className,\n  accept,\n  multiple = false,\n  ...props\n}: FileFieldProps<T>) {\n  const {\n    control,\n    formState: { errors },\n    watch\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n  const files = watch(name);\n\n  const formatFileSize = (bytes: number) => {\n    if (bytes === 0) return '0 Bytes';\n    const k = 1024;\n    const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n    const i = Math.floor(Math.log(bytes) / Math.log(k));\n    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n  };\n\n  const { valueAsNumber, valueAsDate, ...cleanValidation } = validation || {};\n  const validationRules = {\n    ...cleanValidation,\n    ...(required &&\n      !validation?.required && {\n        required: _('${field} is required', { field: label || name })\n      }),\n    validate: {\n      ...validation?.validate,\n      fileSize: (fileList) => {\n        if (!maxSize || !fileList || fileList.length === 0) return true;\n        for (let i = 0; i < fileList.length; i++) {\n          if (fileList[i].size > maxSize) {\n            return _('File size must be less than ${maxSize}', {\n              maxSize: formatFileSize(maxSize)\n            });\n          }\n        }\n        return true;\n      }\n    }\n  };\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n\n      <Controller\n        name={name}\n        control={control}\n        rules={validationRules}\n        render={({ field: { onChange, value, ...field } }) => (\n          <InputGroupInput\n            {...field}\n            id={fieldId}\n            type=\"file\"\n            accept={accept}\n            multiple={multiple}\n            className={className}\n            aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n            aria-describedby={\n              fieldError !== undefined ? `${fieldId}-error` : undefined\n            }\n            onChange={(e) => {\n              onChange(e.target.files);\n            }}\n            {...props}\n          />\n        )}\n      />\n\n      {maxSize && (\n        <p className=\"file-size-hint\">\n          Maximum file size: {formatFileSize(maxSize)}\n        </p>\n      )}\n\n      {files && files.length > 0 && (\n        <div className=\"file-list\">\n          <p className=\"file-list-label\">Selected files:</p>\n          <ul className=\"file-items\">\n            {Array.from(files as FileList).map((file: File, index) => (\n              <li key={index}>\n                {file.name} ({formatFileSize(file.size)})\n              </li>\n            ))}\n          </ul>\n        </div>\n      )}\n\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/Form.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useEffect, useState } from 'react';\nimport {\n  useForm,\n  FormProvider,\n  UseFormProps,\n  FieldValues,\n  SubmitHandler,\n  UseFormReturn\n} from 'react-hook-form';\nimport { toast } from 'react-toastify';\n\ninterface FormProps<T extends FieldValues = FieldValues>\n  extends Omit<\n    React.FormHTMLAttributes<HTMLFormElement>,\n    'onSubmit' | 'onError'\n  > {\n  form?: UseFormReturn<T>;\n  action?: string;\n  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n  formOptions?: UseFormProps<T>;\n  onSubmit?: SubmitHandler<T>;\n  onSuccess?: (response: any, data: T) => void;\n  onError?: (error: string, data: T) => void;\n  successMessage?: string;\n  errorMessage?: string;\n  submitBtn?: boolean;\n  submitBtnText?: string;\n  loading?: boolean;\n  children: React.ReactNode;\n}\n\nexport function Form<T extends FieldValues = FieldValues>({\n  form: externalForm,\n  action,\n  method = 'POST',\n  formOptions,\n  onSubmit,\n  onSuccess,\n  onError,\n  successMessage = _('Saved successfully!'),\n  errorMessage = _('Something went wrong! Please try again.'),\n  submitBtn = true,\n  submitBtnText = _('Save'),\n  loading = false,\n  children,\n  className,\n  noValidate = true,\n  ...props\n}: FormProps<T>) {\n  const theForm =\n    externalForm ||\n    useForm<T>({\n      shouldUnregister: true,\n      shouldFocusError: false,\n      ...formOptions\n    });\n  const {\n    handleSubmit,\n    formState: { isSubmitting }\n  } = theForm;\n\n  const defaultSubmit: SubmitHandler<T> = async (data) => {\n    if (!action) {\n      return;\n    }\n    try {\n      const response = await fetch(action, {\n        method,\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify(data)\n      });\n\n      const result = await response.json();\n\n      if (result.error) {\n        if (onError) {\n          onError(result.error.message, data);\n        } else {\n          toast.error(result.error.message || errorMessage);\n        }\n      } else if (onSuccess) {\n        onSuccess(result, data);\n      } else {\n        toast.success(successMessage);\n      }\n    } catch (error) {\n      if (onError) {\n        onError(\n          errorMessage || (error instanceof Error ? error.message : ''),\n          data\n        );\n      } else {\n        toast.error(\n          errorMessage || (error instanceof Error ? error.message : '')\n        );\n      }\n    }\n  };\n\n  const [canFocus, setCanFocus] = useState(true);\n\n  const onValidationError = () => {\n    setCanFocus(true);\n  };\n\n  useEffect(() => {\n    if (theForm.formState.errors && canFocus) {\n      const elements = Array.from(\n        document.querySelectorAll('[aria-invalid=\"true\"]')\n      ) as HTMLElement[];\n      elements.sort(\n        (a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top\n      );\n\n      if (elements.length > 0) {\n        const errorElement = elements[0];\n        errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });\n        errorElement.focus({ preventScroll: true });\n        setCanFocus(false);\n      }\n    }\n  }, [theForm.formState, canFocus]);\n\n  const handleFormSubmit = onSubmit || defaultSubmit;\n\n  return (\n    <FormProvider {...theForm}>\n      <form\n        onSubmit={handleSubmit(handleFormSubmit, onValidationError)}\n        className={className}\n        noValidate={noValidate}\n        {...props}\n      >\n        <fieldset disabled={loading}>{children}</fieldset>\n\n        {submitBtn && (\n          <div className=\"mt-4\">\n            <Button\n              title={submitBtnText}\n              type=\"submit\"\n              onClick={() => {\n                handleSubmit(handleFormSubmit, onValidationError)();\n              }}\n              isLoading={isSubmitting || loading}\n            >\n              {submitBtnText}\n            </Button>\n          </div>\n        )}\n      </form>\n    </FormProvider>\n  );\n}\n\nexport { useFormContext } from 'react-hook-form';\nexport { Controller } from 'react-hook-form';\nexport type { Control, FieldPath, FieldValues } from 'react-hook-form';\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/InputField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface InputFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  wrapperClassName?: string;\n  prefixIcon?: React.ReactNode;\n  suffixIcon?: React.ReactNode;\n}\n\nexport function InputField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  helperText,\n  required,\n  validation,\n  wrapperClassName,\n  className,\n  type = 'text',\n  prefixIcon,\n  suffixIcon,\n  defaultValue,\n  ...props\n}: InputFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const validationRules = {\n    ...validation,\n    ...(required &&\n      !validation?.required && {\n        required: _('${field} is required', { field: label || name })\n      })\n  };\n\n  const renderInput = () => (\n    <Controller\n      name={name}\n      control={control}\n      defaultValue={(defaultValue ?? '') as any}\n      rules={validationRules}\n      render={({ field }) => (\n        <InputGroupInput\n          {...field}\n          id={fieldId}\n          type={type}\n          aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n          aria-describedby={\n            fieldError !== undefined ? `${fieldId}-error` : undefined\n          }\n          {...props}\n        />\n      )}\n    />\n  );\n\n  // Special case: hidden inputs don't need labels or error messages\n  if (type === 'hidden') {\n    return (\n      <div>\n        {renderInput()}\n        {fieldError && <FieldError>{fieldError}</FieldError>}\n      </div>\n    );\n  }\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n      <InputGroup>\n        {renderInput()}\n        {prefixIcon && (\n          <InputGroupAddon align={'inline-start'}>{prefixIcon}</InputGroupAddon>\n        )}\n        {suffixIcon && (\n          <InputGroupAddon align={'inline-end'}>{suffixIcon}</InputGroupAddon>\n        )}\n      </InputGroup>\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/NumberField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport { useFormContext, RegisterOptions, Controller } from 'react-hook-form';\n\ninterface NumberFieldProps {\n  name: string;\n  label?: string;\n  placeholder?: string;\n  className?: string;\n  required?: boolean;\n  disabled?: boolean;\n  min?: number;\n  max?: number;\n  step?: number;\n  allowDecimals?: boolean;\n  unit?: string;\n  unitPosition?: 'left' | 'right';\n  defaultValue?: number;\n  error?: string;\n  helperText?: string;\n  validation?: RegisterOptions;\n  onChange?: (value: number | null) => void;\n  wrapperClassName?: string;\n  prefixIcon?: React.ReactNode;\n  suffixIcon?: React.ReactNode;\n}\n\nexport function NumberField({\n  name,\n  label,\n  placeholder,\n  className = '',\n  wrapperClassName,\n  required = false,\n  disabled = false,\n  min,\n  max,\n  step,\n  allowDecimals = true,\n  unit,\n  unitPosition = 'right',\n  defaultValue,\n  error,\n  helperText,\n  validation,\n  onChange,\n  prefixIcon,\n  suffixIcon,\n  ...props\n}: NumberFieldProps) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext();\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const validationRules: RegisterOptions = {\n    setValueAs: (value) => {\n      // Handle empty or null values\n      if (value === '' || value === null || value === undefined) {\n        return null;\n      }\n\n      // Convert string to number\n      const numValue = allowDecimals ? parseFloat(value) : parseInt(value, 10);\n\n      // Return null if conversion resulted in NaN\n      return isNaN(numValue) ? null : numValue;\n    }\n  };\n\n  if (validation) {\n    Object.assign(validationRules, validation);\n  }\n\n  if (required && !validationRules.required) {\n    validationRules.required = _('${field} is required', {\n      field: label || 'This field'\n    });\n  }\n\n  if (min !== undefined && !validationRules.min) {\n    validationRules.min = {\n      value: min,\n      message: _('Value must be at least ${min}', { min: min.toString() })\n    };\n  }\n\n  if (max !== undefined && !validationRules.max) {\n    validationRules.max = {\n      value: max,\n      message: _('Value must be at most ${max}', { max: max.toString() })\n    };\n  }\n\n  if (!allowDecimals && !validation?.validate) {\n    validationRules.validate = (value) => {\n      if (value === null || value === undefined || value === '') return true;\n      return (\n        Number.isInteger(Number(value)) || _('Value must be a whole number')\n      );\n    };\n  } else if (\n    !allowDecimals &&\n    validation?.validate &&\n    typeof validation.validate === 'object'\n  ) {\n    validationRules.validate = {\n      ...validation.validate,\n      isInteger: (value) => {\n        if (value === null || value === undefined || value === '') return true;\n        return (\n          Number.isInteger(Number(value)) || _('Value must be a whole number')\n        );\n      }\n    };\n  }\n\n  const inputStep = step !== undefined ? step : allowDecimals ? 'any' : '1';\n\n  const inputClassName = `${fieldError ? 'error' : ''} ${\n    unit ? 'has-unit' : ''\n  } ${className || ''} ${prefixIcon ? '!pl-10' : ''} ${\n    suffixIcon ? '!pr-10' : ''\n  }`.trim();\n\n  const renderInput = () => (\n    <Controller\n      name={name}\n      control={control}\n      defaultValue={defaultValue ?? null}\n      rules={validationRules}\n      render={({ field }) => (\n        <InputGroupInput\n          {...field}\n          id={fieldId}\n          type=\"number\"\n          placeholder={placeholder}\n          disabled={disabled}\n          min={min}\n          max={max}\n          step={inputStep}\n          className={inputClassName}\n          aria-invalid={fieldError ? 'true' : 'false'}\n          aria-describedby={fieldError ? `${fieldId}-error` : undefined}\n          value={field.value ?? ''}\n          onChange={(e) => {\n            const inputValue = e.target.value;\n            let numValue: number | null = null;\n\n            if (inputValue !== '') {\n              if (allowDecimals) {\n                numValue = parseFloat(inputValue);\n              } else {\n                numValue = parseInt(inputValue, 10);\n              }\n              numValue = isNaN(numValue) ? null : numValue;\n            }\n\n            field.onChange(numValue);\n            if (onChange) {\n              onChange(numValue);\n            }\n          }}\n          {...props}\n        />\n      )}\n    />\n  );\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n      <InputGroup>\n        {renderInput()}\n        {prefixIcon && (\n          <InputGroupAddon align={'inline-start'}>{prefixIcon}</InputGroupAddon>\n        )}\n        {suffixIcon && (\n          <InputGroupAddon align={'inline-end'}>{suffixIcon}</InputGroupAddon>\n        )}\n        {unit && (\n          <InputGroupAddon\n            align={unitPosition === 'right' ? 'inline-end' : 'inline-start'}\n          >\n            {unit}\n          </InputGroupAddon>\n        )}\n      </InputGroup>\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/PasswordField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { Eye, EyeClosed } from 'lucide-react';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface PasswordFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'type'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  minLength?: number;\n  showToggle?: boolean;\n  validation?: RegisterOptions<T>;\n  wrapperClassName?: string;\n  prefixIcon?: React.ReactNode;\n  suffixIcon?: React.ReactNode;\n}\n\nexport function PasswordField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  helperText,\n  required,\n  minLength = 6,\n  showToggle = false,\n  validation,\n  wrapperClassName,\n  className,\n  defaultValue,\n  prefixIcon,\n  suffixIcon,\n  ...props\n}: PasswordFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n  const [showPassword, setShowPassword] = React.useState(false);\n\n  const validationRules = {\n    ...validation,\n    ...(required &&\n      !validation?.required && {\n        required: _('${field} is required', { field: label || name })\n      }),\n    minLength: validation?.minLength || {\n      value: minLength,\n      message: _('Password must be at least ${minLength} characters long', {\n        minLength: minLength.toString()\n      })\n    }\n  };\n\n  const renderToggleButton = () =>\n    showToggle ? (\n      <button\n        type=\"button\"\n        className=\"transition-colors\"\n        onClick={() => setShowPassword(!showPassword)}\n        tabIndex={-1}\n      >\n        {showPassword ? (\n          <Eye className=\"h-5 w-5\" />\n        ) : (\n          <EyeClosed className=\"h-5 w-5\" />\n        )}\n      </button>\n    ) : null;\n\n  const renderInput = () => (\n    <Controller\n      name={name}\n      control={control}\n      defaultValue={defaultValue as any}\n      rules={validationRules}\n      render={({ field }) => (\n        <InputGroupInput\n          {...field}\n          value={field.value ?? ''}\n          id={fieldId}\n          type={showToggle && showPassword ? 'text' : 'password'}\n          aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n          aria-describedby={\n            fieldError !== undefined ? `${fieldId}-error` : undefined\n          }\n          {...props}\n        />\n      )}\n    />\n  );\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n      <InputGroup>\n        {renderInput()}\n        {prefixIcon && (\n          <InputGroupAddon align={'inline-start'}>{prefixIcon}</InputGroupAddon>\n        )}\n        {(suffixIcon || showToggle) && (\n          <InputGroupAddon align={'inline-end'}>\n            {suffixIcon || renderToggleButton()}\n          </InputGroupAddon>\n        )}\n      </InputGroup>\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/RadioGroupField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport {\n  Field,\n  FieldError,\n  FieldLabel,\n  FieldLegend\n} from '@components/common/ui/Field.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport {\n  RadioGroup,\n  RadioGroupItem\n} from '@components/common/ui/RadioGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface RadioOption {\n  value: string | number;\n  label: string;\n  disabled?: boolean;\n}\n\ninterface RadioGroupFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<\n    React.InputHTMLAttributes<HTMLInputElement>,\n    'name' | 'type' | 'value' | 'checked' | 'onChange' | 'onBlur'\n  > {\n  name: FieldPath<T>;\n  options: RadioOption[];\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  disabled?: boolean;\n  validation?: RegisterOptions<T>;\n  defaultValue?: string | number;\n  wrapperClassName?: string;\n}\n\nexport function RadioGroupField<T extends FieldValues = FieldValues>({\n  name,\n  options,\n  label,\n  error,\n  wrapperClassName,\n  helperText,\n  className = '',\n  required = false,\n  disabled = false,\n  validation,\n  defaultValue,\n  ...props\n}: RadioGroupFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const validationRules = {\n    ...validation,\n    ...(required &&\n      !validation?.required && {\n        required: _('${field} is required', { field: label || name })\n      })\n  };\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n\n      <Controller\n        name={name}\n        control={control}\n        rules={validationRules}\n        defaultValue={defaultValue as any}\n        render={({ field }) => (\n          <RadioGroup\n            value={String(field.value ?? '')}\n            onValueChange={(value) => {\n              const option = options.find((o) => String(o.value) === value);\n              if (option) {\n                field.onChange(option.value);\n              }\n            }}\n            className={className}\n            aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n            aria-describedby={\n              fieldError !== undefined ? `${fieldId}-error` : undefined\n            }\n          >\n            {options.map((option) => (\n              <div key={option.value} className=\"flex items-center gap-2\">\n                <RadioGroupItem\n                  value={String(option.value)}\n                  id={`${fieldId}-${option.value}`}\n                  disabled={disabled || option.disabled}\n                />\n                <FieldLabel\n                  htmlFor={`${fieldId}-${option.value}`}\n                  className={`text-sm font-normal cursor-pointer ${\n                    option.disabled ? 'opacity-50 cursor-not-allowed' : ''\n                  }`}\n                >\n                  {option.label}\n                </FieldLabel>\n              </div>\n            ))}\n          </RadioGroup>\n        )}\n      />\n\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/RangeField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues\n} from 'react-hook-form';\n\ninterface RangeFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'type'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  showValue?: boolean;\n  defaultValue?: number;\n  wrapperClassName?: string;\n}\n\nexport function RangeField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  wrapperClassName,\n  helperText,\n  required,\n  validation,\n  showValue = true,\n  defaultValue,\n  className,\n  min = 0,\n  max = 100,\n  step = 1,\n  ...props\n}: RangeFieldProps<T>) {\n  const {\n    register,\n    formState: { errors },\n    watch\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n  const value = watch(name) || min;\n  const { valueAsDate, pattern, ...cleanValidation } = validation || {};\n  const validationRules = {\n    ...cleanValidation,\n    ...(required && {\n      required: _('${field} is required', { field: label || name })\n    }),\n    valueAsNumber: true\n  } as const;\n\n  return (\n    <div\n      className={`form-field ${wrapperClassName} ${fieldError ? 'error' : ''}`}\n    >\n      {label && (\n        <label htmlFor={fieldId}>\n          {label}\n          {required && <span className=\"text-destructive\">*</span>}\n          {showValue && <span className=\"range-value\">({value})</span>}\n          {helperText && <Tooltip content={helperText} position=\"top\" />}\n        </label>\n      )}\n\n      <input\n        id={fieldId}\n        type=\"range\"\n        min={min}\n        max={max}\n        step={step}\n        {...register(name, validationRules)}\n        className={className}\n        aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n        aria-describedby={\n          fieldError !== undefined ? `${fieldId}-error` : undefined\n        }\n        {...props}\n      />\n\n      <div className=\"range-labels\">\n        <span>{min}</span>\n        <span>{max}</span>\n      </div>\n\n      {fieldError && (\n        <p id={`${fieldId}-error`} className=\"field-error\">\n          {fieldError}\n        </p>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/ReactSelectCreatableField.tsx",
    "content": "import { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldLabel } from '@components/common/ui/Field.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  Controller,\n  FieldPath,\n  FieldValues,\n  RegisterOptions,\n  useFormContext\n} from 'react-hook-form';\nimport CreatableSelect, { CreatableProps } from 'react-select/creatable';\n\ninterface SelectOption {\n  value: any;\n  label: string;\n  [key: string]: unknown;\n}\n\ninterface ReactSelectCreatableFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<\n    CreatableProps<SelectOption, boolean, any>,\n    'name' | 'value' | 'onChange'\n  > {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  options: SelectOption[];\n  className?: string;\n  wrapperClassName?: string;\n  defaultValue?: any;\n  onCreateOption?: (inputValue: string) => void;\n  formatCreateLabel?: (inputValue: string) => string;\n}\n\nexport function ReactSelectCreatableField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  wrapperClassName = 'form-field',\n  helperText,\n  required,\n  validation,\n  options,\n  className,\n  isMulti = false,\n  defaultValue,\n  onCreateOption,\n  formatCreateLabel = (inputValue: string) => `Create \"${inputValue}\"`,\n  ...selectProps\n}: ReactSelectCreatableFieldProps<T>) {\n  const {\n    control,\n    unregister,\n    formState: { errors }\n  } = useFormContext<T>();\n  const fieldId = `field-${name}`;\n\n  const [dynamicOptions, setDynamicOptions] =\n    React.useState<SelectOption[]>(options);\n\n  React.useEffect(() => {\n    setDynamicOptions(options);\n  }, [options]);\n\n  React.useEffect(() => {\n    return () => {\n      unregister(name);\n    };\n  }, [name, unregister]);\n\n  const validationRules = {\n    ...validation,\n    ...(required && {\n      required: _('${field} is required', { field: label || name })\n    })\n  };\n  const fieldError = getNestedError(name, errors, error);\n  return (\n    <Controller\n      name={name}\n      control={control}\n      rules={validationRules}\n      defaultValue={defaultValue}\n      render={({ field, fieldState }) => {\n        const handleCreateOption = (inputValue: string) => {\n          const newOption = {\n            value: inputValue.toLowerCase().replace(/\\W/g, ''),\n            label: inputValue\n          };\n          const optionExists = dynamicOptions.some(\n            (option) =>\n              option.value === newOption.value ||\n              option.label === newOption.label\n          );\n\n          if (!optionExists) {\n            setDynamicOptions((prev) => {\n              const updated = [...prev, newOption];\n              return updated;\n            });\n          }\n\n          if (onCreateOption) {\n            onCreateOption(inputValue);\n          }\n\n          if (isMulti) {\n            const currentValues = (field.value as any[]) || [];\n            if (!currentValues.includes(newOption.value)) {\n              const newValues = [...currentValues, newOption.value];\n              field.onChange(newValues);\n            }\n          } else {\n            field.onChange(newOption.value);\n          }\n        };\n\n        return (\n          <Field\n            data-invalid={fieldError ? 'true' : 'false'}\n            className={wrapperClassName}\n          >\n            {label && (\n              <FieldLabel htmlFor={fieldId}>\n                {label}\n                {required && <span className=\"text-destructive\">*</span>}\n              </FieldLabel>\n            )}\n            <CreatableSelect\n              {...field}\n              {...selectProps}\n              inputId={fieldId}\n              options={dynamicOptions}\n              isMulti={isMulti}\n              formatCreateLabel={formatCreateLabel}\n              onCreateOption={handleCreateOption}\n              value={\n                isMulti\n                  ? dynamicOptions.filter((option) =>\n                      field.value?.includes(option.value)\n                    )\n                  : dynamicOptions.find(\n                      (option) => option.value === field.value\n                    ) || null\n              }\n              onChange={(selectedOption) => {\n                if (isMulti) {\n                  const values = selectedOption\n                    ? (selectedOption as SelectOption[]).map(\n                        (option) => option.value\n                      )\n                    : [];\n                  field.onChange(values);\n                } else {\n                  field.onChange(\n                    selectedOption\n                      ? (selectedOption as SelectOption).value\n                      : null\n                  );\n                }\n              }}\n              classNamePrefix=\"react-select\"\n              styles={{\n                control: (base, state) => ({\n                  ...base,\n                  minHeight: 'auto',\n                  border: '1px solid #d1d5db',\n                  borderRadius: '0.375rem',\n                  boxShadow: 'none',\n                  transition: 'border-color 0.15s ease-in-out',\n                  '&:hover': {\n                    borderColor: '#d1d5db'\n                  },\n                  ...(state.isFocused && {\n                    borderColor: '#3b82f6',\n                    boxShadow: '0 0 0 1px rgb(59, 130, 246)'\n                  })\n                }),\n                input: (base) => ({\n                  ...base,\n                  '& input': {\n                    boxShadow: 'none !important',\n                    outline: 'none !important'\n                  }\n                })\n              }}\n            />\n\n            {fieldError && (\n              <p id={`${fieldId}-error`} className=\"field-error\">\n                {fieldError}\n              </p>\n            )}\n\n            {helperText && !fieldError && (\n              <p id={`${fieldId}-helper`} className=\"field-helper\">\n                {helperText}\n              </p>\n            )}\n          </Field>\n        );\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/ReactSelectField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport React from 'react';\nimport {\n  Controller,\n  FieldPath,\n  FieldValues,\n  RegisterOptions,\n  useFormContext\n} from 'react-hook-form';\nimport Select, { Props as ReactSelectProps } from 'react-select';\n\ninterface SelectOption {\n  value: any;\n  label: string;\n  [key: string]: any;\n}\n\ninterface ReactSelectFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<ReactSelectProps<SelectOption>, 'name' | 'value' | 'onChange'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  options: SelectOption[];\n  className?: string;\n  wrapperClassName?: string;\n  defaultValue?: any;\n}\n\nexport function ReactSelectField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  wrapperClassName = 'form-field',\n  helperText,\n  required,\n  validation,\n  options,\n  className,\n  isMulti = false,\n  defaultValue,\n  ...selectProps\n}: ReactSelectFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const validationRules = {\n    ...validation,\n    ...(required &&\n      !validation?.required && {\n        required: _('${field} is required', { field: label || name })\n      })\n  };\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n      id={`field-${name}`}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n\n      <Controller\n        name={name}\n        control={control}\n        rules={validationRules}\n        defaultValue={defaultValue}\n        render={({ field }) => (\n          <Select\n            {...field}\n            {...selectProps}\n            inputId={fieldId}\n            options={options}\n            isMulti={isMulti}\n            className={cn(className)}\n            value={\n              isMulti\n                ? options.filter((option) =>\n                    (field.value || defaultValue || [])?.includes(option.value)\n                  )\n                : options.find(\n                    (option) => option.value === (field.value ?? defaultValue)\n                  ) || null\n            }\n            onChange={(selectedOption) => {\n              if (isMulti) {\n                const values = selectedOption\n                  ? (selectedOption as SelectOption[]).map(\n                      (option) => option.value\n                    )\n                  : [];\n                field.onChange(values);\n              } else {\n                field.onChange(\n                  selectedOption ? (selectedOption as SelectOption).value : null\n                );\n              }\n            }}\n            classNamePrefix=\"react-select\"\n            aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n            aria-describedby={\n              fieldError !== undefined ? `${fieldId}-error` : undefined\n            }\n            classNames={{\n              control: (state) =>\n                cn(\n                  'min-h-auto border border-input rounded-md shadow-xs transition-[color,box-shadow]',\n                  state.isFocused && 'border-ring ring-[3px] ring-ring/50',\n                  fieldError &&\n                    'border-destructive ring-[3px] ring-destructive/20'\n                ),\n              input: () => 'outline-none shadow-none',\n              menu: () => 'bg-popover border border-input rounded-md shadow-md',\n              option: (state) =>\n                cn(\n                  'px-3 py-2 cursor-pointer',\n                  state.isSelected && 'bg-primary text-primary-foreground',\n                  state.isFocused && !state.isSelected && 'bg-accent'\n                )\n            }}\n          />\n        )}\n      />\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/SelectField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue\n} from '@components/common/ui/Select.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface SelectOption {\n  value: string | number;\n  label: string;\n  disabled?: boolean;\n}\n\ninterface SelectFieldProps<T extends FieldValues = FieldValues> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  options: SelectOption[];\n  placeholder?: string;\n  wrapperClassName?: string;\n  className?: string;\n  disabled?: boolean;\n  defaultValue?: string | number;\n  id?: string;\n  onChange?: (value: string | number) => void;\n}\n\nexport function SelectField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  helperText,\n  required,\n  validation,\n  options,\n  placeholder,\n  wrapperClassName,\n  className,\n  defaultValue,\n  disabled,\n  id,\n  onChange: onChangeCallback\n}: SelectFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = id || `field-${name}`;\n\n  const hasDefaultValue =\n    defaultValue !== undefined && defaultValue !== null && defaultValue !== '';\n\n  const validationRules = {\n    ...validation,\n    ...(required &&\n      !validation?.required && {\n        required: {\n          value: true,\n          message: _('${field} is required', { field: label || name })\n        },\n        validate: {\n          ...validation?.validate,\n          notEmpty: (value) => {\n            if (\n              required &&\n              (value === '' || value === null || value === undefined)\n            ) {\n              return _('${field} is required', { field: label || name });\n            }\n            return true;\n          }\n        }\n      })\n  };\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n      <Controller\n        name={name}\n        control={control}\n        rules={validationRules}\n        defaultValue={hasDefaultValue ? defaultValue : ('' as any)}\n        render={({ field }) => (\n          <Select\n            value={options.find((o) => o.value === field.value)}\n            onValueChange={(value) => {\n              const newValue = value?.value === '' ? '' : value?.value;\n              field.onChange(newValue);\n              if (onChangeCallback && value !== null) {\n                onChangeCallback(value.value);\n              }\n            }}\n            disabled={disabled}\n          >\n            <SelectTrigger\n              id={fieldId}\n              className={className}\n              aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n              aria-describedby={\n                fieldError !== undefined ? `${fieldId}-error` : undefined\n              }\n            >\n              <SelectValue>\n                {options.find((o) => String(o.value) === String(field.value))\n                  ?.label || placeholder}\n              </SelectValue>\n            </SelectTrigger>\n            <SelectContent>\n              {placeholder && (\n                <SelectItem value=\"\" disabled>\n                  {placeholder}\n                </SelectItem>\n              )}\n              {options.map((option) => (\n                <SelectItem\n                  key={option.value}\n                  value={option}\n                  disabled={option.disabled}\n                >\n                  {option.label}\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n        )}\n      />\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/TelField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface TelFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'type'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  wrapperClassName?: string;\n  prefixIcon?: React.ReactNode;\n  suffixIcon?: React.ReactNode;\n}\n\nexport function TelField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  wrapperClassName,\n  helperText,\n  required,\n  validation,\n  className,\n  defaultValue,\n  prefixIcon,\n  suffixIcon,\n  ...props\n}: TelFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const validationRules = {\n    ...validation,\n    ...(required &&\n      !validation?.required && {\n        required: _('${field} is required', { field: label || name })\n      })\n  };\n\n  const inputClassName = `${fieldError !== undefined ? 'error' : ''} ${\n    className || ''\n  } ${prefixIcon ? '!pl-10' : ''} ${suffixIcon ? '!pr-10' : ''}`.trim();\n\n  const renderInput = () => (\n    <Controller\n      name={name}\n      control={control}\n      defaultValue={defaultValue as any}\n      rules={validationRules}\n      render={({ field }) => (\n        <InputGroupInput\n          {...field}\n          value={field.value ?? ''}\n          id={fieldId}\n          type=\"tel\"\n          className={inputClassName}\n          aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n          aria-describedby={\n            fieldError !== undefined ? `${fieldId}-error` : undefined\n          }\n          {...props}\n        />\n      )}\n    />\n  );\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n      <InputGroup>\n        {renderInput()}\n        {prefixIcon && (\n          <InputGroupAddon align={'inline-start'}>{prefixIcon}</InputGroupAddon>\n        )}\n        {suffixIcon && (\n          <InputGroupAddon align={'inline-end'}>{suffixIcon}</InputGroupAddon>\n        )}\n      </InputGroup>\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/TextareaField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport { Textarea } from '@components/common/ui/Textarea.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface TextareaFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'name'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  wrapperClassName?: string;\n}\n\nexport function TextareaField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  helperText,\n  wrapperClassName,\n  required,\n  validation,\n  className,\n  rows = 4,\n  defaultValue,\n  ...props\n}: TextareaFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const validationRules = {\n    ...validation,\n    ...(required &&\n      !validation?.required && {\n        required: _('${field} is required', { field: label || name })\n      })\n  };\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n\n      <Controller\n        name={name}\n        control={control}\n        rules={validationRules}\n        defaultValue={defaultValue as any}\n        render={({ field }) => (\n          <Textarea\n            {...field}\n            id={fieldId}\n            rows={rows}\n            className={`${fieldError !== undefined ? 'error' : ''} ${\n              className || ''\n            }`}\n            aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n            aria-describedby={\n              fieldError !== undefined ? `${fieldId}-error` : undefined\n            }\n            {...props}\n          />\n        )}\n      />\n\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/TimeField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  InputGroup,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface TimeFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'type'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  wrapperClassName?: string;\n}\n\nexport function TimeField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  wrapperClassName,\n  helperText,\n  required,\n  validation,\n  className,\n  min,\n  max,\n  step,\n  ...props\n}: TimeFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const { valueAsNumber, valueAsDate, ...cleanValidation } = validation || {};\n  const validationRules = {\n    ...cleanValidation,\n    ...(required && {\n      required: _('${field} is required', { field: label || name })\n    }),\n    validate: {\n      ...validation?.validate,\n      minTime: (value) => {\n        if (!min || !value) return true;\n        return (\n          value >= min ||\n          _('Time must be after ${min}', { min: min.toString() })\n        );\n      },\n      maxTime: (value) => {\n        if (!max || !value) return true;\n        return (\n          value <= max ||\n          _('Time must be before ${max}', { max: max.toString() })\n        );\n      }\n    }\n  };\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n\n      <Controller\n        name={name}\n        control={control}\n        rules={validationRules}\n        render={({ field }) => (\n          <InputGroup>\n            <InputGroupInput\n              {...field}\n              value={field.value ?? ''}\n              id={fieldId}\n              type=\"time\"\n              min={min}\n              max={max}\n              step={step}\n              className={className}\n              aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n              aria-describedby={\n                fieldError !== undefined ? `${fieldId}-error` : undefined\n              }\n              {...props}\n            />\n          </InputGroup>\n        )}\n      />\n\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/ToggleField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport { Switch } from '@components/common/ui/Switch.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  Controller,\n  FieldPath,\n  FieldValues,\n  RegisterOptions,\n  useFormContext\n} from 'react-hook-form';\n\ninterface ToggleFieldProps<T extends FieldValues = FieldValues> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  wrapperClassName?: string;\n  disabled?: boolean;\n  defaultValue?: boolean | 0 | 1;\n  trueValue?: boolean | 1;\n  falseValue?: boolean | 0;\n  trueLabel?: string;\n  falseLabel?: string;\n  size?: 'sm' | 'default';\n  onChange?: (value: boolean | 0 | 1) => void;\n}\n\nexport function ToggleField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  helperText,\n  required,\n  validation,\n  wrapperClassName,\n  disabled = false,\n  defaultValue = false,\n  trueValue = true,\n  falseValue = false,\n  trueLabel = 'Yes',\n  falseLabel = 'No',\n  size = 'default',\n  onChange\n}: ToggleFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const validationRules = {\n    ...validation,\n    ...(required &&\n      !validation?.required && {\n        required: _('${field} is required', { field: label || name })\n      })\n  };\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          {label}\n          {required && <span className=\"text-destructive ml-1\">*</span>}\n          {helperText && <Tooltip content={helperText} position=\"top\" />}\n        </FieldLabel>\n      )}\n\n      <Controller\n        name={name}\n        control={control}\n        rules={validationRules}\n        defaultValue={defaultValue as any}\n        render={({ field }) => {\n          const isActive = field.value === trueValue;\n\n          return (\n            <div className=\"flex items-center gap-3\">\n              <Switch\n                id={fieldId}\n                size={size}\n                checked={isActive}\n                onCheckedChange={(checked) => {\n                  const newValue = checked ? trueValue : falseValue;\n                  field.onChange(newValue);\n                  onChange?.(newValue);\n                }}\n                disabled={disabled}\n                aria-invalid={fieldError ? 'true' : 'false'}\n                aria-describedby={fieldError ? `${fieldId}-error` : undefined}\n              />\n              <span className=\"text-sm text-muted-foreground\">\n                {isActive ? trueLabel : falseLabel}\n              </span>\n            </div>\n          );\n        }}\n      />\n\n      {fieldError && (\n        <FieldError id={`${fieldId}-error`}>{fieldError}</FieldError>\n      )}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/Tooltip.tsx",
    "content": "import React, { useState } from 'react';\n\ninterface TooltipProps {\n  content: string;\n  position?: 'top' | 'bottom' | 'left' | 'right';\n  className?: string;\n}\n\nexport function Tooltip({\n  content,\n  position = 'top',\n  className = ''\n}: TooltipProps) {\n  const [isVisible, setIsVisible] = useState(false);\n\n  const positionClasses = {\n    top: 'bottom-full left-1/2 transform -translate-x-1/2 mb-2',\n    bottom: 'top-full left-1/2 transform -translate-x-1/2 mt-2',\n    left: 'right-full top-1/2 transform -translate-y-1/2 mr-2',\n    right: 'left-full top-1/2 transform -translate-y-1/2 ml-2'\n  };\n\n  const arrowClasses = {\n    top: 'top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800',\n    bottom:\n      'bottom-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-b-4 border-l-transparent border-r-transparent border-b-gray-800',\n    left: 'left-full top-1/2 transform -translate-y-1/2 border-t-4 border-b-4 border-l-4 border-t-transparent border-b-transparent border-l-gray-800',\n    right:\n      'right-full top-1/2 transform -translate-y-1/2 border-t-4 border-b-4 border-r-4 border-t-transparent border-b-transparent border-r-gray-800'\n  };\n\n  return (\n    <div className={`relative inline-flex ${className}`}>\n      <button\n        type=\"button\"\n        className=\"inline-flex items-center justify-center w-4 h-4 ml-1 text-gray-400 hover:text-gray-600 transition-colors duration-200\"\n        onMouseEnter={() => setIsVisible(true)}\n        onMouseLeave={() => setIsVisible(false)}\n        onFocus={() => setIsVisible(true)}\n        onBlur={() => setIsVisible(false)}\n        tabIndex={-1}\n      >\n        <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n          <path\n            fillRule=\"evenodd\"\n            d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z\"\n            clipRule=\"evenodd\"\n          />\n        </svg>\n      </button>\n\n      {isVisible && (\n        <div\n          className={`absolute z-50 px-3 py-2 text-sm font-normal text-white bg-gray-800 rounded-lg shadow-lg transition-all duration-300 ease-in-out transform ${positionClasses[position]} opacity-100 scale-100`}\n          style={{ minWidth: '200px', maxWidth: '300px' }}\n        >\n          {content}\n          <div className={`absolute w-0 h-0 ${arrowClasses[position]}`}></div>\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/UrlField.tsx",
    "content": "import { Tooltip } from '@components/common/form/Tooltip.js';\nimport { getNestedError } from '@components/common/form/utils/getNestedError.js';\nimport { Field, FieldError, FieldLabel } from '@components/common/ui/Field.js';\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport {\n  useFormContext,\n  RegisterOptions,\n  FieldPath,\n  FieldValues,\n  Controller\n} from 'react-hook-form';\n\ninterface UrlFieldProps<T extends FieldValues = FieldValues>\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'type'> {\n  name: FieldPath<T>;\n  label?: string;\n  error?: string;\n  helperText?: string;\n  required?: boolean;\n  validation?: RegisterOptions<T>;\n  defaultValue?: string;\n  wrapperClassName?: string;\n  prefixIcon?: React.ReactNode;\n  suffixIcon?: React.ReactNode;\n}\n\nexport function UrlField<T extends FieldValues = FieldValues>({\n  name,\n  label,\n  error,\n  wrapperClassName,\n  helperText,\n  required,\n  validation,\n  defaultValue,\n  className,\n  prefixIcon,\n  suffixIcon,\n  ...props\n}: UrlFieldProps<T>) {\n  const {\n    control,\n    formState: { errors }\n  } = useFormContext<T>();\n\n  const fieldError = getNestedError(name, errors, error);\n  const fieldId = `field-${name}`;\n\n  const { valueAsNumber, valueAsDate, ...cleanValidation } = validation || {};\n  const validationRules = {\n    ...cleanValidation,\n    ...(required && {\n      required: _('${field} is required', { field: label || name })\n    }),\n    pattern: validation?.pattern || {\n      value: /^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$/,\n      message: _('Please enter a valid URL')\n    }\n  };\n\n  const inputClassName = `${fieldError !== undefined ? 'error' : ''} ${\n    className || ''\n  } ${prefixIcon ? '!pl-10' : ''} ${suffixIcon ? '!pr-10' : ''}`.trim();\n\n  const renderInput = () => (\n    <Controller\n      name={name}\n      control={control}\n      defaultValue={defaultValue as any}\n      rules={validationRules}\n      render={({ field }) => (\n        <InputGroupInput\n          {...field}\n          id={fieldId}\n          type=\"url\"\n          className={inputClassName}\n          aria-invalid={fieldError !== undefined ? 'true' : 'false'}\n          aria-describedby={\n            fieldError !== undefined ? `${fieldId}-error` : undefined\n          }\n          {...props}\n        />\n      )}\n    />\n  );\n\n  return (\n    <Field\n      data-invalid={fieldError ? 'true' : 'false'}\n      className={wrapperClassName}\n    >\n      {label && (\n        <FieldLabel htmlFor={fieldId}>\n          <>\n            {label}\n            {required && <span className=\"text-destructive\">*</span>}\n            {helperText && <Tooltip content={helperText} position=\"top\" />}\n          </>\n        </FieldLabel>\n      )}\n      <InputGroup>\n        {renderInput()}\n        {prefixIcon && (\n          <InputGroupAddon align={'inline-start'}>{prefixIcon}</InputGroupAddon>\n        )}\n        {suffixIcon && (\n          <InputGroupAddon align={'inline-end'}>{suffixIcon}</InputGroupAddon>\n        )}\n      </InputGroup>\n      {fieldError && <FieldError>{fieldError}</FieldError>}\n    </Field>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/editor/GetColumnClasses.tsx",
    "content": "const getColumnClasses = (size: number): string => {\n  switch (size) {\n    case 1:\n      return 'md:col-span-1';\n    case 2:\n      return 'md:col-span-2';\n    case 3:\n      return 'md:col-span-3';\n    default:\n      return 'md:col-span-1';\n  }\n};\n\nexport { getColumnClasses };\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/editor/GetRowClasses.tsx",
    "content": "const getRowClasses = (size: number): string => {\n  switch (size) {\n    case 1:\n      return 'md:grid-cols-1';\n    case 2:\n      return 'md:grid-cols-2';\n    case 3:\n      return 'md:grid-cols-3';\n    case 4:\n      return 'md:grid-cols-4';\n    case 5:\n      return 'md:grid-cols-5';\n    default:\n      return 'md:grid-cols-1';\n  }\n};\n\nexport { getRowClasses };\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/editor/RawToolWrapper.ts",
    "content": "/**\n * Wrapper for @editorjs/raw that fixes keyboard event handling issues\n * This ensures backspace and other keys work properly in the raw HTML block\n */\nexport class RawToolWrapper {\n  private rawTool: any;\n  private api: any;\n  private data: any;\n  private config: any;\n\n  constructor({ data, config, api, block }: any) {\n    this.data = data;\n    this.config = config;\n    this.api = api;\n\n    // We'll load the actual Raw tool dynamically\n    this.initializeRawTool({ data, config, api, block });\n  }\n\n  async initializeRawTool(params: any) {\n    const { default: RawTool } = await import('@editorjs/raw');\n    this.rawTool = new RawTool(params);\n  }\n\n  static get toolbox() {\n    return {\n      title: 'Raw HTML',\n      icon: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"16 18 22 12 16 6\"></polyline><polyline points=\"8 6 2 12 8 18\"></polyline></svg>'\n    };\n  }\n\n  render() {\n    const wrapper = document.createElement('div');\n    wrapper.classList.add('ce-rawtool');\n\n    const textarea = document.createElement('textarea');\n    textarea.classList.add('ce-rawtool__textarea');\n    textarea.placeholder = 'Enter HTML code';\n    textarea.value = this.data?.html || '';\n\n    // Prevent EditorJS from handling keyboard events inside textarea\n    textarea.addEventListener('keydown', (event) => {\n      event.stopPropagation();\n    });\n\n    textarea.addEventListener('keyup', (event) => {\n      event.stopPropagation();\n    });\n\n    textarea.addEventListener('paste', (event) => {\n      event.stopPropagation();\n    });\n\n    // Handle input changes\n    textarea.addEventListener('input', () => {\n      this.data = { html: textarea.value };\n    });\n\n    wrapper.appendChild(textarea);\n    return wrapper;\n  }\n\n  save(blockContent: HTMLElement) {\n    const textarea = blockContent.querySelector('textarea');\n    return {\n      html: textarea?.value || ''\n    };\n  }\n\n  static get sanitize() {\n    return {\n      html: true\n    };\n  }\n\n  static get isReadOnlySupported() {\n    return true;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/editor/RowTemplates.tsx",
    "content": "import React from 'react';\nimport { v4 as uuidv4 } from 'uuid';\nimport { getColumnClasses } from './GetColumnClasses.js';\nimport { getRowClasses } from './GetRowClasses.js';\n\nfunction RowTemplates({ addRow }: { addRow: (row: any) => void }) {\n  const templates = {\n    1: () => (\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"30\"\n        height=\"30\"\n        viewBox=\"0 0 48 48\"\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        fill=\"#949494\"\n      >\n        <path d=\"M0 10a2 2 0 0 1 2-2h44a2 2 0 0 1 2 2v28a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V10Z\" />\n      </svg>\n    ),\n    '1:1': () => (\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"30\"\n        height=\"30\"\n        viewBox=\"0 0 48 48\"\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        fill=\"#949494\"\n      >\n        <path d=\"M0 10a2 2 0 0 1 2-2h19a2 2 0 0 1 2 2v28a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V10Zm25 0a2 2 0 0 1 2-2h19a2 2 0 0 1 2 2v28a2 2 0 0 1-2 2H27a2 2 0 0 1-2-2V10Z\" />\n      </svg>\n    ),\n    '1:2': () => (\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"30\"\n        height=\"30\"\n        viewBox=\"0 0 48 48\"\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        fill=\"#949494\"\n      >\n        <path d=\"M0 10a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v28a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V10Zm17 0a2 2 0 0 1 2-2h27a2 2 0 0 1 2 2v28a2 2 0 0 1-2 2H19a2 2 0 0 1-2-2V10Z\" />\n      </svg>\n    ),\n    '2:1': () => (\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"30\"\n        height=\"30\"\n        viewBox=\"0 0 48 48\"\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        fill=\"#949494\"\n      >\n        <path d=\"M0 10a2 2 0 0 1 2-2h27a2 2 0 0 1 2 2v28a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V10Zm33 0a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v28a2 2 0 0 1-2 2H35a2 2 0 0 1-2-2V10Z\" />\n      </svg>\n    ),\n    '2:3': () => (\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"30\"\n        height=\"30\"\n        viewBox=\"0 0 48 48\"\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        fill=\"#949494\"\n      >\n        <rect x=\"0\" y=\"8\" width=\"18.4\" height=\"32\" rx=\"2\" ry=\"2\" />\n        <rect x=\"21.6\" y=\"8\" width=\"24\" height=\"32\" rx=\"2\" ry=\"2\" />\n      </svg>\n    ),\n    '3:2': () => (\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"30\"\n        height=\"30\"\n        viewBox=\"0 0 48 48\"\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        fill=\"#949494\"\n      >\n        <rect x=\"0\" y=\"8\" width=\"24\" height=\"32\" rx=\"2\" ry=\"2\" />\n        <rect x=\"27.2\" y=\"8\" width=\"18.4\" height=\"32\" rx=\"2\" ry=\"2\" />\n      </svg>\n    ),\n    '1:1:1': () => (\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"30\"\n        height=\"30\"\n        viewBox=\"0 0 48 48\"\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        fill=\"#949494\"\n      >\n        <path d=\"M0 10a2 2 0 0 1 2-2h10.531c1.105 0 1.969.895 1.969 2v28c0 1.105-.864 2-1.969 2H2a2 2 0 0 1-2-2V10Zm16.5 0c0-1.105.864-2 1.969-2H29.53c1.105 0 1.969.895 1.969 2v28c0 1.105-.864 2-1.969 2H18.47c-1.105 0-1.969-.895-1.969-2V10Zm17 0c0-1.105.864-2 1.969-2H46a2 2 0 0 1 2 2v28a2 2 0 0 1-2 2H35.469c-1.105 0-1.969-.895-1.969-2V10Z\" />\n      </svg>\n    ),\n    '1:2:1': () => (\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"30\"\n        height=\"30\"\n        viewBox=\"0 0 48 48\"\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        fill=\"#949494\"\n      >\n        <path d=\"M0 10a2 2 0 0 1 2-2h7.531c1.105 0 1.969.895 1.969 2v28c0 1.105-.864 2-1.969 2H2a2 2 0 0 1-2-2V10Zm13.5 0c0-1.105.864-2 1.969-2H32.53c1.105 0 1.969.895 1.969 2v28c0 1.105-.864 2-1.969 2H15.47c-1.105 0-1.969-.895-1.969-2V10Zm23 0c0-1.105.864-2 1.969-2H46a2 2 0 0 1 2 2v28a2 2 0 0 1-2 2h-7.531c-1.105 0-1.969-.895-1.969-2V10Z\" />\n      </svg>\n    )\n  };\n  return (\n    <div className=\"row-templates flex justify-center gap-7 px-3\">\n      {Object.keys(templates).map((key) => (\n        <a\n          key={key}\n          href=\"#\"\n          onClick={(e) => {\n            e.preventDefault();\n            const split = key.split(':').map((val) => parseInt(val, 10));\n            const sum = split.reduce((acc, val) => acc + val, 0);\n            const rowClassName = getRowClasses(sum);\n            const columns = split.map((size) => {\n              const columnClassName = getColumnClasses(size);\n              return {\n                size,\n                className: columnClassName,\n                id: `c__${uuidv4()}`\n              };\n            });\n            addRow({\n              id: `r__${uuidv4()}`,\n              editSetting: true,\n              columns,\n              size: sum,\n              className: rowClassName\n            });\n          }}\n        >\n          {templates[key]()}\n        </a>\n      ))}\n    </div>\n  );\n}\n\nexport { RowTemplates };\n"
  },
  {
    "path": "packages/evershop/src/components/common/form/utils/getNestedError.ts",
    "content": "/**\n * Helper function to get nested error from react-hook-form errors object\n * Handles both simple field names and nested array field names using dot notation (e.g., \"attributes.0.value\")\n * Also supports legacy bracket notation for backward compatibility\n */\nexport const getNestedError = (\n  name: string,\n  errors: any,\n  error?: string\n): string | undefined => {\n  if (error) return error;\n\n  if (!name.includes('.') && !name.includes('[')) {\n    return errors[name]?.message;\n  }\n\n  let parts: string[];\n\n  if (name.includes('[')) {\n    parts = name.split(/[\\[\\]]+/).filter(Boolean);\n  } else {\n    parts = name.split('.');\n  }\n\n  let current = errors;\n\n  for (const part of parts) {\n    if (current === null || current === undefined) return undefined;\n\n    const index = parseInt(part);\n    if (!isNaN(index)) {\n      current = current[index];\n    } else {\n      current = current[part];\n    }\n  }\n\n  return current?.message;\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/index.tsx",
    "content": "import Area from './Area.jsx';\nimport { HydrateAdmin } from './react/client/HydrateAdmin.jsx';\nimport { HydrateFrontStore } from './react/client/HydrateFrontStore.jsx';\nimport { renderHtml } from './react/server/render.jsx';\nexport { Area };\nexport { HydrateFrontStore };\nexport { HydrateAdmin };\nexport { renderHtml };\nexport default Area;\n"
  },
  {
    "path": "packages/evershop/src/components/common/locale/CountryOption.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\n\n\nfunction CountryOptions(props) {\n  const { countries, children } = props;\n\n  const options = [\n    { value: 'AF', text: 'Afghanistan' },\n    { value: 'AL', text: 'Albania' },\n    { value: 'DZ', text: 'Algeria' },\n    { value: 'AS', text: 'American Samoa' },\n    { value: 'AD', text: 'Andorra' },\n    { value: 'AO', text: 'Angola' },\n    { value: 'AI', text: 'Anguilla' },\n    { value: 'AQ', text: 'Antarctica' },\n    { value: 'AG', text: 'Antigua and Barbuda' },\n    { value: 'AR', text: 'Argentina' },\n    { value: 'AM', text: 'Armenia' },\n    { value: 'AW', text: 'Aruba' },\n    { value: 'AU', text: 'Australia' },\n    { value: 'AT', text: 'Austria' },\n    { value: 'AZ', text: 'Azerbaijan' },\n    { value: 'BS', text: 'Bahamas' },\n    { value: 'BH', text: 'Bahrain' },\n    { value: 'BD', text: 'Bangladesh' },\n    { value: 'BB', text: 'Barbados' },\n    { value: 'BY', text: 'Belarus' },\n    { value: 'BE', text: 'Belgium' },\n    { value: 'BZ', text: 'Belize' },\n    { value: 'BJ', text: 'Benin' },\n    { value: 'BM', text: 'Bermuda' },\n    { value: 'BT', text: 'Bhutan' },\n    { value: 'BO', text: 'Bolivia' },\n    { value: 'BA', text: 'Bosnia and Herzegovina' },\n    { value: 'BW', text: 'Botswana' },\n    { value: 'BV', text: 'Bouvet Island' },\n    { value: 'BR', text: 'Brazil' },\n    { value: 'IO', text: 'British Indian Ocean Territory' },\n    { value: 'VG', text: 'British Virgin Islands' },\n    { value: 'BN', text: 'Brunei' },\n    { value: 'BG', text: 'Bulgaria' },\n    { value: 'BF', text: 'Burkina Faso' },\n    { value: 'BI', text: 'Burundi' },\n    { value: 'KH', text: 'Cambodia' },\n    { value: 'CM', text: 'Cameroon' },\n    { value: 'CA', text: 'Canada' },\n    { value: 'CV', text: 'Cape Verde' },\n    { value: 'KY', text: 'Cayman Islands' },\n    { value: 'CF', text: 'Central African Republic' },\n    { value: 'TD', text: 'Chad' },\n    { value: 'CL', text: 'Chile' },\n    { value: 'CN', text: 'China' },\n    { value: 'CX', text: 'Christmas Island' },\n    { value: 'CC', text: 'Cocos [Keeling] Islands' },\n    { value: 'CO', text: 'Colombia' },\n    { value: 'KM', text: 'Comoros' },\n    { value: 'CG', text: 'Congo - Brazzaville' },\n    { value: 'CD', text: 'Congo - Kinshasa' },\n    { value: 'CK', text: 'Cook Islands' },\n    { value: 'CR', text: 'Costa Rica' },\n    { value: 'HR', text: 'Croatia' },\n    { value: 'CU', text: 'Cuba' },\n    { value: 'CY', text: 'Cyprus' },\n    { value: 'CZ', text: 'Czech Republic' },\n    { value: 'CI', text: 'Côte d’Ivoire' },\n    { value: 'DK', text: 'Denmark' },\n    { value: 'DJ', text: 'Djibouti' },\n    { value: 'DM', text: 'Dominica' },\n    { value: 'DO', text: 'Dominican Republic' },\n    { value: 'EC', text: 'Ecuador' },\n    { value: 'EG', text: 'Egypt' },\n    { value: 'SV', text: 'El Salvador' },\n    { value: 'GQ', text: 'Equatorial Guinea' },\n    { value: 'ER', text: 'Eritrea' },\n    { value: 'EE', text: 'Estonia' },\n    { value: 'ET', text: 'Ethiopia' },\n    { value: 'FK', text: 'Falkland Islands' },\n    { value: 'FO', text: 'Faroe Islands' },\n    { value: 'FJ', text: 'Fiji' },\n    { value: 'FI', text: 'Finland' },\n    { value: 'FR', text: 'France' },\n    { value: 'GF', text: 'French Guiana' },\n    { value: 'PF', text: 'French Polynesia' },\n    { value: 'TF', text: 'French Southern Territories' },\n    { value: 'GA', text: 'Gabon' },\n    { value: 'GM', text: 'Gambia' },\n    { value: 'GE', text: 'Georgia' },\n    { value: 'DE', text: 'Germany' },\n    { value: 'GH', text: 'Ghana' },\n    { value: 'GI', text: 'Gibraltar' },\n    { value: 'GR', text: 'Greece' },\n    { value: 'GL', text: 'Greenland' },\n    { value: 'GD', text: 'Grenada' },\n    { value: 'GP', text: 'Guadeloupe' },\n    { value: 'GU', text: 'Guam' },\n    { value: 'GT', text: 'Guatemala' },\n    { value: 'GG', text: 'Guernsey' },\n    { value: 'GN', text: 'Guinea' },\n    { value: 'GW', text: 'Guinea-Bissau' },\n    { value: 'GY', text: 'Guyana' },\n    { value: 'HT', text: 'Haiti' },\n    { value: 'HM', text: 'Heard Island and McDonald Islands' },\n    { value: 'HN', text: 'Honduras' },\n    { value: 'HK', text: 'Hong Kong SAR China' },\n    { value: 'HU', text: 'Hungary' },\n    { value: 'IS', text: 'Iceland' },\n    { value: 'IN', text: 'India' },\n    { value: 'ID', text: 'Indonesia' },\n    { value: 'IR', text: 'Iran' },\n    { value: 'IQ', text: 'Iraq' },\n    { value: 'IE', text: 'Ireland' },\n    { value: 'IM', text: 'Isle of Man' },\n    { value: 'IL', text: 'Israel' },\n    { value: 'IT', text: 'Italy' },\n    { value: 'JM', text: 'Jamaica' },\n    { value: 'JP', text: 'Japan' },\n    { value: 'JE', text: 'Jersey' },\n    { value: 'JO', text: 'Jordan' },\n    { value: 'KZ', text: 'Kazakhstan' },\n    { value: 'KE', text: 'Kenya' },\n    { value: 'KI', text: 'Kiribati' },\n    { value: 'KW', text: 'Kuwait' },\n    { value: 'KG', text: 'Kyrgyzstan' },\n    { value: 'LA', text: 'Laos' },\n    { value: 'LV', text: 'Latvia' },\n    { value: 'LB', text: 'Lebanon' },\n    { value: 'LS', text: 'Lesotho' },\n    { value: 'LR', text: 'Liberia' },\n    { value: 'LY', text: 'Libya' },\n    { value: 'LI', text: 'Liechtenstein' },\n    { value: 'LT', text: 'Lithuania' },\n    { value: 'LU', text: 'Luxembourg' },\n    { value: 'MO', text: 'Macau SAR China' },\n    { value: 'MK', text: 'Macedonia' },\n    { value: 'MG', text: 'Madagascar' },\n    { value: 'MW', text: 'Malawi' },\n    { value: 'MY', text: 'Malaysia' },\n    { value: 'MV', text: 'Maldives' },\n    { value: 'ML', text: 'Mali' },\n    { value: 'MT', text: 'Malta' },\n    { value: 'MH', text: 'Marshall Islands' },\n    { value: 'MQ', text: 'Martinique' },\n    { value: 'MR', text: 'Mauritania' },\n    { value: 'MU', text: 'Mauritius' },\n    { value: 'YT', text: 'Mayotte' },\n    { value: 'MX', text: 'Mexico' },\n    { value: 'FM', text: 'Micronesia' },\n    { value: 'MD', text: 'Moldova' },\n    { value: 'MC', text: 'Monaco' },\n    { value: 'MN', text: 'Mongolia' },\n    { value: 'ME', text: 'Montenegro' },\n    { value: 'MS', text: 'Montserrat' },\n    { value: 'MA', text: 'Morocco' },\n    { value: 'MZ', text: 'Mozambique' },\n    { value: 'MM', text: 'Myanmar [Burma]' },\n    { value: 'NA', text: 'Namibia' },\n    { value: 'NR', text: 'Nauru' },\n    { value: 'NP', text: 'Nepal' },\n    { value: 'NL', text: 'Netherlands' },\n    { value: 'AN', text: 'Netherlands Antilles' },\n    { value: 'NC', text: 'New Caledonia' },\n    { value: 'NZ', text: 'New Zealand' },\n    { value: 'NI', text: 'Nicaragua' },\n    { value: 'NE', text: 'Niger' },\n    { value: 'NG', text: 'Nigeria' },\n    { value: 'NU', text: 'Niue' },\n    { value: 'NF', text: 'Norfolk Island' },\n    { value: 'KP', text: 'North Korea' },\n    { value: 'MP', text: 'Northern Mariana Islands' },\n    { value: 'NO', text: 'Norway' },\n    { value: 'OM', text: 'Oman' },\n    { value: 'PK', text: 'Pakistan' },\n    { value: 'PW', text: 'Palau' },\n    { value: 'PS', text: 'Palestinian Territories' },\n    { value: 'PA', text: 'Panama' },\n    { value: 'PG', text: 'Papua New Guinea' },\n    { value: 'PY', text: 'Paraguay' },\n    { value: 'PE', text: 'Peru' },\n    { value: 'PH', text: 'Philippines' },\n    { value: 'PN', text: 'Pitcairn Islands' },\n    { value: 'PL', text: 'Poland' },\n    { value: 'PT', text: 'Portugal' },\n    { value: 'PR', text: 'Puerto Rico' },\n    { value: 'QA', text: 'Qatar' },\n    { value: 'RO', text: 'Romania' },\n    { value: 'RU', text: 'Russia' },\n    { value: 'RW', text: 'Rwanda' },\n    { value: 'RE', text: 'Réunion' },\n    { value: 'BL', text: 'Saint Barthélemy' },\n    { value: 'SH', text: 'Saint Helena' },\n    { value: 'KN', text: 'Saint Kitts and Nevis' },\n    { value: 'LC', text: 'Saint Lucia' },\n    { value: 'MF', text: 'Saint Martin' },\n    { value: 'PM', text: 'Saint Pierre and Miquelon' },\n    { value: 'VC', text: 'Saint Vincent and the Grenadines' },\n    { value: 'WS', text: 'Samoa' },\n    { value: 'SM', text: 'San Marino' },\n    { value: 'SA', text: 'Saudi Arabia' },\n    { value: 'SN', text: 'Senegal' },\n    { value: 'RS', text: 'Serbia' },\n    { value: 'SC', text: 'Seychelles' },\n    { value: 'SL', text: 'Sierra Leone' },\n    { value: 'SG', text: 'Singapore' },\n    { value: 'SK', text: 'Slovakia' },\n    { value: 'SI', text: 'Slovenia' },\n    { value: 'SB', text: 'Solomon Islands' },\n    { value: 'SO', text: 'Somalia' },\n    { value: 'ZA', text: 'South Africa' },\n    { value: 'GS', text: 'South Georgia and the South Sandwich Islands' },\n    { value: 'KR', text: 'South Korea' },\n    { value: 'ES', text: 'Spain' },\n    { value: 'LK', text: 'Sri Lanka' },\n    { value: 'SD', text: 'Sudan' },\n    { value: 'SR', text: 'Suriname' },\n    { value: 'SJ', text: 'Svalbard and Jan Mayen' },\n    { value: 'SZ', text: 'Swaziland' },\n    { value: 'SE', text: 'Sweden' },\n    { value: 'CH', text: 'Switzerland' },\n    { value: 'SY', text: 'Syria' },\n    { value: 'ST', text: 'São Tomé and Príncipe' },\n    { value: 'TW', text: 'Taiwan' },\n    { value: 'TJ', text: 'Tajikistan' },\n    { value: 'TZ', text: 'Tanzania' },\n    { value: 'TH', text: 'Thailand' },\n    { value: 'TL', text: 'Timor-Leste' },\n    { value: 'TG', text: 'Togo' },\n    { value: 'TK', text: 'Tokelau' },\n    { value: 'TO', text: 'Tonga' },\n    { value: 'TT', text: 'Trinidad and Tobago' },\n    { value: 'TN', text: 'Tunisia' },\n    { value: 'TR', text: 'Turkey' },\n    { value: 'TM', text: 'Turkmenistan' },\n    { value: 'TC', text: 'Turks and Caicos Islands' },\n    { value: 'TV', text: 'Tuvalu' },\n    { value: 'UM', text: 'U.S. Minor Outlying Islands' },\n    { value: 'VI', text: 'U.S. Virgin Islands' },\n    { value: 'UG', text: 'Uganda' },\n    { value: 'UA', text: 'Ukraine' },\n    { value: 'AE', text: 'United Arab Emirates' },\n    { value: 'GB', text: 'United Kingdom' },\n    { value: 'US', text: 'United States' },\n    { value: 'UY', text: 'Uruguay' },\n    { value: 'UZ', text: 'Uzbekistan' },\n    { value: 'VU', text: 'Vanuatu' },\n    { value: 'VA', text: 'Vatican City' },\n    { value: 'VE', text: 'Venezuela' },\n    { value: 'VN', text: 'Vietnam' },\n    { value: 'WF', text: 'Wallis and Futuna' },\n    { value: 'EH', text: 'Western Sahara' },\n    { value: 'YE', text: 'Yemen' },\n    { value: 'ZM', text: 'Zambia' },\n    { value: 'ZW', text: 'Zimbabwe' },\n    { value: 'AX', text: 'Åland Islands' }\n  ].filter((c) => {\n    if (countries) {\n      return countries.indexOf(c.value) !== -1;\n    } else {\n      return true;\n    }\n  });\n  const childrenWithProps = React.Children.map(children, (child) =>\n    React.cloneElement(child, { options, ...props })\n  );\n\n  return <div>{childrenWithProps}</div>;\n}\n\nCountryOptions.propTypes = {\n  children: PropTypes.node.isRequired,\n  countries: PropTypes.arrayOf(PropTypes.string)\n};\n\nCountryOptions.defaultProps = {\n  countries: []\n};\n\nexport { CountryOptions };\n"
  },
  {
    "path": "packages/evershop/src/components/common/locale/CurrencyOption.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\n\n\nfunction CurrencyOptions(props) {\n  const { currencies, children } = props;\n\n  const options = [\n    { value: 'AFN', text: 'Afghan Afghani' },\n    { value: 'ALL', text: 'Albanian Lek' },\n    { value: 'DZD', text: 'Algerian Dinar' },\n    { value: 'AOA', text: 'Angolan Kwanza' },\n    { value: 'ARS', text: 'Argentine Peso' },\n    { value: 'AMD', text: 'Armenian Dram' },\n    { value: 'AWG', text: 'Aruban Florin' },\n    { value: 'AUD', text: 'Australian Dollar' },\n    { value: 'AZN', text: 'Azerbaijani Manat' },\n    { value: 'AZM', text: 'Azerbaijani Manat (1993-2006)' },\n    { value: 'BSD', text: 'Bahamian Dollar' },\n    { value: 'BHD', text: 'Bahraini Dinar' },\n    { value: 'BDT', text: 'Bangladeshi Taka' },\n    { value: 'BBD', text: 'Barbadian Dollar' },\n    { value: 'BYR', text: 'Belarusian Ruble' },\n    { value: 'BZD', text: 'Belize Dollar' },\n    { value: 'BMD', text: 'Bermudan Dollar' },\n    { value: 'BTN', text: 'Bhutanese Ngultrum' },\n    { value: 'BOB', text: 'Bolivian Boliviano' },\n    { value: 'BAM', text: 'Bosnia-Herzegovina Convertible Mark' },\n    { value: 'BWP', text: 'Botswanan Pula' },\n    { value: 'BRL', text: 'Brazilian Real' },\n    { value: 'GBP', text: 'British Pound Sterling' },\n    { value: 'BND', text: 'Brunei Dollar' },\n    { value: 'BGN', text: 'Bulgarian Lev' },\n    { value: 'BUK', text: 'Burmese Kyat' },\n    { value: 'BIF', text: 'Burundian Franc' },\n    { value: 'XOF', text: 'CFA Franc BCEAO' },\n    { value: 'XPF', text: 'CFP Franc' },\n    { value: 'KHR', text: 'Cambodian Riel' },\n    { value: 'CAD', text: 'Canadian Dollar' },\n    { value: 'CVE', text: 'Cape Verdean Escudo' },\n    { value: 'KYD', text: 'Cayman Islands Dollar' },\n    { value: 'CLP', text: 'Chilean Peso' },\n    { value: 'CNY', text: 'Chinese Yuan Renminbi' },\n    { value: 'COP', text: 'Colombian Peso' },\n    { value: 'KMF', text: 'Comorian Franc' },\n    { value: 'CDF', text: 'Congolese Franc' },\n    { value: 'CRC', text: 'Costa Rican Colón' },\n    { value: 'HRK', text: 'Croatian Kuna' },\n    { value: 'CUP', text: 'Cuban Peso' },\n    { value: 'CZK', text: 'Czech Republic Koruna' },\n    { value: 'DKK', text: 'Danish Krone' },\n    { value: 'DJF', text: 'Djiboutian Franc' },\n    { value: 'DOP', text: 'Dominican Peso' },\n    { value: 'XCD', text: 'East Caribbean Dollar' },\n    { value: 'EGP', text: 'Egyptian Pound' },\n    { value: 'GQE', text: 'Equatorial Guinean Ekwele' },\n    { value: 'ERN', text: 'Eritrean Nakfa' },\n    { value: 'EEK', text: 'Estonian Kroon' },\n    { value: 'ETB', text: 'Ethiopian Birr' },\n    { value: 'EUR', text: 'Euro' },\n    { value: 'FKP', text: 'Falkland Islands Pound' },\n    { value: 'FJD', text: 'Fijian Dollar' },\n    { value: 'GMD', text: 'Gambian Dalasi' },\n    { value: 'GEK', text: 'Georgian Kupon Larit' },\n    { value: 'GEL', text: 'Georgian Lari' },\n    { value: 'GHS', text: 'Ghanaian Cedi' },\n    { value: 'GIP', text: 'Gibraltar Pound' },\n    { value: 'GTQ', text: 'Guatemalan Quetzal' },\n    { value: 'GNF', text: 'Guinean Franc' },\n    { value: 'GYD', text: 'Guyanaese Dollar' },\n    { value: 'HTG', text: 'Haitian Gourde' },\n    { value: 'HNL', text: 'Honduran Lempira' },\n    { value: 'HKD', text: 'Hong Kong Dollar' },\n    { value: 'HUF', text: 'Hungarian Forint' },\n    { value: 'ISK', text: 'Icelandic Króna' },\n    { value: 'INR', text: 'Indian Rupee' },\n    { value: 'IDR', text: 'Indonesian Rupiah' },\n    { value: 'IRR', text: 'Iranian Rial' },\n    { value: 'IQD', text: 'Iraqi Dinar' },\n    { value: 'ILS', text: 'Israeli New Sheqel' },\n    { value: 'JMD', text: 'Jamaican Dollar' },\n    { value: 'JPY', text: 'Japanese Yen' },\n    { value: 'JOD', text: 'Jordanian Dinar' },\n    { value: 'KZT', text: 'Kazakhstan Tenge' },\n    { value: 'KES', text: 'Kenyan Shilling' },\n    { value: 'KWD', text: 'Kuwaiti Dinar' },\n    { value: 'KGS', text: 'Kyrgystani Som' },\n    { value: 'LAK', text: 'Laotian Kip' },\n    { value: 'LVL', text: 'Latvian Lats' },\n    { value: 'LBP', text: 'Lebanese Pound' },\n    { value: 'LSL', text: 'Lesotho Loti' },\n    { value: 'LRD', text: 'Liberian Dollar' },\n    { value: 'LYD', text: 'Libyan Dinar' },\n    { value: 'LTL', text: 'Lithuanian Litas' },\n    { value: 'MOP', text: 'Macanese Pataca' },\n    { value: 'MKD', text: 'Macedonian Denar' },\n    { value: 'MGA', text: 'Malagasy Ariary' },\n    { value: 'MWK', text: 'Malawian Kwacha' },\n    { value: 'MYR', text: 'Malaysian Ringgit' },\n    { value: 'MVR', text: 'Maldivian Rufiyaa' },\n    { value: 'MRO', text: 'Mauritanian Ouguiya' },\n    { value: 'MUR', text: 'Mauritian Rupee' },\n    { value: 'MXN', text: 'Mexican Peso' },\n    { value: 'MDL', text: 'Moldovan Leu' },\n    { value: 'MNT', text: 'Mongolian Tugrik' },\n    { value: 'MAD', text: 'Moroccan Dirham' },\n    { value: 'MZN', text: 'Mozambican Metical' },\n    { value: 'MMK', text: 'Myanma Kyat' },\n    { value: 'NAD', text: 'Namibian Dollar' },\n    { value: 'NPR', text: 'Nepalese Rupee' },\n    { value: 'ANG', text: 'Netherlands Antillean Guilder' },\n    { value: 'TWD', text: 'New Taiwan Dollar' },\n    { value: 'NZD', text: 'New Zealand Dollar' },\n    { value: 'NIC', text: 'Nicaraguan Cordoba' },\n    { value: 'NGN', text: 'Nigerian Naira' },\n    { value: 'KPW', text: 'North Korean Won' },\n    { value: 'NOK', text: 'Norwegian Krone' },\n    { value: 'ROL', text: 'Old Romanian Leu' },\n    { value: 'TRL', text: 'Old Turkish Lira' },\n    { value: 'OMR', text: 'Omani Rial' },\n    { value: 'PKR', text: 'Pakistani Rupee' },\n    { value: 'PAB', text: 'Panamanian Balboa' },\n    { value: 'PGK', text: 'Papua New Guinean Kina' },\n    { value: 'PYG', text: 'Paraguayan Guarani' },\n    { value: 'PEN', text: 'Peruvian Nuevo Sol' },\n    { value: 'PHP', text: 'Philippine Peso' },\n    { value: 'PLN', text: 'Polish Zloty' },\n    { value: 'QAR', text: 'Qatari Rial' },\n    { value: 'RHD', text: 'Rhodesian Dollar' },\n    { value: 'RON', text: 'Romanian Leu' },\n    { value: 'RUB', text: 'Russian Ruble' },\n    { value: 'RWF', text: 'Rwandan Franc' },\n    { value: 'SHP', text: 'Saint Helena Pound' },\n    { value: 'SVC', text: 'Salvadoran Colón' },\n    { value: 'WST', text: 'Samoan Tala' },\n    { value: 'SAR', text: 'Saudi Riyal' },\n    { value: 'RSD', text: 'Serbian Dinar' },\n    { value: 'SCR', text: 'Seychellois Rupee' },\n    { value: 'SLL', text: 'Sierra Leonean Leone' },\n    { value: 'SGD', text: 'Singapore Dollar' },\n    { value: 'SKK', text: 'Slovak Koruna' },\n    { value: 'SBD', text: 'Solomon Islands Dollar' },\n    { value: 'SOS', text: 'Somali Shilling' },\n    { value: 'ZAR', text: 'South African Rand' },\n    { value: 'KRW', text: 'South Korean Won' },\n    { value: 'LKR', text: 'Sri Lanka Rupee' },\n    { value: 'SDG', text: 'Sudanese Pound' },\n    { value: 'SRD', text: 'Surinamese Dollar' },\n    { value: 'SZL', text: 'Swazi Lilangeni' },\n    { value: 'SEK', text: 'Swedish Krona' },\n    { value: 'CHF', text: 'Swiss Franc' },\n    { value: 'SYP', text: 'Syrian Pound' },\n    { value: 'STD', text: 'São Tomé and Príncipe Dobra' },\n    { value: 'TJS', text: 'Tajikistani Somoni' },\n    { value: 'TZS', text: 'Tanzanian Shilling' },\n    { value: 'THB', text: 'Thai Baht' },\n    { value: 'TOP', text: 'Tongan Paʻanga' },\n    { value: 'TTD', text: 'Trinidad and Tobago Dollar' },\n    { value: 'TND', text: 'Tunisian Dinar' },\n    { value: 'TRY', text: 'Turkish Lira' },\n    { value: 'TMM', text: 'Turkmenistani Manat' },\n    { value: 'USD', text: 'US Dollar' },\n    { value: 'UGX', text: 'Ugandan Shilling' },\n    { value: 'UAH', text: 'Ukrainian Hryvnia' },\n    { value: 'AED', text: 'United Arab Emirates Dirham' },\n    { value: 'UYU', text: 'Uruguayan Peso' },\n    { value: 'UZS', text: 'Uzbekistan Som' },\n    { value: 'VUV', text: 'Vanuatu Vatu' },\n    { value: 'VEB', text: 'Venezuelan Bolívar' },\n    { value: 'VEF', text: 'Venezuelan Bolívar Fuerte' },\n    { value: 'VND', text: 'Vietnamese Dong' },\n    { value: 'CHE', text: 'WIR Euro' },\n    { value: 'CHW', text: 'WIR Franc' },\n    { value: 'YER', text: 'Yemeni Rial' },\n    { value: 'ZMK', text: 'Zambian Kwacha' },\n    { value: 'ZWD', text: 'Zimbabwean Dollar' }\n  ].filter((c) => {\n    if (currencies) return currencies.indexOf(c.value) !== -1;\n    else return true;\n  });\n  const childrenWithProps = React.Children.map(children, (child) =>\n    React.cloneElement(child, { options, ...props })\n  );\n\n  return <div>{childrenWithProps}</div>;\n}\n\nCurrencyOptions.propTypes = {\n  children: PropTypes.node.isRequired,\n  currencies: PropTypes.arrayOf(PropTypes.string)\n};\n\nCurrencyOptions.defaultProps = {\n  currencies: []\n};\n\nexport { CurrencyOptions };\n"
  },
  {
    "path": "packages/evershop/src/components/common/locale/LanguageOption.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\n\n\nfunction LanguageOptions(props) {\n  const { languages, children } = props;\n\n  const options = [\n    { value: 'aa', text: 'Afar' },\n    { value: 'ab', text: 'Abkhazian' },\n    { value: 'ae', text: 'Avestan' },\n    { value: 'af', text: 'Afrikaans' },\n    { value: 'ak', text: 'Akan' },\n    { value: 'am', text: 'Amharic' },\n    { value: 'an', text: 'Aragonese' },\n    { value: 'ar', text: 'Arabic' },\n    { value: 'as', text: 'Assamese' },\n    { value: 'av', text: 'Avaric' },\n    { value: 'ay', text: 'Aymara' },\n    { value: 'az', text: 'Azerbaijani' },\n    { value: 'ba', text: 'Bashkir' },\n    { value: 'be', text: 'Belarusian' },\n    { value: 'bg', text: 'Bulgarian' },\n    { value: 'bh', text: 'Bihari languages' },\n    { value: 'bi', text: 'Bislama' },\n    { value: 'bm', text: 'Bambara' },\n    { value: 'bn', text: 'Bengali' },\n    { value: 'bo', text: 'Tibetan' },\n    { value: 'br', text: 'Breton' },\n    { value: 'bs', text: 'Bosnian' },\n    { value: 'ca', text: 'Catalan; Valencian' },\n    { value: 'ce', text: 'Chechen' },\n    { value: 'ch', text: 'Chamorro' },\n    { value: 'co', text: 'Corsican' },\n    { value: 'cr', text: 'Cree' },\n    { value: 'cs', text: 'Czech' },\n    {\n      value: 'cu',\n      text: 'Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic'\n    },\n    { value: 'cv', text: 'Chuvash' },\n    { value: 'cy', text: 'Welsh' },\n    { value: 'da', text: 'Danish' },\n    { value: 'de', text: 'German' },\n    { value: 'dv', text: 'Divehi; Dhivehi; Maldivian' },\n    { value: 'dz', text: 'Dzongkha' },\n    { value: 'ee', text: 'Ewe' },\n    { value: 'el', text: 'Greek, Modern (1453-)' },\n    { value: 'en', text: 'English' },\n    { value: 'eo', text: 'Esperanto' },\n    { value: 'es', text: 'Spanish; Castilian' },\n    { value: 'et', text: 'Estonian' },\n    { value: 'eu', text: 'Basque' },\n    { value: 'fa', text: 'Persian' },\n    { value: 'ff', text: 'Fulah' },\n    { value: 'fi', text: 'Finnish' },\n    { value: 'fj', text: 'Fijian' },\n    { value: 'fo', text: 'Faroese' },\n    { value: 'fr', text: 'French' },\n    { value: 'fy', text: 'Western Frisian' },\n    { value: 'ga', text: 'Irish' },\n    { value: 'gd', text: 'Gaelic; Scottish Gaelic' },\n    { value: 'gl', text: 'Galician' },\n    { value: 'gn', text: 'Guarani' },\n    { value: 'gu', text: 'Gujarati' },\n    { value: 'gv', text: 'Manx' },\n    { value: 'ha', text: 'Hausa' },\n    { value: 'he', text: 'Hebrew' },\n    { value: 'hi', text: 'Hindi' },\n    { value: 'ho', text: 'Hiri Motu' },\n    { value: 'hr', text: 'Croatian' },\n    { value: 'ht', text: 'Haitian; Haitian Creole' },\n    { value: 'hu', text: 'Hungarian' },\n    { value: 'hy', text: 'Armenian' },\n    { value: 'hz', text: 'Herero' },\n    {\n      value: 'ia',\n      text: 'Interlingua (International Auxiliary Language Association)'\n    },\n    { value: 'id', text: 'Indonesian' },\n    { value: 'ie', text: 'Interlingue; Occidental' },\n    { value: 'ig', text: 'Igbo' },\n    { value: 'ii', text: 'Sichuan Yi; Nuosu' },\n    { value: 'ik', text: 'Inupiaq' },\n    { value: 'io', text: 'Ido' },\n    { value: 'is', text: 'Icelandic' },\n    { value: 'it', text: 'Italian' },\n    { value: 'iu', text: 'Inuktitut' },\n    { value: 'ja', text: 'Japanese' },\n    { value: 'jv', text: 'Javanese' },\n    { value: 'ka', text: 'Georgian' },\n    { value: 'kg', text: 'Kongo' },\n    { value: 'ki', text: 'Kikuyu; Gikuyu' },\n    { value: 'kj', text: 'Kuanyama; Kwanyama' },\n    { value: 'kk', text: 'Kazakh' },\n    { value: 'kl', text: 'Kalaallisut; Greenlandic' },\n    { value: 'km', text: 'Central Khmer' },\n    { value: 'kn', text: 'Kannada' },\n    { value: 'ko', text: 'Korean' },\n    { value: 'kr', text: 'Kanuri' },\n    { value: 'ks', text: 'Kashmiri' },\n    { value: 'ku', text: 'Kurdish' },\n    { value: 'kv', text: 'Komi' },\n    { value: 'kw', text: 'Cornish' },\n    { value: 'ky', text: 'Kirghiz; Kyrgyz' },\n    { value: 'la', text: 'Latin' },\n    { value: 'lb', text: 'Luxembourgish; Letzeburgesch' },\n    { value: 'lg', text: 'Ganda' },\n    { value: 'li', text: 'Limburgan; Limburger; Limburgish' },\n    { value: 'ln', text: 'Lingala' },\n    { value: 'lo', text: 'Lao' },\n    { value: 'lt', text: 'Lithuanian' },\n    { value: 'lu', text: 'Luba-Katanga' },\n    { value: 'lv', text: 'Latvian' },\n    { value: 'mg', text: 'Malagasy' },\n    { value: 'mh', text: 'Marshallese' },\n    { value: 'mi', text: 'Maori' },\n    { value: 'mk', text: 'Macedonian' },\n    { value: 'ml', text: 'Malayalam' },\n    { value: 'mn', text: 'Mongolian' },\n    { value: 'mr', text: 'Marathi' },\n    { value: 'ms', text: 'Malay' },\n    { value: 'mt', text: 'Maltese' },\n    { value: 'my', text: 'Burmese' },\n    { value: 'na', text: 'Nauru' },\n    { value: 'nb', text: 'Bokmål, Norwegian; Norwegian Bokmål' },\n    { value: 'nd', text: 'Ndebele, North; North Ndebele' },\n    { value: 'ne', text: 'Nepali' },\n    { value: 'ng', text: 'Ndonga' },\n    { value: 'nl', text: 'Dutch; Flemish' },\n    { value: 'nn', text: 'Norwegian Nynorsk; Nynorsk, Norwegian' },\n    { value: 'no', text: 'Norwegian' },\n    { value: 'nr', text: 'Ndebele, South; South Ndebele' },\n    { value: 'nv', text: 'Navajo; Navaho' },\n    { value: 'ny', text: 'Chichewa; Chewa; Nyanja' },\n    { value: 'oc', text: 'Occitan (post 1500)' },\n    { value: 'oj', text: 'Ojibwa' },\n    { value: 'om', text: 'Oromo' },\n    { value: 'or', text: 'Oriya' },\n    { value: 'os', text: 'Ossetian; Ossetic' },\n    { value: 'pa', text: 'Panjabi; Punjabi' },\n    { value: 'pi', text: 'Pali' },\n    { value: 'pl', text: 'Polish' },\n    { value: 'ps', text: 'Pushto; Pashto' },\n    { value: 'pt', text: 'Portuguese' },\n    { value: 'qu', text: 'Quechua' },\n    { value: 'rm', text: 'Romansh' },\n    { value: 'rn', text: 'Rundi' },\n    { value: 'ro', text: 'Romanian; Moldavian; Moldovan' },\n    { value: 'ru', text: 'Russian' },\n    { value: 'rw', text: 'Kinyarwanda' },\n    { value: 'sa', text: 'Sanskrit' },\n    { value: 'sc', text: 'Sardinian' },\n    { value: 'sd', text: 'Sindhi' },\n    { value: 'se', text: 'Northern Sami' },\n    { value: 'sg', text: 'Sango' },\n    { value: 'si', text: 'Sinhala; Sinhalese' },\n    { value: 'sk', text: 'Slovak' },\n    { value: 'sl', text: 'Slovenian' },\n    { value: 'sm', text: 'Samoan' },\n    { value: 'sn', text: 'Shona' },\n    { value: 'so', text: 'Somali' },\n    { value: 'sq', text: 'Albanian' },\n    { value: 'sr', text: 'Serbian' },\n    { value: 'ss', text: 'Swati' },\n    { value: 'st', text: 'Sotho, Southern' },\n    { value: 'su', text: 'Sundanese' },\n    { value: 'sv', text: 'Swedish' },\n    { value: 'sw', text: 'Swahili' },\n    { value: 'ta', text: 'Tamil' },\n    { value: 'te', text: 'Telugu' },\n    { value: 'tg', text: 'Tajik' },\n    { value: 'th', text: 'Thai' },\n    { value: 'ti', text: 'Tigrinya' },\n    { value: 'tk', text: 'Turkmen' },\n    { value: 'tl', text: 'Tagalog' },\n    { value: 'tn', text: 'Tswana' },\n    { value: 'to', text: 'Tonga (Tonga Islands)' },\n    { value: 'tr', text: 'Turkish' },\n    { value: 'ts', text: 'Tsonga' },\n    { value: 'tt', text: 'Tatar' },\n    { value: 'tw', text: 'Twi' },\n    { value: 'ty', text: 'Tahitian' },\n    { value: 'ug', text: 'Uighur; Uyghur' },\n    { value: 'uk', text: 'Ukrainian' },\n    { value: 'ur', text: 'Urdu' },\n    { value: 'uz', text: 'Uzbek' },\n    { value: 've', text: 'Venda' },\n    { value: 'vi', text: 'Vietnamese' },\n    { value: 'vo', text: 'Volapük' },\n    { value: 'wa', text: 'Walloon' },\n    { value: 'wo', text: 'Wolof' },\n    { value: 'xh', text: 'Xhosa' },\n    { value: 'yi', text: 'Yiddish' },\n    { value: 'yo', text: 'Yoruba' },\n    { value: 'za', text: 'Zhuang; Chuang' },\n    { value: 'zh', text: 'Chinese' },\n    { value: 'zu', text: 'Zulu' }\n  ].filter((l) => {\n    if (languages) return languages.indexOf(l.value) !== -1;\n    else return true;\n  });\n  const childrenWithProps = React.Children.map(children, (child) =>\n    React.cloneElement(child, { options, ...props })\n  );\n\n  return <div>{childrenWithProps}</div>;\n}\n\nLanguageOptions.propTypes = {\n  children: PropTypes.node.isRequired,\n  languages: PropTypes.arrayOf(PropTypes.string)\n};\n\nLanguageOptions.defaultProps = {\n  languages: []\n};\n\nexport { LanguageOptions };\n"
  },
  {
    "path": "packages/evershop/src/components/common/locale/ProvinceOption.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\n\n\nfunction ProvinceOptions(props) {\n  const { country, children } = props;\n\n  const options = [\n    { value: 'AD-07', country_code: 'AD', text: 'Andorra la Vella' },\n    { value: 'AD-02', country_code: 'AD', text: 'Canillo' },\n    { value: 'AD-03', country_code: 'AD', text: 'Encamp' },\n    { value: 'AD-08', country_code: 'AD', text: 'Escaldes-Engordany' },\n    { value: 'AD-04', country_code: 'AD', text: 'La Massana' },\n    { value: 'AD-05', country_code: 'AD', text: 'Ordino' },\n    { value: 'AD-06', country_code: 'AD', text: 'Sant Julia de Loria' },\n    { value: 'AE-AJ', country_code: 'AE', text: \"'Ajman\" },\n    { value: 'AE-AZ', country_code: 'AE', text: 'Abu Zaby' },\n    { value: 'AE-FU', country_code: 'AE', text: 'Al Fujayrah' },\n    { value: 'AE-SH', country_code: 'AE', text: 'Ash Shariqah' },\n    { value: 'AE-DU', country_code: 'AE', text: 'Dubayy' },\n    { value: 'AE-RK', country_code: 'AE', text: \"Ra's al Khaymah\" },\n    { value: 'AE-UQ', country_code: 'AE', text: 'Umm al Qaywayn' },\n    { value: 'AF-BDS', country_code: 'AF', text: 'Badakhshan' },\n    { value: 'AF-BDG', country_code: 'AF', text: 'Badghis' },\n    { value: 'AF-BGL', country_code: 'AF', text: 'Baghlan' },\n    { value: 'AF-BAL', country_code: 'AF', text: 'Balkh' },\n    { value: 'AF-BAM', country_code: 'AF', text: 'Bamyan' },\n    { value: 'AF-DAY', country_code: 'AF', text: 'Daykundi' },\n    { value: 'AF-FRA', country_code: 'AF', text: 'Farah' },\n    { value: 'AF-FYB', country_code: 'AF', text: 'Faryab' },\n    { value: 'AF-GHA', country_code: 'AF', text: 'Ghazni' },\n    { value: 'AF-GHO', country_code: 'AF', text: 'Ghor' },\n    { value: 'AF-HEL', country_code: 'AF', text: 'Helmand' },\n    { value: 'AF-HER', country_code: 'AF', text: 'Herat' },\n    { value: 'AF-JOW', country_code: 'AF', text: 'Jowzjan' },\n    { value: 'AF-KAB', country_code: 'AF', text: 'Kabul' },\n    { value: 'AF-KAN', country_code: 'AF', text: 'Kandahar' },\n    { value: 'AF-KAP', country_code: 'AF', text: 'Kapisa' },\n    { value: 'AF-KHO', country_code: 'AF', text: 'Khost' },\n    { value: 'AF-KNR', country_code: 'AF', text: 'Kunar' },\n    { value: 'AF-KDZ', country_code: 'AF', text: 'Kunduz' },\n    { value: 'AF-LAG', country_code: 'AF', text: 'Laghman' },\n    { value: 'AF-LOG', country_code: 'AF', text: 'Logar' },\n    { value: 'AF-NAN', country_code: 'AF', text: 'Nangarhar' },\n    { value: 'AF-NIM', country_code: 'AF', text: 'Nimroz' },\n    { value: 'AF-NUR', country_code: 'AF', text: 'Nuristan' },\n    { value: 'AF-PKA', country_code: 'AF', text: 'Paktika' },\n    { value: 'AF-PIA', country_code: 'AF', text: 'Paktiya' },\n    { value: 'AF-PAN', country_code: 'AF', text: 'Panjshayr' },\n    { value: 'AF-PAR', country_code: 'AF', text: 'Parwan' },\n    { value: 'AF-SAM', country_code: 'AF', text: 'Samangan' },\n    { value: 'AF-SAR', country_code: 'AF', text: 'Sar-e Pul' },\n    { value: 'AF-TAK', country_code: 'AF', text: 'Takhar' },\n    { value: 'AF-URU', country_code: 'AF', text: 'Uruzgan' },\n    { value: 'AF-WAR', country_code: 'AF', text: 'Wardak' },\n    { value: 'AF-ZAB', country_code: 'AF', text: 'Zabul' },\n    { value: 'AG-04', country_code: 'AG', text: 'Saint John' },\n    { value: 'AG-05', country_code: 'AG', text: 'Saint Mary' },\n    { value: 'AG-06', country_code: 'AG', text: 'Saint Paul' },\n    { value: 'AL-01', country_code: 'AL', text: 'Berat' },\n    { value: 'AL-09', country_code: 'AL', text: 'Diber' },\n    { value: 'AL-02', country_code: 'AL', text: 'Durres' },\n    { value: 'AL-03', country_code: 'AL', text: 'Elbasan' },\n    { value: 'AL-04', country_code: 'AL', text: 'Fier' },\n    { value: 'AL-05', country_code: 'AL', text: 'Gjirokaster' },\n    { value: 'AL-06', country_code: 'AL', text: 'Korce' },\n    { value: 'AL-07', country_code: 'AL', text: 'Kukes' },\n    { value: 'AL-08', country_code: 'AL', text: 'Lezhe' },\n    { value: 'AL-10', country_code: 'AL', text: 'Shkoder' },\n    { value: 'AL-11', country_code: 'AL', text: 'Tirane' },\n    { value: 'AL-12', country_code: 'AL', text: 'Vlore' },\n    { value: 'AM-AG', country_code: 'AM', text: 'Aragacotn' },\n    { value: 'AM-AR', country_code: 'AM', text: 'Ararat' },\n    { value: 'AM-AV', country_code: 'AM', text: 'Armavir' },\n    { value: 'AM-ER', country_code: 'AM', text: 'Erevan' },\n    { value: 'AM-GR', country_code: 'AM', text: \"Gegark'unik'\" },\n    { value: 'AM-KT', country_code: 'AM', text: \"Kotayk'\" },\n    { value: 'AM-LO', country_code: 'AM', text: 'Lori' },\n    { value: 'AM-SH', country_code: 'AM', text: 'Sirak' },\n    { value: 'AM-SU', country_code: 'AM', text: \"Syunik'\" },\n    { value: 'AM-TV', country_code: 'AM', text: 'Tavus' },\n    { value: 'AM-VD', country_code: 'AM', text: 'Vayoc Jor' },\n    { value: 'AO-BGO', country_code: 'AO', text: 'Bengo' },\n    { value: 'AO-BGU', country_code: 'AO', text: 'Benguela' },\n    { value: 'AO-BIE', country_code: 'AO', text: 'Bie' },\n    { value: 'AO-CAB', country_code: 'AO', text: 'Cabinda' },\n    { value: 'AO-CNN', country_code: 'AO', text: 'Cunene' },\n    { value: 'AO-HUA', country_code: 'AO', text: 'Huambo' },\n    { value: 'AO-HUI', country_code: 'AO', text: 'Huila' },\n    { value: 'AO-CCU', country_code: 'AO', text: 'Kuando Kubango' },\n    { value: 'AO-CNO', country_code: 'AO', text: 'Kwanza Norte' },\n    { value: 'AO-CUS', country_code: 'AO', text: 'Kwanza Sul' },\n    { value: 'AO-LUA', country_code: 'AO', text: 'Luanda' },\n    { value: 'AO-LNO', country_code: 'AO', text: 'Lunda Norte' },\n    { value: 'AO-LSU', country_code: 'AO', text: 'Lunda Sul' },\n    { value: 'AO-MAL', country_code: 'AO', text: 'Malange' },\n    { value: 'AO-MOX', country_code: 'AO', text: 'Moxico' },\n    { value: 'AO-NAM', country_code: 'AO', text: 'Namibe' },\n    { value: 'AO-UIG', country_code: 'AO', text: 'Uige' },\n    { value: 'AO-ZAI', country_code: 'AO', text: 'Zaire' },\n    { value: 'AR-B', country_code: 'AR', text: 'Buenos Aires' },\n    { value: 'AR-K', country_code: 'AR', text: 'Catamarca' },\n    { value: 'AR-H', country_code: 'AR', text: 'Chaco' },\n    { value: 'AR-U', country_code: 'AR', text: 'Chubut' },\n    {\n      value: 'AR-C',\n      country_code: 'AR',\n      text: 'Ciudad Autonoma de Buenos Aires'\n    },\n    { value: 'AR-X', country_code: 'AR', text: 'Cordoba' },\n    { value: 'AR-W', country_code: 'AR', text: 'Corrientes' },\n    { value: 'AR-E', country_code: 'AR', text: 'Entre Rios' },\n    { value: 'AR-P', country_code: 'AR', text: 'Formosa' },\n    { value: 'AR-Y', country_code: 'AR', text: 'Jujuy' },\n    { value: 'AR-L', country_code: 'AR', text: 'La Pampa' },\n    { value: 'AR-F', country_code: 'AR', text: 'La Rioja' },\n    { value: 'AR-M', country_code: 'AR', text: 'Mendoza' },\n    { value: 'AR-N', country_code: 'AR', text: 'Misiones' },\n    { value: 'AR-Q', country_code: 'AR', text: 'Neuquen' },\n    { value: 'AR-R', country_code: 'AR', text: 'Rio Negro' },\n    { value: 'AR-A', country_code: 'AR', text: 'Salta' },\n    { value: 'AR-J', country_code: 'AR', text: 'San Juan' },\n    { value: 'AR-D', country_code: 'AR', text: 'San Luis' },\n    { value: 'AR-Z', country_code: 'AR', text: 'Santa Cruz' },\n    { value: 'AR-S', country_code: 'AR', text: 'Santa Fe' },\n    { value: 'AR-G', country_code: 'AR', text: 'Santiago del Estero' },\n    { value: 'AR-V', country_code: 'AR', text: 'Tierra del Fuego' },\n    { value: 'AR-T', country_code: 'AR', text: 'Tucuman' },\n    { value: 'AT-1', country_code: 'AT', text: 'Burgenland' },\n    { value: 'AT-2', country_code: 'AT', text: 'Karnten' },\n    { value: 'AT-3', country_code: 'AT', text: 'Niederosterreich' },\n    { value: 'AT-4', country_code: 'AT', text: 'Oberosterreich' },\n    { value: 'AT-5', country_code: 'AT', text: 'Salzburg' },\n    { value: 'AT-6', country_code: 'AT', text: 'Steiermark' },\n    { value: 'AT-7', country_code: 'AT', text: 'Tirol' },\n    { value: 'AT-8', country_code: 'AT', text: 'Vorarlberg' },\n    { value: 'AT-9', country_code: 'AT', text: 'Wien' },\n    {\n      value: 'AU-ACT',\n      country_code: 'AU',\n      text: 'Australian Capital Territory'\n    },\n    { value: 'AU-NSW', country_code: 'AU', text: 'New South Wales' },\n    { value: 'AU-NT', country_code: 'AU', text: 'Northern Territory' },\n    { value: 'AU-QLD', country_code: 'AU', text: 'Queensland' },\n    { value: 'AU-SA', country_code: 'AU', text: 'South Australia' },\n    { value: 'AU-TAS', country_code: 'AU', text: 'Tasmania' },\n    { value: 'AU-VIC', country_code: 'AU', text: 'Victoria' },\n    { value: 'AU-WA', country_code: 'AU', text: 'Western Australia' },\n    { value: 'AZ-ABS', country_code: 'AZ', text: 'Abseron' },\n    { value: 'AZ-AGC', country_code: 'AZ', text: 'Agcabadi' },\n    { value: 'AZ-AGM', country_code: 'AZ', text: 'Agdam' },\n    { value: 'AZ-AGS', country_code: 'AZ', text: 'Agdas' },\n    { value: 'AZ-AGA', country_code: 'AZ', text: 'Agstafa' },\n    { value: 'AZ-AGU', country_code: 'AZ', text: 'Agsu' },\n    { value: 'AZ-AST', country_code: 'AZ', text: 'Astara' },\n    { value: 'AZ-BA', country_code: 'AZ', text: 'Baki' },\n    { value: 'AZ-BAL', country_code: 'AZ', text: 'Balakan' },\n    { value: 'AZ-BAR', country_code: 'AZ', text: 'Barda' },\n    { value: 'AZ-BEY', country_code: 'AZ', text: 'Beylaqan' },\n    { value: 'AZ-BIL', country_code: 'AZ', text: 'Bilasuvar' },\n    { value: 'AZ-CAB', country_code: 'AZ', text: 'Cabrayil' },\n    { value: 'AZ-CAL', country_code: 'AZ', text: 'Calilabad' },\n    { value: 'AZ-DAS', country_code: 'AZ', text: 'Daskasan' },\n    { value: 'AZ-FUZ', country_code: 'AZ', text: 'Fuzuli' },\n    { value: 'AZ-GAD', country_code: 'AZ', text: 'Gadabay' },\n    { value: 'AZ-GA', country_code: 'AZ', text: 'Ganca' },\n    { value: 'AZ-GOR', country_code: 'AZ', text: 'Goranboy' },\n    { value: 'AZ-GOY', country_code: 'AZ', text: 'Goycay' },\n    { value: 'AZ-GYG', country_code: 'AZ', text: 'Goygol' },\n    { value: 'AZ-HAC', country_code: 'AZ', text: 'Haciqabul' },\n    { value: 'AZ-IMI', country_code: 'AZ', text: 'Imisli' },\n    { value: 'AZ-ISM', country_code: 'AZ', text: 'Ismayilli' },\n    { value: 'AZ-KAL', country_code: 'AZ', text: 'Kalbacar' },\n    { value: 'AZ-LAC', country_code: 'AZ', text: 'Lacin' },\n    { value: 'AZ-LA', country_code: 'AZ', text: 'Lankaran' },\n    { value: 'AZ-LER', country_code: 'AZ', text: 'Lerik' },\n    { value: 'AZ-MAS', country_code: 'AZ', text: 'Masalli' },\n    { value: 'AZ-MI', country_code: 'AZ', text: 'Mingacevir' },\n    { value: 'AZ-NA', country_code: 'AZ', text: 'Naftalan' },\n    { value: 'AZ-NX', country_code: 'AZ', text: 'Naxcivan' },\n    { value: 'AZ-NEF', country_code: 'AZ', text: 'Neftcala' },\n    { value: 'AZ-OGU', country_code: 'AZ', text: 'Oguz' },\n    { value: 'AZ-QAB', country_code: 'AZ', text: 'Qabala' },\n    { value: 'AZ-QAX', country_code: 'AZ', text: 'Qax' },\n    { value: 'AZ-QAZ', country_code: 'AZ', text: 'Qazax' },\n    { value: 'AZ-QOB', country_code: 'AZ', text: 'Qobustan' },\n    { value: 'AZ-QBA', country_code: 'AZ', text: 'Quba' },\n    { value: 'AZ-QBI', country_code: 'AZ', text: 'Qubadli' },\n    { value: 'AZ-QUS', country_code: 'AZ', text: 'Qusar' },\n    { value: 'AZ-SAT', country_code: 'AZ', text: 'Saatli' },\n    { value: 'AZ-SAB', country_code: 'AZ', text: 'Sabirabad' },\n    { value: 'AZ-SA', country_code: 'AZ', text: 'Saki' },\n    { value: 'AZ-SAL', country_code: 'AZ', text: 'Salyan' },\n    { value: 'AZ-SMI', country_code: 'AZ', text: 'Samaxi' },\n    { value: 'AZ-SKR', country_code: 'AZ', text: 'Samkir' },\n    { value: 'AZ-SMX', country_code: 'AZ', text: 'Samux' },\n    { value: 'AZ-SR', country_code: 'AZ', text: 'Sirvan' },\n    { value: 'AZ-SM', country_code: 'AZ', text: 'Sumqayit' },\n    { value: 'AZ-SUS', country_code: 'AZ', text: 'Susa' },\n    { value: 'AZ-TAR', country_code: 'AZ', text: 'Tartar' },\n    { value: 'AZ-TOV', country_code: 'AZ', text: 'Tovuz' },\n    { value: 'AZ-UCA', country_code: 'AZ', text: 'Ucar' },\n    { value: 'AZ-XAC', country_code: 'AZ', text: 'Xacmaz' },\n    { value: 'AZ-XA', country_code: 'AZ', text: 'Xankandi' },\n    { value: 'AZ-XIZ', country_code: 'AZ', text: 'Xizi' },\n    { value: 'AZ-XCI', country_code: 'AZ', text: 'Xocali' },\n    { value: 'AZ-XVD', country_code: 'AZ', text: 'Xocavand' },\n    { value: 'AZ-YAR', country_code: 'AZ', text: 'Yardimli' },\n    { value: 'AZ-YE', country_code: 'AZ', text: 'Yevlax' },\n    { value: 'AZ-ZAN', country_code: 'AZ', text: 'Zangilan' },\n    { value: 'AZ-ZAQ', country_code: 'AZ', text: 'Zaqatala' },\n    { value: 'AZ-ZAR', country_code: 'AZ', text: 'Zardab' },\n    {\n      value: 'BA-BIH',\n      country_code: 'BA',\n      text: 'Federacija Bosne i Hercegovine'\n    },\n    { value: 'BA-SRP', country_code: 'BA', text: 'Republika Srpska' },\n    { value: 'BB-01', country_code: 'BB', text: 'Christ Church' },\n    { value: 'BB-04', country_code: 'BB', text: 'Saint James' },\n    { value: 'BB-06', country_code: 'BB', text: 'Saint Joseph' },\n    { value: 'BB-08', country_code: 'BB', text: 'Saint Michael' },\n    { value: 'BB-09', country_code: 'BB', text: 'Saint Peter' },\n    { value: 'BD-06', country_code: 'BD', text: 'Barisal' },\n    { value: 'BD-10', country_code: 'BD', text: 'Chittagong' },\n    { value: 'BD-13', country_code: 'BD', text: 'Dhaka' },\n    { value: 'BD-27', country_code: 'BD', text: 'Khulna' },\n    { value: 'BD-54', country_code: 'BD', text: 'Rajshahi' },\n    { value: 'BD-55', country_code: 'BD', text: 'Rangpur' },\n    { value: 'BD-60', country_code: 'BD', text: 'Sylhet' },\n    { value: 'BE-VAN', country_code: 'BE', text: 'Antwerpen' },\n    { value: 'BE-WBR', country_code: 'BE', text: 'Brabant wallon' },\n    {\n      value: 'BE-BRU',\n      country_code: 'BE',\n      text: 'Brussels Hoofdstedelijk Gewest'\n    },\n    { value: 'BE-WHT', country_code: 'BE', text: 'Hainaut' },\n    { value: 'BE-WLG', country_code: 'BE', text: 'Liege' },\n    { value: 'BE-VLI', country_code: 'BE', text: 'Limburg' },\n    { value: 'BE-WLX', country_code: 'BE', text: 'Luxembourg' },\n    { value: 'BE-WNA', country_code: 'BE', text: 'Namur' },\n    { value: 'BE-VOV', country_code: 'BE', text: 'Oost-Vlaanderen' },\n    { value: 'BE-VBR', country_code: 'BE', text: 'Vlaams-Brabant' },\n    { value: 'BE-VWV', country_code: 'BE', text: 'West-Vlaanderen' },\n    { value: 'BF-BAL', country_code: 'BF', text: 'Bale' },\n    { value: 'BF-BAM', country_code: 'BF', text: 'Bam' },\n    { value: 'BF-BAN', country_code: 'BF', text: 'Banwa' },\n    { value: 'BF-BAZ', country_code: 'BF', text: 'Bazega' },\n    { value: 'BF-BGR', country_code: 'BF', text: 'Bougouriba' },\n    { value: 'BF-BLG', country_code: 'BF', text: 'Boulgou' },\n    { value: 'BF-BLK', country_code: 'BF', text: 'Boulkiemde' },\n    { value: 'BF-COM', country_code: 'BF', text: 'Comoe' },\n    { value: 'BF-GAN', country_code: 'BF', text: 'Ganzourgou' },\n    { value: 'BF-GNA', country_code: 'BF', text: 'Gnagna' },\n    { value: 'BF-GOU', country_code: 'BF', text: 'Gourma' },\n    { value: 'BF-HOU', country_code: 'BF', text: 'Houet' },\n    { value: 'BF-IOB', country_code: 'BF', text: 'Ioba' },\n    { value: 'BF-KAD', country_code: 'BF', text: 'Kadiogo' },\n    { value: 'BF-KEN', country_code: 'BF', text: 'Kenedougou' },\n    { value: 'BF-KMD', country_code: 'BF', text: 'Komondjari' },\n    { value: 'BF-KMP', country_code: 'BF', text: 'Kompienga' },\n    { value: 'BF-KOS', country_code: 'BF', text: 'Kossi' },\n    { value: 'BF-KOP', country_code: 'BF', text: 'Koulpelogo' },\n    { value: 'BF-KOT', country_code: 'BF', text: 'Kouritenga' },\n    { value: 'BF-KOW', country_code: 'BF', text: 'Kourweogo' },\n    { value: 'BF-LER', country_code: 'BF', text: 'Leraba' },\n    { value: 'BF-LOR', country_code: 'BF', text: 'Loroum' },\n    { value: 'BF-MOU', country_code: 'BF', text: 'Mouhoun' },\n    { value: 'BF-NAO', country_code: 'BF', text: 'Nahouri' },\n    { value: 'BF-NAM', country_code: 'BF', text: 'Namentenga' },\n    { value: 'BF-NAY', country_code: 'BF', text: 'Nayala' },\n    { value: 'BF-NOU', country_code: 'BF', text: 'Noumbiel' },\n    { value: 'BF-OUB', country_code: 'BF', text: 'Oubritenga' },\n    { value: 'BF-OUD', country_code: 'BF', text: 'Oudalan' },\n    { value: 'BF-PAS', country_code: 'BF', text: 'Passore' },\n    { value: 'BF-PON', country_code: 'BF', text: 'Poni' },\n    { value: 'BF-SNG', country_code: 'BF', text: 'Sanguie' },\n    { value: 'BF-SMT', country_code: 'BF', text: 'Sanmatenga' },\n    { value: 'BF-SEN', country_code: 'BF', text: 'Seno' },\n    { value: 'BF-SIS', country_code: 'BF', text: 'Sissili' },\n    { value: 'BF-SOM', country_code: 'BF', text: 'Soum' },\n    { value: 'BF-SOR', country_code: 'BF', text: 'Sourou' },\n    { value: 'BF-TAP', country_code: 'BF', text: 'Tapoa' },\n    { value: 'BF-TUI', country_code: 'BF', text: 'Tuy' },\n    { value: 'BF-YAG', country_code: 'BF', text: 'Yagha' },\n    { value: 'BF-YAT', country_code: 'BF', text: 'Yatenga' },\n    { value: 'BF-ZIR', country_code: 'BF', text: 'Ziro' },\n    { value: 'BF-ZON', country_code: 'BF', text: 'Zondoma' },\n    { value: 'BF-ZOU', country_code: 'BF', text: 'Zoundweogo' },\n    { value: 'BG-01', country_code: 'BG', text: 'Blagoevgrad' },\n    { value: 'BG-02', country_code: 'BG', text: 'Burgas' },\n    { value: 'BG-08', country_code: 'BG', text: 'Dobrich' },\n    { value: 'BG-07', country_code: 'BG', text: 'Gabrovo' },\n    { value: 'BG-26', country_code: 'BG', text: 'Haskovo' },\n    { value: 'BG-09', country_code: 'BG', text: 'Kardzhali' },\n    { value: 'BG-10', country_code: 'BG', text: 'Kyustendil' },\n    { value: 'BG-11', country_code: 'BG', text: 'Lovech' },\n    { value: 'BG-12', country_code: 'BG', text: 'Montana' },\n    { value: 'BG-13', country_code: 'BG', text: 'Pazardzhik' },\n    { value: 'BG-14', country_code: 'BG', text: 'Pernik' },\n    { value: 'BG-15', country_code: 'BG', text: 'Pleven' },\n    { value: 'BG-16', country_code: 'BG', text: 'Plovdiv' },\n    { value: 'BG-17', country_code: 'BG', text: 'Razgrad' },\n    { value: 'BG-18', country_code: 'BG', text: 'Ruse' },\n    { value: 'BG-27', country_code: 'BG', text: 'Shumen' },\n    { value: 'BG-19', country_code: 'BG', text: 'Silistra' },\n    { value: 'BG-20', country_code: 'BG', text: 'Sliven' },\n    { value: 'BG-21', country_code: 'BG', text: 'Smolyan' },\n    { value: 'BG-23', country_code: 'BG', text: 'Sofia' },\n    { value: 'BG-22', country_code: 'BG', text: 'Sofia (stolitsa)' },\n    { value: 'BG-24', country_code: 'BG', text: 'Stara Zagora' },\n    { value: 'BG-25', country_code: 'BG', text: 'Targovishte' },\n    { value: 'BG-03', country_code: 'BG', text: 'Varna' },\n    { value: 'BG-04', country_code: 'BG', text: 'Veliko Tarnovo' },\n    { value: 'BG-05', country_code: 'BG', text: 'Vidin' },\n    { value: 'BG-06', country_code: 'BG', text: 'Vratsa' },\n    { value: 'BG-28', country_code: 'BG', text: 'Yambol' },\n    { value: 'BH-13', country_code: 'BH', text: \"Al 'Asimah\" },\n    { value: 'BH-15', country_code: 'BH', text: 'Al Muharraq' },\n    { value: 'BH-17', country_code: 'BH', text: 'Ash Shamaliyah' },\n    { value: 'BI-BB', country_code: 'BI', text: 'Bubanza' },\n    { value: 'BI-BM', country_code: 'BI', text: 'Bujumbura Mairie' },\n    { value: 'BI-BR', country_code: 'BI', text: 'Bururi' },\n    { value: 'BI-CA', country_code: 'BI', text: 'Cankuzo' },\n    { value: 'BI-CI', country_code: 'BI', text: 'Cibitoke' },\n    { value: 'BI-GI', country_code: 'BI', text: 'Gitega' },\n    { value: 'BI-KR', country_code: 'BI', text: 'Karuzi' },\n    { value: 'BI-KY', country_code: 'BI', text: 'Kayanza' },\n    { value: 'BI-KI', country_code: 'BI', text: 'Kirundo' },\n    { value: 'BI-MA', country_code: 'BI', text: 'Makamba' },\n    { value: 'BI-MU', country_code: 'BI', text: 'Muramvya' },\n    { value: 'BI-MY', country_code: 'BI', text: 'Muyinga' },\n    { value: 'BI-MW', country_code: 'BI', text: 'Mwaro' },\n    { value: 'BI-NG', country_code: 'BI', text: 'Ngozi' },\n    { value: 'BI-RT', country_code: 'BI', text: 'Rutana' },\n    { value: 'BI-RY', country_code: 'BI', text: 'Ruyigi' },\n    { value: 'BJ-AL', country_code: 'BJ', text: 'Alibori' },\n    { value: 'BJ-AK', country_code: 'BJ', text: 'Atacora' },\n    { value: 'BJ-AQ', country_code: 'BJ', text: 'Atlantique' },\n    { value: 'BJ-BO', country_code: 'BJ', text: 'Borgou' },\n    { value: 'BJ-CO', country_code: 'BJ', text: 'Collines' },\n    { value: 'BJ-KO', country_code: 'BJ', text: 'Couffo' },\n    { value: 'BJ-DO', country_code: 'BJ', text: 'Donga' },\n    { value: 'BJ-LI', country_code: 'BJ', text: 'Littoral' },\n    { value: 'BJ-MO', country_code: 'BJ', text: 'Mono' },\n    { value: 'BJ-OU', country_code: 'BJ', text: 'Oueme' },\n    { value: 'BJ-PL', country_code: 'BJ', text: 'Plateau' },\n    { value: 'BJ-ZO', country_code: 'BJ', text: 'Zou' },\n    { value: 'BN-BE', country_code: 'BN', text: 'Belait' },\n    { value: 'BN-BM', country_code: 'BN', text: 'Brunei-Muara' },\n    { value: 'BN-TE', country_code: 'BN', text: 'Temburong' },\n    { value: 'BN-TU', country_code: 'BN', text: 'Tutong' },\n    { value: 'BO-H', country_code: 'BO', text: 'Chuquisaca' },\n    { value: 'BO-C', country_code: 'BO', text: 'Cochabamba' },\n    { value: 'BO-B', country_code: 'BO', text: 'El Beni' },\n    { value: 'BO-L', country_code: 'BO', text: 'La Paz' },\n    { value: 'BO-O', country_code: 'BO', text: 'Oruro' },\n    { value: 'BO-N', country_code: 'BO', text: 'Pando' },\n    { value: 'BO-P', country_code: 'BO', text: 'Potosi' },\n    { value: 'BO-S', country_code: 'BO', text: 'Santa Cruz' },\n    { value: 'BO-T', country_code: 'BO', text: 'Tarija' },\n    { value: 'BQ-BO', country_code: 'BQ', text: 'Bonaire' },\n    { value: 'BQ-SA', country_code: 'BQ', text: 'Saba' },\n    { value: 'BQ-SE', country_code: 'BQ', text: 'Sint Eustatius' },\n    { value: 'BR-AC', country_code: 'BR', text: 'Acre' },\n    { value: 'BR-AL', country_code: 'BR', text: 'Alagoas' },\n    { value: 'BR-AP', country_code: 'BR', text: 'Amapa' },\n    { value: 'BR-AM', country_code: 'BR', text: 'Amazonas' },\n    { value: 'BR-BA', country_code: 'BR', text: 'Bahia' },\n    { value: 'BR-CE', country_code: 'BR', text: 'Ceara' },\n    { value: 'BR-DF', country_code: 'BR', text: 'Distrito Federal' },\n    { value: 'BR-ES', country_code: 'BR', text: 'Espirito Santo' },\n    { value: 'BR-GO', country_code: 'BR', text: 'Goias' },\n    { value: 'BR-MA', country_code: 'BR', text: 'Maranhao' },\n    { value: 'BR-MT', country_code: 'BR', text: 'Mato Grosso' },\n    { value: 'BR-MS', country_code: 'BR', text: 'Mato Grosso do Sul' },\n    { value: 'BR-MG', country_code: 'BR', text: 'Minas Gerais' },\n    { value: 'BR-PA', country_code: 'BR', text: 'Para' },\n    { value: 'BR-PB', country_code: 'BR', text: 'Paraiba' },\n    { value: 'BR-PR', country_code: 'BR', text: 'Parana' },\n    { value: 'BR-PE', country_code: 'BR', text: 'Pernambuco' },\n    { value: 'BR-PI', country_code: 'BR', text: 'Piaui' },\n    { value: 'BR-RJ', country_code: 'BR', text: 'Rio de Janeiro' },\n    { value: 'BR-RN', country_code: 'BR', text: 'Rio Grande do Norte' },\n    { value: 'BR-RS', country_code: 'BR', text: 'Rio Grande do Sul' },\n    { value: 'BR-RO', country_code: 'BR', text: 'Rondonia' },\n    { value: 'BR-RR', country_code: 'BR', text: 'Roraima' },\n    { value: 'BR-SC', country_code: 'BR', text: 'Santa Catarina' },\n    { value: 'BR-SP', country_code: 'BR', text: 'Sao Paulo' },\n    { value: 'BR-SE', country_code: 'BR', text: 'Sergipe' },\n    { value: 'BR-TO', country_code: 'BR', text: 'Tocantins' },\n    { value: 'BS-CS', country_code: 'BS', text: 'Central Andros' },\n    { value: 'BS-FP', country_code: 'BS', text: 'City of Freeport' },\n    { value: 'BS-EG', country_code: 'BS', text: 'East Grand Bahama' },\n    { value: 'BS-HI', country_code: 'BS', text: 'Harbour Island' },\n    { value: 'BS-HT', country_code: 'BS', text: 'Hope Town' },\n    { value: 'BS-LI', country_code: 'BS', text: 'Long Island' },\n    { value: 'BS-SE', country_code: 'BS', text: 'South Eleuthera' },\n    { value: 'BT-12', country_code: 'BT', text: 'Chhukha' },\n    { value: 'BT-22', country_code: 'BT', text: 'Dagana' },\n    { value: 'BT-GA', country_code: 'BT', text: 'Gasa' },\n    { value: 'BT-13', country_code: 'BT', text: 'Haa' },\n    { value: 'BT-42', country_code: 'BT', text: 'Monggar' },\n    { value: 'BT-11', country_code: 'BT', text: 'Paro' },\n    { value: 'BT-23', country_code: 'BT', text: 'Punakha' },\n    { value: 'BT-15', country_code: 'BT', text: 'Thimphu' },\n    { value: 'BT-TY', country_code: 'BT', text: 'Trashi Yangtse' },\n    { value: 'BT-32', country_code: 'BT', text: 'Trongsa' },\n    { value: 'BT-34', country_code: 'BT', text: 'Zhemgang' },\n    { value: 'BW-CE', country_code: 'BW', text: 'Central' },\n    { value: 'BW-GH', country_code: 'BW', text: 'Ghanzi' },\n    { value: 'BW-KG', country_code: 'BW', text: 'Kgalagadi' },\n    { value: 'BW-KL', country_code: 'BW', text: 'Kgatleng' },\n    { value: 'BW-KW', country_code: 'BW', text: 'Kweneng' },\n    { value: 'BW-NE', country_code: 'BW', text: 'North East' },\n    { value: 'BW-NW', country_code: 'BW', text: 'North West' },\n    { value: 'BW-SE', country_code: 'BW', text: 'South East' },\n    { value: 'BW-SO', country_code: 'BW', text: 'Southern' },\n    { value: 'BY-BR', country_code: 'BY', text: \"Brestskaya voblasts'\" },\n    { value: 'BY-HO', country_code: 'BY', text: \"Homyel'skaya voblasts'\" },\n    { value: 'BY-HR', country_code: 'BY', text: \"Hrodzenskaya voblasts'\" },\n    { value: 'BY-MA', country_code: 'BY', text: \"Mahilyowskaya voblasts'\" },\n    { value: 'BY-MI', country_code: 'BY', text: \"Minskaya voblasts'\" },\n    { value: 'BY-VI', country_code: 'BY', text: \"Vitsyebskaya voblasts'\" },\n    { value: 'BZ-BZ', country_code: 'BZ', text: 'Belize' },\n    { value: 'BZ-CY', country_code: 'BZ', text: 'Cayo' },\n    { value: 'BZ-CZL', country_code: 'BZ', text: 'Corozal' },\n    { value: 'BZ-OW', country_code: 'BZ', text: 'Orange Walk' },\n    { value: 'BZ-SC', country_code: 'BZ', text: 'Stann Creek' },\n    { value: 'BZ-TOL', country_code: 'BZ', text: 'Toledo' },\n    { value: 'CA-AB', country_code: 'CA', text: 'Alberta' },\n    { value: 'CA-BC', country_code: 'CA', text: 'British Columbia' },\n    { value: 'CA-MB', country_code: 'CA', text: 'Manitoba' },\n    { value: 'CA-NB', country_code: 'CA', text: 'New Brunswick' },\n    { value: 'CA-NL', country_code: 'CA', text: 'Newfoundland and Labrador' },\n    { value: 'CA-NT', country_code: 'CA', text: 'Northwest Territories' },\n    { value: 'CA-NS', country_code: 'CA', text: 'Nova Scotia' },\n    { value: 'CA-NU', country_code: 'CA', text: 'Nunavut' },\n    { value: 'CA-ON', country_code: 'CA', text: 'Ontario' },\n    { value: 'CA-PE', country_code: 'CA', text: 'Prince Edward Island' },\n    { value: 'CA-QC', country_code: 'CA', text: 'Quebec' },\n    { value: 'CA-SK', country_code: 'CA', text: 'Saskatchewan' },\n    { value: 'CA-YT', country_code: 'CA', text: 'Yukon' },\n    { value: 'CD-BU', country_code: 'CD', text: 'Bas-Uele' },\n    { value: 'CD-EQ', country_code: 'CD', text: 'Equateur' },\n    { value: 'CD-HK', country_code: 'CD', text: 'Haut-Katanga' },\n    { value: 'CD-HL', country_code: 'CD', text: 'Haut-Lomani' },\n    { value: 'CD-HU', country_code: 'CD', text: 'Haut-Uele' },\n    { value: 'CD-IT', country_code: 'CD', text: 'Ituri' },\n    { value: 'CD-KS', country_code: 'CD', text: 'Kasai' },\n    { value: 'CD-KC', country_code: 'CD', text: 'Kasai Central' },\n    { value: 'CD-KE', country_code: 'CD', text: 'Kasai Oriental' },\n    { value: 'CD-KN', country_code: 'CD', text: 'Kinshasa' },\n    { value: 'CD-BC', country_code: 'CD', text: 'Kongo Central' },\n    { value: 'CD-KG', country_code: 'CD', text: 'Kwango' },\n    { value: 'CD-KL', country_code: 'CD', text: 'Kwilu' },\n    { value: 'CD-LO', country_code: 'CD', text: 'Lomami' },\n    { value: 'CD-LU', country_code: 'CD', text: 'Lualaba' },\n    { value: 'CD-MN', country_code: 'CD', text: 'Mai-Ndombe' },\n    { value: 'CD-MA', country_code: 'CD', text: 'Maniema' },\n    { value: 'CD-MO', country_code: 'CD', text: 'Mongala' },\n    { value: 'CD-NK', country_code: 'CD', text: 'Nord-Kivu' },\n    { value: 'CD-NU', country_code: 'CD', text: 'Nord-Ubangi' },\n    { value: 'CD-SA', country_code: 'CD', text: 'Sankuru' },\n    { value: 'CD-SK', country_code: 'CD', text: 'Sud-Kivu' },\n    { value: 'CD-SU', country_code: 'CD', text: 'Sud-Ubangi' },\n    { value: 'CD-TA', country_code: 'CD', text: 'Tanganyika' },\n    { value: 'CD-TO', country_code: 'CD', text: 'Tshopo' },\n    { value: 'CD-TU', country_code: 'CD', text: 'Tshuapa' },\n    { value: 'CF-BB', country_code: 'CF', text: 'Bamingui-Bangoran' },\n    { value: 'CF-BGF', country_code: 'CF', text: 'Bangui' },\n    { value: 'CF-BK', country_code: 'CF', text: 'Basse-Kotto' },\n    { value: 'CF-KB', country_code: 'CF', text: 'Gribingui' },\n    { value: 'CF-HM', country_code: 'CF', text: 'Haut-Mbomou' },\n    { value: 'CF-HK', country_code: 'CF', text: 'Haute-Kotto' },\n    { value: 'CF-KG', country_code: 'CF', text: 'Kemo-Gribingui' },\n    { value: 'CF-LB', country_code: 'CF', text: 'Lobaye' },\n    { value: 'CF-HS', country_code: 'CF', text: 'Mambere-Kadei' },\n    { value: 'CF-MB', country_code: 'CF', text: 'Mbomou' },\n    { value: 'CF-NM', country_code: 'CF', text: 'Nana-Mambere' },\n    { value: 'CF-MP', country_code: 'CF', text: 'Ombella-Mpoko' },\n    { value: 'CF-UK', country_code: 'CF', text: 'Ouaka' },\n    { value: 'CF-AC', country_code: 'CF', text: 'Ouham' },\n    { value: 'CF-OP', country_code: 'CF', text: 'Ouham-Pende' },\n    { value: 'CF-SE', country_code: 'CF', text: 'Sangha' },\n    { value: 'CG-11', country_code: 'CG', text: 'Bouenza' },\n    { value: 'CG-BZV', country_code: 'CG', text: 'Brazzaville' },\n    { value: 'CG-8', country_code: 'CG', text: 'Cuvette' },\n    { value: 'CG-15', country_code: 'CG', text: 'Cuvette-Ouest' },\n    { value: 'CG-2', country_code: 'CG', text: 'Lekoumou' },\n    { value: 'CG-7', country_code: 'CG', text: 'Likouala' },\n    { value: 'CG-9', country_code: 'CG', text: 'Niari' },\n    { value: 'CG-14', country_code: 'CG', text: 'Plateaux' },\n    { value: 'CG-16', country_code: 'CG', text: 'Pointe-Noire' },\n    { value: 'CG-12', country_code: 'CG', text: 'Pool' },\n    { value: 'CG-13', country_code: 'CG', text: 'Sangha' },\n    { value: 'CH-AG', country_code: 'CH', text: 'Aargau' },\n    { value: 'CH-AR', country_code: 'CH', text: 'Appenzell Ausserrhoden' },\n    { value: 'CH-AI', country_code: 'CH', text: 'Appenzell Innerrhoden' },\n    { value: 'CH-BL', country_code: 'CH', text: 'Basel-Landschaft' },\n    { value: 'CH-BS', country_code: 'CH', text: 'Basel-Stadt' },\n    { value: 'CH-BE', country_code: 'CH', text: 'Bern' },\n    { value: 'CH-FR', country_code: 'CH', text: 'Fribourg' },\n    { value: 'CH-GE', country_code: 'CH', text: 'Geneve' },\n    { value: 'CH-GL', country_code: 'CH', text: 'Glarus' },\n    { value: 'CH-GR', country_code: 'CH', text: 'Graubunden' },\n    { value: 'CH-JU', country_code: 'CH', text: 'Jura' },\n    { value: 'CH-LU', country_code: 'CH', text: 'Luzern' },\n    { value: 'CH-NE', country_code: 'CH', text: 'Neuchatel' },\n    { value: 'CH-NW', country_code: 'CH', text: 'Nidwalden' },\n    { value: 'CH-OW', country_code: 'CH', text: 'Obwalden' },\n    { value: 'CH-SG', country_code: 'CH', text: 'Sankt Gallen' },\n    { value: 'CH-SH', country_code: 'CH', text: 'Schaffhausen' },\n    { value: 'CH-SZ', country_code: 'CH', text: 'Schwyz' },\n    { value: 'CH-SO', country_code: 'CH', text: 'Solothurn' },\n    { value: 'CH-TG', country_code: 'CH', text: 'Thurgau' },\n    { value: 'CH-TI', country_code: 'CH', text: 'Ticino' },\n    { value: 'CH-UR', country_code: 'CH', text: 'Uri' },\n    { value: 'CH-VS', country_code: 'CH', text: 'Valais' },\n    { value: 'CH-VD', country_code: 'CH', text: 'Vaud' },\n    { value: 'CH-ZG', country_code: 'CH', text: 'Zug' },\n    { value: 'CH-ZH', country_code: 'CH', text: 'Zurich' },\n    { value: 'CI-AB', country_code: 'CI', text: 'Abidjan' },\n    { value: 'CI-BS', country_code: 'CI', text: 'Bas-Sassandra' },\n    { value: 'CI-CM', country_code: 'CI', text: 'Comoe' },\n    { value: 'CI-DN', country_code: 'CI', text: 'Denguele' },\n    { value: 'CI-GD', country_code: 'CI', text: 'Goh-Djiboua' },\n    { value: 'CI-LC', country_code: 'CI', text: 'Lacs' },\n    { value: 'CI-LG', country_code: 'CI', text: 'Lagunes' },\n    { value: 'CI-MG', country_code: 'CI', text: 'Montagnes' },\n    { value: 'CI-SM', country_code: 'CI', text: 'Sassandra-Marahoue' },\n    { value: 'CI-SV', country_code: 'CI', text: 'Savanes' },\n    { value: 'CI-VB', country_code: 'CI', text: 'Vallee du Bandama' },\n    { value: 'CI-WR', country_code: 'CI', text: 'Woroba' },\n    { value: 'CI-ZZ', country_code: 'CI', text: 'Zanzan' },\n    {\n      value: 'CL-AI',\n      country_code: 'CL',\n      text: 'Aisen del General Carlos Ibanez del Campo'\n    },\n    { value: 'CL-AN', country_code: 'CL', text: 'Antofagasta' },\n    { value: 'CL-AP', country_code: 'CL', text: 'Arica y Parinacota' },\n    { value: 'CL-AT', country_code: 'CL', text: 'Atacama' },\n    { value: 'CL-BI', country_code: 'CL', text: 'Biobio' },\n    { value: 'CL-CO', country_code: 'CL', text: 'Coquimbo' },\n    { value: 'CL-AR', country_code: 'CL', text: 'La Araucania' },\n    {\n      value: 'CL-LI',\n      country_code: 'CL',\n      text: \"Libertador General Bernardo O'Higgins\"\n    },\n    { value: 'CL-LL', country_code: 'CL', text: 'Los Lagos' },\n    { value: 'CL-LR', country_code: 'CL', text: 'Los Rios' },\n    { value: 'CL-MA', country_code: 'CL', text: 'Magallanes' },\n    { value: 'CL-ML', country_code: 'CL', text: 'Maule' },\n    {\n      value: 'CL-RM',\n      country_code: 'CL',\n      text: 'Region Metropolitana de Santiago'\n    },\n    { value: 'CL-TA', country_code: 'CL', text: 'Tarapaca' },\n    { value: 'CL-VS', country_code: 'CL', text: 'Valparaiso' },\n    { value: 'CM-AD', country_code: 'CM', text: 'Adamaoua' },\n    { value: 'CM-CE', country_code: 'CM', text: 'Centre' },\n    { value: 'CM-ES', country_code: 'CM', text: 'Est' },\n    { value: 'CM-EN', country_code: 'CM', text: 'Extreme-Nord' },\n    { value: 'CM-LT', country_code: 'CM', text: 'Littoral' },\n    { value: 'CM-NO', country_code: 'CM', text: 'Nord' },\n    { value: 'CM-NW', country_code: 'CM', text: 'Nord-Ouest' },\n    { value: 'CM-OU', country_code: 'CM', text: 'Ouest' },\n    { value: 'CM-SU', country_code: 'CM', text: 'Sud' },\n    { value: 'CM-SW', country_code: 'CM', text: 'Sud-Ouest' },\n    { value: 'CN-34', country_code: 'CN', text: 'Anhui' },\n    { value: 'CN-11', country_code: 'CN', text: 'Beijing' },\n    { value: 'CN-50', country_code: 'CN', text: 'Chongqing' },\n    { value: 'CN-35', country_code: 'CN', text: 'Fujian' },\n    { value: 'CN-62', country_code: 'CN', text: 'Gansu' },\n    { value: 'CN-44', country_code: 'CN', text: 'Guangdong' },\n    { value: 'CN-45', country_code: 'CN', text: 'Guangxi' },\n    { value: 'CN-52', country_code: 'CN', text: 'Guizhou' },\n    { value: 'CN-46', country_code: 'CN', text: 'Hainan' },\n    { value: 'CN-13', country_code: 'CN', text: 'Hebei' },\n    { value: 'CN-23', country_code: 'CN', text: 'Heilongjiang' },\n    { value: 'CN-41', country_code: 'CN', text: 'Henan' },\n    { value: 'CN-42', country_code: 'CN', text: 'Hubei' },\n    { value: 'CN-43', country_code: 'CN', text: 'Hunan' },\n    { value: 'CN-32', country_code: 'CN', text: 'Jiangsu' },\n    { value: 'CN-36', country_code: 'CN', text: 'Jiangxi' },\n    { value: 'CN-22', country_code: 'CN', text: 'Jilin' },\n    { value: 'CN-21', country_code: 'CN', text: 'Liaoning' },\n    { value: 'CN-15', country_code: 'CN', text: 'Nei Mongol' },\n    { value: 'CN-64', country_code: 'CN', text: 'Ningxia' },\n    { value: 'CN-63', country_code: 'CN', text: 'Qinghai' },\n    { value: 'CN-61', country_code: 'CN', text: 'Shaanxi' },\n    { value: 'CN-37', country_code: 'CN', text: 'Shandong' },\n    { value: 'CN-31', country_code: 'CN', text: 'Shanghai' },\n    { value: 'CN-14', country_code: 'CN', text: 'Shanxi' },\n    { value: 'CN-51', country_code: 'CN', text: 'Sichuan' },\n    { value: 'CN-12', country_code: 'CN', text: 'Tianjin' },\n    { value: 'CN-65', country_code: 'CN', text: 'Xinjiang' },\n    { value: 'CN-54', country_code: 'CN', text: 'Xizang' },\n    { value: 'CN-53', country_code: 'CN', text: 'Yunnan' },\n    { value: 'CN-33', country_code: 'CN', text: 'Zhejiang' },\n    { value: 'CO-AMA', country_code: 'CO', text: 'Amazonas' },\n    { value: 'CO-ANT', country_code: 'CO', text: 'Antioquia' },\n    { value: 'CO-ARA', country_code: 'CO', text: 'Arauca' },\n    { value: 'CO-ATL', country_code: 'CO', text: 'Atlantico' },\n    { value: 'CO-BOL', country_code: 'CO', text: 'Bolivar' },\n    { value: 'CO-BOY', country_code: 'CO', text: 'Boyaca' },\n    { value: 'CO-CAL', country_code: 'CO', text: 'Caldas' },\n    { value: 'CO-CAQ', country_code: 'CO', text: 'Caqueta' },\n    { value: 'CO-CAS', country_code: 'CO', text: 'Casanare' },\n    { value: 'CO-CAU', country_code: 'CO', text: 'Cauca' },\n    { value: 'CO-CES', country_code: 'CO', text: 'Cesar' },\n    { value: 'CO-CHO', country_code: 'CO', text: 'Choco' },\n    { value: 'CO-COR', country_code: 'CO', text: 'Cordoba' },\n    { value: 'CO-CUN', country_code: 'CO', text: 'Cundinamarca' },\n    { value: 'CO-DC', country_code: 'CO', text: 'Distrito Capital de Bogota' },\n    { value: 'CO-GUA', country_code: 'CO', text: 'Guainia' },\n    { value: 'CO-GUV', country_code: 'CO', text: 'Guaviare' },\n    { value: 'CO-HUI', country_code: 'CO', text: 'Huila' },\n    { value: 'CO-LAG', country_code: 'CO', text: 'La Guajira' },\n    { value: 'CO-MAG', country_code: 'CO', text: 'Magdalena' },\n    { value: 'CO-MET', country_code: 'CO', text: 'Meta' },\n    { value: 'CO-NAR', country_code: 'CO', text: 'Narino' },\n    { value: 'CO-NSA', country_code: 'CO', text: 'Norte de Santander' },\n    { value: 'CO-PUT', country_code: 'CO', text: 'Putumayo' },\n    { value: 'CO-QUI', country_code: 'CO', text: 'Quindio' },\n    { value: 'CO-RIS', country_code: 'CO', text: 'Risaralda' },\n    {\n      value: 'CO-SAP',\n      country_code: 'CO',\n      text: 'San Andres, Providencia y Santa Catalina'\n    },\n    { value: 'CO-SAN', country_code: 'CO', text: 'Santander' },\n    { value: 'CO-SUC', country_code: 'CO', text: 'Sucre' },\n    { value: 'CO-TOL', country_code: 'CO', text: 'Tolima' },\n    { value: 'CO-VAC', country_code: 'CO', text: 'Valle del Cauca' },\n    { value: 'CO-VAU', country_code: 'CO', text: 'Vaupes' },\n    { value: 'CO-VID', country_code: 'CO', text: 'Vichada' },\n    { value: 'CR-A', country_code: 'CR', text: 'Alajuela' },\n    { value: 'CR-C', country_code: 'CR', text: 'Cartago' },\n    { value: 'CR-G', country_code: 'CR', text: 'Guanacaste' },\n    { value: 'CR-H', country_code: 'CR', text: 'Heredia' },\n    { value: 'CR-L', country_code: 'CR', text: 'Limon' },\n    { value: 'CR-P', country_code: 'CR', text: 'Puntarenas' },\n    { value: 'CR-SJ', country_code: 'CR', text: 'San Jose' },\n    { value: 'CU-15', country_code: 'CU', text: 'Artemisa' },\n    { value: 'CU-09', country_code: 'CU', text: 'Camaguey' },\n    { value: 'CU-08', country_code: 'CU', text: 'Ciego de Avila' },\n    { value: 'CU-06', country_code: 'CU', text: 'Cienfuegos' },\n    { value: 'CU-12', country_code: 'CU', text: 'Granma' },\n    { value: 'CU-14', country_code: 'CU', text: 'Guantanamo' },\n    { value: 'CU-11', country_code: 'CU', text: 'Holguin' },\n    { value: 'CU-99', country_code: 'CU', text: 'Isla de la Juventud' },\n    { value: 'CU-03', country_code: 'CU', text: 'La Habana' },\n    { value: 'CU-10', country_code: 'CU', text: 'Las Tunas' },\n    { value: 'CU-04', country_code: 'CU', text: 'Matanzas' },\n    { value: 'CU-16', country_code: 'CU', text: 'Mayabeque' },\n    { value: 'CU-01', country_code: 'CU', text: 'Pinar del Rio' },\n    { value: 'CU-07', country_code: 'CU', text: 'Sancti Spiritus' },\n    { value: 'CU-13', country_code: 'CU', text: 'Santiago de Cuba' },\n    { value: 'CU-05', country_code: 'CU', text: 'Villa Clara' },\n    { value: 'CV-BV', country_code: 'CV', text: 'Boa Vista' },\n    { value: 'CV-BR', country_code: 'CV', text: 'Brava' },\n    { value: 'CV-MA', country_code: 'CV', text: 'Maio' },\n    { value: 'CV-MO', country_code: 'CV', text: 'Mosteiros' },\n    { value: 'CV-PA', country_code: 'CV', text: 'Paul' },\n    { value: 'CV-PN', country_code: 'CV', text: 'Porto Novo' },\n    { value: 'CV-PR', country_code: 'CV', text: 'Praia' },\n    { value: 'CV-RB', country_code: 'CV', text: 'Ribeira Brava' },\n    { value: 'CV-RG', country_code: 'CV', text: 'Ribeira Grande' },\n    { value: 'CV-RS', country_code: 'CV', text: 'Ribeira Grande de Santiago' },\n    { value: 'CV-SL', country_code: 'CV', text: 'Sal' },\n    { value: 'CV-CA', country_code: 'CV', text: 'Santa Catarina' },\n    { value: 'CV-CF', country_code: 'CV', text: 'Santa Catarina do Fogo' },\n    { value: 'CV-CR', country_code: 'CV', text: 'Santa Cruz' },\n    { value: 'CV-SD', country_code: 'CV', text: 'Sao Domingos' },\n    { value: 'CV-SF', country_code: 'CV', text: 'Sao Filipe' },\n    { value: 'CV-SM', country_code: 'CV', text: 'Sao Miguel' },\n    { value: 'CV-SS', country_code: 'CV', text: 'Sao Salvador do Mundo' },\n    { value: 'CV-SV', country_code: 'CV', text: 'Sao Vicente' },\n    { value: 'CV-TA', country_code: 'CV', text: 'Tarrafal' },\n    { value: 'CV-TS', country_code: 'CV', text: 'Tarrafal de Sao Nicolau' },\n    { value: 'CY-04', country_code: 'CY', text: 'Ammochostos' },\n    { value: 'CY-06', country_code: 'CY', text: 'Keryneia' },\n    { value: 'CY-03', country_code: 'CY', text: 'Larnaka' },\n    { value: 'CY-01', country_code: 'CY', text: 'Lefkosia' },\n    { value: 'CY-02', country_code: 'CY', text: 'Lemesos' },\n    { value: 'CY-05', country_code: 'CY', text: 'Pafos' },\n    { value: 'CZ-JC', country_code: 'CZ', text: 'Jihocesky kraj' },\n    { value: 'CZ-JM', country_code: 'CZ', text: 'Jihomoravsky kraj' },\n    { value: 'CZ-KA', country_code: 'CZ', text: 'Karlovarsky kraj' },\n    { value: 'CZ-63', country_code: 'CZ', text: 'Kraj Vysocina' },\n    { value: 'CZ-KR', country_code: 'CZ', text: 'Kralovehradecky kraj' },\n    { value: 'CZ-LI', country_code: 'CZ', text: 'Liberecky kraj' },\n    { value: 'CZ-MO', country_code: 'CZ', text: 'Moravskoslezsky kraj' },\n    { value: 'CZ-OL', country_code: 'CZ', text: 'Olomoucky kraj' },\n    { value: 'CZ-PA', country_code: 'CZ', text: 'Pardubicky kraj' },\n    { value: 'CZ-PL', country_code: 'CZ', text: 'Plzensky kraj' },\n    { value: 'CZ-10', country_code: 'CZ', text: 'Praha, Hlavni mesto' },\n    { value: 'CZ-ST', country_code: 'CZ', text: 'Stredocesky kraj' },\n    { value: 'CZ-US', country_code: 'CZ', text: 'Ustecky kraj' },\n    { value: 'CZ-ZL', country_code: 'CZ', text: 'Zlinsky kraj' },\n    { value: 'DE-BW', country_code: 'DE', text: 'Baden-Wurttemberg' },\n    { value: 'DE-BY', country_code: 'DE', text: 'Bayern' },\n    { value: 'DE-BE', country_code: 'DE', text: 'Berlin' },\n    { value: 'DE-BB', country_code: 'DE', text: 'Brandenburg' },\n    { value: 'DE-HB', country_code: 'DE', text: 'Bremen' },\n    { value: 'DE-HH', country_code: 'DE', text: 'Hamburg' },\n    { value: 'DE-HE', country_code: 'DE', text: 'Hessen' },\n    { value: 'DE-MV', country_code: 'DE', text: 'Mecklenburg-Vorpommern' },\n    { value: 'DE-NI', country_code: 'DE', text: 'Niedersachsen' },\n    { value: 'DE-NW', country_code: 'DE', text: 'Nordrhein-Westfalen' },\n    { value: 'DE-RP', country_code: 'DE', text: 'Rheinland-Pfalz' },\n    { value: 'DE-SL', country_code: 'DE', text: 'Saarland' },\n    { value: 'DE-SN', country_code: 'DE', text: 'Sachsen' },\n    { value: 'DE-ST', country_code: 'DE', text: 'Sachsen-Anhalt' },\n    { value: 'DE-SH', country_code: 'DE', text: 'Schleswig-Holstein' },\n    { value: 'DE-TH', country_code: 'DE', text: 'Thuringen' },\n    { value: 'DJ-AS', country_code: 'DJ', text: 'Ali Sabieh' },\n    { value: 'DJ-AR', country_code: 'DJ', text: 'Arta' },\n    { value: 'DJ-DI', country_code: 'DJ', text: 'Dikhil' },\n    { value: 'DJ-DJ', country_code: 'DJ', text: 'Djibouti' },\n    { value: 'DJ-OB', country_code: 'DJ', text: 'Obock' },\n    { value: 'DJ-TA', country_code: 'DJ', text: 'Tadjourah' },\n    { value: 'DK-84', country_code: 'DK', text: 'Hovedstaden' },\n    { value: 'DK-82', country_code: 'DK', text: 'Midtjylland' },\n    { value: 'DK-81', country_code: 'DK', text: 'Nordjylland' },\n    { value: 'DK-85', country_code: 'DK', text: 'Sjelland' },\n    { value: 'DK-83', country_code: 'DK', text: 'Syddanmark' },\n    { value: 'DM-02', country_code: 'DM', text: 'Saint Andrew' },\n    { value: 'DM-03', country_code: 'DM', text: 'Saint David' },\n    { value: 'DM-04', country_code: 'DM', text: 'Saint George' },\n    { value: 'DM-05', country_code: 'DM', text: 'Saint John' },\n    { value: 'DM-06', country_code: 'DM', text: 'Saint Joseph' },\n    { value: 'DM-07', country_code: 'DM', text: 'Saint Luke' },\n    { value: 'DM-08', country_code: 'DM', text: 'Saint Mark' },\n    { value: 'DM-09', country_code: 'DM', text: 'Saint Patrick' },\n    { value: 'DM-10', country_code: 'DM', text: 'Saint Paul' },\n    { value: 'DO-02', country_code: 'DO', text: 'Azua' },\n    { value: 'DO-03', country_code: 'DO', text: 'Baoruco' },\n    { value: 'DO-04', country_code: 'DO', text: 'Barahona' },\n    { value: 'DO-05', country_code: 'DO', text: 'Dajabon' },\n    {\n      value: 'DO-01',\n      country_code: 'DO',\n      text: 'Distrito Nacional (Santo Domingo)'\n    },\n    { value: 'DO-06', country_code: 'DO', text: 'Duarte' },\n    { value: 'DO-08', country_code: 'DO', text: 'El Seibo' },\n    { value: 'DO-07', country_code: 'DO', text: 'Elias Pina' },\n    { value: 'DO-09', country_code: 'DO', text: 'Espaillat' },\n    { value: 'DO-30', country_code: 'DO', text: 'Hato Mayor' },\n    { value: 'DO-19', country_code: 'DO', text: 'Hermanas Mirabal' },\n    { value: 'DO-10', country_code: 'DO', text: 'Independencia' },\n    { value: 'DO-11', country_code: 'DO', text: 'La Altagracia' },\n    { value: 'DO-12', country_code: 'DO', text: 'La Romana' },\n    { value: 'DO-13', country_code: 'DO', text: 'La Vega' },\n    { value: 'DO-14', country_code: 'DO', text: 'Maria Trinidad Sanchez' },\n    { value: 'DO-28', country_code: 'DO', text: 'Monsenor Nouel' },\n    { value: 'DO-15', country_code: 'DO', text: 'Monte Cristi' },\n    { value: 'DO-29', country_code: 'DO', text: 'Monte Plata' },\n    { value: 'DO-16', country_code: 'DO', text: 'Pedernales' },\n    { value: 'DO-17', country_code: 'DO', text: 'Peravia' },\n    { value: 'DO-18', country_code: 'DO', text: 'Puerto Plata' },\n    { value: 'DO-20', country_code: 'DO', text: 'Samana' },\n    { value: 'DO-21', country_code: 'DO', text: 'San Cristobal' },\n    { value: 'DO-22', country_code: 'DO', text: 'San Juan' },\n    { value: 'DO-23', country_code: 'DO', text: 'San Pedro de Macoris' },\n    { value: 'DO-24', country_code: 'DO', text: 'Sanchez Ramirez' },\n    { value: 'DO-25', country_code: 'DO', text: 'Santiago' },\n    { value: 'DO-26', country_code: 'DO', text: 'Santiago Rodriguez' },\n    { value: 'DO-27', country_code: 'DO', text: 'Valverde' },\n    { value: 'DZ-01', country_code: 'DZ', text: 'Adrar' },\n    { value: 'DZ-44', country_code: 'DZ', text: 'Ain Defla' },\n    { value: 'DZ-46', country_code: 'DZ', text: 'Ain Temouchent' },\n    { value: 'DZ-16', country_code: 'DZ', text: 'Alger' },\n    { value: 'DZ-23', country_code: 'DZ', text: 'Annaba' },\n    { value: 'DZ-05', country_code: 'DZ', text: 'Batna' },\n    { value: 'DZ-08', country_code: 'DZ', text: 'Bechar' },\n    { value: 'DZ-06', country_code: 'DZ', text: 'Bejaia' },\n    { value: 'DZ-07', country_code: 'DZ', text: 'Biskra' },\n    { value: 'DZ-09', country_code: 'DZ', text: 'Blida' },\n    { value: 'DZ-34', country_code: 'DZ', text: 'Bordj Bou Arreridj' },\n    { value: 'DZ-10', country_code: 'DZ', text: 'Bouira' },\n    { value: 'DZ-35', country_code: 'DZ', text: 'Boumerdes' },\n    { value: 'DZ-02', country_code: 'DZ', text: 'Chlef' },\n    { value: 'DZ-25', country_code: 'DZ', text: 'Constantine' },\n    { value: 'DZ-17', country_code: 'DZ', text: 'Djelfa' },\n    { value: 'DZ-32', country_code: 'DZ', text: 'El Bayadh' },\n    { value: 'DZ-39', country_code: 'DZ', text: 'El Oued' },\n    { value: 'DZ-36', country_code: 'DZ', text: 'El Tarf' },\n    { value: 'DZ-47', country_code: 'DZ', text: 'Ghardaia' },\n    { value: 'DZ-24', country_code: 'DZ', text: 'Guelma' },\n    { value: 'DZ-33', country_code: 'DZ', text: 'Illizi' },\n    { value: 'DZ-40', country_code: 'DZ', text: 'Khenchela' },\n    { value: 'DZ-03', country_code: 'DZ', text: 'Laghouat' },\n    { value: 'DZ-28', country_code: 'DZ', text: \"M'sila\" },\n    { value: 'DZ-29', country_code: 'DZ', text: 'Mascara' },\n    { value: 'DZ-26', country_code: 'DZ', text: 'Medea' },\n    { value: 'DZ-43', country_code: 'DZ', text: 'Mila' },\n    { value: 'DZ-27', country_code: 'DZ', text: 'Mostaganem' },\n    { value: 'DZ-45', country_code: 'DZ', text: 'Naama' },\n    { value: 'DZ-31', country_code: 'DZ', text: 'Oran' },\n    { value: 'DZ-30', country_code: 'DZ', text: 'Ouargla' },\n    { value: 'DZ-04', country_code: 'DZ', text: 'Oum el Bouaghi' },\n    { value: 'DZ-48', country_code: 'DZ', text: 'Relizane' },\n    { value: 'DZ-20', country_code: 'DZ', text: 'Saida' },\n    { value: 'DZ-19', country_code: 'DZ', text: 'Setif' },\n    { value: 'DZ-22', country_code: 'DZ', text: 'Sidi Bel Abbes' },\n    { value: 'DZ-21', country_code: 'DZ', text: 'Skikda' },\n    { value: 'DZ-41', country_code: 'DZ', text: 'Souk Ahras' },\n    { value: 'DZ-11', country_code: 'DZ', text: 'Tamanrasset' },\n    { value: 'DZ-12', country_code: 'DZ', text: 'Tebessa' },\n    { value: 'DZ-14', country_code: 'DZ', text: 'Tiaret' },\n    { value: 'DZ-37', country_code: 'DZ', text: 'Tindouf' },\n    { value: 'DZ-42', country_code: 'DZ', text: 'Tipaza' },\n    { value: 'DZ-38', country_code: 'DZ', text: 'Tissemsilt' },\n    { value: 'DZ-15', country_code: 'DZ', text: 'Tizi Ouzou' },\n    { value: 'DZ-13', country_code: 'DZ', text: 'Tlemcen' },\n    { value: 'EC-A', country_code: 'EC', text: 'Azuay' },\n    { value: 'EC-B', country_code: 'EC', text: 'Bolivar' },\n    { value: 'EC-F', country_code: 'EC', text: 'Canar' },\n    { value: 'EC-C', country_code: 'EC', text: 'Carchi' },\n    { value: 'EC-H', country_code: 'EC', text: 'Chimborazo' },\n    { value: 'EC-X', country_code: 'EC', text: 'Cotopaxi' },\n    { value: 'EC-O', country_code: 'EC', text: 'El Oro' },\n    { value: 'EC-E', country_code: 'EC', text: 'Esmeraldas' },\n    { value: 'EC-W', country_code: 'EC', text: 'Galapagos' },\n    { value: 'EC-G', country_code: 'EC', text: 'Guayas' },\n    { value: 'EC-I', country_code: 'EC', text: 'Imbabura' },\n    { value: 'EC-L', country_code: 'EC', text: 'Loja' },\n    { value: 'EC-R', country_code: 'EC', text: 'Los Rios' },\n    { value: 'EC-M', country_code: 'EC', text: 'Manabi' },\n    { value: 'EC-S', country_code: 'EC', text: 'Morona-Santiago' },\n    { value: 'EC-N', country_code: 'EC', text: 'Napo' },\n    { value: 'EC-D', country_code: 'EC', text: 'Orellana' },\n    { value: 'EC-Y', country_code: 'EC', text: 'Pastaza' },\n    { value: 'EC-P', country_code: 'EC', text: 'Pichincha' },\n    { value: 'EC-SE', country_code: 'EC', text: 'Santa Elena' },\n    { value: 'EC-U', country_code: 'EC', text: 'Sucumbios' },\n    { value: 'EC-T', country_code: 'EC', text: 'Tungurahua' },\n    { value: 'EC-Z', country_code: 'EC', text: 'Zamora-Chinchipe' },\n    { value: 'EE-37', country_code: 'EE', text: 'Harjumaa' },\n    { value: 'EE-39', country_code: 'EE', text: 'Hiiumaa' },\n    { value: 'EE-44', country_code: 'EE', text: 'Ida-Virumaa' },\n    { value: 'EE-51', country_code: 'EE', text: 'Jarvamaa' },\n    { value: 'EE-49', country_code: 'EE', text: 'Jogevamaa' },\n    { value: 'EE-59', country_code: 'EE', text: 'Laane-Virumaa' },\n    { value: 'EE-57', country_code: 'EE', text: 'Laanemaa' },\n    { value: 'EE-67', country_code: 'EE', text: 'Parnumaa' },\n    { value: 'EE-65', country_code: 'EE', text: 'Polvamaa' },\n    { value: 'EE-70', country_code: 'EE', text: 'Raplamaa' },\n    { value: 'EE-74', country_code: 'EE', text: 'Saaremaa' },\n    { value: 'EE-78', country_code: 'EE', text: 'Tartumaa' },\n    { value: 'EE-82', country_code: 'EE', text: 'Valgamaa' },\n    { value: 'EE-84', country_code: 'EE', text: 'Viljandimaa' },\n    { value: 'EE-86', country_code: 'EE', text: 'Vorumaa' },\n    { value: 'EG-DK', country_code: 'EG', text: 'Ad Daqahliyah' },\n    { value: 'EG-BA', country_code: 'EG', text: 'Al Bahr al Ahmar' },\n    { value: 'EG-BH', country_code: 'EG', text: 'Al Buhayrah' },\n    { value: 'EG-FYM', country_code: 'EG', text: 'Al Fayyum' },\n    { value: 'EG-GH', country_code: 'EG', text: 'Al Gharbiyah' },\n    { value: 'EG-ALX', country_code: 'EG', text: 'Al Iskandariyah' },\n    { value: 'EG-IS', country_code: 'EG', text: \"Al Isma'iliyah\" },\n    { value: 'EG-GZ', country_code: 'EG', text: 'Al Jizah' },\n    { value: 'EG-MNF', country_code: 'EG', text: 'Al Minufiyah' },\n    { value: 'EG-MN', country_code: 'EG', text: 'Al Minya' },\n    { value: 'EG-C', country_code: 'EG', text: 'Al Qahirah' },\n    { value: 'EG-KB', country_code: 'EG', text: 'Al Qalyubiyah' },\n    { value: 'EG-LX', country_code: 'EG', text: 'Al Uqsur' },\n    { value: 'EG-WAD', country_code: 'EG', text: 'Al Wadi al Jadid' },\n    { value: 'EG-SUZ', country_code: 'EG', text: 'As Suways' },\n    { value: 'EG-SHR', country_code: 'EG', text: 'Ash Sharqiyah' },\n    { value: 'EG-ASN', country_code: 'EG', text: 'Aswan' },\n    { value: 'EG-AST', country_code: 'EG', text: 'Asyut' },\n    { value: 'EG-BNS', country_code: 'EG', text: 'Bani Suwayf' },\n    { value: 'EG-PTS', country_code: 'EG', text: \"Bur Sa'id\" },\n    { value: 'EG-DT', country_code: 'EG', text: 'Dumyat' },\n    { value: 'EG-JS', country_code: 'EG', text: \"Janub Sina'\" },\n    { value: 'EG-KFS', country_code: 'EG', text: 'Kafr ash Shaykh' },\n    { value: 'EG-MT', country_code: 'EG', text: 'Matruh' },\n    { value: 'EG-KN', country_code: 'EG', text: 'Qina' },\n    { value: 'EG-SIN', country_code: 'EG', text: \"Shamal Sina'\" },\n    { value: 'EG-SHG', country_code: 'EG', text: 'Suhaj' },\n    { value: 'ER-MA', country_code: 'ER', text: 'Al Awsat' },\n    { value: 'ER-DU', country_code: 'ER', text: 'Al Janubi' },\n    { value: 'ER-AN', country_code: 'ER', text: 'Ansaba' },\n    { value: 'ER-DK', country_code: 'ER', text: 'Janubi al Bahri al Ahmar' },\n    { value: 'ER-GB', country_code: 'ER', text: 'Qash-Barkah' },\n    { value: 'ER-SK', country_code: 'ER', text: 'Shimali al Bahri al Ahmar' },\n    { value: 'ES-AN', country_code: 'ES', text: 'Andalucia' },\n    { value: 'ES-AR', country_code: 'ES', text: 'Aragon' },\n    { value: 'ES-AS', country_code: 'ES', text: 'Asturias, Principado de' },\n    { value: 'ES-CN', country_code: 'ES', text: 'Canarias' },\n    { value: 'ES-CB', country_code: 'ES', text: 'Cantabria' },\n    { value: 'ES-CL', country_code: 'ES', text: 'Castilla y Leon' },\n    { value: 'ES-CM', country_code: 'ES', text: 'Castilla-La Mancha' },\n    { value: 'ES-CT', country_code: 'ES', text: 'Catalunya' },\n    { value: 'ES-CE', country_code: 'ES', text: 'Ceuta' },\n    { value: 'ES-EX', country_code: 'ES', text: 'Extremadura' },\n    { value: 'ES-GA', country_code: 'ES', text: 'Galicia' },\n    { value: 'ES-IB', country_code: 'ES', text: 'Illes Balears' },\n    { value: 'ES-RI', country_code: 'ES', text: 'La Rioja' },\n    { value: 'ES-MD', country_code: 'ES', text: 'Madrid, Comunidad de' },\n    { value: 'ES-ML', country_code: 'ES', text: 'Melilla' },\n    { value: 'ES-MC', country_code: 'ES', text: 'Murcia, Region de' },\n    { value: 'ES-NC', country_code: 'ES', text: 'Navarra, Comunidad Foral de' },\n    { value: 'ES-PV', country_code: 'ES', text: 'Pais Vasco' },\n    { value: 'ES-VC', country_code: 'ES', text: 'Valenciana, Comunidad' },\n    { value: 'ET-AA', country_code: 'ET', text: 'Adis Abeba' },\n    { value: 'ET-AF', country_code: 'ET', text: 'Afar' },\n    { value: 'ET-AM', country_code: 'ET', text: 'Amara' },\n    { value: 'ET-BE', country_code: 'ET', text: 'Binshangul Gumuz' },\n    { value: 'ET-DD', country_code: 'ET', text: 'Dire Dawa' },\n    { value: 'ET-GA', country_code: 'ET', text: 'Gambela Hizboch' },\n    { value: 'ET-HA', country_code: 'ET', text: 'Hareri Hizb' },\n    { value: 'ET-OR', country_code: 'ET', text: 'Oromiya' },\n    { value: 'ET-SO', country_code: 'ET', text: 'Sumale' },\n    { value: 'ET-TI', country_code: 'ET', text: 'Tigray' },\n    {\n      value: 'ET-SN',\n      country_code: 'ET',\n      text: 'YeDebub Biheroch Bihereseboch na Hizboch'\n    },\n    { value: 'FI-02', country_code: 'FI', text: 'Etela-Karjala' },\n    { value: 'FI-03', country_code: 'FI', text: 'Etela-Pohjanmaa' },\n    { value: 'FI-04', country_code: 'FI', text: 'Etela-Savo' },\n    { value: 'FI-05', country_code: 'FI', text: 'Kainuu' },\n    { value: 'FI-06', country_code: 'FI', text: 'Kanta-Hame' },\n    { value: 'FI-07', country_code: 'FI', text: 'Keski-Pohjanmaa' },\n    { value: 'FI-08', country_code: 'FI', text: 'Keski-Suomi' },\n    { value: 'FI-09', country_code: 'FI', text: 'Kymenlaakso' },\n    { value: 'FI-10', country_code: 'FI', text: 'Lappi' },\n    { value: 'FI-16', country_code: 'FI', text: 'Paijat-Hame' },\n    { value: 'FI-11', country_code: 'FI', text: 'Pirkanmaa' },\n    { value: 'FI-12', country_code: 'FI', text: 'Pohjanmaa' },\n    { value: 'FI-13', country_code: 'FI', text: 'Pohjois-Karjala' },\n    { value: 'FI-14', country_code: 'FI', text: 'Pohjois-Pohjanmaa' },\n    { value: 'FI-15', country_code: 'FI', text: 'Pohjois-Savo' },\n    { value: 'FI-17', country_code: 'FI', text: 'Satakunta' },\n    { value: 'FI-18', country_code: 'FI', text: 'Uusimaa' },\n    { value: 'FI-19', country_code: 'FI', text: 'Varsinais-Suomi' },\n    { value: 'FJ-C', country_code: 'FJ', text: 'Central' },\n    { value: 'FJ-N', country_code: 'FJ', text: 'Northern' },\n    { value: 'FJ-W', country_code: 'FJ', text: 'Western' },\n    { value: 'FM-TRK', country_code: 'FM', text: 'Chuuk' },\n    { value: 'FM-KSA', country_code: 'FM', text: 'Kosrae' },\n    { value: 'FM-PNI', country_code: 'FM', text: 'Pohnpei' },\n    { value: 'FM-YAP', country_code: 'FM', text: 'Yap' },\n    { value: 'FR-ARA', country_code: 'FR', text: 'Auvergne-Rhone-Alpes' },\n    { value: 'FR-BFC', country_code: 'FR', text: 'Bourgogne-Franche-Comte' },\n    { value: 'FR-E', country_code: 'FR', text: 'Bretagne' },\n    { value: 'FR-CVL', country_code: 'FR', text: 'Centre-Val de Loire' },\n    { value: 'FR-H', country_code: 'FR', text: 'Corse' },\n    { value: 'FR-GES', country_code: 'FR', text: 'Grand-Est' },\n    { value: 'FR-HDF', country_code: 'FR', text: 'Hauts-de-France' },\n    { value: 'FR-J', country_code: 'FR', text: 'Ile-de-France' },\n    { value: 'FR-NOR', country_code: 'FR', text: 'Normandie' },\n    { value: 'FR-NAQ', country_code: 'FR', text: 'Nouvelle-Aquitaine' },\n    { value: 'FR-OCC', country_code: 'FR', text: 'Occitanie' },\n    { value: 'FR-R', country_code: 'FR', text: 'Pays-de-la-Loire' },\n    { value: 'FR-PAC', country_code: 'FR', text: \"Provence-Alpes-Cote d'Azur\" },\n    { value: 'GA-1', country_code: 'GA', text: 'Estuaire' },\n    { value: 'GA-2', country_code: 'GA', text: 'Haut-Ogooue' },\n    { value: 'GA-3', country_code: 'GA', text: 'Moyen-Ogooue' },\n    { value: 'GA-4', country_code: 'GA', text: 'Ngounie' },\n    { value: 'GA-5', country_code: 'GA', text: 'Nyanga' },\n    { value: 'GA-6', country_code: 'GA', text: 'Ogooue-Ivindo' },\n    { value: 'GA-7', country_code: 'GA', text: 'Ogooue-Lolo' },\n    { value: 'GA-8', country_code: 'GA', text: 'Ogooue-Maritime' },\n    { value: 'GA-9', country_code: 'GA', text: 'Woleu-Ntem' },\n    { value: 'GB-ENG', country_code: 'GB', text: 'England' },\n    { value: 'GB-NIR', country_code: 'GB', text: 'Northern Ireland' },\n    { value: 'GB-SCT', country_code: 'GB', text: 'Scotland' },\n    { value: 'GB-WLS', country_code: 'GB', text: 'Wales' },\n    { value: 'GD-01', country_code: 'GD', text: 'Saint Andrew' },\n    { value: 'GD-02', country_code: 'GD', text: 'Saint David' },\n    { value: 'GD-03', country_code: 'GD', text: 'Saint George' },\n    { value: 'GD-04', country_code: 'GD', text: 'Saint John' },\n    { value: 'GD-05', country_code: 'GD', text: 'Saint Mark' },\n    { value: 'GD-06', country_code: 'GD', text: 'Saint Patrick' },\n    { value: 'GE-AB', country_code: 'GE', text: 'Abkhazia' },\n    { value: 'GE-AJ', country_code: 'GE', text: 'Ajaria' },\n    { value: 'GE-GU', country_code: 'GE', text: 'Guria' },\n    { value: 'GE-IM', country_code: 'GE', text: 'Imereti' },\n    { value: 'GE-KA', country_code: 'GE', text: \"K'akheti\" },\n    { value: 'GE-KK', country_code: 'GE', text: 'Kvemo Kartli' },\n    { value: 'GE-MM', country_code: 'GE', text: 'Mtskheta-Mtianeti' },\n    {\n      value: 'GE-RL',\n      country_code: 'GE',\n      text: \"Rach'a-Lechkhumi-Kvemo Svaneti\"\n    },\n    { value: 'GE-SZ', country_code: 'GE', text: 'Samegrelo-Zemo Svaneti' },\n    { value: 'GE-SJ', country_code: 'GE', text: 'Samtskhe-Javakheti' },\n    { value: 'GE-SK', country_code: 'GE', text: 'Shida Kartli' },\n    { value: 'GE-TB', country_code: 'GE', text: 'Tbilisi' },\n    { value: 'GH-AH', country_code: 'GH', text: 'Ashanti' },\n    { value: 'GH-BA', country_code: 'GH', text: 'Brong-Ahafo' },\n    { value: 'GH-CP', country_code: 'GH', text: 'Central' },\n    { value: 'GH-EP', country_code: 'GH', text: 'Eastern' },\n    { value: 'GH-AA', country_code: 'GH', text: 'Greater Accra' },\n    { value: 'GH-NP', country_code: 'GH', text: 'Northern' },\n    { value: 'GH-UE', country_code: 'GH', text: 'Upper East' },\n    { value: 'GH-UW', country_code: 'GH', text: 'Upper West' },\n    { value: 'GH-TV', country_code: 'GH', text: 'Volta' },\n    { value: 'GH-WP', country_code: 'GH', text: 'Western' },\n    { value: 'GL-KU', country_code: 'GL', text: 'Kommune Kujalleq' },\n    { value: 'GL-SM', country_code: 'GL', text: 'Kommuneqarfik Sermersooq' },\n    { value: 'GL-QA', country_code: 'GL', text: 'Qaasuitsup Kommunia' },\n    { value: 'GL-QE', country_code: 'GL', text: 'Qeqqata Kommunia' },\n    { value: 'GM-B', country_code: 'GM', text: 'Banjul' },\n    { value: 'GM-M', country_code: 'GM', text: 'Central River' },\n    { value: 'GM-L', country_code: 'GM', text: 'Lower River' },\n    { value: 'GM-N', country_code: 'GM', text: 'North Bank' },\n    { value: 'GM-U', country_code: 'GM', text: 'Upper River' },\n    { value: 'GM-W', country_code: 'GM', text: 'Western' },\n    { value: 'GN-BE', country_code: 'GN', text: 'Beyla' },\n    { value: 'GN-BF', country_code: 'GN', text: 'Boffa' },\n    { value: 'GN-B', country_code: 'GN', text: 'Boke' },\n    { value: 'GN-C', country_code: 'GN', text: 'Conakry' },\n    { value: 'GN-CO', country_code: 'GN', text: 'Coyah' },\n    { value: 'GN-DB', country_code: 'GN', text: 'Dabola' },\n    { value: 'GN-DL', country_code: 'GN', text: 'Dalaba' },\n    { value: 'GN-DI', country_code: 'GN', text: 'Dinguiraye' },\n    { value: 'GN-DU', country_code: 'GN', text: 'Dubreka' },\n    { value: 'GN-F', country_code: 'GN', text: 'Faranah' },\n    { value: 'GN-FO', country_code: 'GN', text: 'Forecariah' },\n    { value: 'GN-FR', country_code: 'GN', text: 'Fria' },\n    { value: 'GN-GA', country_code: 'GN', text: 'Gaoual' },\n    { value: 'GN-GU', country_code: 'GN', text: 'Guekedou' },\n    { value: 'GN-K', country_code: 'GN', text: 'Kankan' },\n    { value: 'GN-KE', country_code: 'GN', text: 'Kerouane' },\n    { value: 'GN-D', country_code: 'GN', text: 'Kindia' },\n    { value: 'GN-KS', country_code: 'GN', text: 'Kissidougou' },\n    { value: 'GN-KB', country_code: 'GN', text: 'Koubia' },\n    { value: 'GN-KN', country_code: 'GN', text: 'Koundara' },\n    { value: 'GN-KO', country_code: 'GN', text: 'Kouroussa' },\n    { value: 'GN-L', country_code: 'GN', text: 'Labe' },\n    { value: 'GN-LE', country_code: 'GN', text: 'Lelouma' },\n    { value: 'GN-LO', country_code: 'GN', text: 'Lola' },\n    { value: 'GN-MC', country_code: 'GN', text: 'Macenta' },\n    { value: 'GN-ML', country_code: 'GN', text: 'Mali' },\n    { value: 'GN-M', country_code: 'GN', text: 'Mamou' },\n    { value: 'GN-MD', country_code: 'GN', text: 'Mandiana' },\n    { value: 'GN-N', country_code: 'GN', text: 'Nzerekore' },\n    { value: 'GN-PI', country_code: 'GN', text: 'Pita' },\n    { value: 'GN-SI', country_code: 'GN', text: 'Siguiri' },\n    { value: 'GN-TE', country_code: 'GN', text: 'Telimele' },\n    { value: 'GN-TO', country_code: 'GN', text: 'Tougue' },\n    { value: 'GN-YO', country_code: 'GN', text: 'Yomou' },\n    { value: 'GQ-AN', country_code: 'GQ', text: 'Annobon' },\n    { value: 'GQ-BN', country_code: 'GQ', text: 'Bioko Norte' },\n    { value: 'GQ-BS', country_code: 'GQ', text: 'Bioko Sur' },\n    { value: 'GQ-CS', country_code: 'GQ', text: 'Centro Sur' },\n    { value: 'GQ-KN', country_code: 'GQ', text: 'Kie-Ntem' },\n    { value: 'GQ-LI', country_code: 'GQ', text: 'Litoral' },\n    { value: 'GQ-WN', country_code: 'GQ', text: 'Wele-Nzas' },\n    {\n      value: 'GR-A',\n      country_code: 'GR',\n      text: 'Anatoliki Makedonia kai Thraki'\n    },\n    { value: 'GR-I', country_code: 'GR', text: 'Attiki' },\n    { value: 'GR-G', country_code: 'GR', text: 'Dytiki Ellada' },\n    { value: 'GR-C', country_code: 'GR', text: 'Dytiki Makedonia' },\n    { value: 'GR-F', country_code: 'GR', text: 'Ionia Nisia' },\n    { value: 'GR-D', country_code: 'GR', text: 'Ipeiros' },\n    { value: 'GR-B', country_code: 'GR', text: 'Kentriki Makedonia' },\n    { value: 'GR-M', country_code: 'GR', text: 'Kriti' },\n    { value: 'GR-L', country_code: 'GR', text: 'Notio Aigaio' },\n    { value: 'GR-J', country_code: 'GR', text: 'Peloponnisos' },\n    { value: 'GR-H', country_code: 'GR', text: 'Sterea Ellada' },\n    { value: 'GR-E', country_code: 'GR', text: 'Thessalia' },\n    { value: 'GR-K', country_code: 'GR', text: 'Voreio Aigaio' },\n    { value: 'GT-AV', country_code: 'GT', text: 'Alta Verapaz' },\n    { value: 'GT-BV', country_code: 'GT', text: 'Baja Verapaz' },\n    { value: 'GT-CM', country_code: 'GT', text: 'Chimaltenango' },\n    { value: 'GT-CQ', country_code: 'GT', text: 'Chiquimula' },\n    { value: 'GT-PR', country_code: 'GT', text: 'El Progreso' },\n    { value: 'GT-ES', country_code: 'GT', text: 'Escuintla' },\n    { value: 'GT-GU', country_code: 'GT', text: 'Guatemala' },\n    { value: 'GT-HU', country_code: 'GT', text: 'Huehuetenango' },\n    { value: 'GT-IZ', country_code: 'GT', text: 'Izabal' },\n    { value: 'GT-JA', country_code: 'GT', text: 'Jalapa' },\n    { value: 'GT-JU', country_code: 'GT', text: 'Jutiapa' },\n    { value: 'GT-PE', country_code: 'GT', text: 'Peten' },\n    { value: 'GT-QZ', country_code: 'GT', text: 'Quetzaltenango' },\n    { value: 'GT-QC', country_code: 'GT', text: 'Quiche' },\n    { value: 'GT-RE', country_code: 'GT', text: 'Retalhuleu' },\n    { value: 'GT-SA', country_code: 'GT', text: 'Sacatepequez' },\n    { value: 'GT-SM', country_code: 'GT', text: 'San Marcos' },\n    { value: 'GT-SR', country_code: 'GT', text: 'Santa Rosa' },\n    { value: 'GT-SO', country_code: 'GT', text: 'Solola' },\n    { value: 'GT-SU', country_code: 'GT', text: 'Suchitepequez' },\n    { value: 'GT-TO', country_code: 'GT', text: 'Totonicapan' },\n    { value: 'GT-ZA', country_code: 'GT', text: 'Zacapa' },\n    { value: 'GW-BA', country_code: 'GW', text: 'Bafata' },\n    { value: 'GW-BM', country_code: 'GW', text: 'Biombo' },\n    { value: 'GW-BS', country_code: 'GW', text: 'Bissau' },\n    { value: 'GW-BL', country_code: 'GW', text: 'Bolama' },\n    { value: 'GW-CA', country_code: 'GW', text: 'Cacheu' },\n    { value: 'GW-GA', country_code: 'GW', text: 'Gabu' },\n    { value: 'GW-OI', country_code: 'GW', text: 'Oio' },\n    { value: 'GW-QU', country_code: 'GW', text: 'Quinara' },\n    { value: 'GW-TO', country_code: 'GW', text: 'Tombali' },\n    { value: 'GY-CU', country_code: 'GY', text: 'Cuyuni-Mazaruni' },\n    { value: 'GY-DE', country_code: 'GY', text: 'Demerara-Mahaica' },\n    { value: 'GY-EB', country_code: 'GY', text: 'East Berbice-Corentyne' },\n    {\n      value: 'GY-ES',\n      country_code: 'GY',\n      text: 'Essequibo Islands-West Demerara'\n    },\n    { value: 'GY-MA', country_code: 'GY', text: 'Mahaica-Berbice' },\n    { value: 'GY-PM', country_code: 'GY', text: 'Pomeroon-Supenaam' },\n    { value: 'GY-UD', country_code: 'GY', text: 'Upper Demerara-Berbice' },\n    { value: 'HN-AT', country_code: 'HN', text: 'Atlantida' },\n    { value: 'HN-CH', country_code: 'HN', text: 'Choluteca' },\n    { value: 'HN-CL', country_code: 'HN', text: 'Colon' },\n    { value: 'HN-CM', country_code: 'HN', text: 'Comayagua' },\n    { value: 'HN-CP', country_code: 'HN', text: 'Copan' },\n    { value: 'HN-CR', country_code: 'HN', text: 'Cortes' },\n    { value: 'HN-EP', country_code: 'HN', text: 'El Paraiso' },\n    { value: 'HN-FM', country_code: 'HN', text: 'Francisco Morazan' },\n    { value: 'HN-GD', country_code: 'HN', text: 'Gracias a Dios' },\n    { value: 'HN-IN', country_code: 'HN', text: 'Intibuca' },\n    { value: 'HN-IB', country_code: 'HN', text: 'Islas de la Bahia' },\n    { value: 'HN-LP', country_code: 'HN', text: 'La Paz' },\n    { value: 'HN-LE', country_code: 'HN', text: 'Lempira' },\n    { value: 'HN-OC', country_code: 'HN', text: 'Ocotepeque' },\n    { value: 'HN-OL', country_code: 'HN', text: 'Olancho' },\n    { value: 'HN-SB', country_code: 'HN', text: 'Santa Barbara' },\n    { value: 'HN-VA', country_code: 'HN', text: 'Valle' },\n    { value: 'HN-YO', country_code: 'HN', text: 'Yoro' },\n    {\n      value: 'HR-07',\n      country_code: 'HR',\n      text: 'Bjelovarsko-bilogorska zupanija'\n    },\n    { value: 'HR-12', country_code: 'HR', text: 'Brodsko-posavska zupanija' },\n    {\n      value: 'HR-19',\n      country_code: 'HR',\n      text: 'Dubrovacko-neretvanska zupanija'\n    },\n    { value: 'HR-21', country_code: 'HR', text: 'Grad Zagreb' },\n    { value: 'HR-18', country_code: 'HR', text: 'Istarska zupanija' },\n    { value: 'HR-04', country_code: 'HR', text: 'Karlovacka zupanija' },\n    {\n      value: 'HR-06',\n      country_code: 'HR',\n      text: 'Koprivnicko-krizevacka zupanija'\n    },\n    { value: 'HR-02', country_code: 'HR', text: 'Krapinsko-zagorska zupanija' },\n    { value: 'HR-09', country_code: 'HR', text: 'Licko-senjska zupanija' },\n    { value: 'HR-20', country_code: 'HR', text: 'Medimurska zupanija' },\n    { value: 'HR-14', country_code: 'HR', text: 'Osjecko-baranjska zupanija' },\n    { value: 'HR-11', country_code: 'HR', text: 'Pozesko-slavonska zupanija' },\n    { value: 'HR-08', country_code: 'HR', text: 'Primorsko-goranska zupanija' },\n    { value: 'HR-15', country_code: 'HR', text: 'Sibensko-kninska zupanija' },\n    { value: 'HR-03', country_code: 'HR', text: 'Sisacko-moslavacka zupanija' },\n    {\n      value: 'HR-17',\n      country_code: 'HR',\n      text: 'Splitsko-dalmatinska zupanija'\n    },\n    { value: 'HR-05', country_code: 'HR', text: 'Varazdinska zupanija' },\n    {\n      value: 'HR-10',\n      country_code: 'HR',\n      text: 'Viroviticko-podravska zupanija'\n    },\n    {\n      value: 'HR-16',\n      country_code: 'HR',\n      text: 'Vukovarsko-srijemska zupanija'\n    },\n    { value: 'HR-13', country_code: 'HR', text: 'Zadarska zupanija' },\n    { value: 'HR-01', country_code: 'HR', text: 'Zagrebacka zupanija' },\n    { value: 'HT-AR', country_code: 'HT', text: 'Artibonite' },\n    { value: 'HT-CE', country_code: 'HT', text: 'Centre' },\n    { value: 'HT-GA', country_code: 'HT', text: \"Grande'Anse\" },\n    { value: 'HT-NI', country_code: 'HT', text: 'Nippes' },\n    { value: 'HT-ND', country_code: 'HT', text: 'Nord' },\n    { value: 'HT-NE', country_code: 'HT', text: 'Nord-Est' },\n    { value: 'HT-NO', country_code: 'HT', text: 'Nord-Ouest' },\n    { value: 'HT-OU', country_code: 'HT', text: 'Ouest' },\n    { value: 'HT-SD', country_code: 'HT', text: 'Sud' },\n    { value: 'HT-SE', country_code: 'HT', text: 'Sud-Est' },\n    { value: 'HU-BK', country_code: 'HU', text: 'Bacs-Kiskun' },\n    { value: 'HU-BA', country_code: 'HU', text: 'Baranya' },\n    { value: 'HU-BE', country_code: 'HU', text: 'Bekes' },\n    { value: 'HU-BZ', country_code: 'HU', text: 'Borsod-Abauj-Zemplen' },\n    { value: 'HU-BU', country_code: 'HU', text: 'Budapest' },\n    { value: 'HU-CS', country_code: 'HU', text: 'Csongrad' },\n    { value: 'HU-FE', country_code: 'HU', text: 'Fejer' },\n    { value: 'HU-GS', country_code: 'HU', text: 'Gyor-Moson-Sopron' },\n    { value: 'HU-HB', country_code: 'HU', text: 'Hajdu-Bihar' },\n    { value: 'HU-HE', country_code: 'HU', text: 'Heves' },\n    { value: 'HU-JN', country_code: 'HU', text: 'Jasz-Nagykun-Szolnok' },\n    { value: 'HU-KE', country_code: 'HU', text: 'Komarom-Esztergom' },\n    { value: 'HU-NO', country_code: 'HU', text: 'Nograd' },\n    { value: 'HU-PE', country_code: 'HU', text: 'Pest' },\n    { value: 'HU-SO', country_code: 'HU', text: 'Somogy' },\n    { value: 'HU-SZ', country_code: 'HU', text: 'Szabolcs-Szatmar-Bereg' },\n    { value: 'HU-TO', country_code: 'HU', text: 'Tolna' },\n    { value: 'HU-VA', country_code: 'HU', text: 'Vas' },\n    { value: 'HU-VM', country_code: 'HU', text: 'Veszprem' },\n    { value: 'HU-ZA', country_code: 'HU', text: 'Zala' },\n    { value: 'ID-AC', country_code: 'ID', text: 'Aceh' },\n    { value: 'ID-BA', country_code: 'ID', text: 'Bali' },\n    { value: 'ID-BT', country_code: 'ID', text: 'Banten' },\n    { value: 'ID-BE', country_code: 'ID', text: 'Bengkulu' },\n    { value: 'ID-GO', country_code: 'ID', text: 'Gorontalo' },\n    { value: 'ID-JK', country_code: 'ID', text: 'Jakarta Raya' },\n    { value: 'ID-JA', country_code: 'ID', text: 'Jambi' },\n    { value: 'ID-JB', country_code: 'ID', text: 'Jawa Barat' },\n    { value: 'ID-JT', country_code: 'ID', text: 'Jawa Tengah' },\n    { value: 'ID-JI', country_code: 'ID', text: 'Jawa Timur' },\n    { value: 'ID-KB', country_code: 'ID', text: 'Kalimantan Barat' },\n    { value: 'ID-KS', country_code: 'ID', text: 'Kalimantan Selatan' },\n    { value: 'ID-KT', country_code: 'ID', text: 'Kalimantan Tengah' },\n    { value: 'ID-KI', country_code: 'ID', text: 'Kalimantan Timur' },\n    { value: 'ID-BB', country_code: 'ID', text: 'Kepulauan Bangka Belitung' },\n    { value: 'ID-KR', country_code: 'ID', text: 'Kepulauan Riau' },\n    { value: 'ID-LA', country_code: 'ID', text: 'Lampung' },\n    { value: 'ID-ML', country_code: 'ID', text: 'Maluku' },\n    { value: 'ID-MU', country_code: 'ID', text: 'Maluku Utara' },\n    { value: 'ID-NB', country_code: 'ID', text: 'Nusa Tenggara Barat' },\n    { value: 'ID-NT', country_code: 'ID', text: 'Nusa Tenggara Timur' },\n    { value: 'ID-PP', country_code: 'ID', text: 'Papua' },\n    { value: 'ID-PB', country_code: 'ID', text: 'Papua Barat' },\n    { value: 'ID-RI', country_code: 'ID', text: 'Riau' },\n    { value: 'ID-SR', country_code: 'ID', text: 'Sulawesi Barat' },\n    { value: 'ID-SN', country_code: 'ID', text: 'Sulawesi Selatan' },\n    { value: 'ID-ST', country_code: 'ID', text: 'Sulawesi Tengah' },\n    { value: 'ID-SG', country_code: 'ID', text: 'Sulawesi Tenggara' },\n    { value: 'ID-SA', country_code: 'ID', text: 'Sulawesi Utara' },\n    { value: 'ID-SB', country_code: 'ID', text: 'Sumatera Barat' },\n    { value: 'ID-SS', country_code: 'ID', text: 'Sumatera Selatan' },\n    { value: 'ID-SU', country_code: 'ID', text: 'Sumatera Utara' },\n    { value: 'ID-YO', country_code: 'ID', text: 'Yogyakarta' },\n    { value: 'IE-CW', country_code: 'IE', text: 'Carlow' },\n    { value: 'IE-CN', country_code: 'IE', text: 'Cavan' },\n    { value: 'IE-CE', country_code: 'IE', text: 'Clare' },\n    { value: 'IE-CO', country_code: 'IE', text: 'Cork' },\n    { value: 'IE-DL', country_code: 'IE', text: 'Donegal' },\n    { value: 'IE-D', country_code: 'IE', text: 'Dublin' },\n    { value: 'IE-G', country_code: 'IE', text: 'Galway' },\n    { value: 'IE-KY', country_code: 'IE', text: 'Kerry' },\n    { value: 'IE-KE', country_code: 'IE', text: 'Kildare' },\n    { value: 'IE-KK', country_code: 'IE', text: 'Kilkenny' },\n    { value: 'IE-LS', country_code: 'IE', text: 'Laois' },\n    { value: 'IE-LM', country_code: 'IE', text: 'Leitrim' },\n    { value: 'IE-LK', country_code: 'IE', text: 'Limerick' },\n    { value: 'IE-LD', country_code: 'IE', text: 'Longford' },\n    { value: 'IE-LH', country_code: 'IE', text: 'Louth' },\n    { value: 'IE-MO', country_code: 'IE', text: 'Mayo' },\n    { value: 'IE-MH', country_code: 'IE', text: 'Meath' },\n    { value: 'IE-MN', country_code: 'IE', text: 'Monaghan' },\n    { value: 'IE-OY', country_code: 'IE', text: 'Offaly' },\n    { value: 'IE-RN', country_code: 'IE', text: 'Roscommon' },\n    { value: 'IE-SO', country_code: 'IE', text: 'Sligo' },\n    { value: 'IE-TA', country_code: 'IE', text: 'Tipperary' },\n    { value: 'IE-WD', country_code: 'IE', text: 'Waterford' },\n    { value: 'IE-WH', country_code: 'IE', text: 'Westmeath' },\n    { value: 'IE-WX', country_code: 'IE', text: 'Wexford' },\n    { value: 'IE-WW', country_code: 'IE', text: 'Wicklow' },\n    { value: 'IL-D', country_code: 'IL', text: 'HaDarom' },\n    { value: 'IL-M', country_code: 'IL', text: 'HaMerkaz' },\n    { value: 'IL-Z', country_code: 'IL', text: 'HaTsafon' },\n    { value: 'IL-HA', country_code: 'IL', text: 'Hefa' },\n    { value: 'IL-TA', country_code: 'IL', text: 'Tel Aviv' },\n    { value: 'IL-JM', country_code: 'IL', text: 'Yerushalayim' },\n    { value: 'IN-AN', country_code: 'IN', text: 'Andaman and Nicobar Islands' },\n    { value: 'IN-AP', country_code: 'IN', text: 'Andhra Pradesh' },\n    { value: 'IN-AR', country_code: 'IN', text: 'Arunachal Pradesh' },\n    { value: 'IN-AS', country_code: 'IN', text: 'Assam' },\n    { value: 'IN-BR', country_code: 'IN', text: 'Bihar' },\n    { value: 'IN-CH', country_code: 'IN', text: 'Chandigarh' },\n    { value: 'IN-CT', country_code: 'IN', text: 'Chhattisgarh' },\n    { value: 'IN-DN', country_code: 'IN', text: 'Dadra and Nagar Haveli' },\n    { value: 'IN-DD', country_code: 'IN', text: 'Daman and Diu' },\n    { value: 'IN-DL', country_code: 'IN', text: 'Delhi' },\n    { value: 'IN-GA', country_code: 'IN', text: 'Goa' },\n    { value: 'IN-GJ', country_code: 'IN', text: 'Gujarat' },\n    { value: 'IN-HR', country_code: 'IN', text: 'Haryana' },\n    { value: 'IN-HP', country_code: 'IN', text: 'Himachal Pradesh' },\n    { value: 'IN-JK', country_code: 'IN', text: 'Jammu and Kashmir' },\n    { value: 'IN-JH', country_code: 'IN', text: 'Jharkhand' },\n    { value: 'IN-KA', country_code: 'IN', text: 'Karnataka' },\n    { value: 'IN-KL', country_code: 'IN', text: 'Kerala' },\n    { value: 'IN-LD', country_code: 'IN', text: 'Lakshadweep' },\n    { value: 'IN-MP', country_code: 'IN', text: 'Madhya Pradesh' },\n    { value: 'IN-MH', country_code: 'IN', text: 'Maharashtra' },\n    { value: 'IN-MN', country_code: 'IN', text: 'Manipur' },\n    { value: 'IN-ML', country_code: 'IN', text: 'Meghalaya' },\n    { value: 'IN-MZ', country_code: 'IN', text: 'Mizoram' },\n    { value: 'IN-NL', country_code: 'IN', text: 'Nagaland' },\n    { value: 'IN-OR', country_code: 'IN', text: 'Odisha' },\n    { value: 'IN-PY', country_code: 'IN', text: 'Puducherry' },\n    { value: 'IN-PB', country_code: 'IN', text: 'Punjab' },\n    { value: 'IN-RJ', country_code: 'IN', text: 'Rajasthan' },\n    { value: 'IN-SK', country_code: 'IN', text: 'Sikkim' },\n    { value: 'IN-TN', country_code: 'IN', text: 'Tamil Nadu' },\n    { value: 'IN-TG', country_code: 'IN', text: 'Telangana' },\n    { value: 'IN-TR', country_code: 'IN', text: 'Tripura' },\n    { value: 'IN-UP', country_code: 'IN', text: 'Uttar Pradesh' },\n    { value: 'IN-UT', country_code: 'IN', text: 'Uttarakhand' },\n    { value: 'IN-WB', country_code: 'IN', text: 'West Bengal' },\n    { value: 'IQ-AN', country_code: 'IQ', text: 'Al Anbar' },\n    { value: 'IQ-BA', country_code: 'IQ', text: 'Al Basrah' },\n    { value: 'IQ-MU', country_code: 'IQ', text: 'Al Muthanna' },\n    { value: 'IQ-QA', country_code: 'IQ', text: 'Al Qadisiyah' },\n    { value: 'IQ-NA', country_code: 'IQ', text: 'An Najaf' },\n    { value: 'IQ-AR', country_code: 'IQ', text: 'Arbil' },\n    { value: 'IQ-SU', country_code: 'IQ', text: 'As Sulaymaniyah' },\n    { value: 'IQ-BB', country_code: 'IQ', text: 'Babil' },\n    { value: 'IQ-BG', country_code: 'IQ', text: 'Baghdad' },\n    { value: 'IQ-DA', country_code: 'IQ', text: 'Dahuk' },\n    { value: 'IQ-DQ', country_code: 'IQ', text: 'Dhi Qar' },\n    { value: 'IQ-DI', country_code: 'IQ', text: 'Diyala' },\n    { value: 'IQ-KA', country_code: 'IQ', text: \"Karbala'\" },\n    { value: 'IQ-KI', country_code: 'IQ', text: 'Kirkuk' },\n    { value: 'IQ-MA', country_code: 'IQ', text: 'Maysan' },\n    { value: 'IQ-NI', country_code: 'IQ', text: 'Ninawa' },\n    { value: 'IQ-SD', country_code: 'IQ', text: 'Salah ad Din' },\n    { value: 'IQ-WA', country_code: 'IQ', text: 'Wasit' },\n    { value: 'IR-32', country_code: 'IR', text: 'Alborz' },\n    { value: 'IR-03', country_code: 'IR', text: 'Ardabil' },\n    { value: 'IR-02', country_code: 'IR', text: 'Azarbayjan-e Gharbi' },\n    { value: 'IR-01', country_code: 'IR', text: 'Azarbayjan-e Sharqi' },\n    { value: 'IR-06', country_code: 'IR', text: 'Bushehr' },\n    { value: 'IR-08', country_code: 'IR', text: 'Chahar Mahal va Bakhtiari' },\n    { value: 'IR-04', country_code: 'IR', text: 'Esfahan' },\n    { value: 'IR-14', country_code: 'IR', text: 'Fars' },\n    { value: 'IR-19', country_code: 'IR', text: 'Gilan' },\n    { value: 'IR-27', country_code: 'IR', text: 'Golestan' },\n    { value: 'IR-24', country_code: 'IR', text: 'Hamadan' },\n    { value: 'IR-23', country_code: 'IR', text: 'Hormozgan' },\n    { value: 'IR-05', country_code: 'IR', text: 'Ilam' },\n    { value: 'IR-15', country_code: 'IR', text: 'Kerman' },\n    { value: 'IR-17', country_code: 'IR', text: 'Kermanshah' },\n    { value: 'IR-29', country_code: 'IR', text: 'Khorasan-e Jonubi' },\n    { value: 'IR-30', country_code: 'IR', text: 'Khorasan-e Razavi' },\n    { value: 'IR-31', country_code: 'IR', text: 'Khorasan-e Shomali' },\n    { value: 'IR-10', country_code: 'IR', text: 'Khuzestan' },\n    { value: 'IR-18', country_code: 'IR', text: 'Kohgiluyeh va Bowyer Ahmad' },\n    { value: 'IR-16', country_code: 'IR', text: 'Kordestan' },\n    { value: 'IR-20', country_code: 'IR', text: 'Lorestan' },\n    { value: 'IR-22', country_code: 'IR', text: 'Markazi' },\n    { value: 'IR-21', country_code: 'IR', text: 'Mazandaran' },\n    { value: 'IR-28', country_code: 'IR', text: 'Qazvin' },\n    { value: 'IR-26', country_code: 'IR', text: 'Qom' },\n    { value: 'IR-12', country_code: 'IR', text: 'Semnan' },\n    { value: 'IR-13', country_code: 'IR', text: 'Sistan va Baluchestan' },\n    { value: 'IR-07', country_code: 'IR', text: 'Tehran' },\n    { value: 'IR-25', country_code: 'IR', text: 'Yazd' },\n    { value: 'IR-11', country_code: 'IR', text: 'Zanjan' },\n    { value: 'IS-7', country_code: 'IS', text: 'Austurland' },\n    {\n      value: 'IS-1',\n      country_code: 'IS',\n      text: 'Hofudborgarsvaedi utan Reykjavikur'\n    },\n    { value: 'IS-6', country_code: 'IS', text: 'Nordurland eystra' },\n    { value: 'IS-5', country_code: 'IS', text: 'Nordurland vestra' },\n    { value: 'IS-8', country_code: 'IS', text: 'Sudurland' },\n    { value: 'IS-2', country_code: 'IS', text: 'Sudurnes' },\n    { value: 'IS-4', country_code: 'IS', text: 'Vestfirdir' },\n    { value: 'IS-3', country_code: 'IS', text: 'Vesturland' },\n    { value: 'IT-65', country_code: 'IT', text: 'Abruzzo' },\n    { value: 'IT-77', country_code: 'IT', text: 'Basilicata' },\n    { value: 'IT-78', country_code: 'IT', text: 'Calabria' },\n    { value: 'IT-72', country_code: 'IT', text: 'Campania' },\n    { value: 'IT-45', country_code: 'IT', text: 'Emilia-Romagna' },\n    { value: 'IT-36', country_code: 'IT', text: 'Friuli-Venezia Giulia' },\n    { value: 'IT-62', country_code: 'IT', text: 'Lazio' },\n    { value: 'IT-42', country_code: 'IT', text: 'Liguria' },\n    { value: 'IT-25', country_code: 'IT', text: 'Lombardia' },\n    { value: 'IT-57', country_code: 'IT', text: 'Marche' },\n    { value: 'IT-67', country_code: 'IT', text: 'Molise' },\n    { value: 'IT-21', country_code: 'IT', text: 'Piemonte' },\n    { value: 'IT-75', country_code: 'IT', text: 'Puglia' },\n    { value: 'IT-88', country_code: 'IT', text: 'Sardegna' },\n    { value: 'IT-82', country_code: 'IT', text: 'Sicilia' },\n    { value: 'IT-52', country_code: 'IT', text: 'Toscana' },\n    { value: 'IT-32', country_code: 'IT', text: 'Trentino-Alto Adige' },\n    { value: 'IT-55', country_code: 'IT', text: 'Umbria' },\n    { value: 'IT-23', country_code: 'IT', text: \"Valle d'Aosta\" },\n    { value: 'IT-34', country_code: 'IT', text: 'Veneto' },\n    { value: 'JM-13', country_code: 'JM', text: 'Clarendon' },\n    { value: 'JM-09', country_code: 'JM', text: 'Hanover' },\n    { value: 'JM-01', country_code: 'JM', text: 'Kingston' },\n    { value: 'JM-12', country_code: 'JM', text: 'Manchester' },\n    { value: 'JM-04', country_code: 'JM', text: 'Portland' },\n    { value: 'JM-02', country_code: 'JM', text: 'Saint Andrew' },\n    { value: 'JM-06', country_code: 'JM', text: 'Saint Ann' },\n    { value: 'JM-14', country_code: 'JM', text: 'Saint Catherine' },\n    { value: 'JM-11', country_code: 'JM', text: 'Saint Elizabeth' },\n    { value: 'JM-08', country_code: 'JM', text: 'Saint James' },\n    { value: 'JM-05', country_code: 'JM', text: 'Saint Mary' },\n    { value: 'JM-03', country_code: 'JM', text: 'Saint Thomas' },\n    { value: 'JM-07', country_code: 'JM', text: 'Trelawny' },\n    { value: 'JM-10', country_code: 'JM', text: 'Westmoreland' },\n    { value: 'JO-AQ', country_code: 'JO', text: \"Al 'Aqabah\" },\n    { value: 'JO-AM', country_code: 'JO', text: \"Al 'Asimah\" },\n    { value: 'JO-BA', country_code: 'JO', text: \"Al Balqa'\" },\n    { value: 'JO-KA', country_code: 'JO', text: 'Al Karak' },\n    { value: 'JO-MA', country_code: 'JO', text: 'Al Mafraq' },\n    { value: 'JO-AT', country_code: 'JO', text: 'At Tafilah' },\n    { value: 'JO-AZ', country_code: 'JO', text: \"Az Zarqa'\" },\n    { value: 'JO-IR', country_code: 'JO', text: 'Irbid' },\n    { value: 'JO-MN', country_code: 'JO', text: \"Ma'an\" },\n    { value: 'JO-MD', country_code: 'JO', text: 'Madaba' },\n    { value: 'JP-23', country_code: 'JP', text: 'Aichi' },\n    { value: 'JP-05', country_code: 'JP', text: 'Akita' },\n    { value: 'JP-02', country_code: 'JP', text: 'Aomori' },\n    { value: 'JP-12', country_code: 'JP', text: 'Chiba' },\n    { value: 'JP-38', country_code: 'JP', text: 'Ehime' },\n    { value: 'JP-18', country_code: 'JP', text: 'Fukui' },\n    { value: 'JP-40', country_code: 'JP', text: 'Fukuoka' },\n    { value: 'JP-07', country_code: 'JP', text: 'Fukushima' },\n    { value: 'JP-21', country_code: 'JP', text: 'Gifu' },\n    { value: 'JP-10', country_code: 'JP', text: 'Gunma' },\n    { value: 'JP-34', country_code: 'JP', text: 'Hiroshima' },\n    { value: 'JP-01', country_code: 'JP', text: 'Hokkaido' },\n    { value: 'JP-28', country_code: 'JP', text: 'Hyogo' },\n    { value: 'JP-08', country_code: 'JP', text: 'Ibaraki' },\n    { value: 'JP-17', country_code: 'JP', text: 'Ishikawa' },\n    { value: 'JP-03', country_code: 'JP', text: 'Iwate' },\n    { value: 'JP-37', country_code: 'JP', text: 'Kagawa' },\n    { value: 'JP-46', country_code: 'JP', text: 'Kagoshima' },\n    { value: 'JP-14', country_code: 'JP', text: 'Kanagawa' },\n    { value: 'JP-39', country_code: 'JP', text: 'Kochi' },\n    { value: 'JP-43', country_code: 'JP', text: 'Kumamoto' },\n    { value: 'JP-26', country_code: 'JP', text: 'Kyoto' },\n    { value: 'JP-24', country_code: 'JP', text: 'Mie' },\n    { value: 'JP-04', country_code: 'JP', text: 'Miyagi' },\n    { value: 'JP-45', country_code: 'JP', text: 'Miyazaki' },\n    { value: 'JP-20', country_code: 'JP', text: 'Nagano' },\n    { value: 'JP-42', country_code: 'JP', text: 'Nagasaki' },\n    { value: 'JP-29', country_code: 'JP', text: 'Nara' },\n    { value: 'JP-15', country_code: 'JP', text: 'Niigata' },\n    { value: 'JP-44', country_code: 'JP', text: 'Oita' },\n    { value: 'JP-33', country_code: 'JP', text: 'Okayama' },\n    { value: 'JP-47', country_code: 'JP', text: 'Okinawa' },\n    { value: 'JP-27', country_code: 'JP', text: 'Osaka' },\n    { value: 'JP-41', country_code: 'JP', text: 'Saga' },\n    { value: 'JP-11', country_code: 'JP', text: 'Saitama' },\n    { value: 'JP-25', country_code: 'JP', text: 'Shiga' },\n    { value: 'JP-32', country_code: 'JP', text: 'Shimane' },\n    { value: 'JP-22', country_code: 'JP', text: 'Shizuoka' },\n    { value: 'JP-09', country_code: 'JP', text: 'Tochigi' },\n    { value: 'JP-36', country_code: 'JP', text: 'Tokushima' },\n    { value: 'JP-13', country_code: 'JP', text: 'Tokyo' },\n    { value: 'JP-31', country_code: 'JP', text: 'Tottori' },\n    { value: 'JP-16', country_code: 'JP', text: 'Toyama' },\n    { value: 'JP-30', country_code: 'JP', text: 'Wakayama' },\n    { value: 'JP-06', country_code: 'JP', text: 'Yamagata' },\n    { value: 'JP-35', country_code: 'JP', text: 'Yamaguchi' },\n    { value: 'JP-19', country_code: 'JP', text: 'Yamanashi' },\n    { value: 'KE-01', country_code: 'KE', text: 'Baringo' },\n    { value: 'KE-02', country_code: 'KE', text: 'Bomet' },\n    { value: 'KE-03', country_code: 'KE', text: 'Bungoma' },\n    { value: 'KE-04', country_code: 'KE', text: 'Busia' },\n    { value: 'KE-06', country_code: 'KE', text: 'Embu' },\n    { value: 'KE-07', country_code: 'KE', text: 'Garissa' },\n    { value: 'KE-08', country_code: 'KE', text: 'Homa Bay' },\n    { value: 'KE-09', country_code: 'KE', text: 'Isiolo' },\n    { value: 'KE-10', country_code: 'KE', text: 'Kajiado' },\n    { value: 'KE-11', country_code: 'KE', text: 'Kakamega' },\n    { value: 'KE-12', country_code: 'KE', text: 'Kericho' },\n    { value: 'KE-13', country_code: 'KE', text: 'Kiambu' },\n    { value: 'KE-14', country_code: 'KE', text: 'Kilifi' },\n    { value: 'KE-15', country_code: 'KE', text: 'Kirinyaga' },\n    { value: 'KE-16', country_code: 'KE', text: 'Kisii' },\n    { value: 'KE-17', country_code: 'KE', text: 'Kisumu' },\n    { value: 'KE-18', country_code: 'KE', text: 'Kitui' },\n    { value: 'KE-19', country_code: 'KE', text: 'Kwale' },\n    { value: 'KE-20', country_code: 'KE', text: 'Laikipia' },\n    { value: 'KE-21', country_code: 'KE', text: 'Lamu' },\n    { value: 'KE-22', country_code: 'KE', text: 'Machakos' },\n    { value: 'KE-23', country_code: 'KE', text: 'Makueni' },\n    { value: 'KE-24', country_code: 'KE', text: 'Mandera' },\n    { value: 'KE-25', country_code: 'KE', text: 'Marsabit' },\n    { value: 'KE-26', country_code: 'KE', text: 'Meru' },\n    { value: 'KE-27', country_code: 'KE', text: 'Migori' },\n    { value: 'KE-28', country_code: 'KE', text: 'Mombasa' },\n    { value: 'KE-29', country_code: 'KE', text: \"Murang'a\" },\n    { value: 'KE-30', country_code: 'KE', text: 'Nairobi City' },\n    { value: 'KE-31', country_code: 'KE', text: 'Nakuru' },\n    { value: 'KE-32', country_code: 'KE', text: 'Nandi' },\n    { value: 'KE-33', country_code: 'KE', text: 'Narok' },\n    { value: 'KE-34', country_code: 'KE', text: 'Nyamira' },\n    { value: 'KE-36', country_code: 'KE', text: 'Nyeri' },\n    { value: 'KE-37', country_code: 'KE', text: 'Samburu' },\n    { value: 'KE-38', country_code: 'KE', text: 'Siaya' },\n    { value: 'KE-39', country_code: 'KE', text: 'Taita/Taveta' },\n    { value: 'KE-40', country_code: 'KE', text: 'Tana River' },\n    { value: 'KE-41', country_code: 'KE', text: 'Tharaka-Nithi' },\n    { value: 'KE-42', country_code: 'KE', text: 'Trans Nzoia' },\n    { value: 'KE-43', country_code: 'KE', text: 'Turkana' },\n    { value: 'KE-44', country_code: 'KE', text: 'Uasin Gishu' },\n    { value: 'KE-45', country_code: 'KE', text: 'Vihiga' },\n    { value: 'KE-46', country_code: 'KE', text: 'Wajir' },\n    { value: 'KE-47', country_code: 'KE', text: 'West Pokot' },\n    { value: 'KG-B', country_code: 'KG', text: 'Batken' },\n    { value: 'KG-GB', country_code: 'KG', text: 'Bishkek' },\n    { value: 'KG-C', country_code: 'KG', text: 'Chuy' },\n    { value: 'KG-J', country_code: 'KG', text: 'Jalal-Abad' },\n    { value: 'KG-N', country_code: 'KG', text: 'Naryn' },\n    { value: 'KG-GO', country_code: 'KG', text: 'Osh' },\n    { value: 'KG-T', country_code: 'KG', text: 'Talas' },\n    { value: 'KG-Y', country_code: 'KG', text: 'Ysyk-Kol' },\n    { value: 'KH-2', country_code: 'KH', text: 'Baat Dambang' },\n    { value: 'KH-1', country_code: 'KH', text: 'Banteay Mean Chey' },\n    { value: 'KH-3', country_code: 'KH', text: 'Kampong Chaam' },\n    { value: 'KH-4', country_code: 'KH', text: 'Kampong Chhnang' },\n    { value: 'KH-5', country_code: 'KH', text: 'Kampong Spueu' },\n    { value: 'KH-6', country_code: 'KH', text: 'Kampong Thum' },\n    { value: 'KH-7', country_code: 'KH', text: 'Kampot' },\n    { value: 'KH-8', country_code: 'KH', text: 'Kandaal' },\n    { value: 'KH-9', country_code: 'KH', text: 'Kaoh Kong' },\n    { value: 'KH-10', country_code: 'KH', text: 'Kracheh' },\n    { value: 'KH-23', country_code: 'KH', text: 'Krong Kaeb' },\n    { value: 'KH-24', country_code: 'KH', text: 'Krong Pailin' },\n    { value: 'KH-18', country_code: 'KH', text: 'Krong Preah Sihanouk' },\n    { value: 'KH-11', country_code: 'KH', text: 'Mondol Kiri' },\n    { value: 'KH-22', country_code: 'KH', text: 'Otdar Mean Chey' },\n    { value: 'KH-12', country_code: 'KH', text: 'Phnom Penh' },\n    { value: 'KH-15', country_code: 'KH', text: 'Pousaat' },\n    { value: 'KH-13', country_code: 'KH', text: 'Preah Vihear' },\n    { value: 'KH-14', country_code: 'KH', text: 'Prey Veaeng' },\n    { value: 'KH-16', country_code: 'KH', text: 'Rotanak Kiri' },\n    { value: 'KH-17', country_code: 'KH', text: 'Siem Reab' },\n    { value: 'KH-19', country_code: 'KH', text: 'Stueng Traeng' },\n    { value: 'KH-20', country_code: 'KH', text: 'Svaay Rieng' },\n    { value: 'KH-21', country_code: 'KH', text: 'Taakaev' },\n    { value: 'KI-G', country_code: 'KI', text: 'Gilbert Islands' },\n    { value: 'KI-L', country_code: 'KI', text: 'Line Islands' },\n    { value: 'KM-A', country_code: 'KM', text: 'Anjouan' },\n    { value: 'KM-G', country_code: 'KM', text: 'Grande Comore' },\n    { value: 'KM-M', country_code: 'KM', text: 'Moheli' },\n    { value: 'KN-03', country_code: 'KN', text: 'Saint George Basseterre' },\n    { value: 'KN-10', country_code: 'KN', text: 'Saint Paul Charlestown' },\n    { value: 'KP-04', country_code: 'KP', text: 'Chagang-do' },\n    { value: 'KP-09', country_code: 'KP', text: 'Hamgyong-bukto' },\n    { value: 'KP-08', country_code: 'KP', text: 'Hamgyong-namdo' },\n    { value: 'KP-06', country_code: 'KP', text: 'Hwanghae-bukto' },\n    { value: 'KP-05', country_code: 'KP', text: 'Hwanghae-namdo' },\n    { value: 'KP-07', country_code: 'KP', text: 'Kangwon-do' },\n    { value: 'KP-13', country_code: 'KP', text: 'Nason' },\n    { value: 'KP-03', country_code: 'KP', text: \"P'yongan-bukto\" },\n    { value: 'KP-02', country_code: 'KP', text: \"P'yongan-namdo\" },\n    { value: 'KP-01', country_code: 'KP', text: \"P'yongyang\" },\n    { value: 'KP-10', country_code: 'KP', text: 'Yanggang-do' },\n    { value: 'KR-26', country_code: 'KR', text: 'Busan-gwangyeoksi' },\n    { value: 'KR-43', country_code: 'KR', text: 'Chungcheongbuk-do' },\n    { value: 'KR-44', country_code: 'KR', text: 'Chungcheongnam-do' },\n    { value: 'KR-27', country_code: 'KR', text: 'Daegu-gwangyeoksi' },\n    { value: 'KR-30', country_code: 'KR', text: 'Daejeon-gwangyeoksi' },\n    { value: 'KR-42', country_code: 'KR', text: 'Gangwon-do' },\n    { value: 'KR-29', country_code: 'KR', text: 'Gwangju-gwangyeoksi' },\n    { value: 'KR-41', country_code: 'KR', text: 'Gyeonggi-do' },\n    { value: 'KR-47', country_code: 'KR', text: 'Gyeongsangbuk-do' },\n    { value: 'KR-48', country_code: 'KR', text: 'Gyeongsangnam-do' },\n    { value: 'KR-28', country_code: 'KR', text: 'Incheon-gwangyeoksi' },\n    { value: 'KR-49', country_code: 'KR', text: 'Jeju-teukbyeoljachido' },\n    { value: 'KR-45', country_code: 'KR', text: 'Jeollabuk-do' },\n    { value: 'KR-46', country_code: 'KR', text: 'Jeollanam-do' },\n    { value: 'KR-11', country_code: 'KR', text: 'Seoul-teukbyeolsi' },\n    { value: 'KR-31', country_code: 'KR', text: 'Ulsan-gwangyeoksi' },\n    { value: 'KW-KU', country_code: 'KW', text: \"Al 'Asimah\" },\n    { value: 'KW-AH', country_code: 'KW', text: 'Al Ahmadi' },\n    { value: 'KW-FA', country_code: 'KW', text: 'Al Farwaniyah' },\n    { value: 'KW-JA', country_code: 'KW', text: 'Al Jahra' },\n    { value: 'KW-HA', country_code: 'KW', text: 'Hawalli' },\n    { value: 'KW-MU', country_code: 'KW', text: 'Mubarak al Kabir' },\n    { value: 'KZ-ALA', country_code: 'KZ', text: 'Almaty' },\n    { value: 'KZ-ALM', country_code: 'KZ', text: 'Almaty oblysy' },\n    { value: 'KZ-AKM', country_code: 'KZ', text: 'Aqmola oblysy' },\n    { value: 'KZ-AKT', country_code: 'KZ', text: 'Aqtobe oblysy' },\n    { value: 'KZ-AST', country_code: 'KZ', text: 'Astana' },\n    { value: 'KZ-ATY', country_code: 'KZ', text: 'Atyrau oblysy' },\n    { value: 'KZ-ZAP', country_code: 'KZ', text: 'Batys Qazaqstan oblysy' },\n    { value: 'KZ-BAY', country_code: 'KZ', text: 'Bayqongyr' },\n    { value: 'KZ-MAN', country_code: 'KZ', text: 'Mangghystau oblysy' },\n    { value: 'KZ-YUZ', country_code: 'KZ', text: 'Ongtustik Qazaqstan oblysy' },\n    { value: 'KZ-PAV', country_code: 'KZ', text: 'Pavlodar oblysy' },\n    { value: 'KZ-KAR', country_code: 'KZ', text: 'Qaraghandy oblysy' },\n    { value: 'KZ-KUS', country_code: 'KZ', text: 'Qostanay oblysy' },\n    { value: 'KZ-KZY', country_code: 'KZ', text: 'Qyzylorda oblysy' },\n    { value: 'KZ-VOS', country_code: 'KZ', text: 'Shyghys Qazaqstan oblysy' },\n    { value: 'KZ-SEV', country_code: 'KZ', text: 'Soltustik Qazaqstan oblysy' },\n    { value: 'KZ-ZHA', country_code: 'KZ', text: 'Zhambyl oblysy' },\n    { value: 'LA-AT', country_code: 'LA', text: 'Attapu' },\n    { value: 'LA-BK', country_code: 'LA', text: 'Bokeo' },\n    { value: 'LA-BL', country_code: 'LA', text: 'Bolikhamxai' },\n    { value: 'LA-CH', country_code: 'LA', text: 'Champasak' },\n    { value: 'LA-HO', country_code: 'LA', text: 'Houaphan' },\n    { value: 'LA-KH', country_code: 'LA', text: 'Khammouan' },\n    { value: 'LA-LM', country_code: 'LA', text: 'Louang Namtha' },\n    { value: 'LA-LP', country_code: 'LA', text: 'Louangphabang' },\n    { value: 'LA-OU', country_code: 'LA', text: 'Oudomxai' },\n    { value: 'LA-PH', country_code: 'LA', text: 'Phongsali' },\n    { value: 'LA-SL', country_code: 'LA', text: 'Salavan' },\n    { value: 'LA-SV', country_code: 'LA', text: 'Savannakhet' },\n    { value: 'LA-VI', country_code: 'LA', text: 'Viangchan' },\n    { value: 'LA-XA', country_code: 'LA', text: 'Xaignabouli' },\n    { value: 'LA-XE', country_code: 'LA', text: 'Xekong' },\n    { value: 'LA-XI', country_code: 'LA', text: 'Xiangkhouang' },\n    { value: 'LB-AK', country_code: 'LB', text: 'Aakkar' },\n    { value: 'LB-BH', country_code: 'LB', text: 'Baalbek-Hermel' },\n    { value: 'LB-BI', country_code: 'LB', text: 'Beqaa' },\n    { value: 'LB-BA', country_code: 'LB', text: 'Beyrouth' },\n    { value: 'LB-AS', country_code: 'LB', text: 'Liban-Nord' },\n    { value: 'LB-JA', country_code: 'LB', text: 'Liban-Sud' },\n    { value: 'LB-JL', country_code: 'LB', text: 'Mont-Liban' },\n    { value: 'LB-NA', country_code: 'LB', text: 'Nabatiye' },\n    { value: 'LC-01', country_code: 'LC', text: 'Anse la Raye' },\n    { value: 'LC-02', country_code: 'LC', text: 'Castries' },\n    { value: 'LC-05', country_code: 'LC', text: 'Dennery' },\n    { value: 'LC-06', country_code: 'LC', text: 'Gros Islet' },\n    { value: 'LC-07', country_code: 'LC', text: 'Laborie' },\n    { value: 'LC-08', country_code: 'LC', text: 'Micoud' },\n    { value: 'LC-10', country_code: 'LC', text: 'Soufriere' },\n    { value: 'LC-11', country_code: 'LC', text: 'Vieux Fort' },\n    { value: 'LI-01', country_code: 'LI', text: 'Balzers' },\n    { value: 'LI-02', country_code: 'LI', text: 'Eschen' },\n    { value: 'LI-03', country_code: 'LI', text: 'Gamprin' },\n    { value: 'LI-04', country_code: 'LI', text: 'Mauren' },\n    { value: 'LI-05', country_code: 'LI', text: 'Planken' },\n    { value: 'LI-06', country_code: 'LI', text: 'Ruggell' },\n    { value: 'LI-07', country_code: 'LI', text: 'Schaan' },\n    { value: 'LI-08', country_code: 'LI', text: 'Schellenberg' },\n    { value: 'LI-09', country_code: 'LI', text: 'Triesen' },\n    { value: 'LI-10', country_code: 'LI', text: 'Triesenberg' },\n    { value: 'LI-11', country_code: 'LI', text: 'Vaduz' },\n    { value: 'LK-2', country_code: 'LK', text: 'Central Province' },\n    { value: 'LK-5', country_code: 'LK', text: 'Eastern Province' },\n    { value: 'LK-7', country_code: 'LK', text: 'North Central Province' },\n    { value: 'LK-6', country_code: 'LK', text: 'North Western Province' },\n    { value: 'LK-4', country_code: 'LK', text: 'Northern Province' },\n    { value: 'LK-9', country_code: 'LK', text: 'Sabaragamuwa Province' },\n    { value: 'LK-3', country_code: 'LK', text: 'Southern Province' },\n    { value: 'LK-8', country_code: 'LK', text: 'Uva Province' },\n    { value: 'LK-1', country_code: 'LK', text: 'Western Province' },\n    { value: 'LR-BM', country_code: 'LR', text: 'Bomi' },\n    { value: 'LR-BG', country_code: 'LR', text: 'Bong' },\n    { value: 'LR-GP', country_code: 'LR', text: 'Gbarpolu' },\n    { value: 'LR-GB', country_code: 'LR', text: 'Grand Bassa' },\n    { value: 'LR-CM', country_code: 'LR', text: 'Grand Cape Mount' },\n    { value: 'LR-GG', country_code: 'LR', text: 'Grand Gedeh' },\n    { value: 'LR-GK', country_code: 'LR', text: 'Grand Kru' },\n    { value: 'LR-LO', country_code: 'LR', text: 'Lofa' },\n    { value: 'LR-MG', country_code: 'LR', text: 'Margibi' },\n    { value: 'LR-MY', country_code: 'LR', text: 'Maryland' },\n    { value: 'LR-MO', country_code: 'LR', text: 'Montserrado' },\n    { value: 'LR-NI', country_code: 'LR', text: 'Nimba' },\n    { value: 'LR-RI', country_code: 'LR', text: 'River Cess' },\n    { value: 'LR-RG', country_code: 'LR', text: 'River Gee' },\n    { value: 'LR-SI', country_code: 'LR', text: 'Sinoe' },\n    { value: 'LS-D', country_code: 'LS', text: 'Berea' },\n    { value: 'LS-B', country_code: 'LS', text: 'Butha-Buthe' },\n    { value: 'LS-C', country_code: 'LS', text: 'Leribe' },\n    { value: 'LS-E', country_code: 'LS', text: 'Mafeteng' },\n    { value: 'LS-A', country_code: 'LS', text: 'Maseru' },\n    { value: 'LS-F', country_code: 'LS', text: \"Mohale's Hoek\" },\n    { value: 'LS-J', country_code: 'LS', text: 'Mokhotlong' },\n    { value: 'LS-H', country_code: 'LS', text: \"Qacha's Nek\" },\n    { value: 'LS-G', country_code: 'LS', text: 'Quthing' },\n    { value: 'LS-K', country_code: 'LS', text: 'Thaba-Tseka' },\n    { value: 'LT-AL', country_code: 'LT', text: 'Alytaus apskritis' },\n    { value: 'LT-KU', country_code: 'LT', text: 'Kauno apskritis' },\n    { value: 'LT-KL', country_code: 'LT', text: 'Klaipedos apskritis' },\n    { value: 'LT-MR', country_code: 'LT', text: 'Marijampoles apskritis' },\n    { value: 'LT-PN', country_code: 'LT', text: 'Panevezio apskritis' },\n    { value: 'LT-SA', country_code: 'LT', text: 'Siauliu apskritis' },\n    { value: 'LT-TA', country_code: 'LT', text: 'Taurages apskritis' },\n    { value: 'LT-TE', country_code: 'LT', text: 'Telsiu apskritis' },\n    { value: 'LT-UT', country_code: 'LT', text: 'Utenos apskritis' },\n    { value: 'LT-VL', country_code: 'LT', text: 'Vilniaus apskritis' },\n    { value: 'LU-DI', country_code: 'LU', text: 'Diekirch' },\n    { value: 'LU-GR', country_code: 'LU', text: 'Grevenmacher' },\n    { value: 'LU-LU', country_code: 'LU', text: 'Luxembourg' },\n    { value: 'LV-011', country_code: 'LV', text: 'Adazu novads' },\n    { value: 'LV-001', country_code: 'LV', text: 'Aglonas novads' },\n    { value: 'LV-002', country_code: 'LV', text: 'Aizkraukles novads' },\n    { value: 'LV-003', country_code: 'LV', text: 'Aizputes novads' },\n    { value: 'LV-005', country_code: 'LV', text: 'Alojas novads' },\n    { value: 'LV-007', country_code: 'LV', text: 'Aluksnes novads' },\n    { value: 'LV-012', country_code: 'LV', text: 'Babites novads' },\n    { value: 'LV-014', country_code: 'LV', text: 'Baltinavas novads' },\n    { value: 'LV-015', country_code: 'LV', text: 'Balvu novads' },\n    { value: 'LV-016', country_code: 'LV', text: 'Bauskas novads' },\n    { value: 'LV-017', country_code: 'LV', text: 'Beverinas novads' },\n    { value: 'LV-018', country_code: 'LV', text: 'Brocenu novads' },\n    { value: 'LV-020', country_code: 'LV', text: 'Carnikavas novads' },\n    { value: 'LV-022', country_code: 'LV', text: 'Cesu novads' },\n    { value: 'LV-021', country_code: 'LV', text: 'Cesvaines novads' },\n    { value: 'LV-023', country_code: 'LV', text: 'Ciblas novads' },\n    { value: 'LV-025', country_code: 'LV', text: 'Daugavpils novads' },\n    { value: 'LV-026', country_code: 'LV', text: 'Dobeles novads' },\n    { value: 'LV-027', country_code: 'LV', text: 'Dundagas novads' },\n    { value: 'LV-033', country_code: 'LV', text: 'Gulbenes novads' },\n    { value: 'LV-034', country_code: 'LV', text: 'Iecavas novads' },\n    { value: 'LV-037', country_code: 'LV', text: 'Incukalna novads' },\n    { value: 'LV-038', country_code: 'LV', text: 'Jaunjelgavas novads' },\n    { value: 'LV-039', country_code: 'LV', text: 'Jaunpiebalgas novads' },\n    { value: 'LV-040', country_code: 'LV', text: 'Jaunpils novads' },\n    { value: 'LV-042', country_code: 'LV', text: 'Jekabpils novads' },\n    { value: 'LV-JEL', country_code: 'LV', text: 'Jelgava' },\n    { value: 'LV-041', country_code: 'LV', text: 'Jelgavas novads' },\n    { value: 'LV-JUR', country_code: 'LV', text: 'Jurmala' },\n    { value: 'LV-052', country_code: 'LV', text: 'Kekavas novads' },\n    { value: 'LV-046', country_code: 'LV', text: 'Kokneses novads' },\n    { value: 'LV-047', country_code: 'LV', text: 'Kraslavas novads' },\n    { value: 'LV-050', country_code: 'LV', text: 'Kuldigas novads' },\n    { value: 'LV-LPX', country_code: 'LV', text: 'Liepaja' },\n    { value: 'LV-054', country_code: 'LV', text: 'Limbazu novads' },\n    { value: 'LV-057', country_code: 'LV', text: 'Lubanas novads' },\n    { value: 'LV-058', country_code: 'LV', text: 'Ludzas novads' },\n    { value: 'LV-059', country_code: 'LV', text: 'Madonas novads' },\n    { value: 'LV-061', country_code: 'LV', text: 'Malpils novads' },\n    { value: 'LV-067', country_code: 'LV', text: 'Ogres novads' },\n    { value: 'LV-068', country_code: 'LV', text: 'Olaines novads' },\n    { value: 'LV-069', country_code: 'LV', text: 'Ozolnieku novads' },\n    { value: 'LV-073', country_code: 'LV', text: 'Preilu novads' },\n    { value: 'LV-077', country_code: 'LV', text: 'Rezeknes novads' },\n    { value: 'LV-RIX', country_code: 'LV', text: 'Riga' },\n    { value: 'LV-079', country_code: 'LV', text: 'Rojas novads' },\n    { value: 'LV-080', country_code: 'LV', text: 'Ropazu novads' },\n    { value: 'LV-082', country_code: 'LV', text: 'Rugaju novads' },\n    { value: 'LV-083', country_code: 'LV', text: 'Rundales novads' },\n    { value: 'LV-086', country_code: 'LV', text: 'Salacgrivas novads' },\n    { value: 'LV-088', country_code: 'LV', text: 'Saldus novads' },\n    { value: 'LV-090', country_code: 'LV', text: 'Sejas novads' },\n    { value: 'LV-091', country_code: 'LV', text: 'Siguldas novads' },\n    { value: 'LV-093', country_code: 'LV', text: 'Skrundas novads' },\n    { value: 'LV-095', country_code: 'LV', text: 'Stopinu novads' },\n    { value: 'LV-096', country_code: 'LV', text: 'Strencu novads' },\n    { value: 'LV-097', country_code: 'LV', text: 'Talsu novads' },\n    { value: 'LV-099', country_code: 'LV', text: 'Tukuma novads' },\n    { value: 'LV-100', country_code: 'LV', text: 'Vainodes novads' },\n    { value: 'LV-101', country_code: 'LV', text: 'Valkas novads' },\n    { value: 'LV-VMR', country_code: 'LV', text: 'Valmiera' },\n    { value: 'LV-103', country_code: 'LV', text: 'Varkavas novads' },\n    { value: 'LV-105', country_code: 'LV', text: 'Vecumnieku novads' },\n    { value: 'LV-106', country_code: 'LV', text: 'Ventspils novads' },\n    { value: 'LY-BU', country_code: 'LY', text: 'Al Butnan' },\n    { value: 'LY-JA', country_code: 'LY', text: 'Al Jabal al Akhdar' },\n    { value: 'LY-JG', country_code: 'LY', text: 'Al Jabal al Gharbi' },\n    { value: 'LY-JI', country_code: 'LY', text: 'Al Jafarah' },\n    { value: 'LY-JU', country_code: 'LY', text: 'Al Jufrah' },\n    { value: 'LY-KF', country_code: 'LY', text: 'Al Kufrah' },\n    { value: 'LY-MJ', country_code: 'LY', text: 'Al Marj' },\n    { value: 'LY-MB', country_code: 'LY', text: 'Al Marqab' },\n    { value: 'LY-WA', country_code: 'LY', text: 'Al Wahat' },\n    { value: 'LY-NQ', country_code: 'LY', text: 'An Nuqat al Khams' },\n    { value: 'LY-ZA', country_code: 'LY', text: 'Az Zawiyah' },\n    { value: 'LY-BA', country_code: 'LY', text: 'Banghazi' },\n    { value: 'LY-DR', country_code: 'LY', text: 'Darnah' },\n    { value: 'LY-GT', country_code: 'LY', text: 'Ghat' },\n    { value: 'LY-MI', country_code: 'LY', text: 'Misratah' },\n    { value: 'LY-MQ', country_code: 'LY', text: 'Murzuq' },\n    { value: 'LY-NL', country_code: 'LY', text: 'Nalut' },\n    { value: 'LY-SB', country_code: 'LY', text: 'Sabha' },\n    { value: 'LY-SR', country_code: 'LY', text: 'Surt' },\n    { value: 'LY-TB', country_code: 'LY', text: 'Tarabulus' },\n    { value: 'LY-WD', country_code: 'LY', text: 'Wadi al Hayat' },\n    { value: 'LY-WS', country_code: 'LY', text: \"Wadi ash Shati'\" },\n    { value: 'MA-09', country_code: 'MA', text: 'Chaouia-Ouardigha' },\n    { value: 'MA-10', country_code: 'MA', text: 'Doukhala-Abda' },\n    { value: 'MA-05', country_code: 'MA', text: 'Fes-Boulemane' },\n    { value: 'MA-02', country_code: 'MA', text: 'Gharb-Chrarda-Beni Hssen' },\n    { value: 'MA-08', country_code: 'MA', text: 'Grand Casablanca' },\n    { value: 'MA-14', country_code: 'MA', text: 'Guelmim-Es Semara' },\n    { value: 'MA-04', country_code: 'MA', text: \"L'Oriental\" },\n    { value: 'MA-11', country_code: 'MA', text: 'Marrakech-Tensift-Al Haouz' },\n    { value: 'MA-06', country_code: 'MA', text: 'Meknes-Tafilalet' },\n    { value: 'MA-07', country_code: 'MA', text: 'Rabat-Sale-Zemmour-Zaer' },\n    { value: 'MA-13', country_code: 'MA', text: 'Souss-Massa-Draa' },\n    { value: 'MA-12', country_code: 'MA', text: 'Tadla-Azilal' },\n    { value: 'MA-01', country_code: 'MA', text: 'Tanger-Tetouan' },\n    { value: 'MA-03', country_code: 'MA', text: 'Taza-Al Hoceima-Taounate' },\n    { value: 'MC-FO', country_code: 'MC', text: 'Fontvieille' },\n    { value: 'MC-CO', country_code: 'MC', text: 'La Condamine' },\n    { value: 'MC-MO', country_code: 'MC', text: 'Monaco-Ville' },\n    { value: 'MC-MG', country_code: 'MC', text: 'Moneghetti' },\n    { value: 'MC-MC', country_code: 'MC', text: 'Monte-Carlo' },\n    { value: 'MC-SR', country_code: 'MC', text: 'Saint-Roman' },\n    { value: 'MD-AN', country_code: 'MD', text: 'Anenii Noi' },\n    { value: 'MD-BA', country_code: 'MD', text: 'Balti' },\n    { value: 'MD-BS', country_code: 'MD', text: 'Basarabeasca' },\n    { value: 'MD-BD', country_code: 'MD', text: 'Bender' },\n    { value: 'MD-BR', country_code: 'MD', text: 'Briceni' },\n    { value: 'MD-CA', country_code: 'MD', text: 'Cahul' },\n    { value: 'MD-CL', country_code: 'MD', text: 'Calarasi' },\n    { value: 'MD-CT', country_code: 'MD', text: 'Cantemir' },\n    { value: 'MD-CS', country_code: 'MD', text: 'Causeni' },\n    { value: 'MD-CU', country_code: 'MD', text: 'Chisinau' },\n    { value: 'MD-CM', country_code: 'MD', text: 'Cimislia' },\n    { value: 'MD-CR', country_code: 'MD', text: 'Criuleni' },\n    { value: 'MD-DO', country_code: 'MD', text: 'Donduseni' },\n    { value: 'MD-DR', country_code: 'MD', text: 'Drochia' },\n    { value: 'MD-DU', country_code: 'MD', text: 'Dubasari' },\n    { value: 'MD-ED', country_code: 'MD', text: 'Edinet' },\n    { value: 'MD-FA', country_code: 'MD', text: 'Falesti' },\n    { value: 'MD-FL', country_code: 'MD', text: 'Floresti' },\n    {\n      value: 'MD-GA',\n      country_code: 'MD',\n      text: 'Gagauzia, Unitatea teritoriala autonoma'\n    },\n    { value: 'MD-GL', country_code: 'MD', text: 'Glodeni' },\n    { value: 'MD-HI', country_code: 'MD', text: 'Hincesti' },\n    { value: 'MD-IA', country_code: 'MD', text: 'Ialoveni' },\n    { value: 'MD-LE', country_code: 'MD', text: 'Leova' },\n    { value: 'MD-NI', country_code: 'MD', text: 'Nisporeni' },\n    { value: 'MD-OC', country_code: 'MD', text: 'Ocnita' },\n    { value: 'MD-OR', country_code: 'MD', text: 'Orhei' },\n    { value: 'MD-RE', country_code: 'MD', text: 'Rezina' },\n    { value: 'MD-RI', country_code: 'MD', text: 'Riscani' },\n    { value: 'MD-SI', country_code: 'MD', text: 'Singerei' },\n    { value: 'MD-SD', country_code: 'MD', text: 'Soldanesti' },\n    { value: 'MD-SO', country_code: 'MD', text: 'Soroca' },\n    { value: 'MD-SV', country_code: 'MD', text: 'Stefan Voda' },\n    {\n      value: 'MD-SN',\n      country_code: 'MD',\n      text: 'Stinga Nistrului, unitatea teritoriala din'\n    },\n    { value: 'MD-ST', country_code: 'MD', text: 'Straseni' },\n    { value: 'MD-TA', country_code: 'MD', text: 'Taraclia' },\n    { value: 'MD-TE', country_code: 'MD', text: 'Telenesti' },\n    { value: 'MD-UN', country_code: 'MD', text: 'Ungheni' },\n    { value: 'ME-02', country_code: 'ME', text: 'Bar' },\n    { value: 'ME-05', country_code: 'ME', text: 'Budva' },\n    { value: 'ME-06', country_code: 'ME', text: 'Cetinje' },\n    { value: 'ME-07', country_code: 'ME', text: 'Danilovgrad' },\n    { value: 'ME-08', country_code: 'ME', text: 'Herceg-Novi' },\n    { value: 'ME-09', country_code: 'ME', text: 'Kolasin' },\n    { value: 'ME-10', country_code: 'ME', text: 'Kotor' },\n    { value: 'ME-11', country_code: 'ME', text: 'Mojkovac' },\n    { value: 'ME-12', country_code: 'ME', text: 'Niksic' },\n    { value: 'ME-16', country_code: 'ME', text: 'Podgorica' },\n    { value: 'ME-19', country_code: 'ME', text: 'Tivat' },\n    { value: 'ME-20', country_code: 'ME', text: 'Ulcinj' },\n    { value: 'ME-21', country_code: 'ME', text: 'Zabljak' },\n    { value: 'MG-T', country_code: 'MG', text: 'Antananarivo' },\n    { value: 'MG-D', country_code: 'MG', text: 'Antsiranana' },\n    { value: 'MG-F', country_code: 'MG', text: 'Fianarantsoa' },\n    { value: 'MG-M', country_code: 'MG', text: 'Mahajanga' },\n    { value: 'MG-A', country_code: 'MG', text: 'Toamasina' },\n    { value: 'MG-U', country_code: 'MG', text: 'Toliara' },\n    { value: 'MH-ALL', country_code: 'MH', text: 'Ailinglaplap' },\n    { value: 'MH-ALK', country_code: 'MH', text: 'Ailuk' },\n    { value: 'MH-ARN', country_code: 'MH', text: 'Arno' },\n    { value: 'MH-AUR', country_code: 'MH', text: 'Aur' },\n    { value: 'MH-KIL', country_code: 'MH', text: 'Bikini and Kili' },\n    { value: 'MH-EBO', country_code: 'MH', text: 'Ebon' },\n    { value: 'MH-ENI', country_code: 'MH', text: 'Enewetak and Ujelang' },\n    { value: 'MH-JAB', country_code: 'MH', text: 'Jabat' },\n    { value: 'MH-JAL', country_code: 'MH', text: 'Jaluit' },\n    { value: 'MH-KWA', country_code: 'MH', text: 'Kwajalein' },\n    { value: 'MH-LAE', country_code: 'MH', text: 'Lae' },\n    { value: 'MH-LIB', country_code: 'MH', text: 'Lib' },\n    { value: 'MH-LIK', country_code: 'MH', text: 'Likiep' },\n    { value: 'MH-MAJ', country_code: 'MH', text: 'Majuro' },\n    { value: 'MH-MAL', country_code: 'MH', text: 'Maloelap' },\n    { value: 'MH-MEJ', country_code: 'MH', text: 'Mejit' },\n    { value: 'MH-MIL', country_code: 'MH', text: 'Mili' },\n    { value: 'MH-NMK', country_code: 'MH', text: 'Namdrik' },\n    { value: 'MH-NMU', country_code: 'MH', text: 'Namu' },\n    { value: 'MH-RON', country_code: 'MH', text: 'Rongelap' },\n    { value: 'MH-UJA', country_code: 'MH', text: 'Ujae' },\n    { value: 'MH-UTI', country_code: 'MH', text: 'Utrik' },\n    { value: 'MH-WTH', country_code: 'MH', text: 'Wotho' },\n    { value: 'MH-WTJ', country_code: 'MH', text: 'Wotje' },\n    { value: 'MK-02', country_code: 'MK', text: 'Aracinovo' },\n    { value: 'MK-03', country_code: 'MK', text: 'Berovo' },\n    { value: 'MK-04', country_code: 'MK', text: 'Bitola' },\n    { value: 'MK-05', country_code: 'MK', text: 'Bogdanci' },\n    { value: 'MK-06', country_code: 'MK', text: 'Bogovinje' },\n    { value: 'MK-07', country_code: 'MK', text: 'Bosilovo' },\n    { value: 'MK-08', country_code: 'MK', text: 'Brvenica' },\n    { value: 'MK-80', country_code: 'MK', text: 'Caska' },\n    { value: 'MK-78', country_code: 'MK', text: 'Centar Zupa' },\n    { value: 'MK-81', country_code: 'MK', text: 'Cesinovo-Oblesevo' },\n    { value: 'MK-82', country_code: 'MK', text: 'Cucer Sandevo' },\n    { value: 'MK-21', country_code: 'MK', text: 'Debar' },\n    { value: 'MK-22', country_code: 'MK', text: 'Debarca' },\n    { value: 'MK-23', country_code: 'MK', text: 'Delcevo' },\n    { value: 'MK-25', country_code: 'MK', text: 'Demir Hisar' },\n    { value: 'MK-24', country_code: 'MK', text: 'Demir Kapija' },\n    { value: 'MK-26', country_code: 'MK', text: 'Dojran' },\n    { value: 'MK-27', country_code: 'MK', text: 'Dolneni' },\n    { value: 'MK-18', country_code: 'MK', text: 'Gevgelija' },\n    { value: 'MK-19', country_code: 'MK', text: 'Gostivar' },\n    { value: 'MK-20', country_code: 'MK', text: 'Gradsko' },\n    { value: 'MK-34', country_code: 'MK', text: 'Ilinden' },\n    { value: 'MK-35', country_code: 'MK', text: 'Jegunovce' },\n    { value: 'MK-37', country_code: 'MK', text: 'Karbinci' },\n    { value: 'MK-36', country_code: 'MK', text: 'Kavadarci' },\n    { value: 'MK-40', country_code: 'MK', text: 'Kicevo' },\n    { value: 'MK-42', country_code: 'MK', text: 'Kocani' },\n    { value: 'MK-41', country_code: 'MK', text: 'Konce' },\n    { value: 'MK-43', country_code: 'MK', text: 'Kratovo' },\n    { value: 'MK-44', country_code: 'MK', text: 'Kriva Palanka' },\n    { value: 'MK-45', country_code: 'MK', text: 'Krivogastani' },\n    { value: 'MK-46', country_code: 'MK', text: 'Krusevo' },\n    { value: 'MK-47', country_code: 'MK', text: 'Kumanovo' },\n    { value: 'MK-48', country_code: 'MK', text: 'Lipkovo' },\n    { value: 'MK-49', country_code: 'MK', text: 'Lozovo' },\n    { value: 'MK-51', country_code: 'MK', text: 'Makedonska Kamenica' },\n    { value: 'MK-52', country_code: 'MK', text: 'Makedonski Brod' },\n    { value: 'MK-50', country_code: 'MK', text: 'Mavrovo i Rostusa' },\n    { value: 'MK-53', country_code: 'MK', text: 'Mogila' },\n    { value: 'MK-54', country_code: 'MK', text: 'Negotino' },\n    { value: 'MK-55', country_code: 'MK', text: 'Novaci' },\n    { value: 'MK-56', country_code: 'MK', text: 'Novo Selo' },\n    { value: 'MK-58', country_code: 'MK', text: 'Ohrid' },\n    { value: 'MK-60', country_code: 'MK', text: 'Pehcevo' },\n    { value: 'MK-59', country_code: 'MK', text: 'Petrovec' },\n    { value: 'MK-61', country_code: 'MK', text: 'Plasnica' },\n    { value: 'MK-62', country_code: 'MK', text: 'Prilep' },\n    { value: 'MK-63', country_code: 'MK', text: 'Probistip' },\n    { value: 'MK-64', country_code: 'MK', text: 'Radovis' },\n    { value: 'MK-65', country_code: 'MK', text: 'Rankovce' },\n    { value: 'MK-66', country_code: 'MK', text: 'Resen' },\n    { value: 'MK-67', country_code: 'MK', text: 'Rosoman' },\n    { value: 'MK-85', country_code: 'MK', text: 'Skopje' },\n    { value: 'MK-70', country_code: 'MK', text: 'Sopiste' },\n    { value: 'MK-71', country_code: 'MK', text: 'Staro Nagoricane' },\n    { value: 'MK-83', country_code: 'MK', text: 'Stip' },\n    { value: 'MK-72', country_code: 'MK', text: 'Struga' },\n    { value: 'MK-73', country_code: 'MK', text: 'Strumica' },\n    { value: 'MK-74', country_code: 'MK', text: 'Studenicani' },\n    { value: 'MK-69', country_code: 'MK', text: 'Sveti Nikole' },\n    { value: 'MK-75', country_code: 'MK', text: 'Tearce' },\n    { value: 'MK-76', country_code: 'MK', text: 'Tetovo' },\n    { value: 'MK-10', country_code: 'MK', text: 'Valandovo' },\n    { value: 'MK-11', country_code: 'MK', text: 'Vasilevo' },\n    { value: 'MK-13', country_code: 'MK', text: 'Veles' },\n    { value: 'MK-12', country_code: 'MK', text: 'Vevcani' },\n    { value: 'MK-14', country_code: 'MK', text: 'Vinica' },\n    { value: 'MK-16', country_code: 'MK', text: 'Vrapciste' },\n    { value: 'MK-32', country_code: 'MK', text: 'Zelenikovo' },\n    { value: 'MK-30', country_code: 'MK', text: 'Zelino' },\n    { value: 'MK-33', country_code: 'MK', text: 'Zrnovci' },\n    { value: 'ML-BKO', country_code: 'ML', text: 'Bamako' },\n    { value: 'ML-7', country_code: 'ML', text: 'Gao' },\n    { value: 'ML-1', country_code: 'ML', text: 'Kayes' },\n    { value: 'ML-8', country_code: 'ML', text: 'Kidal' },\n    { value: 'ML-2', country_code: 'ML', text: 'Koulikoro' },\n    { value: 'ML-5', country_code: 'ML', text: 'Mopti' },\n    { value: 'ML-4', country_code: 'ML', text: 'Segou' },\n    { value: 'ML-3', country_code: 'ML', text: 'Sikasso' },\n    { value: 'ML-6', country_code: 'ML', text: 'Tombouctou' },\n    { value: 'MM-07', country_code: 'MM', text: 'Ayeyarwady' },\n    { value: 'MM-02', country_code: 'MM', text: 'Bago' },\n    { value: 'MM-14', country_code: 'MM', text: 'Chin' },\n    { value: 'MM-11', country_code: 'MM', text: 'Kachin' },\n    { value: 'MM-12', country_code: 'MM', text: 'Kayah' },\n    { value: 'MM-13', country_code: 'MM', text: 'Kayin' },\n    { value: 'MM-03', country_code: 'MM', text: 'Magway' },\n    { value: 'MM-04', country_code: 'MM', text: 'Mandalay' },\n    { value: 'MM-15', country_code: 'MM', text: 'Mon' },\n    { value: 'MM-18', country_code: 'MM', text: 'Nay Pyi Taw' },\n    { value: 'MM-16', country_code: 'MM', text: 'Rakhine' },\n    { value: 'MM-01', country_code: 'MM', text: 'Sagaing' },\n    { value: 'MM-17', country_code: 'MM', text: 'Shan' },\n    { value: 'MM-05', country_code: 'MM', text: 'Tanintharyi' },\n    { value: 'MM-06', country_code: 'MM', text: 'Yangon' },\n    { value: 'MN-073', country_code: 'MN', text: 'Arhangay' },\n    { value: 'MN-071', country_code: 'MN', text: 'Bayan-Olgiy' },\n    { value: 'MN-069', country_code: 'MN', text: 'Bayanhongor' },\n    { value: 'MN-067', country_code: 'MN', text: 'Bulgan' },\n    { value: 'MN-037', country_code: 'MN', text: 'Darhan uul' },\n    { value: 'MN-061', country_code: 'MN', text: 'Dornod' },\n    { value: 'MN-063', country_code: 'MN', text: 'Dornogovi' },\n    { value: 'MN-059', country_code: 'MN', text: 'Dundgovi' },\n    { value: 'MN-057', country_code: 'MN', text: 'Dzavhan' },\n    { value: 'MN-065', country_code: 'MN', text: 'Govi-Altay' },\n    { value: 'MN-064', country_code: 'MN', text: 'Govi-Sumber' },\n    { value: 'MN-039', country_code: 'MN', text: 'Hentiy' },\n    { value: 'MN-043', country_code: 'MN', text: 'Hovd' },\n    { value: 'MN-041', country_code: 'MN', text: 'Hovsgol' },\n    { value: 'MN-053', country_code: 'MN', text: 'Omnogovi' },\n    { value: 'MN-035', country_code: 'MN', text: 'Orhon' },\n    { value: 'MN-055', country_code: 'MN', text: 'Ovorhangay' },\n    { value: 'MN-049', country_code: 'MN', text: 'Selenge' },\n    { value: 'MN-051', country_code: 'MN', text: 'Suhbaatar' },\n    { value: 'MN-047', country_code: 'MN', text: 'Tov' },\n    { value: 'MN-1', country_code: 'MN', text: 'Ulaanbaatar' },\n    { value: 'MN-046', country_code: 'MN', text: 'Uvs' },\n    { value: 'MR-07', country_code: 'MR', text: 'Adrar' },\n    { value: 'MR-03', country_code: 'MR', text: 'Assaba' },\n    { value: 'MR-05', country_code: 'MR', text: 'Brakna' },\n    { value: 'MR-08', country_code: 'MR', text: 'Dakhlet Nouadhibou' },\n    { value: 'MR-04', country_code: 'MR', text: 'Gorgol' },\n    { value: 'MR-10', country_code: 'MR', text: 'Guidimaka' },\n    { value: 'MR-01', country_code: 'MR', text: 'Hodh ech Chargui' },\n    { value: 'MR-02', country_code: 'MR', text: 'Hodh el Gharbi' },\n    { value: 'MR-12', country_code: 'MR', text: 'Inchiri' },\n    { value: 'MR-14', country_code: 'MR', text: 'Nouakchott Nord' },\n    { value: 'MR-09', country_code: 'MR', text: 'Tagant' },\n    { value: 'MR-11', country_code: 'MR', text: 'Tiris Zemmour' },\n    { value: 'MR-06', country_code: 'MR', text: 'Trarza' },\n    { value: 'MT-01', country_code: 'MT', text: 'Attard' },\n    { value: 'MT-02', country_code: 'MT', text: 'Balzan' },\n    { value: 'MT-04', country_code: 'MT', text: 'Birkirkara' },\n    { value: 'MT-05', country_code: 'MT', text: 'Birzebbuga' },\n    { value: 'MT-06', country_code: 'MT', text: 'Bormla' },\n    { value: 'MT-07', country_code: 'MT', text: 'Dingli' },\n    { value: 'MT-13', country_code: 'MT', text: 'Ghajnsielem' },\n    { value: 'MT-15', country_code: 'MT', text: 'Gharghur' },\n    { value: 'MT-17', country_code: 'MT', text: 'Ghaxaq' },\n    { value: 'MT-64', country_code: 'MT', text: 'Haz-Zabbar' },\n    { value: 'MT-60', country_code: 'MT', text: 'Valletta' },\n    { value: 'MT-03', country_code: 'MT', text: 'Birgu' },\n    { value: 'MT-08', country_code: 'MT', text: 'Fgura' },\n    { value: 'MT-09', country_code: 'MT', text: 'Floriana' },\n    { value: 'MT-11', country_code: 'MT', text: 'Gudja' },\n    { value: 'MT-18', country_code: 'MT', text: 'Hamrun' },\n    { value: 'MT-21', country_code: 'MT', text: 'Kalkara' },\n    { value: 'MT-26', country_code: 'MT', text: 'Marsa' },\n    { value: 'MT-30', country_code: 'MT', text: 'Mellieha' },\n    { value: 'MT-32', country_code: 'MT', text: 'Mosta' },\n    { value: 'MT-42', country_code: 'MT', text: 'Qala' },\n    { value: 'MT-44', country_code: 'MT', text: 'Qrendi' },\n    { value: 'MT-37', country_code: 'MT', text: 'Nadur' },\n    { value: 'MT-38', country_code: 'MT', text: 'Naxxar' },\n    { value: 'MT-46', country_code: 'MT', text: 'Rabat Malta' },\n    { value: 'MT-55', country_code: 'MT', text: 'Siggiewi' },\n    { value: 'MT-57', country_code: 'MT', text: 'Swieqi' },\n    { value: 'MT-61', country_code: 'MT', text: 'Xaghra' },\n    { value: 'MT-62', country_code: 'MT', text: 'Xewkija' },\n    { value: 'MT-65', country_code: 'MT', text: 'Zebbug Gozo' },\n    { value: 'MT-67', country_code: 'MT', text: 'Zejtun' },\n    { value: 'MT-68', country_code: 'MT', text: 'Zurrieq' },\n    { value: 'MT-23', country_code: 'MT', text: 'Kirkop' },\n    { value: 'MT-19', country_code: 'MT', text: 'Iklin' },\n    { value: 'MT-33', country_code: 'MT', text: 'Mqabba' },\n    { value: 'MT-34', country_code: 'MT', text: 'Msida' },\n    { value: 'MT-20', country_code: 'MT', text: 'Isla' },\n    { value: 'MT-24', country_code: 'MT', text: 'Lija' },\n    { value: 'MT-25', country_code: 'MT', text: 'Luqa' },\n    { value: 'MT-28', country_code: 'MT', text: 'Marsaxlokk' },\n    { value: 'MT-39', country_code: 'MT', text: 'Paola' },\n    { value: 'MT-43', country_code: 'MT', text: 'Qormi' },\n    { value: 'MT-47', country_code: 'MT', text: 'Safi' },\n    { value: 'MT-49', country_code: 'MT', text: 'Saint John' },\n    { value: 'MT-48', country_code: 'MT', text: 'Saint Julian' },\n    { value: 'MT-53', country_code: 'MT', text: 'Saint Lucia' },\n    { value: 'MT-51', country_code: 'MT', text: \"Saint Paul's Bay\" },\n    { value: 'MT-54', country_code: 'MT', text: 'Saint Venera' },\n    { value: 'MT-52', country_code: 'MT', text: 'Sannat' },\n    { value: 'MT-22', country_code: 'MT', text: 'Kercem' },\n    { value: 'MT-58', country_code: 'MT', text: \"Ta' Xbiex\" },\n    { value: 'MT-59', country_code: 'MT', text: 'Tarxien' },\n    { value: 'MT-56', country_code: 'MT', text: 'Sliema' },\n    { value: 'MT-45', country_code: 'MT', text: 'Rabat Gozo' },\n    { value: 'MU-BL', country_code: 'MU', text: 'Black River' },\n    { value: 'MU-FL', country_code: 'MU', text: 'Flacq' },\n    { value: 'MU-GP', country_code: 'MU', text: 'Grand Port' },\n    { value: 'MU-MO', country_code: 'MU', text: 'Moka' },\n    { value: 'MU-PA', country_code: 'MU', text: 'Pamplemousses' },\n    { value: 'MU-PW', country_code: 'MU', text: 'Plaines Wilhems' },\n    { value: 'MU-PU', country_code: 'MU', text: 'Port Louis' },\n    { value: 'MU-RR', country_code: 'MU', text: 'Riviere du Rempart' },\n    { value: 'MU-SA', country_code: 'MU', text: 'Savanne' },\n    { value: 'MV-02', country_code: 'MV', text: 'Alifu Alifu' },\n    { value: 'MV-20', country_code: 'MV', text: 'Baa' },\n    { value: 'MV-17', country_code: 'MV', text: 'Dhaalu' },\n    { value: 'MV-28', country_code: 'MV', text: 'Gaafu Dhaalu' },\n    { value: 'MV-07', country_code: 'MV', text: 'Haa Alifu' },\n    { value: 'MV-23', country_code: 'MV', text: 'Haa Dhaalu' },\n    { value: 'MV-26', country_code: 'MV', text: 'Kaafu' },\n    { value: 'MV-05', country_code: 'MV', text: 'Laamu' },\n    { value: 'MV-MLE', country_code: 'MV', text: 'Maale' },\n    { value: 'MV-12', country_code: 'MV', text: 'Meemu' },\n    { value: 'MV-25', country_code: 'MV', text: 'Noonu' },\n    { value: 'MV-13', country_code: 'MV', text: 'Raa' },\n    { value: 'MV-01', country_code: 'MV', text: 'Seenu' },\n    { value: 'MV-24', country_code: 'MV', text: 'Shaviyani' },\n    { value: 'MV-08', country_code: 'MV', text: 'Thaa' },\n    { value: 'MW-BA', country_code: 'MW', text: 'Balaka' },\n    { value: 'MW-BL', country_code: 'MW', text: 'Blantyre' },\n    { value: 'MW-CK', country_code: 'MW', text: 'Chikwawa' },\n    { value: 'MW-CR', country_code: 'MW', text: 'Chiradzulu' },\n    { value: 'MW-CT', country_code: 'MW', text: 'Chitipa' },\n    { value: 'MW-DE', country_code: 'MW', text: 'Dedza' },\n    { value: 'MW-DO', country_code: 'MW', text: 'Dowa' },\n    { value: 'MW-KR', country_code: 'MW', text: 'Karonga' },\n    { value: 'MW-KS', country_code: 'MW', text: 'Kasungu' },\n    { value: 'MW-LK', country_code: 'MW', text: 'Likoma' },\n    { value: 'MW-LI', country_code: 'MW', text: 'Lilongwe' },\n    { value: 'MW-MH', country_code: 'MW', text: 'Machinga' },\n    { value: 'MW-MG', country_code: 'MW', text: 'Mangochi' },\n    { value: 'MW-MC', country_code: 'MW', text: 'Mchinji' },\n    { value: 'MW-MU', country_code: 'MW', text: 'Mulanje' },\n    { value: 'MW-MW', country_code: 'MW', text: 'Mwanza' },\n    { value: 'MW-MZ', country_code: 'MW', text: 'Mzimba' },\n    { value: 'MW-NE', country_code: 'MW', text: 'Neno' },\n    { value: 'MW-NB', country_code: 'MW', text: 'Nkhata Bay' },\n    { value: 'MW-NK', country_code: 'MW', text: 'Nkhotakota' },\n    { value: 'MW-NS', country_code: 'MW', text: 'Nsanje' },\n    { value: 'MW-NU', country_code: 'MW', text: 'Ntcheu' },\n    { value: 'MW-NI', country_code: 'MW', text: 'Ntchisi' },\n    { value: 'MW-PH', country_code: 'MW', text: 'Phalombe' },\n    { value: 'MW-RU', country_code: 'MW', text: 'Rumphi' },\n    { value: 'MW-SA', country_code: 'MW', text: 'Salima' },\n    { value: 'MW-TH', country_code: 'MW', text: 'Thyolo' },\n    { value: 'MW-ZO', country_code: 'MW', text: 'Zomba' },\n    { value: 'MX-AGU', country_code: 'MX', text: 'Aguascalientes' },\n    { value: 'MX-BCN', country_code: 'MX', text: 'Baja California' },\n    { value: 'MX-BCS', country_code: 'MX', text: 'Baja California Sur' },\n    { value: 'MX-CAM', country_code: 'MX', text: 'Campeche' },\n    { value: 'MX-CHP', country_code: 'MX', text: 'Chiapas' },\n    { value: 'MX-CHH', country_code: 'MX', text: 'Chihuahua' },\n    { value: 'MX-CMX', country_code: 'MX', text: 'Ciudad de Mexico' },\n    { value: 'MX-COA', country_code: 'MX', text: 'Coahuila de Zaragoza' },\n    { value: 'MX-COL', country_code: 'MX', text: 'Colima' },\n    { value: 'MX-DUR', country_code: 'MX', text: 'Durango' },\n    { value: 'MX-GUA', country_code: 'MX', text: 'Guanajuato' },\n    { value: 'MX-GRO', country_code: 'MX', text: 'Guerrero' },\n    { value: 'MX-HID', country_code: 'MX', text: 'Hidalgo' },\n    { value: 'MX-JAL', country_code: 'MX', text: 'Jalisco' },\n    { value: 'MX-MEX', country_code: 'MX', text: 'Mexico' },\n    { value: 'MX-MIC', country_code: 'MX', text: 'Michoacan de Ocampo' },\n    { value: 'MX-MOR', country_code: 'MX', text: 'Morelos' },\n    { value: 'MX-NAY', country_code: 'MX', text: 'Nayarit' },\n    { value: 'MX-NLE', country_code: 'MX', text: 'Nuevo Leon' },\n    { value: 'MX-OAX', country_code: 'MX', text: 'Oaxaca' },\n    { value: 'MX-PUE', country_code: 'MX', text: 'Puebla' },\n    { value: 'MX-QUE', country_code: 'MX', text: 'Queretaro' },\n    { value: 'MX-ROO', country_code: 'MX', text: 'Quintana Roo' },\n    { value: 'MX-SLP', country_code: 'MX', text: 'San Luis Potosi' },\n    { value: 'MX-SIN', country_code: 'MX', text: 'Sinaloa' },\n    { value: 'MX-SON', country_code: 'MX', text: 'Sonora' },\n    { value: 'MX-TAB', country_code: 'MX', text: 'Tabasco' },\n    { value: 'MX-TAM', country_code: 'MX', text: 'Tamaulipas' },\n    { value: 'MX-TLA', country_code: 'MX', text: 'Tlaxcala' },\n    {\n      value: 'MX-VER',\n      country_code: 'MX',\n      text: 'Veracruz de Ignacio de la Llave'\n    },\n    { value: 'MX-YUC', country_code: 'MX', text: 'Yucatan' },\n    { value: 'MX-ZAC', country_code: 'MX', text: 'Zacatecas' },\n    { value: 'MY-01', country_code: 'MY', text: 'Johor' },\n    { value: 'MY-02', country_code: 'MY', text: 'Kedah' },\n    { value: 'MY-03', country_code: 'MY', text: 'Kelantan' },\n    { value: 'MY-04', country_code: 'MY', text: 'Melaka' },\n    { value: 'MY-05', country_code: 'MY', text: 'Negeri Sembilan' },\n    { value: 'MY-06', country_code: 'MY', text: 'Pahang' },\n    { value: 'MY-08', country_code: 'MY', text: 'Perak' },\n    { value: 'MY-09', country_code: 'MY', text: 'Perlis' },\n    { value: 'MY-07', country_code: 'MY', text: 'Pulau Pinang' },\n    { value: 'MY-12', country_code: 'MY', text: 'Sabah' },\n    { value: 'MY-13', country_code: 'MY', text: 'Sarawak' },\n    { value: 'MY-10', country_code: 'MY', text: 'Selangor' },\n    { value: 'MY-11', country_code: 'MY', text: 'Terengganu' },\n    {\n      value: 'MY-14',\n      country_code: 'MY',\n      text: 'Wilayah Persekutuan Kuala Lumpur'\n    },\n    { value: 'MY-15', country_code: 'MY', text: 'Wilayah Persekutuan Labuan' },\n    {\n      value: 'MY-16',\n      country_code: 'MY',\n      text: 'Wilayah Persekutuan Putrajaya'\n    },\n    { value: 'MZ-P', country_code: 'MZ', text: 'Cabo Delgado' },\n    { value: 'MZ-G', country_code: 'MZ', text: 'Gaza' },\n    { value: 'MZ-I', country_code: 'MZ', text: 'Inhambane' },\n    { value: 'MZ-B', country_code: 'MZ', text: 'Manica' },\n    { value: 'MZ-MPM', country_code: 'MZ', text: 'Maputo' },\n    { value: 'MZ-N', country_code: 'MZ', text: 'Nampula' },\n    { value: 'MZ-A', country_code: 'MZ', text: 'Niassa' },\n    { value: 'MZ-S', country_code: 'MZ', text: 'Sofala' },\n    { value: 'MZ-T', country_code: 'MZ', text: 'Tete' },\n    { value: 'MZ-Q', country_code: 'MZ', text: 'Zambezia' },\n    { value: 'NA-ER', country_code: 'NA', text: 'Erongo' },\n    { value: 'NA-HA', country_code: 'NA', text: 'Hardap' },\n    { value: 'NA-KA', country_code: 'NA', text: 'Karas' },\n    { value: 'NA-KE', country_code: 'NA', text: 'Kavango East' },\n    { value: 'NA-KH', country_code: 'NA', text: 'Khomas' },\n    { value: 'NA-KU', country_code: 'NA', text: 'Kunene' },\n    { value: 'NA-OW', country_code: 'NA', text: 'Ohangwena' },\n    { value: 'NA-OH', country_code: 'NA', text: 'Omaheke' },\n    { value: 'NA-OS', country_code: 'NA', text: 'Omusati' },\n    { value: 'NA-ON', country_code: 'NA', text: 'Oshana' },\n    { value: 'NA-OT', country_code: 'NA', text: 'Oshikoto' },\n    { value: 'NA-OD', country_code: 'NA', text: 'Otjozondjupa' },\n    { value: 'NA-CA', country_code: 'NA', text: 'Zambezi' },\n    { value: 'NE-1', country_code: 'NE', text: 'Agadez' },\n    { value: 'NE-2', country_code: 'NE', text: 'Diffa' },\n    { value: 'NE-3', country_code: 'NE', text: 'Dosso' },\n    { value: 'NE-4', country_code: 'NE', text: 'Maradi' },\n    { value: 'NE-8', country_code: 'NE', text: 'Niamey' },\n    { value: 'NE-5', country_code: 'NE', text: 'Tahoua' },\n    { value: 'NE-6', country_code: 'NE', text: 'Tillaberi' },\n    { value: 'NE-7', country_code: 'NE', text: 'Zinder' },\n    { value: 'NG-AB', country_code: 'NG', text: 'Abia' },\n    {\n      value: 'NG-FC',\n      country_code: 'NG',\n      text: 'Abuja Federal Capital Territory'\n    },\n    { value: 'NG-AD', country_code: 'NG', text: 'Adamawa' },\n    { value: 'NG-AK', country_code: 'NG', text: 'Akwa Ibom' },\n    { value: 'NG-AN', country_code: 'NG', text: 'Anambra' },\n    { value: 'NG-BA', country_code: 'NG', text: 'Bauchi' },\n    { value: 'NG-BY', country_code: 'NG', text: 'Bayelsa' },\n    { value: 'NG-BE', country_code: 'NG', text: 'Benue' },\n    { value: 'NG-BO', country_code: 'NG', text: 'Borno' },\n    { value: 'NG-CR', country_code: 'NG', text: 'Cross River' },\n    { value: 'NG-DE', country_code: 'NG', text: 'Delta' },\n    { value: 'NG-EB', country_code: 'NG', text: 'Ebonyi' },\n    { value: 'NG-ED', country_code: 'NG', text: 'Edo' },\n    { value: 'NG-EK', country_code: 'NG', text: 'Ekiti' },\n    { value: 'NG-EN', country_code: 'NG', text: 'Enugu' },\n    { value: 'NG-GO', country_code: 'NG', text: 'Gombe' },\n    { value: 'NG-IM', country_code: 'NG', text: 'Imo' },\n    { value: 'NG-JI', country_code: 'NG', text: 'Jigawa' },\n    { value: 'NG-KD', country_code: 'NG', text: 'Kaduna' },\n    { value: 'NG-KN', country_code: 'NG', text: 'Kano' },\n    { value: 'NG-KT', country_code: 'NG', text: 'Katsina' },\n    { value: 'NG-KE', country_code: 'NG', text: 'Kebbi' },\n    { value: 'NG-KO', country_code: 'NG', text: 'Kogi' },\n    { value: 'NG-KW', country_code: 'NG', text: 'Kwara' },\n    { value: 'NG-LA', country_code: 'NG', text: 'Lagos' },\n    { value: 'NG-NA', country_code: 'NG', text: 'Nasarawa' },\n    { value: 'NG-NI', country_code: 'NG', text: 'Niger' },\n    { value: 'NG-OG', country_code: 'NG', text: 'Ogun' },\n    { value: 'NG-ON', country_code: 'NG', text: 'Ondo' },\n    { value: 'NG-OS', country_code: 'NG', text: 'Osun' },\n    { value: 'NG-OY', country_code: 'NG', text: 'Oyo' },\n    { value: 'NG-PL', country_code: 'NG', text: 'Plateau' },\n    { value: 'NG-RI', country_code: 'NG', text: 'Rivers' },\n    { value: 'NG-SO', country_code: 'NG', text: 'Sokoto' },\n    { value: 'NG-TA', country_code: 'NG', text: 'Taraba' },\n    { value: 'NG-YO', country_code: 'NG', text: 'Yobe' },\n    { value: 'NG-ZA', country_code: 'NG', text: 'Zamfara' },\n    { value: 'NI-AN', country_code: 'NI', text: 'Atlantico Norte' },\n    { value: 'NI-AS', country_code: 'NI', text: 'Atlantico Sur' },\n    { value: 'NI-BO', country_code: 'NI', text: 'Boaco' },\n    { value: 'NI-CA', country_code: 'NI', text: 'Carazo' },\n    { value: 'NI-CI', country_code: 'NI', text: 'Chinandega' },\n    { value: 'NI-CO', country_code: 'NI', text: 'Chontales' },\n    { value: 'NI-ES', country_code: 'NI', text: 'Esteli' },\n    { value: 'NI-GR', country_code: 'NI', text: 'Granada' },\n    { value: 'NI-JI', country_code: 'NI', text: 'Jinotega' },\n    { value: 'NI-LE', country_code: 'NI', text: 'Leon' },\n    { value: 'NI-MD', country_code: 'NI', text: 'Madriz' },\n    { value: 'NI-MN', country_code: 'NI', text: 'Managua' },\n    { value: 'NI-MS', country_code: 'NI', text: 'Masaya' },\n    { value: 'NI-MT', country_code: 'NI', text: 'Matagalpa' },\n    { value: 'NI-NS', country_code: 'NI', text: 'Nueva Segovia' },\n    { value: 'NI-SJ', country_code: 'NI', text: 'Rio San Juan' },\n    { value: 'NI-RI', country_code: 'NI', text: 'Rivas' },\n    { value: 'NL-DR', country_code: 'NL', text: 'Drenthe' },\n    { value: 'NL-FL', country_code: 'NL', text: 'Flevoland' },\n    { value: 'NL-FR', country_code: 'NL', text: 'Fryslan' },\n    { value: 'NL-GE', country_code: 'NL', text: 'Gelderland' },\n    { value: 'NL-GR', country_code: 'NL', text: 'Groningen' },\n    { value: 'NL-LI', country_code: 'NL', text: 'Limburg' },\n    { value: 'NL-NB', country_code: 'NL', text: 'Noord-Brabant' },\n    { value: 'NL-NH', country_code: 'NL', text: 'Noord-Holland' },\n    { value: 'NL-OV', country_code: 'NL', text: 'Overijssel' },\n    { value: 'NL-UT', country_code: 'NL', text: 'Utrecht' },\n    { value: 'NL-ZE', country_code: 'NL', text: 'Zeeland' },\n    { value: 'NL-ZH', country_code: 'NL', text: 'Zuid-Holland' },\n    { value: 'NO-02', country_code: 'NO', text: 'Akershus' },\n    { value: 'NO-09', country_code: 'NO', text: 'Aust-Agder' },\n    { value: 'NO-06', country_code: 'NO', text: 'Buskerud' },\n    { value: 'NO-20', country_code: 'NO', text: 'Finnmark' },\n    { value: 'NO-04', country_code: 'NO', text: 'Hedmark' },\n    { value: 'NO-12', country_code: 'NO', text: 'Hordaland' },\n    { value: 'NO-15', country_code: 'NO', text: 'More og Romsdal' },\n    { value: 'NO-17', country_code: 'NO', text: 'Nord-Trondelag' },\n    { value: 'NO-18', country_code: 'NO', text: 'Nordland' },\n    { value: 'NO-05', country_code: 'NO', text: 'Oppland' },\n    { value: 'NO-03', country_code: 'NO', text: 'Oslo' },\n    { value: 'NO-01', country_code: 'NO', text: 'Ostfold' },\n    { value: 'NO-11', country_code: 'NO', text: 'Rogaland' },\n    { value: 'NO-14', country_code: 'NO', text: 'Sogn og Fjordane' },\n    { value: 'NO-16', country_code: 'NO', text: 'Sor-Trondelag' },\n    { value: 'NO-08', country_code: 'NO', text: 'Telemark' },\n    { value: 'NO-19', country_code: 'NO', text: 'Troms' },\n    { value: 'NO-10', country_code: 'NO', text: 'Vest-Agder' },\n    { value: 'NO-07', country_code: 'NO', text: 'Vestfold' },\n    { value: 'NP-BA', country_code: 'NP', text: 'Bagmati' },\n    { value: 'NP-BH', country_code: 'NP', text: 'Bheri' },\n    { value: 'NP-DH', country_code: 'NP', text: 'Dhawalagiri' },\n    { value: 'NP-GA', country_code: 'NP', text: 'Gandaki' },\n    { value: 'NP-JA', country_code: 'NP', text: 'Janakpur' },\n    { value: 'NP-KA', country_code: 'NP', text: 'Karnali' },\n    { value: 'NP-KO', country_code: 'NP', text: 'Kosi' },\n    { value: 'NP-LU', country_code: 'NP', text: 'Lumbini' },\n    { value: 'NP-MA', country_code: 'NP', text: 'Mahakali' },\n    { value: 'NP-ME', country_code: 'NP', text: 'Mechi' },\n    { value: 'NP-NA', country_code: 'NP', text: 'Narayani' },\n    { value: 'NP-RA', country_code: 'NP', text: 'Rapti' },\n    { value: 'NP-SA', country_code: 'NP', text: 'Sagarmatha' },\n    { value: 'NP-SE', country_code: 'NP', text: 'Seti' },\n    { value: 'NR-14', country_code: 'NR', text: 'Yaren' },\n    { value: 'NZ-AUK', country_code: 'NZ', text: 'Auckland' },\n    { value: 'NZ-BOP', country_code: 'NZ', text: 'Bay of Plenty' },\n    { value: 'NZ-CAN', country_code: 'NZ', text: 'Canterbury' },\n    { value: 'NZ-CIT', country_code: 'NZ', text: 'Chatham Islands Territory' },\n    { value: 'NZ-GIS', country_code: 'NZ', text: 'Gisborne' },\n    { value: 'NZ-HKB', country_code: 'NZ', text: \"Hawke's Bay\" },\n    { value: 'NZ-MWT', country_code: 'NZ', text: 'Manawatu-Wanganui' },\n    { value: 'NZ-MBH', country_code: 'NZ', text: 'Marlborough' },\n    { value: 'NZ-NSN', country_code: 'NZ', text: 'Nelson' },\n    { value: 'NZ-NTL', country_code: 'NZ', text: 'Northland' },\n    { value: 'NZ-OTA', country_code: 'NZ', text: 'Otago' },\n    { value: 'NZ-STL', country_code: 'NZ', text: 'Southland' },\n    { value: 'NZ-TKI', country_code: 'NZ', text: 'Taranaki' },\n    { value: 'NZ-TAS', country_code: 'NZ', text: 'Tasman' },\n    { value: 'NZ-WKO', country_code: 'NZ', text: 'Waikato' },\n    { value: 'NZ-WGN', country_code: 'NZ', text: 'Wellington' },\n    { value: 'NZ-WTC', country_code: 'NZ', text: 'West Coast' },\n    { value: 'OM-DA', country_code: 'OM', text: 'Ad Dakhiliyah' },\n    { value: 'OM-BU', country_code: 'OM', text: 'Al Buraymi' },\n    { value: 'OM-WU', country_code: 'OM', text: 'Al Wusta' },\n    { value: 'OM-ZA', country_code: 'OM', text: 'Az Zahirah' },\n    { value: 'OM-BJ', country_code: 'OM', text: 'Janub al Batinah' },\n    { value: 'OM-SJ', country_code: 'OM', text: 'Janub ash Sharqiyah' },\n    { value: 'OM-MA', country_code: 'OM', text: 'Masqat' },\n    { value: 'OM-MU', country_code: 'OM', text: 'Musandam' },\n    { value: 'OM-BS', country_code: 'OM', text: 'Shamal al Batinah' },\n    { value: 'OM-SS', country_code: 'OM', text: 'Shamal ash Sharqiyah' },\n    { value: 'OM-ZU', country_code: 'OM', text: 'Zufar' },\n    { value: 'PA-1', country_code: 'PA', text: 'Bocas del Toro' },\n    { value: 'PA-4', country_code: 'PA', text: 'Chiriqui' },\n    { value: 'PA-2', country_code: 'PA', text: 'Cocle' },\n    { value: 'PA-3', country_code: 'PA', text: 'Colon' },\n    { value: 'PA-5', country_code: 'PA', text: 'Darien' },\n    { value: 'PA-6', country_code: 'PA', text: 'Herrera' },\n    { value: 'PA-7', country_code: 'PA', text: 'Los Santos' },\n    { value: 'PA-8', country_code: 'PA', text: 'Panama' },\n    { value: 'PA-9', country_code: 'PA', text: 'Veraguas' },\n    { value: 'PE-AMA', country_code: 'PE', text: 'Amazonas' },\n    { value: 'PE-ANC', country_code: 'PE', text: 'Ancash' },\n    { value: 'PE-APU', country_code: 'PE', text: 'Apurimac' },\n    { value: 'PE-ARE', country_code: 'PE', text: 'Arequipa' },\n    { value: 'PE-AYA', country_code: 'PE', text: 'Ayacucho' },\n    { value: 'PE-CAJ', country_code: 'PE', text: 'Cajamarca' },\n    { value: 'PE-CUS', country_code: 'PE', text: 'Cusco' },\n    { value: 'PE-CAL', country_code: 'PE', text: 'El Callao' },\n    { value: 'PE-HUV', country_code: 'PE', text: 'Huancavelica' },\n    { value: 'PE-HUC', country_code: 'PE', text: 'Huanuco' },\n    { value: 'PE-ICA', country_code: 'PE', text: 'Ica' },\n    { value: 'PE-JUN', country_code: 'PE', text: 'Junin' },\n    { value: 'PE-LAL', country_code: 'PE', text: 'La Libertad' },\n    { value: 'PE-LAM', country_code: 'PE', text: 'Lambayeque' },\n    { value: 'PE-LIM', country_code: 'PE', text: 'Lima' },\n    { value: 'PE-LOR', country_code: 'PE', text: 'Loreto' },\n    { value: 'PE-MDD', country_code: 'PE', text: 'Madre de Dios' },\n    { value: 'PE-MOQ', country_code: 'PE', text: 'Moquegua' },\n    { value: 'PE-PAS', country_code: 'PE', text: 'Pasco' },\n    { value: 'PE-PIU', country_code: 'PE', text: 'Piura' },\n    { value: 'PE-PUN', country_code: 'PE', text: 'Puno' },\n    { value: 'PE-SAM', country_code: 'PE', text: 'San Martin' },\n    { value: 'PE-TAC', country_code: 'PE', text: 'Tacna' },\n    { value: 'PE-TUM', country_code: 'PE', text: 'Tumbes' },\n    { value: 'PE-UCA', country_code: 'PE', text: 'Ucayali' },\n    { value: 'PG-NSB', country_code: 'PG', text: 'Bougainville' },\n    { value: 'PG-CPK', country_code: 'PG', text: 'Chimbu' },\n    { value: 'PG-EBR', country_code: 'PG', text: 'East New Britain' },\n    { value: 'PG-ESW', country_code: 'PG', text: 'East Sepik' },\n    { value: 'PG-EHG', country_code: 'PG', text: 'Eastern Highlands' },\n    { value: 'PG-EPW', country_code: 'PG', text: 'Enga' },\n    { value: 'PG-GPK', country_code: 'PG', text: 'Gulf' },\n    { value: 'PG-MPM', country_code: 'PG', text: 'Madang' },\n    { value: 'PG-MRL', country_code: 'PG', text: 'Manus' },\n    { value: 'PG-MBA', country_code: 'PG', text: 'Milne Bay' },\n    { value: 'PG-MPL', country_code: 'PG', text: 'Morobe' },\n    {\n      value: 'PG-NCD',\n      country_code: 'PG',\n      text: 'National Capital District (Port Moresby)'\n    },\n    { value: 'PG-NIK', country_code: 'PG', text: 'New Ireland' },\n    { value: 'PG-NPP', country_code: 'PG', text: 'Northern' },\n    { value: 'PG-SHM', country_code: 'PG', text: 'Southern Highlands' },\n    { value: 'PG-WBK', country_code: 'PG', text: 'West New Britain' },\n    { value: 'PG-SAN', country_code: 'PG', text: 'West Sepik' },\n    { value: 'PG-WPD', country_code: 'PG', text: 'Western' },\n    { value: 'PG-WHM', country_code: 'PG', text: 'Western Highlands' },\n    { value: 'PH-ABR', country_code: 'PH', text: 'Abra' },\n    { value: 'PH-AGN', country_code: 'PH', text: 'Agusan del Norte' },\n    { value: 'PH-AGS', country_code: 'PH', text: 'Agusan del Sur' },\n    { value: 'PH-AKL', country_code: 'PH', text: 'Aklan' },\n    { value: 'PH-ALB', country_code: 'PH', text: 'Albay' },\n    { value: 'PH-ANT', country_code: 'PH', text: 'Antique' },\n    { value: 'PH-APA', country_code: 'PH', text: 'Apayao' },\n    { value: 'PH-AUR', country_code: 'PH', text: 'Aurora' },\n    { value: 'PH-BAS', country_code: 'PH', text: 'Basilan' },\n    { value: 'PH-BAN', country_code: 'PH', text: 'Bataan' },\n    { value: 'PH-BTN', country_code: 'PH', text: 'Batanes' },\n    { value: 'PH-BTG', country_code: 'PH', text: 'Batangas' },\n    { value: 'PH-BEN', country_code: 'PH', text: 'Benguet' },\n    { value: 'PH-BOH', country_code: 'PH', text: 'Bohol' },\n    { value: 'PH-BUK', country_code: 'PH', text: 'Bukidnon' },\n    { value: 'PH-BUL', country_code: 'PH', text: 'Bulacan' },\n    { value: 'PH-CAG', country_code: 'PH', text: 'Cagayan' },\n    { value: 'PH-CAN', country_code: 'PH', text: 'Camarines Norte' },\n    { value: 'PH-CAS', country_code: 'PH', text: 'Camarines Sur' },\n    { value: 'PH-CAM', country_code: 'PH', text: 'Camiguin' },\n    { value: 'PH-CAP', country_code: 'PH', text: 'Capiz' },\n    { value: 'PH-CAT', country_code: 'PH', text: 'Catanduanes' },\n    { value: 'PH-CAV', country_code: 'PH', text: 'Cavite' },\n    { value: 'PH-CEB', country_code: 'PH', text: 'Cebu' },\n    { value: 'PH-NCO', country_code: 'PH', text: 'Cotabato' },\n    { value: 'PH-DAS', country_code: 'PH', text: 'Davao del Sur' },\n    { value: 'PH-DAO', country_code: 'PH', text: 'Davao Oriental' },\n    { value: 'PH-EAS', country_code: 'PH', text: 'Eastern Samar' },\n    { value: 'PH-IFU', country_code: 'PH', text: 'Ifugao' },\n    { value: 'PH-ILN', country_code: 'PH', text: 'Ilocos Norte' },\n    { value: 'PH-ILS', country_code: 'PH', text: 'Ilocos Sur' },\n    { value: 'PH-ILI', country_code: 'PH', text: 'Iloilo' },\n    { value: 'PH-ISA', country_code: 'PH', text: 'Isabela' },\n    { value: 'PH-KAL', country_code: 'PH', text: 'Kalinga' },\n    { value: 'PH-LUN', country_code: 'PH', text: 'La Union' },\n    { value: 'PH-LAG', country_code: 'PH', text: 'Laguna' },\n    { value: 'PH-LAN', country_code: 'PH', text: 'Lanao del Norte' },\n    { value: 'PH-LAS', country_code: 'PH', text: 'Lanao del Sur' },\n    { value: 'PH-LEY', country_code: 'PH', text: 'Leyte' },\n    { value: 'PH-MAG', country_code: 'PH', text: 'Maguindanao' },\n    { value: 'PH-MAD', country_code: 'PH', text: 'Marinduque' },\n    { value: 'PH-MAS', country_code: 'PH', text: 'Masbate' },\n    { value: 'PH-MDC', country_code: 'PH', text: 'Mindoro Occidental' },\n    { value: 'PH-MDR', country_code: 'PH', text: 'Mindoro Oriental' },\n    { value: 'PH-MSC', country_code: 'PH', text: 'Misamis Occidental' },\n    { value: 'PH-MSR', country_code: 'PH', text: 'Misamis Oriental' },\n    { value: 'PH-MOU', country_code: 'PH', text: 'Mountain Province' },\n    { value: 'PH-00', country_code: 'PH', text: 'National Capital Region' },\n    { value: 'PH-NEC', country_code: 'PH', text: 'Negros Occidental' },\n    { value: 'PH-NER', country_code: 'PH', text: 'Negros Oriental' },\n    { value: 'PH-NSA', country_code: 'PH', text: 'Northern Samar' },\n    { value: 'PH-NUE', country_code: 'PH', text: 'Nueva Ecija' },\n    { value: 'PH-NUV', country_code: 'PH', text: 'Nueva Vizcaya' },\n    { value: 'PH-PLW', country_code: 'PH', text: 'Palawan' },\n    { value: 'PH-PAM', country_code: 'PH', text: 'Pampanga' },\n    { value: 'PH-PAN', country_code: 'PH', text: 'Pangasinan' },\n    { value: 'PH-QUE', country_code: 'PH', text: 'Quezon' },\n    { value: 'PH-QUI', country_code: 'PH', text: 'Quirino' },\n    { value: 'PH-RIZ', country_code: 'PH', text: 'Rizal' },\n    { value: 'PH-ROM', country_code: 'PH', text: 'Romblon' },\n    { value: 'PH-WSA', country_code: 'PH', text: 'Samar' },\n    { value: 'PH-SIG', country_code: 'PH', text: 'Siquijor' },\n    { value: 'PH-SOR', country_code: 'PH', text: 'Sorsogon' },\n    { value: 'PH-SCO', country_code: 'PH', text: 'South Cotabato' },\n    { value: 'PH-SLE', country_code: 'PH', text: 'Southern Leyte' },\n    { value: 'PH-SUK', country_code: 'PH', text: 'Sultan Kudarat' },\n    { value: 'PH-SLU', country_code: 'PH', text: 'Sulu' },\n    { value: 'PH-SUN', country_code: 'PH', text: 'Surigao del Norte' },\n    { value: 'PH-SUR', country_code: 'PH', text: 'Surigao del Sur' },\n    { value: 'PH-TAR', country_code: 'PH', text: 'Tarlac' },\n    { value: 'PH-TAW', country_code: 'PH', text: 'Tawi-Tawi' },\n    { value: 'PH-ZMB', country_code: 'PH', text: 'Zambales' },\n    { value: 'PH-ZAN', country_code: 'PH', text: 'Zamboanga del Norte' },\n    { value: 'PH-ZAS', country_code: 'PH', text: 'Zamboanga del Sur' },\n    { value: 'PK-JK', country_code: 'PK', text: 'Azad Kashmir' },\n    { value: 'PK-BA', country_code: 'PK', text: 'Balochistan' },\n    {\n      value: 'PK-TA',\n      country_code: 'PK',\n      text: 'Federally Administered Tribal Areas'\n    },\n    { value: 'PK-GB', country_code: 'PK', text: 'Gilgit-Baltistan' },\n    { value: 'PK-IS', country_code: 'PK', text: 'Islamabad' },\n    { value: 'PK-KP', country_code: 'PK', text: 'Khyber Pakhtunkhwa' },\n    { value: 'PK-PB', country_code: 'PK', text: 'Punjab' },\n    { value: 'PK-SD', country_code: 'PK', text: 'Sindh' },\n    { value: 'PL-DS', country_code: 'PL', text: 'Dolnoslaskie' },\n    { value: 'PL-KP', country_code: 'PL', text: 'Kujawsko-pomorskie' },\n    { value: 'PL-LD', country_code: 'PL', text: 'Lodzkie' },\n    { value: 'PL-LU', country_code: 'PL', text: 'Lubelskie' },\n    { value: 'PL-LB', country_code: 'PL', text: 'Lubuskie' },\n    { value: 'PL-MA', country_code: 'PL', text: 'Malopolskie' },\n    { value: 'PL-MZ', country_code: 'PL', text: 'Mazowieckie' },\n    { value: 'PL-OP', country_code: 'PL', text: 'Opolskie' },\n    { value: 'PL-PK', country_code: 'PL', text: 'Podkarpackie' },\n    { value: 'PL-PD', country_code: 'PL', text: 'Podlaskie' },\n    { value: 'PL-PM', country_code: 'PL', text: 'Pomorskie' },\n    { value: 'PL-SL', country_code: 'PL', text: 'Slaskie' },\n    { value: 'PL-SK', country_code: 'PL', text: 'Swietokrzyskie' },\n    { value: 'PL-WN', country_code: 'PL', text: 'Warminsko-mazurskie' },\n    { value: 'PL-WP', country_code: 'PL', text: 'Wielkopolskie' },\n    { value: 'PL-ZP', country_code: 'PL', text: 'Zachodniopomorskie' },\n    { value: 'PS-BTH', country_code: 'PS', text: 'Bethlehem' },\n    { value: 'PS-GZA', country_code: 'PS', text: 'Gaza' },\n    { value: 'PS-HBN', country_code: 'PS', text: 'Hebron' },\n    { value: 'PS-JEN', country_code: 'PS', text: 'Jenin' },\n    { value: 'PS-JRH', country_code: 'PS', text: 'Jericho and Al Aghwar' },\n    { value: 'PS-JEM', country_code: 'PS', text: 'Jerusalem' },\n    { value: 'PS-NBS', country_code: 'PS', text: 'Nablus' },\n    { value: 'PS-QQA', country_code: 'PS', text: 'Qalqilya' },\n    { value: 'PS-RBH', country_code: 'PS', text: 'Ramallah' },\n    { value: 'PS-SLT', country_code: 'PS', text: 'Salfit' },\n    { value: 'PS-TBS', country_code: 'PS', text: 'Tubas' },\n    { value: 'PS-TKM', country_code: 'PS', text: 'Tulkarm' },\n    { value: 'PT-01', country_code: 'PT', text: 'Aveiro' },\n    { value: 'PT-02', country_code: 'PT', text: 'Beja' },\n    { value: 'PT-03', country_code: 'PT', text: 'Braga' },\n    { value: 'PT-04', country_code: 'PT', text: 'Braganca' },\n    { value: 'PT-05', country_code: 'PT', text: 'Castelo Branco' },\n    { value: 'PT-06', country_code: 'PT', text: 'Coimbra' },\n    { value: 'PT-07', country_code: 'PT', text: 'Evora' },\n    { value: 'PT-08', country_code: 'PT', text: 'Faro' },\n    { value: 'PT-09', country_code: 'PT', text: 'Guarda' },\n    { value: 'PT-10', country_code: 'PT', text: 'Leiria' },\n    { value: 'PT-11', country_code: 'PT', text: 'Lisboa' },\n    { value: 'PT-12', country_code: 'PT', text: 'Portalegre' },\n    { value: 'PT-13', country_code: 'PT', text: 'Porto' },\n    { value: 'PT-30', country_code: 'PT', text: 'Regiao Autonoma da Madeira' },\n    { value: 'PT-20', country_code: 'PT', text: 'Regiao Autonoma dos Acores' },\n    { value: 'PT-14', country_code: 'PT', text: 'Santarem' },\n    { value: 'PT-15', country_code: 'PT', text: 'Setubal' },\n    { value: 'PT-16', country_code: 'PT', text: 'Viana do Castelo' },\n    { value: 'PT-17', country_code: 'PT', text: 'Vila Real' },\n    { value: 'PT-18', country_code: 'PT', text: 'Viseu' },\n    { value: 'PW-002', country_code: 'PW', text: 'Aimeliik' },\n    { value: 'PW-004', country_code: 'PW', text: 'Airai' },\n    { value: 'PW-010', country_code: 'PW', text: 'Angaur' },\n    { value: 'PW-100', country_code: 'PW', text: 'Kayangel' },\n    { value: 'PW-150', country_code: 'PW', text: 'Koror' },\n    { value: 'PW-212', country_code: 'PW', text: 'Melekeok' },\n    { value: 'PW-214', country_code: 'PW', text: 'Ngaraard' },\n    { value: 'PW-218', country_code: 'PW', text: 'Ngarchelong' },\n    { value: 'PW-222', country_code: 'PW', text: 'Ngardmau' },\n    { value: 'PW-224', country_code: 'PW', text: 'Ngatpang' },\n    { value: 'PW-228', country_code: 'PW', text: 'Ngiwal' },\n    { value: 'PW-350', country_code: 'PW', text: 'Peleliu' },\n    { value: 'PY-16', country_code: 'PY', text: 'Alto Paraguay' },\n    { value: 'PY-10', country_code: 'PY', text: 'Alto Parana' },\n    { value: 'PY-13', country_code: 'PY', text: 'Amambay' },\n    { value: 'PY-ASU', country_code: 'PY', text: 'Asuncion' },\n    { value: 'PY-19', country_code: 'PY', text: 'Boqueron' },\n    { value: 'PY-5', country_code: 'PY', text: 'Caaguazu' },\n    { value: 'PY-6', country_code: 'PY', text: 'Caazapa' },\n    { value: 'PY-14', country_code: 'PY', text: 'Canindeyu' },\n    { value: 'PY-11', country_code: 'PY', text: 'Central' },\n    { value: 'PY-1', country_code: 'PY', text: 'Concepcion' },\n    { value: 'PY-3', country_code: 'PY', text: 'Cordillera' },\n    { value: 'PY-4', country_code: 'PY', text: 'Guaira' },\n    { value: 'PY-7', country_code: 'PY', text: 'Itapua' },\n    { value: 'PY-8', country_code: 'PY', text: 'Misiones' },\n    { value: 'PY-12', country_code: 'PY', text: 'Neembucu' },\n    { value: 'PY-9', country_code: 'PY', text: 'Paraguari' },\n    { value: 'PY-15', country_code: 'PY', text: 'Presidente Hayes' },\n    { value: 'PY-2', country_code: 'PY', text: 'San Pedro' },\n    { value: 'QA-DA', country_code: 'QA', text: 'Ad Dawhah' },\n    { value: 'QA-KH', country_code: 'QA', text: 'Al Khawr wa adh Dhakhirah' },\n    { value: 'QA-WA', country_code: 'QA', text: 'Al Wakrah' },\n    { value: 'QA-RA', country_code: 'QA', text: 'Ar Rayyan' },\n    { value: 'QA-MS', country_code: 'QA', text: 'Ash Shamal' },\n    { value: 'QA-ZA', country_code: 'QA', text: \"Az Za'ayin\" },\n    { value: 'QA-US', country_code: 'QA', text: 'Umm Salal' },\n    { value: 'RO-AB', country_code: 'RO', text: 'Alba' },\n    { value: 'RO-AR', country_code: 'RO', text: 'Arad' },\n    { value: 'RO-AG', country_code: 'RO', text: 'Arges' },\n    { value: 'RO-BC', country_code: 'RO', text: 'Bacau' },\n    { value: 'RO-BH', country_code: 'RO', text: 'Bihor' },\n    { value: 'RO-BN', country_code: 'RO', text: 'Bistrita-Nasaud' },\n    { value: 'RO-BT', country_code: 'RO', text: 'Botosani' },\n    { value: 'RO-BR', country_code: 'RO', text: 'Braila' },\n    { value: 'RO-BV', country_code: 'RO', text: 'Brasov' },\n    { value: 'RO-B', country_code: 'RO', text: 'Bucuresti' },\n    { value: 'RO-BZ', country_code: 'RO', text: 'Buzau' },\n    { value: 'RO-CL', country_code: 'RO', text: 'Calarasi' },\n    { value: 'RO-CS', country_code: 'RO', text: 'Caras-Severin' },\n    { value: 'RO-CJ', country_code: 'RO', text: 'Cluj' },\n    { value: 'RO-CT', country_code: 'RO', text: 'Constanta' },\n    { value: 'RO-CV', country_code: 'RO', text: 'Covasna' },\n    { value: 'RO-DB', country_code: 'RO', text: 'Dambovita' },\n    { value: 'RO-DJ', country_code: 'RO', text: 'Dolj' },\n    { value: 'RO-GL', country_code: 'RO', text: 'Galati' },\n    { value: 'RO-GR', country_code: 'RO', text: 'Giurgiu' },\n    { value: 'RO-GJ', country_code: 'RO', text: 'Gorj' },\n    { value: 'RO-HR', country_code: 'RO', text: 'Harghita' },\n    { value: 'RO-HD', country_code: 'RO', text: 'Hunedoara' },\n    { value: 'RO-IL', country_code: 'RO', text: 'Ialomita' },\n    { value: 'RO-IS', country_code: 'RO', text: 'Iasi' },\n    { value: 'RO-IF', country_code: 'RO', text: 'Ilfov' },\n    { value: 'RO-MM', country_code: 'RO', text: 'Maramures' },\n    { value: 'RO-MH', country_code: 'RO', text: 'Mehedinti' },\n    { value: 'RO-MS', country_code: 'RO', text: 'Mures' },\n    { value: 'RO-NT', country_code: 'RO', text: 'Neamt' },\n    { value: 'RO-OT', country_code: 'RO', text: 'Olt' },\n    { value: 'RO-PH', country_code: 'RO', text: 'Prahova' },\n    { value: 'RO-SJ', country_code: 'RO', text: 'Salaj' },\n    { value: 'RO-SM', country_code: 'RO', text: 'Satu Mare' },\n    { value: 'RO-SB', country_code: 'RO', text: 'Sibiu' },\n    { value: 'RO-SV', country_code: 'RO', text: 'Suceava' },\n    { value: 'RO-TR', country_code: 'RO', text: 'Teleorman' },\n    { value: 'RO-TM', country_code: 'RO', text: 'Timis' },\n    { value: 'RO-TL', country_code: 'RO', text: 'Tulcea' },\n    { value: 'RO-VL', country_code: 'RO', text: 'Valcea' },\n    { value: 'RO-VS', country_code: 'RO', text: 'Vaslui' },\n    { value: 'RO-VN', country_code: 'RO', text: 'Vrancea' },\n    { value: 'RS-00', country_code: 'RS', text: 'Beograd' },\n    { value: 'RS-14', country_code: 'RS', text: 'Borski okrug' },\n    { value: 'RS-11', country_code: 'RS', text: 'Branicevski okrug' },\n    { value: 'RS-23', country_code: 'RS', text: 'Jablanicki okrug' },\n    { value: 'RS-06', country_code: 'RS', text: 'Juznobacki okrug' },\n    { value: 'RS-04', country_code: 'RS', text: 'Juznobanatski okrug' },\n    { value: 'RS-09', country_code: 'RS', text: 'Kolubarski okrug' },\n    { value: 'RS-28', country_code: 'RS', text: 'Kosovsko-Mitrovacki okrug' },\n    { value: 'RS-08', country_code: 'RS', text: 'Macvanski okrug' },\n    { value: 'RS-17', country_code: 'RS', text: 'Moravicki okrug' },\n    { value: 'RS-20', country_code: 'RS', text: 'Nisavski okrug' },\n    { value: 'RS-24', country_code: 'RS', text: 'Pcinjski okrug' },\n    { value: 'RS-26', country_code: 'RS', text: 'Pecki okrug' },\n    { value: 'RS-22', country_code: 'RS', text: 'Pirotski okrug' },\n    { value: 'RS-10', country_code: 'RS', text: 'Podunavski okrug' },\n    { value: 'RS-27', country_code: 'RS', text: 'Prizrenski okrug' },\n    { value: 'RS-19', country_code: 'RS', text: 'Rasinski okrug' },\n    { value: 'RS-18', country_code: 'RS', text: 'Raski okrug' },\n    { value: 'RS-01', country_code: 'RS', text: 'Severnobacki okrug' },\n    { value: 'RS-03', country_code: 'RS', text: 'Severnobanatski okrug' },\n    { value: 'RS-02', country_code: 'RS', text: 'Srednjebanatski okrug' },\n    { value: 'RS-07', country_code: 'RS', text: 'Sremski okrug' },\n    { value: 'RS-12', country_code: 'RS', text: 'Sumadijski okrug' },\n    { value: 'RS-21', country_code: 'RS', text: 'Toplicki okrug' },\n    { value: 'RS-15', country_code: 'RS', text: 'Zajecarski okrug' },\n    { value: 'RS-05', country_code: 'RS', text: 'Zapadnobacki okrug' },\n    { value: 'RS-16', country_code: 'RS', text: 'Zlatiborski okrug' },\n    { value: 'RU-AD', country_code: 'RU', text: 'Adygeya, Respublika' },\n    { value: 'RU-AL', country_code: 'RU', text: 'Altay, Respublika' },\n    { value: 'RU-ALT', country_code: 'RU', text: 'Altayskiy kray' },\n    { value: 'RU-AMU', country_code: 'RU', text: \"Amurskaya oblast'\" },\n    { value: 'RU-ARK', country_code: 'RU', text: \"Arkhangel'skaya oblast'\" },\n    { value: 'RU-AST', country_code: 'RU', text: \"Astrakhanskaya oblast'\" },\n    { value: 'RU-BA', country_code: 'RU', text: 'Bashkortostan, Respublika' },\n    { value: 'RU-BEL', country_code: 'RU', text: \"Belgorodskaya oblast'\" },\n    { value: 'RU-BRY', country_code: 'RU', text: \"Bryanskaya oblast'\" },\n    { value: 'RU-BU', country_code: 'RU', text: 'Buryatiya, Respublika' },\n    { value: 'RU-CE', country_code: 'RU', text: 'Chechenskaya Respublika' },\n    { value: 'RU-CHE', country_code: 'RU', text: \"Chelyabinskaya oblast'\" },\n    {\n      value: 'RU-CHU',\n      country_code: 'RU',\n      text: 'Chukotskiy avtonomnyy okrug'\n    },\n    { value: 'RU-CU', country_code: 'RU', text: 'Chuvashskaya Respublika' },\n    { value: 'RU-DA', country_code: 'RU', text: 'Dagestan, Respublika' },\n    { value: 'RU-IN', country_code: 'RU', text: 'Ingushetiya, Respublika' },\n    { value: 'RU-IRK', country_code: 'RU', text: \"Irkutskaya oblast'\" },\n    { value: 'RU-IVA', country_code: 'RU', text: \"Ivanovskaya oblast'\" },\n    {\n      value: 'RU-KB',\n      country_code: 'RU',\n      text: 'Kabardino-Balkarskaya Respublika'\n    },\n    { value: 'RU-KGD', country_code: 'RU', text: \"Kaliningradskaya oblast'\" },\n    { value: 'RU-KL', country_code: 'RU', text: 'Kalmykiya, Respublika' },\n    { value: 'RU-KLU', country_code: 'RU', text: \"Kaluzhskaya oblast'\" },\n    { value: 'RU-KAM', country_code: 'RU', text: 'Kamchatskiy kray' },\n    {\n      value: 'RU-KC',\n      country_code: 'RU',\n      text: 'Karachayevo-Cherkesskaya Respublika'\n    },\n    { value: 'RU-KR', country_code: 'RU', text: 'Kareliya, Respublika' },\n    { value: 'RU-KEM', country_code: 'RU', text: \"Kemerovskaya oblast'\" },\n    { value: 'RU-KHA', country_code: 'RU', text: 'Khabarovskiy kray' },\n    { value: 'RU-KK', country_code: 'RU', text: 'Khakasiya, Respublika' },\n    {\n      value: 'RU-KHM',\n      country_code: 'RU',\n      text: 'Khanty-Mansiyskiy avtonomnyy okrug'\n    },\n    { value: 'RU-KIR', country_code: 'RU', text: \"Kirovskaya oblast'\" },\n    { value: 'RU-KO', country_code: 'RU', text: 'Komi, Respublika' },\n    { value: 'RU-KOS', country_code: 'RU', text: \"Kostromskaya oblast'\" },\n    { value: 'RU-KDA', country_code: 'RU', text: 'Krasnodarskiy kray' },\n    { value: 'RU-KYA', country_code: 'RU', text: 'Krasnoyarskiy kray' },\n    { value: 'RU-KGN', country_code: 'RU', text: \"Kurganskaya oblast'\" },\n    { value: 'RU-KRS', country_code: 'RU', text: \"Kurskaya oblast'\" },\n    { value: 'RU-LEN', country_code: 'RU', text: \"Leningradskaya oblast'\" },\n    { value: 'RU-LIP', country_code: 'RU', text: \"Lipetskaya oblast'\" },\n    { value: 'RU-MAG', country_code: 'RU', text: \"Magadanskaya oblast'\" },\n    { value: 'RU-ME', country_code: 'RU', text: 'Mariy El, Respublika' },\n    { value: 'RU-MO', country_code: 'RU', text: 'Mordoviya, Respublika' },\n    { value: 'RU-MOS', country_code: 'RU', text: \"Moskovskaya oblast'\" },\n    { value: 'RU-MOW', country_code: 'RU', text: 'Moskva' },\n    { value: 'RU-MUR', country_code: 'RU', text: \"Murmanskaya oblast'\" },\n    { value: 'RU-NEN', country_code: 'RU', text: 'Nenetskiy avtonomnyy okrug' },\n    { value: 'RU-NIZ', country_code: 'RU', text: \"Nizhegorodskaya oblast'\" },\n    { value: 'RU-NGR', country_code: 'RU', text: \"Novgorodskaya oblast'\" },\n    { value: 'RU-NVS', country_code: 'RU', text: \"Novosibirskaya oblast'\" },\n    { value: 'RU-OMS', country_code: 'RU', text: \"Omskaya oblast'\" },\n    { value: 'RU-ORE', country_code: 'RU', text: \"Orenburgskaya oblast'\" },\n    { value: 'RU-ORL', country_code: 'RU', text: \"Orlovskaya oblast'\" },\n    { value: 'RU-PNZ', country_code: 'RU', text: \"Penzenskaya oblast'\" },\n    { value: 'RU-PER', country_code: 'RU', text: 'Permskiy kray' },\n    { value: 'RU-PRI', country_code: 'RU', text: 'Primorskiy kray' },\n    { value: 'RU-PSK', country_code: 'RU', text: \"Pskovskaya oblast'\" },\n    { value: 'RU-ROS', country_code: 'RU', text: \"Rostovskaya oblast'\" },\n    { value: 'RU-RYA', country_code: 'RU', text: \"Ryazanskaya oblast'\" },\n    { value: 'RU-SA', country_code: 'RU', text: 'Saha, Respublika' },\n    { value: 'RU-SAK', country_code: 'RU', text: \"Sakhalinskaya oblast'\" },\n    { value: 'RU-SAM', country_code: 'RU', text: \"Samarskaya oblast'\" },\n    { value: 'RU-SPE', country_code: 'RU', text: 'Sankt-Peterburg' },\n    { value: 'RU-SAR', country_code: 'RU', text: \"Saratovskaya oblast'\" },\n    {\n      value: 'RU-SE',\n      country_code: 'RU',\n      text: 'Severnaya Osetiya, Respublika'\n    },\n    { value: 'RU-SMO', country_code: 'RU', text: \"Smolenskaya oblast'\" },\n    { value: 'RU-STA', country_code: 'RU', text: \"Stavropol'skiy kray\" },\n    { value: 'RU-SVE', country_code: 'RU', text: \"Sverdlovskaya oblast'\" },\n    { value: 'RU-TAM', country_code: 'RU', text: \"Tambovskaya oblast'\" },\n    { value: 'RU-TA', country_code: 'RU', text: 'Tatarstan, Respublika' },\n    { value: 'RU-TOM', country_code: 'RU', text: \"Tomskaya oblast'\" },\n    { value: 'RU-TUL', country_code: 'RU', text: \"Tul'skaya oblast'\" },\n    { value: 'RU-TVE', country_code: 'RU', text: \"Tverskaya oblast'\" },\n    { value: 'RU-TYU', country_code: 'RU', text: \"Tyumenskaya oblast'\" },\n    { value: 'RU-TY', country_code: 'RU', text: 'Tyva, Respublika' },\n    { value: 'RU-UD', country_code: 'RU', text: 'Udmurtskaya Respublika' },\n    { value: 'RU-ULY', country_code: 'RU', text: \"Ul'yanovskaya oblast'\" },\n    { value: 'RU-VLA', country_code: 'RU', text: \"Vladimirskaya oblast'\" },\n    { value: 'RU-VGG', country_code: 'RU', text: \"Volgogradskaya oblast'\" },\n    { value: 'RU-VLG', country_code: 'RU', text: \"Vologodskaya oblast'\" },\n    { value: 'RU-VOR', country_code: 'RU', text: \"Voronezhskaya oblast'\" },\n    {\n      value: 'RU-YAN',\n      country_code: 'RU',\n      text: 'Yamalo-Nenetskiy avtonomnyy okrug'\n    },\n    { value: 'RU-YAR', country_code: 'RU', text: \"Yaroslavskaya oblast'\" },\n    {\n      value: 'RU-YEV',\n      country_code: 'RU',\n      text: \"Yevreyskaya avtonomnaya oblast'\"\n    },\n    { value: 'RU-ZAB', country_code: 'RU', text: \"Zabaykal'skiy kray\" },\n    { value: 'RW-02', country_code: 'RW', text: 'Est' },\n    { value: 'RW-03', country_code: 'RW', text: 'Nord' },\n    { value: 'RW-04', country_code: 'RW', text: 'Ouest' },\n    { value: 'RW-05', country_code: 'RW', text: 'Sud' },\n    { value: 'RW-01', country_code: 'RW', text: 'Ville de Kigali' },\n    { value: 'SA-14', country_code: 'SA', text: \"'Asir\" },\n    { value: 'SA-11', country_code: 'SA', text: 'Al Bahah' },\n    { value: 'SA-08', country_code: 'SA', text: 'Al Hudud ash Shamaliyah' },\n    { value: 'SA-12', country_code: 'SA', text: 'Al Jawf' },\n    { value: 'SA-03', country_code: 'SA', text: 'Al Madinah al Munawwarah' },\n    { value: 'SA-05', country_code: 'SA', text: 'Al Qasim' },\n    { value: 'SA-01', country_code: 'SA', text: 'Ar Riyad' },\n    { value: 'SA-04', country_code: 'SA', text: 'Ash Sharqiyah' },\n    { value: 'SA-06', country_code: 'SA', text: \"Ha'il\" },\n    { value: 'SA-09', country_code: 'SA', text: 'Jazan' },\n    { value: 'SA-02', country_code: 'SA', text: 'Makkah al Mukarramah' },\n    { value: 'SA-10', country_code: 'SA', text: 'Najran' },\n    { value: 'SA-07', country_code: 'SA', text: 'Tabuk' },\n    { value: 'SB-CE', country_code: 'SB', text: 'Central' },\n    { value: 'SB-GU', country_code: 'SB', text: 'Guadalcanal' },\n    { value: 'SB-IS', country_code: 'SB', text: 'Isabel' },\n    { value: 'SB-MK', country_code: 'SB', text: 'Makira-Ulawa' },\n    { value: 'SB-ML', country_code: 'SB', text: 'Malaita' },\n    { value: 'SB-WE', country_code: 'SB', text: 'Western' },\n    { value: 'SC-16', country_code: 'SC', text: 'English River' },\n    { value: 'SD-NB', country_code: 'SD', text: 'Blue Nile' },\n    { value: 'SD-GD', country_code: 'SD', text: 'Gedaref' },\n    { value: 'SD-GZ', country_code: 'SD', text: 'Gezira' },\n    { value: 'SD-KA', country_code: 'SD', text: 'Kassala' },\n    { value: 'SD-KH', country_code: 'SD', text: 'Khartoum' },\n    { value: 'SD-DN', country_code: 'SD', text: 'North Darfur' },\n    { value: 'SD-KN', country_code: 'SD', text: 'North Kordofan' },\n    { value: 'SD-NO', country_code: 'SD', text: 'Northern' },\n    { value: 'SD-RS', country_code: 'SD', text: 'Red Sea' },\n    { value: 'SD-NR', country_code: 'SD', text: 'River Nile' },\n    { value: 'SD-SI', country_code: 'SD', text: 'Sennar' },\n    { value: 'SD-DS', country_code: 'SD', text: 'South Darfur' },\n    { value: 'SD-KS', country_code: 'SD', text: 'South Kordofan' },\n    { value: 'SD-DW', country_code: 'SD', text: 'West Darfur' },\n    { value: 'SD-NW', country_code: 'SD', text: 'White Nile' },\n    { value: 'SE-K', country_code: 'SE', text: 'Blekinge lan' },\n    { value: 'SE-W', country_code: 'SE', text: 'Dalarnas lan' },\n    { value: 'SE-X', country_code: 'SE', text: 'Gavleborgs lan' },\n    { value: 'SE-I', country_code: 'SE', text: 'Gotlands lan' },\n    { value: 'SE-N', country_code: 'SE', text: 'Hallands lan' },\n    { value: 'SE-Z', country_code: 'SE', text: 'Jamtlands lan' },\n    { value: 'SE-F', country_code: 'SE', text: 'Jonkopings lan' },\n    { value: 'SE-H', country_code: 'SE', text: 'Kalmar lan' },\n    { value: 'SE-G', country_code: 'SE', text: 'Kronobergs lan' },\n    { value: 'SE-BD', country_code: 'SE', text: 'Norrbottens lan' },\n    { value: 'SE-T', country_code: 'SE', text: 'Orebro lan' },\n    { value: 'SE-E', country_code: 'SE', text: 'Ostergotlands lan' },\n    { value: 'SE-M', country_code: 'SE', text: 'Skane lan' },\n    { value: 'SE-D', country_code: 'SE', text: 'Sodermanlands lan' },\n    { value: 'SE-AB', country_code: 'SE', text: 'Stockholms lan' },\n    { value: 'SE-C', country_code: 'SE', text: 'Uppsala lan' },\n    { value: 'SE-S', country_code: 'SE', text: 'Varmlands lan' },\n    { value: 'SE-AC', country_code: 'SE', text: 'Vasterbottens lan' },\n    { value: 'SE-Y', country_code: 'SE', text: 'Vasternorrlands lan' },\n    { value: 'SE-U', country_code: 'SE', text: 'Vastmanlands lan' },\n    { value: 'SE-O', country_code: 'SE', text: 'Vastra Gotalands lan' },\n    { value: 'SH-AC', country_code: 'SH', text: 'Ascension' },\n    { value: 'SH-HL', country_code: 'SH', text: 'Saint Helena' },\n    { value: 'SH-TA', country_code: 'SH', text: 'Tristan da Cunha' },\n    { value: 'SI-001', country_code: 'SI', text: 'Ajdovscina' },\n    { value: 'SI-003', country_code: 'SI', text: 'Bled' },\n    { value: 'SI-004', country_code: 'SI', text: 'Bohinj' },\n    { value: 'SI-005', country_code: 'SI', text: 'Borovnica' },\n    { value: 'SI-006', country_code: 'SI', text: 'Bovec' },\n    { value: 'SI-009', country_code: 'SI', text: 'Brezice' },\n    { value: 'SI-008', country_code: 'SI', text: 'Brezovica' },\n    { value: 'SI-011', country_code: 'SI', text: 'Celje' },\n    { value: 'SI-013', country_code: 'SI', text: 'Cerknica' },\n    { value: 'SI-014', country_code: 'SI', text: 'Cerkno' },\n    { value: 'SI-015', country_code: 'SI', text: 'Crensovci' },\n    { value: 'SI-017', country_code: 'SI', text: 'Crnomelj' },\n    { value: 'SI-018', country_code: 'SI', text: 'Destrnik' },\n    { value: 'SI-019', country_code: 'SI', text: 'Divaca' },\n    { value: 'SI-023', country_code: 'SI', text: 'Domzale' },\n    { value: 'SI-025', country_code: 'SI', text: 'Dravograd' },\n    { value: 'SI-029', country_code: 'SI', text: 'Gornja Radgona' },\n    { value: 'SI-032', country_code: 'SI', text: 'Grosuplje' },\n    { value: 'SI-160', country_code: 'SI', text: 'Hoce-Slivnica' },\n    { value: 'SI-162', country_code: 'SI', text: 'Horjul' },\n    { value: 'SI-034', country_code: 'SI', text: 'Hrastnik' },\n    { value: 'SI-036', country_code: 'SI', text: 'Idrija' },\n    { value: 'SI-037', country_code: 'SI', text: 'Ig' },\n    { value: 'SI-038', country_code: 'SI', text: 'Ilirska Bistrica' },\n    { value: 'SI-039', country_code: 'SI', text: 'Ivancna Gorica' },\n    { value: 'SI-040', country_code: 'SI', text: 'Izola' },\n    { value: 'SI-041', country_code: 'SI', text: 'Jesenice' },\n    { value: 'SI-043', country_code: 'SI', text: 'Kamnik' },\n    { value: 'SI-044', country_code: 'SI', text: 'Kanal' },\n    { value: 'SI-045', country_code: 'SI', text: 'Kidricevo' },\n    { value: 'SI-046', country_code: 'SI', text: 'Kobarid' },\n    { value: 'SI-048', country_code: 'SI', text: 'Kocevje' },\n    { value: 'SI-050', country_code: 'SI', text: 'Koper' },\n    { value: 'SI-052', country_code: 'SI', text: 'Kranj' },\n    { value: 'SI-053', country_code: 'SI', text: 'Kranjska Gora' },\n    { value: 'SI-054', country_code: 'SI', text: 'Krsko' },\n    { value: 'SI-057', country_code: 'SI', text: 'Lasko' },\n    { value: 'SI-058', country_code: 'SI', text: 'Lenart' },\n    { value: 'SI-059', country_code: 'SI', text: 'Lendava' },\n    { value: 'SI-060', country_code: 'SI', text: 'Litija' },\n    { value: 'SI-061', country_code: 'SI', text: 'Ljubljana' },\n    { value: 'SI-063', country_code: 'SI', text: 'Ljutomer' },\n    { value: 'SI-208', country_code: 'SI', text: 'Log-Dragomer' },\n    { value: 'SI-064', country_code: 'SI', text: 'Logatec' },\n    { value: 'SI-167', country_code: 'SI', text: 'Lovrenc na Pohorju' },\n    { value: 'SI-070', country_code: 'SI', text: 'Maribor' },\n    { value: 'SI-071', country_code: 'SI', text: 'Medvode' },\n    { value: 'SI-072', country_code: 'SI', text: 'Menges' },\n    { value: 'SI-073', country_code: 'SI', text: 'Metlika' },\n    { value: 'SI-074', country_code: 'SI', text: 'Mezica' },\n    { value: 'SI-169', country_code: 'SI', text: 'Miklavz na Dravskem Polju' },\n    { value: 'SI-075', country_code: 'SI', text: 'Miren-Kostanjevica' },\n    { value: 'SI-076', country_code: 'SI', text: 'Mislinja' },\n    { value: 'SI-079', country_code: 'SI', text: 'Mozirje' },\n    { value: 'SI-080', country_code: 'SI', text: 'Murska Sobota' },\n    { value: 'SI-081', country_code: 'SI', text: 'Muta' },\n    { value: 'SI-084', country_code: 'SI', text: 'Nova Gorica' },\n    { value: 'SI-085', country_code: 'SI', text: 'Novo Mesto' },\n    { value: 'SI-086', country_code: 'SI', text: 'Odranci' },\n    { value: 'SI-171', country_code: 'SI', text: 'Oplotnica' },\n    { value: 'SI-087', country_code: 'SI', text: 'Ormoz' },\n    { value: 'SI-090', country_code: 'SI', text: 'Piran' },\n    { value: 'SI-091', country_code: 'SI', text: 'Pivka' },\n    { value: 'SI-200', country_code: 'SI', text: 'Poljcane' },\n    { value: 'SI-173', country_code: 'SI', text: 'Polzela' },\n    { value: 'SI-094', country_code: 'SI', text: 'Postojna' },\n    { value: 'SI-174', country_code: 'SI', text: 'Prebold' },\n    { value: 'SI-175', country_code: 'SI', text: 'Prevalje' },\n    { value: 'SI-096', country_code: 'SI', text: 'Ptuj' },\n    { value: 'SI-098', country_code: 'SI', text: 'Race-Fram' },\n    { value: 'SI-099', country_code: 'SI', text: 'Radece' },\n    { value: 'SI-100', country_code: 'SI', text: 'Radenci' },\n    { value: 'SI-101', country_code: 'SI', text: 'Radlje ob Dravi' },\n    { value: 'SI-102', country_code: 'SI', text: 'Radovljica' },\n    { value: 'SI-103', country_code: 'SI', text: 'Ravne na Koroskem' },\n    { value: 'SI-104', country_code: 'SI', text: 'Ribnica' },\n    { value: 'SI-106', country_code: 'SI', text: 'Rogaska Slatina' },\n    { value: 'SI-108', country_code: 'SI', text: 'Ruse' },\n    { value: 'SI-183', country_code: 'SI', text: 'Sempeter-Vrtojba' },\n    { value: 'SI-117', country_code: 'SI', text: 'Sencur' },\n    { value: 'SI-118', country_code: 'SI', text: 'Sentilj' },\n    { value: 'SI-120', country_code: 'SI', text: 'Sentjur' },\n    { value: 'SI-110', country_code: 'SI', text: 'Sevnica' },\n    { value: 'SI-111', country_code: 'SI', text: 'Sezana' },\n    { value: 'SI-122', country_code: 'SI', text: 'Skofja Loka' },\n    { value: 'SI-123', country_code: 'SI', text: 'Skofljica' },\n    { value: 'SI-112', country_code: 'SI', text: 'Slovenj Gradec' },\n    { value: 'SI-113', country_code: 'SI', text: 'Slovenska Bistrica' },\n    { value: 'SI-114', country_code: 'SI', text: 'Slovenske Konjice' },\n    { value: 'SI-126', country_code: 'SI', text: 'Sostanj' },\n    { value: 'SI-127', country_code: 'SI', text: 'Store' },\n    { value: 'SI-203', country_code: 'SI', text: 'Straza' },\n    { value: 'SI-128', country_code: 'SI', text: 'Tolmin' },\n    { value: 'SI-129', country_code: 'SI', text: 'Trbovlje' },\n    { value: 'SI-130', country_code: 'SI', text: 'Trebnje' },\n    { value: 'SI-131', country_code: 'SI', text: 'Trzic' },\n    { value: 'SI-186', country_code: 'SI', text: 'Trzin' },\n    { value: 'SI-132', country_code: 'SI', text: 'Turnisce' },\n    { value: 'SI-133', country_code: 'SI', text: 'Velenje' },\n    { value: 'SI-136', country_code: 'SI', text: 'Vipava' },\n    { value: 'SI-138', country_code: 'SI', text: 'Vodice' },\n    { value: 'SI-139', country_code: 'SI', text: 'Vojnik' },\n    { value: 'SI-140', country_code: 'SI', text: 'Vrhnika' },\n    { value: 'SI-141', country_code: 'SI', text: 'Vuzenica' },\n    { value: 'SI-142', country_code: 'SI', text: 'Zagorje ob Savi' },\n    { value: 'SI-190', country_code: 'SI', text: 'Zalec' },\n    { value: 'SI-146', country_code: 'SI', text: 'Zelezniki' },\n    { value: 'SI-147', country_code: 'SI', text: 'Ziri' },\n    { value: 'SI-144', country_code: 'SI', text: 'Zrece' },\n    { value: 'SI-193', country_code: 'SI', text: 'Zuzemberk' },\n    { value: 'SK-BC', country_code: 'SK', text: 'Banskobystricky kraj' },\n    { value: 'SK-BL', country_code: 'SK', text: 'Bratislavsky kraj' },\n    { value: 'SK-KI', country_code: 'SK', text: 'Kosicky kraj' },\n    { value: 'SK-NI', country_code: 'SK', text: 'Nitriansky kraj' },\n    { value: 'SK-PV', country_code: 'SK', text: 'Presovsky kraj' },\n    { value: 'SK-TC', country_code: 'SK', text: 'Trenciansky kraj' },\n    { value: 'SK-TA', country_code: 'SK', text: 'Trnavsky kraj' },\n    { value: 'SK-ZI', country_code: 'SK', text: 'Zilinsky kraj' },\n    { value: 'SL-E', country_code: 'SL', text: 'Eastern' },\n    { value: 'SL-N', country_code: 'SL', text: 'Northern' },\n    { value: 'SL-S', country_code: 'SL', text: 'Southern' },\n    { value: 'SL-W', country_code: 'SL', text: 'Western Area' },\n    { value: 'SM-01', country_code: 'SM', text: 'Acquaviva' },\n    { value: 'SM-02', country_code: 'SM', text: 'Chiesanuova' },\n    { value: 'SM-07', country_code: 'SM', text: 'San Marino' },\n    { value: 'SM-09', country_code: 'SM', text: 'Serravalle' },\n    { value: 'SN-DK', country_code: 'SN', text: 'Dakar' },\n    { value: 'SN-DB', country_code: 'SN', text: 'Diourbel' },\n    { value: 'SN-FK', country_code: 'SN', text: 'Fatick' },\n    { value: 'SN-KA', country_code: 'SN', text: 'Kaffrine' },\n    { value: 'SN-KL', country_code: 'SN', text: 'Kaolack' },\n    { value: 'SN-KE', country_code: 'SN', text: 'Kedougou' },\n    { value: 'SN-KD', country_code: 'SN', text: 'Kolda' },\n    { value: 'SN-LG', country_code: 'SN', text: 'Louga' },\n    { value: 'SN-MT', country_code: 'SN', text: 'Matam' },\n    { value: 'SN-SL', country_code: 'SN', text: 'Saint-Louis' },\n    { value: 'SN-SE', country_code: 'SN', text: 'Sedhiou' },\n    { value: 'SN-TC', country_code: 'SN', text: 'Tambacounda' },\n    { value: 'SN-TH', country_code: 'SN', text: 'Thies' },\n    { value: 'SN-ZG', country_code: 'SN', text: 'Ziguinchor' },\n    { value: 'SO-AW', country_code: 'SO', text: 'Awdal' },\n    { value: 'SO-BK', country_code: 'SO', text: 'Bakool' },\n    { value: 'SO-BN', country_code: 'SO', text: 'Banaadir' },\n    { value: 'SO-BR', country_code: 'SO', text: 'Bari' },\n    { value: 'SO-BY', country_code: 'SO', text: 'Bay' },\n    { value: 'SO-GA', country_code: 'SO', text: 'Galguduud' },\n    { value: 'SO-GE', country_code: 'SO', text: 'Gedo' },\n    { value: 'SO-HI', country_code: 'SO', text: 'Hiiraan' },\n    { value: 'SO-JD', country_code: 'SO', text: 'Jubbada Dhexe' },\n    { value: 'SO-JH', country_code: 'SO', text: 'Jubbada Hoose' },\n    { value: 'SO-MU', country_code: 'SO', text: 'Mudug' },\n    { value: 'SO-NU', country_code: 'SO', text: 'Nugaal' },\n    { value: 'SO-SA', country_code: 'SO', text: 'Sanaag' },\n    { value: 'SO-SD', country_code: 'SO', text: 'Shabeellaha Dhexe' },\n    { value: 'SO-SH', country_code: 'SO', text: 'Shabeellaha Hoose' },\n    { value: 'SO-SO', country_code: 'SO', text: 'Sool' },\n    { value: 'SO-TO', country_code: 'SO', text: 'Togdheer' },\n    { value: 'SO-WO', country_code: 'SO', text: 'Woqooyi Galbeed' },\n    { value: 'SR-BR', country_code: 'SR', text: 'Brokopondo' },\n    { value: 'SR-CM', country_code: 'SR', text: 'Commewijne' },\n    { value: 'SR-CR', country_code: 'SR', text: 'Coronie' },\n    { value: 'SR-MA', country_code: 'SR', text: 'Marowijne' },\n    { value: 'SR-NI', country_code: 'SR', text: 'Nickerie' },\n    { value: 'SR-PR', country_code: 'SR', text: 'Para' },\n    { value: 'SR-PM', country_code: 'SR', text: 'Paramaribo' },\n    { value: 'SR-SA', country_code: 'SR', text: 'Saramacca' },\n    { value: 'SR-WA', country_code: 'SR', text: 'Wanica' },\n    { value: 'SS-EC', country_code: 'SS', text: 'Central Equatoria' },\n    { value: 'SS-EE', country_code: 'SS', text: 'Eastern Equatoria' },\n    { value: 'SS-JG', country_code: 'SS', text: 'Jonglei' },\n    { value: 'SS-LK', country_code: 'SS', text: 'Lakes' },\n    { value: 'SS-BN', country_code: 'SS', text: 'Northern Bahr el Ghazal' },\n    { value: 'SS-UY', country_code: 'SS', text: 'Unity' },\n    { value: 'SS-NU', country_code: 'SS', text: 'Upper Nile' },\n    { value: 'SS-WR', country_code: 'SS', text: 'Warrap' },\n    { value: 'SS-BW', country_code: 'SS', text: 'Western Bahr el Ghazal' },\n    { value: 'SS-EW', country_code: 'SS', text: 'Western Equatoria' },\n    { value: 'ST-P', country_code: 'ST', text: 'Principe' },\n    { value: 'ST-S', country_code: 'ST', text: 'Sao Tome' },\n    { value: 'SV-AH', country_code: 'SV', text: 'Ahuachapan' },\n    { value: 'SV-CA', country_code: 'SV', text: 'Cabanas' },\n    { value: 'SV-CH', country_code: 'SV', text: 'Chalatenango' },\n    { value: 'SV-CU', country_code: 'SV', text: 'Cuscatlan' },\n    { value: 'SV-LI', country_code: 'SV', text: 'La Libertad' },\n    { value: 'SV-PA', country_code: 'SV', text: 'La Paz' },\n    { value: 'SV-UN', country_code: 'SV', text: 'La Union' },\n    { value: 'SV-MO', country_code: 'SV', text: 'Morazan' },\n    { value: 'SV-SM', country_code: 'SV', text: 'San Miguel' },\n    { value: 'SV-SS', country_code: 'SV', text: 'San Salvador' },\n    { value: 'SV-SV', country_code: 'SV', text: 'San Vicente' },\n    { value: 'SV-SA', country_code: 'SV', text: 'Santa Ana' },\n    { value: 'SV-SO', country_code: 'SV', text: 'Sonsonate' },\n    { value: 'SV-US', country_code: 'SV', text: 'Usulutan' },\n    { value: 'SY-HA', country_code: 'SY', text: 'Al Hasakah' },\n    { value: 'SY-LA', country_code: 'SY', text: 'Al Ladhiqiyah' },\n    { value: 'SY-QU', country_code: 'SY', text: 'Al Qunaytirah' },\n    { value: 'SY-RA', country_code: 'SY', text: 'Ar Raqqah' },\n    { value: 'SY-SU', country_code: 'SY', text: \"As Suwayda'\" },\n    { value: 'SY-DR', country_code: 'SY', text: \"Dar'a\" },\n    { value: 'SY-DY', country_code: 'SY', text: 'Dayr az Zawr' },\n    { value: 'SY-DI', country_code: 'SY', text: 'Dimashq' },\n    { value: 'SY-HL', country_code: 'SY', text: 'Halab' },\n    { value: 'SY-HM', country_code: 'SY', text: 'Hamah' },\n    { value: 'SY-HI', country_code: 'SY', text: 'Hims' },\n    { value: 'SY-ID', country_code: 'SY', text: 'Idlib' },\n    { value: 'SY-RD', country_code: 'SY', text: 'Rif Dimashq' },\n    { value: 'SY-TA', country_code: 'SY', text: 'Tartus' },\n    { value: 'SZ-HH', country_code: 'SZ', text: 'Hhohho' },\n    { value: 'SZ-LU', country_code: 'SZ', text: 'Lubombo' },\n    { value: 'SZ-MA', country_code: 'SZ', text: 'Manzini' },\n    { value: 'SZ-SH', country_code: 'SZ', text: 'Shiselweni' },\n    { value: 'TD-BG', country_code: 'TD', text: 'Bahr el Gazel' },\n    { value: 'TD-BA', country_code: 'TD', text: 'Batha' },\n    { value: 'TD-BO', country_code: 'TD', text: 'Borkou' },\n    { value: 'TD-CB', country_code: 'TD', text: 'Chari-Baguirmi' },\n    { value: 'TD-GR', country_code: 'TD', text: 'Guera' },\n    { value: 'TD-HL', country_code: 'TD', text: 'Hadjer Lamis' },\n    { value: 'TD-KA', country_code: 'TD', text: 'Kanem' },\n    { value: 'TD-LC', country_code: 'TD', text: 'Lac' },\n    { value: 'TD-LO', country_code: 'TD', text: 'Logone-Occidental' },\n    { value: 'TD-LR', country_code: 'TD', text: 'Logone-Oriental' },\n    { value: 'TD-MA', country_code: 'TD', text: 'Mandoul' },\n    { value: 'TD-ME', country_code: 'TD', text: 'Mayo-Kebbi-Est' },\n    { value: 'TD-MO', country_code: 'TD', text: 'Mayo-Kebbi-Ouest' },\n    { value: 'TD-MC', country_code: 'TD', text: 'Moyen-Chari' },\n    { value: 'TD-OD', country_code: 'TD', text: 'Ouaddai' },\n    { value: 'TD-SA', country_code: 'TD', text: 'Salamat' },\n    { value: 'TD-TA', country_code: 'TD', text: 'Tandjile' },\n    { value: 'TD-TI', country_code: 'TD', text: 'Tibesti' },\n    { value: 'TD-WF', country_code: 'TD', text: 'Wadi Fira' },\n    { value: 'TG-C', country_code: 'TG', text: 'Centrale' },\n    { value: 'TG-K', country_code: 'TG', text: 'Kara' },\n    { value: 'TG-M', country_code: 'TG', text: 'Maritime' },\n    { value: 'TG-P', country_code: 'TG', text: 'Plateaux' },\n    { value: 'TG-S', country_code: 'TG', text: 'Savannes' },\n    { value: 'TH-37', country_code: 'TH', text: 'Amnat Charoen' },\n    { value: 'TH-15', country_code: 'TH', text: 'Ang Thong' },\n    { value: 'TH-31', country_code: 'TH', text: 'Buri Ram' },\n    { value: 'TH-24', country_code: 'TH', text: 'Chachoengsao' },\n    { value: 'TH-18', country_code: 'TH', text: 'Chai Nat' },\n    { value: 'TH-36', country_code: 'TH', text: 'Chaiyaphum' },\n    { value: 'TH-22', country_code: 'TH', text: 'Chanthaburi' },\n    { value: 'TH-50', country_code: 'TH', text: 'Chiang Mai' },\n    { value: 'TH-57', country_code: 'TH', text: 'Chiang Rai' },\n    { value: 'TH-20', country_code: 'TH', text: 'Chon Buri' },\n    { value: 'TH-86', country_code: 'TH', text: 'Chumphon' },\n    { value: 'TH-46', country_code: 'TH', text: 'Kalasin' },\n    { value: 'TH-62', country_code: 'TH', text: 'Kamphaeng Phet' },\n    { value: 'TH-71', country_code: 'TH', text: 'Kanchanaburi' },\n    { value: 'TH-40', country_code: 'TH', text: 'Khon Kaen' },\n    { value: 'TH-81', country_code: 'TH', text: 'Krabi' },\n    { value: 'TH-10', country_code: 'TH', text: 'Krung Thep Maha Nakhon' },\n    { value: 'TH-52', country_code: 'TH', text: 'Lampang' },\n    { value: 'TH-51', country_code: 'TH', text: 'Lamphun' },\n    { value: 'TH-42', country_code: 'TH', text: 'Loei' },\n    { value: 'TH-16', country_code: 'TH', text: 'Lop Buri' },\n    { value: 'TH-58', country_code: 'TH', text: 'Mae Hong Son' },\n    { value: 'TH-44', country_code: 'TH', text: 'Maha Sarakham' },\n    { value: 'TH-49', country_code: 'TH', text: 'Mukdahan' },\n    { value: 'TH-26', country_code: 'TH', text: 'Nakhon Nayok' },\n    { value: 'TH-73', country_code: 'TH', text: 'Nakhon Pathom' },\n    { value: 'TH-48', country_code: 'TH', text: 'Nakhon Phanom' },\n    { value: 'TH-30', country_code: 'TH', text: 'Nakhon Ratchasima' },\n    { value: 'TH-60', country_code: 'TH', text: 'Nakhon Sawan' },\n    { value: 'TH-80', country_code: 'TH', text: 'Nakhon Si Thammarat' },\n    { value: 'TH-55', country_code: 'TH', text: 'Nan' },\n    { value: 'TH-96', country_code: 'TH', text: 'Narathiwat' },\n    { value: 'TH-39', country_code: 'TH', text: 'Nong Bua Lam Phu' },\n    { value: 'TH-43', country_code: 'TH', text: 'Nong Khai' },\n    { value: 'TH-12', country_code: 'TH', text: 'Nonthaburi' },\n    { value: 'TH-13', country_code: 'TH', text: 'Pathum Thani' },\n    { value: 'TH-94', country_code: 'TH', text: 'Pattani' },\n    { value: 'TH-82', country_code: 'TH', text: 'Phangnga' },\n    { value: 'TH-93', country_code: 'TH', text: 'Phatthalung' },\n    { value: 'TH-56', country_code: 'TH', text: 'Phayao' },\n    { value: 'TH-67', country_code: 'TH', text: 'Phetchabun' },\n    { value: 'TH-76', country_code: 'TH', text: 'Phetchaburi' },\n    { value: 'TH-66', country_code: 'TH', text: 'Phichit' },\n    { value: 'TH-65', country_code: 'TH', text: 'Phitsanulok' },\n    { value: 'TH-14', country_code: 'TH', text: 'Phra Nakhon Si Ayutthaya' },\n    { value: 'TH-54', country_code: 'TH', text: 'Phrae' },\n    { value: 'TH-83', country_code: 'TH', text: 'Phuket' },\n    { value: 'TH-25', country_code: 'TH', text: 'Prachin Buri' },\n    { value: 'TH-77', country_code: 'TH', text: 'Prachuap Khiri Khan' },\n    { value: 'TH-85', country_code: 'TH', text: 'Ranong' },\n    { value: 'TH-70', country_code: 'TH', text: 'Ratchaburi' },\n    { value: 'TH-21', country_code: 'TH', text: 'Rayong' },\n    { value: 'TH-45', country_code: 'TH', text: 'Roi Et' },\n    { value: 'TH-27', country_code: 'TH', text: 'Sa Kaeo' },\n    { value: 'TH-47', country_code: 'TH', text: 'Sakon Nakhon' },\n    { value: 'TH-11', country_code: 'TH', text: 'Samut Prakan' },\n    { value: 'TH-74', country_code: 'TH', text: 'Samut Sakhon' },\n    { value: 'TH-75', country_code: 'TH', text: 'Samut Songkhram' },\n    { value: 'TH-19', country_code: 'TH', text: 'Saraburi' },\n    { value: 'TH-91', country_code: 'TH', text: 'Satun' },\n    { value: 'TH-33', country_code: 'TH', text: 'Si Sa Ket' },\n    { value: 'TH-17', country_code: 'TH', text: 'Sing Buri' },\n    { value: 'TH-90', country_code: 'TH', text: 'Songkhla' },\n    { value: 'TH-64', country_code: 'TH', text: 'Sukhothai' },\n    { value: 'TH-72', country_code: 'TH', text: 'Suphan Buri' },\n    { value: 'TH-84', country_code: 'TH', text: 'Surat Thani' },\n    { value: 'TH-32', country_code: 'TH', text: 'Surin' },\n    { value: 'TH-63', country_code: 'TH', text: 'Tak' },\n    { value: 'TH-92', country_code: 'TH', text: 'Trang' },\n    { value: 'TH-23', country_code: 'TH', text: 'Trat' },\n    { value: 'TH-34', country_code: 'TH', text: 'Ubon Ratchathani' },\n    { value: 'TH-41', country_code: 'TH', text: 'Udon Thani' },\n    { value: 'TH-61', country_code: 'TH', text: 'Uthai Thani' },\n    { value: 'TH-53', country_code: 'TH', text: 'Uttaradit' },\n    { value: 'TH-95', country_code: 'TH', text: 'Yala' },\n    { value: 'TH-35', country_code: 'TH', text: 'Yasothon' },\n    { value: 'TJ-DU', country_code: 'TJ', text: 'Dushanbe' },\n    { value: 'TJ-KT', country_code: 'TJ', text: 'Khatlon' },\n    { value: 'TJ-GB', country_code: 'TJ', text: 'Kuhistoni Badakhshon' },\n    { value: 'TJ-RA', country_code: 'TJ', text: 'Nohiyahoi Tobei Jumhuri' },\n    { value: 'TJ-SU', country_code: 'TJ', text: 'Sughd' },\n    { value: 'TL-DI', country_code: 'TL', text: 'Dili' },\n    { value: 'TM-A', country_code: 'TM', text: 'Ahal' },\n    { value: 'TM-B', country_code: 'TM', text: 'Balkan' },\n    { value: 'TM-D', country_code: 'TM', text: 'Dasoguz' },\n    { value: 'TM-L', country_code: 'TM', text: 'Lebap' },\n    { value: 'TM-M', country_code: 'TM', text: 'Mary' },\n    { value: 'TN-31', country_code: 'TN', text: 'Beja' },\n    { value: 'TN-13', country_code: 'TN', text: 'Ben Arous' },\n    { value: 'TN-23', country_code: 'TN', text: 'Bizerte' },\n    { value: 'TN-81', country_code: 'TN', text: 'Gabes' },\n    { value: 'TN-71', country_code: 'TN', text: 'Gafsa' },\n    { value: 'TN-32', country_code: 'TN', text: 'Jendouba' },\n    { value: 'TN-41', country_code: 'TN', text: 'Kairouan' },\n    { value: 'TN-42', country_code: 'TN', text: 'Kasserine' },\n    { value: 'TN-73', country_code: 'TN', text: 'Kebili' },\n    { value: 'TN-12', country_code: 'TN', text: \"L'Ariana\" },\n    { value: 'TN-14', country_code: 'TN', text: 'La Manouba' },\n    { value: 'TN-33', country_code: 'TN', text: 'Le Kef' },\n    { value: 'TN-53', country_code: 'TN', text: 'Mahdia' },\n    { value: 'TN-82', country_code: 'TN', text: 'Medenine' },\n    { value: 'TN-52', country_code: 'TN', text: 'Monastir' },\n    { value: 'TN-21', country_code: 'TN', text: 'Nabeul' },\n    { value: 'TN-61', country_code: 'TN', text: 'Sfax' },\n    { value: 'TN-43', country_code: 'TN', text: 'Sidi Bouzid' },\n    { value: 'TN-34', country_code: 'TN', text: 'Siliana' },\n    { value: 'TN-51', country_code: 'TN', text: 'Sousse' },\n    { value: 'TN-83', country_code: 'TN', text: 'Tataouine' },\n    { value: 'TN-72', country_code: 'TN', text: 'Tozeur' },\n    { value: 'TN-11', country_code: 'TN', text: 'Tunis' },\n    { value: 'TN-22', country_code: 'TN', text: 'Zaghouan' },\n    { value: 'TO-02', country_code: 'TO', text: \"Ha'apai\" },\n    { value: 'TO-04', country_code: 'TO', text: 'Tongatapu' },\n    { value: 'TO-05', country_code: 'TO', text: \"Vava'u\" },\n    { value: 'TR-01', country_code: 'TR', text: 'Adana' },\n    { value: 'TR-02', country_code: 'TR', text: 'Adiyaman' },\n    { value: 'TR-03', country_code: 'TR', text: 'Afyonkarahisar' },\n    { value: 'TR-04', country_code: 'TR', text: 'Agri' },\n    { value: 'TR-68', country_code: 'TR', text: 'Aksaray' },\n    { value: 'TR-05', country_code: 'TR', text: 'Amasya' },\n    { value: 'TR-06', country_code: 'TR', text: 'Ankara' },\n    { value: 'TR-07', country_code: 'TR', text: 'Antalya' },\n    { value: 'TR-75', country_code: 'TR', text: 'Ardahan' },\n    { value: 'TR-08', country_code: 'TR', text: 'Artvin' },\n    { value: 'TR-09', country_code: 'TR', text: 'Aydin' },\n    { value: 'TR-10', country_code: 'TR', text: 'Balikesir' },\n    { value: 'TR-74', country_code: 'TR', text: 'Bartin' },\n    { value: 'TR-72', country_code: 'TR', text: 'Batman' },\n    { value: 'TR-69', country_code: 'TR', text: 'Bayburt' },\n    { value: 'TR-11', country_code: 'TR', text: 'Bilecik' },\n    { value: 'TR-12', country_code: 'TR', text: 'Bingol' },\n    { value: 'TR-13', country_code: 'TR', text: 'Bitlis' },\n    { value: 'TR-14', country_code: 'TR', text: 'Bolu' },\n    { value: 'TR-15', country_code: 'TR', text: 'Burdur' },\n    { value: 'TR-16', country_code: 'TR', text: 'Bursa' },\n    { value: 'TR-17', country_code: 'TR', text: 'Canakkale' },\n    { value: 'TR-18', country_code: 'TR', text: 'Cankiri' },\n    { value: 'TR-19', country_code: 'TR', text: 'Corum' },\n    { value: 'TR-20', country_code: 'TR', text: 'Denizli' },\n    { value: 'TR-21', country_code: 'TR', text: 'Diyarbakir' },\n    { value: 'TR-81', country_code: 'TR', text: 'Duzce' },\n    { value: 'TR-22', country_code: 'TR', text: 'Edirne' },\n    { value: 'TR-23', country_code: 'TR', text: 'Elazig' },\n    { value: 'TR-24', country_code: 'TR', text: 'Erzincan' },\n    { value: 'TR-25', country_code: 'TR', text: 'Erzurum' },\n    { value: 'TR-26', country_code: 'TR', text: 'Eskisehir' },\n    { value: 'TR-27', country_code: 'TR', text: 'Gaziantep' },\n    { value: 'TR-28', country_code: 'TR', text: 'Giresun' },\n    { value: 'TR-29', country_code: 'TR', text: 'Gumushane' },\n    { value: 'TR-30', country_code: 'TR', text: 'Hakkari' },\n    { value: 'TR-31', country_code: 'TR', text: 'Hatay' },\n    { value: 'TR-76', country_code: 'TR', text: 'Igdir' },\n    { value: 'TR-32', country_code: 'TR', text: 'Isparta' },\n    { value: 'TR-34', country_code: 'TR', text: 'Istanbul' },\n    { value: 'TR-35', country_code: 'TR', text: 'Izmir' },\n    { value: 'TR-46', country_code: 'TR', text: 'Kahramanmaras' },\n    { value: 'TR-78', country_code: 'TR', text: 'Karabuk' },\n    { value: 'TR-70', country_code: 'TR', text: 'Karaman' },\n    { value: 'TR-36', country_code: 'TR', text: 'Kars' },\n    { value: 'TR-37', country_code: 'TR', text: 'Kastamonu' },\n    { value: 'TR-38', country_code: 'TR', text: 'Kayseri' },\n    { value: 'TR-79', country_code: 'TR', text: 'Kilis' },\n    { value: 'TR-71', country_code: 'TR', text: 'Kirikkale' },\n    { value: 'TR-39', country_code: 'TR', text: 'Kirklareli' },\n    { value: 'TR-40', country_code: 'TR', text: 'Kirsehir' },\n    { value: 'TR-41', country_code: 'TR', text: 'Kocaeli' },\n    { value: 'TR-42', country_code: 'TR', text: 'Konya' },\n    { value: 'TR-43', country_code: 'TR', text: 'Kutahya' },\n    { value: 'TR-44', country_code: 'TR', text: 'Malatya' },\n    { value: 'TR-45', country_code: 'TR', text: 'Manisa' },\n    { value: 'TR-47', country_code: 'TR', text: 'Mardin' },\n    { value: 'TR-33', country_code: 'TR', text: 'Mersin' },\n    { value: 'TR-48', country_code: 'TR', text: 'Mugla' },\n    { value: 'TR-49', country_code: 'TR', text: 'Mus' },\n    { value: 'TR-50', country_code: 'TR', text: 'Nevsehir' },\n    { value: 'TR-51', country_code: 'TR', text: 'Nigde' },\n    { value: 'TR-52', country_code: 'TR', text: 'Ordu' },\n    { value: 'TR-80', country_code: 'TR', text: 'Osmaniye' },\n    { value: 'TR-53', country_code: 'TR', text: 'Rize' },\n    { value: 'TR-54', country_code: 'TR', text: 'Sakarya' },\n    { value: 'TR-55', country_code: 'TR', text: 'Samsun' },\n    { value: 'TR-63', country_code: 'TR', text: 'Sanliurfa' },\n    { value: 'TR-56', country_code: 'TR', text: 'Siirt' },\n    { value: 'TR-57', country_code: 'TR', text: 'Sinop' },\n    { value: 'TR-73', country_code: 'TR', text: 'Sirnak' },\n    { value: 'TR-58', country_code: 'TR', text: 'Sivas' },\n    { value: 'TR-59', country_code: 'TR', text: 'Tekirdag' },\n    { value: 'TR-60', country_code: 'TR', text: 'Tokat' },\n    { value: 'TR-61', country_code: 'TR', text: 'Trabzon' },\n    { value: 'TR-62', country_code: 'TR', text: 'Tunceli' },\n    { value: 'TR-64', country_code: 'TR', text: 'Usak' },\n    { value: 'TR-65', country_code: 'TR', text: 'Van' },\n    { value: 'TR-77', country_code: 'TR', text: 'Yalova' },\n    { value: 'TR-66', country_code: 'TR', text: 'Yozgat' },\n    { value: 'TR-67', country_code: 'TR', text: 'Zonguldak' },\n    { value: 'TT-ARI', country_code: 'TT', text: 'Arima' },\n    { value: 'TT-CHA', country_code: 'TT', text: 'Chaguanas' },\n    { value: 'TT-CTT', country_code: 'TT', text: 'Couva-Tabaquite-Talparo' },\n    { value: 'TT-DMN', country_code: 'TT', text: 'Diego Martin' },\n    { value: 'TT-MRC', country_code: 'TT', text: 'Mayaro-Rio Claro' },\n    { value: 'TT-PED', country_code: 'TT', text: 'Penal-Debe' },\n    { value: 'TT-PTF', country_code: 'TT', text: 'Point Fortin' },\n    { value: 'TT-POS', country_code: 'TT', text: 'Port of Spain' },\n    { value: 'TT-PRT', country_code: 'TT', text: 'Princes Town' },\n    { value: 'TT-SFO', country_code: 'TT', text: 'San Fernando' },\n    { value: 'TT-SJL', country_code: 'TT', text: 'San Juan-Laventille' },\n    { value: 'TT-SGE', country_code: 'TT', text: 'Sangre Grande' },\n    { value: 'TT-SIP', country_code: 'TT', text: 'Siparia' },\n    { value: 'TT-TOB', country_code: 'TT', text: 'Tobago' },\n    { value: 'TT-TUP', country_code: 'TT', text: 'Tunapuna-Piarco' },\n    { value: 'TV-FUN', country_code: 'TV', text: 'Funafuti' },\n    { value: 'TW-CHA', country_code: 'TW', text: 'Changhua' },\n    { value: 'TW-CYQ', country_code: 'TW', text: 'Chiayi' },\n    { value: 'TW-HSQ', country_code: 'TW', text: 'Hsinchu' },\n    { value: 'TW-HUA', country_code: 'TW', text: 'Hualien' },\n    { value: 'TW-KHH', country_code: 'TW', text: 'Kaohsiung' },\n    { value: 'TW-KEE', country_code: 'TW', text: 'Keelung' },\n    { value: 'TW-KIN', country_code: 'TW', text: 'Kinmen' },\n    { value: 'TW-LIE', country_code: 'TW', text: 'Lienchiang' },\n    { value: 'TW-MIA', country_code: 'TW', text: 'Miaoli' },\n    { value: 'TW-NAN', country_code: 'TW', text: 'Nantou' },\n    { value: 'TW-NWT', country_code: 'TW', text: 'New Taipei' },\n    { value: 'TW-PEN', country_code: 'TW', text: 'Penghu' },\n    { value: 'TW-PIF', country_code: 'TW', text: 'Pingtung' },\n    { value: 'TW-TXG', country_code: 'TW', text: 'Taichung' },\n    { value: 'TW-TNN', country_code: 'TW', text: 'Tainan' },\n    { value: 'TW-TPE', country_code: 'TW', text: 'Taipei' },\n    { value: 'TW-TTT', country_code: 'TW', text: 'Taitung' },\n    { value: 'TW-TAO', country_code: 'TW', text: 'Taoyuan' },\n    { value: 'TW-ILA', country_code: 'TW', text: 'Yilan' },\n    { value: 'TW-YUN', country_code: 'TW', text: 'Yunlin' },\n    { value: 'TZ-01', country_code: 'TZ', text: 'Arusha' },\n    { value: 'TZ-02', country_code: 'TZ', text: 'Dar es Salaam' },\n    { value: 'TZ-03', country_code: 'TZ', text: 'Dodoma' },\n    { value: 'TZ-04', country_code: 'TZ', text: 'Iringa' },\n    { value: 'TZ-05', country_code: 'TZ', text: 'Kagera' },\n    { value: 'TZ-06', country_code: 'TZ', text: 'Kaskazini Pemba' },\n    { value: 'TZ-07', country_code: 'TZ', text: 'Kaskazini Unguja' },\n    { value: 'TZ-08', country_code: 'TZ', text: 'Kigoma' },\n    { value: 'TZ-09', country_code: 'TZ', text: 'Kilimanjaro' },\n    { value: 'TZ-10', country_code: 'TZ', text: 'Kusini Pemba' },\n    { value: 'TZ-11', country_code: 'TZ', text: 'Kusini Unguja' },\n    { value: 'TZ-12', country_code: 'TZ', text: 'Lindi' },\n    { value: 'TZ-26', country_code: 'TZ', text: 'Manyara' },\n    { value: 'TZ-13', country_code: 'TZ', text: 'Mara' },\n    { value: 'TZ-14', country_code: 'TZ', text: 'Mbeya' },\n    { value: 'TZ-15', country_code: 'TZ', text: 'Mjini Magharibi' },\n    { value: 'TZ-16', country_code: 'TZ', text: 'Morogoro' },\n    { value: 'TZ-17', country_code: 'TZ', text: 'Mtwara' },\n    { value: 'TZ-18', country_code: 'TZ', text: 'Mwanza' },\n    { value: 'TZ-19', country_code: 'TZ', text: 'Pwani' },\n    { value: 'TZ-20', country_code: 'TZ', text: 'Rukwa' },\n    { value: 'TZ-21', country_code: 'TZ', text: 'Ruvuma' },\n    { value: 'TZ-22', country_code: 'TZ', text: 'Shinyanga' },\n    { value: 'TZ-23', country_code: 'TZ', text: 'Singida' },\n    { value: 'TZ-24', country_code: 'TZ', text: 'Tabora' },\n    { value: 'TZ-25', country_code: 'TZ', text: 'Tanga' },\n    { value: 'UA-43', country_code: 'UA', text: 'Avtonomna Respublika Krym' },\n    { value: 'UA-71', country_code: 'UA', text: 'Cherkaska oblast' },\n    { value: 'UA-74', country_code: 'UA', text: 'Chernihivska oblast' },\n    { value: 'UA-77', country_code: 'UA', text: 'Chernivetska oblast' },\n    { value: 'UA-12', country_code: 'UA', text: 'Dnipropetrovska oblast' },\n    { value: 'UA-14', country_code: 'UA', text: 'Donetska oblast' },\n    { value: 'UA-26', country_code: 'UA', text: 'Ivano-Frankivska oblast' },\n    { value: 'UA-63', country_code: 'UA', text: 'Kharkivska oblast' },\n    { value: 'UA-65', country_code: 'UA', text: 'Khersonska oblast' },\n    { value: 'UA-68', country_code: 'UA', text: 'Khmelnytska oblast' },\n    { value: 'UA-35', country_code: 'UA', text: 'Kirovohradska oblast' },\n    { value: 'UA-30', country_code: 'UA', text: 'Kyiv' },\n    { value: 'UA-32', country_code: 'UA', text: 'Kyivska oblast' },\n    { value: 'UA-09', country_code: 'UA', text: 'Luhanska oblast' },\n    { value: 'UA-46', country_code: 'UA', text: 'Lvivska oblast' },\n    { value: 'UA-48', country_code: 'UA', text: 'Mykolaivska oblast' },\n    { value: 'UA-51', country_code: 'UA', text: 'Odeska oblast' },\n    { value: 'UA-53', country_code: 'UA', text: 'Poltavska oblast' },\n    { value: 'UA-56', country_code: 'UA', text: 'Rivnenska oblast' },\n    { value: 'UA-40', country_code: 'UA', text: \"Sevastopol'\" },\n    { value: 'UA-59', country_code: 'UA', text: 'Sumska oblast' },\n    { value: 'UA-61', country_code: 'UA', text: 'Ternopilska oblast' },\n    { value: 'UA-05', country_code: 'UA', text: 'Vinnytska oblast' },\n    { value: 'UA-07', country_code: 'UA', text: 'Volynska oblast' },\n    { value: 'UA-21', country_code: 'UA', text: 'Zakarpatska oblast' },\n    { value: 'UA-23', country_code: 'UA', text: 'Zaporizka oblast' },\n    { value: 'UA-18', country_code: 'UA', text: 'Zhytomyrska oblast' },\n    { value: 'UG-317', country_code: 'UG', text: 'Abim' },\n    { value: 'UG-301', country_code: 'UG', text: 'Adjumani' },\n    { value: 'UG-322', country_code: 'UG', text: 'Agago' },\n    { value: 'UG-323', country_code: 'UG', text: 'Alebtong' },\n    { value: 'UG-314', country_code: 'UG', text: 'Amolatar' },\n    { value: 'UG-324', country_code: 'UG', text: 'Amudat' },\n    { value: 'UG-216', country_code: 'UG', text: 'Amuria' },\n    { value: 'UG-319', country_code: 'UG', text: 'Amuru' },\n    { value: 'UG-302', country_code: 'UG', text: 'Apac' },\n    { value: 'UG-303', country_code: 'UG', text: 'Arua' },\n    { value: 'UG-217', country_code: 'UG', text: 'Budaka' },\n    { value: 'UG-223', country_code: 'UG', text: 'Bududa' },\n    { value: 'UG-201', country_code: 'UG', text: 'Bugiri' },\n    { value: 'UG-117', country_code: 'UG', text: 'Buikwe' },\n    { value: 'UG-224', country_code: 'UG', text: 'Bukedea' },\n    { value: 'UG-118', country_code: 'UG', text: 'Bukomansibi' },\n    { value: 'UG-218', country_code: 'UG', text: 'Bukwa' },\n    { value: 'UG-225', country_code: 'UG', text: 'Bulambuli' },\n    { value: 'UG-419', country_code: 'UG', text: 'Buliisa' },\n    { value: 'UG-401', country_code: 'UG', text: 'Bundibugyo' },\n    { value: 'UG-402', country_code: 'UG', text: 'Bushenyi' },\n    { value: 'UG-202', country_code: 'UG', text: 'Busia' },\n    { value: 'UG-219', country_code: 'UG', text: 'Butaleja' },\n    { value: 'UG-120', country_code: 'UG', text: 'Buvuma' },\n    { value: 'UG-226', country_code: 'UG', text: 'Buyende' },\n    { value: 'UG-318', country_code: 'UG', text: 'Dokolo' },\n    { value: 'UG-121', country_code: 'UG', text: 'Gomba' },\n    { value: 'UG-304', country_code: 'UG', text: 'Gulu' },\n    { value: 'UG-403', country_code: 'UG', text: 'Hoima' },\n    { value: 'UG-203', country_code: 'UG', text: 'Iganga' },\n    { value: 'UG-417', country_code: 'UG', text: 'Isingiro' },\n    { value: 'UG-204', country_code: 'UG', text: 'Jinja' },\n    { value: 'UG-315', country_code: 'UG', text: 'Kaabong' },\n    { value: 'UG-404', country_code: 'UG', text: 'Kabale' },\n    { value: 'UG-405', country_code: 'UG', text: 'Kabarole' },\n    { value: 'UG-213', country_code: 'UG', text: 'Kaberamaido' },\n    { value: 'UG-101', country_code: 'UG', text: 'Kalangala' },\n    { value: 'UG-220', country_code: 'UG', text: 'Kaliro' },\n    { value: 'UG-102', country_code: 'UG', text: 'Kampala' },\n    { value: 'UG-205', country_code: 'UG', text: 'Kamuli' },\n    { value: 'UG-413', country_code: 'UG', text: 'Kamwenge' },\n    { value: 'UG-414', country_code: 'UG', text: 'Kanungu' },\n    { value: 'UG-206', country_code: 'UG', text: 'Kapchorwa' },\n    { value: 'UG-406', country_code: 'UG', text: 'Kasese' },\n    { value: 'UG-207', country_code: 'UG', text: 'Katakwi' },\n    { value: 'UG-112', country_code: 'UG', text: 'Kayunga' },\n    { value: 'UG-407', country_code: 'UG', text: 'Kibaale' },\n    { value: 'UG-103', country_code: 'UG', text: 'Kiboga' },\n    { value: 'UG-227', country_code: 'UG', text: 'Kibuku' },\n    { value: 'UG-420', country_code: 'UG', text: 'Kiryandongo' },\n    { value: 'UG-408', country_code: 'UG', text: 'Kisoro' },\n    { value: 'UG-305', country_code: 'UG', text: 'Kitgum' },\n    { value: 'UG-316', country_code: 'UG', text: 'Koboko' },\n    { value: 'UG-326', country_code: 'UG', text: 'Kole' },\n    { value: 'UG-306', country_code: 'UG', text: 'Kotido' },\n    { value: 'UG-208', country_code: 'UG', text: 'Kumi' },\n    { value: 'UG-228', country_code: 'UG', text: 'Kween' },\n    { value: 'UG-123', country_code: 'UG', text: 'Kyankwanzi' },\n    { value: 'UG-421', country_code: 'UG', text: 'Kyegegwa' },\n    { value: 'UG-415', country_code: 'UG', text: 'Kyenjojo' },\n    { value: 'UG-307', country_code: 'UG', text: 'Lira' },\n    { value: 'UG-229', country_code: 'UG', text: 'Luuka' },\n    { value: 'UG-104', country_code: 'UG', text: 'Luwero' },\n    { value: 'UG-124', country_code: 'UG', text: 'Lwengo' },\n    { value: 'UG-116', country_code: 'UG', text: 'Lyantonde' },\n    { value: 'UG-221', country_code: 'UG', text: 'Manafwa' },\n    { value: 'UG-320', country_code: 'UG', text: 'Maracha' },\n    { value: 'UG-105', country_code: 'UG', text: 'Masaka' },\n    { value: 'UG-409', country_code: 'UG', text: 'Masindi' },\n    { value: 'UG-214', country_code: 'UG', text: 'Mayuge' },\n    { value: 'UG-209', country_code: 'UG', text: 'Mbale' },\n    { value: 'UG-410', country_code: 'UG', text: 'Mbarara' },\n    { value: 'UG-422', country_code: 'UG', text: 'Mitooma' },\n    { value: 'UG-114', country_code: 'UG', text: 'Mityana' },\n    { value: 'UG-308', country_code: 'UG', text: 'Moroto' },\n    { value: 'UG-309', country_code: 'UG', text: 'Moyo' },\n    { value: 'UG-106', country_code: 'UG', text: 'Mpigi' },\n    { value: 'UG-107', country_code: 'UG', text: 'Mubende' },\n    { value: 'UG-108', country_code: 'UG', text: 'Mukono' },\n    { value: 'UG-311', country_code: 'UG', text: 'Nakapiripirit' },\n    { value: 'UG-115', country_code: 'UG', text: 'Nakaseke' },\n    { value: 'UG-109', country_code: 'UG', text: 'Nakasongola' },\n    { value: 'UG-230', country_code: 'UG', text: 'Namayingo' },\n    { value: 'UG-222', country_code: 'UG', text: 'Namutumba' },\n    { value: 'UG-328', country_code: 'UG', text: 'Napak' },\n    { value: 'UG-310', country_code: 'UG', text: 'Nebbi' },\n    { value: 'UG-231', country_code: 'UG', text: 'Ngora' },\n    { value: 'UG-423', country_code: 'UG', text: 'Ntoroko' },\n    { value: 'UG-411', country_code: 'UG', text: 'Ntungamo' },\n    { value: 'UG-330', country_code: 'UG', text: 'Otuke' },\n    { value: 'UG-321', country_code: 'UG', text: 'Oyam' },\n    { value: 'UG-312', country_code: 'UG', text: 'Pader' },\n    { value: 'UG-210', country_code: 'UG', text: 'Pallisa' },\n    { value: 'UG-110', country_code: 'UG', text: 'Rakai' },\n    { value: 'UG-424', country_code: 'UG', text: 'Rubirizi' },\n    { value: 'UG-412', country_code: 'UG', text: 'Rukungiri' },\n    { value: 'UG-111', country_code: 'UG', text: 'Sembabule' },\n    { value: 'UG-232', country_code: 'UG', text: 'Serere' },\n    { value: 'UG-425', country_code: 'UG', text: 'Sheema' },\n    { value: 'UG-215', country_code: 'UG', text: 'Sironko' },\n    { value: 'UG-211', country_code: 'UG', text: 'Soroti' },\n    { value: 'UG-212', country_code: 'UG', text: 'Tororo' },\n    { value: 'UG-113', country_code: 'UG', text: 'Wakiso' },\n    { value: 'UG-313', country_code: 'UG', text: 'Yumbe' },\n    { value: 'UG-331', country_code: 'UG', text: 'Zombo' },\n    { value: 'UM-95', country_code: 'UM', text: 'Palmyra Atoll' },\n    { value: 'US-AL', country_code: 'US', text: 'Alabama' },\n    { value: 'US-AK', country_code: 'US', text: 'Alaska' },\n    { value: 'US-AZ', country_code: 'US', text: 'Arizona' },\n    { value: 'US-AR', country_code: 'US', text: 'Arkansas' },\n    { value: 'US-CA', country_code: 'US', text: 'California' },\n    { value: 'US-CO', country_code: 'US', text: 'Colorado' },\n    { value: 'US-CT', country_code: 'US', text: 'Connecticut' },\n    { value: 'US-DE', country_code: 'US', text: 'Delaware' },\n    { value: 'US-DC', country_code: 'US', text: 'District of Columbia' },\n    { value: 'US-FL', country_code: 'US', text: 'Florida' },\n    { value: 'US-GA', country_code: 'US', text: 'Georgia' },\n    { value: 'US-HI', country_code: 'US', text: 'Hawaii' },\n    { value: 'US-ID', country_code: 'US', text: 'Idaho' },\n    { value: 'US-IL', country_code: 'US', text: 'Illinois' },\n    { value: 'US-IN', country_code: 'US', text: 'Indiana' },\n    { value: 'US-IA', country_code: 'US', text: 'Iowa' },\n    { value: 'US-KS', country_code: 'US', text: 'Kansas' },\n    { value: 'US-KY', country_code: 'US', text: 'Kentucky' },\n    { value: 'US-LA', country_code: 'US', text: 'Louisiana' },\n    { value: 'US-ME', country_code: 'US', text: 'Maine' },\n    { value: 'US-MD', country_code: 'US', text: 'Maryland' },\n    { value: 'US-MA', country_code: 'US', text: 'Massachusetts' },\n    { value: 'US-MI', country_code: 'US', text: 'Michigan' },\n    { value: 'US-MN', country_code: 'US', text: 'Minnesota' },\n    { value: 'US-MS', country_code: 'US', text: 'Mississippi' },\n    { value: 'US-MO', country_code: 'US', text: 'Missouri' },\n    { value: 'US-MT', country_code: 'US', text: 'Montana' },\n    { value: 'US-NE', country_code: 'US', text: 'Nebraska' },\n    { value: 'US-NV', country_code: 'US', text: 'Nevada' },\n    { value: 'US-NH', country_code: 'US', text: 'New Hampshire' },\n    { value: 'US-NJ', country_code: 'US', text: 'New Jersey' },\n    { value: 'US-NM', country_code: 'US', text: 'New Mexico' },\n    { value: 'US-NY', country_code: 'US', text: 'New York' },\n    { value: 'US-NC', country_code: 'US', text: 'North Carolina' },\n    { value: 'US-ND', country_code: 'US', text: 'North Dakota' },\n    { value: 'US-OH', country_code: 'US', text: 'Ohio' },\n    { value: 'US-OK', country_code: 'US', text: 'Oklahoma' },\n    { value: 'US-OR', country_code: 'US', text: 'Oregon' },\n    { value: 'US-PA', country_code: 'US', text: 'Pennsylvania' },\n    { value: 'US-RI', country_code: 'US', text: 'Rhode Island' },\n    { value: 'US-SC', country_code: 'US', text: 'South Carolina' },\n    { value: 'US-SD', country_code: 'US', text: 'South Dakota' },\n    { value: 'US-TN', country_code: 'US', text: 'Tennessee' },\n    { value: 'US-TX', country_code: 'US', text: 'Texas' },\n    { value: 'US-UT', country_code: 'US', text: 'Utah' },\n    { value: 'US-VT', country_code: 'US', text: 'Vermont' },\n    { value: 'US-VA', country_code: 'US', text: 'Virginia' },\n    { value: 'US-WA', country_code: 'US', text: 'Washington' },\n    { value: 'US-WV', country_code: 'US', text: 'West Virginia' },\n    { value: 'US-WI', country_code: 'US', text: 'Wisconsin' },\n    { value: 'US-WY', country_code: 'US', text: 'Wyoming' },\n    { value: 'UY-AR', country_code: 'UY', text: 'Artigas' },\n    { value: 'UY-CA', country_code: 'UY', text: 'Canelones' },\n    { value: 'UY-CL', country_code: 'UY', text: 'Cerro Largo' },\n    { value: 'UY-CO', country_code: 'UY', text: 'Colonia' },\n    { value: 'UY-DU', country_code: 'UY', text: 'Durazno' },\n    { value: 'UY-FS', country_code: 'UY', text: 'Flores' },\n    { value: 'UY-FD', country_code: 'UY', text: 'Florida' },\n    { value: 'UY-LA', country_code: 'UY', text: 'Lavalleja' },\n    { value: 'UY-MA', country_code: 'UY', text: 'Maldonado' },\n    { value: 'UY-MO', country_code: 'UY', text: 'Montevideo' },\n    { value: 'UY-PA', country_code: 'UY', text: 'Paysandu' },\n    { value: 'UY-RN', country_code: 'UY', text: 'Rio Negro' },\n    { value: 'UY-RV', country_code: 'UY', text: 'Rivera' },\n    { value: 'UY-RO', country_code: 'UY', text: 'Rocha' },\n    { value: 'UY-SA', country_code: 'UY', text: 'Salto' },\n    { value: 'UY-SJ', country_code: 'UY', text: 'San Jose' },\n    { value: 'UY-SO', country_code: 'UY', text: 'Soriano' },\n    { value: 'UY-TA', country_code: 'UY', text: 'Tacuarembo' },\n    { value: 'UY-TT', country_code: 'UY', text: 'Treinta y Tres' },\n    { value: 'UZ-AN', country_code: 'UZ', text: 'Andijon' },\n    { value: 'UZ-BU', country_code: 'UZ', text: 'Buxoro' },\n    { value: 'UZ-FA', country_code: 'UZ', text: \"Farg'ona\" },\n    { value: 'UZ-JI', country_code: 'UZ', text: 'Jizzax' },\n    { value: 'UZ-NG', country_code: 'UZ', text: 'Namangan' },\n    { value: 'UZ-NW', country_code: 'UZ', text: 'Navoiy' },\n    { value: 'UZ-QA', country_code: 'UZ', text: 'Qashqadaryo' },\n    {\n      value: 'UZ-QR',\n      country_code: 'UZ',\n      text: \"Qoraqalpog'iston Respublikasi\"\n    },\n    { value: 'UZ-SA', country_code: 'UZ', text: 'Samarqand' },\n    { value: 'UZ-SI', country_code: 'UZ', text: 'Sirdaryo' },\n    { value: 'UZ-SU', country_code: 'UZ', text: 'Surxondaryo' },\n    { value: 'UZ-TK', country_code: 'UZ', text: 'Toshkent' },\n    { value: 'UZ-XO', country_code: 'UZ', text: 'Xorazm' },\n    { value: 'VC-01', country_code: 'VC', text: 'Charlotte' },\n    { value: 'VC-04', country_code: 'VC', text: 'Saint George' },\n    { value: 'VE-Z', country_code: 'VE', text: 'Amazonas' },\n    { value: 'VE-B', country_code: 'VE', text: 'Anzoategui' },\n    { value: 'VE-C', country_code: 'VE', text: 'Apure' },\n    { value: 'VE-D', country_code: 'VE', text: 'Aragua' },\n    { value: 'VE-E', country_code: 'VE', text: 'Barinas' },\n    { value: 'VE-F', country_code: 'VE', text: 'Bolivar' },\n    { value: 'VE-G', country_code: 'VE', text: 'Carabobo' },\n    { value: 'VE-H', country_code: 'VE', text: 'Cojedes' },\n    { value: 'VE-Y', country_code: 'VE', text: 'Delta Amacuro' },\n    { value: 'VE-A', country_code: 'VE', text: 'Distrito Capital' },\n    { value: 'VE-I', country_code: 'VE', text: 'Falcon' },\n    { value: 'VE-J', country_code: 'VE', text: 'Guarico' },\n    { value: 'VE-K', country_code: 'VE', text: 'Lara' },\n    { value: 'VE-L', country_code: 'VE', text: 'Merida' },\n    { value: 'VE-M', country_code: 'VE', text: 'Miranda' },\n    { value: 'VE-N', country_code: 'VE', text: 'Monagas' },\n    { value: 'VE-O', country_code: 'VE', text: 'Nueva Esparta' },\n    { value: 'VE-P', country_code: 'VE', text: 'Portuguesa' },\n    { value: 'VE-R', country_code: 'VE', text: 'Sucre' },\n    { value: 'VE-S', country_code: 'VE', text: 'Tachira' },\n    { value: 'VE-T', country_code: 'VE', text: 'Trujillo' },\n    { value: 'VE-X', country_code: 'VE', text: 'Vargas' },\n    { value: 'VE-U', country_code: 'VE', text: 'Yaracuy' },\n    { value: 'VE-V', country_code: 'VE', text: 'Zulia' },\n    { value: 'VN-44', country_code: 'VN', text: 'An Giang' },\n    { value: 'VN-54', country_code: 'VN', text: 'Bac Giang' },\n    { value: 'VN-53', country_code: 'VN', text: 'Bac Kan' },\n    { value: 'VN-55', country_code: 'VN', text: 'Bac Lieu' },\n    { value: 'VN-56', country_code: 'VN', text: 'Bac Ninh' },\n    { value: 'VN-50', country_code: 'VN', text: 'Ben Tre' },\n    { value: 'VN-31', country_code: 'VN', text: 'Binh Dinh' },\n    { value: 'VN-57', country_code: 'VN', text: 'Binh Duong' },\n    { value: 'VN-58', country_code: 'VN', text: 'Binh Phuoc' },\n    { value: 'VN-40', country_code: 'VN', text: 'Binh Thuan' },\n    { value: 'VN-59', country_code: 'VN', text: 'Ca Mau' },\n    { value: 'VN-CT', country_code: 'VN', text: 'Can Tho' },\n    { value: 'VN-04', country_code: 'VN', text: 'Cao Bang' },\n    { value: 'VN-DN', country_code: 'VN', text: 'Da Nang' },\n    { value: 'VN-33', country_code: 'VN', text: 'Dak Lak' },\n    { value: 'VN-71', country_code: 'VN', text: 'Dien Bien' },\n    { value: 'VN-39', country_code: 'VN', text: 'Dong Nai' },\n    { value: 'VN-45', country_code: 'VN', text: 'Dong Thap' },\n    { value: 'VN-30', country_code: 'VN', text: 'Gia Lai' },\n    { value: 'VN-03', country_code: 'VN', text: 'Ha Giang' },\n    { value: 'VN-63', country_code: 'VN', text: 'Ha Nam' },\n    { value: 'VN-HN', country_code: 'VN', text: 'Ha Noi' },\n    { value: 'VN-23', country_code: 'VN', text: 'Ha Tinh' },\n    { value: 'VN-61', country_code: 'VN', text: 'Hai Duong' },\n    { value: 'VN-HP', country_code: 'VN', text: 'Hai Phong' },\n    { value: 'VN-SG', country_code: 'VN', text: 'Ho Chi Minh' },\n    { value: 'VN-14', country_code: 'VN', text: 'Hoa Binh' },\n    { value: 'VN-66', country_code: 'VN', text: 'Hung Yen' },\n    { value: 'VN-34', country_code: 'VN', text: 'Khanh Hoa' },\n    { value: 'VN-47', country_code: 'VN', text: 'Kien Giang' },\n    { value: 'VN-01', country_code: 'VN', text: 'Lai Chau' },\n    { value: 'VN-35', country_code: 'VN', text: 'Lam Dong' },\n    { value: 'VN-09', country_code: 'VN', text: 'Lang Son' },\n    { value: 'VN-02', country_code: 'VN', text: 'Lao Cai' },\n    { value: 'VN-41', country_code: 'VN', text: 'Long An' },\n    { value: 'VN-67', country_code: 'VN', text: 'Nam Dinh' },\n    { value: 'VN-22', country_code: 'VN', text: 'Nghe An' },\n    { value: 'VN-18', country_code: 'VN', text: 'Ninh Binh' },\n    { value: 'VN-36', country_code: 'VN', text: 'Ninh Thuan' },\n    { value: 'VN-68', country_code: 'VN', text: 'Phu Tho' },\n    { value: 'VN-32', country_code: 'VN', text: 'Phu Yen' },\n    { value: 'VN-24', country_code: 'VN', text: 'Quang Binh' },\n    { value: 'VN-27', country_code: 'VN', text: 'Quang Nam' },\n    { value: 'VN-29', country_code: 'VN', text: 'Quang Ngai' },\n    { value: 'VN-13', country_code: 'VN', text: 'Quang Ninh' },\n    { value: 'VN-25', country_code: 'VN', text: 'Quang Tri' },\n    { value: 'VN-52', country_code: 'VN', text: 'Soc Trang' },\n    { value: 'VN-05', country_code: 'VN', text: 'Son La' },\n    { value: 'VN-37', country_code: 'VN', text: 'Tay Ninh' },\n    { value: 'VN-20', country_code: 'VN', text: 'Thai Binh' },\n    { value: 'VN-69', country_code: 'VN', text: 'Thai Nguyen' },\n    { value: 'VN-21', country_code: 'VN', text: 'Thanh Hoa' },\n    { value: 'VN-26', country_code: 'VN', text: 'Thua Thien-Hue' },\n    { value: 'VN-46', country_code: 'VN', text: 'Tien Giang' },\n    { value: 'VN-51', country_code: 'VN', text: 'Tra Vinh' },\n    { value: 'VN-07', country_code: 'VN', text: 'Tuyen Quang' },\n    { value: 'VN-49', country_code: 'VN', text: 'Vinh Long' },\n    { value: 'VN-70', country_code: 'VN', text: 'Vinh Phuc' },\n    { value: 'VN-06', country_code: 'VN', text: 'Yen Bai' },\n    { value: 'VU-MAP', country_code: 'VU', text: 'Malampa' },\n    { value: 'VU-SAM', country_code: 'VU', text: 'Sanma' },\n    { value: 'VU-SEE', country_code: 'VU', text: 'Shefa' },\n    { value: 'VU-TAE', country_code: 'VU', text: 'Tafea' },\n    { value: 'VU-TOB', country_code: 'VU', text: 'Torba' },\n    { value: 'WF-UV', country_code: 'WF', text: 'Uvea' },\n    { value: 'WS-AA', country_code: 'WS', text: \"A'ana\" },\n    { value: 'WS-AT', country_code: 'WS', text: 'Atua' },\n    { value: 'WS-GI', country_code: 'WS', text: 'Gagaifomauga' },\n    { value: 'WS-PA', country_code: 'WS', text: 'Palauli' },\n    { value: 'WS-TU', country_code: 'WS', text: 'Tuamasaga' },\n    { value: 'YE-AD', country_code: 'YE', text: \"'Adan\" },\n    { value: 'YE-AM', country_code: 'YE', text: \"'Amran\" },\n    { value: 'YE-AB', country_code: 'YE', text: 'Abyan' },\n    { value: 'YE-DA', country_code: 'YE', text: \"Ad Dali'\" },\n    { value: 'YE-BA', country_code: 'YE', text: \"Al Bayda'\" },\n    { value: 'YE-HU', country_code: 'YE', text: 'Al Hudaydah' },\n    { value: 'YE-JA', country_code: 'YE', text: 'Al Jawf' },\n    { value: 'YE-MR', country_code: 'YE', text: 'Al Mahrah' },\n    { value: 'YE-MW', country_code: 'YE', text: 'Al Mahwit' },\n    { value: 'YE-SA', country_code: 'YE', text: \"Amanat al 'Asimah\" },\n    { value: 'YE-DH', country_code: 'YE', text: 'Dhamar' },\n    { value: 'YE-HD', country_code: 'YE', text: 'Hadramawt' },\n    { value: 'YE-HJ', country_code: 'YE', text: 'Hajjah' },\n    { value: 'YE-IB', country_code: 'YE', text: 'Ibb' },\n    { value: 'YE-LA', country_code: 'YE', text: 'Lahij' },\n    { value: 'YE-MA', country_code: 'YE', text: \"Ma'rib\" },\n    { value: 'YE-RA', country_code: 'YE', text: 'Raymah' },\n    { value: 'YE-SD', country_code: 'YE', text: \"Sa'dah\" },\n    { value: 'YE-SN', country_code: 'YE', text: \"San'a'\" },\n    { value: 'YE-SH', country_code: 'YE', text: 'Shabwah' },\n    { value: 'YE-TA', country_code: 'YE', text: \"Ta'izz\" },\n    { value: 'ZA-EC', country_code: 'ZA', text: 'Eastern Cape' },\n    { value: 'ZA-FS', country_code: 'ZA', text: 'Free State' },\n    { value: 'ZA-GT', country_code: 'ZA', text: 'Gauteng' },\n    { value: 'ZA-NL', country_code: 'ZA', text: 'Kwazulu-Natal' },\n    { value: 'ZA-LP', country_code: 'ZA', text: 'Limpopo' },\n    { value: 'ZA-MP', country_code: 'ZA', text: 'Mpumalanga' },\n    { value: 'ZA-NW', country_code: 'ZA', text: 'North-West' },\n    { value: 'ZA-NC', country_code: 'ZA', text: 'Northern Cape' },\n    { value: 'ZA-WC', country_code: 'ZA', text: 'Western Cape' },\n    { value: 'ZM-02', country_code: 'ZM', text: 'Central' },\n    { value: 'ZM-08', country_code: 'ZM', text: 'Copperbelt' },\n    { value: 'ZM-03', country_code: 'ZM', text: 'Eastern' },\n    { value: 'ZM-04', country_code: 'ZM', text: 'Luapula' },\n    { value: 'ZM-09', country_code: 'ZM', text: 'Lusaka' },\n    { value: 'ZM-06', country_code: 'ZM', text: 'North-Western' },\n    { value: 'ZM-05', country_code: 'ZM', text: 'Northern' },\n    { value: 'ZM-07', country_code: 'ZM', text: 'Southern' },\n    { value: 'ZM-01', country_code: 'ZM', text: 'Western' },\n    { value: 'ZW-BU', country_code: 'ZW', text: 'Bulawayo' },\n    { value: 'ZW-HA', country_code: 'ZW', text: 'Harare' },\n    { value: 'ZW-MA', country_code: 'ZW', text: 'Manicaland' },\n    { value: 'ZW-MC', country_code: 'ZW', text: 'Mashonaland Central' },\n    { value: 'ZW-ME', country_code: 'ZW', text: 'Mashonaland East' },\n    { value: 'ZW-MW', country_code: 'ZW', text: 'Mashonaland West' },\n    { value: 'ZW-MV', country_code: 'ZW', text: 'Masvingo' },\n    { value: 'ZW-MN', country_code: 'ZW', text: 'Matabeleland North' },\n    { value: 'ZW-MS', country_code: 'ZW', text: 'Matabeleland South' },\n    { value: 'ZW-MI', country_code: 'ZW', text: 'Midlands' }\n  ].filter((p) => p.country_code === country);\n\n  const childrenWithProps = React.Children.map(children, (child) =>\n    React.cloneElement(child, {\n      options: options.filter((o) => o.country_code === country),\n      ...props\n    })\n  );\n\n  return <div>{childrenWithProps}</div>;\n}\n\nProvinceOptions.propTypes = {\n  children: PropTypes.node.isRequired,\n  country: PropTypes.string.isRequired\n};\n\nexport { ProvinceOptions };\n"
  },
  {
    "path": "packages/evershop/src/components/common/locale/TimezoneOption.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\n\n\nfunction TimezoneOptions(props) {\n  const { timezones, children } = props;\n\n  const options = [\n    {\n      value: 'Australia/Darwin',\n      text: 'AUS Central Standard Time (Australia/Darwin)'\n    },\n    {\n      value: 'Australia/Melbourne',\n      text: 'AUS Eastern Standard Time (Australia/Melbourne)'\n    },\n    {\n      value: 'Australia/Sydney',\n      text: 'AUS Eastern Standard Time (Australia/Sydney)'\n    },\n    { value: 'Asia/Kabul', text: 'Afghanistan Standard Time (Asia/Kabul)' },\n    {\n      value: 'America/Anchorage',\n      text: 'Alaskan Standard Time (America/Anchorage)'\n    },\n    { value: 'America/Juneau', text: 'Alaskan Standard Time (America/Juneau)' },\n    { value: 'America/Nome', text: 'Alaskan Standard Time (America/Nome)' },\n    { value: 'America/Sitka', text: 'Alaskan Standard Time (America/Sitka)' },\n    {\n      value: 'America/Yakutat',\n      text: 'Alaskan Standard Time (America/Yakutat)'\n    },\n    { value: 'Asia/Aden', text: 'Arab Standard Time (Asia/Aden)' },\n    { value: 'Asia/Bahrain', text: 'Arab Standard Time (Asia/Bahrain)' },\n    { value: 'Asia/Kuwait', text: 'Arab Standard Time (Asia/Kuwait)' },\n    { value: 'Asia/Qatar', text: 'Arab Standard Time (Asia/Qatar)' },\n    { value: 'Asia/Riyadh', text: 'Arab Standard Time (Asia/Riyadh)' },\n    { value: 'Asia/Dubai', text: 'Arabian Standard Time (Asia/Dubai)' },\n    { value: 'Asia/Muscat', text: 'Arabian Standard Time (Asia/Muscat)' },\n    { value: 'Etc/GMT-4', text: 'Arabian Standard Time (Etc/GMT-4)' },\n    { value: 'Asia/Baghdad', text: 'Arabic Standard Time (Asia/Baghdad)' },\n    {\n      value: 'America/Argentina/La_Rioja',\n      text: 'Argentina Standard Time (America/Argentina/La_Rioja)'\n    },\n    {\n      value: 'America/Argentina/Rio_Gallegos',\n      text: 'Argentina Standard Time (America/Argentina/Rio_Gallegos)'\n    },\n    {\n      value: 'America/Argentina/Salta',\n      text: 'Argentina Standard Time (America/Argentina/Salta)'\n    },\n    {\n      value: 'America/Argentina/San_Juan',\n      text: 'Argentina Standard Time (America/Argentina/San_Juan)'\n    },\n    {\n      value: 'America/Argentina/San_Luis',\n      text: 'Argentina Standard Time (America/Argentina/San_Luis)'\n    },\n    {\n      value: 'America/Argentina/Tucuman',\n      text: 'Argentina Standard Time (America/Argentina/Tucuman)'\n    },\n    {\n      value: 'America/Argentina/Ushuaia',\n      text: 'Argentina Standard Time (America/Argentina/Ushuaia)'\n    },\n    {\n      value: 'America/Buenos_Aires',\n      text: 'Argentina Standard Time (America/Buenos_Aires)'\n    },\n    {\n      value: 'America/Catamarca',\n      text: 'Argentina Standard Time (America/Catamarca)'\n    },\n    {\n      value: 'America/Cordoba',\n      text: 'Argentina Standard Time (America/Cordoba)'\n    },\n    { value: 'America/Jujuy', text: 'Argentina Standard Time (America/Jujuy)' },\n    {\n      value: 'America/Mendoza',\n      text: 'Argentina Standard Time (America/Mendoza)'\n    },\n    {\n      value: 'America/Glace_Bay',\n      text: 'Atlantic Standard Time (America/Glace_Bay)'\n    },\n    {\n      value: 'America/Goose_Bay',\n      text: 'Atlantic Standard Time (America/Goose_Bay)'\n    },\n    {\n      value: 'America/Halifax',\n      text: 'Atlantic Standard Time (America/Halifax)'\n    },\n    {\n      value: 'America/Moncton',\n      text: 'Atlantic Standard Time (America/Moncton)'\n    },\n    { value: 'America/Thule', text: 'Atlantic Standard Time (America/Thule)' },\n    {\n      value: 'Atlantic/Bermuda',\n      text: 'Atlantic Standard Time (Atlantic/Bermuda)'\n    },\n    { value: 'Asia/Baku', text: 'Azerbaijan Standard Time (Asia/Baku)' },\n    {\n      value: 'America/Scoresbysund',\n      text: 'Azores Standard Time (America/Scoresbysund)'\n    },\n    {\n      value: 'Atlantic/Azores',\n      text: 'Azores Standard Time (Atlantic/Azores)'\n    },\n    { value: 'America/Bahia', text: 'Bahia Standard Time (America/Bahia)' },\n    { value: 'Asia/Dhaka', text: 'Bangladesh Standard Time (Asia/Dhaka)' },\n    { value: 'Asia/Thimphu', text: 'Bangladesh Standard Time (Asia/Thimphu)' },\n    {\n      value: 'America/Regina',\n      text: 'Canada Central Standard Time (America/Regina)'\n    },\n    {\n      value: 'America/Swift_Current',\n      text: 'Canada Central Standard Time (America/Swift_Current)'\n    },\n    {\n      value: 'Atlantic/Cape_Verde',\n      text: 'Cape Verde Standard Time (Atlantic/Cape_Verde)'\n    },\n    { value: 'Etc/GMT+1', text: 'Cape Verde Standard Time (Etc/GMT+1)' },\n    { value: 'Asia/Yerevan', text: 'Caucasus Standard Time (Asia/Yerevan)' },\n    {\n      value: 'Australia/Adelaide',\n      text: 'Cen. Australia Standard Time (Australia/Adelaide)'\n    },\n    {\n      value: 'Australia/Broken_Hill',\n      text: 'Cen. Australia Standard Time (Australia/Broken_Hill)'\n    },\n    {\n      value: 'America/Belize',\n      text: 'Central America Standard Time (America/Belize)'\n    },\n    {\n      value: 'America/Costa_Rica',\n      text: 'Central America Standard Time (America/Costa_Rica)'\n    },\n    {\n      value: 'America/El_Salvador',\n      text: 'Central America Standard Time (America/El_Salvador)'\n    },\n    {\n      value: 'America/Guatemala',\n      text: 'Central America Standard Time (America/Guatemala)'\n    },\n    {\n      value: 'America/Managua',\n      text: 'Central America Standard Time (America/Managua)'\n    },\n    {\n      value: 'America/Tegucigalpa',\n      text: 'Central America Standard Time (America/Tegucigalpa)'\n    },\n    { value: 'Etc/GMT+6', text: 'Central America Standard Time (Etc/GMT+6)' },\n    {\n      value: 'Pacific/Galapagos',\n      text: 'Central America Standard Time (Pacific/Galapagos)'\n    },\n    {\n      value: 'Antarctica/Vostok',\n      text: 'Central Asia Standard Time (Antarctica/Vostok)'\n    },\n    { value: 'Asia/Almaty', text: 'Central Asia Standard Time (Asia/Almaty)' },\n    {\n      value: 'Asia/Bishkek',\n      text: 'Central Asia Standard Time (Asia/Bishkek)'\n    },\n    {\n      value: 'Asia/Qyzylorda',\n      text: 'Central Asia Standard Time (Asia/Qyzylorda)'\n    },\n    { value: 'Etc/GMT-6', text: 'Central Asia Standard Time (Etc/GMT-6)' },\n    {\n      value: 'Indian/Chagos',\n      text: 'Central Asia Standard Time (Indian/Chagos)'\n    },\n    {\n      value: 'America/Campo_Grande',\n      text: 'Central Brazilian Standard Time (America/Campo_Grande)'\n    },\n    {\n      value: 'America/Cuiaba',\n      text: 'Central Brazilian Standard Time (America/Cuiaba)'\n    },\n    {\n      value: 'Europe/Belgrade',\n      text: 'Central Europe Standard Time (Europe/Belgrade)'\n    },\n    {\n      value: 'Europe/Bratislava',\n      text: 'Central Europe Standard Time (Europe/Bratislava)'\n    },\n    {\n      value: 'Europe/Budapest',\n      text: 'Central Europe Standard Time (Europe/Budapest)'\n    },\n    {\n      value: 'Europe/Ljubljana',\n      text: 'Central Europe Standard Time (Europe/Ljubljana)'\n    },\n    {\n      value: 'Europe/Podgorica',\n      text: 'Central Europe Standard Time (Europe/Podgorica)'\n    },\n    {\n      value: 'Europe/Prague',\n      text: 'Central Europe Standard Time (Europe/Prague)'\n    },\n    {\n      value: 'Europe/Tirane',\n      text: 'Central Europe Standard Time (Europe/Tirane)'\n    },\n    {\n      value: 'Europe/Sarajevo',\n      text: 'Central European Standard Time (Europe/Sarajevo)'\n    },\n    {\n      value: 'Europe/Skopje',\n      text: 'Central European Standard Time (Europe/Skopje)'\n    },\n    {\n      value: 'Europe/Warsaw',\n      text: 'Central European Standard Time (Europe/Warsaw)'\n    },\n    {\n      value: 'Europe/Zagreb',\n      text: 'Central European Standard Time (Europe/Zagreb)'\n    },\n    {\n      value: 'Antarctica/Macquarie',\n      text: 'Central Pacific Standard Time (Antarctica/Macquarie)'\n    },\n    { value: 'Etc/GMT-11', text: 'Central Pacific Standard Time (Etc/GMT-11)' },\n    {\n      value: 'Pacific/Efate',\n      text: 'Central Pacific Standard Time (Pacific/Efate)'\n    },\n    {\n      value: 'Pacific/Guadalcanal',\n      text: 'Central Pacific Standard Time (Pacific/Guadalcanal)'\n    },\n    {\n      value: 'Pacific/Kosrae',\n      text: 'Central Pacific Standard Time (Pacific/Kosrae)'\n    },\n    {\n      value: 'Pacific/Noumea',\n      text: 'Central Pacific Standard Time (Pacific/Noumea)'\n    },\n    {\n      value: 'Pacific/Ponape',\n      text: 'Central Pacific Standard Time (Pacific/Ponape)'\n    },\n    {\n      value: 'America/Chicago',\n      text: 'Central Standard Time (America/Chicago)'\n    },\n    {\n      value: 'America/Indiana/Knox',\n      text: 'Central Standard Time (America/Indiana/Knox)'\n    },\n    {\n      value: 'America/Indiana/Tell_City',\n      text: 'Central Standard Time (America/Indiana/Tell_City)'\n    },\n    {\n      value: 'America/Matamoros',\n      text: 'Central Standard Time (America/Matamoros)'\n    },\n    {\n      value: 'America/Menominee',\n      text: 'Central Standard Time (America/Menominee)'\n    },\n    {\n      value: 'America/North_Dakota/Beulah',\n      text: 'Central Standard Time (America/North_Dakota/Beulah)'\n    },\n    {\n      value: 'America/North_Dakota/Center',\n      text: 'Central Standard Time (America/North_Dakota/Center)'\n    },\n    {\n      value: 'America/North_Dakota/New_Salem',\n      text: 'Central Standard Time (America/North_Dakota/New_Salem)'\n    },\n    {\n      value: 'America/Rainy_River',\n      text: 'Central Standard Time (America/Rainy_River)'\n    },\n    {\n      value: 'America/Rankin_Inlet',\n      text: 'Central Standard Time (America/Rankin_Inlet)'\n    },\n    {\n      value: 'America/Resolute',\n      text: 'Central Standard Time (America/Resolute)'\n    },\n    {\n      value: 'America/Winnipeg',\n      text: 'Central Standard Time (America/Winnipeg)'\n    },\n    { value: 'CST6CDT', text: 'Central Standard Time (CST6CDT)' },\n    {\n      value: 'America/Bahia_Banderas',\n      text: 'Central Standard Time (Mexico) (America/Bahia_Banderas)'\n    },\n    {\n      value: 'America/Cancun',\n      text: 'Central Standard Time (Mexico) (America/Cancun)'\n    },\n    {\n      value: 'America/Merida',\n      text: 'Central Standard Time (Mexico) (America/Merida)'\n    },\n    {\n      value: 'America/Mexico_City',\n      text: 'Central Standard Time (Mexico) (America/Mexico_City)'\n    },\n    {\n      value: 'America/Monterrey',\n      text: 'Central Standard Time (Mexico) (America/Monterrey)'\n    },\n    { value: 'Asia/Chongqing', text: 'China Standard Time (Asia/Chongqing)' },\n    { value: 'Asia/Harbin', text: 'China Standard Time (Asia/Harbin)' },\n    { value: 'Asia/Hong_Kong', text: 'China Standard Time (Asia/Hong_Kong)' },\n    { value: 'Asia/Kashgar', text: 'China Standard Time (Asia/Kashgar)' },\n    { value: 'Asia/Macau', text: 'China Standard Time (Asia/Macau)' },\n    { value: 'Asia/Shanghai', text: 'China Standard Time (Asia/Shanghai)' },\n    { value: 'Asia/Urumqi', text: 'China Standard Time (Asia/Urumqi)' },\n    { value: 'Etc/GMT+12', text: 'Dateline Standard Time (Etc/GMT+12)' },\n    {\n      value: 'Africa/Addis_Ababa',\n      text: 'E. Africa Standard Time (Africa/Addis_Ababa)'\n    },\n    { value: 'Africa/Asmera', text: 'E. Africa Standard Time (Africa/Asmera)' },\n    {\n      value: 'Africa/Dar_es_Salaam',\n      text: 'E. Africa Standard Time (Africa/Dar_es_Salaam)'\n    },\n    {\n      value: 'Africa/Djibouti',\n      text: 'E. Africa Standard Time (Africa/Djibouti)'\n    },\n    { value: 'Africa/Juba', text: 'E. Africa Standard Time (Africa/Juba)' },\n    {\n      value: 'Africa/Kampala',\n      text: 'E. Africa Standard Time (Africa/Kampala)'\n    },\n    {\n      value: 'Africa/Khartoum',\n      text: 'E. Africa Standard Time (Africa/Khartoum)'\n    },\n    {\n      value: 'Africa/Mogadishu',\n      text: 'E. Africa Standard Time (Africa/Mogadishu)'\n    },\n    {\n      value: 'Africa/Nairobi',\n      text: 'E. Africa Standard Time (Africa/Nairobi)'\n    },\n    {\n      value: 'Antarctica/Syowa',\n      text: 'E. Africa Standard Time (Antarctica/Syowa)'\n    },\n    { value: 'Etc/GMT-3', text: 'E. Africa Standard Time (Etc/GMT-3)' },\n    {\n      value: 'Indian/Antananarivo',\n      text: 'E. Africa Standard Time (Indian/Antananarivo)'\n    },\n    { value: 'Indian/Comoro', text: 'E. Africa Standard Time (Indian/Comoro)' },\n    {\n      value: 'Indian/Mayotte',\n      text: 'E. Africa Standard Time (Indian/Mayotte)'\n    },\n    {\n      value: 'Australia/Brisbane',\n      text: 'E. Australia Standard Time (Australia/Brisbane)'\n    },\n    {\n      value: 'Australia/Lindeman',\n      text: 'E. Australia Standard Time (Australia/Lindeman)'\n    },\n    {\n      value: 'America/Sao_Paulo',\n      text: 'E. South America Standard Time (America/Sao_Paulo)'\n    },\n    {\n      value: 'America/Detroit',\n      text: 'Eastern Standard Time (America/Detroit)'\n    },\n    {\n      value: 'America/Grand_Turk',\n      text: 'Eastern Standard Time (America/Grand_Turk)'\n    },\n    { value: 'America/Havana', text: 'Eastern Standard Time (America/Havana)' },\n    {\n      value: 'America/Indiana/Petersburg',\n      text: 'Eastern Standard Time (America/Indiana/Petersburg)'\n    },\n    {\n      value: 'America/Indiana/Vincennes',\n      text: 'Eastern Standard Time (America/Indiana/Vincennes)'\n    },\n    {\n      value: 'America/Indiana/Winamac',\n      text: 'Eastern Standard Time (America/Indiana/Winamac)'\n    },\n    {\n      value: 'America/Iqaluit',\n      text: 'Eastern Standard Time (America/Iqaluit)'\n    },\n    {\n      value: 'America/Kentucky/Monticello',\n      text: 'Eastern Standard Time (America/Kentucky/Monticello)'\n    },\n    {\n      value: 'America/Louisville',\n      text: 'Eastern Standard Time (America/Louisville)'\n    },\n    {\n      value: 'America/Montreal',\n      text: 'Eastern Standard Time (America/Montreal)'\n    },\n    { value: 'America/Nassau', text: 'Eastern Standard Time (America/Nassau)' },\n    {\n      value: 'America/New_York',\n      text: 'Eastern Standard Time (America/New_York)'\n    },\n    {\n      value: 'America/Nipigon',\n      text: 'Eastern Standard Time (America/Nipigon)'\n    },\n    {\n      value: 'America/Pangnirtung',\n      text: 'Eastern Standard Time (America/Pangnirtung)'\n    },\n    {\n      value: 'America/Port-au-Prince',\n      text: 'Eastern Standard Time (America/Port-au-Prince)'\n    },\n    {\n      value: 'America/Thunder_Bay',\n      text: 'Eastern Standard Time (America/Thunder_Bay)'\n    },\n    {\n      value: 'America/Toronto',\n      text: 'Eastern Standard Time (America/Toronto)'\n    },\n    { value: 'EST5EDT', text: 'Eastern Standard Time (EST5EDT)' },\n    { value: 'Africa/Cairo', text: 'Egypt Standard Time (Africa/Cairo)' },\n    {\n      value: 'Asia/Yekaterinburg',\n      text: 'Ekaterinburg Standard Time (Asia/Yekaterinburg)'\n    },\n    { value: 'Europe/Helsinki', text: 'FLE Standard Time (Europe/Helsinki)' },\n    { value: 'Europe/Kiev', text: 'FLE Standard Time (Europe/Kiev)' },\n    { value: 'Europe/Mariehamn', text: 'FLE Standard Time (Europe/Mariehamn)' },\n    { value: 'Europe/Riga', text: 'FLE Standard Time (Europe/Riga)' },\n    {\n      value: 'Europe/Simferopol',\n      text: 'FLE Standard Time (Europe/Simferopol)'\n    },\n    { value: 'Europe/Sofia', text: 'FLE Standard Time (Europe/Sofia)' },\n    { value: 'Europe/Tallinn', text: 'FLE Standard Time (Europe/Tallinn)' },\n    { value: 'Europe/Uzhgorod', text: 'FLE Standard Time (Europe/Uzhgorod)' },\n    { value: 'Europe/Vilnius', text: 'FLE Standard Time (Europe/Vilnius)' },\n    {\n      value: 'Europe/Zaporozhye',\n      text: 'FLE Standard Time (Europe/Zaporozhye)'\n    },\n    { value: 'Pacific/Fiji', text: 'Fiji Standard Time (Pacific/Fiji)' },\n    { value: 'Atlantic/Canary', text: 'GMT Standard Time (Atlantic/Canary)' },\n    { value: 'Atlantic/Faeroe', text: 'GMT Standard Time (Atlantic/Faeroe)' },\n    { value: 'Atlantic/Madeira', text: 'GMT Standard Time (Atlantic/Madeira)' },\n    { value: 'Europe/Dublin', text: 'GMT Standard Time (Europe/Dublin)' },\n    { value: 'Europe/Guernsey', text: 'GMT Standard Time (Europe/Guernsey)' },\n    {\n      value: 'Europe/Isle_of_Man',\n      text: 'GMT Standard Time (Europe/Isle_of_Man)'\n    },\n    { value: 'Europe/Jersey', text: 'GMT Standard Time (Europe/Jersey)' },\n    { value: 'Europe/Lisbon', text: 'GMT Standard Time (Europe/Lisbon)' },\n    { value: 'Europe/London', text: 'GMT Standard Time (Europe/London)' },\n    { value: 'Asia/Nicosia', text: 'GTB Standard Time (Asia/Nicosia)' },\n    { value: 'Europe/Athens', text: 'GTB Standard Time (Europe/Athens)' },\n    { value: 'Europe/Bucharest', text: 'GTB Standard Time (Europe/Bucharest)' },\n    { value: 'Europe/Chisinau', text: 'GTB Standard Time (Europe/Chisinau)' },\n    { value: 'Asia/Tbilisi', text: 'Georgian Standard Time (Asia/Tbilisi)' },\n    {\n      value: 'America/Godthab',\n      text: 'Greenland Standard Time (America/Godthab)'\n    },\n    {\n      value: 'Africa/Abidjan',\n      text: 'Greenwich Standard Time (Africa/Abidjan)'\n    },\n    { value: 'Africa/Accra', text: 'Greenwich Standard Time (Africa/Accra)' },\n    { value: 'Africa/Bamako', text: 'Greenwich Standard Time (Africa/Bamako)' },\n    { value: 'Africa/Banjul', text: 'Greenwich Standard Time (Africa/Banjul)' },\n    { value: 'Africa/Bissau', text: 'Greenwich Standard Time (Africa/Bissau)' },\n    {\n      value: 'Africa/Conakry',\n      text: 'Greenwich Standard Time (Africa/Conakry)'\n    },\n    { value: 'Africa/Dakar', text: 'Greenwich Standard Time (Africa/Dakar)' },\n    {\n      value: 'Africa/Freetown',\n      text: 'Greenwich Standard Time (Africa/Freetown)'\n    },\n    { value: 'Africa/Lome', text: 'Greenwich Standard Time (Africa/Lome)' },\n    {\n      value: 'Africa/Monrovia',\n      text: 'Greenwich Standard Time (Africa/Monrovia)'\n    },\n    {\n      value: 'Africa/Nouakchott',\n      text: 'Greenwich Standard Time (Africa/Nouakchott)'\n    },\n    {\n      value: 'Africa/Ouagadougou',\n      text: 'Greenwich Standard Time (Africa/Ouagadougou)'\n    },\n    {\n      value: 'Africa/Sao_Tome',\n      text: 'Greenwich Standard Time (Africa/Sao_Tome)'\n    },\n    {\n      value: 'Atlantic/Reykjavik',\n      text: 'Greenwich Standard Time (Atlantic/Reykjavik)'\n    },\n    {\n      value: 'Atlantic/St_Helena',\n      text: 'Greenwich Standard Time (Atlantic/St_Helena)'\n    },\n    { value: 'Etc/GMT+10', text: 'Hawaiian Standard Time (Etc/GMT+10)' },\n    {\n      value: 'Pacific/Honolulu',\n      text: 'Hawaiian Standard Time (Pacific/Honolulu)'\n    },\n    {\n      value: 'Pacific/Johnston',\n      text: 'Hawaiian Standard Time (Pacific/Johnston)'\n    },\n    {\n      value: 'Pacific/Rarotonga',\n      text: 'Hawaiian Standard Time (Pacific/Rarotonga)'\n    },\n    {\n      value: 'Pacific/Tahiti',\n      text: 'Hawaiian Standard Time (Pacific/Tahiti)'\n    },\n    { value: 'Asia/Calcutta', text: 'India Standard Time (Asia/Calcutta)' },\n    { value: 'Asia/Tehran', text: 'Iran Standard Time (Asia/Tehran)' },\n    { value: 'Asia/Jerusalem', text: 'Israel Standard Time (Asia/Jerusalem)' },\n    { value: 'Asia/Amman', text: 'Jordan Standard Time (Asia/Amman)' },\n    {\n      value: 'Europe/Kaliningrad',\n      text: 'Kaliningrad Standard Time (Europe/Kaliningrad)'\n    },\n    { value: 'Europe/Minsk', text: 'Kaliningrad Standard Time (Europe/Minsk)' },\n    { value: 'Asia/Pyongyang', text: 'Korea Standard Time (Asia/Pyongyang)' },\n    { value: 'Asia/Seoul', text: 'Korea Standard Time (Asia/Seoul)' },\n    { value: 'Africa/Tripoli', text: 'Libya Standard Time (Africa/Tripoli)' },\n    { value: 'Asia/Anadyr', text: 'Magadan Standard Time (Asia/Anadyr)' },\n    { value: 'Asia/Kamchatka', text: 'Magadan Standard Time (Asia/Kamchatka)' },\n    { value: 'Asia/Magadan', text: 'Magadan Standard Time (Asia/Magadan)' },\n    { value: 'Indian/Mahe', text: 'Mauritius Standard Time (Indian/Mahe)' },\n    {\n      value: 'Indian/Mauritius',\n      text: 'Mauritius Standard Time (Indian/Mauritius)'\n    },\n    {\n      value: 'Indian/Reunion',\n      text: 'Mauritius Standard Time (Indian/Reunion)'\n    },\n    { value: 'Asia/Beirut', text: 'Middle East Standard Time (Asia/Beirut)' },\n    {\n      value: 'America/Montevideo',\n      text: 'Montevideo Standard Time (America/Montevideo)'\n    },\n    {\n      value: 'Africa/Casablanca',\n      text: 'Morocco Standard Time (Africa/Casablanca)'\n    },\n    {\n      value: 'Africa/El_Aaiun',\n      text: 'Morocco Standard Time (Africa/El_Aaiun)'\n    },\n    { value: 'America/Boise', text: 'Mountain Standard Time (America/Boise)' },\n    {\n      value: 'America/Cambridge_Bay',\n      text: 'Mountain Standard Time (America/Cambridge_Bay)'\n    },\n    {\n      value: 'America/Denver',\n      text: 'Mountain Standard Time (America/Denver)'\n    },\n    {\n      value: 'America/Edmonton',\n      text: 'Mountain Standard Time (America/Edmonton)'\n    },\n    {\n      value: 'America/Inuvik',\n      text: 'Mountain Standard Time (America/Inuvik)'\n    },\n    {\n      value: 'America/Ojinaga',\n      text: 'Mountain Standard Time (America/Ojinaga)'\n    },\n    {\n      value: 'America/Shiprock',\n      text: 'Mountain Standard Time (America/Shiprock)'\n    },\n    {\n      value: 'America/Yellowknife',\n      text: 'Mountain Standard Time (America/Yellowknife)'\n    },\n    { value: 'MST7MDT', text: 'Mountain Standard Time (MST7MDT)' },\n    {\n      value: 'America/Chihuahua',\n      text: 'Mountain Standard Time (Mexico) (America/Chihuahua)'\n    },\n    {\n      value: 'America/Mazatlan',\n      text: 'Mountain Standard Time (Mexico) (America/Mazatlan)'\n    },\n    { value: 'Asia/Rangoon', text: 'Myanmar Standard Time (Asia/Rangoon)' },\n    { value: 'Indian/Cocos', text: 'Myanmar Standard Time (Indian/Cocos)' },\n    {\n      value: 'Asia/Novokuznetsk',\n      text: 'N. Central Asia Standard Time (Asia/Novokuznetsk)'\n    },\n    {\n      value: 'Asia/Novosibirsk',\n      text: 'N. Central Asia Standard Time (Asia/Novosibirsk)'\n    },\n    { value: 'Asia/Omsk', text: 'N. Central Asia Standard Time (Asia/Omsk)' },\n    {\n      value: 'Africa/Windhoek',\n      text: 'Namibia Standard Time (Africa/Windhoek)'\n    },\n    { value: 'Asia/Katmandu', text: 'Nepal Standard Time (Asia/Katmandu)' },\n    {\n      value: 'Antarctica/McMurdo',\n      text: 'New Zealand Standard Time (Antarctica/McMurdo)'\n    },\n    {\n      value: 'Antarctica/South_Pole',\n      text: 'New Zealand Standard Time (Antarctica/South_Pole)'\n    },\n    {\n      value: 'Pacific/Auckland',\n      text: 'New Zealand Standard Time (Pacific/Auckland)'\n    },\n    {\n      value: 'America/St_Johns',\n      text: 'Newfoundland Standard Time (America/St_Johns)'\n    },\n    {\n      value: 'Asia/Irkutsk',\n      text: 'North Asia East Standard Time (Asia/Irkutsk)'\n    },\n    {\n      value: 'Asia/Krasnoyarsk',\n      text: 'North Asia Standard Time (Asia/Krasnoyarsk)'\n    },\n    {\n      value: 'America/Santiago',\n      text: 'Pacific SA Standard Time (America/Santiago)'\n    },\n    {\n      value: 'Antarctica/Palmer',\n      text: 'Pacific SA Standard Time (Antarctica/Palmer)'\n    },\n    { value: 'America/Dawson', text: 'Pacific Standard Time (America/Dawson)' },\n    {\n      value: 'America/Los_Angeles',\n      text: 'Pacific Standard Time (America/Los_Angeles)'\n    },\n    {\n      value: 'America/Tijuana',\n      text: 'Pacific Standard Time (America/Tijuana)'\n    },\n    {\n      value: 'America/Vancouver',\n      text: 'Pacific Standard Time (America/Vancouver)'\n    },\n    {\n      value: 'America/Whitehorse',\n      text: 'Pacific Standard Time (America/Whitehorse)'\n    },\n    {\n      value: 'America/Santa_Isabel',\n      text: 'Pacific Standard Time (Mexico) (America/Santa_Isabel)'\n    },\n    { value: 'PST8PDT', text: 'Pacific Standard Time (PST8PDT)' },\n    { value: 'Asia/Karachi', text: 'Pakistan Standard Time (Asia/Karachi)' },\n    {\n      value: 'America/Asuncion',\n      text: 'Paraguay Standard Time (America/Asuncion)'\n    },\n    { value: 'Africa/Ceuta', text: 'Romance Standard Time (Africa/Ceuta)' },\n    {\n      value: 'Europe/Brussels',\n      text: 'Romance Standard Time (Europe/Brussels)'\n    },\n    {\n      value: 'Europe/Copenhagen',\n      text: 'Romance Standard Time (Europe/Copenhagen)'\n    },\n    { value: 'Europe/Madrid', text: 'Romance Standard Time (Europe/Madrid)' },\n    { value: 'Europe/Paris', text: 'Romance Standard Time (Europe/Paris)' },\n    { value: 'Europe/Moscow', text: 'Russian Standard Time (Europe/Moscow)' },\n    { value: 'Europe/Samara', text: 'Russian Standard Time (Europe/Samara)' },\n    {\n      value: 'Europe/Volgograd',\n      text: 'Russian Standard Time (Europe/Volgograd)'\n    },\n    {\n      value: 'America/Araguaina',\n      text: 'SA Eastern Standard Time (America/Araguaina)'\n    },\n    {\n      value: 'America/Belem',\n      text: 'SA Eastern Standard Time (America/Belem)'\n    },\n    {\n      value: 'America/Cayenne',\n      text: 'SA Eastern Standard Time (America/Cayenne)'\n    },\n    {\n      value: 'America/Fortaleza',\n      text: 'SA Eastern Standard Time (America/Fortaleza)'\n    },\n    {\n      value: 'America/Maceio',\n      text: 'SA Eastern Standard Time (America/Maceio)'\n    },\n    {\n      value: 'America/Paramaribo',\n      text: 'SA Eastern Standard Time (America/Paramaribo)'\n    },\n    {\n      value: 'America/Recife',\n      text: 'SA Eastern Standard Time (America/Recife)'\n    },\n    {\n      value: 'America/Santarem',\n      text: 'SA Eastern Standard Time (America/Santarem)'\n    },\n    {\n      value: 'Antarctica/Rothera',\n      text: 'SA Eastern Standard Time (Antarctica/Rothera)'\n    },\n    {\n      value: 'Atlantic/Stanley',\n      text: 'SA Eastern Standard Time (Atlantic/Stanley)'\n    },\n    { value: 'Etc/GMT+3', text: 'SA Eastern Standard Time (Etc/GMT+3)' },\n    {\n      value: 'America/Bogota',\n      text: 'SA Pacific Standard Time (America/Bogota)'\n    },\n    {\n      value: 'America/Cayman',\n      text: 'SA Pacific Standard Time (America/Cayman)'\n    },\n    {\n      value: 'America/Coral_Harbour',\n      text: 'SA Pacific Standard Time (America/Coral_Harbour)'\n    },\n    {\n      value: 'America/Eirunepe',\n      text: 'SA Pacific Standard Time (America/Eirunepe)'\n    },\n    {\n      value: 'America/Guayaquil',\n      text: 'SA Pacific Standard Time (America/Guayaquil)'\n    },\n    {\n      value: 'America/Jamaica',\n      text: 'SA Pacific Standard Time (America/Jamaica)'\n    },\n    { value: 'America/Lima', text: 'SA Pacific Standard Time (America/Lima)' },\n    {\n      value: 'America/Panama',\n      text: 'SA Pacific Standard Time (America/Panama)'\n    },\n    {\n      value: 'America/Rio_Branco',\n      text: 'SA Pacific Standard Time (America/Rio_Branco)'\n    },\n    { value: 'Etc/GMT+5', text: 'SA Pacific Standard Time (Etc/GMT+5)' },\n    {\n      value: 'America/Anguilla',\n      text: 'SA Western Standard Time (America/Anguilla)'\n    },\n    {\n      value: 'America/Antigua',\n      text: 'SA Western Standard Time (America/Antigua)'\n    },\n    {\n      value: 'America/Aruba',\n      text: 'SA Western Standard Time (America/Aruba)'\n    },\n    {\n      value: 'America/Barbados',\n      text: 'SA Western Standard Time (America/Barbados)'\n    },\n    {\n      value: 'America/Blanc-Sablon',\n      text: 'SA Western Standard Time (America/Blanc-Sablon)'\n    },\n    {\n      value: 'America/Boa_Vista',\n      text: 'SA Western Standard Time (America/Boa_Vista)'\n    },\n    {\n      value: 'America/Curacao',\n      text: 'SA Western Standard Time (America/Curacao)'\n    },\n    {\n      value: 'America/Dominica',\n      text: 'SA Western Standard Time (America/Dominica)'\n    },\n    {\n      value: 'America/Grenada',\n      text: 'SA Western Standard Time (America/Grenada)'\n    },\n    {\n      value: 'America/Guadeloupe',\n      text: 'SA Western Standard Time (America/Guadeloupe)'\n    },\n    {\n      value: 'America/Guyana',\n      text: 'SA Western Standard Time (America/Guyana)'\n    },\n    {\n      value: 'America/Kralendijk',\n      text: 'SA Western Standard Time (America/Kralendijk)'\n    },\n    {\n      value: 'America/La_Paz',\n      text: 'SA Western Standard Time (America/La_Paz)'\n    },\n    {\n      value: 'America/Lower_Princes',\n      text: 'SA Western Standard Time (America/Lower_Princes)'\n    },\n    {\n      value: 'America/Manaus',\n      text: 'SA Western Standard Time (America/Manaus)'\n    },\n    {\n      value: 'America/Marigot',\n      text: 'SA Western Standard Time (America/Marigot)'\n    },\n    {\n      value: 'America/Martinique',\n      text: 'SA Western Standard Time (America/Martinique)'\n    },\n    {\n      value: 'America/Montserrat',\n      text: 'SA Western Standard Time (America/Montserrat)'\n    },\n    {\n      value: 'America/Port_of_Spain',\n      text: 'SA Western Standard Time (America/Port_of_Spain)'\n    },\n    {\n      value: 'America/Porto_Velho',\n      text: 'SA Western Standard Time (America/Porto_Velho)'\n    },\n    {\n      value: 'America/Puerto_Rico',\n      text: 'SA Western Standard Time (America/Puerto_Rico)'\n    },\n    {\n      value: 'America/Santo_Domingo',\n      text: 'SA Western Standard Time (America/Santo_Domingo)'\n    },\n    {\n      value: 'America/St_Barthelemy',\n      text: 'SA Western Standard Time (America/St_Barthelemy)'\n    },\n    {\n      value: 'America/St_Kitts',\n      text: 'SA Western Standard Time (America/St_Kitts)'\n    },\n    {\n      value: 'America/St_Lucia',\n      text: 'SA Western Standard Time (America/St_Lucia)'\n    },\n    {\n      value: 'America/St_Thomas',\n      text: 'SA Western Standard Time (America/St_Thomas)'\n    },\n    {\n      value: 'America/St_Vincent',\n      text: 'SA Western Standard Time (America/St_Vincent)'\n    },\n    {\n      value: 'America/Tortola',\n      text: 'SA Western Standard Time (America/Tortola)'\n    },\n    { value: 'Etc/GMT+4', text: 'SA Western Standard Time (Etc/GMT+4)' },\n    {\n      value: 'Antarctica/Davis',\n      text: 'SE Asia Standard Time (Antarctica/Davis)'\n    },\n    { value: 'Asia/Bangkok', text: 'SE Asia Standard Time (Asia/Bangkok)' },\n    { value: 'Asia/Hovd', text: 'SE Asia Standard Time (Asia/Hovd)' },\n    { value: 'Asia/Jakarta', text: 'SE Asia Standard Time (Asia/Jakarta)' },\n    {\n      value: 'Asia/Phnom_Penh',\n      text: 'SE Asia Standard Time (Asia/Phnom_Penh)'\n    },\n    { value: 'Asia/Pontianak', text: 'SE Asia Standard Time (Asia/Pontianak)' },\n    { value: 'Asia/Saigon', text: 'SE Asia Standard Time (Asia/Saigon)' },\n    { value: 'Asia/Vientiane', text: 'SE Asia Standard Time (Asia/Vientiane)' },\n    { value: 'Etc/GMT-7', text: 'SE Asia Standard Time (Etc/GMT-7)' },\n    {\n      value: 'Indian/Christmas',\n      text: 'SE Asia Standard Time (Indian/Christmas)'\n    },\n    { value: 'Pacific/Apia', text: 'Samoa Standard Time (Pacific/Apia)' },\n    { value: 'Asia/Brunei', text: 'Singapore Standard Time (Asia/Brunei)' },\n    {\n      value: 'Asia/Kuala_Lumpur',\n      text: 'Singapore Standard Time (Asia/Kuala_Lumpur)'\n    },\n    { value: 'Asia/Kuching', text: 'Singapore Standard Time (Asia/Kuching)' },\n    { value: 'Asia/Makassar', text: 'Singapore Standard Time (Asia/Makassar)' },\n    { value: 'Asia/Manila', text: 'Singapore Standard Time (Asia/Manila)' },\n    {\n      value: 'Asia/Singapore',\n      text: 'Singapore Standard Time (Asia/Singapore)'\n    },\n    { value: 'Etc/GMT-8', text: 'Singapore Standard Time (Etc/GMT-8)' },\n    {\n      value: 'Africa/Blantyre',\n      text: 'South Africa Standard Time (Africa/Blantyre)'\n    },\n    {\n      value: 'Africa/Bujumbura',\n      text: 'South Africa Standard Time (Africa/Bujumbura)'\n    },\n    {\n      value: 'Africa/Gaborone',\n      text: 'South Africa Standard Time (Africa/Gaborone)'\n    },\n    {\n      value: 'Africa/Harare',\n      text: 'South Africa Standard Time (Africa/Harare)'\n    },\n    {\n      value: 'Africa/Johannesburg',\n      text: 'South Africa Standard Time (Africa/Johannesburg)'\n    },\n    {\n      value: 'Africa/Kigali',\n      text: 'South Africa Standard Time (Africa/Kigali)'\n    },\n    {\n      value: 'Africa/Lubumbashi',\n      text: 'South Africa Standard Time (Africa/Lubumbashi)'\n    },\n    {\n      value: 'Africa/Lusaka',\n      text: 'South Africa Standard Time (Africa/Lusaka)'\n    },\n    {\n      value: 'Africa/Maputo',\n      text: 'South Africa Standard Time (Africa/Maputo)'\n    },\n    {\n      value: 'Africa/Maseru',\n      text: 'South Africa Standard Time (Africa/Maseru)'\n    },\n    {\n      value: 'Africa/Mbabane',\n      text: 'South Africa Standard Time (Africa/Mbabane)'\n    },\n    { value: 'Etc/GMT-2', text: 'South Africa Standard Time (Etc/GMT-2)' },\n    { value: 'Asia/Colombo', text: 'Sri Lanka Standard Time (Asia/Colombo)' },\n    { value: 'Asia/Damascus', text: 'Syria Standard Time (Asia/Damascus)' },\n    { value: 'Asia/Taipei', text: 'Taipei Standard Time (Asia/Taipei)' },\n    {\n      value: 'Australia/Currie',\n      text: 'Tasmania Standard Time (Australia/Currie)'\n    },\n    {\n      value: 'Australia/Hobart',\n      text: 'Tasmania Standard Time (Australia/Hobart)'\n    },\n    { value: 'Asia/Dili', text: 'Tokyo Standard Time (Asia/Dili)' },\n    { value: 'Asia/Jayapura', text: 'Tokyo Standard Time (Asia/Jayapura)' },\n    { value: 'Asia/Tokyo', text: 'Tokyo Standard Time (Asia/Tokyo)' },\n    { value: 'Etc/GMT-9', text: 'Tokyo Standard Time (Etc/GMT-9)' },\n    { value: 'Pacific/Palau', text: 'Tokyo Standard Time (Pacific/Palau)' },\n    { value: 'Etc/GMT-13', text: 'Tonga Standard Time (Etc/GMT-13)' },\n    {\n      value: 'Pacific/Enderbury',\n      text: 'Tonga Standard Time (Pacific/Enderbury)'\n    },\n    { value: 'Pacific/Fakaofo', text: 'Tonga Standard Time (Pacific/Fakaofo)' },\n    {\n      value: 'Pacific/Tongatapu',\n      text: 'Tonga Standard Time (Pacific/Tongatapu)'\n    },\n    {\n      value: 'Europe/Istanbul',\n      text: 'Turkey Standard Time (Europe/Istanbul)'\n    },\n    {\n      value: 'America/Indiana/Marengo',\n      text: 'US Eastern Standard Time (America/Indiana/Marengo)'\n    },\n    {\n      value: 'America/Indiana/Vevay',\n      text: 'US Eastern Standard Time (America/Indiana/Vevay)'\n    },\n    {\n      value: 'America/Indianapolis',\n      text: 'US Eastern Standard Time (America/Indianapolis)'\n    },\n    {\n      value: 'America/Creston',\n      text: 'US Mountain Standard Time (America/Creston)'\n    },\n    {\n      value: 'America/Dawson_Creek',\n      text: 'US Mountain Standard Time (America/Dawson_Creek)'\n    },\n    {\n      value: 'America/Hermosillo',\n      text: 'US Mountain Standard Time (America/Hermosillo)'\n    },\n    {\n      value: 'America/Phoenix',\n      text: 'US Mountain Standard Time (America/Phoenix)'\n    },\n    { value: 'Etc/GMT+7', text: 'US Mountain Standard Time (Etc/GMT+7)' },\n    { value: 'America/Danmarkshavn', text: 'UTC (America/Danmarkshavn)' },\n    { value: 'Etc/GMT', text: 'UTC (Etc/GMT)' },\n    { value: 'Etc/GMT-12', text: 'UTC+12 (Etc/GMT-12)' },\n    { value: 'Pacific/Funafuti', text: 'UTC+12 (Pacific/Funafuti)' },\n    { value: 'Pacific/Kwajalein', text: 'UTC+12 (Pacific/Kwajalein)' },\n    { value: 'Pacific/Majuro', text: 'UTC+12 (Pacific/Majuro)' },\n    { value: 'Pacific/Nauru', text: 'UTC+12 (Pacific/Nauru)' },\n    { value: 'Pacific/Tarawa', text: 'UTC+12 (Pacific/Tarawa)' },\n    { value: 'Pacific/Wake', text: 'UTC+12 (Pacific/Wake)' },\n    { value: 'Pacific/Wallis', text: 'UTC+12 (Pacific/Wallis)' },\n    { value: 'America/Noronha', text: 'UTC-02 (America/Noronha)' },\n    {\n      value: 'Atlantic/South_Georgia',\n      text: 'UTC-02 (Atlantic/South_Georgia)'\n    },\n    { value: 'Etc/GMT+2', text: 'UTC-02 (Etc/GMT+2)' },\n    { value: 'Etc/GMT+11', text: 'UTC-11 (Etc/GMT+11)' },\n    { value: 'Pacific/Midway', text: 'UTC-11 (Pacific/Midway)' },\n    { value: 'Pacific/Niue', text: 'UTC-11 (Pacific/Niue)' },\n    { value: 'Pacific/Pago_Pago', text: 'UTC-11 (Pacific/Pago_Pago)' },\n    {\n      value: 'Asia/Choibalsan',\n      text: 'Ulaanbaatar Standard Time (Asia/Choibalsan)'\n    },\n    {\n      value: 'Asia/Ulaanbaatar',\n      text: 'Ulaanbaatar Standard Time (Asia/Ulaanbaatar)'\n    },\n    {\n      value: 'America/Caracas',\n      text: 'Venezuela Standard Time (America/Caracas)'\n    },\n    {\n      value: 'Asia/Sakhalin',\n      text: 'Vladivostok Standard Time (Asia/Sakhalin)'\n    },\n    {\n      value: 'Asia/Ust-Nera',\n      text: 'Vladivostok Standard Time (Asia/Ust-Nera)'\n    },\n    {\n      value: 'Asia/Vladivostok',\n      text: 'Vladivostok Standard Time (Asia/Vladivostok)'\n    },\n    {\n      value: 'Antarctica/Casey',\n      text: 'W. Australia Standard Time (Antarctica/Casey)'\n    },\n    {\n      value: 'Australia/Perth',\n      text: 'W. Australia Standard Time (Australia/Perth)'\n    },\n    {\n      value: 'Africa/Algiers',\n      text: 'W. Central Africa Standard Time (Africa/Algiers)'\n    },\n    {\n      value: 'Africa/Bangui',\n      text: 'W. Central Africa Standard Time (Africa/Bangui)'\n    },\n    {\n      value: 'Africa/Brazzaville',\n      text: 'W. Central Africa Standard Time (Africa/Brazzaville)'\n    },\n    {\n      value: 'Africa/Douala',\n      text: 'W. Central Africa Standard Time (Africa/Douala)'\n    },\n    {\n      value: 'Africa/Kinshasa',\n      text: 'W. Central Africa Standard Time (Africa/Kinshasa)'\n    },\n    {\n      value: 'Africa/Lagos',\n      text: 'W. Central Africa Standard Time (Africa/Lagos)'\n    },\n    {\n      value: 'Africa/Libreville',\n      text: 'W. Central Africa Standard Time (Africa/Libreville)'\n    },\n    {\n      value: 'Africa/Luanda',\n      text: 'W. Central Africa Standard Time (Africa/Luanda)'\n    },\n    {\n      value: 'Africa/Malabo',\n      text: 'W. Central Africa Standard Time (Africa/Malabo)'\n    },\n    {\n      value: 'Africa/Ndjamena',\n      text: 'W. Central Africa Standard Time (Africa/Ndjamena)'\n    },\n    {\n      value: 'Africa/Niamey',\n      text: 'W. Central Africa Standard Time (Africa/Niamey)'\n    },\n    {\n      value: 'Africa/Porto-Novo',\n      text: 'W. Central Africa Standard Time (Africa/Porto-Novo)'\n    },\n    {\n      value: 'Africa/Tunis',\n      text: 'W. Central Africa Standard Time (Africa/Tunis)'\n    },\n    { value: 'Etc/GMT-1', text: 'W. Central Africa Standard Time (Etc/GMT-1)' },\n    {\n      value: 'Arctic/Longyearbyen',\n      text: 'W. Europe Standard Time (Arctic/Longyearbyen)'\n    },\n    {\n      value: 'Europe/Amsterdam',\n      text: 'W. Europe Standard Time (Europe/Amsterdam)'\n    },\n    {\n      value: 'Europe/Andorra',\n      text: 'W. Europe Standard Time (Europe/Andorra)'\n    },\n    { value: 'Europe/Berlin', text: 'W. Europe Standard Time (Europe/Berlin)' },\n    {\n      value: 'Europe/Busingen',\n      text: 'W. Europe Standard Time (Europe/Busingen)'\n    },\n    {\n      value: 'Europe/Gibraltar',\n      text: 'W. Europe Standard Time (Europe/Gibraltar)'\n    },\n    {\n      value: 'Europe/Luxembourg',\n      text: 'W. Europe Standard Time (Europe/Luxembourg)'\n    },\n    { value: 'Europe/Malta', text: 'W. Europe Standard Time (Europe/Malta)' },\n    { value: 'Europe/Monaco', text: 'W. Europe Standard Time (Europe/Monaco)' },\n    { value: 'Europe/Oslo', text: 'W. Europe Standard Time (Europe/Oslo)' },\n    { value: 'Europe/Rome', text: 'W. Europe Standard Time (Europe/Rome)' },\n    {\n      value: 'Europe/San_Marino',\n      text: 'W. Europe Standard Time (Europe/San_Marino)'\n    },\n    {\n      value: 'Europe/Stockholm',\n      text: 'W. Europe Standard Time (Europe/Stockholm)'\n    },\n    { value: 'Europe/Vaduz', text: 'W. Europe Standard Time (Europe/Vaduz)' },\n    {\n      value: 'Europe/Vatican',\n      text: 'W. Europe Standard Time (Europe/Vatican)'\n    },\n    { value: 'Europe/Vienna', text: 'W. Europe Standard Time (Europe/Vienna)' },\n    { value: 'Europe/Zurich', text: 'W. Europe Standard Time (Europe/Zurich)' },\n    {\n      value: 'Antarctica/Mawson',\n      text: 'West Asia Standard Time (Antarctica/Mawson)'\n    },\n    { value: 'Asia/Aqtau', text: 'West Asia Standard Time (Asia/Aqtau)' },\n    { value: 'Asia/Aqtobe', text: 'West Asia Standard Time (Asia/Aqtobe)' },\n    { value: 'Asia/Ashgabat', text: 'West Asia Standard Time (Asia/Ashgabat)' },\n    { value: 'Asia/Dushanbe', text: 'West Asia Standard Time (Asia/Dushanbe)' },\n    { value: 'Asia/Oral', text: 'West Asia Standard Time (Asia/Oral)' },\n    {\n      value: 'Asia/Samarkand',\n      text: 'West Asia Standard Time (Asia/Samarkand)'\n    },\n    { value: 'Asia/Tashkent', text: 'West Asia Standard Time (Asia/Tashkent)' },\n    { value: 'Etc/GMT-5', text: 'West Asia Standard Time (Etc/GMT-5)' },\n    {\n      value: 'Indian/Kerguelen',\n      text: 'West Asia Standard Time (Indian/Kerguelen)'\n    },\n    {\n      value: 'Indian/Maldives',\n      text: 'West Asia Standard Time (Indian/Maldives)'\n    },\n    {\n      value: 'Antarctica/DumontDUrville',\n      text: 'West Pacific Standard Time (Antarctica/DumontDUrville)'\n    },\n    { value: 'Etc/GMT-10', text: 'West Pacific Standard Time (Etc/GMT-10)' },\n    {\n      value: 'Pacific/Guam',\n      text: 'West Pacific Standard Time (Pacific/Guam)'\n    },\n    {\n      value: 'Pacific/Port_Moresby',\n      text: 'West Pacific Standard Time (Pacific/Port_Moresby)'\n    },\n    {\n      value: 'Pacific/Saipan',\n      text: 'West Pacific Standard Time (Pacific/Saipan)'\n    },\n    {\n      value: 'Pacific/Truk',\n      text: 'West Pacific Standard Time (Pacific/Truk)'\n    },\n    { value: 'Asia/Khandyga', text: 'Yakutsk Standard Time (Asia/Khandyga)' },\n    { value: 'Asia/Yakutsk', text: 'Yakutsk Standard Time (Asia/Yakutsk)' }\n  ].filter((t) => {\n    if (timezones) return timezones.indexOf(t.value) !== -1;\n    else return true;\n  });\n  const childrenWithProps = React.Children.map(children, (child) =>\n    React.cloneElement(child, { options, ...props })\n  );\n\n  return <div>{childrenWithProps}</div>;\n}\n\nTimezoneOptions.propTypes = {\n  children: PropTypes.node.isRequired,\n  timezones: PropTypes.arrayOf(PropTypes.string).isRequired\n};\n\nexport { TimezoneOptions };\n"
  },
  {
    "path": "packages/evershop/src/components/common/modal/Alert.jsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport { assign } from '@evershop/evershop/lib/util/assign';\nimport { produce } from 'immer';\nimport PropTypes from 'prop-types';\nimport React, { useReducer } from 'react';\nimport ReactDOM from 'react-dom';\n\nimport './Alert.scss';\nimport { Card } from '@components/common/ui/Card';\nimport {\n  CardContent,\n  CardFooter,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\n\nconst AlertContext = React.createContext();\nexport const useAlertContext = () => React.useContext(AlertContext);\n\nfunction reducer(state, action) {\n  switch (action.type) {\n    case 'close':\n      return { ...state, showing: false, closing: false };\n    case 'closing':\n      return { ...state, showing: true, closing: true };\n    case 'open':\n      return { ...state, showing: true, closing: false };\n    default:\n      throw new Error();\n  }\n}\n\nconst alertReducer = produce((draff, action) => {\n  switch (action.type) {\n    case 'open':\n      draff = { ...action.payload };\n      return draff;\n    case 'remove':\n      return {};\n    case 'update':\n      assign(draff, action.payload);\n      return draff;\n    default:\n      throw new Error();\n  }\n});\n\nfunction Alert({ children }) {\n  const [alert, dispatchAlert] = useReducer(alertReducer, {});\n  const [state, dispatch] = useReducer(reducer, {\n    showing: false,\n    closing: false\n  });\n\n  const openAlert = ({ heading, content, primaryAction, secondaryAction }) => {\n    dispatchAlert({\n      type: 'open',\n      payload: {\n        heading,\n        content,\n        primaryAction,\n        secondaryAction\n      }\n    });\n    dispatch({ type: 'open' });\n  };\n\n  return (\n    <AlertContext.Provider\n      value={{\n        dispatchAlert,\n        openAlert,\n        closeAlert: () => dispatch({ type: 'closing' })\n      }}\n    >\n      {children}\n      {state.showing === true &&\n        ReactDOM.createPortal(\n          <div\n            className={\n              state.closing === false\n                ? 'modal-overlay fadeIn'\n                : 'modal-overlay fadeOut'\n            }\n            onAnimationEnd={() => {\n              if (state.closing) {\n                dispatch({ type: 'close' });\n                dispatchAlert({ type: 'remove' });\n              }\n            }}\n          >\n            <div\n              key={state.key}\n              className=\"modal-wrapper flex self-center justify-center\"\n              aria-modal\n              aria-hidden\n              tabIndex={-1}\n              role=\"dialog\"\n            >\n              <div className=\"modal\">\n                <Card>\n                  {alert.heading && (\n                    <CardHeader>\n                      <CardTitle>{alert.heading}</CardTitle>\n                    </CardHeader>\n                  )}\n                  <CardContent>{alert.content}</CardContent>\n                  {(alert.primaryAction !== undefined ||\n                    alert.secondaryAction !== undefined) && (\n                    <CardFooter>\n                      <div className=\"flex justify-end space-x-2 w-full\">\n                        {alert.primaryAction && (\n                          <Button\n                            onClick={alert.primaryAction.onAction}\n                            variant={alert.primaryAction.variant}\n                          >\n                            {alert.primaryAction.title}\n                          </Button>\n                        )}\n                        {alert.secondaryAction && (\n                          <Button\n                            onClick={alert.secondaryAction.onAction}\n                            variant={alert.secondaryAction.variant}\n                          >\n                            {alert.secondaryAction.title}\n                          </Button>\n                        )}\n                      </div>\n                    </CardFooter>\n                  )}\n                </Card>\n              </div>\n            </div>\n          </div>,\n          document.body\n        )}\n    </AlertContext.Provider>\n  );\n}\n\nAlert.propTypes = {\n  children: PropTypes.node.isRequired\n};\n\nexport { Alert };\n"
  },
  {
    "path": "packages/evershop/src/components/common/modal/Alert.scss",
    "content": ".modal-overlay {\n  position: fixed;\n  top: 100%;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  display: flex;\n  justify-content: center;\n  z-index: 1001;\n  background-color: rgba(17, 18, 19, 0.3);\n  -webkit-animation-duration: 0.25s;\n  animation-duration: 0.25s;\n  .modal-wrapper {\n    max-height: calc(100vh - 4rem);\n    .modal {\n      max-height: calc(100vh - 4rem);\n    }\n    .modal-content {\n      max-height: calc(100vh - 18rem);\n      overflow-y: auto;\n    }\n  }\n  &.fadeIn {\n    top: 0;\n    -webkit-animation-name: fadeIn;\n    animation-name: fadeIn;\n  }\n  &.fadeOut {\n    top: 100%;\n    -webkit-animation-name: fadeOut;\n    animation-name: fadeOut;\n  }\n  .modal {\n    min-width: 31.25rem;\n    max-width: 50%;\n    position: relative;\n    .modal-close-button {\n      position: absolute;\n      top: 0.938rem;\n      right: 0.938rem;\n    }\n  }\n}\n@-webkit-keyframes fadeIn {\n  0% {\n    top: 100%;\n    opacity: 0;\n  }\n  100% {\n    top: 0%;\n    opacity: 100;\n  }\n}\n@keyframes fadeIn {\n  0% {\n    top: 100%;\n    opacity: 0;\n  }\n  100% {\n    top: 0%;\n    opacity: 100;\n  }\n}\n\n@-webkit-keyframes fadeOut {\n  0% {\n    opacity: 100;\n    top: 0%;\n  }\n  100% {\n    opacity: 0;\n    top: 100%;\n  }\n}\n@keyframes fadeOut {\n  0% {\n    opacity: 100;\n    top: 0%;\n  }\n  100% {\n    opacity: 0;\n    top: 100%;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/react/Head.jsx",
    "content": "import Area from '@components/common/Area';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nexport default function Head() {\n  return ReactDOM.createPortal(\n    <Area id=\"head\" noOuter />,\n    document.getElementsByTagName('head')[0]\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/react/client/Client.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { AppProvider } from '@components/common/context/app.js';\nimport { Alert } from '@components/common/modal/Alert.js';\nimport Head from '@components/common/react/Head.js';\nimport React from 'react';\nimport { createClient, Provider } from 'urql';\n\ndeclare global {\n  interface Window {\n    eContext: any;\n  }\n}\n\nconst client = createClient({\n  url: window.eContext?.config?.pageMeta?.route?.isAdmin\n    ? '/api/admin/graphql'\n    : '/api/graphql'\n});\n\ninterface AppProps {\n  children: React.ReactNode;\n}\n\nexport function App({ children }: AppProps) {\n  return (\n    <AppProvider value={window.eContext}>\n      <Provider value={client}>\n        <Alert>\n          <Head />\n          <Area id=\"body\" className=\"wrapper\" />\n        </Alert>\n      </Provider>\n      {children}\n    </AppProvider>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/react/client/HotReload.tsx",
    "content": "import { useAppDispatch, useAppState } from '@components/common/context/app.js';\nimport axios from 'axios';\nimport { produce } from 'immer';\nimport React from 'react';\n\ninterface HotReloadProps {\n  hot: {\n    subscribe: (callback: (event: { action: string }) => void) => void;\n  };\n}\n\nexport function HotReload({ hot }: HotReloadProps): React.ReactElement | null {\n  const [isRefreshing, setIsRefreshing] = React.useState(false);\n  const appContext = useAppState();\n  const { setData } = useAppDispatch();\n\n  React.useEffect(() => {\n    hot.subscribe(async (event) => {\n      if (event.action === 'serverReloaded') {\n        const interval = setInterval(async () => {\n          try {\n            const response = await axios.get('/', {\n              validateStatus(status) {\n                return status >= 200 && status <= 500;\n              }\n            });\n            if (response.status === 200) {\n              // Server is back, reload the page\n              window.location.reload();\n              clearInterval(interval);\n            }\n          } catch (error) {\n            // Ignore errors, server might still be down\n          }\n        }, 300);\n      }\n    });\n  }, []);\n\n  // React.useEffect(() => {\n  //   if (isRefreshing) {\n  //     // Use preflight request to check if server is back\n  //     const interval = setInterval(async () => {\n  //       try {\n  //         const response = await axios.get('/', {\n  //           validateStatus(status) {\n  //             return status >= 200 && status <= 500;\n  //           }\n  //         });\n  //         if (response.status === 200) {\n  //           // Server is back, reload the page\n  //           window.location.reload();\n  //           clearInterval(interval);\n  //         }\n  //       } catch (error) {\n  //         // Ignore errors, server might still be down\n  //       }\n  //     }, 300);\n  //   }\n  // }, [isRefreshing]);\n\n  return null;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/react/client/Hydrate.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { AppProvider } from '@components/common/context/app.js';\nimport { Alert } from '@components/common/modal/Alert.js';\nimport React from 'react';\nimport { Client, Provider } from 'urql';\n\ninterface HydrateProps {\n  client: Client;\n}\n\nexport default function Hydrate({ client }: HydrateProps): React.ReactElement {\n  return (\n    <Provider value={client}>\n      <AppProvider value={window.eContext}>\n        <Alert>\n          <Area id=\"body\" className=\"wrapper\" />\n        </Alert>\n      </AppProvider>\n    </Provider>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/react/client/HydrateAdmin.tsx",
    "content": "import React from 'react';\nimport { createClient } from 'urql';\nimport Hydrate from './Hydrate.js';\n\nconst client = createClient({\n  url: '/api/admin/graphql'\n});\n\nexport function HydrateAdmin() {\n  return <Hydrate client={client} />;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/react/client/HydrateFrontStore.tsx",
    "content": "import React from 'react';\nimport { createClient } from 'urql';\nimport Hydrate from './Hydrate.js';\n\nconst client = createClient({\n  url: '/api/graphql'\n});\n\nexport function HydrateFrontStore() {\n  return <Hydrate client={client} />;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/react/client/Index.jsx",
    "content": "import Area from '@components/common/Area';\nimport { App } from '@components/common/react/client/Client';\nimport { HotReload } from '@components/common/react/client/HotReload';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n/** render */\nReactDOM.render(\n  <App>\n    <Area />\n  </App>,\n  document.getElementById('app')\n);\n"
  },
  {
    "path": "packages/evershop/src/components/common/react/getComponents.js",
    "content": "import { resolve } from 'path';\nimport { useAppState } from '@components/common/context/app';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { get } from '../../../lib/util/get.js';\n\nexport function getComponents() {\n  const componentsPath = get(useAppState(), 'componentsPath');\n  if (!componentsPath) {\n    return {};\n  } else {\n    return require(resolve(\n      CONSTANTS.ROOTPATH,\n      '.evershop/build/',\n      componentsPath\n    ));\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/react/server/Server.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Alert } from '@components/common/modal/Alert.js';\nimport React from 'react';\nimport { Route } from '../../../../types/route.js';\n\ninterface ServerHtmlProps {\n  route: Route;\n  css: string[];\n  js: string[];\n  appContext: string;\n}\nfunction ServerHtml({ route, css, js, appContext }: ServerHtmlProps) {\n  const classes = route.isAdmin\n    ? `admin ${route.id}`\n    : `frontStore ${route.id}`;\n  return (\n    <>\n      <head>\n        <meta charSet=\"utf-8\" />\n        <script dangerouslySetInnerHTML={{ __html: appContext }} />\n        {css.map((source, index) => (\n          <style key={index} dangerouslySetInnerHTML={{ __html: source }} />\n        ))}\n        <Area noOuter id=\"head\" />\n      </head>\n      <body id=\"body\" className={classes}>\n        <div id=\"app\">\n          <Alert>\n            <Area id=\"body\" className=\"wrapper\" />\n          </Alert>\n        </div>\n        {js.map((src, index) => (\n          <script src={src} key={index} />\n        ))}\n      </body>\n    </>\n  );\n}\n\nexport default ServerHtml;\n"
  },
  {
    "path": "packages/evershop/src/components/common/react/server/render.tsx",
    "content": "import { AppProvider } from '@components/common/context/app.js';\nimport ServerHtml from '@components/common/react/server/Server.js';\nimport React from 'react';\nimport { renderToString } from 'react-dom/server.js';\n\nfunction renderHtml(route, js, css, contextData, langeCode) {\n  const source = renderToString(\n    <AppProvider value={JSON.parse(contextData)}>\n      <ServerHtml\n        route={route}\n        js={js}\n        css={css}\n        appContext={`var eContext = ${contextData}`}\n      />\n    </AppProvider>\n  );\n\n  return `<!DOCTYPE html><html id=\"root\" lang=\"${langeCode}\">${source}</html>`;\n}\n\nexport { renderHtml };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Accordion.tsx",
    "content": "import { Accordion as AccordionPrimitive } from '@base-ui/react/accordion';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport React from 'react';\n\nfunction Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {\n  return (\n    <AccordionPrimitive.Root\n      data-slot=\"accordion\"\n      className={cn('flex w-full flex-col', className)}\n      {...props}\n    />\n  );\n}\n\nfunction AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {\n  return (\n    <AccordionPrimitive.Item\n      data-slot=\"accordion-item\"\n      className={cn('not-last:border-b', className)}\n      {...props}\n    />\n  );\n}\n\nfunction AccordionTrigger({\n  className,\n  children,\n  ...props\n}: AccordionPrimitive.Trigger.Props) {\n  return (\n    <AccordionPrimitive.Header className=\"flex\">\n      <AccordionPrimitive.Trigger\n        data-slot=\"accordion-trigger\"\n        className={cn(\n          'focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground rounded-md py-4 text-left text-sm font-medium hover:underline focus-visible:ring-[3px] **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none disabled:pointer-events-none disabled:opacity-50',\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <ChevronDownIcon\n          data-slot=\"accordion-trigger-icon\"\n          className=\"pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden\"\n        />\n        <ChevronUpIcon\n          data-slot=\"accordion-trigger-icon\"\n          className=\"pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline\"\n        />\n      </AccordionPrimitive.Trigger>\n    </AccordionPrimitive.Header>\n  );\n}\n\nfunction AccordionContent({\n  className,\n  children,\n  ...props\n}: AccordionPrimitive.Panel.Props) {\n  return (\n    <AccordionPrimitive.Panel\n      data-slot=\"accordion-content\"\n      className=\"data-open:animate-accordion-down data-closed:animate-accordion-up text-sm overflow-hidden\"\n      {...props}\n    >\n      <div\n        className={cn(\n          'pt-0 pb-4 [&_a]:hover:text-foreground h-(--accordion-panel-height) data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4',\n          className\n        )}\n      >\n        {children}\n      </div>\n    </AccordionPrimitive.Panel>\n  );\n}\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Alert.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\n\nconst alertVariants = cva(\n  \"grid gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-card text-card-foreground',\n        destructive:\n          'text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current'\n      }\n    },\n    defaultVariants: {\n      variant: 'default'\n    }\n  }\n);\n\nfunction Alert({\n  className,\n  variant,\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {\n  return (\n    <div\n      data-slot=\"alert\"\n      role=\"alert\"\n      className={cn(alertVariants({ variant }), className)}\n      {...props}\n    />\n  );\n}\n\nfunction AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"alert-title\"\n      className={cn(\n        'font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AlertDescription({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"alert-description\"\n      className={cn(\n        'text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AlertAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"alert-action\"\n      className={cn('absolute top-2.5 right-3', className)}\n      {...props}\n    />\n  );\n}\n\nexport { Alert, AlertTitle, AlertDescription, AlertAction };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/AlertDialog.tsx",
    "content": "import { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog';\nimport { Button } from '@components/common/ui/Button.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\n\nfunction AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {\n  return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />;\n}\n\nfunction AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {\n  return (\n    <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />\n  );\n}\n\nfunction AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {\n  return (\n    <AlertDialogPrimitive.Portal data-slot=\"alert-dialog-portal\" {...props} />\n  );\n}\n\nfunction AlertDialogOverlay({\n  className,\n  ...props\n}: AlertDialogPrimitive.Backdrop.Props) {\n  return (\n    <AlertDialogPrimitive.Backdrop\n      data-slot=\"alert-dialog-overlay\"\n      className={cn(\n        'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogContent({\n  className,\n  size = 'default',\n  ...props\n}: AlertDialogPrimitive.Popup.Props & {\n  size?: 'default' | 'sm';\n}) {\n  return (\n    <AlertDialogPortal>\n      <AlertDialogOverlay />\n      <AlertDialogPrimitive.Popup\n        data-slot=\"alert-dialog-content\"\n        data-size={size}\n        className={cn(\n          'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none',\n          className\n        )}\n        {...props}\n      />\n    </AlertDialogPortal>\n  );\n}\n\nfunction AlertDialogHeader({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"alert-dialog-header\"\n      className={cn(\n        'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogFooter({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"alert-dialog-footer\"\n      className={cn(\n        'flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogMedia({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"alert-dialog-media\"\n      className={cn(\n        \"bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {\n  return (\n    <AlertDialogPrimitive.Title\n      data-slot=\"alert-dialog-title\"\n      className={cn(\n        'text-lg font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {\n  return (\n    <AlertDialogPrimitive.Description\n      data-slot=\"alert-dialog-description\"\n      className={cn(\n        'text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogAction({\n  className,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  return (\n    <Button\n      data-slot=\"alert-dialog-action\"\n      className={cn(className)}\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogCancel({\n  className,\n  variant = 'outline',\n  size = 'default',\n  ...props\n}: AlertDialogPrimitive.Close.Props &\n  Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {\n  return (\n    <AlertDialogPrimitive.Close\n      data-slot=\"alert-dialog-cancel\"\n      className={cn(className)}\n      render={<Button variant={variant} size={size} />}\n      {...props}\n    />\n  );\n}\n\nexport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogMedia,\n  AlertDialogOverlay,\n  AlertDialogPortal,\n  AlertDialogTitle,\n  AlertDialogTrigger\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/AspectRatio.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport React from 'react';\n\nfunction AspectRatio({\n  ratio,\n  className,\n  ...props\n}: React.ComponentProps<'div'> & { ratio: number }) {\n  return (\n    <div\n      data-slot=\"aspect-ratio\"\n      style={\n        {\n          '--ratio': ratio\n        } as React.CSSProperties\n      }\n      className={cn('relative aspect-(--ratio)', className)}\n      {...props}\n    />\n  );\n}\n\nexport { AspectRatio };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Avatar.tsx",
    "content": "import { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      data-slot=\"avatar\"\n      data-size={size}\n      className={cn(\n        'size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      data-slot=\"avatar-image\"\n      className={cn(\n        'rounded-full aspect-square size-full object-cover',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      data-slot=\"avatar-fallback\"\n      className={cn(\n        'bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      data-slot=\"avatar-badge\"\n      className={cn(\n        'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"avatar-group\"\n      className={cn(\n        '*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"avatar-group-count\"\n      className={cn(\n        'bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Badge.tsx",
    "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport React from 'react';\n\nconst badgeVariants = cva(\n  'h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge',\n  {\n    variants: {\n      variant: {\n        default:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20',\n        success:\n          'bg-green-500/10 [a]:hover:bg-green-500/20 focus-visible:ring-green-500/20 dark:focus-visible:ring-green-500/40 text-green-700 dark:text-green-400 dark:bg-green-500/20',\n        warning:\n          'bg-amber-500/10 [a]:hover:bg-amber-500/20 focus-visible:ring-amber-500/20 dark:focus-visible:ring-amber-500/40 text-amber-700 dark:text-amber-400 dark:bg-amber-500/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline'\n      }\n    },\n    defaultVariants: {\n      variant: 'default'\n    }\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ className, variant }))\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant\n    }\n  });\n}\n\nexport { Badge, badgeVariants };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Breadcrumb.tsx",
    "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react';\nimport * as React from 'react';\n\nfunction Breadcrumb({ className, ...props }: React.ComponentProps<'nav'>) {\n  return (\n    <nav\n      aria-label=\"breadcrumb\"\n      data-slot=\"breadcrumb\"\n      className={cn(className)}\n      {...props}\n    />\n  );\n}\n\nfunction BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {\n  return (\n    <ol\n      data-slot=\"breadcrumb-list\"\n      className={cn(\n        'text-muted-foreground gap-1.5 text-sm sm:gap-2.5 flex flex-wrap items-center break-words',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"breadcrumb-item\"\n      className={cn('gap-1.5 inline-flex items-center', className)}\n      {...props}\n    />\n  );\n}\n\nfunction BreadcrumbLink({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'a'>) {\n  return useRender({\n    defaultTagName: 'a',\n    props: mergeProps<'a'>(\n      {\n        className: cn('hover:text-foreground transition-colors', className)\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'breadcrumb-link'\n    }\n  });\n}\n\nfunction BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      data-slot=\"breadcrumb-page\"\n      role=\"link\"\n      aria-disabled=\"true\"\n      aria-current=\"page\"\n      className={cn('text-foreground font-normal', className)}\n      {...props}\n    />\n  );\n}\n\nfunction BreadcrumbSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"breadcrumb-separator\"\n      role=\"presentation\"\n      aria-hidden=\"true\"\n      className={cn('[&>svg]:size-3.5', className)}\n      {...props}\n    >\n      {children ?? <ChevronRightIcon />}\n    </li>\n  );\n}\n\nfunction BreadcrumbEllipsis({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      data-slot=\"breadcrumb-ellipsis\"\n      role=\"presentation\"\n      aria-hidden=\"true\"\n      className={cn(\n        'size-5 [&>svg]:size-4 flex items-center justify-center',\n        className\n      )}\n      {...props}\n    >\n      <MoreHorizontalIcon />\n      <span className=\"sr-only\">More</span>\n    </span>\n  );\n}\n\nexport {\n  Breadcrumb,\n  BreadcrumbList,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n  BreadcrumbEllipsis\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Button.tsx",
    "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport React from 'react';\nimport { Spinner } from './Spinner.js';\n\nconst buttonVariants = cva(\n  \"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground',\n        destructive:\n          'bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline'\n      },\n      size: {\n        default:\n          'h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: 'h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5',\n        lg: 'h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3',\n        xl: \"h-12 gap-2 px-4 text-base has-data-[icon=inline-end]:pr-3.5 has-data-[icon=inline-start]:pl-3.5 [&_svg:not([class*='size-'])]:size-5\",\n        icon: 'size-9',\n        'icon-xs':\n          \"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md',\n        'icon-lg': 'size-10',\n        'icon-xl': 'size-12'\n      }\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default'\n    }\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  isLoading = false,\n  disabled,\n  children,\n  ...props\n}: ButtonPrimitive.Props &\n  VariantProps<typeof buttonVariants> & {\n    isLoading?: boolean;\n  }) {\n  return (\n    <ButtonPrimitive\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      disabled={disabled || isLoading}\n      {...props}\n    >\n      {isLoading ? <Spinner /> : children}\n    </ButtonPrimitive>\n  );\n}\n\nexport { Button, buttonVariants };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/ButtonGroup.tsx",
    "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { Separator } from '@components/common/ui/Separator.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport React from 'react';\n\nconst buttonGroupVariants = cva(\n  \"has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1\",\n  {\n    variants: {\n      orientation: {\n        horizontal:\n          '[&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0 [&>[data-slot]]:rounded-r-none',\n        vertical:\n          '[&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! flex-col [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0 [&>[data-slot]]:rounded-b-none'\n      }\n    },\n    defaultVariants: {\n      orientation: 'horizontal'\n    }\n  }\n);\n\nfunction ButtonGroup({\n  className,\n  orientation,\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof buttonGroupVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"button-group\"\n      data-orientation={orientation}\n      className={cn(buttonGroupVariants({ orientation }), className)}\n      {...props}\n    />\n  );\n}\n\nfunction ButtonGroupText({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          \"bg-muted gap-2 rounded-md border px-2.5 text-sm font-medium shadow-xs [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none\",\n          className\n        )\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'button-group-text'\n    }\n  });\n}\n\nfunction ButtonGroupSeparator({\n  className,\n  orientation = 'vertical',\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"button-group-separator\"\n      orientation={orientation}\n      className={cn(\n        'bg-input relative self-stretch data-[orientation=horizontal]:mx-px data-[orientation=horizontal]:w-auto data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  ButtonGroup,\n  ButtonGroupSeparator,\n  ButtonGroupText,\n  buttonGroupVariants\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Card.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      data-slot=\"card\"\n      data-size={size}\n      className={cn(\n        'ring-foreground/10 bg-card text-card-foreground gap-6 rounded-xl py-6 text-sm shadow-xs ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        'gap-1 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn(\n        'text-base leading-normal font-medium group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn('text-muted-foreground text-sm', className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-content\"\n      className={cn('px-6 group-data-[size=sm]/card:px-4', className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn(\n        'rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4 flex items-center',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Chart.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\nimport * as RechartsPrimitive from 'recharts';\n\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: '', dark: '.dark' } as const;\n\nexport type ChartConfig = {\n  [k in string]: {\n    label?: React.ReactNode;\n    icon?: React.ComponentType;\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  );\n};\n\ntype ChartContextProps = {\n  config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n  const context = React.useContext(ChartContext);\n\n  if (!context) {\n    throw new Error('useChart must be used within a <ChartContainer />');\n  }\n\n  return context;\n}\n\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  ...props\n}: React.ComponentProps<'div'> & {\n  config: ChartConfig;\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >['children'];\n}) {\n  const uniqueId = React.useId();\n  const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        data-slot=\"chart\"\n        data-chart={chartId}\n        className={cn(\n          \"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n          className\n        )}\n        {...props}\n      >\n        <ChartStyle id={chartId} config={config} />\n        <RechartsPrimitive.ResponsiveContainer>\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(\n    ([, config]) => config.theme || config.color\n  );\n\n  if (!colorConfig.length) {\n    return null;\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color =\n      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||\n      itemConfig.color;\n    return color ? `  --color-${key}: ${color};` : null;\n  })\n  .join('\\n')}\n}\n`\n          )\n          .join('\\n')\n      }}\n    />\n  );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = 'dot',\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  color,\n  nameKey,\n  labelKey\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<'div'> & {\n    hideLabel?: boolean;\n    hideIndicator?: boolean;\n    indicator?: 'line' | 'dot' | 'dashed';\n    nameKey?: string;\n    labelKey?: string;\n  }) {\n  const { config } = useChart();\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null;\n    }\n\n    const [item] = payload;\n    const key = `${labelKey || item?.dataKey || item?.name || 'value'}`;\n    const itemConfig = getPayloadConfigFromPayload(config, item, key);\n    const value =\n      !labelKey && typeof label === 'string'\n        ? config[label as keyof typeof config]?.label || label\n        : itemConfig?.label;\n\n    if (labelFormatter) {\n      return (\n        <div className={cn('font-medium', labelClassName)}>\n          {labelFormatter(value, payload)}\n        </div>\n      );\n    }\n\n    if (!value) {\n      return null;\n    }\n\n    return <div className={cn('font-medium', labelClassName)}>{value}</div>;\n  }, [\n    label,\n    labelFormatter,\n    payload,\n    hideLabel,\n    labelClassName,\n    config,\n    labelKey\n  ]);\n\n  if (!active || !payload?.length) {\n    return null;\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== 'dot';\n\n  return (\n    <div\n      className={cn(\n        'border-border/50 bg-background gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl grid min-w-[8rem] items-start',\n        className\n      )}\n    >\n      {!nestLabel ? tooltipLabel : null}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== 'none')\n          .map((item, index) => {\n            const key = `${nameKey || item.name || item.dataKey || 'value'}`;\n            const itemConfig = getPayloadConfigFromPayload(config, item, key);\n            const indicatorColor = color || item.payload.fill || item.color;\n\n            return (\n              <div\n                key={item.dataKey}\n                className={cn(\n                  '[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5',\n                  indicator === 'dot' && 'items-center'\n                )}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\n                            'shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)',\n                            {\n                              'h-2.5 w-2.5': indicator === 'dot',\n                              'w-1': indicator === 'line',\n                              'w-0 border-[1.5px] border-dashed bg-transparent':\n                                indicator === 'dashed',\n                              'my-0.5': nestLabel && indicator === 'dashed'\n                            }\n                          )}\n                          style={\n                            {\n                              '--color-bg': indicatorColor,\n                              '--color-border': indicatorColor\n                            } as React.CSSProperties\n                          }\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        'flex flex-1 justify-between leading-none',\n                        nestLabel ? 'items-end' : 'items-center'\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label || item.name}\n                        </span>\n                      </div>\n                      {item.value && (\n                        <span className=\"text-foreground font-mono font-medium tabular-nums\">\n                          {item.value.toLocaleString()}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            );\n          })}\n      </div>\n    </div>\n  );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = 'bottom',\n  nameKey\n}: React.ComponentProps<'div'> &\n  Pick<RechartsPrimitive.LegendProps, 'payload' | 'verticalAlign'> & {\n    hideIcon?: boolean;\n    nameKey?: string;\n  }) {\n  const { config } = useChart();\n\n  if (!payload?.length) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        'flex items-center justify-center gap-4',\n        verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n        className\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== 'none')\n        .map((item) => {\n          const key = `${nameKey || item.dataKey || 'value'}`;\n          const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n          return (\n            <div\n              key={item.value}\n              className={cn(\n                '[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3'\n              )}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <div\n                  className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n                  style={{\n                    backgroundColor: item.color\n                  }}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          );\n        })}\n    </div>\n  );\n}\n\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== 'object' || payload === null) {\n    return undefined;\n  }\n\n  const payloadPayload =\n    'payload' in payload &&\n    typeof payload.payload === 'object' &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined;\n\n  let configLabelKey: string = key;\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === 'string'\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string;\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string;\n  }\n\n  return configLabelKey in config\n    ? config[configLabelKey]\n    : config[key as keyof typeof config];\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Checkbox.tsx",
    "content": "import { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { CheckIcon } from 'lucide-react';\nimport React from 'react';\n\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\n  return (\n    <CheckboxPrimitive.Root\n      data-slot=\"checkbox\"\n      className={cn(\n        'border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border shadow-xs transition-shadow group-has-disabled/field:opacity-50 focus-visible:ring-[3px] aria-invalid:ring-[3px] peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50',\n        className\n      )}\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        data-slot=\"checkbox-indicator\"\n        className=\"[&>svg]:size-3.5 grid place-content-center text-current transition-none\"\n      >\n        <CheckIcon />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  );\n}\n\nexport { Checkbox };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Circle.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva } from 'class-variance-authority';\nimport React from 'react';\n\nconst circleVariants = cva('rounded-full inline-flex border-4', {\n  variants: {\n    variant: {\n      default: 'border-muted',\n      success: 'border-green-500/30',\n      info: 'border-cyan-500/30',\n      attention: 'border-yellow-500/30',\n      destructive: 'border-destructive/30',\n      warning: 'border-amber-500/30',\n      new: 'border-muted'\n    }\n  },\n  defaultVariants: {\n    variant: 'default'\n  }\n});\n\nconst circleInnerVariants = cva(\n  'border-2 rounded-full block w-[1.125rem] h-[1.125rem] flex justify-center',\n  {\n    variants: {\n      variant: {\n        default: 'border-muted-foreground',\n        success: 'border-green-700 dark:border-green-400',\n        info: 'border-cyan-600 dark:border-cyan-400',\n        attention: 'border-yellow-700 dark:border-yellow-400',\n        destructive: 'border-destructive',\n        warning: 'border-amber-700 dark:border-amber-400',\n        new: 'border-muted-foreground'\n      }\n    },\n    defaultVariants: {\n      variant: 'default'\n    }\n  }\n);\n\nconst circleDotVariants = cva(\n  'block w-[40%] h-[40%] self-center rounded-full',\n  {\n    variants: {\n      variant: {\n        default: 'bg-muted-foreground',\n        success: 'bg-green-700 dark:bg-green-400',\n        info: 'bg-cyan-600 dark:bg-cyan-400',\n        attention: 'bg-yellow-700 dark:bg-yellow-400',\n        destructive: 'bg-destructive',\n        warning: 'bg-amber-700 dark:bg-amber-400',\n        new: 'bg-muted-foreground'\n      }\n    },\n    defaultVariants: {\n      variant: 'default'\n    }\n  }\n);\n\nexport type CircleVariant =\n  | 'default'\n  | 'success'\n  | 'info'\n  | 'attention'\n  | 'destructive'\n  | 'warning'\n  | 'new';\n\nexport interface CircleProps {\n  variant?: CircleVariant;\n  className?: string;\n}\n\nexport function Circle({ variant = 'default', className }: CircleProps) {\n  return (\n    <span className={cn(circleVariants({ variant }), className)}>\n      <span className={circleInnerVariants({ variant })}>\n        <span className={circleDotVariants({ variant })} />\n      </span>\n    </span>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Collapsible.tsx",
    "content": "import { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible';\nimport React from 'react';\n\nfunction Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />;\n}\n\nfunction CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {\n  return (\n    <CollapsiblePrimitive.Trigger data-slot=\"collapsible-trigger\" {...props} />\n  );\n}\n\nfunction CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {\n  return (\n    <CollapsiblePrimitive.Panel data-slot=\"collapsible-content\" {...props} />\n  );\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/ContextMenu.tsx",
    "content": "import { ContextMenu as ContextMenuPrimitive } from '@base-ui/react/context-menu';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { ChevronRightIcon, CheckIcon } from 'lucide-react';\nimport * as React from 'react';\n\nfunction ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) {\n  return <ContextMenuPrimitive.Root data-slot=\"context-menu\" {...props} />;\n}\n\nfunction ContextMenuPortal({ ...props }: ContextMenuPrimitive.Portal.Props) {\n  return (\n    <ContextMenuPrimitive.Portal data-slot=\"context-menu-portal\" {...props} />\n  );\n}\n\nfunction ContextMenuTrigger({\n  className,\n  ...props\n}: ContextMenuPrimitive.Trigger.Props) {\n  return (\n    <ContextMenuPrimitive.Trigger\n      data-slot=\"context-menu-trigger\"\n      className={cn('select-none', className)}\n      {...props}\n    />\n  );\n}\n\nfunction ContextMenuContent({\n  className,\n  align = 'start',\n  alignOffset = 4,\n  side = 'right',\n  sideOffset = 0,\n  ...props\n}: ContextMenuPrimitive.Popup.Props &\n  Pick<\n    ContextMenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <ContextMenuPrimitive.Portal>\n      <ContextMenuPrimitive.Positioner\n        className=\"isolate z-50 outline-none\"\n        align={align}\n        alignOffset={alignOffset}\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <ContextMenuPrimitive.Popup\n          data-slot=\"context-menu-content\"\n          className={cn(\n            'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-36 rounded-md p-1 shadow-md ring-1 duration-100 z-50 max-h-(--available-height) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none',\n            className\n          )}\n          {...props}\n        />\n      </ContextMenuPrimitive.Positioner>\n    </ContextMenuPrimitive.Portal>\n  );\n}\n\nfunction ContextMenuGroup({ ...props }: ContextMenuPrimitive.Group.Props) {\n  return (\n    <ContextMenuPrimitive.Group data-slot=\"context-menu-group\" {...props} />\n  );\n}\n\nfunction ContextMenuLabel({\n  className,\n  inset,\n  ...props\n}: ContextMenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <ContextMenuPrimitive.GroupLabel\n      data-slot=\"context-menu-label\"\n      data-inset={inset}\n      className={cn(\n        'text-muted-foreground px-2 py-1.5 text-xs font-medium data-[inset]:pl-8',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ContextMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: ContextMenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <ContextMenuPrimitive.Item\n      data-slot=\"context-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive focus:*:[svg]:text-accent-foreground gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 group/context-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ContextMenuSub({ ...props }: ContextMenuPrimitive.SubmenuRoot.Props) {\n  return (\n    <ContextMenuPrimitive.SubmenuRoot data-slot=\"context-menu-sub\" {...props} />\n  );\n}\n\nfunction ContextMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: ContextMenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <ContextMenuPrimitive.SubmenuTrigger\n      data-slot=\"context-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </ContextMenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction ContextMenuSubContent({\n  ...props\n}: React.ComponentProps<typeof ContextMenuContent>) {\n  return (\n    <ContextMenuContent\n      data-slot=\"context-menu-sub-content\"\n      className=\"shadow-lg\"\n      side=\"right\"\n      {...props}\n    />\n  );\n}\n\nfunction ContextMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: ContextMenuPrimitive.CheckboxItem.Props) {\n  return (\n    <ContextMenuPrimitive.CheckboxItem\n      data-slot=\"context-menu-checkbox-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"absolute right-2 pointer-events-none\">\n        <ContextMenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </ContextMenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </ContextMenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction ContextMenuRadioGroup({\n  ...props\n}: ContextMenuPrimitive.RadioGroup.Props) {\n  return (\n    <ContextMenuPrimitive.RadioGroup\n      data-slot=\"context-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction ContextMenuRadioItem({\n  className,\n  children,\n  ...props\n}: ContextMenuPrimitive.RadioItem.Props) {\n  return (\n    <ContextMenuPrimitive.RadioItem\n      data-slot=\"context-menu-radio-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"absolute right-2 pointer-events-none\">\n        <ContextMenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </ContextMenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </ContextMenuPrimitive.RadioItem>\n  );\n}\n\nfunction ContextMenuSeparator({\n  className,\n  ...props\n}: ContextMenuPrimitive.Separator.Props) {\n  return (\n    <ContextMenuPrimitive.Separator\n      data-slot=\"context-menu-separator\"\n      className={cn('bg-border -mx-1 my-1 h-px', className)}\n      {...props}\n    />\n  );\n}\n\nfunction ContextMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      data-slot=\"context-menu-shortcut\"\n      className={cn(\n        'text-muted-foreground group-focus/context-menu-item:text-accent-foreground ml-auto text-xs tracking-widest',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  ContextMenu,\n  ContextMenuTrigger,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuCheckboxItem,\n  ContextMenuRadioItem,\n  ContextMenuLabel,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuGroup,\n  ContextMenuPortal,\n  ContextMenuSub,\n  ContextMenuSubContent,\n  ContextMenuSubTrigger,\n  ContextMenuRadioGroup\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Dialog.tsx",
    "content": "import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { Button } from '@components/common/ui/Button.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { XIcon } from 'lucide-react';\nimport * as React from 'react';\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      data-slot=\"dialog-overlay\"\n      className={cn(\n        'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        data-slot=\"dialog-content\"\n        className={cn(\n          'bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-6 rounded-xl p-6 text-sm ring-1 duration-100 sm:max-w-md fixed top-1/2 left-1/2 z-1001 w-full -translate-x-1/2 -translate-y-1/2 outline-none',\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                variant=\"ghost\"\n                className=\"absolute top-4 right-4\"\n                size=\"icon-sm\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      className={cn('gap-2 flex flex-col', className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      className={cn(\n        'gap-2 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',\n        className\n      )}\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      data-slot=\"dialog-title\"\n      className={cn('leading-none font-medium', className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      data-slot=\"dialog-description\"\n      className={cn(\n        'text-muted-foreground *:[a]:hover:text-foreground text-sm *:[a]:underline *:[a]:underline-offset-3',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/DropdownMenu.tsx",
    "content": "import { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { ChevronRightIcon, CheckIcon } from 'lucide-react';\nimport * as React from 'react';\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        className=\"isolate z-50 outline-none\"\n        align={align}\n        alignOffset={alignOffset}\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          data-slot=\"dropdown-menu-content\"\n          className={cn(\n            'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-md p-1 shadow-md ring-1 duration-100 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden',\n            className\n          )}\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<'div'> & {\n  inset?: boolean;\n}) {\n  return (\n    <div\n      data-slot=\"dropdown-menu-label\"\n      data-inset={inset}\n      className={cn(\n        'text-muted-foreground px-2 py-1.5 text-xs font-medium data-inset:pl-8',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      data-slot=\"dropdown-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      data-slot=\"dropdown-menu-sub-content\"\n      className={cn(\n        'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-24 rounded-md p-1 shadow-lg ring-1 duration-100 w-auto',\n        className\n      )}\n      align={align}\n      alignOffset={alignOffset}\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span\n        className=\"absolute right-2 flex items-center justify-center pointer-events-none\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  ...props\n}: MenuPrimitive.RadioItem.Props) {\n  return (\n    <MenuPrimitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    >\n      <span\n        className=\"absolute right-2 flex items-center justify-center pointer-events-none\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      data-slot=\"dropdown-menu-separator\"\n      className={cn('bg-border -mx-1 my-1 h-px', className)}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      data-slot=\"dropdown-menu-shortcut\"\n      className={cn(\n        'text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Empty.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport React from 'react';\n\n\nfunction Empty({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"empty\"\n      className={cn(\n        'gap-4 rounded-lg border-dashed p-12 flex w-full min-w-0 flex-1 flex-col items-center justify-center text-center text-balance',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"empty-header\"\n      className={cn('gap-2 flex max-w-sm flex-col items-center', className)}\n      {...props}\n    />\n  );\n}\n\nconst emptyMediaVariants = cva(\n  'mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'bg-transparent',\n        icon: \"bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6\"\n      }\n    },\n    defaultVariants: {\n      variant: 'default'\n    }\n  }\n);\n\nfunction EmptyMedia({\n  className,\n  variant = 'default',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof emptyMediaVariants>) {\n  return (\n    <div\n      data-slot=\"empty-icon\"\n      data-variant={variant}\n      className={cn(emptyMediaVariants({ variant, className }))}\n      {...props}\n    />\n  );\n}\n\nfunction EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"empty-title\"\n      className={cn('text-lg font-medium tracking-tight', className)}\n      {...props}\n    />\n  );\n}\n\nfunction EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <div\n      data-slot=\"empty-description\"\n      className={cn(\n        'text-sm/relaxed text-muted-foreground [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction EmptyContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"empty-content\"\n      className={cn(\n        'gap-4 text-sm flex w-full max-w-sm min-w-0 flex-col items-center text-balance',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Empty,\n  EmptyHeader,\n  EmptyTitle,\n  EmptyDescription,\n  EmptyContent,\n  EmptyMedia\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Field.tsx",
    "content": "\nimport { Label } from '@components/common/ui/Label.js';\nimport { Separator } from '@components/common/ui/Separator.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport React, { useMemo } from 'react';\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n  return (\n    <fieldset\n      data-slot=\"field-set\"\n      className={cn(\n        'gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldLegend({\n  className,\n  variant = 'legend',\n  ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n  return (\n    <legend\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      className={cn(\n        'mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"field-group\"\n      className={cn(\n        'gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4 group/field-group @container/field-group flex w-full flex-col',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nconst fieldVariants = cva(\n  'data-[invalid=true]:text-destructive gap-3 group/field flex w-full',\n  {\n    variants: {\n      orientation: {\n        vertical: 'flex-col [&>*]:w-full [&>.sr-only]:w-auto',\n        horizontal:\n          'flex-row items-center [&>[data-slot=field-label]]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n        responsive:\n          'flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'\n      }\n    },\n    defaultVariants: {\n      orientation: 'vertical'\n    }\n  }\n);\n\nfunction Field({\n  className,\n  orientation = 'vertical',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"field\"\n      data-orientation={orientation}\n      className={cn(fieldVariants({ orientation }), className)}\n      {...props}\n    />\n  );\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"field-content\"\n      className={cn(\n        'gap-1 group/field-content flex flex-1 flex-col leading-snug',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      data-slot=\"field-label\"\n      className={cn(\n        'has-data-checked:bg-primary/5 has-data-checked:border-primary dark:has-data-checked:bg-primary/10 gap-1 group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-3 group/field-label peer/field-label flex w-fit leading-snug',\n        'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"field-label\"\n      className={cn(\n        'gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <p\n      data-slot=\"field-description\"\n      className={cn(\n        'text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',\n        'last:mt-0 nth-last-2:-mt-1',\n        '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'div'> & {\n  children?: React.ReactNode;\n}) {\n  return (\n    <div\n      data-slot=\"field-separator\"\n      data-content={!!children}\n      className={cn(\n        '-my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2 relative',\n        className\n      )}\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"text-muted-foreground px-2 bg-background relative mx-auto block w-fit\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  );\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<'div'> & {\n  errors?: Array<{ message?: string } | undefined>;\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children;\n    }\n\n    if (!errors?.length) {\n      return null;\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map((error) => [error?.message, error])).values()\n    ];\n\n    if (uniqueErrors?.length == 1) {\n      return uniqueErrors[0]?.message;\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={index}>{error.message}</li>\n        )}\n      </ul>\n    );\n  }, [children, errors]);\n\n  if (!content) {\n    return null;\n  }\n\n  return (\n    <div\n      role=\"alert\"\n      data-slot=\"field-error\"\n      className={cn('text-destructive text-sm font-normal', className)}\n      {...props}\n    >\n      {content}\n    </div>\n  );\n}\n\nexport {\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldContent,\n  FieldTitle\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/HoverCard.tsx",
    "content": "import { PreviewCard as PreviewCardPrimitive } from '@base-ui/react/preview-card';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport React from 'react';\n\nfunction HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {\n  return <PreviewCardPrimitive.Root data-slot=\"hover-card\" {...props} />;\n}\n\nfunction HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {\n  return (\n    <PreviewCardPrimitive.Trigger data-slot=\"hover-card-trigger\" {...props} />\n  );\n}\n\nfunction HoverCardContent({\n  className,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 4,\n  ...props\n}: PreviewCardPrimitive.Popup.Props &\n  Pick<\n    PreviewCardPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <PreviewCardPrimitive.Portal data-slot=\"hover-card-portal\">\n      <PreviewCardPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        side={side}\n        sideOffset={sideOffset}\n        className=\"isolate z-50\"\n      >\n        <PreviewCardPrimitive.Popup\n          data-slot=\"hover-card-content\"\n          className={cn(\n            'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground w-64 rounded-lg p-4 text-sm shadow-md ring-1 duration-100 z-50 origin-(--transform-origin) outline-hidden',\n            className\n          )}\n          {...props}\n        />\n      </PreviewCardPrimitive.Positioner>\n    </PreviewCardPrimitive.Portal>\n  );\n}\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Input.tsx",
    "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(\n  ({ className, type, ...props }, ref) => {\n    return (\n      <InputPrimitive\n        ref={ref}\n        type={type}\n        data-slot=\"input\"\n        className={cn(\n          'dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-9 rounded-md border bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] file:h-7 file:text-sm file:font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',\n          className\n        )}\n        {...props}\n      />\n    );\n  }\n);\nInput.displayName = 'Input';\n\nexport { Input };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/InputGroup.tsx",
    "content": "/* eslint-disable jsx-a11y/click-events-have-key-events */\nimport { Button } from '@components/common/ui/Button.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport { Textarea } from '@components/common/ui/Textarea.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nconst InputGroup = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<'div'>\n>(({ className, ...props }, ref) => {\n  return (\n    <div\n      ref={ref}\n      data-slot=\"input-group\"\n      role=\"group\"\n      className={cn(\n        'border-input dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 h-9 rounded-md border shadow-xs transition-[color,box-shadow] has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot][aria-invalid=true]]:ring-[3px] has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 [[data-slot=combobox-content]_&]:focus-within:border-inherit [[data-slot=combobox-content]_&]:focus-within:ring-0 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto',\n        className\n      )}\n      {...props}\n    />\n  );\n});\nInputGroup.displayName = 'InputGroup';\n\nconst inputGroupAddonVariants = cva(\n  \"text-muted-foreground h-auto gap-2 py-1.5 text-sm font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4 flex cursor-text items-center justify-center select-none\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'pl-2 has-[>button]:ml-[-0.25rem] has-[>kbd]:ml-[-0.15rem] order-first',\n        'inline-end':\n          'pr-2 has-[>button]:mr-[-0.25rem] has-[>kbd]:mr-[-0.15rem] order-last',\n        'block-start':\n          'px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start',\n        'block-end':\n          'px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start'\n      }\n    },\n    defaultVariants: {\n      align: 'inline-start'\n    }\n  }\n);\n\nconst InputGroupAddon = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>\n>(({ className, align = 'inline-start', ...props }, ref) => {\n  return (\n    <div\n      ref={ref}\n      role=\"group\"\n      data-slot=\"input-group-addon\"\n      data-align={align}\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      {...props}\n    />\n  );\n});\nInputGroupAddon.displayName = 'InputGroupAddon';\n\nconst inputGroupButtonVariants = cva(\n  'gap-2 text-sm shadow-none flex items-center',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0'\n      }\n    },\n    defaultVariants: {\n      size: 'xs'\n    }\n  }\n);\n\nconst InputGroupButton = React.forwardRef<\n  HTMLButtonElement,\n  Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n    VariantProps<typeof inputGroupButtonVariants> & {\n      type?: 'button' | 'submit' | 'reset';\n    }\n>(({ className, type = 'button', variant = 'ghost', size = 'xs', ...props }, ref) => {\n  return (\n    <Button\n      ref={ref}\n      type={type}\n      data-size={size}\n      variant={variant}\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      {...props}\n    />\n  );\n});\nInputGroupButton.displayName = 'InputGroupButton';\n\nconst InputGroupText = React.forwardRef<\n  HTMLSpanElement,\n  React.ComponentProps<'span'>\n>(({ className, ...props }, ref) => {\n  return (\n    <span\n      ref={ref}\n      className={cn(\n        \"text-muted-foreground gap-2 text-sm [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n});\nInputGroupText.displayName = 'InputGroupText';\n\nconst InputGroupInput = React.forwardRef<\n  HTMLInputElement,\n  React.ComponentProps<'input'>\n>(({ className, ...props }, ref) => {\n  return (\n    <Input\n      ref={ref}\n      data-slot=\"input-group-control\"\n      className={cn(\n        'rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent flex-1',\n        className\n      )}\n      {...props}\n    />\n  );\n});\nInputGroupInput.displayName = 'InputGroupInput';\n\nconst InputGroupTextarea = React.forwardRef<\n  HTMLTextAreaElement,\n  React.ComponentProps<'textarea'>\n>(({ className, ...props }, ref) => {\n  return (\n    <Textarea\n      ref={ref}\n      data-slot=\"input-group-control\"\n      className={cn(\n        'rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent flex-1 resize-none',\n        className\n      )}\n      {...props}\n    />\n  );\n});\nInputGroupTextarea.displayName = 'InputGroupTextarea';\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Item.tsx",
    "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { Separator } from '@components/common/ui/Separator.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nfunction ItemGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      role=\"list\"\n      data-slot=\"item-group\"\n      className={cn(\n        'gap-4 has-[[data-size=sm]]:gap-2.5 has-[[data-size=xs]]:gap-2 group/item-group flex w-full flex-col',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ItemSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"item-separator\"\n      orientation=\"horizontal\"\n      className={cn('my-2', className)}\n      {...props}\n    />\n  );\n}\n\nconst itemVariants = cva(\n  '[a]:hover:bg-muted rounded-md border text-sm w-full group/item focus-visible:border-ring focus-visible:ring-ring/50 flex items-center flex-wrap outline-none transition-colors duration-100 focus-visible:ring-[3px] [a]:transition-colors',\n  {\n    variants: {\n      variant: {\n        default: 'border-transparent',\n        outline: 'border-border',\n        muted: 'bg-muted/50 border-transparent'\n      },\n      size: {\n        default: 'gap-3.5 px-4 py-3.5',\n        sm: 'gap-2.5 px-3 py-2.5',\n        xs: 'gap-2 px-2.5 py-2 [[data-slot=dropdown-menu-content]_&]:p-0'\n      }\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default'\n    }\n  }\n);\n\nfunction Item({\n  className,\n  variant = 'default',\n  size = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'div'> & VariantProps<typeof itemVariants>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(itemVariants({ variant, size, className }))\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'item',\n      variant,\n      size\n    }\n  });\n}\n\nconst itemMediaVariants = cva(\n  'gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start flex shrink-0 items-center justify-center [&_svg]:pointer-events-none',\n  {\n    variants: {\n      variant: {\n        default: 'bg-transparent',\n        icon: \"[&_svg:not([class*='size-'])]:size-4\",\n        image:\n          'size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover'\n      }\n    },\n    defaultVariants: {\n      variant: 'default'\n    }\n  }\n);\n\nfunction ItemMedia({\n  className,\n  variant = 'default',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>) {\n  return (\n    <div\n      data-slot=\"item-media\"\n      data-variant={variant}\n      className={cn(itemMediaVariants({ variant, className }))}\n      {...props}\n    />\n  );\n}\n\nfunction ItemContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"item-content\"\n      className={cn(\n        'gap-1 group-data-[size=xs]/item:gap-0 flex flex-1 flex-col [&+[data-slot=item-content]]:flex-none',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ItemTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"item-title\"\n      className={cn(\n        'gap-2 text-sm leading-snug font-medium underline-offset-4 line-clamp-1 flex w-fit items-center',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ItemDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <p\n      data-slot=\"item-description\"\n      className={cn(\n        'text-muted-foreground text-left text-sm leading-normal group-data-[size=xs]/item:text-xs [&>a:hover]:text-primary line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ItemActions({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"item-actions\"\n      className={cn('gap-2 flex items-center', className)}\n      {...props}\n    />\n  );\n}\n\nfunction ItemHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"item-header\"\n      className={cn(\n        'gap-2 flex basis-full items-center justify-between',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ItemFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"item-footer\"\n      className={cn(\n        'gap-2 flex basis-full items-center justify-between',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Item,\n  ItemMedia,\n  ItemContent,\n  ItemActions,\n  ItemGroup,\n  ItemSeparator,\n  ItemTitle,\n  ItemDescription,\n  ItemHeader,\n  ItemFooter\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Kbd.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport React from 'react';\n\nfunction Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {\n  return (\n    <kbd\n      data-slot=\"kbd\"\n      className={cn(\n        \"bg-muted text-muted-foreground [[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10 h-5 w-fit min-w-5 gap-1 rounded-sm px-1 font-sans text-xs font-medium [&_svg:not([class*='size-'])]:size-3 pointer-events-none inline-flex items-center justify-center select-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <kbd\n      data-slot=\"kbd-group\"\n      className={cn('gap-1 inline-flex items-center', className)}\n      {...props}\n    />\n  );\n}\n\nexport { Kbd, KbdGroup };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Label.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\n\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      data-slot=\"label\"\n      className={cn(\n        'gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Menubar.tsx",
    "content": "import { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { Menubar as MenubarPrimitive } from '@base-ui/react/menubar';\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuPortal,\n  DropdownMenuRadioGroup,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger\n} from '@components/common/ui/DropdownMenu.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { CheckIcon } from 'lucide-react';\nimport * as React from 'react';\n\nfunction Menubar({ className, ...props }: MenubarPrimitive.Props) {\n  return (\n    <MenubarPrimitive\n      data-slot=\"menubar\"\n      className={cn(\n        'bg-background h-9 gap-1 rounded-md border p-1 shadow-xs flex items-center',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarMenu({ ...props }: React.ComponentProps<typeof DropdownMenu>) {\n  return <DropdownMenu data-slot=\"menubar-menu\" {...props} />;\n}\n\nfunction MenubarGroup({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuGroup>) {\n  return <DropdownMenuGroup data-slot=\"menubar-group\" {...props} />;\n}\n\nfunction MenubarPortal({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPortal>) {\n  return <DropdownMenuPortal data-slot=\"menubar-portal\" {...props} />;\n}\n\nfunction MenubarTrigger({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuTrigger>) {\n  return (\n    <DropdownMenuTrigger\n      data-slot=\"menubar-trigger\"\n      className={cn(\n        'hover:bg-muted aria-expanded:bg-muted rounded-sm px-2 py-1 text-sm font-medium flex items-center outline-hidden select-none',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarContent({\n  className,\n  align = 'start',\n  alignOffset = -4,\n  sideOffset = 8,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      data-slot=\"menubar-content\"\n      align={align}\n      alignOffset={alignOffset}\n      sideOffset={sideOffset}\n      className={cn(\n        'bg-popover text-popover-foreground data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-md p-1 shadow-md ring-1 duration-100',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: React.ComponentProps<typeof DropdownMenuItem>) {\n  return (\n    <DropdownMenuItem\n      data-slot=\"menubar-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg:not([class*='size-'])]:size-4 group/menubar-item\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      data-slot=\"menubar-checkbox-item\"\n      className={cn(\n        'focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-md py-1.5 pr-2 pl-8 text-sm data-disabled:opacity-50 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"left-2 size-4 [&_svg:not([class*='size-'])]:size-4 pointer-events-none absolute flex items-center justify-center\">\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction MenubarRadioGroup({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuRadioGroup>) {\n  return <DropdownMenuRadioGroup data-slot=\"menubar-radio-group\" {...props} />;\n}\n\nfunction MenubarRadioItem({\n  className,\n  children,\n  ...props\n}: MenuPrimitive.RadioItem.Props) {\n  return (\n    <MenuPrimitive.RadioItem\n      data-slot=\"menubar-radio-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-md py-1.5 pr-2 pl-8 text-sm data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"left-2 size-4 [&_svg:not([class*='size-'])]:size-4 pointer-events-none absolute flex items-center justify-center\">\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction MenubarLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuLabel>) {\n  return (\n    <DropdownMenuLabel\n      data-slot=\"menubar-label\"\n      data-inset={inset}\n      className={cn(\n        'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuSeparator>) {\n  return (\n    <DropdownMenuSeparator\n      data-slot=\"menubar-separator\"\n      className={cn('bg-border -mx-1 my-1 h-px', className)}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarShortcut({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuShortcut>) {\n  return (\n    <DropdownMenuShortcut\n      data-slot=\"menubar-shortcut\"\n      className={cn(\n        'text-muted-foreground group-focus/menubar-item:text-accent-foreground text-xs tracking-widest ml-auto',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarSub({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuSub>) {\n  return <DropdownMenuSub data-slot=\"menubar-sub\" {...props} />;\n}\n\nfunction MenubarSubTrigger({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuSubTrigger> & {\n  inset?: boolean;\n}) {\n  return (\n    <DropdownMenuSubTrigger\n      data-slot=\"menubar-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground gap-2 rounded-sm px-2 py-1.5 text-sm data-[inset]:pl-8 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction MenubarSubContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuSubContent>) {\n  return (\n    <DropdownMenuSubContent\n      data-slot=\"menubar-sub-content\"\n      className={cn(\n        'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-32 rounded-md p-1 shadow-lg ring-1 duration-100',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Menubar,\n  MenubarPortal,\n  MenubarMenu,\n  MenubarTrigger,\n  MenubarContent,\n  MenubarGroup,\n  MenubarSeparator,\n  MenubarLabel,\n  MenubarItem,\n  MenubarShortcut,\n  MenubarCheckboxItem,\n  MenubarRadioGroup,\n  MenubarRadioItem,\n  MenubarSub,\n  MenubarSubTrigger,\n  MenubarSubContent\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/NavigationMenu.tsx",
    "content": "import { NavigationMenu as NavigationMenuPrimitive } from '@base-ui/react/navigation-menu';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva } from 'class-variance-authority';\nimport { ChevronDownIcon } from 'lucide-react';\nimport React from 'react';\n\nfunction NavigationMenu({\n  className,\n  children,\n  ...props\n}: NavigationMenuPrimitive.Root.Props) {\n  return (\n    <NavigationMenuPrimitive.Root\n      data-slot=\"navigation-menu\"\n      className={cn(\n        'group/navigation-menu relative flex max-w-max flex-1 items-center justify-center',\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <NavigationMenuPositioner />\n    </NavigationMenuPrimitive.Root>\n  );\n}\n\nfunction NavigationMenuList({\n  className,\n  ...props\n}: NavigationMenuPrimitive.List.Props) {\n  return (\n    <NavigationMenuPrimitive.List\n      data-slot=\"navigation-menu-list\"\n      className={cn(\n        'gap-0 group flex flex-1 list-none items-center justify-center',\n        className\n      )}\n      {...(props as any)}\n    />\n  );\n}\n\nfunction NavigationMenuItem({\n  className,\n  ...props\n}: NavigationMenuPrimitive.Item.Props) {\n  return (\n    <NavigationMenuPrimitive.Item\n      data-slot=\"navigation-menu-item\"\n      className={cn('relative', className)}\n      {...(props as any)}\n    />\n  );\n}\n\nconst navigationMenuTriggerStyle = cva(\n  'bg-background hover:bg-muted focus:bg-muted data-open:hover:bg-muted data-open:focus:bg-muted data-open:bg-muted/50 focus-visible:ring-ring/50 data-popup-open:bg-muted/50 data-popup-open:hover:bg-muted rounded-md px-4 py-2 text-sm font-medium transition-all focus-visible:ring-[3px] focus-visible:outline-1 disabled:opacity-50 group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center disabled:pointer-events-none outline-none'\n);\n\nfunction NavigationMenuTrigger({\n  className,\n  children,\n  ...props\n}: NavigationMenuPrimitive.Trigger.Props) {\n  return (\n    <NavigationMenuPrimitive.Trigger\n      data-slot=\"navigation-menu-trigger\"\n      className={cn(navigationMenuTriggerStyle(), 'group', className)}\n      {...props}\n    >\n      {children}{' '}\n      <ChevronDownIcon\n        className=\"relative top-px ml-1 size-3 transition duration-300 group-data-open/navigation-menu-trigger:rotate-180 group-data-popup-open/navigation-menu-trigger:rotate-180\"\n        aria-hidden=\"true\"\n      />\n    </NavigationMenuPrimitive.Trigger>\n  );\n}\n\nfunction NavigationMenuContent({\n  className,\n  ...props\n}: NavigationMenuPrimitive.Content.Props) {\n  return (\n    <NavigationMenuPrimitive.Content\n      data-slot=\"navigation-menu-content\"\n      className={cn(\n        'data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:ring-foreground/10 p-2 pr-2.5 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:duration-300 h-full w-auto **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction NavigationMenuPositioner({\n  className,\n  side = 'bottom',\n  sideOffset = 8,\n  align = 'start',\n  alignOffset = 0,\n  ...props\n}: NavigationMenuPrimitive.Positioner.Props) {\n  return (\n    <NavigationMenuPrimitive.Portal>\n      <NavigationMenuPrimitive.Positioner\n        side={side}\n        sideOffset={sideOffset}\n        align={align}\n        alignOffset={alignOffset}\n        className={cn(\n          'transition-[top,left,right,bottom] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] data-[side=bottom]:before:-top-2.5 data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0 isolate z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) data-instant:transition-none',\n          className\n        )}\n        {...props}\n      >\n        <NavigationMenuPrimitive.Popup className=\"bg-popover text-popover-foreground ring-foreground/10 rounded-lg shadow ring-1 transition-all ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:scale-90 data-starting-style:opacity-0 xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin)\">\n          <NavigationMenuPrimitive.Viewport className=\"relative size-full overflow-hidden\" />\n        </NavigationMenuPrimitive.Popup>\n      </NavigationMenuPrimitive.Positioner>\n    </NavigationMenuPrimitive.Portal>\n  );\n}\n\nfunction NavigationMenuLink({\n  className,\n  ...props\n}: NavigationMenuPrimitive.Link.Props) {\n  return (\n    <NavigationMenuPrimitive.Link\n      data-slot=\"navigation-menu-link\"\n      className={cn(\n        \"data-[active=true]:focus:bg-muted data-[active=true]:hover:bg-muted data-[active=true]:bg-muted/50 focus-visible:ring-ring/50 hover:bg-muted focus:bg-muted flex items-center gap-1.5 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction NavigationMenuIndicator({\n  className,\n  ...props\n}: NavigationMenuPrimitive.Icon.Props) {\n  return (\n    <NavigationMenuPrimitive.Icon\n      data-slot=\"navigation-menu-indicator\"\n      className={cn(\n        'data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-1 flex h-1.5 items-end justify-center overflow-hidden',\n        className\n      )}\n      {...(props as any)}\n    >\n      <div className=\"bg-border rounded-tl-sm shadow-md relative top-[60%] h-2 w-2 rotate-45\" />\n    </NavigationMenuPrimitive.Icon>\n  );\n}\n\nexport {\n  NavigationMenu,\n  NavigationMenuContent,\n  NavigationMenuIndicator,\n  NavigationMenuItem,\n  NavigationMenuLink,\n  NavigationMenuList,\n  NavigationMenuTrigger,\n  navigationMenuTriggerStyle,\n  NavigationMenuPositioner\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Pagination.tsx",
    "content": "/* eslint-disable jsx-a11y/anchor-has-content */\nimport { Button } from '@components/common/ui/Button.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  MoreHorizontalIcon\n} from 'lucide-react';\nimport * as React from 'react';\n\nfunction Pagination({ className, ...props }: React.ComponentProps<'nav'>) {\n  return (\n    <nav\n      role=\"navigation\"\n      aria-label=\"pagination\"\n      data-slot=\"pagination\"\n      className={cn('mx-auto flex w-full justify-center', className)}\n      {...props}\n    />\n  );\n}\n\nfunction PaginationContent({\n  className,\n  ...props\n}: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      data-slot=\"pagination-content\"\n      className={cn('gap-1 flex items-center', className)}\n      {...props}\n    />\n  );\n}\n\nfunction PaginationItem({ ...props }: React.ComponentProps<'li'>) {\n  return <li data-slot=\"pagination-item\" {...props} />;\n}\n\ntype PaginationLinkProps = {\n  isActive?: boolean;\n} & Pick<React.ComponentProps<typeof Button>, 'size'> &\n  React.ComponentProps<'a'>;\n\nfunction PaginationLink({\n  className,\n  isActive,\n  size = 'icon',\n  ...props\n}: PaginationLinkProps) {\n  return (\n    <Button\n      variant={isActive ? 'outline' : 'ghost'}\n      size={size}\n      className={cn(className)}\n      nativeButton={false}\n      render={\n        <a\n          aria-current={isActive ? 'page' : undefined}\n          data-slot=\"pagination-link\"\n          data-active={isActive}\n          {...props}\n        />\n      }\n    />\n  );\n}\n\nfunction PaginationPrevious({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) {\n  return (\n    <PaginationLink\n      aria-label=\"Go to previous page\"\n      size=\"default\"\n      className={cn('pl-2!', className)}\n      {...props}\n    >\n      <ChevronLeftIcon data-icon=\"inline-start\" />\n      <span className=\"hidden sm:block\">Previous</span>\n    </PaginationLink>\n  );\n}\n\nfunction PaginationNext({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) {\n  return (\n    <PaginationLink\n      aria-label=\"Go to next page\"\n      size=\"default\"\n      className={cn('pr-2!', className)}\n      {...props}\n    >\n      <span className=\"hidden sm:block\">Next</span>\n      <ChevronRightIcon data-icon=\"inline-end\" />\n    </PaginationLink>\n  );\n}\n\nfunction PaginationEllipsis({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      aria-hidden\n      data-slot=\"pagination-ellipsis\"\n      className={cn(\n        \"size-9 items-center justify-center [&_svg:not([class*='size-'])]:size-4 flex items-center justify-center\",\n        className\n      )}\n      {...props}\n    >\n      <MoreHorizontalIcon />\n      <span className=\"sr-only\">More pages</span>\n    </span>\n  );\n}\n\nexport {\n  Pagination,\n  PaginationContent,\n  PaginationEllipsis,\n  PaginationItem,\n  PaginationLink,\n  PaginationNext,\n  PaginationPrevious\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Popover.tsx",
    "content": "import { Popover as PopoverPrimitive } from '@base-ui/react/popover';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\n\nfunction Popover({ ...props }: PopoverPrimitive.Root.Props) {\n  return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {\n  return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverContent({\n  className,\n  align = 'center',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  ...props\n}: PopoverPrimitive.Popup.Props &\n  Pick<\n    PopoverPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        side={side}\n        sideOffset={sideOffset}\n        className=\"isolate z-50\"\n      >\n        <PopoverPrimitive.Popup\n          data-slot=\"popover-content\"\n          className={cn(\n            'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 flex flex-col gap-4 rounded-md p-4 text-sm shadow-md ring-1 duration-100 z-50 w-72 origin-(--transform-origin) outline-hidden',\n            className\n          )}\n          {...props}\n        />\n      </PopoverPrimitive.Positioner>\n    </PopoverPrimitive.Portal>\n  );\n}\n\nfunction PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"popover-header\"\n      className={cn('flex flex-col gap-1 text-sm', className)}\n      {...props}\n    />\n  );\n}\n\nfunction PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {\n  return (\n    <PopoverPrimitive.Title\n      data-slot=\"popover-title\"\n      className={cn('font-medium', className)}\n      {...props}\n    />\n  );\n}\n\nfunction PopoverDescription({\n  className,\n  ...props\n}: PopoverPrimitive.Description.Props) {\n  return (\n    <PopoverPrimitive.Description\n      data-slot=\"popover-description\"\n      className={cn('text-muted-foreground', className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Popover,\n  PopoverContent,\n  PopoverDescription,\n  PopoverHeader,\n  PopoverTitle,\n  PopoverTrigger\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Progress.tsx",
    "content": "import { Progress as ProgressPrimitive } from '@base-ui/react/progress';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport React from 'react';\n\nfunction Progress({\n  className,\n  children,\n  value,\n  ...props\n}: ProgressPrimitive.Root.Props) {\n  return (\n    <ProgressPrimitive.Root\n      value={value}\n      data-slot=\"progress\"\n      className={cn('flex flex-wrap gap-3', className)}\n      {...props}\n    >\n      {children}\n      <ProgressTrack>\n        <ProgressIndicator />\n      </ProgressTrack>\n    </ProgressPrimitive.Root>\n  );\n}\n\nfunction ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {\n  return (\n    <ProgressPrimitive.Track\n      className={cn(\n        'bg-muted h-1.5 rounded-full relative flex w-full items-center overflow-x-hidden',\n        className\n      )}\n      data-slot=\"progress-track\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressIndicator({\n  className,\n  ...props\n}: ProgressPrimitive.Indicator.Props) {\n  return (\n    <ProgressPrimitive.Indicator\n      data-slot=\"progress-indicator\"\n      className={cn('bg-primary h-full transition-all', className)}\n      {...props}\n    />\n  );\n}\n\nfunction ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {\n  return (\n    <ProgressPrimitive.Label\n      className={cn('text-sm font-medium', className)}\n      data-slot=\"progress-label\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {\n  return (\n    <ProgressPrimitive.Value\n      className={cn(\n        'text-muted-foreground ml-auto text-sm tabular-nums',\n        className\n      )}\n      data-slot=\"progress-value\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Progress,\n  ProgressTrack,\n  ProgressIndicator,\n  ProgressLabel,\n  ProgressValue\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/RadioGroup.tsx",
    "content": "import { Radio as RadioPrimitive } from '@base-ui/react/radio';\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { CircleIcon } from 'lucide-react';\nimport React from 'react';\n\nfunction RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {\n  return (\n    <RadioGroupPrimitive\n      data-slot=\"radio-group\"\n      className={cn('grid gap-3 w-full', className)}\n      {...props}\n    />\n  );\n}\n\nfunction RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {\n  return (\n    <RadioPrimitive.Root\n      data-slot=\"radio-group-item\"\n      className={cn(\n        'border-input text-primary dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 flex size-4 rounded-full shadow-xs focus-visible:ring-[3px] aria-invalid:ring-[3px] group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50',\n        className\n      )}\n      {...props}\n    >\n      <RadioPrimitive.Indicator\n        data-slot=\"radio-group-indicator\"\n        className=\"group-aria-invalid/radio-group-item:text-destructive text-primary flex size-4 items-center justify-center\"\n      >\n        <CircleIcon className=\"absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-current\" />\n      </RadioPrimitive.Indicator>\n    </RadioPrimitive.Root>\n  );\n}\n\nexport { RadioGroup, RadioGroupItem };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/ScrollArea.tsx",
    "content": "import { ScrollArea as ScrollAreaPrimitive } from '@base-ui/react/scroll-area';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\n\nfunction ScrollArea({\n  className,\n  children,\n  ...props\n}: ScrollAreaPrimitive.Root.Props) {\n  return (\n    <ScrollAreaPrimitive.Root\n      data-slot=\"scroll-area\"\n      className={cn('relative', className)}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Viewport\n        data-slot=\"scroll-area-viewport\"\n        className=\"focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1\"\n      >\n        {children}\n      </ScrollAreaPrimitive.Viewport>\n      <ScrollBar />\n      <ScrollAreaPrimitive.Corner />\n    </ScrollAreaPrimitive.Root>\n  );\n}\n\nfunction ScrollBar({\n  className,\n  orientation = 'vertical',\n  ...props\n}: ScrollAreaPrimitive.Scrollbar.Props) {\n  return (\n    <ScrollAreaPrimitive.Scrollbar\n      data-slot=\"scroll-area-scrollbar\"\n      data-orientation={orientation}\n      orientation={orientation}\n      className={cn(\n        'data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent flex touch-none p-px transition-colors select-none',\n        className\n      )}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Thumb\n        data-slot=\"scroll-area-thumb\"\n        className=\"rounded-full bg-border relative flex-1\"\n      />\n    </ScrollAreaPrimitive.Scrollbar>\n  );\n}\n\nexport { ScrollArea, ScrollBar };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Select.tsx",
    "content": "import { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { ChevronDownIcon, CheckIcon, ChevronUpIcon } from 'lucide-react';\nimport * as React from 'react';\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      data-slot=\"select-group\"\n      className={cn('scroll-my-1 p-1', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      data-slot=\"select-value\"\n      className={cn('flex flex-1 text-left', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      data-slot=\"select-trigger\"\n      data-size={size}\n      className={cn(\n        \"border-input data-[placeholder]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-md border bg-transparent py-2 pr-2 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] focus-visible:ring-[3px] aria-invalid:ring-[3px] data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"text-muted-foreground size-4 pointer-events-none\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        side={side}\n        sideOffset={sideOffset}\n        align={align}\n        alignOffset={alignOffset}\n        alignItemWithTrigger={alignItemWithTrigger}\n        className=\"isolate z-5000\"\n      >\n        <SelectPrimitive.Popup\n          data-slot=\"select-content\"\n          className={cn(\n            'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-md shadow-md ring-1 duration-100 relative isolate z-5000 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto',\n            className\n          )}\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      data-slot=\"select-label\"\n      className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      data-slot=\"select-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 gap-2 shrink-0 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      data-slot=\"select-separator\"\n      className={cn('bg-border -mx-1 my-1 h-px pointer-events-none', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      data-slot=\"select-scroll-up-button\"\n      className={cn(\n        \"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 top-0 w-full\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      data-slot=\"select-scroll-down-button\"\n      className={cn(\n        \"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 bottom-0 w-full\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Separator.tsx",
    "content": "import { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport React from 'react';\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      data-slot=\"separator\"\n      orientation={orientation}\n      className={cn(\n        'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Sheet.tsx",
    "content": "import { Dialog as SheetPrimitive } from '@base-ui/react/dialog';\nimport { Button } from '@components/common/ui/Button.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { XIcon } from 'lucide-react';\nimport * as React from 'react';\n\nfunction Sheet({ ...props }: SheetPrimitive.Root.Props) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {\n  return (\n    <SheetPrimitive.Backdrop\n      data-slot=\"sheet-overlay\"\n      className={cn(\n        'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: SheetPrimitive.Popup.Props & {\n  side?: 'top' | 'right' | 'bottom' | 'left';\n  showCloseButton?: boolean;\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Popup\n        data-slot=\"sheet-content\"\n        data-side={side}\n        className={cn(\n          'bg-background data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col gap-4 bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close\n            data-slot=\"sheet-close\"\n            render={\n              <Button\n                variant=\"ghost\"\n                className=\"absolute top-4 right-4\"\n                size=\"icon-sm\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Popup>\n    </SheetPortal>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sheet-header\"\n      className={cn('gap-1.5 p-4 flex flex-col', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sheet-footer\"\n      className={cn('gap-2 p-4 mt-auto flex flex-col', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\n  return (\n    <SheetPrimitive.Title\n      data-slot=\"sheet-title\"\n      className={cn('text-foreground font-medium', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: SheetPrimitive.Description.Props) {\n  return (\n    <SheetPrimitive.Description\n      data-slot=\"sheet-description\"\n      className={cn('text-muted-foreground text-sm', className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Sidebar.tsx",
    "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { Button } from '@components/common/ui/Button.js';\nimport { useIsMobile } from '@components/common/ui/hooks/useIsMobile.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport { Separator } from '@components/common/ui/Separator.js';\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle\n} from '@components/common/ui/Sheet.js';\nimport { Skeleton } from '@components/common/ui/Skeleton.js';\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger\n} from '@components/common/ui/Tooltip.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { PanelLeftIcon } from 'lucide-react';\nimport * as React from 'react';\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_MOBILE = '18rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.');\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen, setOpenMobile]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed';\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar\n    }),\n    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <div\n        data-slot=\"sidebar-wrapper\"\n        style={\n          {\n            '--sidebar-width': SIDEBAR_WIDTH,\n            '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n            ...style\n          } as React.CSSProperties\n        }\n        className={cn(\n          'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full',\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offExamples',\n  className,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right';\n  variant?: 'sidebar' | 'floating' | 'inset';\n  collapsible?: 'offExamples' | 'icon' | 'none';\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        data-slot=\"sidebar\"\n        className={cn(\n          'bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col',\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n        <SheetContent\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          data-mobile=\"true\"\n          className=\"bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden\"\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE\n            } as React.CSSProperties\n          }\n          side={side}\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer text-sidebar-foreground hidden md:block\"\n      data-state={state}\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-variant={variant}\n      data-side={side}\n      data-slot=\"sidebar\"\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        data-slot=\"sidebar-gap\"\n        className={cn(\n          'transition-[width] duration-200 ease-linear relative w-(--sidebar-width) bg-transparent',\n          'group-data-[collapsible=offExamples]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'\n        )}\n      />\n      <div\n        data-slot=\"sidebar-container\"\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',\n          side === 'left'\n            ? 'left-0 group-data-[collapsible=offExamples]:left-[calc(var(--sidebar-width)*-1)]'\n            : 'right-0 group-data-[collapsible=offExamples]:right-[calc(var(--sidebar-width)*-1)]',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className\n        )}\n        {...props}\n      >\n        <div\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n          className=\"bg-sidebar group-data-[variant=floating]:ring-sidebar-border group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 flex size-full flex-col\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      variant=\"ghost\"\n      size=\"icon-sm\"\n      className={cn(className)}\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      aria-label=\"Toggle Sidebar\"\n      tabIndex={-1}\n      onClick={toggleSidebar}\n      title=\"Toggle Sidebar\"\n      className={cn(\n        'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'hover:group-data-[collapsible=offExamples]:bg-sidebar group-data-[collapsible=offExamples]:translate-x-0 group-data-[collapsible=offExamples]:after:left-full',\n        '[[data-side=left][data-collapsible=offExamples]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offExamples]_&]:-left-2',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      data-slot=\"sidebar-inset\"\n      className={cn(\n        'bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 relative flex w-full flex-1 flex-col',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      data-slot=\"sidebar-input\"\n      data-sidebar=\"input\"\n      className={cn('bg-background h-8 w-full shadow-none', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-header\"\n      data-sidebar=\"header\"\n      className={cn('gap-2 p-2 flex flex-col', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-footer\"\n      data-sidebar=\"footer\"\n      className={cn('gap-2 p-2 flex flex-col', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"sidebar-separator\"\n      data-sidebar=\"separator\"\n      className={cn('bg-sidebar-border mx-2 w-auto', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-content\"\n      data-sidebar=\"content\"\n      className={cn(\n        'no-scrollbar gap-2 flex min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-group\"\n      data-sidebar=\"group\"\n      className={cn('p-2 relative flex w-full min-w-0 flex-col', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'> & React.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'text-sidebar-foreground/70 ring-sidebar-ring h-8 rounded-md px-2 text-xs font-medium transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 flex shrink-0 items-center outline-hidden [&>svg]:shrink-0',\n          className\n        )\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-label',\n      sidebar: 'group-label'\n    }\n  });\n}\n\nfunction SidebarGroupAction({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'button'> & React.ComponentProps<'button'>) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 w-5 rounded-md p-0 focus-visible:ring-2 [&>svg]:size-4 flex aspect-square items-center justify-center outline-hidden transition-transform [&>svg]:shrink-0 after:absolute after:-inset-2 md:after:hidden group-data-[collapsible=icon]:hidden',\n          className\n        )\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-action',\n      sidebar: 'group-action'\n    }\n  });\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-group-content\"\n      data-sidebar=\"group-content\"\n      className={cn('text-sm w-full', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu\"\n      data-sidebar=\"menu\"\n      className={cn('gap-1 flex w-full min-w-0 flex-col', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-item\"\n      data-sidebar=\"menu-item\"\n      className={cn('group/menu-item relative', className)}\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          'bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]'\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!'\n      }\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default'\n    }\n  }\n);\n\nfunction SidebarMenuButton({\n  render,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    isActive?: boolean;\n    tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n  } & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const { isMobile, state } = useSidebar();\n  const comp = useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(sidebarMenuButtonVariants({ variant, size }), className)\n      },\n      props\n    ),\n    render: !tooltip ? render : TooltipTrigger,\n    state: {\n      slot: 'sidebar-menu-button',\n      sidebar: 'menu-button',\n      size,\n      active: isActive\n    }\n  });\n\n  if (!tooltip) {\n    return comp;\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip\n    };\n  }\n\n  return (\n    <Tooltip>\n      {comp}\n      <TooltipContent\n        side=\"right\"\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  render,\n  showOnHover = false,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    showOnHover?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 aspect-square w-5 rounded-md p-0 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 focus-visible:ring-2 [&>svg]:size-4 flex items-center justify-center outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 md:after:hidden [&>svg]:shrink-0',\n          showOnHover &&\n            'peer-data-active/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-open:opacity-100 md:opacity-0',\n          className\n        )\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-action',\n      sidebar: 'menu-action'\n    }\n  });\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-menu-badge\"\n      data-sidebar=\"menu-badge\"\n      className={cn(\n        'text-sidebar-foreground peer-hover/menu-button:text-sidebar-accent-foreground peer-data-active/menu-button:text-sidebar-accent-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 rounded-md px-1 text-xs font-medium peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 flex items-center justify-center tabular-nums select-none group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean;\n}) {\n  // Random width between 50 to 90%.\n  const [width] = React.useState(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  });\n\n  return (\n    <div\n      data-slot=\"sidebar-menu-skeleton\"\n      data-sidebar=\"menu-skeleton\"\n      className={cn('h-8 gap-2 rounded-md px-2 flex items-center', className)}\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu-sub\"\n      data-sidebar=\"menu-sub\"\n      className={cn(\n        'border-sidebar-border mx-3.5 translate-x-px gap-1 border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden flex min-w-0 flex-col',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-sub-item\"\n      data-sidebar=\"menu-sub-item\"\n      className={cn('group/menu-sub-item relative', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  render,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: useRender.ComponentProps<'a'> &\n  React.ComponentProps<'a'> & {\n    size?: 'sm' | 'md';\n    isActive?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'a',\n    props: mergeProps<'a'>(\n      {\n        className: cn(\n          'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground h-7 gap-2 rounded-md px-2 focus-visible:ring-2 data-[size=md]:text-sm data-[size=sm]:text-xs [&>svg]:size-4 flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0',\n          className\n        )\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-sub-button',\n      sidebar: 'menu-sub-button',\n      size,\n      active: isActive\n    }\n  });\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Skeleton.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport React from 'react';\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"skeleton\"\n      className={cn('bg-muted rounded-md animate-pulse', className)}\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Slider.tsx",
    "content": "import { Slider as SliderPrimitive } from '@base-ui/react/slider';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\n\nfunction Slider({\n  className,\n  defaultValue,\n  value,\n  min = 0,\n  max = 100,\n  ...props\n}: SliderPrimitive.Root.Props) {\n  const _values = React.useMemo(\n    () =>\n      Array.isArray(value)\n        ? value\n        : Array.isArray(defaultValue)\n        ? defaultValue\n        : [min, max],\n    [value, defaultValue, min, max]\n  );\n\n  return (\n    <SliderPrimitive.Root\n      className=\"data-horizontal:w-full data-vertical:h-full\"\n      data-slot=\"slider\"\n      defaultValue={defaultValue}\n      value={value}\n      min={min}\n      max={max}\n      thumbAlignment=\"edge\"\n      {...props}\n    >\n      <SliderPrimitive.Control\n        className={cn(\n          'data-vertical:min-h-40 relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:w-auto data-vertical:flex-col',\n          className\n        )}\n      >\n        <SliderPrimitive.Track\n          data-slot=\"slider-track\"\n          className=\"bg-muted rounded-full data-horizontal:h-1.5 data-horizontal:w-full data-vertical:h-full data-vertical:w-1.5 relative overflow-hidden select-none\"\n        >\n          <SliderPrimitive.Indicator\n            data-slot=\"slider-range\"\n            className=\"bg-primary select-none data-horizontal:h-full data-vertical:w-full\"\n          />\n        </SliderPrimitive.Track>\n        {Array.from({ length: _values.length }, (_, index) => (\n          <SliderPrimitive.Thumb\n            data-slot=\"slider-thumb\"\n            key={index}\n            className=\"border-primary ring-ring/50 size-4 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden block shrink-0 select-none disabled:pointer-events-none disabled:opacity-50\"\n          />\n        ))}\n      </SliderPrimitive.Control>\n    </SliderPrimitive.Root>\n  );\n}\n\nexport { Slider };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Spinner.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport { Loader2Icon } from 'lucide-react';\nimport React from 'react';\n\nfunction Spinner({ className, ...props }: React.ComponentProps<'svg'>) {\n  return (\n    <Loader2Icon\n      role=\"status\"\n      aria-label=\"Loading\"\n      className={cn('size-4 animate-spin', className)}\n      {...props}\n    />\n  );\n}\n\nexport { Spinner };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Switch.tsx",
    "content": "import { Switch as SwitchPrimitive } from '@base-ui/react/switch';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport React from 'react';\n\nfunction Switch({\n  className,\n  size = 'default',\n  ...props\n}: SwitchPrimitive.Root.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SwitchPrimitive.Root\n      data-slot=\"switch\"\n      data-size={size}\n      className={cn(\n        'data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 shrink-0 rounded-full border border-transparent shadow-xs focus-visible:ring-[3px] aria-invalid:ring-[3px] data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] peer group/switch relative inline-flex items-center transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 data-disabled:cursor-not-allowed data-disabled:opacity-50',\n        className\n      )}\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        data-slot=\"switch-thumb\"\n        className=\"bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground rounded-full group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 pointer-events-none block ring-0 transition-transform\"\n      />\n    </SwitchPrimitive.Root>\n  );\n}\n\nexport { Switch };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Table.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\n\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\n  return (\n    <div data-slot=\"table-container\" className=\"relative w-full\">\n      <table\n        data-slot=\"table\"\n        className={cn('w-full caption-bottom text-sm', className)}\n        {...props}\n      />\n    </div>\n  );\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n  return (\n    <thead\n      data-slot=\"table-header\"\n      className={cn('[&_tr]:border-b', className)}\n      {...props}\n    />\n  );\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n  return (\n    <tbody\n      data-slot=\"table-body\"\n      className={cn('[&_tr:last-child]:border-0', className)}\n      {...props}\n    />\n  );\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n  return (\n    <tfoot\n      data-slot=\"table-footer\"\n      className={cn(\n        'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n  return (\n    <tr\n      data-slot=\"table-row\"\n      className={cn(\n        'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors border-border',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n  return (\n    <th\n      data-slot=\"table-head\"\n      className={cn(\n        'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n  return (\n    <td\n      data-slot=\"table-cell\"\n      className={cn(\n        'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<'caption'>) {\n  return (\n    <caption\n      data-slot=\"table-caption\"\n      className={cn('text-muted-foreground mt-4 text-sm', className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption\n};\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Tabs.tsx",
    "content": "import { Tabs as TabsPrimitive } from '@base-ui/react/tabs';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport React from 'react';\n\nfunction Tabs({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: TabsPrimitive.Root.Props) {\n  return (\n    <TabsPrimitive.Root\n      data-slot=\"tabs\"\n      data-orientation={orientation}\n      className={cn(\n        'gap-2 group/tabs flex data-[orientation=horizontal]:flex-col',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nconst tabsListVariants = cva(\n  'rounded-lg p-[3px] group-data-horizontal/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col',\n  {\n    variants: {\n      variant: {\n        default: 'bg-muted',\n        line: 'gap-1 bg-transparent'\n      }\n    },\n    defaultVariants: {\n      variant: 'default'\n    }\n  }\n);\n\nfunction TabsList({\n  className,\n  variant = 'default',\n  ...props\n}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {\n  return (\n    <TabsPrimitive.List\n      data-slot=\"tabs-list\"\n      data-variant={variant}\n      className={cn(tabsListVariants({ variant }), className)}\n      {...props}\n    />\n  );\n}\n\nfunction TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {\n  return (\n    <TabsPrimitive.Tab\n      data-slot=\"tabs-trigger\"\n      className={cn(\n        \"gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        'group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent',\n        'data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground',\n        'after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {\n  return (\n    <TabsPrimitive.Panel\n      data-slot=\"tabs-content\"\n      className={cn('text-sm flex-1 outline-none', className)}\n      {...props}\n    />\n  );\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Textarea.tsx",
    "content": "import { cn } from '@evershop/evershop/lib/util/cn';\nimport * as React from 'react';\n\nconst Textarea = React.forwardRef<\n  HTMLTextAreaElement,\n  React.ComponentProps<'textarea'>\n>(({ className, ...props }, ref) => {\n  return (\n    <textarea\n      ref={ref}\n      data-slot=\"textarea\"\n      className={cn(\n        'border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50',\n        className\n      )}\n      {...props}\n    />\n  );\n});\nTextarea.displayName = 'Textarea';\n\nexport { Textarea };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Toggle.tsx",
    "content": "import { Toggle as TogglePrimitive } from '@base-ui/react/toggle';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport React from 'react';\n\nconst toggleVariants = cva(\n  \"hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive gap-1 rounded-md text-sm font-medium transition-[color,box-shadow] [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-muted inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-transparent',\n        outline: 'border-input hover:bg-muted border bg-transparent shadow-xs'\n      },\n      size: {\n        default: 'h-9 min-w-9 px-2',\n        sm: 'h-8 min-w-8 px-1.5',\n        lg: 'h-10 min-w-10 px-2.5'\n      }\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default'\n    }\n  }\n);\n\nfunction Toggle({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {\n  return (\n    <TogglePrimitive\n      data-slot=\"toggle\"\n      className={cn(toggleVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Toggle, toggleVariants };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/ToggleGroup.tsx",
    "content": "import { Toggle as TogglePrimitive } from '@base-ui/react/toggle';\nimport { ToggleGroup as ToggleGroupPrimitive } from '@base-ui/react/toggle-group';\nimport { toggleVariants } from '@components/common/ui/Toggle.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nconst ToggleGroupContext = React.createContext<\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number;\n    orientation?: 'horizontal' | 'vertical';\n  }\n>({\n  size: 'default',\n  variant: 'default',\n  spacing: 0,\n  orientation: 'horizontal'\n});\n\nfunction ToggleGroup({\n  className,\n  variant,\n  size,\n  spacing = 0,\n  orientation = 'horizontal',\n  children,\n  ...props\n}: ToggleGroupPrimitive.Props &\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number;\n    orientation?: 'horizontal' | 'vertical';\n  }) {\n  return (\n    <ToggleGroupPrimitive\n      data-slot=\"toggle-group\"\n      data-variant={variant}\n      data-size={size}\n      data-spacing={spacing}\n      data-orientation={orientation}\n      style={{ '--gap': spacing } as React.CSSProperties}\n      className={cn(\n        'rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch',\n        className\n      )}\n      {...props}\n    >\n      <ToggleGroupContext.Provider\n        value={{ variant, size, spacing, orientation }}\n      >\n        {children}\n      </ToggleGroupContext.Provider>\n    </ToggleGroupPrimitive>\n  );\n}\n\nfunction ToggleGroupItem({\n  className,\n  children,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {\n  const context = React.useContext(ToggleGroupContext);\n\n  return (\n    <TogglePrimitive\n      data-slot=\"toggle-group-item\"\n      data-variant={context.variant || variant}\n      data-size={context.size || size}\n      data-spacing={context.spacing}\n      className={cn(\n        'data-[state=on]:bg-muted group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md shrink-0 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t',\n        toggleVariants({\n          variant: context.variant || variant,\n          size: context.size || size\n        }),\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </TogglePrimitive>\n  );\n}\n\nexport { ToggleGroup, ToggleGroupItem };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/Tooltip.tsx",
    "content": "import { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport React from 'react';\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return (\n    <TooltipProvider>\n      <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n    </TooltipProvider>\n  );\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        side={side}\n        sideOffset={sideOffset}\n        className=\"isolate z-50\"\n      >\n        <TooltipPrimitive.Popup\n          data-slot=\"tooltip-content\"\n          className={cn(\n            'data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 rounded-md px-3 py-1.5 text-xs bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin)',\n            className\n          )}\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground z-50 data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
  },
  {
    "path": "packages/evershop/src/components/common/ui/hooks/useIsMobile.tsx",
    "content": "import * as React from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(\n    undefined\n  );\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener('change', onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener('change', onChange);\n  }, []);\n\n  return !!isMobile;\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/Coupon.tsx",
    "content": "import {\n  useCartDispatch,\n  useCartState\n} from '@components/frontStore/cart/CartContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useState, useCallback } from 'react';\n\nexport interface CouponState {\n  isLoading: boolean;\n  error: string | null;\n  appliedCoupon: string | null;\n  canApplyCoupon: boolean;\n  canRemoveCoupon: boolean;\n  hasActiveCoupon: boolean;\n}\n\nexport interface CouponActions {\n  applyCoupon: (code: string) => Promise<void>;\n  removeCoupon: () => Promise<void>;\n  clearError: () => void;\n}\n\nexport interface CouponProps {\n  onApplySuccess?: (couponCode: string) => void;\n  onRemoveSuccess?: () => void;\n  onError?: (error: string) => void;\n  children: (state: CouponState, actions: CouponActions) => React.ReactNode;\n}\n\nexport const Coupon: React.FC<CouponProps> = ({\n  onApplySuccess,\n  onRemoveSuccess,\n  onError,\n  children\n}) => {\n  const cartDispatch = useCartDispatch();\n  const cartState = useCartState();\n\n  const [localError, setLocalError] = useState<string | null>(null);\n\n  const appliedCoupon = cartState.data?.coupon || null;\n  const hasActiveCoupon = !!appliedCoupon && appliedCoupon.trim() !== '';\n\n  const canApplyCoupon = !!cartState.data && !hasActiveCoupon;\n  const canRemoveCoupon = !!cartState.data && hasActiveCoupon;\n\n  const isLoading = cartState.loading;\n\n  const clearError = useCallback(() => {\n    setLocalError(null);\n    cartDispatch.clearError();\n  }, [cartDispatch]);\n\n  const applyCoupon = useCallback(\n    async (code: string) => {\n      if (!canApplyCoupon || !code.trim()) {\n        const errorMsg = !cartState.data\n          ? _('Cart is not initialized')\n          : hasActiveCoupon\n          ? _('A coupon is already applied')\n          : _('Please enter a coupon code');\n\n        setLocalError(errorMsg);\n        onError?.(errorMsg);\n        return;\n      }\n\n      try {\n        setLocalError(null);\n        cartDispatch.clearError();\n        await cartDispatch.applyCoupon(code.trim());\n        onApplySuccess?.(code.trim());\n      } catch (error) {\n        const errorMessage =\n          error instanceof Error ? error.message : _('Failed to apply coupon');\n        setLocalError(errorMessage);\n        onError?.(errorMessage);\n      }\n    },\n    [\n      canApplyCoupon,\n      cartState.data,\n      hasActiveCoupon,\n      cartDispatch,\n      onApplySuccess,\n      onError\n    ]\n  );\n\n  const removeCoupon = useCallback(async () => {\n    if (!canRemoveCoupon) {\n      const errorMsg = !cartState.data\n        ? _('Cart is not initialized')\n        : _('No coupon to remove');\n\n      setLocalError(errorMsg);\n      onError?.(errorMsg);\n      return;\n    }\n\n    try {\n      setLocalError(null);\n      cartDispatch.clearError();\n      await cartDispatch.removeCoupon();\n\n      onRemoveSuccess?.();\n    } catch (error) {\n      const errorMessage =\n        error instanceof Error ? error.message : _('Failed to remove coupon');\n      setLocalError(errorMessage);\n      onError?.(errorMessage);\n    }\n  }, [canRemoveCoupon, cartState.data, cartDispatch, onRemoveSuccess, onError]);\n\n  const state: CouponState = {\n    isLoading,\n    error: localError || cartState.data?.error || null,\n    appliedCoupon,\n    canApplyCoupon,\n    canRemoveCoupon,\n    hasActiveCoupon\n  };\n\n  const actions: CouponActions = {\n    applyCoupon,\n    removeCoupon,\n    clearError\n  };\n\n  return <>{children(state, actions)}</>;\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/CouponForm.tsx",
    "content": "import { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Coupon,\n  CouponActions,\n  CouponState\n} from '@components/frontStore/Coupon.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\nimport { toast } from 'react-toastify';\n\nexport function CouponForm() {\n  const form = useForm<{ coupon: string }>();\n  const coupon = form.watch('coupon');\n  return (\n    <Coupon\n      onApplySuccess={() => {\n        toast.success(_('Coupon applied successfully!'));\n      }}\n      onError={() => {\n        toast.error(_('Invalid coupon'));\n      }}\n      onRemoveSuccess={() => {\n        toast.success(_('Coupon removed successfully!'));\n      }}\n    >\n      {(state: CouponState, actions: CouponActions) => (\n        <div className=\"coupon-form\">\n          <Form form={form} method=\"POST\" submitBtn={false}>\n            <div className=\"flex justify-between gap-3\">\n              <div className=\"w-4/5\">\n                <InputField\n                  name=\"coupon\"\n                  required\n                  validation={{\n                    required: {\n                      value: true,\n                      message: _('Coupon code is required')\n                    }\n                  }}\n                  defaultValue={state.appliedCoupon || ''}\n                  disabled={!!state.appliedCoupon}\n                  placeholder={_('Enter coupon code')}\n                  wrapperClassName=\"mb-0 form-field\"\n                />\n              </div>\n              <div className=\"col-span-1\">\n                <Button\n                  isLoading={state.isLoading}\n                  onClick={async () => {\n                    if (state.appliedCoupon) {\n                      await actions.removeCoupon();\n                    } else {\n                      const isValid = await form.trigger();\n                      if (isValid) {\n                        actions.applyCoupon(coupon);\n                      }\n                    }\n                  }}\n                  variant={state.appliedCoupon ? 'destructive' : 'default'}\n                >\n                  {state.appliedCoupon ? _('Remove') : _('Apply')}\n                </Button>\n              </div>\n            </div>\n          </Form>\n        </div>\n      )}\n    </Coupon>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/Footer.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport React from 'react';\n\ninterface FooterProps {\n  copyRight: string;\n}\n\nexport function Footer({ copyRight }: FooterProps) {\n  return (\n    <footer className=\"footer bg-gray-100 mt-24 pt-2.5 pb-2.5 border-t border-gray-300\">\n      <Area id=\"footerTop\" className=\"footer__top\" />\n      <div className=\"footer__middle flex justify-between items-center\">\n        <Area id=\"footerMiddleLeft\" className=\"footer__middle__left\" />\n        <Area id=\"footerMiddleCenter\" className=\"footer__middle__center\" />\n        <Area id=\"footerMiddleRight\" className=\"footer__middle__right\" />\n      </div>\n      <Area\n        id=\"footerBottom\"\n        className=\"footer__bottom\"\n        coreComponents={[\n          {\n            component: {\n              default: (\n                <div className=\"page-width grid grid-cols-1 md:grid-cols-2 gap-5 justify-between\">\n                  <div>\n                    <div className=\"card-icons flex justify-center space-x-2 md:justify-start\">\n                      <div>\n                        <svg\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                          width=\"38\"\n                          height=\"24\"\n                          aria-labelledby=\"pi-visa\"\n                          viewBox=\"0 0 38 24\"\n                          className=\"h-10\"\n                        >\n                          <path\n                            d=\"M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z\"\n                            opacity=\"0.07\"\n                          />\n                          <path\n                            fill=\"#fff\"\n                            d=\"M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32\"\n                          />\n                          <path\n                            fill=\"#142688\"\n                            d=\"M28.3 10.1H28c-.4 1-.7 1.5-1 3h1.9c-.3-1.5-.3-2.2-.6-3zm2.9 5.9h-1.7c-.1 0-.1 0-.2-.1l-.2-.9-.1-.2h-2.4c-.1 0-.2 0-.2.2l-.3.9c0 .1-.1.1-.1.1h-2.1l.2-.5L27 8.7c0-.5.3-.7.8-.7h1.5c.1 0 .2 0 .2.2l1.4 6.5c.1.4.2.7.2 1.1.1.1.1.1.1.2zm-13.4-.3l.4-1.8c.1 0 .2.1.2.1.7.3 1.4.5 2.1.4.2 0 .5-.1.7-.2.5-.2.5-.7.1-1.1-.2-.2-.5-.3-.8-.5-.4-.2-.8-.4-1.1-.7-1.2-1-.8-2.4-.1-3.1.6-.4.9-.8 1.7-.8 1.2 0 2.5 0 3.1.2h.1c-.1.6-.2 1.1-.4 1.7-.5-.2-1-.4-1.5-.4-.3 0-.6 0-.9.1-.2 0-.3.1-.4.2-.2.2-.2.5 0 .7l.5.4c.4.2.8.4 1.1.6.5.3 1 .8 1.1 1.4.2.9-.1 1.7-.9 2.3-.5.4-.7.6-1.4.6-1.4 0-2.5.1-3.4-.2-.1.2-.1.2-.2.1zm-3.5.3c.1-.7.1-.7.2-1 .5-2.2 1-4.5 1.4-6.7.1-.2.1-.3.3-.3H18c-.2 1.2-.4 2.1-.7 3.2-.3 1.5-.6 3-1 4.5 0 .2-.1.2-.3.2M5 8.2c0-.1.2-.2.3-.2h3.4c.5 0 .9.3 1 .8l.9 4.4c0 .1 0 .1.1.2 0-.1.1-.1.1-.1l2.1-5.1c-.1-.1 0-.2.1-.2h2.1c0 .1 0 .1-.1.2l-3.1 7.3c-.1.2-.1.3-.2.4-.1.1-.3 0-.5 0H9.7c-.1 0-.2 0-.2-.2L7.9 9.5c-.2-.2-.5-.5-.9-.6-.6-.3-1.7-.5-1.9-.5L5 8.2z\"\n                          />\n                        </svg>\n                      </div>\n                      <div>\n                        <svg\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                          width=\"38\"\n                          height=\"24\"\n                          aria-labelledby=\"pi-master\"\n                          viewBox=\"0 0 38 24\"\n                          className=\"h-10\"\n                        >\n                          <path\n                            d=\"M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z\"\n                            opacity=\"0.07\"\n                          />\n                          <path\n                            fill=\"#fff\"\n                            d=\"M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32\"\n                          />\n                          <circle cx=\"15\" cy=\"12\" r=\"7\" fill=\"#EB001B\" />\n                          <circle cx=\"23\" cy=\"12\" r=\"7\" fill=\"#F79E1B\" />\n                          <path\n                            fill=\"#FF5F00\"\n                            d=\"M22 12c0-2.4-1.2-4.5-3-5.7-1.8 1.3-3 3.4-3 5.7s1.2 4.5 3 5.7c1.8-1.2 3-3.3 3-5.7z\"\n                          />\n                        </svg>\n                      </div>\n                      <div>\n                        <svg\n                          viewBox=\"0 0 38 24\"\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                          width=\"38\"\n                          height=\"24\"\n                          role=\"img\"\n                          aria-labelledby=\"pi-paypal\"\n                          className=\"h-10\"\n                        >\n                          <title id=\"pi-paypal\">PayPal</title>\n                          <path\n                            opacity=\".07\"\n                            d=\"M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z\"\n                          />\n                          <path\n                            fill=\"#fff\"\n                            d=\"M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32\"\n                          />\n                          <path\n                            fill=\"#003087\"\n                            d=\"M23.9 8.3c.2-1 0-1.7-.6-2.3-.6-.7-1.7-1-3.1-1h-4.1c-.3 0-.5.2-.6.5L14 15.6c0 .2.1.4.3.4H17l.4-3.4 1.8-2.2 4.7-2.1z\"\n                          />\n                          <path\n                            fill=\"#3086C8\"\n                            d=\"M23.9 8.3l-.2.2c-.5 2.8-2.2 3.8-4.6 3.8H18c-.3 0-.5.2-.6.5l-.6 3.9-.2 1c0 .2.1.4.3.4H19c.3 0 .5-.2.5-.4v-.1l.4-2.4v-.1c0-.2.3-.4.5-.4h.3c2.1 0 3.7-.8 4.1-3.2.2-1 .1-1.8-.4-2.4-.1-.5-.3-.7-.5-.8z\"\n                          />\n                          <path\n                            fill=\"#012169\"\n                            d=\"M23.3 8.1c-.1-.1-.2-.1-.3-.1-.1 0-.2 0-.3-.1-.3-.1-.7-.1-1.1-.1h-3c-.1 0-.2 0-.2.1-.2.1-.3.2-.3.4l-.7 4.4v.1c0-.3.3-.5.6-.5h1.3c2.5 0 4.1-1 4.6-3.8v-.2c-.1-.1-.3-.2-.5-.2h-.1z\"\n                          />\n                        </svg>\n                      </div>\n                    </div>\n                  </div>\n                  <div className=\"self-center\">\n                    <div className=\"copyright text-center md:text-right text-textSubdued\">\n                      <span>{copyRight}</span>\n                    </div>\n                  </div>\n                </div>\n              )\n            },\n            sortOrder: 10\n          }\n        ]}\n      />\n    </footer>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/Header.scss",
    "content": ".header {\n  padding: 1rem;\n  border-bottom: 1px solid #ebebeb;\n  @media only screen and (min-width: 1200px) {\n    padding-left: 50px;\n    padding-right: 50px;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/Header.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport React from 'react';\n\nexport function Header() {\n  return (\n    <header className=\"header px-6\">\n      <Area id=\"headerTop\" className=\"header__top\" />\n      <div className=\"header__middle grid grid-cols-3\">\n        <Area\n          id=\"headerMiddleLeft\"\n          className=\"header__middle__left flex justify-start items-center\"\n        />\n        <Area\n          id=\"headerMiddleCenter\"\n          className=\"header__middle__center flex justify-center items-center\"\n        />\n        <Area\n          id=\"headerMiddleRight\"\n          className=\"header__middle__right flex justify-end items-center gap-3\"\n        />\n      </div>\n      <Area id=\"headerBottom\" className=\"header__bottom\" />\n    </header>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/Og.tsx",
    "content": "import { Meta } from '@components/common/Meta.js';\nimport React from 'react';\n\nexport interface OgProps {\n  type?: 'website' | 'article' | 'product' | string;\n\n  /**\n   * The title of the page to be displayed when shared\n   */\n  title?: string;\n\n  /**\n   * A brief description of the page content\n   */\n  description?: string;\n\n  /**\n   * URL to an image that represents the page\n   * Recommended size: 1200x630 pixels for best display across platforms\n   */\n  image?: string;\n\n  /**\n   * The canonical URL of the page\n   */\n  url?: string;\n\n  /**\n   * The name of the website or app\n   */\n  siteName?: string;\n\n  /**\n   * For article type, the published date in ISO format\n   */\n  publishedTime?: string;\n\n  /**\n   * For article type, author names or URLs\n   */\n  authors?: string[];\n\n  /**\n   * Locale code for the content (e.g., 'en_US')\n   */\n  locale?: string;\n\n  /**\n   * Alternative locales available for the page\n   */\n  alternateLocales?: string[];\n  twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player';\n\n  twitterSite?: string;\n\n  /**\n   * Twitter @username of the content creator\n   */\n  twitterCreator?: string;\n\n  twitterImage?: string;\n\n  /**\n   * Whether to include Twitter card tags\n   */\n  includeTwitterTags?: boolean;\n}\n\nexport function Og({\n  type = 'website',\n  title,\n  description,\n  image,\n  url,\n  siteName,\n  publishedTime,\n  authors,\n  locale,\n  alternateLocales,\n  twitterCard = 'summary',\n  twitterSite,\n  twitterCreator,\n  twitterImage,\n  includeTwitterTags = true\n}: OgProps) {\n  return (\n    <>\n      <Meta property=\"og:type\" content={type} />\n\n      {title && <Meta property=\"og:title\" content={title} />}\n      {description && <Meta property=\"og:description\" content={description} />}\n      {image && <Meta property=\"og:image\" content={image} />}\n      {url && <Meta property=\"og:url\" content={url} />}\n      {siteName && <Meta property=\"og:site_name\" content={siteName} />}\n\n      {type === 'article' && publishedTime && (\n        <Meta property=\"article:published_time\" content={publishedTime} />\n      )}\n\n      {type === 'article' &&\n        authors?.length &&\n        authors.map((author, index) => (\n          <Meta\n            key={`author-${index}`}\n            property=\"article:author\"\n            content={author}\n          />\n        ))}\n\n      {locale && <Meta property=\"og:locale\" content={locale} />}\n\n      {alternateLocales?.length &&\n        alternateLocales.map((alternateLocale, index) => (\n          <Meta\n            key={`locale-${index}`}\n            property=\"og:locale:alternate\"\n            content={alternateLocale}\n          />\n        ))}\n\n      {includeTwitterTags && (\n        <>\n          <Meta name=\"twitter:card\" content={twitterCard} />\n          {title && <Meta name=\"twitter:title\" content={title} />}\n          {description && (\n            <Meta name=\"twitter:description\" content={description} />\n          )}\n          {twitterSite && <Meta name=\"twitter:site\" content={twitterSite} />}\n          {twitterCreator && (\n            <Meta name=\"twitter:creator\" content={twitterCreator} />\n          )}\n          {twitterImage && <Meta name=\"twitter:image\" content={twitterImage} />}\n        </>\n      )}\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/Pagination.tsx",
    "content": "import { useAppDispatch } from '@components/common/context/app.js';\nimport {\n  Pagination as PaginationUI,\n  PaginationContent,\n  PaginationEllipsis,\n  PaginationItem,\n  PaginationLink,\n  PaginationNext,\n  PaginationPrevious\n} from '@components/common/ui/Pagination.js';\nimport React, { useState, useEffect, useCallback } from 'react';\n\nexport interface PaginationProps {\n  total: number;\n  limit: number;\n  currentPage: number;\n  children: (props: PaginationRenderProps) => React.ReactNode;\n  onPageChange?: (page: number) => void;\n  scrollToTop?: boolean;\n  scrollBehavior?: 'auto' | 'smooth';\n}\n\nexport interface PaginationRenderProps {\n  currentPage: number;\n  totalPages: number;\n  total: number;\n  limit: number;\n  hasNext: boolean;\n  hasPrev: boolean;\n  startItem: number;\n  endItem: number;\n\n  goToPage: (page: number) => Promise<void>;\n  goToNext: () => Promise<void>;\n  goToPrev: () => Promise<void>;\n  goToFirst: () => Promise<void>;\n  goToLast: () => Promise<void>;\n\n  getPageNumbers: (range?: number) => number[];\n  isCurrentPage: (page: number) => boolean;\n  isValidPage: (page: number) => boolean;\n\n  isLoading: boolean;\n\n  getDisplayText: () => string;\n  getPageInfo: () => {\n    showing: string;\n    total: string;\n  };\n}\n\nexport const PaginationContext = React.createContext<{\n  goToPage: (page: number) => Promise<void>;\n} | null>(null);\n\nexport const usePagination = () => {\n  const context = React.useContext(PaginationContext);\n  if (!context) {\n    throw new Error('usePagination must be used within a PaginationProvider');\n  }\n  return context;\n};\n\nexport const usePaginationLogic = (\n  total: number,\n  limit: number,\n  initialPage: number,\n  onPageChange?: (page: number) => void,\n  scrollToTop: boolean = true,\n  scrollBehavior: 'auto' | 'smooth' = 'smooth'\n) => {\n  const AppContextDispatch = useAppDispatch();\n  const [page, setPage] = useState(initialPage);\n  const [isLoading, setIsLoading] = useState(false);\n\n  const totalPages = Math.ceil(total / limit);\n\n  useEffect(() => {\n    setPage(initialPage);\n  }, [initialPage]);\n\n  const navigateToPage = useCallback(\n    async (pageNum: number) => {\n      if (isLoading) return;\n\n      let validPage;\n      if (pageNum < 1) validPage = 1;\n      else if (pageNum > totalPages) validPage = totalPages;\n      else validPage = pageNum;\n\n      if (validPage === page) return;\n\n      setIsLoading(true);\n\n      try {\n        const url = new URL(window.location.href, window.location.origin);\n        url.searchParams.set('page', validPage.toString());\n        url.searchParams.append('ajax', 'true');\n\n        setPage(validPage);\n        await AppContextDispatch.fetchPageData(url);\n\n        url.searchParams.delete('ajax');\n        history.pushState(null, '', url);\n\n        if (scrollToTop) {\n          window.scrollTo({ top: 0, behavior: scrollBehavior });\n        }\n\n        onPageChange?.(validPage);\n      } catch (error) {\n        setPage(page);\n      } finally {\n        setIsLoading(false);\n      }\n    },\n    [\n      AppContextDispatch,\n      page,\n      totalPages,\n      isLoading,\n      onPageChange,\n      scrollToTop,\n      scrollBehavior\n    ]\n  );\n\n  const goToPage = useCallback(\n    (pageNum: number) => navigateToPage(pageNum),\n    [navigateToPage]\n  );\n\n  const goToNext = useCallback(\n    () => navigateToPage(page + 1),\n    [navigateToPage, page]\n  );\n\n  const goToPrev = useCallback(\n    () => navigateToPage(page - 1),\n    [navigateToPage, page]\n  );\n\n  const goToFirst = useCallback(() => navigateToPage(1), [navigateToPage]);\n\n  const goToLast = useCallback(\n    () => navigateToPage(totalPages),\n    [navigateToPage, totalPages]\n  );\n\n  const getPageNumbers = useCallback(\n    (range: number = 5) => {\n      const pages: number[] = [];\n      const half = Math.floor(range / 2);\n\n      let start = Math.max(1, page - half);\n      const end = Math.min(totalPages, start + range - 1);\n\n      // Adjust start if we're near the end\n      if (end - start + 1 < range) {\n        start = Math.max(1, end - range + 1);\n      }\n\n      for (let i = start; i <= end; i++) {\n        pages.push(i);\n      }\n\n      return pages;\n    },\n    [page, totalPages]\n  );\n\n  const isCurrentPage = useCallback(\n    (pageNum: number) => pageNum === page,\n    [page]\n  );\n\n  const isValidPage = useCallback(\n    (pageNum: number) => pageNum >= 1 && pageNum <= totalPages,\n    [totalPages]\n  );\n\n  const getDisplayText = useCallback(() => {\n    const start = (page - 1) * limit + 1;\n    const end = Math.min(page * limit, total);\n    return `Showing ${start}-${end} of ${total} results`;\n  }, [page, limit, total]);\n\n  const getPageInfo = useCallback(() => {\n    const start = (page - 1) * limit + 1;\n    const end = Math.min(page * limit, total);\n    return {\n      showing: `${start}-${end}`,\n      total: total.toString()\n    };\n  }, [page, limit, total]);\n\n  return {\n    currentPage: page,\n    totalPages,\n    hasNext: page < totalPages,\n    hasPrev: page > 1,\n    startItem: (page - 1) * limit + 1,\n    endItem: Math.min(page * limit, total),\n    goToPage,\n    goToNext,\n    goToPrev,\n    goToFirst,\n    goToLast,\n    getPageNumbers,\n    isCurrentPage,\n    isValidPage,\n    isLoading,\n    getDisplayText,\n    getPageInfo\n  };\n};\n\n// Main Pagination component with render prop pattern\nexport function Pagination({\n  total,\n  limit,\n  currentPage,\n  children,\n  onPageChange,\n  scrollToTop = true,\n  scrollBehavior = 'smooth'\n}: PaginationProps) {\n  const paginationLogic = usePaginationLogic(\n    total,\n    limit,\n    currentPage,\n    onPageChange,\n    scrollToTop,\n    scrollBehavior\n  );\n\n  // Prepare render props\n  const renderProps: PaginationRenderProps = {\n    // Pagination data\n    currentPage: paginationLogic.currentPage,\n    totalPages: paginationLogic.totalPages,\n    total,\n    limit,\n    hasNext: paginationLogic.hasNext,\n    hasPrev: paginationLogic.hasPrev,\n    startItem: paginationLogic.startItem,\n    endItem: paginationLogic.endItem,\n\n    // Navigation functions\n    goToPage: paginationLogic.goToPage,\n    goToNext: paginationLogic.goToNext,\n    goToPrev: paginationLogic.goToPrev,\n    goToFirst: paginationLogic.goToFirst,\n    goToLast: paginationLogic.goToLast,\n\n    // Utility functions\n    getPageNumbers: paginationLogic.getPageNumbers,\n    isCurrentPage: paginationLogic.isCurrentPage,\n    isValidPage: paginationLogic.isValidPage,\n\n    // State\n    isLoading: paginationLogic.isLoading,\n\n    // Display helpers\n    getDisplayText: paginationLogic.getDisplayText,\n    getPageInfo: paginationLogic.getPageInfo\n  };\n\n  const contextValue = React.useMemo(\n    () => ({ goToPage: paginationLogic.goToPage }),\n    [paginationLogic.goToPage]\n  );\n\n  return (\n    <PaginationContext.Provider value={contextValue}>\n      {children(renderProps)}\n    </PaginationContext.Provider>\n  );\n}\n\n// Default pagination renderer component (maintains original design)\nexport const DefaultPaginationRenderer: React.FC<{\n  renderProps: PaginationRenderProps;\n  className?: string;\n  showInfo?: boolean;\n}> = ({ renderProps, className = '', showInfo = false }) => {\n  const {\n    currentPage,\n    totalPages,\n    hasNext,\n    hasPrev,\n    goToNext,\n    goToPrev,\n    goToPage,\n    getPageNumbers,\n    isCurrentPage,\n    isLoading,\n    getDisplayText\n  } = renderProps;\n\n  const pageNumbers = getPageNumbers(7);\n  const showStartEllipsis = pageNumbers[0] > 1;\n  const showEndEllipsis = pageNumbers[pageNumbers.length - 1] < totalPages;\n\n  return (\n    <div className={`products-pagination ${className}`}>\n      {showInfo && (\n        <div className=\"pagination-info text-center text-muted-foreground mb-4\">\n          {getDisplayText()}\n        </div>\n      )}\n\n      <PaginationUI>\n        <PaginationContent>\n          {hasPrev && (\n            <PaginationItem>\n              <PaginationPrevious\n                href=\"#\"\n                onClick={(e) => {\n                  e.preventDefault();\n                  if (!isLoading) goToPrev();\n                }}\n                aria-disabled={isLoading}\n                className={isLoading ? 'pointer-events-none opacity-50' : ''}\n              />\n            </PaginationItem>\n          )}\n\n          {showStartEllipsis && (\n            <>\n              <PaginationItem>\n                <PaginationLink\n                  href=\"#\"\n                  onClick={(e) => {\n                    e.preventDefault();\n                    if (!isLoading) goToPage(1);\n                  }}\n                  aria-disabled={isLoading}\n                  className={isLoading ? 'pointer-events-none opacity-50' : ''}\n                >\n                  1\n                </PaginationLink>\n              </PaginationItem>\n              <PaginationItem>\n                <PaginationEllipsis />\n              </PaginationItem>\n            </>\n          )}\n\n          {pageNumbers.map((p) => (\n            <PaginationItem key={p}>\n              <PaginationLink\n                href=\"#\"\n                onClick={(e) => {\n                  e.preventDefault();\n                  if (!isLoading && !isCurrentPage(p)) goToPage(p);\n                }}\n                isActive={isCurrentPage(p)}\n                aria-disabled={isLoading || isCurrentPage(p)}\n                className={\n                  isLoading || isCurrentPage(p)\n                    ? 'pointer-events-none opacity-50'\n                    : ''\n                }\n              >\n                {p}\n              </PaginationLink>\n            </PaginationItem>\n          ))}\n\n          {showEndEllipsis && (\n            <>\n              <PaginationItem>\n                <PaginationEllipsis />\n              </PaginationItem>\n              <PaginationItem>\n                <PaginationLink\n                  href=\"#\"\n                  onClick={(e) => {\n                    e.preventDefault();\n                    if (!isLoading) goToPage(totalPages);\n                  }}\n                  aria-disabled={isLoading}\n                  className={isLoading ? 'pointer-events-none opacity-50' : ''}\n                >\n                  {totalPages}\n                </PaginationLink>\n              </PaginationItem>\n            </>\n          )}\n\n          {hasNext && (\n            <PaginationItem>\n              <PaginationNext\n                href=\"#\"\n                onClick={(e) => {\n                  e.preventDefault();\n                  if (!isLoading) goToNext();\n                }}\n                aria-disabled={isLoading}\n                className={isLoading ? 'pointer-events-none opacity-50' : ''}\n              />\n            </PaginationItem>\n          )}\n        </PaginationContent>\n      </PaginationUI>\n\n      {isLoading && (\n        <div className=\"pagination-loading text-center mt-2\">\n          <div className=\"inline-block w-4 h-4 border-2 border-primary border-t-transparent rounded-full animate-spin\"></div>\n        </div>\n      )}\n    </div>\n  );\n};\n\n// Compact pagination renderer\nexport const CompactPaginationRenderer: React.FC<{\n  renderProps: PaginationRenderProps;\n  className?: string;\n}> = ({ renderProps, className = '' }) => {\n  const {\n    currentPage,\n    totalPages,\n    hasNext,\n    hasPrev,\n    goToNext,\n    goToPrev,\n    getPageInfo,\n    isLoading\n  } = renderProps;\n\n  const { showing, total } = getPageInfo();\n\n  return (\n    <div\n      className={`compact-pagination flex items-center justify-between ${className}`}\n    >\n      <div className=\"pagination-info text-sm text-gray-600\">\n        Showing {showing} of {total}\n      </div>\n\n      <div className=\"pagination-controls flex items-center space-x-2\">\n        <button\n          onClick={goToPrev}\n          disabled={!hasPrev || isLoading}\n          className=\"px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed\"\n        >\n          Previous\n        </button>\n\n        <span className=\"text-sm text-gray-600\">\n          Page {currentPage} of {totalPages}\n        </span>\n\n        <button\n          onClick={goToNext}\n          disabled={!hasNext || isLoading}\n          className=\"px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed\"\n        >\n          Next\n        </button>\n      </div>\n    </div>\n  );\n};\n\nexport const InputPaginationRenderer: React.FC<{\n  renderProps: PaginationRenderProps;\n  className?: string;\n}> = ({ renderProps, className = '' }) => {\n  const {\n    currentPage,\n    totalPages,\n    hasNext,\n    hasPrev,\n    goToNext,\n    goToPrev,\n    goToPage,\n    goToFirst,\n    goToLast,\n    getDisplayText,\n    isLoading\n  } = renderProps;\n\n  const [inputPage, setInputPage] = React.useState(currentPage.toString());\n\n  React.useEffect(() => {\n    setInputPage(currentPage.toString());\n  }, [currentPage]);\n\n  const handleInputSubmit = (e: React.FormEvent) => {\n    e.preventDefault();\n    const page = parseInt(inputPage);\n    if (!isNaN(page)) {\n      goToPage(page);\n    }\n  };\n\n  return (\n    <div className={`input-pagination ${className}`}>\n      <div className=\"text-center text-sm text-gray-600 mb-4\">\n        {getDisplayText()}\n      </div>\n\n      <div className=\"flex items-center justify-center space-x-4\">\n        <button\n          onClick={goToFirst}\n          disabled={!hasPrev || isLoading}\n          className=\"px-2 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50\"\n        >\n          First\n        </button>\n\n        <button\n          onClick={goToPrev}\n          disabled={!hasPrev || isLoading}\n          className=\"px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50\"\n        >\n          ←\n        </button>\n\n        <form\n          onSubmit={handleInputSubmit}\n          className=\"flex items-center space-x-2\"\n        >\n          <span className=\"text-sm\">Page</span>\n          <input\n            type=\"number\"\n            min=\"1\"\n            max={totalPages}\n            value={inputPage}\n            onChange={(e) => setInputPage(e.target.value)}\n            className=\"w-16 px-2 py-1 text-sm border border-gray-300 rounded text-center\"\n            disabled={isLoading}\n          />\n          <span className=\"text-sm\">of {totalPages}</span>\n          <button\n            type=\"submit\"\n            className=\"px-2 py-1 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50\"\n            disabled={isLoading}\n          >\n            Go\n          </button>\n        </form>\n\n        <button\n          onClick={goToNext}\n          disabled={!hasNext || isLoading}\n          className=\"px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50\"\n        >\n          →\n        </button>\n\n        <button\n          onClick={goToLast}\n          disabled={!hasNext || isLoading}\n          className=\"px-2 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50\"\n        >\n          Last\n        </button>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/AddToCart.tsx",
    "content": "import {\n  useCartDispatch,\n  useCartState\n} from '@components/frontStore/cart/CartContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useState, useCallback } from 'react';\n\nexport interface ProductInfo {\n  sku: string;\n  isInStock: boolean;\n}\n\nexport interface AddToCartState {\n  isLoading: boolean;\n  error: string | null;\n  canAddToCart: boolean;\n  isInStock: boolean;\n}\n\nexport interface AddToCartActions {\n  addToCart: () => Promise<void>;\n  clearError: () => void;\n}\n\nexport interface AddToCartProps {\n  product: ProductInfo;\n  qty: number; // Quantity to add to cart\n  onSuccess?: (quantity: number) => void;\n  onError?: (error: string) => void;\n  children: (\n    state: AddToCartState,\n    actions: AddToCartActions\n  ) => React.ReactNode;\n}\n\nexport const AddToCart: React.FC<AddToCartProps> = ({\n  product,\n  qty,\n  onSuccess,\n  onError,\n  children\n}) => {\n  const cartDispatch = useCartDispatch();\n  const cartState = useCartState();\n  const [localError, setLocalError] = useState<string | null>(null);\n  const canAddToCart = product.isInStock && qty > 0 && !!cartState.data;\n\n  const isLoading = cartState.loading;\n\n  const clearError = useCallback(() => {\n    setLocalError(null);\n    cartDispatch.clearError();\n  }, [cartDispatch]);\n\n  const addToCart = useCallback(async () => {\n    if (!canAddToCart) {\n      const errorMsg = !product.isInStock\n        ? _('Product is out of stock')\n        : !cartState.data\n        ? _('Cart is not initialized')\n        : _('Invalid quantity');\n\n      setLocalError(errorMsg);\n      onError?.(errorMsg);\n      return;\n    }\n\n    try {\n      setLocalError(null);\n      cartDispatch.clearError();\n      await cartDispatch.addItem({\n        sku: product.sku,\n        qty: qty\n      });\n      onSuccess?.(qty);\n    } catch (error) {\n      const errorMessage =\n        error instanceof Error\n          ? error.message\n          : _('Failed to add item to cart');\n      setLocalError(errorMessage);\n      onError?.(errorMessage);\n    }\n  }, [\n    canAddToCart,\n    product.isInStock,\n    product.sku,\n    qty,\n    cartState.data,\n    cartDispatch,\n    onSuccess,\n    onError\n  ]);\n\n  const state: AddToCartState = {\n    isLoading,\n    error: localError || cartState.data?.error || null,\n    canAddToCart,\n    isInStock: product.isInStock\n  };\n\n  const actions: AddToCartActions = {\n    addToCart,\n    clearError\n  };\n\n  return <>{children(state, actions)}</>;\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/CartContext.tsx",
    "content": "import { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { ApiResponse } from '@evershop/evershop/types/apiResponse';\nimport {\n  CustomerAddressGraphql,\n  Address\n} from '@evershop/evershop/types/customerAddress';\nimport { produce } from 'immer';\nimport React, {\n  createContext,\n  useReducer,\n  useContext,\n  ReactNode,\n  useCallback\n} from 'react';\nimport { useQuery, useClient } from 'urql';\n\nconst ShippingMethodsQuery = `\n  query GetCartShippingMethods($country: String!, $province: String, $postcode: String) {\n    myCart {\n      availableShippingMethods(country: $country, province: $province, postcode: $postcode) {\n        code\n        name\n        cost {\n          value\n          text\n        }\n      }\n    }\n  }\n`;\n\nexport enum CartSyncTrigger {\n  ADD_ITEM = 'addItem',\n  REMOVE_ITEM = 'removeItem',\n  UPDATE_ITEM = 'updateItem',\n  ADD_PAYMENT_METHOD = 'addPaymentMethod',\n  ADD_SHIPPING_METHOD = 'addShippingMethod',\n  ADD_SHIPPING_ADDRESS = 'addShippingAddress',\n  ADD_BILLING_ADDRESS = 'addBillingAddress',\n  ADD_CONTACT_INFO = 'addContactInfo',\n  APPLY_COUPON = 'applyCoupon',\n  REMOVE_COUPON = 'removeCoupon'\n}\n\nexport interface CartItem {\n  cartItemId: string;\n  uuid: string;\n  productId: string;\n  qty: number;\n  productSku: string;\n  productName: string;\n  productUrl: string;\n  thumbnail?: string;\n  noShippingRequired: boolean;\n  productWeight: {\n    value: number;\n    unit: string;\n  };\n  variantOptions?: {\n    attributeCode: string;\n    attributeName: string;\n    attributeId: number;\n    optionId: number;\n    optionText: string;\n  }[];\n  productPrice: {\n    value: number;\n    text: string;\n  };\n  productPriceInclTax: {\n    value: number;\n    text: string;\n  };\n  finalPrice: {\n    value: number;\n    text: string;\n  };\n  finalPriceInclTax: {\n    value: number;\n    text: string;\n  };\n  taxPercent: number;\n  taxAmount: {\n    value: number;\n    text: string;\n  };\n  taxAmountBeforeDiscount: {\n    value: number;\n    text: string;\n  };\n  discountAmount: {\n    value: number;\n    text: string;\n  };\n  lineTotal: {\n    value: number;\n    text: string;\n  };\n  subTotal: {\n    value: number;\n    text: string;\n  };\n  lineTotalWithDiscount: {\n    value: number;\n    text: string;\n  };\n  lineTotalWithDiscountInclTax: {\n    value: number;\n    text: string;\n  };\n  lineTotalInclTax: {\n    value: number;\n    text: string;\n  };\n  total: {\n    value: number;\n    text: string;\n  };\n  variantGroupId?: number;\n  removeApi: string; // API endpoint to remove item from cart\n  updateQtyApi: string; // API endpoint to update item quantity\n  errors?: string[]; // Validation errors for this item\n}\n\nexport interface PaymentMethod {\n  id: string;\n  name: string;\n  code: string;\n}\n\nexport interface ShippingMethod {\n  id: string;\n  name: string;\n  code: string;\n  price: number;\n}\n\nexport interface ShippingAddressParams {\n  country: string;\n  province?: string;\n  postcode?: string;\n}\n\nexport interface CartError {\n  field?: string;\n  message: string;\n  code?: string;\n}\n\n// Extensible cart data interface - third-party extensions can add any fields from server-side cart data\nexport interface CartData {\n  uuid?: string; // Cart unique identifier\n  currency: string; // Currency code\n  totalQty: number; // Total quantity of items\n  totalWeight: {\n    value: number;\n    unit: string;\n  }; // Total weight of items\n  customerId?: number; // Optional customer ID\n  customerGroupId?: number; // Optional customer group ID\n  customerEmail?: string; // Optional customer email\n  customerFullName?: string; // Optional customer full name\n  coupon?: string; // Coupon code applied to cart\n  noShippingRequired: boolean;\n  shippingMethod?: string; // Selected shipping method code\n  shippingMethodName?: string; // Selected shipping method name\n  paymentMethod?: string; // Selected payment method code\n  paymentMethodName?: string; // Selected payment method name\n  shippingNote?: string; // Shipping note\n  items: CartItem[];\n  taxAmount: {\n    value: number;\n    text: string;\n  };\n  totalTaxAmount: {\n    value: number;\n    text: string;\n  };\n  taxAmountBeforeDiscount: {\n    value: number;\n    text: string;\n  };\n  discountAmount: {\n    value: number;\n    text: string;\n  };\n  shippingFeeExclTax: {\n    value: number;\n    text: string;\n  };\n  shippingFeeInclTax: {\n    value: number;\n    text: string;\n  };\n  shippingTaxAmount: {\n    value: number;\n    text: string;\n  };\n  subTotal: {\n    value: number;\n    text: string;\n  };\n  subTotalInclTax: {\n    value: number;\n    text: string;\n  };\n  subTotalWithDiscount: {\n    value: number;\n    text: string;\n  };\n  subTotalWithDiscountInclTax: {\n    value: number;\n    text: string;\n  };\n  grandTotal: {\n    value: number;\n    text: string;\n  };\n  billingAddress?: CustomerAddressGraphql;\n  shippingAddress?: CustomerAddressGraphql;\n  createdAt: {\n    value: string;\n    text: string;\n  };\n  updatedAt: {\n    value: string;\n    text: string;\n  };\n  // API endpoints\n  addItemApi: string;\n  addPaymentMethodApi: string;\n  addShippingMethodApi: string;\n  addContactInfoApi: string;\n  addAddressApi: string;\n  addNoteApi: string;\n  applyCouponApi: string;\n  checkoutApi: string;\n  removeCouponApi?: string;\n  // Available methods\n  availablePaymentMethods: {\n    code: string;\n    name: string;\n  }[];\n  availableShippingMethods: {\n    code: string;\n    name: string;\n    cost?: {\n      value: number;\n      text: string;\n    };\n  }[];\n  // Errors\n  errors: CartError[];\n  error: string | null;\n  [extendedFields: string]: unknown; // Allow third-party extensions to add fields\n}\n\n// Complete cart state with detailed loading states\nexport interface CartState {\n  data: CartData; // Cart data, can be undefined if not initialized\n  loading: boolean; // Overall loading state (true if any operation is loading) - derived from loadingStates\n  loadingStates: {\n    addingItem: boolean;\n    removingItem: string | null; // Item ID being removed, null if none\n    updatingItem: string | null; // Item ID being updated, null if none\n    addingPaymentMethod: boolean;\n    addingShippingMethod: boolean;\n    addingShippingAddress: boolean;\n    addingBillingAddress: boolean;\n    addingContactInfo: boolean;\n    applyingCoupon: boolean;\n    removingCoupon: boolean;\n    fetchingShippingMethods: boolean; // New loading state for fetching shipping methods\n  };\n  syncStatus: {\n    syncing: boolean; // Whether a sync operation is in progress\n    synced: boolean; // Whether the last sync was successful\n    trigger?: string; // The reason/trigger that caused the sync (can be internal enum or external string)\n  };\n}\n\n// Action type for cart operations\ntype CartAction =\n  | { type: 'SET_CART'; payload: Partial<CartData> }\n  | {\n      type: 'SET_SPECIFIC_LOADING';\n      payload: {\n        operation: keyof CartState['loadingStates'];\n        loading: boolean;\n        itemId?: string;\n      };\n    }\n  | { type: 'SET_ERROR'; payload: string | null }\n  | { type: 'CLEAR_ERROR' }\n  | {\n      type: 'SET_SYNC_STATUS';\n      payload: { syncing?: boolean; synced?: boolean; trigger?: string };\n    };\n\n// The shape of the functions that will be returned by the useCartDispatch hook\ninterface CartDispatch {\n  addItem: (payload: { sku: string; qty: number }) => Promise<void>;\n  removeItem: (itemId: string) => Promise<void>;\n  updateItem: (\n    itemId: string,\n    payload: { qty: number; action: 'increase' | 'decrease' }\n  ) => Promise<void>;\n  addPaymentMethod: (code: string, name: string) => Promise<void>;\n  addShippingMethod: (code: string, name: string) => Promise<void>;\n  addShippingAddress: (address: Address) => Promise<void>;\n  addBillingAddress: (address: Address) => Promise<void>;\n  addContactInfo: (contactInfo: { email: string }) => Promise<void>;\n  applyCoupon: (couponCode: string) => Promise<void>;\n  removeCoupon: () => Promise<void>;\n  clearError: () => void;\n  isShippingRequired: () => boolean;\n  isReadyForCheckout: () => boolean;\n  getErrors: () => CartError[];\n  getId: () => string | null;\n  fetchAvailableShippingMethods: (\n    params: ShippingAddressParams\n  ) => Promise<void>;\n  syncCartWithServer: (trigger?: string) => Promise<void>; // Added trigger parameter\n}\n\nconst cartReducer = (state: CartState, action: CartAction): CartState => {\n  return produce(state, (draft) => {\n    switch (action.type) {\n      case 'SET_CART':\n        if (draft.data) {\n          Object.assign(draft.data, action.payload);\n          draft.data.error = null;\n        } else {\n          draft.data = action.payload as CartData;\n        }\n        // Clear all loading states when cart is set\n        draft.loadingStates = {\n          addingItem: false,\n          removingItem: null,\n          updatingItem: null,\n          addingPaymentMethod: false,\n          addingShippingMethod: false,\n          addingShippingAddress: false,\n          addingBillingAddress: false,\n          addingContactInfo: false,\n          applyingCoupon: false,\n          removingCoupon: false,\n          fetchingShippingMethods: false\n        };\n        draft.loading = false;\n        break;\n\n      case 'SET_SPECIFIC_LOADING':\n        const { operation, loading, itemId } = action.payload;\n        if (operation === 'removingItem' || operation === 'updatingItem') {\n          draft.loadingStates[operation] = loading ? itemId || null : null;\n        } else {\n          (draft.loadingStates as any)[operation] = loading;\n        }\n        // Update overall loading state based on loadingStates\n        draft.loading = Object.values(draft.loadingStates).some(\n          (state) =>\n            state === true || (typeof state === 'string' && state !== null)\n        );\n        break;\n\n      case 'SET_ERROR':\n        if (draft.data) {\n          draft.data.error = action.payload;\n        }\n        // Clear all loading states on error\n        draft.loadingStates = {\n          addingItem: false,\n          removingItem: null,\n          updatingItem: null,\n          addingPaymentMethod: false,\n          addingShippingMethod: false,\n          addingShippingAddress: false,\n          addingBillingAddress: false,\n          addingContactInfo: false,\n          applyingCoupon: false,\n          removingCoupon: false,\n          fetchingShippingMethods: false\n        };\n        draft.loading = false;\n        break;\n\n      case 'CLEAR_ERROR':\n        if (draft.data) {\n          draft.data.error = null;\n          draft.data.errors = [];\n        }\n        break;\n\n      case 'SET_SYNC_STATUS':\n        Object.assign(draft.syncStatus, action.payload);\n        break;\n    }\n  });\n};\n\nconst CartStateContext = createContext<CartState | undefined>(undefined);\nconst CartDispatchContext = createContext<CartDispatch | undefined>(undefined);\n\ninterface CartProviderProps {\n  children: ReactNode;\n  query: string;\n  cart?: CartData;\n  addMineCartItemApi: string;\n}\n\nconst initialEmptyState: CartState = {\n  data: {\n    currency: 'USD',\n    addItemApi: '', // initial addItemApi\n    items: [],\n    totalQty: 0,\n    noShippingRequired: false,\n    totalWeight: { value: 0, unit: 'kg' },\n    billingAddress: undefined,\n    shippingAddress: undefined,\n    errors: [],\n    error: null,\n    taxAmount: { value: 0, text: '0.00' },\n    totalTaxAmount: { value: 0, text: '0.00' },\n    taxAmountBeforeDiscount: { value: 0, text: '0.00' },\n    discountAmount: { value: 0, text: '0.00' },\n    shippingFeeExclTax: { value: 0, text: '0.00' },\n    shippingFeeInclTax: { value: 0, text: '0.00' },\n    shippingTaxAmount: { value: 0, text: '0.00' },\n    subTotal: { value: 0, text: '0.00' },\n    subTotalInclTax: { value: 0, text: '0.00' },\n    subTotalWithDiscount: { value: 0, text: '0.00' },\n    subTotalWithDiscountInclTax: { value: 0, text: '0.00' },\n    grandTotal: { value: 0, text: '0.00' },\n    createdAt: { value: '', text: '' },\n    updatedAt: { value: '', text: '' },\n    coupon: '',\n    addPaymentMethodApi: '', // Will be set by server\n    addShippingMethodApi: '', // Will be set by server\n    addAddressApi: '', // Will be set by server\n    applyCouponApi: '', // Will be set by server\n    addNoteApi: '', // Will be set by server\n    addContactInfoApi: '', // Will be set by server\n    checkoutApi: '', // Will be set by server\n    availablePaymentMethods: [],\n    availableShippingMethods: []\n  },\n  loading: false,\n  loadingStates: {\n    addingItem: false,\n    removingItem: null,\n    updatingItem: null,\n    addingPaymentMethod: false,\n    addingShippingMethod: false,\n    addingShippingAddress: false,\n    addingBillingAddress: false,\n    addingContactInfo: false,\n    applyingCoupon: false,\n    removingCoupon: false,\n    fetchingShippingMethods: false\n  },\n  syncStatus: {\n    syncing: false,\n    synced: false,\n    trigger: undefined\n  }\n};\n\nexport const CartProvider = ({\n  children,\n  query,\n  cart,\n  addMineCartItemApi\n}: CartProviderProps) => {\n  const client = useClient(); // Get urql client for GraphQL queries\n\n  const hydratedInitialState: Partial<CartState> = {\n    loading: initialEmptyState.loading,\n    loadingStates: { ...initialEmptyState.loadingStates },\n    syncStatus: { ...initialEmptyState.syncStatus }\n  };\n  if (cart) {\n    hydratedInitialState.data = cart;\n  } else {\n    hydratedInitialState.data = {\n      ...initialEmptyState.data,\n      addItemApi: addMineCartItemApi\n    };\n  }\n\n  const [state, dispatch] = useReducer(cartReducer, hydratedInitialState);\n\n  // Use urql to query cart data\n  const [cartQueryResult, refetchCart] = useQuery({\n    query: query,\n    pause: true\n  });\n\n  const retry = async function <T>(\n    fn: () => Promise<T>,\n    retries = 2,\n    delay = 1000\n  ): Promise<T> {\n    try {\n      return await fn();\n    } catch (error) {\n      if (retries > 0) {\n        await new Promise((resolve) => setTimeout(resolve, delay));\n        return retry(fn, retries - 1, delay * 2);\n      }\n      throw error;\n    }\n  };\n\n  const syncCartWithServer = useCallback(\n    async (trigger?: string): Promise<void> => {\n      try {\n        // Set syncing to true and synced to false when starting sync\n        dispatch({\n          type: 'SET_SYNC_STATUS',\n          payload: { syncing: true, synced: false, trigger }\n        });\n\n        await refetchCart({ requestPolicy: 'network-only' });\n\n        // Set syncing to false and synced to true on success\n        dispatch({\n          type: 'SET_SYNC_STATUS',\n          payload: { syncing: false, synced: true, trigger }\n        });\n      } catch (error) {\n        dispatch({\n          type: 'SET_ERROR',\n          payload:\n            error instanceof Error ? error.message : 'Failed to sync cart'\n        });\n\n        // Set syncing to false and keep synced as false on error\n        dispatch({\n          type: 'SET_SYNC_STATUS',\n          payload: { syncing: false, synced: false, trigger }\n        });\n      }\n    },\n    [refetchCart]\n  );\n\n  // Effect to update cart when GraphQL query result changes\n  React.useEffect(() => {\n    // Only process if we have fetched data (either successful or error state)\n    if (cartQueryResult.fetching === false) {\n      if (cartQueryResult.data?.myCart) {\n        const serverCart = cartQueryResult.data.myCart;\n        dispatch({\n          type: 'SET_CART',\n          payload: serverCart\n        });\n      } else if (cartQueryResult.error) {\n        // Handle error case\n        dispatch({\n          type: 'SET_ERROR',\n          payload: cartQueryResult.error.message || 'Failed to fetch cart data'\n        });\n      } else if (cartQueryResult.operation) {\n        // Query executed but returned no data - initialize empty cart\n        dispatch({\n          type: 'SET_CART',\n          payload: {\n            ...initialEmptyState.data,\n            addItemApi: addMineCartItemApi\n          }\n        });\n      }\n    }\n  }, [\n    cartQueryResult.data,\n    cartQueryResult.error,\n    cartQueryResult.fetching,\n    cartQueryResult.operation\n  ]);\n\n  React.useEffect(() => {\n    if (cart && JSON.stringify(cart) !== JSON.stringify(state.data)) {\n      dispatch({ type: 'SET_CART', payload: cart });\n    }\n  }, [cart]);\n\n  const addItem = useCallback(\n    async (payload: { sku: string; qty: number }) => {\n      if (!state.data) {\n        throw new Error('Cannot add item: cart not initialized');\n      }\n\n      try {\n        // Set specific loading state\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingItem', loading: true }\n        });\n\n        // Server request with retry\n        const response = await retry(() =>\n          fetch(state.data!.addItemApi, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify(payload)\n          })\n        );\n\n        const json = (await response.json()) as ApiResponse<CartData>;\n\n        if (!response.ok) {\n          throw new Error(json.error?.message || 'Failed to add item.');\n        }\n\n        // Sync with server (both immediate update and GraphQL refetch)\n        await syncCartWithServer(CartSyncTrigger.ADD_ITEM);\n      } catch (error) {\n        dispatch({\n          type: 'SET_ERROR',\n          payload: error instanceof Error ? error.message : 'Failed to add item'\n        });\n        throw error;\n      } finally {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingItem', loading: false }\n        });\n      }\n    },\n    [state.data?.addItemApi, syncCartWithServer]\n  );\n\n  const removeItem = useCallback(\n    async (itemId: string) => {\n      if (!state.data) {\n        throw new Error('Cannot remove item: cart not initialized');\n      }\n\n      const item = state.data.items.find((item) => item.cartItemId === itemId);\n      if (!item) {\n        throw new Error('Item not found in cart');\n      }\n\n      try {\n        // Set specific loading state for this item\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'removingItem', loading: true, itemId }\n        });\n\n        // Server request with retry using item's remove API\n        const response = await retry(() =>\n          fetch(item.removeApi, {\n            method: 'DELETE'\n          })\n        );\n\n        const json = await response.json();\n\n        if (!response.ok) {\n          throw new Error(json.error?.message || 'Failed to remove item.');\n        }\n\n        // Sync with server (both immediate update and GraphQL refetch)\n        await syncCartWithServer(CartSyncTrigger.REMOVE_ITEM);\n      } catch (error) {\n        dispatch({\n          type: 'SET_ERROR',\n          payload:\n            error instanceof Error ? error.message : 'Failed to remove item'\n        });\n        throw error;\n      } finally {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'removingItem', loading: false }\n        });\n      }\n    },\n    [state, syncCartWithServer]\n  );\n\n  const updateItem = useCallback(\n    async (\n      itemId: string,\n      payload: { qty: number; action: 'increase' | 'decrease' }\n    ) => {\n      if (!state.data) {\n        throw new Error('Cannot update item: cart not initialized');\n      }\n\n      const item = state.data.items.find((item) => item.cartItemId === itemId);\n      if (!item) {\n        throw new Error('Item not found in cart');\n      }\n\n      try {\n        // Set specific loading state for this item\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'updatingItem', loading: true, itemId }\n        });\n\n        // Server request with retry using item's update API\n        const response = await retry(() =>\n          fetch(item.updateQtyApi, {\n            method: 'PATCH',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify(payload)\n          })\n        );\n\n        const json = await response.json();\n\n        if (!response.ok) {\n          throw new Error(json.error?.message || 'Failed to update item.');\n        }\n\n        // Sync with server (both immediate update and GraphQL refetch)\n        await syncCartWithServer(CartSyncTrigger.UPDATE_ITEM);\n      } catch (error) {\n        dispatch({\n          type: 'SET_ERROR',\n          payload:\n            error instanceof Error ? error.message : 'Failed to update item'\n        });\n        throw error;\n      } finally {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'updatingItem', loading: false }\n        });\n      }\n    },\n    [state, syncCartWithServer]\n  );\n\n  // Clear error function\n  const clearError = useCallback(() => {\n    dispatch({ type: 'CLEAR_ERROR' });\n  }, []);\n\n  // Add payment method\n  const addPaymentMethod = useCallback(\n    async (code: string, name: string) => {\n      if (!state.data) {\n        throw new Error(_('Cannot add payment method: cart not initialized'));\n      }\n\n      try {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingPaymentMethod', loading: true }\n        });\n\n        const response = await retry(() =>\n          fetch(state.data!.addPaymentMethodApi, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ method_code: code, method_name: name })\n          })\n        );\n\n        const json = await response.json();\n\n        if (!response.ok) {\n          throw new Error(\n            json.error?.message || _('Failed to add payment method.')\n          );\n        }\n\n        // Sync with server (both immediate update and GraphQL refetch)\n        await syncCartWithServer(CartSyncTrigger.ADD_PAYMENT_METHOD);\n      } catch (error) {\n        dispatch({\n          type: 'SET_ERROR',\n          payload:\n            error instanceof Error\n              ? error.message\n              : _('Failed to add payment method')\n        });\n        throw error;\n      } finally {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingPaymentMethod', loading: false }\n        });\n      }\n    },\n    [state.data?.addPaymentMethodApi, syncCartWithServer]\n  );\n\n  // Add shipping method\n  const addShippingMethod = useCallback(\n    async (code: string, name: string) => {\n      if (!state.data) {\n        throw new Error(_('Cannot add shipping method: cart not initialized'));\n      }\n\n      try {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingShippingMethod', loading: true }\n        });\n\n        const response = await retry(() =>\n          fetch(state.data!.addShippingMethodApi, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ method_code: code, method_name: name })\n          })\n        );\n\n        const json = await response.json();\n\n        if (!response.ok) {\n          throw new Error(\n            json.error?.message || _('Failed to add shipping method.')\n          );\n        }\n\n        // Sync with server (both immediate update and GraphQL refetch)\n        await syncCartWithServer(CartSyncTrigger.ADD_SHIPPING_METHOD);\n      } catch (error) {\n        dispatch({\n          type: 'SET_ERROR',\n          payload:\n            error instanceof Error\n              ? error.message\n              : _('Failed to add shipping method')\n        });\n        throw error;\n      } finally {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingShippingMethod', loading: false }\n        });\n      }\n    },\n    [state.data?.addShippingMethodApi, syncCartWithServer]\n  );\n\n  // Add shipping address\n  const addShippingAddress = useCallback(\n    async (address: Address) => {\n      if (!state.data) {\n        throw new Error(_('Cannot add shipping address: cart not initialized'));\n      }\n\n      try {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingShippingAddress', loading: true }\n        });\n\n        const response = await retry(() =>\n          fetch(state.data!.addAddressApi, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ address: { ...address }, type: 'shipping' })\n          })\n        );\n\n        const json = await response.json();\n\n        if (!response.ok) {\n          throw new Error(\n            json.error?.message || _('Failed to add shipping address.')\n          );\n        }\n\n        // Sync with server (both immediate update and GraphQL refetch)\n        await syncCartWithServer(CartSyncTrigger.ADD_SHIPPING_ADDRESS);\n      } catch (error) {\n        dispatch({\n          type: 'SET_ERROR',\n          payload:\n            error instanceof Error\n              ? error.message\n              : _('Failed to add shipping address')\n        });\n        throw error;\n      } finally {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingShippingAddress', loading: false }\n        });\n      }\n    },\n    [state.data?.addAddressApi, syncCartWithServer]\n  );\n\n  // Add billing address\n  const addBillingAddress = useCallback(\n    async (address: Address) => {\n      if (!state.data) {\n        throw new Error(_('Cannot add billing address: cart not initialized'));\n      }\n\n      try {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingBillingAddress', loading: true }\n        });\n\n        const response = await retry(() =>\n          fetch(state.data!.addAddressApi, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ address: { ...address }, type: 'billing' })\n          })\n        );\n\n        const json = await response.json();\n\n        if (!response.ok) {\n          throw new Error(\n            json.error?.message || _('Failed to add billing address.')\n          );\n        }\n\n        // Sync with server (both immediate update and GraphQL refetch)\n        await syncCartWithServer(CartSyncTrigger.ADD_BILLING_ADDRESS);\n      } catch (error) {\n        dispatch({\n          type: 'SET_ERROR',\n          payload:\n            error instanceof Error\n              ? error.message\n              : _('Failed to add billing address')\n        });\n        throw error;\n      } finally {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingBillingAddress', loading: false }\n        });\n      }\n    },\n    [state.data?.addAddressApi, syncCartWithServer]\n  );\n\n  // Add contact info\n  const addContactInfo = useCallback(\n    async (contactInfo: { email: string }) => {\n      if (!state.data) {\n        throw new Error(_('Cannot add contact info: cart not initialized'));\n      }\n\n      try {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingContactInfo', loading: true }\n        });\n\n        const response = await retry(() =>\n          fetch(state.data!.addContactInfoApi, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify(contactInfo)\n          })\n        );\n\n        const json = await response.json();\n\n        if (!response.ok) {\n          throw new Error(\n            json.error?.message || _('Failed to add contact info.')\n          );\n        }\n\n        // Sync with server (both immediate update and GraphQL refetch)\n        await syncCartWithServer(CartSyncTrigger.ADD_CONTACT_INFO);\n      } catch (error) {\n        dispatch({\n          type: 'SET_ERROR',\n          payload:\n            error instanceof Error\n              ? error.message\n              : _('Failed to add contact info')\n        });\n        throw error;\n      } finally {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'addingContactInfo', loading: false }\n        });\n      }\n    },\n    [state.data?.addContactInfoApi, syncCartWithServer]\n  );\n\n  // Apply coupon\n  const applyCoupon = useCallback(\n    async (couponCode: string) => {\n      if (!state.data) {\n        throw new Error(_('Cannot apply coupon: cart not initialized'));\n      }\n\n      try {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'applyingCoupon', loading: true }\n        });\n\n        const response = await retry(() =>\n          fetch(state.data!.applyCouponApi, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ coupon: couponCode })\n          })\n        );\n\n        const json = await response.json();\n\n        if (!response.ok) {\n          throw new Error(json.error?.message || 'Failed to apply coupon.');\n        }\n\n        // Sync with server (both immediate update and GraphQL refetch)\n        await syncCartWithServer(CartSyncTrigger.APPLY_COUPON);\n      } catch (error) {\n        dispatch({\n          type: 'SET_ERROR',\n          payload:\n            error instanceof Error ? error.message : 'Failed to apply coupon'\n        });\n        throw error;\n      } finally {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'applyingCoupon', loading: false }\n        });\n      }\n    },\n    [state.data?.applyCouponApi, syncCartWithServer]\n  );\n\n  // Remove coupon\n  const removeCoupon = useCallback(async () => {\n    if (!state.data) {\n      throw new Error(_('Cannot remove coupon: cart not initialized'));\n    }\n    if (!state.data?.removeCouponApi) {\n      throw new Error(_('No coupon to remove'));\n    }\n\n    try {\n      dispatch({\n        type: 'SET_SPECIFIC_LOADING',\n        payload: { operation: 'removingCoupon', loading: true }\n      });\n\n      const response = await retry(() =>\n        fetch(state.data!.removeCouponApi as string, {\n          method: 'DELETE',\n          headers: { 'Content-Type': 'application/json' }\n        })\n      );\n\n      const json = await response.json();\n\n      if (!response.ok) {\n        throw new Error(json.error?.message || _('Failed to remove coupon.'));\n      }\n\n      // Sync with server (both immediate update and GraphQL refetch)\n      await syncCartWithServer(CartSyncTrigger.REMOVE_COUPON);\n    } catch (error) {\n      dispatch({\n        type: 'SET_ERROR',\n        payload:\n          error instanceof Error ? error.message : _('Failed to remove coupon')\n      });\n      throw error;\n    } finally {\n      dispatch({\n        type: 'SET_SPECIFIC_LOADING',\n        payload: { operation: 'removingCoupon', loading: false }\n      });\n    }\n  }, [state.data?.removeCouponApi, syncCartWithServer]);\n\n  // Check if shipping is required\n  // Note: Currently assumes all items require shipping\n  // If you need to support virtual/downloadable products, add a 'virtual' or 'requiresShipping' field to CartItem\n  const isShippingRequired = useCallback(() => {\n    if (!state.data) return false;\n    // If there are items in the cart, shipping is required\n    // This can be enhanced with a virtual/downloadable product check if needed\n    return state.data.items.length > 0;\n  }, [state.data?.items]);\n\n  // Check if cart is ready for checkout\n  const isReadyForCheckout = useCallback(() => {\n    if (!state.data) return false;\n\n    const hasItems = state.data.items.length > 0;\n    const hasBillingAddress = !!state.data.billingAddress;\n    const hasShippingAddress =\n      !isShippingRequired() || !!state.data.shippingAddress;\n    const noErrors = state.data.errors.length === 0;\n\n    return hasItems && hasBillingAddress && hasShippingAddress && noErrors;\n  }, [state.data, isShippingRequired]);\n\n  // Get validation errors\n  const getErrors = useCallback(() => {\n    return state.data?.errors ?? [];\n  }, [state.data?.errors]);\n\n  // Get cart ID\n  const getId = useCallback(() => {\n    return state.data?.uuid ?? null;\n  }, [state.data?.uuid]);\n\n  // Fetch available shipping methods based on address parameters and update cart state\n  const fetchAvailableShippingMethods = useCallback(\n    async (params: ShippingAddressParams) => {\n      if (!state.data?.uuid) {\n        throw new Error('Cannot fetch shipping methods: cart not initialized');\n      }\n\n      try {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'fetchingShippingMethods', loading: true }\n        });\n\n        const result = await client\n          .query(ShippingMethodsQuery, {\n            country: params.country,\n            province: params.province || null,\n            postcode: params.postcode || null\n          })\n          .toPromise();\n\n        if (result.error) {\n          throw new Error(\n            result.error.message || 'Failed to fetch shipping methods'\n          );\n        }\n\n        // Update cart state with new shipping methods\n        if (result.data?.myCart?.availableShippingMethods) {\n          dispatch({\n            type: 'SET_CART',\n            payload: {\n              availableShippingMethods:\n                result.data.myCart.availableShippingMethods\n            }\n          });\n        }\n      } catch (error) {\n        const errorMessage =\n          error instanceof Error\n            ? error.message\n            : 'Failed to fetch shipping methods';\n\n        dispatch({\n          type: 'SET_ERROR',\n          payload: errorMessage\n        });\n\n        throw new Error(errorMessage);\n      } finally {\n        dispatch({\n          type: 'SET_SPECIFIC_LOADING',\n          payload: { operation: 'fetchingShippingMethods', loading: false }\n        });\n      }\n    },\n    [state.data?.uuid, client]\n  );\n\n  const cartDispatch: CartDispatch = {\n    addItem,\n    removeItem,\n    updateItem,\n    addPaymentMethod,\n    addShippingMethod,\n    addShippingAddress,\n    addBillingAddress,\n    addContactInfo,\n    applyCoupon,\n    removeCoupon,\n    clearError,\n    isShippingRequired,\n    isReadyForCheckout,\n    getErrors,\n    getId,\n    fetchAvailableShippingMethods,\n    syncCartWithServer\n  };\n\n  return (\n    <CartStateContext.Provider value={state as CartState}>\n      <CartDispatchContext.Provider value={cartDispatch}>\n        {children}\n      </CartDispatchContext.Provider>\n    </CartStateContext.Provider>\n  );\n};\n\nexport const useCartState = (): CartState => {\n  const context = useContext(CartStateContext);\n  if (!context) {\n    throw new Error('useCartState must be used within a CartProvider');\n  }\n  return context;\n};\n\nexport const useCartDispatch = (): CartDispatch => {\n  const context = useContext(CartDispatchContext);\n  if (!context) {\n    throw new Error('useCartDispatch must be used within a CartProvider');\n  }\n  return context;\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/CartItems.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { useAppState } from '@components/common/context/app.js';\nimport {\n  useCartState,\n  useCartDispatch,\n  CartItem\n} from '@components/frontStore/cart/CartContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface CartItemsProps {\n  children: (props: {\n    items: CartItem[];\n    showPriceIncludingTax?: boolean;\n    loading: boolean;\n    isEmpty: boolean;\n    totalItems: number;\n    onRemoveItem: (itemId: string) => Promise<void>;\n  }) => React.ReactNode;\n}\n\nfunction CartItems({ children }: CartItemsProps) {\n  const { data: cart, loading } = useCartState();\n  const {\n    config: {\n      tax: { priceIncludingTax }\n    }\n  } = useAppState();\n  const { removeItem } = useCartDispatch();\n\n  const isEmpty = cart?.totalQty === 0;\n  const totalItems = cart?.totalQty || 0;\n\n  const handleRemoveItem = async (itemId: string) => {\n    await removeItem(itemId);\n  };\n\n  return (\n    <div className=\"cart-items\">\n      <Area id=\"cartItemsBefore\" noOuter />\n      {children\n        ? children({\n            items: cart?.items || [],\n            showPriceIncludingTax: priceIncludingTax,\n            loading,\n            isEmpty,\n            totalItems,\n            onRemoveItem: handleRemoveItem\n          })\n        : null}\n      <Area id=\"cartItemsAfter\" noOuter />\n    </div>\n  );\n}\n\nexport { CartItems };\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/CartSummaryItems.tsx",
    "content": "import { Image } from '@components/common/Image.js';\nimport { ProductNoThumbnail } from '@components/common/ProductNoThumbnail.js';\nimport { Skeleton } from '@components/common/ui/Skeleton.js';\nimport { CartItem } from '@components/frontStore/cart/CartContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nconst CartSummarySkeleton: React.FC<{ rows?: number }> = ({ rows = 2 }) => {\n  return (\n    <ul className=\"divide-y divide-border\">\n      {Array.from({ length: rows }).map((_, i) => (\n        <li key={i} className=\"flex items-center py-6 animate-pulse\">\n          <div className=\"relative mr-4\">\n            <div className=\"w-16 h-16 bg-gray-200 rounded border border-border p-2 box-border\" />\n            <span className=\"absolute -top-2 -right-2 bg-muted rounded-full w-6 h-6 flex items-center justify-center text-muted-foreground text-sm\">\n              {i + 1}\n            </span>\n          </div>\n          <div className=\"flex-1 min-w-0 items-start align-top\">\n            <Skeleton className=\"h-4 w-3/5 mb-2\" />\n            <Skeleton className=\"h-3 w-2/5 mb-1\" />\n          </div>\n          <div className=\"ml-auto text-right\">\n            <Skeleton className=\"h-4 w-16\" />\n          </div>\n        </li>\n      ))}\n    </ul>\n  );\n};\n\nconst CartSummaryItemsList: React.FC<{\n  items: CartItem[];\n  loading: boolean;\n  showPriceIncludingTax?: boolean;\n}> = ({ items, loading, showPriceIncludingTax }) => {\n  if (loading) {\n    return <CartSummarySkeleton rows={items.length} />;\n  }\n\n  if (items.length === 0) {\n    return (\n      <div className=\"text-center py-8 text-muted-foreground\">\n        <p className=\"text-base\">{_('Your cart is empty')}</p>\n        <p className=\"text-sm mt-2\">{_('Add some items to get started')}</p>\n      </div>\n    );\n  }\n\n  return (\n    <ul className=\"item__summary__list divide-y divide-divider mb-3\">\n      {items.map((item) => (\n        <li key={item.uuid} className=\"flex items-start py-3\">\n          <div className=\"relative mr-4 self-center\">\n            {item.thumbnail && (\n              <Image\n                width={100}\n                height={100}\n                src={item.thumbnail}\n                alt={item.productName}\n                className=\"w-16 h-16 object-cover rounded border border-border p-2 box-border\"\n              />\n            )}\n            {!item.thumbnail && (\n              <ProductNoThumbnail className=\"w-16 h-16 rounded border border-border p-2 box-border\" />\n            )}\n            <span className=\"absolute -top-2 -right-2 bg-muted text-muted-foreground rounded-full w-6 h-6 flex items-center justify-center text-sm\">\n              {item.qty}\n            </span>\n          </div>\n          <div className=\"flex-1 min-w-0 items-start align-top\">\n            <div className=\"font-semibold text-sm mb-1\">{item.productName}</div>\n            {item.variantOptions && item.variantOptions.length > 0 && (\n              <div className=\"space-y-1\">\n                {item.variantOptions.map((option) => (\n                  <div key={option.attributeCode} className=\"text-xs\">\n                    <span>{option.attributeName}</span>:{' '}\n                    <span className=\"text-muted-foreground\">\n                      {option.optionText}\n                    </span>\n                  </div>\n                ))}\n              </div>\n            )}\n          </div>\n          <div className=\"ml-auto text-right self-center\">\n            <div className=\"font-semibold\">\n              {showPriceIncludingTax\n                ? item.lineTotalInclTax.text\n                : item.lineTotal.text}\n            </div>\n          </div>\n        </li>\n      ))}\n    </ul>\n  );\n};\n\nexport { CartSummaryItemsList };\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/CartTotalSummary.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { useAppState } from '@components/common/context/app.js';\nimport { Skeleton } from '@components/common/ui/Skeleton.js';\nimport { useCartState } from '@components/frontStore/cart/CartContext.js';\nimport {\n  Coupon,\n  CouponState,\n  CouponActions\n} from '@components/frontStore/Coupon.js';\nimport { CouponForm } from '@components/frontStore/CouponForm.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { CircleX } from 'lucide-react';\nimport React from 'react';\n\nconst SkeletonValue: React.FC<{\n  children: React.ReactNode;\n  loading?: boolean;\n  className?: string;\n}> = ({ children, loading = false, className = '' }) => {\n  if (!loading) {\n    return <>{children}</>;\n  }\n\n  return (\n    <span className={`relative ${className}`}>\n      <span className=\"opacity-0\">{children}</span>\n      <Skeleton className=\"absolute top-0 left-0 w-full h-full\" />\n    </span>\n  );\n};\n\nconst Total: React.FC<{\n  total: string;\n  totalTaxAmount: string;\n  priceIncludingTax: boolean;\n  loading?: boolean;\n}> = ({ total, totalTaxAmount, priceIncludingTax, loading = false }) => {\n  return (\n    <div className=\"summary__row grand-total flex justify-between py-2\">\n      {(priceIncludingTax && (\n        <div>\n          <div className=\"font-bold\">\n            <span>{_('Total')}</span>\n          </div>\n          <div>\n            <span className=\"italic font-normal\">\n              ({_('Inclusive of tax ${totalTaxAmount}', { totalTaxAmount })})\n            </span>\n          </div>\n        </div>\n      )) || <span className=\"self-center font-bold\">{_('Total')}</span>}\n      <div>\n        <div />\n        <SkeletonValue loading={loading} className=\"grand-total-value\">\n          {total}\n        </SkeletonValue>\n      </div>\n    </div>\n  );\n};\n\nconst Tax: React.FC<{\n  showPriceIncludingTax: boolean;\n  amount: string;\n  loading?: boolean;\n}> = ({ showPriceIncludingTax, amount, loading = false }) => {\n  if (showPriceIncludingTax) {\n    return null;\n  }\n\n  return (\n    <div className=\"summary-row flex justify-between py-2\">\n      <span>{_('Tax')}</span>\n      <div>\n        <div />\n        <SkeletonValue loading={loading} className=\"text-right\">\n          {amount}\n        </SkeletonValue>\n      </div>\n    </div>\n  );\n};\n\nconst Subtotal: React.FC<{ subTotal: string; loading?: boolean }> = ({\n  subTotal,\n  loading = false\n}) => {\n  return (\n    <div className=\"flex justify-between gap-7 py-2\">\n      <div>{_('Sub total')}</div>\n      <SkeletonValue loading={loading} className=\"text-right\">\n        {subTotal}\n      </SkeletonValue>\n    </div>\n  );\n};\n\nconst Discount: React.FC<{\n  discountAmount: string;\n  coupon: string | undefined;\n  loading?: boolean;\n}> = ({ discountAmount, coupon, loading = false }) => {\n  if (!coupon) {\n    return (\n      <div className=\"gap-7 py-2\">\n        <CouponForm />\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex justify-between gap-7 py-2\">\n      <Coupon>\n        {(state: CouponState, actions: CouponActions) => (\n          <>\n            <div className=\"flex justify-start items-center gap-2\">\n              <SkeletonValue loading={loading} className=\"text-right\">\n                <span>{_('Discount(${coupon})', { coupon })}</span>\n              </SkeletonValue>\n              {!state.isLoading && (\n                <a\n                  href=\"#\"\n                  className=\"text-destructive\"\n                  onClick={async (e) => {\n                    e.preventDefault();\n                    await actions.removeCoupon();\n                  }}\n                >\n                  <CircleX className=\"w-3.5 h-3.5\" />\n                </a>\n              )}\n            </div>\n            <SkeletonValue loading={loading} className=\"text-right\">\n              {discountAmount}\n            </SkeletonValue>\n          </>\n        )}\n      </Coupon>\n    </div>\n  );\n};\n\nconst Shipping: React.FC<{\n  method: string | undefined;\n  cost: string | undefined;\n  noShippingRequired: boolean;\n  loading?: boolean;\n}> = ({ method, cost, noShippingRequired, loading = false }) => {\n  return (\n    <div className=\"summary-row flex justify-between gap-7 py-2\">\n      {noShippingRequired && (\n        <>\n          <span>{_('Shipping')}</span>\n          <span className=\"text-gray-500 italic font-normal\">\n            {_('No shipping required')}\n          </span>\n        </>\n      )}\n      {method && !noShippingRequired && (\n        <>\n          <span>{_('Shipping (${method})', { method })}</span>\n          <div>\n            <SkeletonValue loading={loading}>{cost}</SkeletonValue>\n          </div>\n        </>\n      )}\n      {!method && !noShippingRequired && (\n        <>\n          <span>{_('Shipping')}</span>\n          <span className=\"text-gray-500 italic font-normal\">\n            {_('Select shipping method')}\n          </span>\n        </>\n      )}\n    </div>\n  );\n};\n\nconst DefaultCartSummary: React.FC<{\n  loading: boolean;\n  showPriceIncludingTax: boolean;\n  noShippingRequired: boolean;\n  subTotal: string;\n  discountAmount: string;\n  coupon: string | undefined;\n  shippingMethod: string | undefined;\n  shippingCost: string | undefined;\n  taxAmount: string;\n  total: string;\n}> = ({\n  loading,\n  showPriceIncludingTax,\n  noShippingRequired,\n  subTotal,\n  discountAmount,\n  coupon,\n  shippingMethod,\n  shippingCost,\n  taxAmount,\n  total\n}) => (\n  <div className=\"cart__total__summary font-semibold\">\n    <Area id=\"cartSummaryBeforeSubTotal\" noOuter />\n    <Subtotal subTotal={subTotal} loading={loading} />\n    <Area id=\"cartSummaryAfterSubTotal\" noOuter />\n    <Area id=\"cartSummaryBeforeDiscount\" noOuter />\n    <Discount\n      discountAmount={discountAmount}\n      coupon={coupon}\n      loading={loading}\n    />\n    <Area id=\"cartSummaryAfterDiscount\" noOuter />\n    <Area id=\"cartSummaryBeforeShipping\" noOuter />\n    <Shipping\n      method={shippingMethod}\n      cost={shippingCost}\n      loading={loading}\n      noShippingRequired={noShippingRequired}\n    />\n    <Area id=\"cartSummaryAfterShipping\" noOuter />\n    <Area id=\"cartSummaryBeforeTax\" noOuter />\n    <Tax\n      amount={taxAmount}\n      showPriceIncludingTax={showPriceIncludingTax}\n      loading={loading}\n    />\n    <Area id=\"cartSummaryAfterTax\" noOuter />\n    <Area id=\"cartSummaryBeforeTotal\" noOuter />\n    <Total\n      total={total}\n      totalTaxAmount={taxAmount}\n      priceIncludingTax={showPriceIncludingTax}\n      loading={loading}\n    />\n    <Area id=\"cartSummaryAfterTotal\" noOuter />\n  </div>\n);\n\ninterface CartTotalSummaryProps {\n  children?: (props: {\n    loading: boolean;\n    showPriceIncludingTax: boolean;\n    noShippingRequired: boolean;\n    subTotal: string;\n    discountAmount: string;\n    coupon: string | undefined;\n    shippingMethod: string | undefined;\n    shippingCost: string | undefined;\n    taxAmount: string;\n    total: string;\n  }) => React.ReactNode;\n}\n\nfunction CartTotalSummary({ children }: CartTotalSummaryProps) {\n  const { data: cart, loadingStates } = useCartState();\n  const {\n    config: {\n      tax: { priceIncludingTax }\n    }\n  } = useAppState();\n\n  const subTotal = priceIncludingTax\n    ? cart?.subTotalInclTax?.text || ''\n    : cart?.subTotal?.text || '';\n\n  const discountAmount = cart?.discountAmount?.text || '';\n  const coupon = cart?.coupon;\n\n  const shippingMethod = cart?.shippingMethodName;\n  const shippingCost = priceIncludingTax\n    ? cart?.shippingFeeInclTax?.text || ''\n    : cart?.shippingFeeExclTax?.text || '';\n\n  const taxAmount = cart?.totalTaxAmount?.text || '';\n  const total = cart?.grandTotal?.text || '';\n\n  return (\n    <div className=\"grid grid-cols-1 gap-3\">\n      {children ? (\n        children({\n          loading: Object.values(loadingStates).some(\n            (state) =>\n              state === true || (typeof state === 'string' && state !== null)\n          ),\n          showPriceIncludingTax: priceIncludingTax,\n          noShippingRequired: cart?.noShippingRequired || false,\n          subTotal,\n          discountAmount,\n          coupon,\n          shippingMethod,\n          shippingCost,\n          taxAmount,\n          total\n        })\n      ) : (\n        <DefaultCartSummary\n          loading={Object.values(loadingStates).some(\n            (state) =>\n              state === true || (typeof state === 'string' && state !== null)\n          )}\n          showPriceIncludingTax={priceIncludingTax}\n          noShippingRequired={cart?.noShippingRequired || false}\n          subTotal={subTotal}\n          discountAmount={discountAmount}\n          coupon={coupon}\n          shippingMethod={shippingMethod}\n          shippingCost={shippingCost}\n          taxAmount={taxAmount}\n          total={total}\n        />\n      )}\n    </div>\n  );\n}\n\nexport {\n  CartTotalSummary,\n  DefaultCartSummary,\n  Subtotal,\n  Discount,\n  Shipping,\n  Tax,\n  Total\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/DefaultCartItemList.tsx",
    "content": "import { Area } from '@components/common/Area.js';\nimport {\n  ExtendableTable,\n  TableColumn\n} from '@components/common/ExtendableTable.js';\nimport { Image } from '@components/common/Image.js';\nimport { ProductNoThumbnail } from '@components/common/ProductNoThumbnail.js';\nimport { CartItem } from '@components/frontStore/cart/CartContext.js';\nimport { ItemQuantity } from '@components/frontStore/cart/ItemQuantity.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface CartItemsTableProps {\n  items: CartItem[];\n  showPriceIncludingTax?: boolean;\n  loading?: boolean;\n  onSort?: (key: string, direction: 'asc' | 'desc') => void;\n  currentSort?: { key: string; direction: 'asc' | 'desc' };\n  onRemoveItem?: (itemId: string) => Promise<void>;\n}\n\nexport const DefaultCartItemList: React.FC<CartItemsTableProps> = ({\n  items,\n  showPriceIncludingTax = true,\n  loading = false,\n  onSort,\n  currentSort,\n  onRemoveItem\n}) => {\n  const columns: TableColumn<CartItem>[] = [\n    {\n      key: 'productInfo',\n      header: { label: _('Product'), className: '' },\n      className: 'font-medium align-top',\n      sortable: false,\n      render: (row) => {\n        const priceValue = showPriceIncludingTax\n          ? row.productPriceInclTax?.text\n          : row.productPrice?.text;\n        return (\n          <div className=\"flex justify-start gap-4\">\n            <div className=\"shrink-0\">\n              {row.thumbnail ? (\n                <Image\n                  src={row.thumbnail}\n                  alt={row.productName}\n                  width={80}\n                  height={80}\n                  className=\"rounded-md\"\n                />\n              ) : (\n                <ProductNoThumbnail width={80} height={80} />\n              )}\n            </div>\n            <div className=\"font-medium flex flex-col gap-1 items-start h-full min-w-0 flex-1\">\n              <div className=\"font-semibold wrap-break-word w-full\">\n                {row.productName}\n              </div>\n              {row.variantOptions?.map((option) => (\n                <span key={option.optionId} className=\"text-xs\">\n                  <span>{option.attributeName}</span>:{' '}\n                  <span className=\"text-muted-foreground\">\n                    {option.optionText}\n                  </span>\n                </span>\n              ))}\n              <span className=\"text-sm text-muted-foreground\">\n                {priceValue} x {row.qty}\n              </span>\n              <a\n                href=\"#\"\n                className=\"text-destructive text-sm\"\n                onClick={(e) => {\n                  e.preventDefault();\n                  onRemoveItem?.(row.cartItemId);\n                }}\n              >\n                {_('Remove')}\n              </a>\n              {row.errors?.map((error, index) => (\n                <span key={index} className=\"text-xs text-destructive\">\n                  {error}\n                </span>\n              ))}\n            </div>\n          </div>\n        );\n      }\n    },\n    {\n      key: 'qty',\n      header: { label: _('Quantity'), className: 'text-right' },\n      sortable: true,\n      render: (row) => {\n        return (\n          <div className=\"flex justify-end\">\n            <ItemQuantity\n              initialValue={row.qty}\n              cartItemId={row.cartItemId}\n              min={1}\n              max={99}\n            >\n              {({ quantity, increase, decrease }) => (\n                <div className=\"flex items-center\">\n                  <button\n                    onClick={decrease}\n                    disabled={loading || quantity <= 1}\n                    className=\"px-1 disabled:opacity-50 text-lg\"\n                  >\n                    −\n                  </button>\n                  <span className=\"min-w-12 text-center\">{quantity}</span>\n                  <button\n                    onClick={increase}\n                    disabled={loading}\n                    className=\"disabled:opacity-50 text-lg\"\n                  >\n                    +\n                  </button>\n                </div>\n              )}\n            </ItemQuantity>\n          </div>\n        );\n      }\n    },\n    {\n      key: 'lineTotal',\n      header: { label: _('Total'), className: 'text-right' },\n      sortable: true,\n      render: (row) => {\n        const totalValue = showPriceIncludingTax\n          ? row.lineTotalInclTax?.text\n          : row.lineTotal?.text;\n        return (\n          <div className=\"text-right\">\n            <span className=\"font-bold\">{totalValue}</span>\n          </div>\n        );\n      }\n    }\n  ];\n\n  const [rows, setRows] = React.useState<CartItem[]>(items);\n\n  React.useEffect(() => {\n    setRows(items);\n  }, [items]);\n\n  return (\n    <>\n      <Area id=\"cartItemListBefore\" noOuter />\n      <ExtendableTable\n        name=\"shoppingCartItems\"\n        columns={columns}\n        initialData={rows}\n        loading={loading}\n        emptyMessage={_('Your cart is empty')}\n        onSort={onSort}\n        currentSort={currentSort}\n        className=\"cart__items__table border-none table-fixed border-spacing-y-2 border-separate w-full\"\n      />\n      <Area id=\"cartItemListAfter\" noOuter />\n      <style>\n        {`\n        .cart__items__table th, .cart__items__table td {\n          padding: 0.75rem;\n          white-space: normal;\n        }\n        .cart__items__table th {\n          border: none;\n        }\n        .cart__items__table td {\n          border: none;\n        }\n        .cart__items__table th:first-child,\n        .cart__items__table td:first-child {\n          width: 60%;\n        }\n        .cart__items__table th:nth-child(2),\n        .cart__items__table td:nth-child(2) {\n          width: 25%;\n        }\n      `}\n      </style>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/DefaultMiniCartDropdown.tsx",
    "content": "import { Area } from '@components/common/Area.js';\nimport {\n  Sheet,\n  SheetContent,\n  SheetHeader,\n  SheetTitle\n} from '@components/common/ui/Sheet.js';\nimport { CartData } from '@components/frontStore/cart/CartContext.js';\nimport { CartItems } from '@components/frontStore/cart/CartItems.js';\nimport { CartTotalSummary } from '@components/frontStore/cart/CartTotalSummary.js';\nimport { DefaultMiniCartDropdownEmpty } from '@components/frontStore/cart/DefaultMiniCartDropdownEmpty.js';\nimport { DefaultMiniCartDropdownSummary } from '@components/frontStore/cart/DefaultMinicartDropdownSummary.js';\nimport { DefaultMiniCartItemList } from '@components/frontStore/cart/DefaultMiniCartItemList.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport const DefaultMiniCartDropdown: React.FC<{\n  cart: CartData | null;\n  isOpen: boolean;\n  onClose: () => void;\n  cartUrl?: string;\n  checkoutUrl?: string;\n  dropdownPosition?: 'left' | 'right';\n  setIsDropdownOpen: (isOpen: boolean) => void;\n}> = ({\n  cart,\n  isOpen,\n  onClose,\n  cartUrl,\n  checkoutUrl,\n  dropdownPosition = 'right',\n  setIsDropdownOpen\n}) => {\n  const totalQty = cart?.totalQty || 0;\n\n  return (\n    <Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>\n      <SheetContent\n        side={dropdownPosition}\n        className=\"w-full md:w-1/3 border-border\"\n      >\n        <SheetHeader className=\"border-b border-border\">\n          <SheetTitle className=\"font-medium text-xl\">\n            {_('Your Cart')}\n          </SheetTitle>\n        </SheetHeader>\n        {totalQty === 0 ? (\n          <DefaultMiniCartDropdownEmpty setIsDropdownOpen={setIsDropdownOpen} />\n        ) : (\n          <div\n            className=\"minicart__items__container flex flex-col px-5 justify-between h-full\"\n            style={{ height: 'calc(100vh - 150px)' }}\n          >\n            <Area id=\"miniCartItemsBefore\" noOuter />\n            <div className=\"overflow-y-auto mb-8\">\n              <CartItems>\n                {({ items, loading }) => (\n                  <DefaultMiniCartItemList items={items} loading={loading} />\n                )}\n              </CartItems>\n            </div>\n            <Area id=\"miniCartItemsAfter\" noOuter />\n            <Area id=\"miniCartSummaryBefore\" noOuter />\n            <CartTotalSummary>\n              {({ total }) => (\n                <DefaultMiniCartDropdownSummary\n                  total={total}\n                  cartUrl={cartUrl || '/cart'}\n                  checkoutUrl={checkoutUrl || '/checkout'}\n                  totalQty={totalQty}\n                />\n              )}\n            </CartTotalSummary>\n            <Area id=\"miniCartSummaryAfter\" noOuter />\n          </div>\n        )}\n      </SheetContent>\n    </Sheet>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/DefaultMiniCartDropdownEmpty.tsx",
    "content": "import { Area } from '@components/common/Area.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { ShoppingBag } from 'lucide-react';\nimport React from 'react';\n\nexport const DefaultMiniCartDropdownEmpty: React.FC<{\n  setIsDropdownOpen: (isOpen: boolean) => void;\n}> = ({ setIsDropdownOpen }) => (\n  <div className=\"minicart__empty p-8 text-center\">\n    <Area id=\"miniCartEmptyBefore\" noOuter />\n    <ShoppingBag\n      width={48}\n      height={48}\n      className=\"mx-auto text-muted-foreground mb-4\"\n    />\n    <p className=\"text-muted-foreground mb-4\">{_('Your cart is empty')}</p>\n    <Button\n      variant=\"default\"\n      onClick={() => setIsDropdownOpen(false)}\n      size={'lg'}\n    >\n      {_('Continue Shopping')}\n    </Button>\n    <Area id=\"miniCartEmptyAfter\" noOuter />\n  </div>\n);\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/DefaultMiniCartIcon.tsx",
    "content": "import { ShoppingBag } from 'lucide-react';\nimport React from 'react';\n\nexport const DefaultMiniCartIcon = ({\n  totalQty,\n  onClick,\n  isOpen,\n  disabled = false,\n  showItemCount = true,\n  syncStatus\n}: {\n  totalQty: number;\n  onClick: () => void;\n  isOpen: boolean;\n  disabled?: boolean;\n  showItemCount?: boolean;\n  syncStatus: { syncing: boolean };\n}) => {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      disabled={disabled}\n      className={`mini-cart-icon relative ${\n        disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'\n      } ${isOpen ? 'active' : ''}`}\n      aria-label={`Shopping cart with ${totalQty} items`}\n    >\n      {syncStatus.syncing ? (\n        <div className=\"w-6 h-6 flex items-center justify-center\">\n          <div className=\"animate-spin rounded-full h-4 w-4 border-2 border-border\"></div>\n        </div>\n      ) : (\n        <ShoppingBag className=\"w-5 h-5 text-foreground hover:text-primary\" />\n      )}\n      {showItemCount && totalQty > 0 && !syncStatus.syncing && (\n        <span className=\"absolute -top-2 -right-2 bg-primary text-primary-foreground text-xs rounded-full h-4 w-4 flex items-center justify-center font-normal\">\n          {totalQty > 99 ? '99+' : totalQty}\n        </span>\n      )}\n    </button>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/DefaultMiniCartItemList.tsx",
    "content": "import { CartItem } from '@components/frontStore/cart/CartContext.js';\nimport { CartSummaryItemsList } from '@components/frontStore/cart/CartSummaryItems.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface CartItemsTableProps {\n  items: CartItem[];\n  showPriceIncludingTax?: boolean;\n  loading?: boolean;\n  onSort?: (key: string, direction: 'asc' | 'desc') => void;\n  currentSort?: { key: string; direction: 'asc' | 'desc' };\n}\n\nexport const DefaultMiniCartItemList: React.FC<CartItemsTableProps> = ({\n  items,\n  showPriceIncludingTax = true,\n  loading = false\n}) => {\n  return (\n    <CartSummaryItemsList\n      items={items}\n      loading={loading}\n      showPriceIncludingTax={showPriceIncludingTax}\n    />\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/DefaultMinicartDropdownSummary.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport function DefaultMiniCartDropdownSummary({\n  total,\n  cartUrl,\n  checkoutUrl,\n  totalQty\n}: {\n  total: string;\n  cartUrl: string;\n  checkoutUrl: string;\n  totalQty: number;\n}) {\n  return (\n    <>\n      <div className=\"minicart__summary flex justify-between items-center mb-3\">\n        <span className=\"font-medium text-gray-900\">{_('Subtotal')}:</span>\n        <span className=\"font-semibold text-lg text-gray-900\">\n          {total || '—'}\n        </span>\n      </div>\n      <Area id=\"miniCartSummaryViewCartButtonBefore\" noOuter />\n      <Button\n        variant={'outline'}\n        size={'lg'}\n        onClick={() => {\n          if (cartUrl) {\n            window.location.href = cartUrl;\n          }\n        }}\n        className=\"minicart__viewcart__button w-full \"\n      >\n        {_('View Cart (${totalQty})', {\n          totalQty: totalQty.toString()\n        })}\n      </Button>\n      <Area id=\"miniCartSummaryViewCartButtonAfter\" noOuter />\n      <Area id=\"miniCartSummaryCheckoutButtonBefore\" noOuter />\n      <Button\n        variant={'default'}\n        size={'lg'}\n        onClick={() => {\n          if (checkoutUrl) {\n            window.location.href = checkoutUrl;\n          }\n        }}\n        className=\"minicart__checkout__button w-full \"\n      >\n        {_('Checkout')}\n      </Button>\n      <Area id=\"miniCartSummaryCheckoutButtonAfter\" noOuter />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/ItemQuantity.tsx",
    "content": "import {\n  useCartDispatch,\n  useCartState\n} from '@components/frontStore/cart/CartContext.js';\nimport React, {\n  useState,\n  useCallback,\n  useEffect,\n  ReactNode,\n  useRef\n} from 'react';\n\ninterface UseItemQuantityProps {\n  initialValue?: number;\n  min?: number;\n  max?: number;\n  onChange?: (quantity: number) => void;\n  cartItemId: string;\n  debounce?: number;\n  onSuccess?: () => void;\n  onFailure?: (error: Error) => void;\n}\n\ninterface UseItemQuantityReturn {\n  quantity: number;\n  loading: boolean;\n  increase: () => void;\n  decrease: () => void;\n  setQuantity: (quantity: number) => void;\n  inputProps: {\n    value: number;\n    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n    onBlur: () => void;\n    type: 'number';\n    min?: number;\n    max?: number;\n    disabled: boolean;\n  };\n}\n\nexport const useItemQuantity = ({\n  initialValue = 1,\n  min = 1,\n  max = Infinity,\n  onChange,\n  cartItemId,\n  debounce = 500,\n  onSuccess,\n  onFailure\n}: UseItemQuantityProps): UseItemQuantityReturn => {\n  const [quantity, setInternalQuantity] = useState(initialValue);\n\n  const cartDispatch = useCartDispatch();\n  const { loading } = useCartState();\n  const previousQuantityRef = useRef(initialValue);\n  const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);\n\n  useEffect(() => {\n    setInternalQuantity(initialValue);\n    previousQuantityRef.current = initialValue;\n  }, [initialValue]);\n\n  const executeCartUpdate = useCallback(\n    async (newQuantity: number) => {\n      try {\n        const currentQuantity = previousQuantityRef.current;\n        const diff = newQuantity - currentQuantity;\n\n        if (diff === 0) {\n          return;\n        }\n\n        const action = diff > 0 ? 'increase' : 'decrease';\n        const qty = Math.abs(diff);\n\n        await cartDispatch.updateItem(cartItemId, { qty, action });\n        previousQuantityRef.current = newQuantity;\n        if (onChange) {\n          onChange(newQuantity);\n        }\n        if (onSuccess) {\n          onSuccess();\n        }\n      } catch (error) {\n        // Revert to the last known good state on failure\n        setInternalQuantity(previousQuantityRef.current);\n        if (onFailure) {\n          onFailure(error as Error);\n        }\n      }\n    },\n    [cartItemId, cartDispatch, onChange, onSuccess, onFailure]\n  );\n\n  const handleUpdate = useCallback(\n    (newQuantity: number, immediate = false) => {\n      const clampedQuantity = Math.max(min, Math.min(newQuantity, max));\n      setInternalQuantity(clampedQuantity);\n\n      if (debounceTimerRef.current) {\n        clearTimeout(debounceTimerRef.current);\n      }\n\n      if (clampedQuantity === previousQuantityRef.current) {\n        return;\n      }\n\n      if (immediate || debounce === 0) {\n        executeCartUpdate(clampedQuantity);\n      } else {\n        debounceTimerRef.current = setTimeout(() => {\n          executeCartUpdate(clampedQuantity);\n        }, debounce);\n      }\n    },\n    [min, max, quantity, debounce, executeCartUpdate]\n  );\n\n  const increase = useCallback(() => {\n    handleUpdate(quantity + 1);\n  }, [quantity, handleUpdate]);\n\n  const decrease = useCallback(() => {\n    handleUpdate(quantity - 1);\n  }, [quantity, handleUpdate]);\n\n  const setQuantity = useCallback(\n    (newQuantity: number) => {\n      handleUpdate(newQuantity);\n    },\n    [handleUpdate]\n  );\n\n  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const newQuantity = parseInt(e.target.value, 10);\n    if (!isNaN(newQuantity)) {\n      setInternalQuantity(newQuantity);\n    } else if (e.target.value === '') {\n      setInternalQuantity(0);\n    }\n  };\n\n  const handleInputBlur = () => {\n    handleUpdate(quantity, true);\n  };\n\n  return {\n    quantity,\n    loading,\n    increase,\n    decrease,\n    setQuantity,\n    inputProps: {\n      value: quantity,\n      onChange: handleInputChange,\n      onBlur: handleInputBlur,\n      type: 'number',\n      min,\n      max,\n      disabled: loading\n    }\n  };\n};\n\ninterface ItemQuantityProps extends UseItemQuantityProps {\n  children: (props: UseItemQuantityReturn) => ReactNode;\n}\n\n/**\n * Item Quantity Component.\n * @param {ItemQuantityProps} props\n * @returns {ReactNode}\n */\nexport function ItemQuantity({\n  children,\n  ...props\n}: ItemQuantityProps): ReactNode {\n  const quantityControls = useItemQuantity(props);\n  return children(quantityControls);\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/MiniCart.tsx",
    "content": "import {\n  useCartState,\n  CartData,\n  CartSyncTrigger\n} from '@components/frontStore/cart/CartContext.js';\nimport { DefaultMiniCartDropdown } from '@components/frontStore/cart/DefaultMiniCartDropdown.js';\nimport { DefaultMiniCartIcon } from '@components/frontStore/cart/DefaultMiniCartIcon.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useCallback, useState, useEffect } from 'react';\n\ninterface MiniCartProps {\n  cartUrl?: string;\n  checkoutUrl?: string;\n  dropdownPosition?: 'left' | 'right';\n  showItemCount?: boolean;\n  CartIconComponent?: React.FC<{\n    totalQty: number;\n    onClick: () => void;\n    isOpen: boolean;\n    disabled?: boolean;\n    showItemCount?: boolean;\n    syncStatus: { syncing: boolean };\n  }>;\n  CartDropdownComponent?: React.FC<{\n    cart: CartData | null;\n    dropdownPosition?: 'left' | 'right';\n    onClose: () => void;\n    cartUrl?: string;\n    setIsDropdownOpen: (isOpen: boolean) => void;\n  }>;\n  onItemRemove?: (itemId: string) => Promise<void> | void;\n  className?: string;\n  disabled?: boolean;\n}\n\nexport function MiniCart({\n  cartUrl = '/cart',\n  checkoutUrl = '/checkout',\n  dropdownPosition = 'right',\n  showItemCount = true,\n  CartIconComponent,\n  CartDropdownComponent,\n  className = '',\n  disabled = false\n}: MiniCartProps) {\n  const { data: cartData, syncStatus } = useCartState();\n  const [isDropdownOpen, setIsDropdownOpen] = useState(false);\n\n  const cart = cartData;\n\n  const handleCartClick = useCallback(() => {\n    if (disabled) return;\n\n    setIsDropdownOpen(!isDropdownOpen);\n  }, [disabled, isDropdownOpen, cartUrl]);\n\n  const handleDropdownClose = useCallback(() => {\n    setIsDropdownOpen(false);\n  }, []);\n\n  useEffect(() => {\n    if (syncStatus.synced && syncStatus.trigger === CartSyncTrigger.ADD_ITEM) {\n      setIsDropdownOpen(true);\n    }\n  }, [syncStatus.synced, syncStatus.trigger]);\n\n  return (\n    <div className={`mini__cart__wrapper relative ${className}`}>\n      {CartIconComponent ? (\n        <CartIconComponent\n          totalQty={cart?.totalQty || 0}\n          onClick={handleCartClick}\n          isOpen={isDropdownOpen}\n          disabled={disabled}\n          showItemCount={showItemCount}\n          syncStatus={syncStatus}\n        />\n      ) : (\n        <DefaultMiniCartIcon\n          totalQty={cart?.totalQty || 0}\n          onClick={handleCartClick}\n          isOpen={isDropdownOpen}\n          disabled={disabled}\n          showItemCount={showItemCount}\n          syncStatus={syncStatus}\n        />\n      )}\n\n      {CartDropdownComponent ? (\n        <CartDropdownComponent\n          cart={cart}\n          dropdownPosition={dropdownPosition}\n          onClose={handleDropdownClose}\n          cartUrl={cartUrl}\n          setIsDropdownOpen={setIsDropdownOpen}\n        />\n      ) : (\n        <DefaultMiniCartDropdown\n          cart={cart}\n          isOpen={isDropdownOpen}\n          dropdownPosition={dropdownPosition}\n          onClose={handleDropdownClose}\n          cartUrl={cartUrl}\n          checkoutUrl={checkoutUrl}\n          setIsDropdownOpen={setIsDropdownOpen}\n        />\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/cart/ShoppingCartEmpty.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport function ShoppingCartEmpty() {\n  return (\n    <div\n      className=\"empty-shopping-cart w-full flex justify-center\"\n      style={{ marginTop: '150px' }}\n    >\n      <div>\n        <div className=\"text-center shopping-cart-heading\">\n          <h2>{_('Shopping cart')}</h2>\n        </div>\n        <div className=\"mt-5 text-center\">\n          <span>{_('Your cart is empty!')}</span>\n        </div>\n        <div className=\"flex justify-center mt-5\">\n          <Button size={'lg'} onClick={() => (window.location.href = '/')}>\n            <span className=\"flex space-x-2\">\n              <span className=\"self-center\">{_('CONTINUE SHOPPING')}</span>{' '}\n              <svg\n                className=\"self-center\"\n                style={{ width: '2rem', height: '2rem' }}\n                xmlns=\"http://www.w3.org/2000/svg\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke=\"currentColor\"\n              >\n                <path\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  strokeWidth={1}\n                  d=\"M17 8l4 4m0 0l-4 4m4-4H3\"\n                />\n              </svg>\n            </span>\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/CategoryContext.tsx",
    "content": "import { Row } from '@components/common/form/Editor.js';\nimport { ProductData } from '@components/frontStore/catalog/ProductContext.js';\nimport {\n  CategoryFilter,\n  FilterableAttribute,\n  FilterInput\n} from '@components/frontStore/catalog/ProductFilter.js';\nimport React, { createContext, useContext, ReactNode } from 'react';\n\nexport interface CategoryProducts {\n  items: ProductData[];\n  currentFilters: FilterInput[];\n  total: number;\n}\n\nexport interface CategoryData {\n  categoryId: number;\n  uuid: string;\n  name: string;\n  description: Array<Row>;\n  url?: string;\n  image?: {\n    alt: string;\n    url: string;\n  };\n  showProducts: boolean;\n  products: CategoryProducts;\n  availableAttributes: FilterableAttribute[];\n  children: CategoryFilter[];\n  priceRange: {\n    min: number;\n    minText: string;\n    max: number;\n    maxText: string;\n  };\n  [extendedFields: string]: any;\n}\n\nconst CategoryContext = createContext<CategoryData | undefined>(undefined);\n\ninterface CategoryProviderProps {\n  children: ReactNode;\n  category: CategoryData;\n}\n\nexport const CategoryProvider: React.FC<CategoryProviderProps> = ({\n  children,\n  category\n}) => {\n  return (\n    <CategoryContext.Provider value={category}>\n      {children}\n    </CategoryContext.Provider>\n  );\n};\n\nexport const useCategory = (): CategoryData => {\n  const context = useContext(CategoryContext);\n  if (context === undefined) {\n    throw new Error('useCategory must be used within a CategoryProvider');\n  }\n  return context;\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/CategoryInfo.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Editor } from '@components/common/Editor.js';\nimport { Image } from '@components/common/Image.js';\nimport { useCategory } from '@components/frontStore/catalog/CategoryContext.js';\nimport React from 'react';\n\nexport function CategoryInfo() {\n  const { name, description, image } = useCategory();\n  return (\n    <>\n      <Area id=\"beforeCategoryInfo\" />\n      <div className=\"mb-2 md:mb-5 category__general\">\n        {image && (\n          <Image\n            className=\"category__image mb-5\"\n            src={image.url}\n            alt={image.alt || name}\n            width={1800}\n            height={1029}\n            priority={true}\n          />\n        )}\n        <div className=\"category__info prose prose-base page-width\">\n          <h1 className=\"category__name\">{name}</h1>\n          <div className=\"category__description\">\n            <Editor rows={description} />\n          </div>\n        </div>\n      </div>\n      <Area id=\"afterCategoryInfo\" />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/CategoryProducts.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { useCategory } from '@components/frontStore/catalog/CategoryContext.js';\nimport { ProductList } from '@components/frontStore/catalog/ProductList.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport function CategoryProducts() {\n  const { showProducts, products } = useCategory();\n  if (!showProducts) {\n    return null;\n  }\n  return (\n    <>\n      <Area\n        id=\"categoryProductsBefore\"\n        className=\"category__products__before\"\n      />\n      <div>\n        <ProductList\n          products={products.items}\n          layout=\"grid\"\n          gridColumns={3}\n          showAddToCart={true}\n        />\n        <span className=\"product-count italic block mt-5\">\n          {_('${count} products', { count: products.total.toString() })}\n        </span>\n      </div>\n      <Area id=\"categoryProductsAfter\" className=\"category__products__after\" />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/CategoryProductsFilter.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { useCategory } from '@components/frontStore/catalog/CategoryContext.js';\nimport { DefaultProductFilterRender } from '@components/frontStore/catalog/DefaultProductFilterRender.js';\nimport { ProductFilter } from '@components/frontStore/catalog/ProductFilter.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport function CategoryProductsFilter() {\n  const category = useCategory();\n  return (\n    <>\n      <Area id=\"beforeFilter\" noOuter />\n      <ProductFilter\n        currentFilters={category.products.currentFilters}\n        availableAttributes={category.availableAttributes}\n        categories={category.children}\n        priceRange={category.priceRange}\n      >\n        {(renderProps) => (\n          <DefaultProductFilterRender\n            renderProps={renderProps}\n            title={_('Product Filters')}\n            showFilterSummary={true}\n          />\n        )}\n      </ProductFilter>\n      <Area id=\"afterFilter\" noOuter />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/CategoryProductsPagination.tsx",
    "content": "import { useCategory } from '@components/frontStore/catalog/CategoryContext.js';\nimport {\n  Pagination,\n  DefaultPaginationRenderer\n} from '@components/frontStore/Pagination.js';\nimport React from 'react';\n\nexport function CategoryProductsPagination() {\n  const { showProducts, products } = useCategory();\n  if (!showProducts) {\n    return null;\n  }\n  const page = products.currentFilters.find((filter) => filter.key === 'page');\n  const limit = products.currentFilters.find(\n    (filter) => filter.key === 'limit'\n  );\n  return (\n    <Pagination\n      total={products.total}\n      limit={limit ? parseInt(limit.value, 10) : 20}\n      currentPage={parseInt(page?.value || '1', 10)}\n    >\n      {(paginationProps) => (\n        <DefaultPaginationRenderer renderProps={paginationProps} />\n      )}\n    </Pagination>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/DefaultAttributeFilterRender.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport {\n  FilterableAttribute,\n  FilterInput,\n  useProductFilter\n} from '@components/frontStore/catalog/ProductFilter.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useState } from 'react';\n\nexport const DefaultAttributeFilterRender: React.FC<{\n  availableAttributes: FilterableAttribute[];\n  currentFilters: FilterInput[];\n}> = ({ availableAttributes, currentFilters }) => {\n  const { updateFilter } = useProductFilter();\n  const [searchTerms, setSearchTerms] = useState<{ [key: string]: string }>({});\n  const [collapsedAttributes, setCollapsedAttributes] = useState<{\n    [key: string]: boolean;\n  }>({});\n\n  const handleAttributeChange = (\n    attributeCode: string,\n    optionId: string,\n    checked: boolean\n  ) => {\n    let newFilters = [...currentFilters];\n    const existingFilterIndex = newFilters.findIndex(\n      (f) => f.key === attributeCode\n    );\n\n    if (checked) {\n      if (existingFilterIndex !== -1) {\n        const existingFilter = newFilters[existingFilterIndex];\n        const values = existingFilter.value.split(',');\n        if (!values.includes(optionId)) {\n          values.push(optionId);\n          newFilters[existingFilterIndex] = {\n            ...existingFilter,\n            value: values.join(',')\n          };\n        }\n      } else {\n        newFilters.push({\n          key: attributeCode,\n          operation: 'in',\n          value: optionId\n        });\n      }\n    } else if (existingFilterIndex !== -1) {\n      const existingFilter = newFilters[existingFilterIndex];\n      const values = existingFilter.value\n        .split(',')\n        .filter((v) => v !== optionId);\n      if (values.length === 0) {\n        newFilters = newFilters.filter((f) => f.key !== attributeCode);\n      } else {\n        newFilters[existingFilterIndex] = {\n          ...existingFilter,\n          value: values.join(',')\n        };\n      }\n    }\n\n    updateFilter(newFilters);\n  };\n\n  const isOptionSelected = (attributeCode: string, optionId: string) => {\n    const filter = currentFilters.find((f) => f.key === attributeCode);\n    return filter\n      ? filter.value.split(',').includes(optionId.toString())\n      : false;\n  };\n\n  const getSelectedCount = (attributeCode: string) => {\n    const filter = currentFilters.find((f) => f.key === attributeCode);\n    return filter ? filter.value.split(',').length : 0;\n  };\n\n  const getFilteredOptions = (attribute: FilterableAttribute) => {\n    const searchTerm = searchTerms[attribute.attributeCode] || '';\n    if (!searchTerm) return attribute.options;\n\n    return attribute.options.filter((option) =>\n      option.optionText.toLowerCase().includes(searchTerm.toLowerCase())\n    );\n  };\n\n  const toggleCollapse = (attributeCode: string) => {\n    setCollapsedAttributes((prev) => ({\n      ...prev,\n      [attributeCode]: !prev[attributeCode]\n    }));\n  };\n\n  const clearAttributeFilter = (attributeCode: string) => {\n    const newFilters = currentFilters.filter((f) => f.key !== attributeCode);\n    updateFilter(newFilters);\n  };\n\n  return (\n    <>\n      {availableAttributes.map((attribute) => {\n        const selectedCount = getSelectedCount(attribute.attributeCode);\n        const filteredOptions = getFilteredOptions(attribute);\n        const isCollapsed = collapsedAttributes[attribute.attributeCode];\n\n        return (\n          <div\n            key={attribute.attributeCode}\n            className=\"attribute__filter__section border-b border-border pb-2 mb-2\"\n          >\n            <div className=\"filter__header flex items-center justify-between mb-3\">\n              <button\n                onClick={() => toggleCollapse(attribute.attributeCode)}\n                className=\"flex items-center justify-between text-left flex-1 hover:text-primary transition-colors\"\n              >\n                <span className=\"font-medium\">{attribute.attributeName}</span>\n                <svg\n                  className={`w-4 h-4 transition-transform ${\n                    isCollapsed ? 'rotate-180' : ''\n                  }`}\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  viewBox=\"0 0 24 24\"\n                >\n                  <path\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    strokeWidth={2}\n                    d=\"M19 9l-7 7-7-7\"\n                  />\n                </svg>\n              </button>\n\n              {selectedCount > 0 && (\n                <Button\n                  variant={'link'}\n                  onClick={() => clearAttributeFilter(attribute.attributeCode)}\n                  className=\"hover:text-destructive text-sm transition-colors\"\n                  title=\"Clear all\"\n                >\n                  ✕\n                </Button>\n              )}\n            </div>\n\n            {!isCollapsed && (\n              <div className=\"filter__content\">\n                {attribute.options.length > 5 && (\n                  <div className=\"mb-3\">\n                    <Checkbox\n                      value={searchTerms[attribute.attributeCode] || ''}\n                      onCheckedChange={(checked) =>\n                        setSearchTerms((prev) => ({\n                          ...prev,\n                          [attribute.attributeCode]: checked\n                            ? checked.toString()\n                            : ''\n                        }))\n                      }\n                    />\n                  </div>\n                )}\n\n                <div className=\"attribute__options space-y-2 max-h-48 overflow-y-auto\">\n                  {filteredOptions.length > 0 ? (\n                    filteredOptions.map((option) => {\n                      const isSelected = isOptionSelected(\n                        attribute.attributeCode,\n                        option.optionId.toString()\n                      );\n                      return (\n                        <div\n                          key={option.optionId}\n                          className={`flex items-center space-x-2 cursor-pointer py-2`}\n                        >\n                          <Checkbox\n                            checked={isSelected}\n                            id={`${attribute.attributeCode}-${option.optionId}`}\n                            onCheckedChange={(checked) =>\n                              handleAttributeChange(\n                                attribute.attributeCode,\n                                option.optionId.toString(),\n                                checked\n                              )\n                            }\n                          />\n                          <Label\n                            htmlFor={`${attribute.attributeCode}-${option.optionId}`}\n                          >\n                            {option.optionText}\n                          </Label>\n                        </div>\n                      );\n                    })\n                  ) : (\n                    <div className=\"text-muted-foreground text-sm text-center py-4\">\n                      {_('No options found for \"${code}\"', {\n                        code: searchTerms[attribute.attributeCode]\n                      })}\n                    </div>\n                  )}\n                </div>\n\n                {!searchTerms[attribute.attributeCode] &&\n                  attribute.options.length > 10 && (\n                    <Button\n                      variant={'link'}\n                      className=\"text-primary text-sm mt-2 hover:underline\"\n                    >\n                      {_('Show all ${count} options', {\n                        count: attribute.options.length.toString()\n                      })}\n                    </Button>\n                  )}\n              </div>\n            )}\n          </div>\n        );\n      })}\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/DefaultCategoryFilterRender.tsx",
    "content": "import { Checkbox } from '@components/common/ui/Checkbox.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport {\n  CategoryFilter,\n  FilterInput,\n  useProductFilter\n} from '@components/frontStore/catalog/ProductFilter.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useState } from 'react';\n\nexport const DefaultCategoryFilterRender: React.FC<{\n  categories: CategoryFilter[];\n  currentFilters: FilterInput[];\n}> = ({ categories, currentFilters }) => {\n  const { updateFilter } = useProductFilter();\n  const [searchTerm, setSearchTerm] = useState('');\n  const [isCollapsed, setIsCollapsed] = useState(false);\n\n  const handleCategoryChange = (categoryId: string, checked: boolean) => {\n    let newFilters = currentFilters.map((f) => ({ ...f }));\n    const existingFilter = newFilters.find((f) => f.key === 'cat');\n\n    if (checked) {\n      if (existingFilter) {\n        const values = existingFilter.value.split(',');\n        if (!values.includes(categoryId)) {\n          values.push(categoryId);\n          existingFilter.value = values.join(',');\n        }\n      } else {\n        newFilters.push({\n          key: 'cat',\n          operation: 'in',\n          value: categoryId\n        });\n      }\n    } else if (existingFilter) {\n      const values = existingFilter.value\n        .split(',')\n        .filter((v) => v !== categoryId);\n      if (values.length === 0) {\n        newFilters = newFilters.filter((f) => f.key !== 'cat');\n      } else {\n        existingFilter.value = values.join(',');\n      }\n    }\n\n    updateFilter(newFilters);\n  };\n\n  const isCategorySelected = (categoryId: string) => {\n    const filter = currentFilters.find((f) => f.key === 'cat');\n    return filter ? filter.value.split(',').includes(categoryId) : false;\n  };\n\n  const getSelectedCount = () => {\n    const filter = currentFilters.find((f) => f.key === 'cat');\n    return filter ? filter.value.split(',').length : 0;\n  };\n\n  const clearCategoryFilter = () => {\n    const newFilters = currentFilters.filter((f) => f.key !== 'cat');\n    updateFilter(newFilters);\n  };\n\n  const getFilteredCategories = () => {\n    if (!searchTerm) return categories;\n    return categories.filter((category) =>\n      category.name.toLowerCase().includes(searchTerm.toLowerCase())\n    );\n  };\n\n  if (!categories || categories.length === 0) {\n    return null;\n  }\n\n  const selectedCount = getSelectedCount();\n  const filteredCategories = getFilteredCategories();\n\n  return (\n    <div className=\"category__filter__section border-b border-border pb-2 mb-2\">\n      <div className=\"filter__header flex items-center justify-between mb-3\">\n        <button\n          onClick={() => setIsCollapsed(!isCollapsed)}\n          className=\"flex items-center justify-between text-left flex-1 hover:text-primary transition-colors\"\n        >\n          <span className=\"font-medium\">Categories</span>\n          <svg\n            className={`w-4 h-4 transition-transform ${\n              isCollapsed ? 'rotate-180' : ''\n            }`}\n            fill=\"none\"\n            stroke=\"currentColor\"\n            viewBox=\"0 0 24 24\"\n          >\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth={2}\n              d=\"M19 9l-7 7-7-7\"\n            />\n          </svg>\n        </button>\n\n        {selectedCount > 0 && (\n          <button\n            onClick={clearCategoryFilter}\n            className=\"text-muted-foreground hover:text-destructive text-sm transition-colors\"\n            title=\"Clear categories\"\n          >\n            ✕\n          </button>\n        )}\n      </div>\n\n      {!isCollapsed && (\n        <div className=\"filter__content\">\n          <div className=\"category__options space-y-2 max-h-48 overflow-y-auto\">\n            {filteredCategories.length > 0 ? (\n              filteredCategories.map((category) => {\n                const isSelected = isCategorySelected(\n                  category.categoryId.toString()\n                );\n                return (\n                  <div\n                    key={category.categoryId}\n                    className={`flex items-center space-x-3 cursor-pointer py-2`}\n                  >\n                    <Checkbox\n                      id={`category-${category.categoryId}`}\n                      checked={isSelected}\n                      onCheckedChange={(checked) =>\n                        handleCategoryChange(\n                          category.categoryId.toString(),\n                          checked\n                        )\n                      }\n                    />\n                    <Label htmlFor={`category-${category.categoryId}`}>\n                      {category.name}\n                    </Label>\n                  </div>\n                );\n              })\n            ) : (\n              <div className=\"text-gray-500 text-sm text-center py-4\">\n                {_('No categories found for \"${term}\"', { term: searchTerm })}\n              </div>\n            )}\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/DefaultFilterWrapperRender.tsx",
    "content": "import React from 'react';\n\nexport const DefaultFilterWrapperRender: React.FC<{\n  title: string;\n  children: React.ReactNode;\n}> = ({ title, children }) => (\n  <div className=\"filter__section\">\n    <div className=\"filter__title font-medium mb-3\">{title}</div>\n    <div className=\"filter__content\">{children}</div>\n  </div>\n);\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/DefaultPriceFilterRender.tsx",
    "content": "import { Slider } from '@components/common/ui/Slider.js';\nimport { DefaultFilterWrapperRender } from '@components/frontStore/catalog/DefaultFilterWrapperRender.js';\nimport {\n  PriceRange,\n  FilterInput,\n  useProductFilter,\n  ProductFilterProps\n} from '@components/frontStore/catalog/ProductFilter.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useState, useMemo } from 'react';\n\nexport const DefaultPriceFilterRender: React.FC<{\n  priceRange: PriceRange;\n  currentFilters: FilterInput[];\n  setting?: ProductFilterProps['setting'];\n}> = ({ priceRange, currentFilters, setting }) => {\n  const { updateFilter } = useProductFilter();\n\n  // Initialize from current filters\n  const [localMin, setLocalMin] = useState(() => {\n    const minFilter = currentFilters.find((f) => f.key === 'min_price');\n    return minFilter ? parseInt(minFilter.value) : priceRange.min;\n  });\n\n  const [localMax, setLocalMax] = useState(() => {\n    const maxFilter = currentFilters.find((f) => f.key === 'max_price');\n    return maxFilter ? parseInt(maxFilter.value) : priceRange.max;\n  });\n\n  const debouncedUpdate = useMemo(() => {\n    let timeoutId: NodeJS.Timeout;\n    return (min: number, max: number) => {\n      clearTimeout(timeoutId);\n      timeoutId = setTimeout(() => {\n        const newFilters = currentFilters.filter(\n          (f) => f.key !== 'min_price' && f.key !== 'max_price'\n        );\n\n        if (min > priceRange.min) {\n          newFilters.push({\n            key: 'min_price',\n            operation: 'eq',\n            value: min.toString()\n          });\n        }\n        if (max < priceRange.max) {\n          newFilters.push({\n            key: 'max_price',\n            operation: 'eq',\n            value: max.toString()\n          });\n        }\n\n        updateFilter(newFilters);\n      }, 300); // 300ms debounce\n    };\n  }, [currentFilters, priceRange, updateFilter]);\n\n  // Sync with external filter changes\n  React.useEffect(() => {\n    const minFilter = currentFilters.find((f) => f.key === 'min_price');\n    const maxFilter = currentFilters.find((f) => f.key === 'max_price');\n\n    setLocalMin(minFilter ? parseInt(minFilter.value) : priceRange.min);\n    setLocalMax(maxFilter ? parseInt(maxFilter.value) : priceRange.max);\n  }, [currentFilters, priceRange]);\n\n  const handleRangeChange = (values: number[]) => {\n    const [min, max] = values;\n    setLocalMin(min);\n    setLocalMax(max);\n    debouncedUpdate(min, max);\n  };\n\n  return (\n    <DefaultFilterWrapperRender title={_('Price')}>\n      <div className=\"price__filter border-b border-gray-200 pb-2 mb-2\">\n        <div className=\"price__slider mb-4\">\n          <Slider\n            min={priceRange.min}\n            max={priceRange.max}\n            value={[localMin, localMax]}\n            onValueChange={handleRangeChange}\n          />\n        </div>\n\n        <div className=\"flex justify-between text-small text-gray-500 mt-2\">\n          <span>{priceRange.minText}</span>\n          <span>{priceRange.maxText}</span>\n        </div>\n      </div>\n    </DefaultFilterWrapperRender>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/DefaultProductFilterRender.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Sheet,\n  SheetContent,\n  SheetHeader,\n  SheetTitle,\n  SheetFooter\n} from '@components/common/ui/Sheet.js';\nimport { DefaultAttributeFilterRender } from '@components/frontStore/catalog/DefaultAttributeFilterRender.js';\nimport { DefaultCategoryFilterRender } from '@components/frontStore/catalog/DefaultCategoryFilterRender.js';\nimport { DefaultPriceFilterRender as PriceFilterRenderer } from '@components/frontStore/catalog/DefaultPriceFilterRender.js';\nimport { DefaultProductFilterSummary } from '@components/frontStore/catalog/DefaultProductFilterSummary.js';\nimport {\n  ProductFilterRenderProps,\n  FilterComponent,\n  ProductFilterDispatch\n} from '@components/frontStore/catalog/ProductFilter.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { useState, useMemo } from 'react';\nimport React from 'react';\n\nexport const DefaultProductFilterRender: React.FC<{\n  renderProps: ProductFilterRenderProps;\n  className?: string;\n  title?: string;\n  showFilterSummary?: boolean;\n}> = ({\n  renderProps,\n  className = '',\n  title = _('Filter Products'),\n  showFilterSummary = true\n}) => {\n  const [isMobileFilterOpen, setIsMobileFilterOpen] = useState(false);\n\n  const {\n    currentFilters,\n    availableAttributes,\n    priceRange,\n    categories,\n    setting,\n    removeFilter,\n    updateFilter,\n    clearAllFilters,\n    isLoading,\n    activeFilterCount\n  } = renderProps;\n\n  const defaultComponents = useMemo(() => {\n    const components: FilterComponent[] = [];\n\n    if (priceRange && priceRange.min !== priceRange.max) {\n      components.push({\n        component: { default: PriceFilterRenderer },\n        props: { priceRange, currentFilters, setting },\n        sortOrder: 10,\n        id: 'priceFilter'\n      });\n    }\n\n    if (categories.length > 0) {\n      components.push({\n        component: { default: DefaultCategoryFilterRender },\n        props: { categories, currentFilters },\n        sortOrder: 15,\n        id: 'categoryFilter'\n      });\n    }\n\n    if (availableAttributes.length > 0) {\n      components.push({\n        component: { default: DefaultAttributeFilterRender },\n        props: { availableAttributes, currentFilters },\n        sortOrder: 20,\n        id: 'attributeFilter'\n      });\n    }\n\n    return components;\n  }, [availableAttributes, priceRange, categories, currentFilters, setting]);\n\n  const contextValue = useMemo(\n    () => ({ updateFilter, removeFilter, clearAllFilters }),\n    [updateFilter, removeFilter, clearAllFilters]\n  );\n\n  return (\n    <ProductFilterDispatch.Provider value={contextValue}>\n      <button\n        onClick={() => setIsMobileFilterOpen(true)}\n        className=\"md:hidden w-full flex items-center justify-center space-x-2 py-3 px-4 border border-gray-300 rounded-md bg-white hover:bg-gray-50 transition-colors\"\n      >\n        <svg\n          className=\"w-5 h-5\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          viewBox=\"0 0 24 24\"\n        >\n          <path\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            strokeWidth={2}\n            d=\"M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707v4.586a1 1 0 01-.293.707l-2 2A1 1 0 0111 21v-6.586a1 1 0 00-.293-.707L4.293 7.293A1 1 0 014 6.586V4z\"\n          />\n        </svg>\n        <span>{_('Filters')}</span>\n      </button>\n\n      <Sheet open={isMobileFilterOpen} onOpenChange={setIsMobileFilterOpen}>\n        <SheetContent\n          side=\"bottom\"\n          className=\"md:hidden max-h-[85vh] border-border\"\n        >\n          <SheetHeader>\n            <SheetTitle>{_('Filters')}</SheetTitle>\n          </SheetHeader>\n\n          <div className=\"flex-1 overflow-y-auto px-4 py-4\">\n            {showFilterSummary && (\n              <DefaultProductFilterSummary\n                availableAttributes={availableAttributes}\n                currentFilters={currentFilters}\n                priceRange={priceRange}\n                categories={categories}\n              />\n            )}\n\n            <div className={isLoading ? 'opacity-75 pointer-events-none' : ''}>\n              <Area\n                id=\"productFilter\"\n                noOuter\n                coreComponents={defaultComponents}\n                availableAttributes={availableAttributes}\n                priceRange={priceRange}\n                currentFilters={currentFilters}\n                categories={categories}\n                setting={setting}\n              />\n            </div>\n          </div>\n\n          <SheetFooter className=\"grid grid-cols-2 gap-3\">\n            <Button\n              variant=\"outline\"\n              onClick={clearAllFilters}\n              disabled={isLoading || activeFilterCount === 0}\n              className=\"flex-1\"\n            >\n              {_('Clear All')}\n            </Button>\n            <Button\n              variant=\"default\"\n              onClick={() => setIsMobileFilterOpen(false)}\n            >\n              {_('Apply Filters')}\n            </Button>\n          </SheetFooter>\n        </SheetContent>\n      </Sheet>\n\n      <div className={`hidden md:block product__filters ${className}`}>\n        <div className=\"product__filters__header flex items-center justify-between mb-4\">\n          {title && (\n            <h3 className=\"font-bold text-lg flex items-center\">{title}</h3>\n          )}\n\n          {activeFilterCount > 0 && (\n            <button\n              onClick={clearAllFilters}\n              disabled={isLoading}\n              className=\"text-sm hover:text-destructive transition-colors disabled:opacity-50\"\n            >\n              {_('Clear All')}\n            </button>\n          )}\n        </div>\n\n        {showFilterSummary && (\n          <DefaultProductFilterSummary\n            availableAttributes={availableAttributes}\n            currentFilters={currentFilters}\n            priceRange={priceRange}\n            categories={categories}\n          />\n        )}\n\n        <div className={isLoading ? 'opacity-75 pointer-events-none' : ''}>\n          <Area\n            id=\"productFilter\"\n            noOuter\n            coreComponents={defaultComponents}\n            availableAttributes={availableAttributes}\n            priceRange={priceRange}\n            currentFilters={currentFilters}\n            categories={categories}\n            setting={setting}\n          />\n        </div>\n      </div>\n    </ProductFilterDispatch.Provider>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/DefaultProductFilterSummary.tsx",
    "content": "import {\n  Item,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport {\n  CategoryFilter,\n  FilterableAttribute,\n  FilterInput,\n  PriceRange\n} from '@components/frontStore/catalog/ProductFilter.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport const formatPrice = (oldFormatted: string, price: number) => {\n  const match = oldFormatted.match(/^[^\\d.,]+/);\n  const currencySymbol = match ? match[0] : '';\n  return currencySymbol + price;\n};\n\nexport const getFilterSummary = (\n  availableAttributes,\n  currentFilters,\n  priceRange,\n  categories\n) => {\n  const summaries: string[] = [];\n\n  // Price filters\n  const minPrice = currentFilters.find((f) => f.key === 'min_price');\n  const maxPrice = currentFilters.find((f) => f.key === 'max_price');\n  if (minPrice || maxPrice) {\n    const min = minPrice?.value || priceRange?.min.toString() || '0';\n    const max = maxPrice?.value || priceRange?.max.toString() || '∞';\n    summaries.push(\n      _('Price: ${value}', {\n        value: `${formatPrice(\n          priceRange.minText,\n          parseInt(min)\n        )} - ${formatPrice(priceRange.maxText, parseInt(max))}`\n      })\n    );\n  }\n\n  const categoryFilter = currentFilters.find((f) => f.key === 'cat');\n  if (categoryFilter) {\n    const selectedCategoryIds = categoryFilter.value.split(',');\n    const selectedCategories = categories.filter((cat) =>\n      selectedCategoryIds.includes(cat.categoryId.toString())\n    );\n    if (selectedCategories.length > 0) {\n      summaries.push(\n        `${_('Categories')}: ${selectedCategories\n          .map((c) => c.name)\n          .join(', ')}`\n      );\n    }\n  }\n\n  availableAttributes.forEach((attr) => {\n    const filter = currentFilters.find((f) => f.key === attr.attributeCode);\n    if (filter) {\n      const selectedOptionIds = filter.value.split(',');\n      const selectedOptions = attr.options.filter((opt) =>\n        selectedOptionIds.includes(opt.optionId.toString())\n      );\n      if (selectedOptions.length > 0) {\n        summaries.push(\n          `${attr.attributeName}: ${selectedOptions\n            .map((o) => o.optionText)\n            .join(', ')}`\n        );\n      }\n    }\n  });\n\n  return summaries;\n};\n\nexport const DefaultProductFilterSummary: React.FC<{\n  availableAttributes: FilterableAttribute[];\n  currentFilters: FilterInput[];\n  priceRange?: PriceRange;\n  categories: CategoryFilter[];\n}> = ({ availableAttributes, currentFilters, priceRange, categories }) => {\n  const filterSummary = getFilterSummary(\n    availableAttributes,\n    currentFilters,\n    priceRange,\n    categories\n  );\n\n  if (filterSummary.length === 0) {\n    return null;\n  }\n  return (\n    <Item variant={'outline'} className=\"mb-3\">\n      <ItemContent>\n        <ItemTitle>{_('Active Filters')}</ItemTitle>\n        <ItemDescription>\n          <div className=\"space-y-2\">\n            {filterSummary.map((summary, index) => (\n              <div key={index} className=\"text-sm\">\n                {summary}\n              </div>\n            ))}\n          </div>\n        </ItemDescription>\n      </ItemContent>\n    </Item>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/DefaultVariantSelectorRender.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  VariantAttributeGroupProps,\n  VariantOptionItemProps\n} from '@components/frontStore/catalog/VariantSelector.js';\nimport React from 'react';\n\nconst DefaultVariantOptionItem: React.FC<VariantOptionItemProps> = ({\n  option,\n  attribute,\n  isSelected,\n  onSelect\n}) => {\n  let className = 'group ';\n  if (isSelected) {\n    className += 'selected';\n  }\n  if (option.available === false) {\n    className += 'un-available';\n  }\n\n  return (\n    <li key={option.optionId} className={className}>\n      <Button\n        variant={isSelected ? 'default' : 'outline'}\n        onClick={async (e) => {\n          e.preventDefault();\n          if (option.available === false) {\n            return;\n          }\n          await onSelect(attribute.attributeCode, option.optionId);\n        }}\n        className={'group-[.selected]:border-primary'}\n      >\n        {option.optionText}\n      </Button>\n    </li>\n  );\n};\n\nconst DefaultVariantAttribute: React.FC<VariantAttributeGroupProps> = ({\n  attribute,\n  options,\n  onSelect,\n  OptionItem = DefaultVariantOptionItem\n}) => {\n  return (\n    <div key={attribute.attributeCode}>\n      <div className=\"mb-2 text-textSubdued uppercase\">\n        <span>{attribute.attributeName}</span>\n      </div>\n      <ul className=\"variant-option-list flex justify-start gap-2 flex-wrap\">\n        {options.map((option) => (\n          <OptionItem\n            key={option.optionId}\n            option={option}\n            attribute={attribute}\n            isSelected={\n              attribute.selected && attribute.selectedOption === option.optionId\n            }\n            onSelect={onSelect}\n          />\n        ))}\n      </ul>\n    </div>\n  );\n};\n\nexport { DefaultVariantAttribute, DefaultVariantOptionItem };\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/Media.scss",
    "content": ".product-media-container {\n  position: relative;\n  width: 100%;\n  margin-bottom: 2rem;\n\n  .slick-arrow {\n    position: absolute;\n    bottom: 20px !important;\n    top: auto !important;\n    z-index: 10;\n    width: 40px;\n    height: 40px;\n    border-radius: 50%;\n    background-color: rgba(255, 255, 255, 0.7);\n    display: flex !important;\n    align-items: center;\n    justify-content: center;\n\n    &:hover {\n      background-color: rgba(255, 255, 255, 0.9);\n    }\n\n    &::before {\n      color: #333;\n      font-size: 16px;\n      opacity: 0.8;\n    }\n\n    &.slick-disabled {\n      opacity: 0.4;\n    }\n  }\n\n  .slick-prev {\n    right: 70px !important;\n    left: auto !important;\n  }\n\n  .slick-next {\n    right: 20px !important;\n  }\n\n  .main-image-container {\n    position: relative;\n    margin-bottom: 1rem;\n    border-radius: 8px;\n    overflow: hidden;\n\n    .product-image {\n      display: flex !important;\n      justify-content: center;\n      align-items: center;\n      background-color: #f7f7f7;\n      cursor: pointer;\n\n      img {\n        max-width: 100%;\n        height: auto;\n        object-fit: contain;\n      }\n    }\n  }\n\n  .slick-dots.slick-thumb {\n    position: static;\n    display: flex !important;\n    flex-wrap: wrap;\n    justify-content: center;\n    margin-top: 15px;\n    width: 100%;\n    padding-right: 130px;\n\n    li {\n      width: auto;\n      height: auto;\n      margin: 0 5px 10px;\n      border-radius: 4px;\n      transition: all 0.2s ease;\n      opacity: 0.7;\n\n      button {\n        padding: 0;\n        font-size: 0;\n        line-height: 0;\n      }\n\n      .thumbnail-wrapper {\n        width: 70px;\n        height: 70px;\n        border: 1px solid #e0e0e0;\n        border-radius: 4px;\n        padding: 2px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        overflow: hidden;\n        background: #fff;\n        transition: all 0.3s ease;\n      }\n\n      &.slick-active {\n        opacity: 1;\n        transform: scale(1.05);\n\n        .thumbnail-wrapper {\n          border-color: #999999;\n        }\n      }\n\n      &:hover .thumbnail-wrapper {\n        transform: translateY(-2px);\n      }\n\n      img {\n        max-width: 100%;\n        max-height: 100%;\n        object-fit: contain;\n      }\n    }\n  }\n\n  .product-image-modal .slick-dots.slick-thumb {\n    margin-top: 20px;\n\n    li .thumbnail-wrapper {\n      width: 60px;\n      height: 60px;\n      background: rgba(255, 255, 255, 0.9);\n    }\n  }\n  .product-image-modal {\n    position: fixed;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    z-index: 1000;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n\n    .modal-overlay {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      background-color: rgba(0, 0, 0, 0.7);\n    }\n\n    .modal-content {\n      position: relative;\n      width: 100%;\n      height: 100%;\n      z-index: 1001;\n      background-color: black;\n      overflow: hidden;\n\n      .modal-close {\n        position: absolute;\n        top: 20px;\n        right: 20px;\n        background: rgba(255, 255, 255, 0.2);\n        border: none;\n        color: white;\n        width: 40px;\n        height: 40px;\n        border-radius: 50%;\n        font-size: 24px;\n        line-height: 1;\n        cursor: pointer;\n        z-index: 1002;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        transition: background-color 0.2s;\n\n        &:hover {\n          background-color: rgba(0, 0, 0, 0.5);\n        }\n      }\n\n      .modal-slider-container {\n        width: 100%;\n        height: 100vh;\n        display: flex;\n        flex-direction: column;\n        justify-content: center;\n        position: relative;\n\n        .loading-indicator {\n          position: absolute;\n          top: 50%;\n          left: 50%;\n          transform: translate(-50%, -50%);\n          z-index: 1003;\n          color: white;\n\n          .spinner {\n            animation: spin 1.5s linear infinite;\n          }\n\n          @keyframes spin {\n            0% {\n              transform: rotate(0deg);\n            }\n            100% {\n              transform: rotate(360deg);\n            }\n          }\n        }\n\n        .modal-image {\n          display: flex !important;\n          justify-content: center;\n          align-items: center;\n          height: calc(100vh - 100px);\n\n          img {\n            max-width: 100%;\n            max-height: 90vh;\n            object-fit: contain;\n          }\n        }\n\n        .slick-dots.slick-thumb {\n          position: absolute;\n          bottom: 20px;\n          left: 0;\n          right: 0;\n          margin: 0;\n          padding: 0 170px 0 20px;\n\n          li {\n            margin: 0 8px;\n\n            .thumbnail-wrapper {\n              width: 60px;\n              height: 60px;\n              border-color: rgba(255, 255, 255, 0.3);\n              background-color: rgba(0, 0, 0, 0.3);\n              border-radius: 4px;\n            }\n\n            &.slick-active .thumbnail-wrapper {\n              border-color: white;\n              transform: translateY(-5px);\n            }\n\n            &:hover .thumbnail-wrapper {\n              border-color: rgba(255, 255, 255, 0.8);\n            }\n          }\n        }\n      }\n\n      .slick-arrow {\n        position: absolute;\n        bottom: 30px !important;\n        top: auto !important;\n        z-index: 1002;\n        width: 50px;\n        height: 50px;\n        background-color: rgba(255, 255, 255, 0.2);\n        border-radius: 50%;\n        transition: all 0.2s ease;\n        display: flex !important;\n        align-items: center;\n        justify-content: center;\n\n        &:before {\n          font-size: 30px;\n          opacity: 1;\n        }\n\n        &:hover {\n          background-color: rgba(255, 255, 255, 0.4);\n        }\n      }\n\n      .slick-prev {\n        right: 110px !important;\n        left: auto !important;\n      }\n\n      .slick-next {\n        right: 40px !important;\n      }\n    }\n  }\n\n  .slick-slide {\n    outline: none;\n  }\n\n  .custom-arrow {\n    display: flex !important;\n    align-items: center;\n    justify-content: center;\n    color: #333;\n\n    svg {\n      color: #fff;\n    }\n\n    &:hover {\n      color: #000;\n    }\n\n    &:before {\n      display: none !important;\n    }\n  }\n\n  .slick-dots {\n    bottom: -30px;\n\n    li button:before {\n      font-size: 8px;\n    }\n  }\n\n  .slick-prev {\n    left: -10px;\n    z-index: 1;\n  }\n\n  .slick-next {\n    right: -10px;\n    z-index: 1;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/Media.tsx",
    "content": "/* eslint-disable jsx-a11y/click-events-have-key-events */\n/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React, { useState, useRef } from 'react';\nimport Slider from 'react-slick';\nimport 'slick-carousel/slick/slick.css';\nimport 'slick-carousel/slick/slick-theme.css';\nimport { Image } from '@components/common/Image.js';\nimport { useProduct } from '@components/frontStore/catalog/ProductContext.js';\nimport './Media.scss';\nimport { ProductNoThumbnail } from '@components/common/ProductNoThumbnail.js';\n\nconst SliderComponent = Slider as any;\n\ntype SliderType = any;\n\nconst PrevArrow = (props: any) => {\n  const { className, onClick } = props;\n  return (\n    <button\n      className={`${className} custom-arrow prev-arrow`}\n      onClick={onClick}\n      aria-label=\"Previous slide\"\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"18\"\n        height=\"18\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M15 18l-6-6 6-6\" />\n      </svg>\n    </button>\n  );\n};\n\nconst NextArrow = (props: any) => {\n  const { className, onClick } = props;\n  return (\n    <button\n      className={`${className} custom-arrow next-arrow`}\n      onClick={onClick}\n      aria-label=\"Next slide\"\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"18\"\n        height=\"18\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M9 18l6-6-6-6\" />\n      </svg>\n    </button>\n  );\n};\n\ninterface ImageWithDimensionsProps {\n  url: string;\n  alt?: string;\n  width: number;\n  height: number;\n}\n\ninterface MediaProps {\n  imageSize?: {\n    width: number;\n    height: number;\n  };\n  thumbnailSize?: {\n    width: number;\n    height: number;\n  };\n  modalSize?: {\n    width: number;\n    height: number;\n  };\n}\n\nexport const Media: React.FC<MediaProps> = ({\n  imageSize = { width: 600, height: 600 },\n  thumbnailSize = { width: 100, height: 100 },\n  modalSize = { width: 1200, height: 1200 }\n}) => {\n  const product = useProduct();\n  const [isModalOpen, setIsModalOpen] = useState(false);\n  const [activeSlide, setActiveSlide] = useState(0);\n  const [isImageLoading, setIsImageLoading] = useState(false);\n  const mainSliderRef = useRef<SliderType>(null);\n  const modalSliderRef = useRef<SliderType>(null);\n\n  const allImages: ImageWithDimensionsProps[] = [];\n\n  const fullscreenWidth = modalSize.width * 1.5;\n  const fullscreenHeight = modalSize.height * 1.5;\n\n  if (product.image) {\n    allImages.push({\n      url: product.image.url,\n      alt: product.image.alt || product.name,\n      width: imageSize.width,\n      height: imageSize.height\n    });\n  }\n\n  if (product.gallery && Array.isArray(product.gallery)) {\n    product.gallery.forEach((img) => {\n      allImages.push({\n        url: img.url,\n        alt: img.alt || product.name,\n        width: imageSize.width,\n        height: imageSize.height\n      });\n    });\n  }\n\n  const mainSliderSettings = {\n    dots: allImages.length > 1,\n    dotsClass: 'slick-dots slick-thumb',\n    infinite: true,\n    speed: 500,\n    slidesToShow: 1,\n    slidesToScroll: 1,\n    arrows: allImages.length > 1,\n    fade: false,\n    prevArrow: <PrevArrow />,\n    nextArrow: <NextArrow />,\n    beforeChange: (_: number, next: number) => {\n      setActiveSlide(next);\n    },\n    customPaging: function (i: number) {\n      return (\n        <div className=\"thumbnail-wrapper\">\n          <Image\n            src={allImages[i]?.url}\n            alt={`Thumbnail ${i + 1}`}\n            width={thumbnailSize.width}\n            height={thumbnailSize.height}\n            objectFit=\"contain\"\n          />\n        </div>\n      );\n    }\n  };\n\n  const modalSliderSettings = {\n    dots: allImages.length > 1,\n    dotsClass: 'slick-dots slick-thumb',\n    infinite: true,\n    speed: 500,\n    slidesToShow: 1,\n    slidesToScroll: 1,\n    arrows: true,\n    prevArrow: <PrevArrow />,\n    nextArrow: <NextArrow />,\n    initialSlide: activeSlide,\n    adaptiveHeight: true,\n    lazyLoad: 'ondemand',\n    fade: false,\n    swipe: true,\n    beforeChange: () => setIsImageLoading(true),\n    afterChange: () => setIsImageLoading(false),\n    customPaging: function (i: number) {\n      return (\n        <div className=\"thumbnail-wrapper\">\n          <Image\n            src={allImages[i]?.url}\n            alt={`Thumbnail ${i + 1}`}\n            width={thumbnailSize.width}\n            height={thumbnailSize.height}\n            objectFit=\"contain\"\n          />\n        </div>\n      );\n    }\n  };\n\n  const openModal = (index: number) => {\n    setActiveSlide(index);\n    setIsModalOpen(true);\n\n    setTimeout(() => {\n      if (modalSliderRef.current) {\n        modalSliderRef.current.slickGoTo(index);\n      }\n    }, 100);\n\n    document.addEventListener('keydown', handleKeyDown);\n    document.body.style.overflow = 'hidden';\n  };\n\n  const closeModal = () => {\n    setIsModalOpen(false);\n    document.removeEventListener('keydown', handleKeyDown);\n    document.body.style.overflow = '';\n  };\n\n  const handleKeyDown = (e: KeyboardEvent) => {\n    if (e.key === 'Escape') {\n      closeModal();\n    } else if (e.key === 'ArrowRight' && modalSliderRef.current) {\n      modalSliderRef.current.slickNext();\n    } else if (e.key === 'ArrowLeft' && modalSliderRef.current) {\n      modalSliderRef.current.slickPrev();\n    }\n  };\n\n  return (\n    <div className=\"product-media-container\">\n      <div className=\"main-image-container\">\n        {allImages.length > 0 && (\n          <SliderComponent.default\n            ref={mainSliderRef}\n            {...mainSliderSettings}\n            className=\"product-slider\"\n          >\n            {allImages.map((image, index) => (\n              <div\n                key={index}\n                className=\"product-image\"\n                onClick={() => openModal(index)}\n                style={{ width: imageSize.width, height: imageSize.height }}\n              >\n                <Image\n                  src={image.url}\n                  alt={image.alt || 'Product image'}\n                  width={imageSize.width}\n                  height={imageSize.height}\n                  objectFit=\"scale-down\"\n                />\n              </div>\n            ))}\n          </SliderComponent.default>\n        )}\n        {allImages.length === 0 && (\n          <div className=\"w-full h-full flex items-center justify-center py-24 bg-gray-100\">\n            <ProductNoThumbnail className=\"w-48 h-48\" />\n          </div>\n        )}\n      </div>\n\n      {isModalOpen && (\n        <div className=\"product-image-modal\">\n          <div className=\"modal-overlay\" onClick={closeModal}></div>\n          <div className=\"modal-content\">\n            <button\n              className=\"modal-close\"\n              onClick={closeModal}\n              aria-label=\"Close fullscreen view\"\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"24\"\n                height=\"24\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              >\n                <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n                <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n              </svg>\n            </button>\n            <div className=\"modal-slider-container\">\n              {isImageLoading && (\n                <div className=\"loading-indicator\">\n                  <svg\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    width=\"40\"\n                    height=\"40\"\n                    viewBox=\"0 0 24 24\"\n                    fill=\"none\"\n                    stroke=\"currentColor\"\n                    strokeWidth=\"2\"\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    className=\"spinner\"\n                  >\n                    <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\n                    <path d=\"M12 2a10 10 0 1 0 10 10\"></path>\n                  </svg>\n                </div>\n              )}\n              <SliderComponent.default\n                ref={modalSliderRef}\n                {...modalSliderSettings}\n              >\n                {allImages.map((image, index) => (\n                  <div key={index} className=\"modal-image\">\n                    <Image\n                      src={image.url}\n                      alt={image.alt || 'Product image'}\n                      width={fullscreenWidth}\n                      height={fullscreenHeight}\n                      objectFit=\"contain\"\n                    />\n                  </div>\n                ))}\n              </SliderComponent.default>\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductContext.tsx",
    "content": "import { Row } from '@components/common/form/Editor.js';\nimport React, { createContext, useContext, ReactNode } from 'react';\n\nexport interface ProductPrice {\n  value: number;\n  text: string;\n}\n\nexport interface ProductPriceData {\n  regular: ProductPrice;\n  special?: ProductPrice;\n}\n\nexport interface AttributeOption {\n  optionId: number;\n  optionText: string;\n  productId?: number;\n}\n\nexport interface VariantAttribute {\n  attributeId: number;\n  attributeCode: string;\n  attributeName: string;\n  options: AttributeOption[];\n}\n\nexport interface ImageData {\n  url: string;\n  alt?: string;\n}\n\nexport interface AttributeIndexItem {\n  attributeName: string;\n  attributeCode: string;\n  optionId: number;\n  optionText: string;\n}\n\nexport interface VariantGroup {\n  variantAttributes: VariantAttribute[];\n  items: {\n    attributes: {\n      attributeCode: string;\n      optionId: number;\n    }[];\n    product?: {\n      productId: number;\n      name: string;\n      sku: string;\n      url: string;\n      price: ProductPriceData;\n      image?: ImageData;\n    };\n  }[];\n}\n\nexport interface ProductData {\n  productId: number;\n  uuid: string;\n  name: string;\n  description: Array<Row>;\n  sku: string;\n  price: ProductPriceData;\n  inventory: {\n    isInStock: boolean;\n  };\n  weight?: {\n    value: number;\n    unit: string;\n  };\n  url?: string;\n  image?: ImageData;\n  gallery?: ImageData[];\n  attributes?: AttributeIndexItem[];\n  variantGroup?: VariantGroup;\n  [extendedFields: string]: any;\n}\n\nconst ProductContext = createContext<ProductData | undefined>(undefined);\n\ninterface ProductProviderProps {\n  children: ReactNode;\n  product: ProductData;\n}\n\nexport const ProductProvider: React.FC<ProductProviderProps> = ({\n  children,\n  product\n}) => {\n  return (\n    <ProductContext.Provider value={product}>\n      {children}\n    </ProductContext.Provider>\n  );\n};\n\nexport const useProduct = (): ProductData => {\n  const context = useContext(ProductContext);\n  if (context === undefined) {\n    throw new Error('useProduct must be used within a ProductProvider');\n  }\n  return context;\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductFilter.tsx",
    "content": "import { useAppDispatch } from '@components/common/context/app.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useState, useContext, useCallback } from 'react';\n\nexport interface FilterInput {\n  key: string;\n  operation: 'eq' | 'in' | 'range' | 'gt' | 'lt';\n  value: string;\n}\n\nexport interface FilterableAttribute {\n  attributeCode: string;\n  attributeName: string;\n  attributeId: number;\n  options: Array<{\n    optionId: number;\n    optionText: string;\n  }>;\n}\n\nexport interface PriceRange {\n  min: number;\n  minText: string;\n  max: number;\n  maxText: string;\n}\n\nexport interface CategoryFilter {\n  categoryId: number;\n  name: string;\n  uuid: string;\n}\n\nexport interface FilterComponent {\n  component: { default: React.ComponentType<any> };\n  props: Record<string, any>;\n  sortOrder: number;\n  id?: string;\n}\n\nexport interface ProductFilterRenderProps {\n  currentFilters: FilterInput[];\n  availableAttributes: FilterableAttribute[];\n  priceRange?: PriceRange;\n  categories: CategoryFilter[];\n  setting?: {\n    storeLanguage: string;\n    storeCurrency: string;\n  };\n\n  updateFilter: (filters: FilterInput[]) => void;\n  clearAllFilters: () => void;\n  addFilter: (\n    key: string,\n    operation: FilterInput['operation'],\n    value: string\n  ) => void;\n  removeFilter: (key: string) => void;\n  removeFilterValue: (key: string, value: string) => void;\n  toggleFilter: (\n    key: string,\n    operation: FilterInput['operation'],\n    value: string\n  ) => void;\n\n  hasFilter: (key: string) => boolean;\n  getFilterValue: (key: string) => string | undefined;\n  isLoading: boolean;\n  activeFilterCount: number;\n\n  isOptionSelected: (attributeCode: string, optionId: string) => boolean;\n  isCategorySelected: (categoryId: string) => boolean;\n  getSelectedCount: (attributeCode: string) => number;\n  getCategorySelectedCount: () => number;\n}\n\nexport interface ProductFilterProps {\n  currentFilters: FilterInput[];\n  availableAttributes?: FilterableAttribute[];\n  priceRange: PriceRange;\n  categories?: CategoryFilter[];\n  setting?: {\n    storeLanguage: string;\n    storeCurrency: string;\n  };\n  onFilterUpdate?: (filters: FilterInput[]) => void;\n  children: (props: ProductFilterRenderProps) => React.ReactNode;\n}\n\n// Create a context for filter dispatch\nexport const ProductFilterDispatch = React.createContext<{\n  updateFilter: (filters: FilterInput[]) => void;\n} | null>(null);\n\n// Hook to use the filter context\nexport const useProductFilter = () => {\n  const context = useContext(ProductFilterDispatch);\n  if (!context) {\n    throw new Error(\n      'useProductFilter must be used within a ProductFilterProvider'\n    );\n  }\n  return context;\n};\n\nexport const ProductFilter: React.FC<ProductFilterProps> = ({\n  currentFilters,\n  availableAttributes = [],\n  priceRange,\n  categories = [],\n  setting,\n  onFilterUpdate,\n  children\n}) => {\n  const AppContextDispatch = useAppDispatch();\n  const [isLoading, setIsLoading] = useState(false);\n\n  const defaultUpdateFilter = async (newFilters: FilterInput[]) => {\n    setIsLoading(true);\n    try {\n      const currentUrl = window.location.href;\n      const url = new URL(currentUrl, window.location.origin);\n\n      for (const filter of currentFilters) {\n        if (['page', 'limit', 'ob', 'od'].includes(filter.key)) {\n          continue;\n        }\n\n        if (filter.operation === 'eq') {\n          url.searchParams.delete(filter.key);\n        } else {\n          url.searchParams.delete(`${filter.key}[operation]`);\n          url.searchParams.delete(`${filter.key}[value]`);\n        }\n      }\n\n      // Add new filter parameters\n      for (const filter of newFilters) {\n        if (['page', 'limit', 'ob', 'od'].includes(filter.key)) {\n          continue;\n        }\n\n        if (filter.operation === 'eq') {\n          url.searchParams.append(filter.key, filter.value);\n        } else {\n          url.searchParams.append(`${filter.key}[operation]`, filter.operation);\n          url.searchParams.append(`${filter.key}[value]`, filter.value);\n        }\n      }\n\n      url.searchParams.delete('page');\n      url.searchParams.append('ajax', 'true');\n\n      // Update page data via GraphQL\n      await AppContextDispatch.fetchPageData(url);\n      url.searchParams.delete('ajax');\n\n      history.pushState(null, '', url);\n    } catch (error) {\n      //eslint-disable-next-line no-console\n      console.error('Failed to update filters:', error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const updateFilter = onFilterUpdate || defaultUpdateFilter;\n\n  // Filter management functions\n  const addFilter = useCallback(\n    (key: string, operation: FilterInput['operation'], value: string) => {\n      let newFilters: FilterInput[];\n\n      // For 'in' operation, handle multiple values\n      if (operation === 'in') {\n        const existingFilter = currentFilters.find(\n          (f) => f.key === key && f.operation === 'in'\n        );\n        if (existingFilter) {\n          // Add value to existing 'in' filter if not already present\n          const existingValues = existingFilter.value.split(',');\n          if (!existingValues.includes(value)) {\n            const newValues = [...existingValues, value].join(',');\n            newFilters = currentFilters.map((f) =>\n              f.key === key && f.operation === 'in'\n                ? { ...f, value: newValues }\n                : f\n            );\n          } else {\n            return;\n          }\n        } else {\n          newFilters = currentFilters.filter((f) => f.key !== key);\n          newFilters.push({ key, operation, value });\n        }\n      } else {\n        newFilters = currentFilters.filter((f) => f.key !== key);\n        newFilters.push({ key, operation, value });\n      }\n\n      updateFilter(newFilters);\n    },\n    [currentFilters, updateFilter]\n  );\n\n  const removeFilter = useCallback(\n    (key: string) => {\n      const newFilters = currentFilters.filter((f) => f.key !== key);\n      updateFilter(newFilters);\n    },\n    [currentFilters, updateFilter]\n  );\n\n  const removeFilterValue = useCallback(\n    (key: string, value: string) => {\n      const filter = currentFilters.find(\n        (f) => f.key === key && f.operation === 'in'\n      );\n      if (filter) {\n        const values = filter.value.split(',');\n        const newValues = values.filter((v) => v !== value);\n\n        if (newValues.length === 0) {\n          const newFilters = currentFilters.filter(\n            (f) => !(f.key === key && f.operation === 'in')\n          );\n          updateFilter(newFilters);\n        } else if (newValues.length === 1) {\n          const newFilters = currentFilters.map((f) =>\n            f.key === key && f.operation === 'in'\n              ? { key, operation: 'eq' as const, value: newValues[0] }\n              : f\n          );\n          updateFilter(newFilters);\n        } else {\n          const newFilters = currentFilters.map((f) =>\n            f.key === key && f.operation === 'in'\n              ? { ...f, value: newValues.join(',') }\n              : f\n          );\n          updateFilter(newFilters);\n        }\n      } else {\n        const newFilters = currentFilters.filter((f) => f.key !== key);\n        updateFilter(newFilters);\n      }\n    },\n    [currentFilters, updateFilter]\n  );\n\n  const toggleFilter = useCallback(\n    (key: string, operation: FilterInput['operation'], value: string) => {\n      // For 'in' operation, handle multi-value toggle\n      if (operation === 'in') {\n        const filter = currentFilters.find(\n          (f) => f.key === key && f.operation === 'in'\n        );\n        if (filter) {\n          const values = filter.value.split(',');\n          if (values.includes(value)) {\n            removeFilterValue(key, value);\n          } else {\n            addFilter(key, operation, value);\n          }\n        } else {\n          addFilter(key, operation, value);\n        }\n      } else {\n        const hasFilter = currentFilters.some((f) => f.key === key);\n        if (hasFilter) {\n          removeFilter(key);\n        } else {\n          addFilter(key, operation, value);\n        }\n      }\n    },\n    [currentFilters, addFilter, removeFilter, removeFilterValue]\n  );\n\n  const clearAllFilters = useCallback(() => {\n    setIsLoading(true);\n    const url = new URL(window.location.href, window.location.origin);\n    for (const key of [...url.searchParams.keys()]) {\n      if (!['page', 'limit', 'ob', 'od'].includes(key)) {\n        url.searchParams.delete(key);\n      }\n    }\n    url.searchParams.append('ajax', 'true');\n    AppContextDispatch.fetchPageData(url)\n      .then(() => {\n        url.searchParams.delete('ajax');\n        history.pushState(null, '', url);\n      })\n      .finally(() => setIsLoading(false));\n  }, [currentFilters, updateFilter]);\n\n  const hasFilter = useCallback(\n    (key: string) => {\n      return currentFilters.some((f) => f.key === key);\n    },\n    [currentFilters]\n  );\n\n  const getFilterValue = useCallback(\n    (key: string) => {\n      return currentFilters.find((f) => f.key === key)?.value;\n    },\n    [currentFilters]\n  );\n\n  const isOptionSelected = useCallback(\n    (attributeCode: string, optionId: string) => {\n      const filter = currentFilters.find((f) => f.key === attributeCode);\n      return filter\n        ? filter.value.split(',').includes(optionId.toString())\n        : false;\n    },\n    [currentFilters]\n  );\n\n  const isCategorySelected = useCallback(\n    (categoryId: string) => {\n      const filter = currentFilters.find((f) => f.key === 'cat');\n      return filter ? filter.value.split(',').includes(categoryId) : false;\n    },\n    [currentFilters]\n  );\n\n  const getSelectedCount = useCallback(\n    (attributeCode: string) => {\n      const filter = currentFilters.find((f) => f.key === attributeCode);\n      return filter ? filter.value.split(',').length : 0;\n    },\n    [currentFilters]\n  );\n\n  const getCategorySelectedCount = useCallback(() => {\n    const filter = currentFilters.find((f) => f.key === 'cat');\n    return filter ? filter.value.split(',').length : 0;\n  }, [currentFilters]);\n\n  const activeFilterCount = currentFilters.filter(\n    (f) => !['page', 'limit', 'ob', 'od'].includes(f.key)\n  ).length;\n\n  const renderProps: ProductFilterRenderProps = {\n    currentFilters,\n    availableAttributes,\n    priceRange,\n    categories,\n    setting,\n\n    updateFilter,\n    clearAllFilters,\n    addFilter,\n    removeFilter,\n    removeFilterValue,\n    toggleFilter,\n\n    hasFilter,\n    getFilterValue,\n\n    isLoading,\n    activeFilterCount,\n\n    isOptionSelected,\n    isCategorySelected,\n    getSelectedCount,\n    getCategorySelectedCount\n  };\n\n  return children(renderProps);\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductList.tsx",
    "content": "import { ProductData } from '@components/frontStore/catalog/ProductContext.js';\nimport { ProductListEmptyRender } from '@components/frontStore/catalog/ProductListEmptyRender.js';\nimport { ProductListItemRender } from '@components/frontStore/catalog/ProductListItemRender.js';\nimport { ProductListLoadingSkeleton } from '@components/frontStore/catalog/ProductListLoadingSkeleton.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { ReactNode } from 'react';\n\nexport interface ProductListProps {\n  products: ProductData[];\n  imageWidth?: number;\n  imageHeight?: number;\n  isLoading?: boolean;\n  emptyMessage?: string | ReactNode;\n  className?: string;\n  layout?: 'grid' | 'list';\n  gridColumns?: number;\n  showAddToCart?: boolean;\n  customAddToCartRenderer?: (product: ProductData) => ReactNode;\n  renderItem?: (product: ProductData) => ReactNode;\n}\n\nexport const ProductList: React.FC<ProductListProps> = ({\n  products = [],\n  imageWidth = 300,\n  imageHeight = 300,\n  isLoading = false,\n  emptyMessage = _('No products found'),\n  className = '',\n  layout = 'grid',\n  gridColumns = 4,\n  showAddToCart = false,\n  customAddToCartRenderer,\n  renderItem\n}) => {\n  if (isLoading) {\n    return (\n      <ProductListLoadingSkeleton\n        count={layout === 'list' ? 5 : gridColumns * 2}\n        gridColumns={gridColumns}\n        layout={layout}\n      />\n    );\n  }\n\n  if (!products || products.length === 0) {\n    return <ProductListEmptyRender message={emptyMessage} />;\n  }\n\n  const layoutClass = layout === 'grid' ? 'product__grid' : 'product__list';\n\n  // Compute responsive grid columns class based on gridColumns\n  const gridClassName = (() => {\n    switch (gridColumns) {\n      case 1:\n        return 'grid-cols-1';\n      case 2:\n        return 'grid-cols-1 md:grid-cols-2 gap-8';\n      case 3:\n        return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8';\n      case 4:\n        return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6';\n      case 5:\n        return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6';\n      case 6:\n        return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-6';\n      default:\n        return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6';\n    }\n  })();\n  const styleClasses = layout === 'grid' ? 'grid' : 'flex flex-col';\n  const containerClass = `${layoutClass} ${gridClassName} ${className} ${styleClasses}`;\n  const itemImageWidth =\n    layout === 'list' ? (imageWidth > 150 ? 150 : imageWidth) : imageWidth;\n  const itemImageHeight =\n    layout === 'list' ? (imageHeight > 150 ? 150 : imageHeight) : imageHeight;\n\n  return (\n    <div className={containerClass}>\n      {products.map((product) => (\n        <div\n          key={product.productId}\n          className={`product__list__item ${\n            layout === 'list'\n              ? 'product__list__item__list'\n              : 'product__list__item__grid'\n          }`}\n        >\n          {renderItem ? (\n            renderItem(product)\n          ) : (\n            <ProductListItemRender\n              product={product}\n              imageWidth={itemImageWidth}\n              imageHeight={itemImageHeight}\n              layout={layout}\n              showAddToCart={showAddToCart}\n              customAddToCartRenderer={customAddToCartRenderer}\n            />\n          )}\n        </div>\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductListEmptyRender.tsx",
    "content": "import React, { ReactNode } from 'react';\n\nexport const ProductListEmptyRender = ({\n  message\n}: {\n  message: string | ReactNode;\n}) => {\n  return (\n    <div className=\"empty-product-list\">\n      {typeof message === 'string' ? <p>{message}</p> : message}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductListItemRender.tsx",
    "content": "import { Image } from '@components/common/Image.js';\nimport { ProductNoThumbnail } from '@components/common/ProductNoThumbnail.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { AddToCart } from '@components/frontStore/cart/AddToCart.js';\nimport { ProductData } from '@components/frontStore/catalog/ProductContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { ReactNode } from 'react';\nimport { toast } from 'react-toastify';\n\nexport const ProductListItemRender = ({\n  product,\n  imageWidth,\n  imageHeight,\n  layout = 'grid',\n  showAddToCart = false,\n  customAddToCartRenderer\n}: {\n  product: ProductData;\n  imageWidth?: number;\n  imageHeight?: number;\n  layout?: 'grid' | 'list';\n  showAddToCart?: boolean;\n  customAddToCartRenderer?: (product: ProductData) => ReactNode;\n}) => {\n  if (layout === 'list') {\n    return (\n      <div className=\"product__list__item__inner group relative overflow-hidden flex gap-4 p-4\">\n        <div className=\"product__list__image flex-shrink-0\">\n          <a href={product.url}>\n            {product.image && (\n              <Image\n                src={product.image.url}\n                alt={product.image.alt || product.name}\n                width={imageWidth || 120}\n                height={imageHeight || 120}\n                loading=\"lazy\"\n                sizes=\"(max-width: 768px) 100vw, 33vw\" // Assume 3 columns on larger screens\n                className=\"transition-transform duration-300 ease-in-out group-hover:scale-105 rounded-lg\"\n              />\n            )}\n            {!product.image && (\n              <ProductNoThumbnail width={imageWidth} height={imageHeight} />\n            )}\n          </a>\n        </div>\n\n        <div className=\"product__list__info flex-1 flex flex-col justify-between\">\n          <div>\n            <h3 className=\"product__list__name h5 mb-2\">\n              <a\n                href={product.url}\n                className=\"hover:text-primary transition-colors\"\n              >\n                {product.name}\n              </a>\n            </h3>\n\n            <div className=\"product__list__sku text-sm text-gray-600 mb-2\">\n              {_('SKU ${sku}', { sku: product.sku })}\n            </div>\n\n            <div className=\"product__list__price mb-2\">\n              {product.price.special &&\n              product.price.regular < product.price.special ? (\n                <div className=\"flex items-center gap-2\">\n                  <span\n                    className=\"regular-price text-sm\"\n                    style={{ textDecoration: 'line-through', color: '#777' }}\n                  >\n                    {product.price.regular.text}\n                  </span>\n                  <span\n                    className=\"special-price text-lg font-bold\"\n                    style={{ color: '#e53e3e' }}\n                  >\n                    {product.price.special.text}\n                  </span>\n                </div>\n              ) : (\n                <span className=\"regular-price text-lg font-bold\">\n                  {product.price.regular.text}\n                </span>\n              )}\n            </div>\n\n            <div className=\"product__list__stock mb-3\">\n              {product.inventory.isInStock ? (\n                <span className=\"text-green-600 text-sm font-medium\">\n                  {_('In Stock')}\n                </span>\n              ) : (\n                <span className=\"text-red-600 text-sm font-medium\">\n                  {_('Out of Stock')}\n                </span>\n              )}\n            </div>\n          </div>\n\n          {showAddToCart && (\n            <div className=\"product__list__actions invisible transform translate-y-2 transition-all duration-300 ease-in-out group-hover:visible group-hover:translate-y-0\">\n              {customAddToCartRenderer ? (\n                customAddToCartRenderer(product)\n              ) : (\n                <AddToCart\n                  product={{\n                    sku: product.sku,\n                    isInStock: product.inventory.isInStock\n                  }}\n                  qty={1}\n                  onError={(error) => toast.error(error)}\n                >\n                  {(state, actions) => (\n                    <Button\n                      disabled={!state.canAddToCart || state.isLoading}\n                      onClick={(e) => {\n                        e.preventDefault();\n                        e.stopPropagation();\n                        actions.addToCart();\n                      }}\n                    >\n                      {state.isLoading ? _('Adding...') : _('Add to Cart')}\n                    </Button>\n                  )}\n                </AddToCart>\n              )}\n            </div>\n          )}\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"product__list__item__inner group overflow-hidden\">\n      <a href={product.url} className=\"product__list__link block\">\n        <div className=\"product__list__image overflow-hidden flex w-full justify-center\">\n          {product.image && (\n            <Image\n              src={product.image.url}\n              alt={product.image.alt || product.name}\n              width={imageWidth || 120}\n              height={imageHeight || 120}\n              sizes=\"(max-width: 768px) 100vw, 33vw\" // Assume 3 columns on larger screens\n              className=\"transition-transform duration-500 ease-in-out group-hover:scale-110\"\n            />\n          )}\n          {!product.image && (\n            <ProductNoThumbnail width={imageWidth} height={imageHeight} />\n          )}\n        </div>\n        <div className=\"product__list__info mt-3\">\n          <h3 className=\"product__list__name h5 font-medium\">{product.name}</h3>\n          <div className=\"product__list__price\">\n            {product.price.special &&\n            product.price.regular < product.price.special ? (\n              <>\n                <span className=\"regular-price\">\n                  {product.price.regular.text}\n                </span>\n                <span className=\"special-price\">\n                  {product.price.special.text}\n                </span>\n              </>\n            ) : (\n              <span className=\"regular-price\">\n                {product.price.regular.text}\n              </span>\n            )}\n          </div>\n        </div>\n      </a>\n      {showAddToCart && (\n        <div className=\"product__list__actions p-4 invisible transform translate-y-4 transition-all duration-300 ease-in-out group-hover:visible group-hover:translate-y-0\">\n          {customAddToCartRenderer ? (\n            customAddToCartRenderer(product)\n          ) : (\n            <AddToCart\n              product={{\n                sku: product.sku,\n                isInStock: product.inventory.isInStock\n              }}\n              qty={1}\n              onError={(error) => toast.error(error)}\n            >\n              {(state, actions) => (\n                <Button\n                  className={'w-full'}\n                  disabled={!state.canAddToCart || state.isLoading}\n                  onClick={(e) => {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    actions.addToCart();\n                  }}\n                >\n                  {state.isLoading ? _('Adding...') : _('Add to Cart')}\n                </Button>\n              )}\n            </AddToCart>\n          )}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductListLoadingSkeleton.tsx",
    "content": "import React from 'react';\n\ninterface LoadingSkeletonProps {\n  count?: number;\n  gridColumns?: number;\n  layout?: 'grid' | 'list';\n}\n\nexport const ProductListLoadingSkeleton = ({\n  count = 4,\n  gridColumns = 4,\n  layout = 'grid'\n}: LoadingSkeletonProps) => {\n  if (layout === 'list') {\n    return (\n      <div\n        className=\"product-list\"\n        style={{\n          display: 'flex',\n          flexDirection: 'column' as const,\n          gap: '20px'\n        }}\n      >\n        {Array.from({ length: count }).map((_, i) => (\n          <div\n            key={i}\n            className=\"product-skeleton product-skeleton-list\"\n            style={{\n              display: 'flex',\n              gap: '20px'\n            }}\n          >\n            <div\n              className=\"skeleton-image\"\n              style={{\n                flexShrink: 0,\n                width: '120px',\n                height: '120px',\n                backgroundColor: '#f0f0f0'\n              }}\n            />\n            <div className=\"skeleton-content\" style={{ flex: 1 }}>\n              <div\n                className=\"skeleton-name\"\n                style={{\n                  height: '20px',\n                  backgroundColor: '#f0f0f0',\n                  marginBottom: '10px',\n                  width: '60%'\n                }}\n              />\n              <div\n                className=\"skeleton-sku\"\n                style={{\n                  height: '16px',\n                  backgroundColor: '#f0f0f0',\n                  marginBottom: '10px',\n                  width: '30%'\n                }}\n              />\n              <div\n                className=\"skeleton-price\"\n                style={{\n                  height: '20px',\n                  backgroundColor: '#f0f0f0',\n                  marginBottom: '10px',\n                  width: '25%'\n                }}\n              />\n              <div\n                className=\"skeleton-stock\"\n                style={{\n                  height: '16px',\n                  backgroundColor: '#f0f0f0',\n                  width: '20%'\n                }}\n              />\n            </div>\n          </div>\n        ))}\n      </div>\n    );\n  }\n  // Compute responsive grid columns class based on gridColumns\n  const className = (() => {\n    switch (gridColumns) {\n      case 1:\n        return 'grid-cols-1';\n      case 2:\n        return 'grid-cols-1 md:grid-cols-2';\n      case 3:\n        return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3';\n      case 4:\n        return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4';\n      case 5:\n        return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-5';\n      case 6:\n        return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-6';\n      default:\n        return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4';\n    }\n  })();\n  return (\n    <div className={`product__list grid gap-8 ${className}`}>\n      {Array.from({ length: count }).map((_, i) => (\n        <div key={i} className=\"product-skeleton\">\n          <div\n            className=\"skeleton-image\"\n            style={{\n              aspectRatio: '1/1',\n              backgroundColor: '#f0f0f0',\n              marginBottom: '10px'\n            }}\n          />\n          <div\n            className=\"skeleton-name\"\n            style={{\n              height: '20px',\n              backgroundColor: '#f0f0f0',\n              marginBottom: '10px',\n              width: '80%'\n            }}\n          />\n          <div\n            className=\"skeleton-price\"\n            style={{\n              height: '20px',\n              backgroundColor: '#f0f0f0',\n              width: '40%'\n            }}\n          />\n        </div>\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductSingleAttributes.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { useProduct } from '@components/frontStore/catalog/ProductContext.js';\nimport React from 'react';\n\nexport const ProductSingleAttributes = () => {\n  const { attributes, sku } = useProduct();\n  const list = attributes\n    ? [\n        { attributeCode: 'sku', attributeName: 'SKU', optionText: sku },\n        ...attributes\n      ]\n    : [];\n  if (!list || list.length === 0) {\n    return null;\n  }\n\n  return (\n    <>\n      <Area id=\"productAttributesBefore\" noOuter />\n      <div className=\"product__single__attributes py-3\">\n        <ul className=\"list-none\">\n          {list.map((attribute) => (\n            <li key={attribute.attributeCode} className=\"py-1\">\n              <strong>{attribute.attributeName}: </strong>{' '}\n              <span>{attribute.optionText}</span>\n            </li>\n          ))}\n        </ul>\n      </div>\n      <Area id=\"productAttributesAfter\" noOuter />\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductSingleDescription.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Editor } from '@components/common/Editor.js';\nimport { useProduct } from '@components/frontStore/catalog/ProductContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport const ProductSingleDescription = () => {\n  const { description } = useProduct();\n\n  return (\n    <>\n      <Area id=\"productDescriptionBefore\" noOuter />\n      <div className=\"product__single__description mt-8\">\n        <h3>{_('Product Description')}</h3>\n        <Editor rows={description} />\n      </div>\n      <Area id=\"productDescriptionAfter\" noOuter />\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductSingleForm.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  AddToCart,\n  AddToCartActions,\n  AddToCartState\n} from '@components/frontStore/cart/AddToCart.js';\nimport { useProduct } from '@components/frontStore/catalog/ProductContext.js';\nimport { VariantSelector } from '@components/frontStore/catalog/VariantSelector.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\nimport { toast } from 'react-toastify';\n\nexport function ProductSingleForm() {\n  const {\n    price,\n    sku,\n    inventory: { isInStock }\n  } = useProduct();\n  const form = useForm();\n  const [addingToCart, setAddingToCart] = React.useState(false);\n  return (\n    <Form id=\"productForm\" method=\"POST\" submitBtn={false} form={form}>\n      <Area\n        id=\"productSinglePageForm\"\n        coreComponents={[\n          {\n            component: {\n              default: (\n                <div className=\"product__single__price text-2xl\">\n                  {price.regular.text}\n                </div>\n              )\n            },\n            sortOrder: 5,\n            id: 'price'\n          },\n          {\n            component: {\n              default: <VariantSelector />\n            },\n            sortOrder: 10,\n            id: 'variantSelector'\n          },\n          {\n            component: {\n              default: (\n                <AddToCart\n                  product={{\n                    sku: sku,\n                    isInStock: isInStock\n                  }}\n                  qty={form.watch('qty') || 1}\n                  onSuccess={() => {\n                    // To show the mini cart after adding a product to cart\n                  }}\n                  onError={(errorMessage) => {\n                    toast.error(\n                      errorMessage || _('Failed to add product to cart')\n                    );\n                  }}\n                >\n                  {(state: AddToCartState, actions: AddToCartActions) => (\n                    <div className=\"mt-6 space-y-3\">\n                      {state.isInStock === true && (\n                        <>\n                          <NumberField\n                            name=\"qty\"\n                            label={_('Quantity')}\n                            className=\"w-24\"\n                            min={1}\n                            required\n                            placeholder={_('Quantity')}\n                            defaultValue={1}\n                            wrapperClassName=\"w-1/2\"\n                          />\n                          <Button\n                            variant={'default'}\n                            size={'lg'}\n                            onClick={() => {\n                              form\n                                .trigger()\n                                .then((isValid) => {\n                                  if (isValid) {\n                                    setAddingToCart(true);\n                                    actions.addToCart();\n                                  }\n                                })\n                                .finally(() => {\n                                  setAddingToCart(false);\n                                });\n                            }}\n                            className=\"w-full py-6\"\n                            isLoading={addingToCart || state.isLoading}\n                          >\n                            {_('ADD TO CART')}\n                          </Button>\n                        </>\n                      )}\n                      {state.isInStock === false && (\n                        <Button\n                          onClick={() => {}}\n                          className=\"w-full py-6\"\n                          disabled\n                        >\n                          {_('SOLD OUT')}\n                        </Button>\n                      )}\n                    </div>\n                  )}\n                </AddToCart>\n              )\n            },\n            sortOrder: 30,\n            id: 'addToCartButton'\n          }\n        ]}\n      />\n    </Form>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductSingleName.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { useProduct } from '@components/frontStore/catalog/ProductContext.js';\nimport React from 'react';\n\nexport const ProductSingleName = () => {\n  const { name } = useProduct();\n  return (\n    <>\n      <Area id=\"productNameBefore\" noOuter />\n      <h1 className=\"product__single__name capitalize\">{name}</h1>\n      <Area id=\"productNameAfter\" noOuter />\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductSingleSku.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { useProduct } from '@components/frontStore/catalog/ProductContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport const ProductSingleSku = () => {\n  const { sku } = useProduct();\n  return (\n    <>\n      <Area id=\"productSkuBefore\" noOuter />\n      <div className=\"product__single__sku\">{_('SKU: ${sku}', { sku })}</div>\n      <Area id=\"productSkuAfter\" noOuter />\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/ProductSorting.tsx",
    "content": "/* eslint-disable react/prop-types */\nimport { useAppDispatch } from '@components/common/context/app.js';\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectTrigger,\n  SelectValue\n} from '@components/common/ui/Select.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { ArrowDownWideNarrow, ArrowUpWideNarrow } from 'lucide-react';\nimport React, { ReactNode, useCallback } from 'react';\n\nexport interface SortOption {\n  code: string;\n  name: string;\n  label?: string;\n  disabled?: boolean;\n}\n\nexport interface SortState {\n  sortBy: string;\n  sortOrder: 'asc' | 'desc';\n}\n\ninterface ProductSortingProps {\n  sortOptions?: SortOption[];\n  defaultSortBy?: string;\n  defaultSortOrder?: 'asc' | 'desc';\n  showSortDirection?: boolean;\n  enableUrlUpdate?: boolean;\n  onSortChange?: (sortState: SortState) => Promise<void> | void;\n  renderSortSelect?: (props: {\n    options: SortOption[];\n    value: string;\n    onChange: (value: string) => void;\n    disabled?: boolean;\n  }) => ReactNode;\n  renderSortDirection?: (props: {\n    sortOrder: 'asc' | 'desc';\n    onToggle: () => void;\n    disabled?: boolean;\n  }) => ReactNode;\n  className?: string;\n  disabled?: boolean;\n  count: number;\n}\n\nconst defaultSortOptions: SortOption[] = [\n  { code: '', name: _('Default'), label: _('Default') },\n  { code: 'price', name: _('Price'), label: _('Price') },\n  { code: 'name', name: _('Name'), label: _('Name') }\n];\n\nexport function ProductSorting({\n  sortOptions = defaultSortOptions,\n  defaultSortBy = '',\n  defaultSortOrder = 'asc',\n  showSortDirection = true,\n  enableUrlUpdate = true,\n  onSortChange,\n  renderSortSelect,\n  renderSortDirection,\n  className = '',\n  disabled = false,\n  count\n}: ProductSortingProps) {\n  const AppContextDispatch = useAppDispatch();\n\n  const [sortBy, setSortBy] = React.useState<string>(() => {\n    // Check if this is browser or server\n    if (typeof window !== 'undefined') {\n      const params = new URL(document.location.href).searchParams;\n      return params.get('ob') || defaultSortBy;\n    }\n    return defaultSortBy;\n  });\n\n  const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>(() => {\n    // Check if this is browser or server\n    if (typeof window !== 'undefined') {\n      const params = new URL(document.location.href).searchParams;\n      return (params.get('od') as 'asc' | 'desc') || defaultSortOrder;\n    }\n    return defaultSortOrder;\n  });\n\n  const defaultSortChangeHandler = useCallback(\n    async (newSortState: SortState) => {\n      if (!enableUrlUpdate) return;\n\n      const currentUrl = window.location.href;\n      const url = new URL(currentUrl, window.location.origin);\n\n      if (newSortState.sortBy === '' || newSortState.sortBy === defaultSortBy) {\n        url.searchParams.delete('ob');\n      } else {\n        url.searchParams.set('ob', newSortState.sortBy);\n      }\n\n      if (newSortState.sortOrder === defaultSortOrder) {\n        url.searchParams.delete('od');\n      } else {\n        url.searchParams.set('od', newSortState.sortOrder);\n      }\n\n      url.searchParams.append('ajax', 'true');\n      await AppContextDispatch.fetchPageData(url);\n      url.searchParams.delete('ajax');\n      history.pushState(null, '', url);\n    },\n    [AppContextDispatch, enableUrlUpdate, defaultSortBy, defaultSortOrder]\n  );\n\n  const handleSortChange = onSortChange || defaultSortChangeHandler;\n\n  const onChangeSort = useCallback(\n    async (newSortBy: string) => {\n      if (disabled) return;\n\n      const newSortState = { sortBy: newSortBy, sortOrder };\n      setSortBy(newSortBy);\n      await handleSortChange(newSortState);\n    },\n    [sortOrder, handleSortChange, disabled]\n  );\n\n  const onChangeDirection = useCallback(async () => {\n    if (disabled) return;\n\n    const newOrder: 'asc' | 'desc' = sortOrder === 'asc' ? 'desc' : 'asc';\n    const newSortState = { sortBy, sortOrder: newOrder };\n    setSortOrder(newOrder);\n    await handleSortChange(newSortState);\n  }, [sortBy, sortOrder, handleSortChange, disabled]);\n\n  const defaultSortSelect = (props: {\n    options: SortOption[];\n    value: string;\n    onChange: (value: string) => void;\n    disabled?: boolean;\n  }) => (\n    <Select\n      value={props.options.find((option) => option.code === props.value)}\n      onValueChange={(value) => props.onChange(value?.code || '')}\n      disabled={props.disabled}\n    >\n      <SelectTrigger className=\"w-full\">\n        <SelectValue placeholder={_('Select sort')} />\n      </SelectTrigger>\n      <SelectContent>\n        <SelectGroup>\n          <SelectLabel>{_('Sort By')}</SelectLabel>\n          {props.options.map((option) => (\n            <SelectItem\n              key={option.code}\n              value={option}\n              disabled={option.disabled}\n            >\n              {option.label || option.name}\n            </SelectItem>\n          ))}\n        </SelectGroup>\n      </SelectContent>\n    </Select>\n  );\n\n  const defaultSortDirection = (props: {\n    sortOrder: 'asc' | 'desc';\n    onToggle: () => void;\n    disabled?: boolean;\n  }) => (\n    <button\n      type=\"button\"\n      onClick={props.onToggle}\n      disabled={props.disabled}\n      className={`sort-direction-btn flex items-center justify-center ${\n        props.disabled\n          ? 'opacity-50 cursor-not-allowed'\n          : 'hover:text-primary cursor-pointer'\n      }`}\n      aria-label={`Sort ${\n        props.sortOrder === 'asc' ? 'descending' : 'ascending'\n      }`}\n    >\n      {props.sortOrder === 'desc' ? (\n        <ArrowDownWideNarrow className=\"w-5 h-5 text-muted-foreground\" />\n      ) : (\n        <ArrowUpWideNarrow className=\"w-5 h-5 text-muted-foreground\" />\n      )}\n    </button>\n  );\n\n  const containerContent = (\n    <>\n      <div className=\"sort-select grow\">\n        {renderSortSelect\n          ? renderSortSelect({\n              options: sortOptions,\n              value: sortBy,\n              onChange: onChangeSort,\n              disabled\n            })\n          : defaultSortSelect({\n              options: sortOptions,\n              value: sortBy,\n              onChange: onChangeSort,\n              disabled\n            })}\n      </div>\n      {showSortDirection && (\n        <div className=\"sort-direction self-center\">\n          {renderSortDirection\n            ? renderSortDirection({\n                sortOrder,\n                onToggle: onChangeDirection,\n                disabled\n              })\n            : defaultSortDirection({\n                sortOrder,\n                onToggle: onChangeDirection,\n                disabled\n              })}\n        </div>\n      )}\n    </>\n  );\n\n  return (\n    <div className=\"flex justify-between items-center border-b border-border pb-2 mb-8\">\n      <div>\n        {_('${count} Products', {\n          count: count.toString()\n        })}\n      </div>\n      <div className={cn(`product-sorting flex gap-2 items-center`, className)}>\n        {containerContent}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/SearchBox.tsx",
    "content": "import { Image } from '@components/common/Image.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { Search, X } from 'lucide-react';\nimport React, { useRef, useState, ReactNode, useCallback } from 'react';\nimport { useClient } from 'urql';\n\nconst SEARCH_PRODUCTS_QUERY = `\n  query Query($filters: [FilterInput]) {\n    products(filters: $filters) {\n      items {\n        ...Product\n      }\n    }\n  }\n`;\n\nconst PRODUCT_FRAGMENT = `\n  fragment Product on Product {\n    productId\n    name\n    sku\n    price {\n      regular {\n        value\n        text\n      }\n      special {\n        value\n        text\n      }\n    }\n    image {\n      url\n      alt\n    }\n    url\n    inventory {\n      isInStock\n    }\n  }\n`;\n\nexport interface SearchResult {\n  id: string;\n  title: string;\n  url?: string;\n  image?: string;\n  price?: string;\n  type?: 'product' | 'category' | 'page';\n  [key: string]: unknown;\n}\n\ninterface SearchBoxProps {\n  searchPageUrl: string;\n  enableAutocomplete?: boolean;\n  autocompleteDelay?: number;\n  minSearchLength?: number;\n  maxResults?: number;\n  onSearch?: (query: string) => Promise<SearchResult[]>;\n  renderSearchInput?: (props: {\n    value: string;\n    onChange: (value: string) => void;\n    onKeyDown: (event: React.KeyboardEvent) => void;\n    onFocus: () => void;\n    onBlur: () => void;\n    placeholder: string;\n    ref: React.RefObject<HTMLInputElement | null>;\n  }) => ReactNode;\n  renderSearchResults?: (props: {\n    results: SearchResult[];\n    query: string;\n    onSelect: (result: SearchResult) => void;\n    isLoading: boolean;\n  }) => ReactNode;\n  renderSearchIcon?: () => ReactNode;\n  renderCloseIcon?: () => ReactNode;\n}\nexport function SearchBox({\n  searchPageUrl,\n  enableAutocomplete = false,\n  autocompleteDelay = 300,\n  minSearchLength = 2,\n  maxResults = 10,\n  onSearch,\n  renderSearchInput,\n  renderSearchResults,\n  renderSearchIcon,\n  renderCloseIcon\n}: SearchBoxProps) {\n  const InputRef = useRef<HTMLInputElement>(null);\n  const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n  const client = useClient();\n\n  const [keyword, setKeyword] = useState<string>('');\n  const [showing, setShowing] = useState(false);\n  const [searchResults, setSearchResults] = useState<SearchResult[]>([]);\n  const [isSearching, setIsSearching] = useState(false);\n  const [showResults, setShowResults] = useState(false);\n\n  const defaultSearchFunction = useCallback(\n    async (query: string): Promise<SearchResult[]> => {\n      try {\n        const result = await client\n          .query(\n            `\n            ${PRODUCT_FRAGMENT}\n            ${SEARCH_PRODUCTS_QUERY}\n          `,\n            {\n              filters: [\n                {\n                  key: 'keyword',\n                  operation: 'eq',\n                  value: query\n                },\n                {\n                  key: 'limit',\n                  operation: 'eq',\n                  value: `${maxResults}`\n                }\n              ]\n            }\n          )\n          .toPromise();\n\n        if (result.error) {\n          return [];\n        }\n\n        if (!result.data?.products?.items) {\n          return [];\n        }\n\n        return result.data.products.items.map((product: any) => ({\n          id: product.productId,\n          title: product.name,\n          url: product.url,\n          image: product.image?.url,\n          price: product.price?.special?.text || product.price?.regular?.text,\n          type: 'product' as const,\n          sku: product.sku,\n          isInStock: product.inventory?.isInStock\n        }));\n      } catch (error) {\n        return [];\n      }\n    },\n    [client]\n  );\n\n  const searchFunction = onSearch || defaultSearchFunction;\n\n  React.useEffect(() => {\n    const url = new URL(window.location.href);\n    const key = url.searchParams.get('keyword');\n    setKeyword(key || '');\n  }, []);\n\n  React.useEffect(() => {\n    if (showing) {\n      InputRef.current?.focus();\n    }\n  }, [showing]);\n\n  const performSearch = useCallback(\n    async (query: string) => {\n      if (!enableAutocomplete || query.length < minSearchLength) {\n        setSearchResults([]);\n        setShowResults(false);\n        return;\n      }\n\n      setIsSearching(true);\n      try {\n        const results = await searchFunction(query);\n        setSearchResults(results.slice(0, maxResults));\n        setShowResults(true);\n      } catch (error) {\n        setSearchResults([]);\n      } finally {\n        setIsSearching(false);\n      }\n    },\n    [enableAutocomplete, searchFunction, minSearchLength, maxResults]\n  );\n\n  const handleInputChange = useCallback(\n    (value: string) => {\n      setKeyword(value);\n\n      if (enableAutocomplete) {\n        if (searchTimeoutRef.current) {\n          clearTimeout(searchTimeoutRef.current);\n        }\n\n        searchTimeoutRef.current = setTimeout(() => {\n          performSearch(value);\n        }, autocompleteDelay);\n      }\n    },\n    [enableAutocomplete, autocompleteDelay, performSearch]\n  );\n\n  const handleResultSelect = useCallback(\n    (result: SearchResult) => {\n      if (result.url) {\n        window.location.href = result.url;\n      } else {\n        const url = new URL(searchPageUrl, window.location.origin);\n        url.searchParams.set('keyword', result.title);\n        window.location.href = url.toString();\n      }\n      setShowing(false);\n      setShowResults(false);\n    },\n    [searchPageUrl]\n  );\n\n  const handleKeyDown = useCallback(\n    (event: React.KeyboardEvent) => {\n      if (event.key === 'Enter') {\n        setShowResults(false);\n        const url = new URL(searchPageUrl, window.location.origin);\n        url.searchParams.set('keyword', keyword);\n        window.location.href = url.toString();\n      } else if (event.key === 'Escape') {\n        setShowResults(false);\n        setShowing(false);\n      }\n    },\n    [searchPageUrl, keyword]\n  );\n\n  const handleFocus = useCallback(() => {\n    if (\n      enableAutocomplete &&\n      keyword.length >= minSearchLength &&\n      searchResults.length > 0\n    ) {\n      setShowResults(true);\n    }\n  }, [enableAutocomplete, keyword, minSearchLength, searchResults.length]);\n\n  const handleBlur = useCallback(() => {\n    setTimeout(() => {\n      setShowResults(false);\n    }, 150);\n  }, []);\n\n  const defaultSearchIcon = () => (\n    <Search className=\"w-5 h-5 text-foreground hover:text-primary\" />\n  );\n\n  const defaultCloseIcon = () => (\n    <X className=\"w-5 h-5 text-foreground hover:text-primary\" />\n  );\n\n  return (\n    <div className=\"search__box\">\n      <a\n        href=\"#\"\n        className=\"search__icon\"\n        onClick={(e) => {\n          e.preventDefault();\n          setShowing(!showing);\n        }}\n      >\n        {renderSearchIcon ? renderSearchIcon() : defaultSearchIcon()}\n      </a>\n      {showing && (\n        <div className=\"search__input__container fixed top-0 left-0 right-0 bottom-0 bg-white shadow-md z-50 p-10\">\n          <div className=\"search__input relative flex justify-between\">\n            {renderSearchInput\n              ? renderSearchInput({\n                  value: keyword || '',\n                  onChange: handleInputChange,\n                  onKeyDown: handleKeyDown,\n                  onFocus: handleFocus,\n                  onBlur: handleBlur,\n                  placeholder: _('Search'),\n                  ref: InputRef\n                })\n              : defaultSearchInput({\n                  value: keyword || '',\n                  onChange: handleInputChange,\n                  onKeyDown: handleKeyDown,\n                  onFocus: handleFocus,\n                  onBlur: handleBlur,\n                  placeholder: _('Search'),\n                  ref: InputRef\n                })}\n            <a\n              href=\"#\"\n              className=\"close-icon flex items-center p-3\"\n              onClick={(e) => {\n                e.preventDefault();\n                setShowing(false);\n                setShowResults(false);\n              }}\n            >\n              {renderCloseIcon ? renderCloseIcon() : defaultCloseIcon()}\n            </a>\n            {enableAutocomplete &&\n              showResults &&\n              (renderSearchResults\n                ? renderSearchResults({\n                    results: searchResults,\n                    query: keyword || '',\n                    onSelect: handleResultSelect,\n                    isLoading: isSearching\n                  })\n                : defaultSearchResults({\n                    results: searchResults,\n                    query: keyword || '',\n                    onSelect: handleResultSelect,\n                    isLoading: isSearching\n                  }))}\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n\nconst defaultSearchInput = (props: {\n  value: string;\n  onChange: (value: string) => void;\n  onKeyDown: (event: React.KeyboardEvent) => void;\n  onFocus: () => void;\n  onBlur: () => void;\n  placeholder: string;\n  ref: React.RefObject<HTMLInputElement | null>;\n}) => (\n  <div className=\"form__field flex items-center justify-center relative grow\">\n    <InputGroup>\n      <InputGroupAddon>\n        <Search />\n      </InputGroupAddon>\n      <InputGroupInput\n        ref={props.ref}\n        placeholder={props.placeholder}\n        value={props.value}\n        onChange={(e) => props.onChange(e.target.value)}\n        onKeyDown={props.onKeyDown}\n        onFocus={props.onFocus}\n        onBlur={props.onBlur}\n        enterKeyHint=\"done\"\n        className=\"w-full focus:outline-none\"\n      />\n    </InputGroup>\n  </div>\n);\n\nconst defaultSearchResults = (props: {\n  results: SearchResult[];\n  query: string;\n  onSelect: (result: SearchResult) => void;\n  isLoading: boolean;\n}) => {\n  return (\n    <div className=\"search__results absolute top-full left-0 right-0 bg-white border border-border rounded-b-lg shadow-lg z-50 max-h-64 overflow-y-auto\">\n      {props.isLoading && (\n        <div className=\"p-3 text-center text-gray-500\">\n          <span>Searching...</span>\n        </div>\n      )}\n      {!props.isLoading && props.results.length === 0 && (\n        <div className=\"p-3 text-center text-gray-500\">\n          <span>No results found for &ldquo;{props.query}&rdquo;</span>\n        </div>\n      )}\n      {!props.isLoading &&\n        props.results.map((result) => (\n          <div\n            key={result.id}\n            className=\"flex items-center p-3 hover:bg-gray-50 cursor-pointer border-b border-border last:border-b-0\"\n            onClick={(e) => {\n              e.preventDefault();\n              props.onSelect(result);\n            }}\n            onKeyDown={(e) => {\n              if (e.key === 'Enter' || e.key === ' ') {\n                e.preventDefault();\n                props.onSelect(result);\n              }\n            }}\n            role=\"button\"\n            tabIndex={0}\n          >\n            {result.image && (\n              <Image\n                src={result.image}\n                alt={result.title}\n                width={100}\n                height={100}\n                className=\"w-10 h-10 object-cover rounded mr-3 shrink-0\"\n              />\n            )}\n            <div className=\"flex-1 min-w-0\">\n              <div className=\"font-medium truncate\">{result.title}</div>\n              {result.price && <div className=\"text-sm\">{result.price}</div>}\n              {result.type && (\n                <div className=\"text-xs text-gray-400 capitalize\">\n                  {result.type}\n                </div>\n              )}\n            </div>\n          </div>\n        ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/SearchContext.tsx",
    "content": "import { ProductData } from '@components/frontStore/catalog/ProductContext.js';\nimport { FilterInput } from '@components/frontStore/catalog/ProductFilter.js';\nimport React, { createContext, useContext, ReactNode } from 'react';\n\nexport interface SearchProducts {\n  items: ProductData[];\n  currentFilters: FilterInput[];\n  total: number;\n}\n\nexport interface SearchPageData {\n  keyword: string;\n  url?: string;\n  products: SearchProducts;\n  [extendedFields: string]: any;\n}\n\nconst SearchContext = createContext<SearchPageData | undefined>(undefined);\n\ninterface SearchProviderProps {\n  children: ReactNode;\n  searchData: SearchPageData;\n}\n\nexport const SearchProvider: React.FC<SearchProviderProps> = ({\n  children,\n  searchData\n}) => {\n  return (\n    <SearchContext.Provider value={searchData}>\n      {children}\n    </SearchContext.Provider>\n  );\n};\n\nexport const useSearch = (): SearchPageData => {\n  const context = useContext(SearchContext);\n  if (context === undefined) {\n    throw new Error('useSearch must be used within a SearchProvider');\n  }\n  return context;\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/SearchInfo.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { useSearch } from '@components/frontStore/catalog/SearchContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport function SearchInfo() {\n  const { keyword } = useSearch();\n\n  return (\n    <>\n      <Area id=\"searchInfoBefore\" noOuter />\n      <div className=\"page-width\">\n        <div className=\"mb-2 md:mb-5\">\n          <div className=\"text-left \">\n            <h1 className=\"search-name mt-6\">\n              {_('Search results for \"${keyword}\"', { keyword })}\n            </h1>\n          </div>\n        </div>\n      </div>\n      <Area id=\"searchInfoAfter\" noOuter />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/SearchProducts.tsx",
    "content": "import { Area } from '@components/common/index.js';\nimport { ProductList } from '@components/frontStore/catalog/ProductList.js';\nimport { useSearch } from '@components/frontStore/catalog/SearchContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport function SearchProducts() {\n  const { products } = useSearch();\n\n  return (\n    <>\n      <Area id=\"searchProductsBefore\" noOuter />\n      <div>\n        <ProductList\n          products={products.items}\n          layout=\"grid\"\n          gridColumns={3}\n          showAddToCart={true}\n        />\n        <span className=\"product-count italic block mt-5\">\n          {_('${count} products', { count: products.total.toString() })}\n        </span>\n      </div>\n      <Area id=\"searchProductsAfter\" noOuter />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/SearchProductsPagination.tsx",
    "content": "import { Area } from '@components/common/index.js';\nimport { useSearch } from '@components/frontStore/catalog/SearchContext.js';\nimport {\n  Pagination,\n  DefaultPaginationRenderer\n} from '@components/frontStore/Pagination.js';\nimport React from 'react';\n\nexport function SearchProductsPagination() {\n  const { products } = useSearch();\n  const page = products.currentFilters.find((filter) => filter.key === 'page');\n  const limit = products.currentFilters.find(\n    (filter) => filter.key === 'limit'\n  );\n\n  return (\n    <>\n      <Area id=\"searchProductsPaginationBefore\" noOuter />\n      <Pagination\n        total={products.total}\n        limit={limit ? parseInt(limit.value, 10) : 20}\n        currentPage={parseInt(page?.value || '1', 10)}\n      >\n        {(paginationProps) => (\n          <DefaultPaginationRenderer renderProps={paginationProps} />\n        )}\n      </Pagination>\n      <Area id=\"searchProductsPaginationAfter\" noOuter />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/catalog/VariantSelector.tsx",
    "content": "import { useAppDispatch, useAppState } from '@components/common/context/app.js';\nimport {\n  DefaultVariantAttribute,\n  DefaultVariantOptionItem\n} from '@components/frontStore/catalog/DefaultVariantSelectorRender.js';\nimport {\n  useProduct,\n  VariantAttribute,\n  VariantGroup,\n  AttributeOption\n} from '@components/frontStore/catalog/ProductContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useEffect, useMemo } from 'react';\nimport { useFormContext } from 'react-hook-form';\n\ninterface SelectedOption {\n  attributeCode: string;\n  optionId: number;\n}\n\ninterface ProcessedAttribute extends VariantAttribute {\n  selected: boolean;\n  selectedOption: number | null;\n  options: (AttributeOption & { available: boolean })[];\n}\n\nconst processAttributes = (\n  vs: VariantGroup | undefined,\n  attributes: VariantAttribute[],\n  currentUrl: string\n): ProcessedAttribute[] => {\n  if (!vs) return [];\n\n  const selectedOptions: SelectedOption[] = [];\n  let newAttributes: ProcessedAttribute[];\n\n  newAttributes = attributes.map((attribute) => {\n    const url = new URL(currentUrl);\n    const params = new URLSearchParams(url.search).entries();\n    const check = Array.from(params).find(\n      ([key, value]) =>\n        key === attribute.attributeCode &&\n        attribute.options.find(\n          (option) => option.optionId === parseInt(value, 10)\n        )\n    );\n\n    if (check) {\n      const terms = [\n        ...selectedOptions,\n        { attributeCode: check[0], optionId: parseInt(check[1], 10) }\n      ];\n      const variant = vs.items.find((item) =>\n        terms.every((attr) =>\n          item.attributes.find(\n            (term) =>\n              term.attributeCode === attr.attributeCode &&\n              parseInt(term.optionId.toString(), 10) ===\n                parseInt(attr.optionId.toString(), 10)\n          )\n        )\n      );\n\n      if (variant) {\n        selectedOptions.push({\n          attributeCode: check[0],\n          optionId: parseInt(check[1], 10)\n        });\n        return {\n          ...attribute,\n          selected: true,\n          selectedOption: parseInt(check[1], 10)\n        } as ProcessedAttribute;\n      } else {\n        return {\n          ...attribute,\n          selected: false,\n          selectedOption: null\n        } as ProcessedAttribute;\n      }\n    } else {\n      return {\n        ...attribute,\n        selected: false,\n        selectedOption: null\n      } as ProcessedAttribute;\n    }\n  });\n\n  newAttributes = newAttributes.map((attribute) => {\n    const options = attribute.options.map((option) => {\n      const terms = selectedOptions\n        .filter(\n          (selected) => selected.attributeCode !== attribute.attributeCode\n        )\n        .concat({\n          attributeCode: attribute.attributeCode,\n          optionId: option.optionId\n        });\n\n      const variant = vs.items.find((item) =>\n        terms.every((attr) =>\n          item.attributes.find(\n            (term) =>\n              term.attributeCode === attr.attributeCode &&\n              term.optionId === attr.optionId\n          )\n        )\n      );\n\n      return {\n        ...option,\n        available: !!variant\n      };\n    });\n\n    return { ...attribute, options };\n  });\n\n  return newAttributes;\n};\n\nexport interface VariantOptionItemProps {\n  option: AttributeOption & { available: boolean };\n  attribute: ProcessedAttribute;\n  isSelected: boolean;\n  onSelect: (attributeCode: string, optionId: number) => Promise<void>;\n}\n\nexport interface VariantAttributeGroupProps {\n  attribute: ProcessedAttribute;\n  options: (AttributeOption & { available: boolean })[];\n  onSelect: (attributeCode: string, optionId: number) => Promise<void>;\n  OptionItem?: React.ComponentType<VariantOptionItemProps>;\n}\n\ninterface VariantsProps {\n  AttributeRenderer?: React.ComponentType<VariantAttributeGroupProps>;\n  OptionRenderer?: React.ComponentType<VariantOptionItemProps>;\n}\n\nexport function VariantSelector({\n  AttributeRenderer = DefaultVariantAttribute,\n  OptionRenderer = DefaultVariantOptionItem\n}: VariantsProps) {\n  const { variantGroup: vs, productId } = useProduct();\n  const {\n    config: {\n      pageMeta: {\n        route: { url: currentProductUrl }\n      }\n    }\n  } = useAppState();\n  const {\n    register,\n    formState: { errors }\n  } = useFormContext();\n  const AppContextDispatch = useAppDispatch();\n\n  const initialAttributes = useMemo(\n    () => processAttributes(vs, vs?.variantAttributes || [], currentProductUrl),\n    [vs, currentProductUrl]\n  );\n\n  const [attributes, setAttributes] =\n    React.useState<ProcessedAttribute[]>(initialAttributes);\n  const attributeRef = React.useRef<ProcessedAttribute[]>(initialAttributes);\n\n  const validate = () => {\n    return !attributeRef.current.find((a) => a.selected !== true);\n  };\n\n  useEffect(() => {\n    const handleProductChange = () => {\n      const newAttributes = processAttributes(\n        vs,\n        vs?.variantAttributes || [],\n        currentProductUrl\n      );\n      setAttributes(newAttributes);\n      attributeRef.current = newAttributes;\n    };\n\n    handleProductChange();\n  }, [vs, productId]);\n\n  const handleOptionClick = async (\n    attributeCode: string,\n    optionId: number\n  ): Promise<void> => {\n    const url = new URL(window.location.href);\n    url.searchParams.set('ajax', 'true');\n    url.searchParams.set(attributeCode, optionId.toString());\n    await AppContextDispatch.fetchPageData(url);\n    url.searchParams.delete('ajax');\n\n    history.pushState(null, '', url);\n  };\n\n  if (!vs || attributes.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"variant variant__container grid grid-cols-1 gap-2 mt-5\">\n      {attributes.map((attribute) => {\n        const options = attribute.options.filter(\n          (v, j, s) =>\n            s.findIndex((o) => o.optionId === v.optionId) === j && v.productId\n        );\n\n        return (\n          <AttributeRenderer\n            key={attribute.attributeCode}\n            attribute={attribute}\n            options={options}\n            onSelect={handleOptionClick}\n            OptionItem={OptionRenderer}\n          />\n        );\n      })}\n      <input\n        type=\"hidden\"\n        {...register('variant_selected', {\n          validate: validate\n        })}\n      />\n      {errors.variant_selected && (\n        <div className=\"text-destructive\">\n          {_('Please select variant options')}\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/checkout/CheckoutButton.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { useCartState } from '@components/frontStore/cart/CartContext.js';\nimport { useCheckout } from '@components/frontStore/checkout/CheckoutContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport { useWatch } from 'react-hook-form';\n\nexport function CheckoutButton() {\n  const {\n    data: { noShippingRequired, billingAddress }\n  } = useCartState();\n  const { form, registeredPaymentComponents } = useCheckout();\n\n  // Watch the selected payment method\n  const selectedPaymentMethod = useWatch({\n    control: form.control,\n    name: 'paymentMethod'\n  });\n\n  // Get the payment component for the selected method\n  const getPaymentComponent = (methodCode: string) => {\n    return registeredPaymentComponents[methodCode] || null;\n  };\n\n  // Helper function to render a component safely\n  const renderComponent = (\n    component: React.ComponentType<any> | undefined,\n    props: any\n  ) => {\n    return component ? React.createElement(component, props) : null;\n  };\n\n  // Get the selected payment method component\n  const selectedComponent = selectedPaymentMethod\n    ? getPaymentComponent(selectedPaymentMethod)\n    : null;\n\n  if (noShippingRequired && !billingAddress) {\n    return (\n      <>\n        <Area id=\"checkoutButtonBefore\" />\n        <div className=\"checkout-button-section mt-6\">\n          <button\n            type=\"button\"\n            className=\"w-full bg-muted text-muted-foreground py-3 px-4 rounded-lg font-medium cursor-not-allowed\"\n            disabled\n          >\n            {_('Please provide billing address to proceed')}\n          </button>\n        </div>\n        <Area id=\"checkoutButtonAfter\" />\n      </>\n    );\n  }\n  return (\n    <>\n      <Area id=\"checkoutButtonBefore\" />\n      <div className=\"checkout-button-section mt-6\">\n        {selectedPaymentMethod && selectedComponent?.checkoutButtonRenderer ? (\n          // Render the custom checkout button for the selected payment method\n          renderComponent(selectedComponent.checkoutButtonRenderer, {\n            isSelected: true\n          })\n        ) : (\n          // Default checkout button when no payment method is selected or no custom button\n          <Button\n            variant={'outline'}\n            type=\"submit\"\n            size={'xl'}\n            className=\"w-full disabled:opacity-50 disabled:cursor-not-allowed\"\n            disabled={!selectedPaymentMethod}\n          >\n            {selectedPaymentMethod\n              ? _('Complete Order')\n              : _('Select a payment method')}\n          </Button>\n        )}\n      </div>\n      <Area id=\"checkoutButtonAfter\" />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/checkout/CheckoutContext.tsx",
    "content": "import {\n  useCartState,\n  useCartDispatch\n} from '@components/frontStore/cart/CartContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { CreateOrderResult } from '@evershop/evershop/checkout/services';\nimport { CheckoutData } from '@evershop/evershop/types/checkoutData';\nimport { produce } from 'immer';\nimport React, {\n  createContext,\n  useReducer,\n  useContext,\n  ReactNode,\n  useCallback,\n  useMemo\n} from 'react';\nimport { UseFormReturn, FieldValues } from 'react-hook-form';\n\ninterface PaymentMethod {\n  code: string;\n  name: string;\n  [key: string]: unknown;\n}\n\ninterface PaymentMethodRendererProps {\n  isSelected: boolean;\n}\n\ninterface PaymentMethodComponent {\n  nameRenderer: React.ComponentType<PaymentMethodRendererProps>;\n  formRenderer: React.ComponentType<PaymentMethodRendererProps>;\n  checkoutButtonRenderer: React.ComponentType<PaymentMethodRendererProps>;\n}\n\ninterface ShippingMethod {\n  code: string;\n  name: string;\n  cost?: {\n    value: number;\n    text: string;\n  };\n  [key: string]: unknown;\n}\n\ninterface ShippingAddressParams {\n  country: string;\n  province?: string;\n  postcode?: string;\n}\n\ninterface CheckoutState {\n  orderPlaced: boolean;\n  orderId?: string;\n  loadingStates: {\n    placingOrder: boolean;\n  };\n  allowGuestCheckout: boolean;\n  checkoutData: CheckoutData; // Add checkout data to state\n  registeredPaymentComponents: Record<string, PaymentMethodComponent>;\n}\n\ntype CheckoutAction =\n  | { type: 'SET_PLACING_ORDER'; payload: boolean }\n  | { type: 'SET_ORDER_PLACED'; payload: { orderId: string } }\n  | { type: 'SET_CHECKOUT_DATA'; payload: CheckoutData }\n  | { type: 'UPDATE_CHECKOUT_DATA'; payload: Partial<CheckoutData> }\n  | { type: 'CLEAR_CHECKOUT_DATA' }\n  // Payment method component registry actions\n  | {\n      type: 'REGISTER_PAYMENT_COMPONENT';\n      payload: { code: string; component: PaymentMethodComponent };\n    };\n\nconst initialState: CheckoutState = {\n  orderPlaced: false,\n  orderId: undefined,\n  loadingStates: {\n    placingOrder: false\n  },\n  allowGuestCheckout: false, // Default to false, will be set by provider\n  checkoutData: {}, // Initialize empty checkout data\n  registeredPaymentComponents: {} // Initialize empty payment component registry\n};\n\n// Reducer with Immer for immutable updates\nconst checkoutReducer = (\n  state: CheckoutState,\n  action: CheckoutAction\n): CheckoutState => {\n  return produce(state, (draft) => {\n    switch (action.type) {\n      case 'SET_PLACING_ORDER':\n        draft.loadingStates.placingOrder = action.payload;\n        break;\n      case 'SET_ORDER_PLACED':\n        draft.orderPlaced = true;\n        draft.orderId = action.payload.orderId;\n        draft.loadingStates.placingOrder = false;\n        break;\n      case 'SET_CHECKOUT_DATA':\n        draft.checkoutData = action.payload;\n        break;\n      case 'UPDATE_CHECKOUT_DATA':\n        draft.checkoutData = { ...draft.checkoutData, ...action.payload };\n        break;\n      case 'CLEAR_CHECKOUT_DATA':\n        draft.checkoutData = {};\n        break;\n      case 'REGISTER_PAYMENT_COMPONENT':\n        draft.registeredPaymentComponents[action.payload.code] =\n          action.payload.component;\n        break;\n    }\n  });\n};\n\n// Context types\ninterface CheckoutContextValue extends CheckoutState {\n  cartId: string | undefined;\n  checkoutSuccessUrl: string;\n  loading: boolean; // Computed from loadingStates\n  requiresShipment: boolean; // Computed from cart items\n  form: UseFormReturn<FieldValues>; // React Hook Form instance\n}\n\ninterface CheckoutDispatchContextValue<\n  T extends CreateOrderResult = CreateOrderResult\n> {\n  placeOrder: () => Promise<T>;\n  checkout: () => Promise<T>;\n  getPaymentMethods: () => PaymentMethod[];\n  getShippingMethods: (\n    params?: ShippingAddressParams\n  ) => Promise<ShippingMethod[]>;\n  // Checkout data management\n  setCheckoutData: (data: CheckoutData) => void;\n  updateCheckoutData: (data: Partial<CheckoutData>) => void;\n  clearCheckoutData: () => void;\n  // Payment method component registry\n  registerPaymentComponent: (\n    code: string,\n    component: PaymentMethodComponent\n  ) => void;\n  enableForm: () => void;\n  disableForm: () => void;\n}\n\n// Contexts\nconst CheckoutContext = createContext<CheckoutContextValue | undefined>(\n  undefined\n);\nconst CheckoutDispatchContext = createContext<\n  CheckoutDispatchContextValue | undefined\n>(undefined);\n\n// Provider props\ninterface CheckoutProviderProps {\n  children: ReactNode;\n  placeOrderApi: string;\n  checkoutSuccessUrl: string;\n  allowGuestCheckout?: boolean; // Optional, defaults to false\n  form: UseFormReturn<FieldValues>; // React Hook Form instance passed from outside\n  enableForm: () => void;\n  disableForm: () => void;\n}\n\n// Retry utility (similar to cart context)\nconst retry = async (\n  fn: () => Promise<Response>,\n  retries = 3,\n  delay = 1000\n): Promise<Response> => {\n  try {\n    return await fn();\n  } catch (error) {\n    if (retries > 0) {\n      await new Promise((resolve) => setTimeout(resolve, delay));\n      return retry(fn, retries - 1, delay * 2);\n    }\n    throw error;\n  }\n};\n\nexport function CheckoutProvider({\n  children,\n  placeOrderApi,\n  checkoutSuccessUrl,\n  allowGuestCheckout = false,\n  form,\n  enableForm,\n  disableForm\n}: CheckoutProviderProps) {\n  const [state, dispatch] = useReducer(checkoutReducer, {\n    ...initialState,\n    allowGuestCheckout\n  });\n\n  // Get cart state for computing requiresShipment and cartId\n  const cartState = useCartState();\n  const cartDispatch = useCartDispatch();\n  const cartId = cartState?.data?.uuid;\n\n  // Get payment methods - return the list from cart context\n  const getPaymentMethods = useCallback((): PaymentMethod[] => {\n    return (cartState.data?.availablePaymentMethods || []).map((method) => ({\n      code: method.code,\n      name: method.name\n    }));\n  }, [cartState.data?.availablePaymentMethods]);\n\n  // Get shipping methods - if params provided, fetch dynamically; otherwise return from cart context\n  const getShippingMethods = useCallback(\n    async (params?: ShippingAddressParams): Promise<ShippingMethod[]> => {\n      if (params) {\n        // Fetch shipping methods dynamically based on address\n        try {\n          await cartDispatch.fetchAvailableShippingMethods(params);\n          // Get updated methods from cart state\n          const methods = cartState.data?.availableShippingMethods || [];\n          return methods.map((method) => ({\n            code: method.code,\n            name: method.name,\n            cost: method.cost || { value: 0, text: 'Free' }\n          }));\n        } catch (error) {\n          // Return empty array on error, let the error be handled by cart context\n          return [];\n        }\n      } else {\n        // Return the initial shipping methods from cart context\n        return (cartState.data?.availableShippingMethods || []).map(\n          (method) => ({\n            code: method.code,\n            name: method.name,\n            cost: method.cost || { value: 0, text: 'Free' }\n          })\n        );\n      }\n    },\n    [cartDispatch, cartState.data?.availableShippingMethods]\n  );\n\n  // Compute requiresShipment based on cart items\n  const requiresShipment = useMemo(() => {\n    // Just return true for now as all products require shipping. We will get back to this when virtual/downloadable products are supported.\n    return true;\n  }, [cartState?.data?.items]);\n\n  // Place order with loading state and error handling (original API - expects data already in cart)\n  const placeOrder = useCallback(async () => {\n    if (!cartId) {\n      throw new Error('Cart ID is required to place order');\n    }\n\n    dispatch({ type: 'SET_PLACING_ORDER', payload: true });\n\n    const response = await retry(() =>\n      fetch(placeOrderApi, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ cart_id: cartId })\n      })\n    );\n\n    const json = await response.json();\n\n    if (!response.ok) {\n      throw new Error(json.error?.message || _('Failed to place order'));\n    }\n\n    dispatch({\n      type: 'SET_ORDER_PLACED',\n      payload: { orderId: json.data.uuid }\n    });\n\n    return json.data;\n  }, [placeOrderApi, cartId]);\n\n  // New checkout method with all data submission (cart.checkoutApi)\n  const checkout = useCallback(async () => {\n    if (!cartId) {\n      throw new Error(_('Cart ID is required to checkout'));\n    }\n\n    // Trigger form validation\n    const isValid = await form.trigger(undefined, {\n      shouldFocus: true\n    });\n    if (!isValid) {\n      return;\n    }\n\n    disableForm();\n    dispatch({ type: 'SET_PLACING_ORDER', payload: true });\n\n    const response = await retry(() =>\n      fetch(cartState.data?.checkoutApi, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          cart_id: cartId,\n          ...state.checkoutData\n        })\n      })\n    );\n\n    const json = await response.json();\n\n    if (!response.ok) {\n      enableForm();\n      dispatch({ type: 'SET_PLACING_ORDER', payload: false });\n      throw new Error(json.error?.message || _('Failed to checkout'));\n    }\n\n    dispatch({\n      type: 'SET_ORDER_PLACED',\n      payload: { orderId: json.data.uuid }\n    });\n\n    return json.data;\n  }, [\n    cartState.data?.checkoutApi,\n    cartId,\n    state.checkoutData,\n    form,\n    enableForm,\n    disableForm\n  ]);\n\n  // Checkout data management\n  const setCheckoutData = useCallback((data: CheckoutData) => {\n    dispatch({ type: 'SET_CHECKOUT_DATA', payload: data });\n  }, []);\n\n  const updateCheckoutData = useCallback((data: Partial<CheckoutData>) => {\n    dispatch({ type: 'UPDATE_CHECKOUT_DATA', payload: data });\n  }, []);\n\n  const clearCheckoutData = useCallback(() => {\n    dispatch({ type: 'CLEAR_CHECKOUT_DATA' });\n  }, []);\n\n  // Payment method component registry\n  const registerPaymentComponent = useCallback(\n    (code: string, component: PaymentMethodComponent) => {\n      dispatch({\n        type: 'REGISTER_PAYMENT_COMPONENT',\n        payload: { code, component }\n      });\n    },\n    []\n  );\n\n  const contextValue = useMemo(\n    (): CheckoutContextValue => ({\n      ...state,\n      cartId,\n      checkoutSuccessUrl,\n      requiresShipment,\n      form,\n      loading: state.loadingStates.placingOrder\n    }),\n    [state, cartId, checkoutSuccessUrl, requiresShipment, form]\n  );\n\n  const dispatchMethods = useMemo(\n    (): CheckoutDispatchContextValue => ({\n      placeOrder,\n      checkout,\n      getPaymentMethods,\n      getShippingMethods,\n      setCheckoutData,\n      updateCheckoutData,\n      clearCheckoutData,\n      registerPaymentComponent,\n      enableForm,\n      disableForm\n    }),\n    [\n      placeOrder,\n      checkout,\n      getPaymentMethods,\n      getShippingMethods,\n      setCheckoutData,\n      updateCheckoutData,\n      clearCheckoutData,\n      registerPaymentComponent,\n      enableForm,\n      disableForm\n    ]\n  );\n\n  return (\n    <CheckoutDispatchContext.Provider value={dispatchMethods}>\n      <CheckoutContext.Provider value={contextValue}>\n        {children}\n      </CheckoutContext.Provider>\n    </CheckoutDispatchContext.Provider>\n  );\n}\n\nexport const useCheckout = (): CheckoutContextValue => {\n  const context = useContext(CheckoutContext);\n  if (context === undefined) {\n    throw new Error('useCheckout must be used within a CheckoutProvider');\n  }\n  return context;\n};\n\nexport const useCheckoutDispatch = <\n  T extends CreateOrderResult = CreateOrderResult\n>(): CheckoutDispatchContextValue<T> => {\n  const context = useContext(CheckoutDispatchContext);\n  if (context === undefined) {\n    throw new Error(\n      'useCheckoutDispatch must be used within a CheckoutProvider'\n    );\n  }\n  return context as CheckoutDispatchContextValue<T>;\n};\n\nexport type {\n  PaymentMethod,\n  ShippingMethod,\n  ShippingAddressParams,\n  CheckoutState\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/checkout/ContactInformation.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { EmailField } from '@components/common/form/EmailField.js';\nimport { PasswordField } from '@components/common/form/PasswordField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport { useCartState } from '@components/frontStore/cart/CartContext.js';\nimport {\n  useCheckout,\n  useCheckoutDispatch\n} from '@components/frontStore/checkout/CheckoutContext.js';\nimport {\n  useCustomer,\n  useCustomerDispatch\n} from '@components/frontStore/customer/CustomerContext.jsx';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { CircleUser } from 'lucide-react';\nimport React, { useEffect, useState } from 'react';\nimport { toast } from 'react-toastify';\n\nconst LoggedIn: React.FC<{\n  fullName: string;\n  email: string;\n  uuid: string;\n}> = ({ uuid, fullName, email }) => {\n  const [isLoggingOut, setIsLoggingOut] = useState(false);\n  const { logout } = useCustomerDispatch();\n  const { updateCheckoutData } = useCheckoutDispatch();\n\n  useEffect(() => {\n    updateCheckoutData({\n      customer: {\n        id: uuid,\n        email: email,\n        fullName: fullName\n      }\n    });\n  }, [fullName, email]);\n\n  const handleLogout = async () => {\n    if (isLoggingOut) return;\n\n    try {\n      setIsLoggingOut(true);\n      await logout();\n      toast.success(_('Successfully logged out'));\n    } catch (error) {\n      const errorMessage =\n        error instanceof Error ? error.message : _('Logout failed');\n      toast.error(errorMessage);\n    } finally {\n      setIsLoggingOut(false);\n    }\n  };\n\n  return (\n    <Item variant={'outline'}>\n      <ItemContent>\n        <ItemTitle>\n          <div className=\"flex items-center space-x-2\">\n            <svg\n              className=\"w-4 h-4 text-primary\"\n              fill=\"currentColor\"\n              viewBox=\"0 0 20 20\"\n            >\n              <path\n                fillRule=\"evenodd\"\n                d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\"\n                clipRule=\"evenodd\"\n              />\n            </svg>\n            <span className=\"text-sm font-medium text-primary\">\n              {_('Logged in as')} {fullName}\n            </span>\n          </div>\n        </ItemTitle>\n        <ItemDescription>{email}</ItemDescription>\n      </ItemContent>\n      <ItemActions>\n        <Button onClick={handleLogout} disabled={isLoggingOut}>\n          {isLoggingOut ? _('Logging out...') : _('Logout')}\n        </Button>\n      </ItemActions>\n    </Item>\n  );\n};\n\nconst Guest: React.FC<{\n  email: string;\n}> = ({ email }) => {\n  const [showLogin, setShowLogin] = useState(false);\n  const [isLogging, setIsLogging] = useState(false);\n  const { login } = useCustomerDispatch();\n  const { form } = useCheckout();\n  const { updateCheckoutData } = useCheckoutDispatch();\n  const contactEmail = form.watch('contact.email', email);\n  const handleLoginClick = (e: React.MouseEvent) => {\n    e.preventDefault();\n    setShowLogin(true);\n  };\n\n  useEffect(() => {\n    updateCheckoutData({\n      customer: {\n        email: contactEmail\n      }\n    });\n  }, [contactEmail]);\n\n  const handleLogin = async () => {\n    if (isLogging) return;\n\n    try {\n      setIsLogging(true);\n      const isValid = await form.trigger(['contact.email', 'contact.password']);\n      if (!isValid) {\n        return;\n      }\n      const formData = form.getValues();\n      const loginEmail = formData?.contact?.email;\n      const password = formData?.contact?.password;\n      await login(\n        {\n          email: loginEmail,\n          password: password\n        },\n        window.location.href\n      );\n      toast.success(_('Successfully logged in'));\n      setShowLogin(false);\n    } catch (error) {\n      const errorMessage =\n        error instanceof Error ? error.message : _('Login failed');\n      toast.error(errorMessage);\n    } finally {\n      setIsLogging(false);\n    }\n  };\n\n  const handleCancelLogin = () => {\n    setShowLogin(false);\n    // Clear password field\n    form.setValue('contact.password', '');\n  };\n\n  return (\n    <div>\n      <EmailField\n        defaultValue={email}\n        name=\"contact.email\"\n        label={_('Email')}\n        required\n        validation={{\n          required: _('Email is required')\n        }}\n        placeholder={_('Enter your email')}\n      />\n\n      {showLogin && (\n        <div className=\"mt-4\">\n          <PasswordField\n            name=\"contact.password\"\n            label={_('Password')}\n            required\n            validation={{\n              required: _('Password is required')\n            }}\n            placeholder={_('Enter your password')}\n          />\n          <div className=\"mt-4 flex gap-2\">\n            <Button\n              onClick={handleLogin}\n              disabled={isLogging}\n              className=\"disabled:opacity-50 disabled:cursor-not-allowed\"\n            >\n              {isLogging ? _('Logging in...') : _('Log in')}\n            </Button>\n            <Button variant={'outline'} onClick={handleCancelLogin}>\n              {_('Cancel')}\n            </Button>\n          </div>\n        </div>\n      )}\n\n      {!showLogin && (\n        <p className=\"mt-2\">\n          {_('Already have an account?')}{' '}\n          <button\n            type=\"button\"\n            onClick={handleLoginClick}\n            className=\"underline text-primary hover:cursor-pointer\"\n          >\n            {_('Log in')}\n          </button>\n        </p>\n      )}\n    </div>\n  );\n};\nexport function ContactInformation() {\n  const { customer } = useCustomer();\n  const { data: cart } = useCartState();\n\n  return (\n    <>\n      <Area id=\"checkoutContactInformationBefore\" />\n      <div className=\"checkout-contact checkout-step\">\n        <Card>\n          <CardHeader>\n            <CardTitle>\n              <div className=\"flex items-center gap-2\">\n                <CircleUser className=\"w-5 h-5\" />\n                <span>{_('Contact Information')}</span>\n              </div>\n            </CardTitle>\n          </CardHeader>\n          <CardContent>\n            {customer ? (\n              <LoggedIn\n                fullName={customer.fullName}\n                email={customer.email}\n                uuid={customer.uuid}\n              />\n            ) : (\n              <Guest email={cart.customerEmail || ''} />\n            )}\n          </CardContent>\n        </Card>\n      </div>\n      <Area id=\"checkoutContactInformationAfter\" />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/checkout/OrderSummaryItems.tsx",
    "content": "import { useAppState } from '@components/common/context/app.js';\nimport { Image } from '@components/common/Image.js';\nimport { ProductNoThumbnail } from '@components/common/ProductNoThumbnail.js';\nimport { OrderItem } from '@components/frontStore/customer/CustomerContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nconst OrderSummaryItems: React.FC<{\n  items: OrderItem[];\n}> = ({ items }) => {\n  const {\n    config: {\n      tax: { priceIncludingTax }\n    }\n  } = useAppState();\n  if (items.length === 0) {\n    return null;\n  }\n\n  return (\n    <ul className=\"order__item__summary__list divide-y divide-border border-b border-border mb-3\">\n      {items.map((item) => (\n        <li key={item.uuid} className=\"flex items-start py-3\">\n          <div className=\"relative mr-4 self-center\">\n            {item.thumbnail && (\n              <Image\n                width={100}\n                height={100}\n                src={item.thumbnail}\n                alt={item.productName}\n                className=\"w-16 h-16 object-cover rounded border p-2 box-border border-border\"\n              />\n            )}\n            {!item.thumbnail && (\n              <ProductNoThumbnail className=\"w-16 h-16 rounded border border-border p-2 box-border\" />\n            )}\n            <span className=\"absolute -top-2 -right-2 bg-muted rounded-full w-6 h-6 flex items-center justify-center text-muted-foreground text-sm\">\n              {item.qty}\n            </span>\n          </div>\n          <div className=\"flex-1 min-w-0 items-start align-top\">\n            <div\n              className=\"font-semibold text-sm mb-1\"\n              title={item.productName}\n            >\n              {item.productName.length > 50\n                ? `${item.productName.substring(0, 50)}...`\n                : item.productName}\n            </div>\n            {item.variantOptions && item.variantOptions.length > 0 && (\n              <div className=\"space-y-1\">\n                {item.variantOptions.map((option) => (\n                  <div\n                    key={option.attributeCode}\n                    className=\"text-xs text-gray-700\"\n                  >\n                    {option.attributeName}: {option.optionText}\n                  </div>\n                ))}\n              </div>\n            )}\n          </div>\n          <div className=\"ml-auto text-right self-center\">\n            <div className=\"font-semibold\">\n              {priceIncludingTax\n                ? item.lineTotalInclTax.text\n                : item.lineTotal.text}\n            </div>\n          </div>\n        </li>\n      ))}\n    </ul>\n  );\n};\n\nexport { OrderSummaryItems };\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/checkout/OrderTotalSummary.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { useAppState } from '@components/common/context/app.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nconst Total: React.FC<{\n  total: string;\n  totalTaxAmount: string;\n  priceIncludingTax: boolean;\n}> = ({ total, totalTaxAmount, priceIncludingTax }) => {\n  return (\n    <div className=\"summary__row grand-total flex justify-between py-2\">\n      {(priceIncludingTax && (\n        <div>\n          <div className=\"font-bold\">\n            <span>{_('Total')}</span>\n          </div>\n          <div>\n            <span className=\"italic font-normal\">\n              ({_('Inclusive of tax ${totalTaxAmount}', { totalTaxAmount })})\n            </span>\n          </div>\n        </div>\n      )) || <span className=\"self-center font-bold\">{_('Total')}</span>}\n      <div>\n        <div />\n        <span>{total}</span>\n      </div>\n    </div>\n  );\n};\n\nconst Tax: React.FC<{\n  showPriceIncludingTax: boolean;\n  amount: string;\n}> = ({ showPriceIncludingTax, amount }) => {\n  if (showPriceIncludingTax) {\n    return null;\n  }\n\n  return (\n    <div className=\"summary-row flex justify-between py-2\">\n      <span>{_('Tax')}</span>\n      <div>\n        <div />\n        <span>{amount}</span>\n      </div>\n    </div>\n  );\n};\n\nconst Subtotal: React.FC<{ subTotal: string }> = ({ subTotal }) => {\n  return (\n    <div className=\"flex justify-between gap-7 py-2\">\n      <div>{_('Sub total')}</div>\n      <span>{subTotal}</span>\n    </div>\n  );\n};\n\nconst Discount: React.FC<{\n  discountAmount: string;\n  coupon: string | undefined;\n}> = ({ discountAmount, coupon }) => {\n  if (!coupon) {\n    return null;\n  }\n\n  return (\n    <div className=\"flex justify-between gap-7 py-2\">\n      <div>{_('Discount(${coupon})', { coupon })}</div>\n      <span>- {discountAmount}</span>\n    </div>\n  );\n};\n\nconst Shipping: React.FC<{\n  method: string | undefined;\n  cost: string | undefined;\n}> = ({ method, cost }) => {\n  return (\n    <div className=\"summary-row flex justify-between gap-7 py-2\">\n      {method && (\n        <>\n          <span>{_('Shipping (${method})', { method })}</span>\n          <div>\n            <span className=\"block\">{cost}</span>\n          </div>\n        </>\n      )}\n      {!method && (\n        <>\n          <span>{_('Shipping')}</span>\n          <span className=\"text-gray-500 italic font-normal\">\n            {_('No shipping is required for this order')}\n          </span>\n        </>\n      )}\n    </div>\n  );\n};\n\nconst OrderTotalSummary: React.FC<{\n  subTotal: string;\n  discountAmount: string;\n  coupon: string | undefined;\n  shippingMethod: string | undefined;\n  shippingCost: string | undefined;\n  taxAmount: string;\n  total: string;\n}> = ({\n  subTotal,\n  discountAmount,\n  coupon,\n  shippingMethod,\n  shippingCost,\n  taxAmount,\n  total\n}) => {\n  const {\n    config: {\n      tax: { priceIncludingTax }\n    }\n  } = useAppState();\n  return (\n    <div className=\"order__total__summary font-semibold\">\n      <Area id=\"orderSummaryBeforeSubTotal\" noOuter />\n      <Subtotal subTotal={subTotal} />\n      <Area id=\"orderSummaryAfterSubTotal\" noOuter />\n      <Area id=\"orderSummaryBeforeDiscount\" noOuter />\n      <Discount discountAmount={discountAmount} coupon={coupon} />\n      <Area id=\"orderSummaryAfterDiscount\" noOuter />\n      <Area id=\"orderSummaryBeforeShipping\" noOuter />\n      <Shipping method={shippingMethod} cost={shippingCost} />\n      <Area id=\"orderSummaryAfterShipping\" noOuter />\n      <Area id=\"orderSummaryBeforeTax\" noOuter />\n      <Tax amount={taxAmount} showPriceIncludingTax={priceIncludingTax} />\n      <Area id=\"orderSummaryAfterTax\" noOuter />\n      <Area id=\"orderSummaryBeforeTotal\" noOuter />\n      <Total\n        total={total}\n        totalTaxAmount={taxAmount}\n        priceIncludingTax={priceIncludingTax}\n      />\n      <Area id=\"orderSummaryAfterTotal\" noOuter />\n    </div>\n  );\n};\n\nexport { OrderTotalSummary, Subtotal, Discount, Shipping, Tax, Total };\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/checkout/Payment.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport {\n  useCartDispatch,\n  useCartState\n} from '@components/frontStore/cart/CartContext.js';\nimport {\n  useCheckout,\n  useCheckoutDispatch\n} from '@components/frontStore/checkout/CheckoutContext.js';\nimport { BillingAddress } from '@components/frontStore/checkout/payment/BillingAddress.js';\nimport { PaymentMethods } from '@components/frontStore/checkout/payment/PaymentMethods.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { CreditCard } from 'lucide-react';\nimport React, { useEffect } from 'react';\nimport { useWatch } from 'react-hook-form';\nimport { toast } from 'react-toastify';\n\nexport function Payment() {\n  const {\n    data: { noShippingRequired, billingAddress, availablePaymentMethods },\n    loadingStates: { addingBillingAddress }\n  } = useCartState();\n  const { addBillingAddress } = useCartDispatch();\n  const { updateCheckoutData } = useCheckoutDispatch();\n  const { form } = useCheckout();\n  const paymentMethod = useWatch({\n    name: 'paymentMethod',\n    control: form.control\n  });\n\n  useEffect(() => {\n    const updatePaymentMethod = async () => {\n      try {\n        const paymentMethod = form.getValues('paymentMethod');\n        const methodDetails = availablePaymentMethods?.find(\n          (method) => method.code === paymentMethod\n        );\n        if (!methodDetails) {\n          throw new Error('Please select a valid payment method');\n        }\n        updateCheckoutData({ paymentMethod: methodDetails.code });\n      } catch (error) {\n        toast.error(\n          error instanceof Error\n            ? error.message\n            : _('Failed to update shipment')\n        );\n      }\n    };\n    if (paymentMethod) {\n      updatePaymentMethod();\n    }\n  }, [paymentMethod]);\n\n  return (\n    <>\n      <Area id=\"checkoutPaymentBefore\" />\n      <div className=\"checkout__payment space-y-6 mt-6\">\n        <Card>\n          <CardHeader>\n            <CardTitle>\n              <div className=\"flex items-center gap-2\">\n                <CreditCard className=\"w-5 h-5\" />\n                <span>{_('Payment Information')}</span>\n              </div>\n            </CardTitle>\n          </CardHeader>\n          <CardContent>\n            <BillingAddress\n              billingAddress={billingAddress}\n              addBillingAddress={addBillingAddress}\n              addingBillingAddress={addingBillingAddress}\n              noShippingRequired={noShippingRequired}\n            />\n            {(billingAddress || noShippingRequired === false) && (\n              <>\n                <Area id=\"checkoutPaymentMethodsBefore\" />\n                <PaymentMethods\n                  methods={availablePaymentMethods?.map((method) => ({\n                    ...method\n                  }))}\n                  isLoading={addingBillingAddress}\n                />\n                <Area id=\"checkoutPaymentMethodsAfter\" />\n              </>\n            )}\n          </CardContent>\n        </Card>\n      </div>\n      <Area id=\"checkoutPaymentAfter\" />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/checkout/Shipment.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport {\n  useCartDispatch,\n  useCartState\n} from '@components/frontStore/cart/CartContext.js';\nimport {\n  useCheckout,\n  useCheckoutDispatch\n} from '@components/frontStore/checkout/CheckoutContext.js';\nimport { ShippingMethods } from '@components/frontStore/checkout/shipment/ShippingMethods.js';\nimport CustomerAddressForm from '@components/frontStore/customer/address/addressForm/Index.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { MapPin } from 'lucide-react';\nimport React, { useEffect, useRef } from 'react';\nimport { useWatch } from 'react-hook-form';\nimport { toast } from 'react-toastify';\n\nexport function Shipment() {\n  const {\n    data: {\n      shippingAddress,\n      noShippingRequired,\n      availableShippingMethods,\n      shippingMethod: selectedShippingMethod\n    },\n    loadingStates: { fetchingShippingMethods }\n  } = useCartState();\n\n  // Early return if no shipping is required\n  if (noShippingRequired) {\n    return null;\n  }\n\n  const {\n    addShippingAddress,\n    addShippingMethod,\n    fetchAvailableShippingMethods\n  } = useCartDispatch();\n  const { form } = useCheckout();\n  const { updateCheckoutData } = useCheckoutDispatch();\n\n  // Use useWatch for better performance and cleaner code\n  const watchedShippingAddress = useWatch({\n    control: form.control,\n    name: 'shippingAddress'\n  });\n\n  const dirtyFields = form.formState.dirtyFields;\n  const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n  const lastFetchParamsRef = useRef<{\n    country?: string;\n    province?: string;\n    postcode?: string;\n  } | null>(\n    // Initialize with current shipping address if available\n    shippingAddress\n      ? {\n          country: shippingAddress.country?.code,\n          province: shippingAddress.province?.code,\n          postcode: shippingAddress.postcode || undefined\n        }\n      : null\n  );\n\n  useEffect(() => {\n    const fetchShippingMethods = async () => {\n      try {\n        const country = form.getValues('shippingAddress.country');\n        const province = form.getValues('shippingAddress.province');\n        const postcode = form.getValues('shippingAddress.postcode');\n\n        if (!country) {\n          return;\n        }\n\n        // Check if parameters have actually changed\n        const currentParams = { country, province, postcode };\n        const lastParams = lastFetchParamsRef.current;\n\n        if (\n          lastParams &&\n          lastParams.country === country &&\n          lastParams.province === province &&\n          lastParams.postcode === postcode\n        ) {\n          // Parameters haven't changed, skip API call\n          return;\n        }\n\n        // Cache the current parameters\n        lastFetchParamsRef.current = currentParams;\n\n        await fetchAvailableShippingMethods({ country, province, postcode });\n      } catch (error) {\n        toast.error(\n          error instanceof Error\n            ? error.message\n            : _('Failed to update shipment')\n        );\n      }\n    };\n\n    if (watchedShippingAddress && dirtyFields.shippingAddress) {\n      // Clear existing timeout\n      if (debounceTimeoutRef.current) {\n        clearTimeout(debounceTimeoutRef.current);\n      }\n\n      // Set new timeout\n      debounceTimeoutRef.current = setTimeout(() => {\n        fetchShippingMethods();\n      }, 800);\n    }\n\n    // Cleanup function\n    return () => {\n      if (debounceTimeoutRef.current) {\n        clearTimeout(debounceTimeoutRef.current);\n      }\n    };\n  }, [watchedShippingAddress, dirtyFields.shippingAddress]); // Clean dependency array\n\n  const updateShipment = async (method: { code: string; name: string }) => {\n    try {\n      const validate = await form.trigger('shippingAddress');\n      if (!validate) {\n        return false;\n      }\n      const shippingAddress = form.getValues('shippingAddress');\n\n      await addShippingAddress(shippingAddress);\n      await addShippingMethod(method.code, method.name);\n      updateCheckoutData({ shippingAddress, shippingMethod: method.code });\n      return true;\n    } catch (error) {\n      toast.error(\n        error instanceof Error ? error.message : _('Failed to update shipment')\n      );\n      return false;\n    }\n  };\n\n  return (\n    <>\n      <Area id=\"checkoutShipmentBefore\" />\n      <div className=\"checkout__shipment space-y-6 mt-6\">\n        <Card className=\"transition-all overflow-hidden duration-200\">\n          <CardHeader>\n            <CardTitle>\n              <div className=\"flex items-center gap-2\">\n                <MapPin className=\"w-5 h-5\" />\n                <span>{_('Shipping Address')}</span>\n              </div>\n            </CardTitle>\n          </CardHeader>\n          <CardContent>\n            <CustomerAddressForm\n              areaId=\"checkoutShippingAddressForm\"\n              fieldNamePrefix=\"shippingAddress\"\n              address={shippingAddress}\n            />\n          </CardContent>\n        </Card>\n        <Area id=\"checkoutShippingMethodsBefore\" noOuter />\n        <ShippingMethods\n          methods={availableShippingMethods?.map((method) => ({\n            ...method,\n            isSelected: method.code === selectedShippingMethod\n          }))}\n          shippingAddress={shippingAddress}\n          onSelect={updateShipment}\n          isLoading={fetchingShippingMethods}\n        />\n        <Area id=\"checkoutShippingMethodsAfter\" noOuter />\n      </div>\n      <Area id=\"checkoutShipmentAfter\" />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/checkout/payment/BillingAddress.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  Item,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport {\n  RadioGroup,\n  RadioGroupItem\n} from '@components/common/ui/RadioGroup.js';\nimport {\n  useCheckout,\n  useCheckoutDispatch\n} from '@components/frontStore/checkout/CheckoutContext.js';\nimport CustomerAddressForm from '@components/frontStore/customer/address/addressForm/Index.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport {\n  Address,\n  CustomerAddressGraphql\n} from '@evershop/evershop/types/customerAddress';\nimport React, { useEffect, useState } from 'react';\nimport { useWatch } from 'react-hook-form';\n\nexport function BillingAddress({\n  billingAddress,\n  addBillingAddress,\n  addingBillingAddress,\n  noShippingRequired\n}: {\n  billingAddress?: CustomerAddressGraphql;\n  addBillingAddress?: (address: Address) => Promise<void>;\n  addingBillingAddress?: boolean;\n  noShippingRequired: boolean;\n}) {\n  const { form, checkoutData } = useCheckout();\n  const { updateCheckoutData } = useCheckoutDispatch();\n  const {\n    setValue,\n    getValues,\n    trigger,\n    formState: { disabled }\n  } = form;\n\n  const shippingAddress = useWatch({\n    control: form.control,\n    name: 'shippingAddress'\n  });\n\n  const billingAddressField = useWatch({\n    control: form.control,\n    name: 'billingAddress'\n  });\n\n  const [useSameAddress, setUseSameAddress] = useState(!noShippingRequired);\n\n  useEffect(() => {\n    if (useSameAddress && shippingAddress) {\n      updateCheckoutData({ billingAddress: shippingAddress });\n    } else if (!useSameAddress) {\n      setValue('billingAddress', billingAddress);\n    }\n  }, [useSameAddress, checkoutData.shippingAddress]);\n\n  useEffect(() => {\n    if (!useSameAddress) {\n      const billingAddress = { ...getValues('billingAddress') };\n      updateCheckoutData({ billingAddress });\n    }\n  }, [billingAddressField]);\n\n  const handleAddressOptionChange = (value: string) => {\n    const isSameAddress = value === 'same';\n    if (isSameAddress === useSameAddress || disabled) {\n      return;\n    }\n    setUseSameAddress(isSameAddress);\n    if (!isSameAddress) {\n      updateCheckoutData({ billingAddress: undefined });\n    } else if (checkoutData.shippingAddress) {\n      updateCheckoutData({ billingAddress: checkoutData.shippingAddress });\n    }\n  };\n\n  const handleGoToPayment = async () => {\n    const isValid = await trigger('billingAddress');\n\n    if (isValid && addBillingAddress) {\n      const billingAddressData = getValues('billingAddress');\n      await addBillingAddress(billingAddressData);\n    }\n  };\n\n  return (\n    <div className=\"billing-address-section\">\n      <Item className=\"py-0 px-0\">\n        <ItemContent className=\"gap-2\">\n          <ItemTitle>{_('Billing Address')}</ItemTitle>\n          <RadioGroup\n            value={useSameAddress ? 'same' : 'different'}\n            onValueChange={(value) => {\n              handleAddressOptionChange(value as string);\n            }}\n          >\n            {!noShippingRequired ? (\n              <>\n                <Item variant={'outline'}>\n                  <ItemContent>\n                    <ItemTitle>\n                      <div className=\"flex items-center space-x-3\">\n                        <RadioGroupItem id=\"same-address\" value=\"same\" />\n                        <Label htmlFor=\"same-address\">\n                          {_('Same as shipping address')}\n                        </Label>\n                      </div>\n                    </ItemTitle>\n                  </ItemContent>\n                </Item>\n                <Item variant={'outline'}>\n                  <ItemContent>\n                    <ItemTitle>\n                      <div className=\"flex items-center space-x-3\">\n                        <RadioGroupItem\n                          id=\"different-address\"\n                          value=\"different\"\n                        />\n                        <Label htmlFor=\"different-address\">\n                          {_('Use a different billing address')}\n                        </Label>\n                      </div>\n                    </ItemTitle>\n\n                    {!useSameAddress && (\n                      <ItemDescription className=\"text-inherit mt-3 overflow-visible\">\n                        <div className=\"text-inherit bg-white\">\n                          <CustomerAddressForm\n                            areaId=\"checkoutBillingAddressForm\"\n                            fieldNamePrefix=\"billingAddress\"\n                            address={undefined} // Always start empty for different address\n                          />\n                          {noShippingRequired && (\n                            <Button\n                              onClick={() => handleGoToPayment()}\n                              variant=\"default\"\n                              isLoading={addingBillingAddress}\n                            >\n                              {_('Continue to payment')}\n                            </Button>\n                          )}\n                        </div>\n                      </ItemDescription>\n                    )}\n                  </ItemContent>\n                </Item>\n              </>\n            ) : (\n              <ItemDescription className=\"text-inherit mt-3 overflow-visible\">\n                <div className=\"text-inherit bg-white\">\n                  <CustomerAddressForm\n                    areaId=\"checkoutBillingAddressForm\"\n                    fieldNamePrefix=\"billingAddress\"\n                    address={undefined} // Always start empty for different address\n                  />\n                  {noShippingRequired && (\n                    <Button\n                      onClick={() => handleGoToPayment()}\n                      variant=\"default\"\n                      isLoading={addingBillingAddress}\n                      className=\"mt-4\"\n                    >\n                      {_('Continue to payment')}\n                    </Button>\n                  )}\n                </div>\n              </ItemDescription>\n            )}\n          </RadioGroup>\n        </ItemContent>\n      </Item>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/checkout/payment/PaymentMethods.tsx",
    "content": "import {\n  Item,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport {\n  RadioGroup,\n  RadioGroupItem\n} from '@components/common/ui/RadioGroup.js';\nimport { Skeleton } from '@components/common/ui/Skeleton.js';\nimport { useCheckout } from '@components/frontStore/checkout/CheckoutContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface PaymentMethod {\n  code: string;\n  name: string;\n  cost?: {\n    value: number;\n    text: string;\n  };\n  description?: string;\n}\n\n// Skeleton component for loading state\nfunction PaymentMethodSkeleton() {\n  return (\n    <div className=\"payment-method-skeleton\">\n      {[1, 2, 3, 4].map((index) => (\n        <div\n          key={index}\n          className=\"border border-border rounded-lg p-4 mb-3 animate-pulse\"\n        >\n          <div className=\"flex items-center justify-between\">\n            <div className=\"flex items-center space-x-3\">\n              <Skeleton className=\"w-4 h-4\" />\n              <div className=\"space-y-2\">\n                <Skeleton className=\"h-4 w-20\" />\n                <Skeleton className=\"h-3 w-40\" />\n              </div>\n            </div>\n            <div className=\"text-right space-y-1\">\n              <Skeleton className=\"h-3 w-12\" />\n              <Skeleton className=\"h-4 w-16\" />\n            </div>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nexport function PaymentMethods({\n  methods,\n  isLoading\n}: {\n  methods: PaymentMethod[];\n  isLoading?: boolean;\n}) {\n  const { form, registeredPaymentComponents } = useCheckout();\n  const { formState, watch, setValue } = form;\n\n  const selectedPaymentMethod = watch('paymentMethod');\n\n  const getPaymentComponent = (methodCode: string) => {\n    return registeredPaymentComponents[methodCode] || null;\n  };\n\n  const renderComponent = (\n    component: React.ComponentType<any> | undefined,\n    props: any\n  ) => {\n    return component ? React.createElement(component, props) : null;\n  };\n\n  return (\n    <div className=\"checkout-payment-methods mt-6\">\n      <Item className=\"px-0 py-0\">\n        <ItemContent className=\"gap-2\">\n          <ItemTitle>{_('Pick a payment method')}</ItemTitle>\n          <ItemDescription>\n            {isLoading ? (\n              <PaymentMethodSkeleton />\n            ) : (\n              <>\n                <div className=\"payment-methods-list\">\n                  {methods?.length === 0 ? (\n                    <div className=\"text-muted-foreground text-center py-8\">\n                      <div className=\"mb-2\">\n                        {_('No payment methods available')}\n                      </div>\n                    </div>\n                  ) : (\n                    <RadioGroup\n                      value={selectedPaymentMethod}\n                      onValueChange={(value) => {\n                        setValue('paymentMethod', value);\n                      }}\n                    >\n                      {methods.map((method: PaymentMethod) => {\n                        const isSelected =\n                          selectedPaymentMethod === method.code;\n                        const component = getPaymentComponent(method.code);\n                        return (\n                          <Item\n                            key={method.code}\n                            variant={'outline'}\n                            className={isSelected ? 'border-primary' : ''}\n                          >\n                            <ItemContent>\n                              <ItemTitle className=\"w-full\">\n                                <div className=\"flex items-center space-x-3 w-full\">\n                                  <RadioGroupItem\n                                    id={`payment-method-${method.code}`}\n                                    value={method.code}\n                                  />\n                                  <Label\n                                    htmlFor={`payment-method-${method.code}`}\n                                    className=\"w-full\"\n                                  >\n                                    {component?.nameRenderer\n                                      ? renderComponent(\n                                          component.nameRenderer,\n                                          {\n                                            isSelected\n                                          }\n                                        )\n                                      : _(method.name)}\n                                  </Label>\n                                </div>\n                              </ItemTitle>\n                              {component?.formRenderer && isSelected && (\n                                <ItemDescription className=\"text-inherit overflow-visible\">\n                                  {renderComponent(component.formRenderer, {\n                                    isSelected\n                                  })}\n                                </ItemDescription>\n                              )}\n                            </ItemContent>\n                          </Item>\n                        );\n                      })}\n                    </RadioGroup>\n                  )}\n                </div>\n\n                {formState.errors.paymentMethod && (\n                  <div className=\"text-destructive text-sm mt-2\">\n                    {formState.errors.paymentMethod?.message?.toString() ||\n                      _('Please select a payment method')}\n                  </div>\n                )}\n              </>\n            )}\n          </ItemDescription>\n        </ItemContent>\n      </Item>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/checkout/shipment/ShippingMethods.tsx",
    "content": "import {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport {\n  RadioGroup,\n  RadioGroupItem\n} from '@components/common/ui/RadioGroup.js';\nimport { useCheckout } from '@components/frontStore/checkout/CheckoutContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { CustomerAddressGraphql } from '@evershop/evershop/types/customerAddress';\nimport { Package } from 'lucide-react';\nimport React from 'react';\n\ninterface ShippingMethod {\n  code: string;\n  name: string;\n  cost?: {\n    value: number;\n    text: string;\n  };\n  description?: string;\n  isSelected?: boolean;\n}\n\n// Skeleton component for loading state\nfunction ShippingMethodSkeleton() {\n  return (\n    <div className=\"shipping-method-skeleton\">\n      {[1, 2, 3, 4].map((index) => (\n        <div\n          key={index}\n          className=\"border border-gray-200 rounded-lg p-4 mb-3 animate-pulse\"\n        >\n          <div className=\"flex items-center justify-between\">\n            <div className=\"flex items-center space-x-3\">\n              <div className=\"w-4 h-4 bg-gray-200 rounded-full\"></div>\n              <div className=\"space-y-2\">\n                <div className=\"h-4 bg-gray-200 rounded w-20\"></div>\n                <div className=\"h-3 bg-gray-200 rounded w-40\"></div>\n              </div>\n            </div>\n            <div className=\"text-right space-y-1\">\n              <div className=\"h-3 bg-gray-200 rounded w-12\"></div>\n              <div className=\"h-4 bg-gray-200 rounded w-16\"></div>\n            </div>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nexport function ShippingMethods({\n  methods,\n  shippingAddress,\n  isLoading,\n  onSelect\n}: {\n  methods: ShippingMethod[];\n  shippingAddress?: CustomerAddressGraphql;\n  isLoading?: boolean;\n  onSelect?: (method: ShippingMethod) => Promise<boolean> | boolean;\n}) {\n  const { form } = useCheckout();\n  const { formState, setValue, watch } = form;\n  const [isProcessing, setIsProcessing] = React.useState(false);\n  const currentValue = watch('shippingMethod');\n\n  const handleMethodSelect = async (method: ShippingMethod) => {\n    if (!onSelect) {\n      // If no onSelect function provided, allow normal behavior\n      setValue('shippingMethod', method.code);\n      return;\n    }\n\n    if (isProcessing || formState.disabled) {\n      return;\n    }\n\n    try {\n      setIsProcessing(true);\n      const result = await Promise.resolve(onSelect(method));\n\n      if (result) {\n        // Only update the form value if onSelect returns true\n        setValue('shippingMethod', method.code);\n      }\n      // If result is false, keep the current selection\n    } catch (error) {\n      // Keep the current selection on error\n    } finally {\n      setIsProcessing(false);\n    }\n  };\n\n  return (\n    <div className=\"checkout-shipment\">\n      <Card>\n        <CardHeader>\n          <CardTitle>\n            <div className=\"flex items-center gap-2\">\n              <Package className=\"w-5 h-5\" />\n              <span>{_('Shipping Method')}</span>\n            </div>\n          </CardTitle>\n        </CardHeader>\n        <CardContent>\n          {isLoading ? (\n            <ShippingMethodSkeleton />\n          ) : (\n            <>\n              <div className=\"shipping-methods-list\">\n                <input\n                  type=\"hidden\"\n                  {...form.register('shippingMethod', { required: true })}\n                  defaultValue={currentValue}\n                />\n                {methods?.length === 0 ? (\n                  <div className=\"text-left\">\n                    {!shippingAddress?.country || !shippingAddress?.province ? (\n                      <div>\n                        <div className=\"text-sm\">\n                          {_(\n                            'Available shipping methods will appear once you provide your address details'\n                          )}\n                        </div>\n                      </div>\n                    ) : (\n                      <div>\n                        <div className=\"mb-2\">\n                          {_('No shipping methods available')}\n                        </div>\n                        <div className=\"text-sm\">\n                          {_(\n                            'No shipping options are available for your location'\n                          )}\n                        </div>\n                      </div>\n                    )}\n                  </div>\n                ) : (\n                  <RadioGroup\n                    value={currentValue}\n                    onValueChange={(value) => {\n                      const method = methods.find((m) => m.code === value);\n                      if (method) {\n                        handleMethodSelect(method);\n                      } else {\n                        setValue('shippingMethod', value);\n                      }\n                    }}\n                  >\n                    {methods.map((method: ShippingMethod) => (\n                      <Item\n                        key={method.code}\n                        className={`cursor-pointer transition-colors ${\n                          currentValue === method.code\n                            ? 'border-primary bg-primary-foreground/10 hover:border-primary'\n                            : 'border-border'\n                        } ${\n                          isProcessing ? 'opacity-50 cursor-not-allowed' : ''\n                        }`}\n                      >\n                        <ItemContent>\n                          <ItemTitle>\n                            <div className=\"flex items-center gap-2\">\n                              <RadioGroupItem\n                                id={`shipping-method-${method.code}`}\n                                value={method.code}\n                                onChange={() => {\n                                  !isProcessing && handleMethodSelect(method);\n                                }}\n                                disabled={isProcessing}\n                              />\n                              <Label htmlFor={`shipping-method-${method.code}`}>\n                                {method.name}\n                              </Label>\n                            </div>\n                          </ItemTitle>\n                          {method.description && (\n                            <ItemDescription>\n                              {method.description}\n                            </ItemDescription>\n                          )}\n                        </ItemContent>\n                        <ItemActions>\n                          {method.cost ? (\n                            <>\n                              {method.cost.value > 0 ? (\n                                <div className=\"font-medium\">\n                                  {method.cost.text}\n                                </div>\n                              ) : (\n                                <>\n                                  <div className=\"text-sm text-gray-500 line-through\">\n                                    {method.cost.text}\n                                  </div>\n                                  <div className=\"font-medium text-primary\">\n                                    {_('FREE')}\n                                  </div>\n                                </>\n                              )}\n                            </>\n                          ) : (\n                            <div className=\"font-medium text-gray-900\">\n                              {_('Contact for pricing')}\n                            </div>\n                          )}\n                        </ItemActions>\n                      </Item>\n                    ))}\n                  </RadioGroup>\n                )}\n              </div>\n              {formState.errors.shippingMethod && (\n                <div className=\"text-destructive text-sm mt-2\">\n                  {formState.errors.shippingMethod?.message?.toString() ||\n                    _('Please select a shipping method')}\n                </div>\n              )}\n            </>\n          )}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/AccountInfo.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport {\n  useCustomer,\n  useCustomerDispatch\n} from '@components/frontStore/customer/CustomerContext.jsx';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { Mail, User } from 'lucide-react';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface AccountInfoProps {\n  title?: string;\n  showLogout?: boolean;\n}\nexport default function AccountInfo({ title, showLogout }: AccountInfoProps) {\n  const { customer: account } = useCustomer();\n  const { logout } = useCustomerDispatch();\n  return (\n    <div className=\"account__details divide-y\">\n      <div className=\"flex justify-between items-center border-border\">\n        {title && <h2>{title}</h2>}\n        {showLogout && (\n          <a\n            className=\"text-interactive\"\n            href=\"#\"\n            onClick={async (e) => {\n              e.preventDefault();\n              try {\n                await logout();\n                window.location.href = '/';\n              } catch (error) {\n                toast.error(error.message);\n              }\n            }}\n          >\n            {_('Logout')}\n          </a>\n        )}\n      </div>\n      <div className=\"grid grid-cols-1 gap-2 py-5\">\n        <Area\n          id=\"accountDetails\"\n          coreComponents={[\n            {\n              component: {\n                default: (\n                  <div className=\"account__details__name flex gap-2 py-2\">\n                    <div>\n                      <User width={20} height={20} />\n                    </div>\n                    <div>{account?.fullName}</div>\n                  </div>\n                )\n              },\n              sortOrder: 10\n            },\n            {\n              component: {\n                default: () => (\n                  <div className=\"account__details__email flex gap-2 py-2\">\n                    <div>\n                      <Mail width={20} height={20} />\n                    </div>\n                    <div>{account?.email}</div>\n                  </div>\n                )\n              },\n              sortOrder: 15\n            }\n          ]}\n        />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/CustomerContext.tsx",
    "content": "import { useAppDispatch } from '@components/common/context/app.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { CustomerAddressGraphql } from '@evershop/evershop/types/customerAddress';\nimport { produce } from 'immer';\nimport React, {\n  createContext,\n  useReducer,\n  useContext,\n  ReactNode,\n  useCallback,\n  useMemo,\n  useEffect\n} from 'react';\n\ntype ExtendedCustomerAddress = CustomerAddressGraphql & {\n  addressId: string | number;\n  isDefault?: boolean;\n  updateApi?: string;\n  deleteApi?: string;\n};\n\nexport interface OrderItem {\n  orderItemId: string;\n  uuid: string;\n  productId: string;\n  qty: number;\n  productSku: string;\n  productName: string;\n  productUrl: string;\n  thumbnail?: string;\n  productWeight: {\n    value: number;\n    unit: string;\n  };\n  variantOptions?: {\n    attributeCode: string;\n    attributeName: string;\n    attributeId: number;\n    optionId: number;\n    optionText: string;\n  }[];\n  productPrice: {\n    value: number;\n    text: string;\n  };\n  productPriceInclTax: {\n    value: number;\n    text: string;\n  };\n  finalPrice: {\n    value: number;\n    text: string;\n  };\n  finalPriceInclTax: {\n    value: number;\n    text: string;\n  };\n  taxPercent: number;\n  taxAmount: {\n    value: number;\n    text: string;\n  };\n  taxAmountBeforeDiscount: {\n    value: number;\n    text: string;\n  };\n  discountAmount: {\n    value: number;\n    text: string;\n  };\n  lineTotal: {\n    value: number;\n    text: string;\n  };\n  subTotal: {\n    value: number;\n    text: string;\n  };\n  lineTotalWithDiscount: {\n    value: number;\n    text: string;\n  };\n  lineTotalWithDiscountInclTax: {\n    value: number;\n    text: string;\n  };\n  lineTotalInclTax: {\n    value: number;\n    text: string;\n  };\n  total: {\n    value: number;\n    text: string;\n  };\n  variantGroupId?: number;\n}\n\nexport interface Order {\n  orderId: number;\n  uuid: string;\n  orderNumber: string;\n  currency: string;\n  customerId?: number;\n  customerGroupId?: number;\n  customerEmail?: string;\n  customerFullName?: string;\n  coupon?: string;\n  shippingMethod?: string;\n  shippingMethodName?: string;\n  paymentMethod?: string;\n  paymentMethodName?: string;\n  shippingNote?: string;\n  status: {\n    name: string;\n    code: string;\n    badge: string;\n  };\n  shipmentStatus?: {\n    name: string;\n    code: string;\n    badge: string;\n  };\n  paymentStatus?: {\n    name: string;\n    code: string;\n    badge: string;\n  };\n  items: OrderItem[];\n  totalQty: number;\n  totalWeight: {\n    value: number;\n    unit: string;\n  };\n  taxAmount: {\n    value: number;\n    text: string;\n  };\n  totalTaxAmount: {\n    value: number;\n    text: string;\n  };\n  taxAmountBeforeDiscount: {\n    value: number;\n    text: string;\n  };\n  discountAmount: {\n    value: number;\n    text: string;\n  };\n  shippingFeeExclTax: {\n    value: number;\n    text: string;\n  };\n  shippingFeeInclTax: {\n    value: number;\n    text: string;\n  };\n  shippingTaxAmount: {\n    value: number;\n    text: string;\n  };\n  subTotal: {\n    value: number;\n    text: string;\n  };\n  subTotalInclTax: {\n    value: number;\n    text: string;\n  };\n  subTotalWithDiscount: {\n    value: number;\n    text: string;\n  };\n  subTotalWithDiscountInclTax: {\n    value: number;\n    text: string;\n  };\n  grandTotal: {\n    value: number;\n    text: string;\n  };\n  billingAddress?: CustomerAddressGraphql;\n  shippingAddress?: CustomerAddressGraphql;\n  createdAt: {\n    value: string;\n    text: string;\n  };\n  updatedAt: {\n    value: string;\n    text: string;\n  };\n}\n\ninterface Customer {\n  uuid: string;\n  email: string;\n  fullName: string;\n  groupId: string;\n  addresses: ExtendedCustomerAddress[];\n  orders: Order[];\n  addAddressApi: string;\n  createdAt: {\n    value: string;\n    text: string;\n  };\n  [key: string]: unknown;\n}\n\ninterface CustomerState {\n  customer: Customer | undefined; // undefined for guest users\n  isLoading: boolean;\n}\n\ntype CustomerAction =\n  | { type: 'SET_LOADING'; payload: boolean }\n  | { type: 'SET_CUSTOMER'; payload: Customer | undefined }\n  | { type: 'LOGOUT' };\n\nconst initialState: CustomerState = {\n  customer: undefined,\n  isLoading: false\n};\n\nconst customerReducer = (\n  state: CustomerState,\n  action: CustomerAction\n): CustomerState => {\n  return produce(state, (draft) => {\n    switch (action.type) {\n      case 'SET_LOADING':\n        draft.isLoading = action.payload;\n        break;\n      case 'SET_CUSTOMER':\n        draft.customer = action.payload;\n        draft.isLoading = false;\n        break;\n      case 'LOGOUT':\n        draft.customer = undefined;\n        draft.isLoading = false;\n        break;\n    }\n  });\n};\n\ninterface CustomerContextValue extends CustomerState {}\n\ninterface CustomerDispatchContextValue {\n  login: (\n    data: {\n      email: string;\n      password: string;\n      [key: string]: unknown;\n    },\n    redirectUrl: string\n  ) => Promise<boolean>;\n  register: (\n    data: {\n      full_name: string;\n      email: string;\n      password: string;\n      [key: string]: unknown;\n    },\n    loginIfSuccess: boolean,\n    redirectUrl: string\n  ) => Promise<boolean>;\n  logout: () => Promise<void>;\n  setCustomer: (customer: Customer | undefined) => void;\n  addAddress: (\n    addressData: Omit<ExtendedCustomerAddress, 'id'>\n  ) => Promise<ExtendedCustomerAddress>;\n  updateAddress: (\n    addressId: string | number,\n    addressData: Partial<ExtendedCustomerAddress>\n  ) => Promise<ExtendedCustomerAddress>;\n  deleteAddress: (addressId: string | number) => Promise<void>;\n}\n\nconst CustomerContext = createContext<CustomerContextValue | undefined>(\n  undefined\n);\nconst CustomerDispatchContext = createContext<\n  CustomerDispatchContextValue | undefined\n>(undefined);\n\ninterface CustomerProviderProps {\n  children: ReactNode;\n  loginAPI: string;\n  logoutAPI: string;\n  registerAPI: string;\n  initialCustomer?: Customer;\n}\n\nconst retry = async (\n  fn: () => Promise<Response>,\n  retries = 3,\n  delay = 1000\n): Promise<Response> => {\n  try {\n    return await fn();\n  } catch (error) {\n    if (retries > 0) {\n      await new Promise((resolve) => setTimeout(resolve, delay));\n      return retry(fn, retries - 1, delay * 2);\n    }\n    throw error;\n  }\n};\n\nexport function CustomerProvider({\n  children,\n  loginAPI,\n  registerAPI,\n  logoutAPI,\n  initialCustomer\n}: CustomerProviderProps) {\n  const [state, dispatch] = useReducer(customerReducer, {\n    ...initialState,\n    customer: initialCustomer\n  });\n\n  const appDispatch = useAppDispatch();\n\n  // Effect to update customer when initialCustomer prop changes\n  useEffect(() => {\n    // Compare by JSON string to handle object changes properly\n    const currentCustomerStr = JSON.stringify(state.customer);\n    const initialCustomerStr = JSON.stringify(initialCustomer);\n\n    if (initialCustomerStr !== currentCustomerStr) {\n      dispatch({ type: 'SET_CUSTOMER', payload: initialCustomer });\n    }\n  }, [initialCustomer]);\n\n  // Helper function to get current URL with isAjax=true\n  const getCurrentAjaxUrl = useCallback(() => {\n    const currentUrl = new URL(window.location.href);\n    currentUrl.searchParams.set('ajax', 'true');\n    return currentUrl.toString();\n  }, []);\n\n  // Login function\n  const login = useCallback(\n    async (\n      data: {\n        email: string;\n        password: string;\n        [key: string]: unknown;\n      },\n      redirectUrl: string\n    ): Promise<boolean> => {\n      dispatch({ type: 'SET_LOADING', payload: true });\n\n      try {\n        const response = await retry(() =>\n          fetch(loginAPI, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify(data)\n          })\n        );\n\n        const json = await response.json();\n\n        if (!response.ok) {\n          throw new Error(json.error?.message || _('Login failed'));\n        }\n\n        // Trigger page data refresh which will update customer via useEffect\n        await appDispatch.fetchPageData(getCurrentAjaxUrl());\n        if (redirectUrl) {\n          window.location.href = redirectUrl;\n        }\n        return true;\n      } catch (error) {\n        dispatch({ type: 'SET_LOADING', payload: false });\n        throw error;\n      }\n    },\n    [loginAPI, appDispatch, getCurrentAjaxUrl]\n  );\n\n  const register = useCallback(\n    async (\n      data: {\n        full_name: string;\n        email: string;\n        password: string;\n        [key: string]: unknown;\n      },\n      loginIfSuccess: boolean,\n      redirectUrl: string\n    ): Promise<boolean> => {\n      if (state.customer) {\n        throw new Error(_('You are already logged in'));\n      }\n      dispatch({ type: 'SET_LOADING', payload: true });\n\n      try {\n        const response = await retry(() =>\n          fetch(registerAPI, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify(data)\n          })\n        );\n\n        const json = await response.json();\n\n        if (!response.ok) {\n          throw new Error(json.error?.message || _('Registration failed'));\n        }\n\n        // Trigger page data refresh which will update customer via useEffect\n        await appDispatch.fetchPageData(getCurrentAjaxUrl());\n        if (loginIfSuccess) {\n          // Auto login after successful registration\n          await login(\n            { email: data.email, password: data.password },\n            redirectUrl\n          );\n        }\n        return true;\n      } catch (error) {\n        dispatch({ type: 'SET_LOADING', payload: false });\n        throw error;\n      }\n    },\n    [registerAPI, appDispatch, getCurrentAjaxUrl, login]\n  );\n\n  // Logout function\n  const logout = useCallback(async (): Promise<void> => {\n    dispatch({ type: 'SET_LOADING', payload: true });\n\n    try {\n      await retry(() =>\n        fetch(logoutAPI, {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' }\n        })\n      );\n\n      // After successful logout, clear customer data locally\n      dispatch({ type: 'LOGOUT' });\n    } catch (error) {\n      // Even if logout API fails, clear local customer data\n      dispatch({ type: 'LOGOUT' });\n      throw error;\n    } finally {\n      dispatch({ type: 'SET_LOADING', payload: false });\n    }\n  }, [logoutAPI]);\n\n  // Set customer directly (for external updates)\n  const setCustomer = useCallback((customer: Customer | undefined) => {\n    dispatch({ type: 'SET_CUSTOMER', payload: customer });\n  }, []);\n\n  // Add address function\n  const addAddress = useCallback(\n    async (\n      addressData: Omit<ExtendedCustomerAddress, 'id'>\n    ): Promise<ExtendedCustomerAddress> => {\n      if (!state.customer?.addAddressApi) {\n        throw new Error(_('Add address API not available'));\n      }\n\n      dispatch({ type: 'SET_LOADING', payload: true });\n\n      const response = await retry(() =>\n        fetch(state.customer!.addAddressApi!, {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify(addressData)\n        })\n      );\n\n      const json = await response.json();\n\n      if (!response.ok) {\n        throw new Error(json.error?.message || _('Failed to add address'));\n      }\n\n      if (json.error) {\n        throw new Error(json.error.message || _('Failed to add address'));\n      }\n\n      // Sync with server to get fresh customer data including the new address\n      await appDispatch.fetchPageData(getCurrentAjaxUrl());\n\n      // Return the address from the API response for immediate use\n      const newAddress = json.data;\n      if (!newAddress) {\n        throw new Error(_('No address data received'));\n      }\n\n      return newAddress;\n    },\n    [state.customer, appDispatch, getCurrentAjaxUrl]\n  );\n\n  // Update address function\n  const updateAddress = useCallback(\n    async (\n      addressId: string | number,\n      addressData: Partial<ExtendedCustomerAddress>\n    ): Promise<ExtendedCustomerAddress> => {\n      const address = state.customer?.addresses?.find(\n        (addr) => addr.addressId === addressId\n      );\n      if (!address?.updateApi) {\n        throw new Error(_('Update address API not available'));\n      }\n\n      dispatch({ type: 'SET_LOADING', payload: true });\n\n      const response = await retry(() =>\n        fetch(address.updateApi!, {\n          method: 'PATCH',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify(addressData)\n        })\n      );\n\n      const json = await response.json();\n\n      if (!response.ok) {\n        throw new Error(json.error?.message || _('Failed to update address'));\n      }\n\n      if (json.error) {\n        throw new Error(json.error.message || _('Failed to update address'));\n      }\n\n      // Sync with server to get fresh customer data including the updated address\n      await appDispatch.fetchPageData(getCurrentAjaxUrl());\n\n      // Return the address from the API response for immediate use\n      const updatedAddress = json.data;\n      if (!updatedAddress) {\n        throw new Error(_('No address data received'));\n      }\n\n      return updatedAddress;\n    },\n    [state.customer, appDispatch, getCurrentAjaxUrl]\n  );\n\n  // Delete address function\n  const deleteAddress = useCallback(\n    async (addressId: string | number): Promise<void> => {\n      const address = state.customer?.addresses?.find(\n        (addr) => addr.addressId === addressId\n      );\n      if (!address?.deleteApi) {\n        throw new Error(_('Delete address API not available'));\n      }\n\n      dispatch({ type: 'SET_LOADING', payload: true });\n\n      const response = await retry(() =>\n        fetch(address.deleteApi!, {\n          method: 'DELETE',\n          headers: { 'Content-Type': 'application/json' }\n        })\n      );\n\n      const json = await response.json();\n\n      if (!response.ok) {\n        throw new Error(json.error?.message || _('Failed to delete address'));\n      }\n\n      if (json.error) {\n        throw new Error(json.error.message || _('Failed to delete address'));\n      }\n\n      await appDispatch.fetchPageData(getCurrentAjaxUrl());\n    },\n    [state.customer, appDispatch, getCurrentAjaxUrl]\n  );\n\n  const contextValue = useMemo(\n    (): CustomerContextValue => ({\n      ...state\n    }),\n    [state]\n  );\n\n  const dispatchMethods = useMemo(\n    (): CustomerDispatchContextValue => ({\n      login,\n      register,\n      logout,\n      setCustomer,\n      addAddress,\n      updateAddress,\n      deleteAddress\n    }),\n    [login, logout, setCustomer, addAddress, updateAddress, deleteAddress]\n  );\n\n  return (\n    <CustomerDispatchContext.Provider value={dispatchMethods}>\n      <CustomerContext.Provider value={contextValue}>\n        {children}\n      </CustomerContext.Provider>\n    </CustomerDispatchContext.Provider>\n  );\n}\n\nexport const useCustomer = (): CustomerContextValue => {\n  const context = useContext(CustomerContext);\n  if (context === undefined) {\n    throw new Error('useCustomer must be used within a CustomerProvider');\n  }\n  return context;\n};\n\nexport const useCustomerDispatch = (): CustomerDispatchContextValue => {\n  const context = useContext(CustomerDispatchContext);\n  if (context === undefined) {\n    throw new Error(\n      'useCustomerDispatch must be used within a CustomerProvider'\n    );\n  }\n  return context;\n};\n\nexport type { Customer, CustomerState, ExtendedCustomerAddress };\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/LoginForm.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Form, useFormContext } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { PasswordField } from '@components/common/form/PasswordField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { useCustomerDispatch } from '@components/frontStore/customer/CustomerContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { LockKeyhole, Mail } from 'lucide-react';\nimport React from 'react';\n\nconst SubmitButton: React.FC<{ formId: string }> = ({ formId }) => {\n  const {\n    formState: { isSubmitting }\n  } = useFormContext();\n  return (\n    <div className=\"form-submit-button flex border-t border-border mt-4 pt-4 justify-between\">\n      <Button\n        className={'w-full'}\n        size={'lg'}\n        onClick={() => {\n          (document.getElementById(formId) as HTMLFormElement).dispatchEvent(\n            new Event('submit', { cancelable: true, bubbles: true })\n          );\n        }}\n        isLoading={isSubmitting}\n      >\n        {_(isSubmitting ? 'Signing In...' : 'Sign In')}\n      </Button>\n    </div>\n  );\n};\n\nexport const CustomerLoginForm: React.FC<{\n  title?: string;\n  subtitle?: string;\n  redirectUrl: string;\n  onError?: (error: any) => void;\n  className?: string;\n}> = ({ title, subtitle, redirectUrl, onError, className }) => {\n  const { login } = useCustomerDispatch();\n  return (\n    <div className={cn(`login__form`, className)}>\n      <div className=\"login__form__inner w-full\">\n        <Area id=\"customerLoginFormTitleBefore\" noOuter />\n        {title && (\n          <h1 className=\"login__form__title text-2xl text-center mb-6\">\n            {_(title)}\n          </h1>\n        )}\n        {subtitle && (\n          <p className=\"login__form__subtitle text-center mb-6\">\n            {_(subtitle)}\n          </p>\n        )}\n        <Area id=\"customerLoginFormTitleAfter\" noOuter />\n        <Area id=\"customerLoginFormBefore\" noOuter />\n        <Form\n          id=\"loginForm\"\n          method=\"POST\"\n          onSubmit={async (data) => {\n            try {\n              await login(\n                {\n                  email: data.email,\n                  password: data.password,\n                  ...data\n                },\n                redirectUrl\n              );\n            } catch (error) {\n              onError?.(error);\n            }\n          }}\n          onError={onError}\n          submitBtn={false}\n        >\n          <Area\n            id=\"customerLoginForm\"\n            className=\"space-y-3\"\n            coreComponents={[\n              {\n                component: {\n                  default: (\n                    <InputField\n                      prefixIcon={<Mail className=\"h-5 w-5\" />}\n                      label={_('Email')}\n                      name=\"email\"\n                      placeholder={_('Email')}\n                      required\n                      validation={{\n                        required: _('Email is required')\n                      }}\n                    />\n                  )\n                },\n                sortOrder: 10\n              },\n              {\n                component: {\n                  default: (\n                    <PasswordField\n                      prefixIcon={<LockKeyhole className=\"h-5 w-5\" />}\n                      label={_('Password')}\n                      name=\"password\"\n                      placeholder={_('Password')}\n                      required\n                      validation={{\n                        required: _('Password is required')\n                      }}\n                      showToggle\n                    />\n                  )\n                },\n                sortOrder: 20\n              },\n              {\n                component: {\n                  default: <SubmitButton formId=\"loginForm\" />\n                },\n                sortOrder: 30\n              }\n            ]}\n          />\n        </Form>\n        <Area id=\"customerLoginFormAfter\" noOuter />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/MyAddresses.tsx",
    "content": "import { AddressSummary } from '@components/common/customer/address/AddressSummary.jsx';\nimport { CheckboxField } from '@components/common/form/CheckboxField.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport { Item, ItemActions, ItemContent } from '@components/common/ui/Item.js';\nimport CustomerAddressForm from '@components/frontStore/customer/address/addressForm/Index.js';\nimport {\n  ExtendedCustomerAddress,\n  useCustomer,\n  useCustomerDispatch\n} from '@components/frontStore/customer/CustomerContext.jsx';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\nconst Address: React.FC<{\n  address: ExtendedCustomerAddress;\n}> = ({ address }) => {\n  const { updateAddress, deleteAddress } = useCustomerDispatch();\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const classes = address.isDefault ? 'border-2 border-primary' : '';\n  return (\n    <Item variant={'outline'} className={`${classes}`}>\n      <ItemContent>\n        <AddressSummary address={address} />\n      </ItemContent>\n      <ItemActions>\n        <div className=\"flex flex-col items-start gap-1\">\n          <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n            <DialogTrigger>\n              <Button\n                variant=\"outline\"\n                onClick={(e) => {\n                  e.preventDefault();\n                }}\n              >\n                {_('Edit')}\n              </Button>\n            </DialogTrigger>\n            <DialogContent>\n              <DialogHeader>\n                <DialogTitle>{_('Edit Address')}</DialogTitle>\n              </DialogHeader>\n              <Form\n                id=\"customerAddressForm\"\n                method=\"PATCH\"\n                onSubmit={async (data) => {\n                  try {\n                    await updateAddress(address.addressId, data);\n                    setDialogOpen(false);\n                    toast.success(_('Address has been updated successfully!'));\n                  } catch (error) {\n                    toast.error(error.message);\n                  }\n                }}\n              >\n                <CustomerAddressForm address={address} fieldNamePrefix=\"\" />\n                <div className=\"mt-3\">\n                  <CheckboxField\n                    label={_('Set as default')}\n                    defaultChecked={address.isDefault}\n                    name=\"is_default\"\n                  />\n                </div>\n              </Form>\n            </DialogContent>\n            <DialogFooter>\n              <Button\n                variant=\"destructive\"\n                onClick={async (e) => {\n                  e.preventDefault();\n                  try {\n                    await deleteAddress(address.addressId);\n                    toast.success(_('Address has been deleted successfully!'));\n                  } catch (error) {\n                    toast.error(error.message);\n                  }\n                }}\n              >\n                {_('Delete')}\n              </Button>\n            </DialogFooter>\n          </Dialog>\n        </div>\n      </ItemActions>\n    </Item>\n  );\n};\n\nexport function MyAddresses({ title }: { title?: string }) {\n  const { customer } = useCustomer();\n  const { addAddress } = useCustomerDispatch();\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  if (!customer) {\n    return null;\n  }\n  return (\n    <div>\n      {title && (\n        <div className=\"border-b mb-5 border-gray-200\">\n          <h2>{_('Address Book')}</h2>\n        </div>\n      )}\n      {customer.addresses.length === 0 && (\n        <div className=\"order-history-empty\">\n          {_('You have no addresses saved')}\n        </div>\n      )}\n      <div className=\"grid grid-cols-1 md:grid-cols-3 gap-5 mb-3\">\n        {customer.addresses.map((address) => (\n          <Address key={address.uuid} address={address} />\n        ))}\n      </div>\n      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n        <DialogTrigger>\n          <Button\n            variant=\"outline\"\n            onClick={(e) => {\n              e.preventDefault();\n            }}\n          >\n            {_('Add new address')}\n          </Button>\n        </DialogTrigger>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>{_('Add new address')}</DialogTitle>\n          </DialogHeader>\n          <Form\n            id=\"customerAddressForm\"\n            method={'POST'}\n            onSubmit={async (data) => {\n              try {\n                await addAddress(data as ExtendedCustomerAddress);\n                setDialogOpen(false);\n                toast.success(_('Address has been saved successfully!'));\n              } catch (error) {\n                toast.error(error.message);\n              }\n            }}\n          >\n            <CustomerAddressForm address={undefined} fieldNamePrefix=\"\" />\n            <div className=\"mt-3\">\n              <CheckboxField\n                label={_('Set as default')}\n                defaultChecked={false}\n                name=\"is_default\"\n              />\n            </div>\n          </Form>\n        </DialogContent>\n      </Dialog>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/OrderHistory.tsx",
    "content": "import { Image } from '@components/common/Image.js';\nimport { ProductNoThumbnail } from '@components/common/ProductNoThumbnail.js';\nimport {\n  Order,\n  useCustomer\n} from '@components/frontStore/customer/CustomerContext.jsx';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nconst OrderDetail: React.FC<{ order: Order }> = ({ order }) => {\n  return (\n    <div className=\"order border-divider\">\n      <div className=\"order-inner grid grid-cols-1 md:grid-cols-3 gap-5\">\n        <div className=\"order-items col-span-2\">\n          {order.items.map((item) => (\n            <div\n              className=\"order-item mb-2 flex gap-5 items-center\"\n              key={item.productSku}\n            >\n              <div className=\"thumbnail border border-divider p-2 rounded\">\n                {item.thumbnail && (\n                  <Image\n                    width={50}\n                    height={50}\n                    style={{ maxWidth: '6rem' }}\n                    src={item.thumbnail}\n                    alt={item.productName}\n                  />\n                )}\n                {!item.thumbnail && (\n                  <ProductNoThumbnail width={50} height={50} />\n                )}\n              </div>\n              <div className=\"order-item-info\">\n                <div className=\"order-item-name font-semibold\">\n                  {item.productName}\n                </div>\n                <div className=\"order-item-sku italic\">\n                  {_('Sku')}: #{item.productSku}\n                </div>\n                <div className=\"order-item-qty\">\n                  {item.qty} x {item.productPrice.text}\n                </div>\n              </div>\n            </div>\n          ))}\n        </div>\n        <div className=\"order-total col-span-1\">\n          <div className=\"order-header\">\n            <div className=\"order-number\">\n              <span className=\"font-bold\">\n                {_('Order')}: #{order.orderNumber}\n              </span>\n              <span className=\"italic pl-2\">{order.createdAt.text}</span>\n            </div>\n          </div>\n          <div className=\"order-total-value font-bold\">\n            {_('Total')}:{order.grandTotal.text}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default function OrderHistory({ title }: { title?: string }) {\n  const { customer } = useCustomer();\n  const orders = customer?.orders || [];\n  return (\n    <div className=\"order-history divide-y\">\n      {title && <h2 className=\"order-history-title border-border\">{title}</h2>}\n      {orders.length === 0 && (\n        <div className=\"order-history-empty\">\n          {_('You have not placed any orders yet')}\n        </div>\n      )}\n      {orders.map((order) => (\n        <div\n          className=\"order-history-order border-divider py-5\"\n          key={order.orderId}\n        >\n          <OrderDetail order={order} key={order.orderId} />\n        </div>\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/RegistrationForm.tsx",
    "content": "import { EmailField } from '@components/common/form/EmailField.js';\nimport { Form, useFormContext } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { PasswordField } from '@components/common/form/PasswordField.js';\nimport { Area } from '@components/common/index.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { useCustomerDispatch } from '@components/frontStore/customer/CustomerContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { LockKeyhole, Mail, User } from 'lucide-react';\nimport React from 'react';\n\nconst SubmitButton: React.FC<{ formId: string }> = ({ formId }) => {\n  const {\n    formState: { isSubmitting }\n  } = useFormContext();\n  return (\n    <div className=\"form-submit-button flex border-t border-border mt-4 pt-4 justify-between\">\n      <Button\n        className={'w-full'}\n        size={'lg'}\n        onClick={() => {\n          (document.getElementById(formId) as HTMLFormElement).dispatchEvent(\n            new Event('submit', { cancelable: true, bubbles: true })\n          );\n        }}\n        isLoading={isSubmitting}\n      >\n        {_(isSubmitting ? 'Signing Up...' : 'Sign Up')}\n      </Button>\n    </div>\n  );\n};\n\nexport const CustomerRegistrationForm: React.FC<{\n  title?: string;\n  subtitle?: string;\n  className?: string;\n  redirectUrl: string;\n  onError?: (error: string) => void;\n}> = ({ title, subtitle, redirectUrl, onError, className }) => {\n  const { register } = useCustomerDispatch();\n  return (\n    <div className={`register__form ${className}`}>\n      <div className=\"register__form__inner w-full\">\n        <Area id=\"customerRegisterFormTitleBefore\" noOuter />\n        {title && (\n          <h1 className=\"register__form__title text-2xl text-center mb-6\">\n            {_(title)}\n          </h1>\n        )}\n        <Area id=\"customerRegisterFormTitleAfter\" noOuter />\n        {subtitle && (\n          <p className=\"register__form__subtitle text-center mb-6\">\n            {_(subtitle)}\n          </p>\n        )}\n        <Area id=\"customerRegisterFormBefore\" noOuter />\n        <Form\n          id=\"registerForm\"\n          method=\"POST\"\n          onSubmit={async (data) => {\n            try {\n              await register(\n                {\n                  full_name: data.full_name,\n                  email: data.email,\n                  password: data.password,\n                  ...data\n                },\n                true,\n                redirectUrl\n              );\n            } catch (error) {\n              onError?.(error.message);\n            }\n          }}\n          submitBtn={false}\n        >\n          <Area\n            id=\"customerRegisterForm\"\n            className=\"space-y-3\"\n            coreComponents={[\n              {\n                component: {\n                  default: (\n                    <InputField\n                      prefixIcon={<User className=\"h-5 w-5\" />}\n                      name=\"full_name\"\n                      label={_('Full Name')}\n                      placeholder={_('Full Name')}\n                      required\n                      validation={{ required: _('Full Name is required') }}\n                    />\n                  )\n                },\n                sortOrder: 10\n              },\n              {\n                component: {\n                  default: (\n                    <EmailField\n                      prefixIcon={<Mail className=\"h-5 w-5\" />}\n                      name=\"email\"\n                      label={_('Email')}\n                      placeholder={_('Email')}\n                      required\n                      validation={{ required: _('Email is required') }}\n                    />\n                  )\n                },\n                sortOrder: 20\n              },\n              {\n                component: {\n                  default: (\n                    <PasswordField\n                      prefixIcon={<LockKeyhole className=\"h-5 w-5\" />}\n                      name=\"password\"\n                      label={_('Password')}\n                      placeholder={_('Password')}\n                      required\n                      showToggle\n                      validation={{ required: _('Password is required') }}\n                    />\n                  )\n                },\n                sortOrder: 30\n              },\n              {\n                component: {\n                  default: <SubmitButton formId=\"registerForm\" />\n                },\n                sortOrder: 30\n              }\n            ]}\n          />\n        </Form>\n        <Area id=\"customerRegisterFormAfter\" noOuter />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/ResetPasswordForm.tsx",
    "content": "import { EmailField } from '@components/common/form/EmailField.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { Mail } from 'lucide-react';\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\n\nexport const ResetPasswordForm: React.FC<{\n  title: string;\n  subtitle: string;\n  action: string;\n  className: string;\n  onSuccess: () => void;\n}> = ({ title, subtitle, action, className, onSuccess }) => {\n  const [error, setError] = React.useState(null);\n  const form = useForm();\n  const {\n    formState: { isSubmitting: loading }\n  } = form;\n\n  return (\n    <div className=\"flex justify-center items-center\">\n      <div className={`reset__password__form ${className}`}>\n        <div className=\"reset__password__form__inner\">\n          {title && (\n            <h1 className=\"reset__password__form__title text-2xl text-center mb-6\">\n              {title}\n            </h1>\n          )}\n          {subtitle && (\n            <p className=\"reset__password__form__subtitle text-center mb-6\">\n              {subtitle}\n            </p>\n          )}\n          {error && <div className=\"text-destructive mb-2\">{error}</div>}\n          <Form\n            id=\"resetPasswordForm\"\n            form={form}\n            action={action}\n            method=\"POST\"\n            onSuccess={(response) => {\n              if (!response.error) {\n                onSuccess();\n              } else {\n                setError(response.error.message);\n              }\n            }}\n            submitBtn={false}\n          >\n            <EmailField\n              prefixIcon={<Mail className=\"h-5 w-5\" />}\n              name=\"email\"\n              label={_('Email')}\n              placeholder={_('Email')}\n              required\n              validation={{\n                required: _('Email is required')\n              }}\n            />\n            <div className=\"reset__password__form__submit__button flex border-t border-divider mt-2 pt-2\">\n              <Button\n                type=\"submit\"\n                variant={'default'}\n                onClick={() => {\n                  (\n                    document.getElementById(\n                      'resetPasswordForm'\n                    ) as HTMLFormElement\n                  ).dispatchEvent(\n                    new Event('submit', { cancelable: true, bubbles: true })\n                  );\n                }}\n                isLoading={loading}\n              >\n                {_('Reset Password')}\n              </Button>\n            </div>\n          </Form>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/address/addressForm/AddressForm.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { NameAndTelephone } from '@components/frontStore/customer/address/addressForm/NameAndTelephone.js';\nimport { ProvinceAndPostcode } from '@components/frontStore/customer/address/addressForm/ProvinceAndPostcode.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { CustomerAddressGraphql } from '@evershop/evershop/types/customerAddress';\nimport React from 'react';\nimport { useFormContext } from 'react-hook-form';\n\ninterface CustomerAddressFormProps {\n  allowCountries: {\n    value: string;\n    label: string;\n    provinces: {\n      value: string;\n      label: string;\n    }[];\n  }[];\n  address?: CustomerAddressGraphql;\n  areaId?: string;\n  fieldNamePrefix?: string;\n}\nexport function CustomerAddressForm({\n  allowCountries = [],\n  address = {},\n  areaId = 'customerAddressForm',\n  fieldNamePrefix = 'address'\n}: CustomerAddressFormProps) {\n  const { watch, setValue } = useFormContext();\n\n  const getFieldName = (fieldName: string) => {\n    return fieldNamePrefix ? `${fieldNamePrefix}.${fieldName}` : fieldName;\n  };\n\n  const selectedCountry = watch(\n    getFieldName('country'),\n    address?.country?.code || ''\n  );\n  return (\n    <Area\n      id={areaId}\n      className=\"space-y-3\"\n      coreComponents={[\n        {\n          component: {\n            default: (\n              <NameAndTelephone\n                fullName={address?.fullName || ''}\n                telephone={address?.telephone || ''}\n                getFieldName={getFieldName}\n              />\n            )\n          },\n          sortOrder: 10\n        },\n        {\n          component: {\n            default: (\n              <InputField\n                name={getFieldName('address_1')}\n                label={_('Address')}\n                placeholder={_('Address')}\n                defaultValue={address?.address1 || ''}\n                required\n                validation={{\n                  required: _('Address is required')\n                }}\n              />\n            )\n          },\n          sortOrder: 20\n        },\n        {\n          component: {\n            default: (\n              <InputField\n                name={getFieldName('address_2')}\n                label={_('Address 2')}\n                placeholder={_('Address 2')}\n                defaultValue={address?.address2 || ''}\n              />\n            )\n          },\n          sortOrder: 30\n        },\n        {\n          component: {\n            default: (\n              <InputField\n                name={getFieldName('city')}\n                label={_('City')}\n                placeholder={_('City')}\n                required\n                validation={{ required: _('City is required') }}\n                defaultValue={address?.city || ''}\n              />\n            )\n          },\n          sortOrder: 40\n        },\n        {\n          component: {\n            default: (\n              <SelectField\n                defaultValue={address?.country?.code || ''}\n                label={_('Country')}\n                name={getFieldName('country')}\n                placeholder={_('Country')}\n                onChange={(value) => {\n                  setValue(getFieldName('country'), value);\n                  setValue(getFieldName('province'), '');\n                }}\n                required\n                validation={{ required: _('Country is required') }}\n                options={allowCountries}\n              />\n            )\n          },\n          sortOrder: 50\n        },\n        {\n          component: {\n            default: (\n              <ProvinceAndPostcode\n                provinces={\n                  allowCountries.find(\n                    (country) => country.value === selectedCountry\n                  )?.provinces || []\n                }\n                province={address?.province || { code: '' }}\n                postcode={address?.postcode || ''}\n                getFieldName={getFieldName}\n              />\n            )\n          },\n          sortOrder: 60\n        }\n      ]}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/address/addressForm/AddressFormLoadingSkeleton.scss",
    "content": ".address-loading-skeleton {\n  width: 100%;\n  display: flex;\n  justify-content: center;\n  flex-direction: column;\n  padding: 20px;\n  box-sizing: border-box;\n\n  .skeleton:empty {\n    width: 100%;\n    height: 45px;\n    margin-top: 10px;\n    cursor: progress;\n    background: linear-gradient(0.25turn, transparent, #fff, transparent),\n      linear-gradient(#eee, #eee);\n    background-repeat: no-repeat;\n    animation: loading 1.5s infinite;\n    border: 1px solid #eee;\n  }\n\n  @keyframes loading {\n    to {\n      background-position: 315px 0, 0 0, 0 190px, 50px 195px;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/address/addressForm/AddressFormLoadingSkeleton.tsx",
    "content": "import React from 'react';\nimport './AddressFormLoadingSkeleton.scss';\n\nexport function AddressFormLoadingSkeleton() {\n  return (\n    <div className=\"address-loading-skeleton\">\n      <div className=\"grid gap-5 grid-cols-2\">\n        <div className=\"skeleton\" />\n        <div className=\"skeleton\" />\n      </div>\n      <div className=\"skeleton\" />\n      <div className=\"skeleton\" />\n      <div className=\"skeleton\" />\n      <div className=\"grid gap-5 grid-cols-2\">\n        <div className=\"skeleton\" />\n        <div className=\"skeleton\" />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/address/addressForm/Index.tsx",
    "content": "import { CustomerAddressForm } from '@components/frontStore/customer/address/addressForm/AddressForm.js';\nimport { AddressFormLoadingSkeleton } from '@components/frontStore/customer/address/addressForm/AddressFormLoadingSkeleton.js';\nimport { CustomerAddressGraphql } from '@evershop/evershop/types/customerAddress';\nimport React from 'react';\nimport { useQuery } from 'urql';\n\nconst CountriesQuery = `\n  query Country {\n    allowedCountries  {\n      value: code\n      label: name\n      provinces {\n        label: name\n        value: code\n      }\n    }\n  }\n`;\n\ninterface IndexProps {\n  address?: CustomerAddressGraphql;\n  areaId?: string;\n  fieldNamePrefix?: string;\n}\n\nexport default function Index({\n  address = {},\n  areaId = 'customerAddressForm',\n  fieldNamePrefix = 'address'\n}: IndexProps) {\n  const [result] = useQuery({\n    query: CountriesQuery\n  });\n\n  const { data, fetching, error } = result;\n\n  if (fetching) return <AddressFormLoadingSkeleton />;\n  if (error) {\n    return <p className=\"text-destructive\">{error.message}</p>;\n  }\n\n  return (\n    <CustomerAddressForm\n      address={address}\n      areaId={areaId}\n      allowCountries={data.allowedCountries}\n      fieldNamePrefix={fieldNamePrefix}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/address/addressForm/NameAndTelephone.tsx",
    "content": "import { InputField } from '@components/common/form/InputField.js';\nimport { TelField } from '@components/common/form/TelField.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface NameAndTelephoneProps {\n  fullName?: string;\n  telephone?: string;\n  getFieldName?: (fieldName: string) => string;\n}\nexport function NameAndTelephone({\n  fullName,\n  telephone,\n  getFieldName\n}: NameAndTelephoneProps) {\n  return (\n    <div className=\"grid grid-cols-2 gap-2\">\n      <div>\n        <InputField\n          name={getFieldName ? getFieldName('full_name') : 'full_name'}\n          defaultValue={fullName}\n          label={_('Full name')}\n          placeholder={_('Full name')}\n          required\n          validation={{\n            required: _('Full name is required')\n          }}\n        />\n      </div>\n      <div>\n        <TelField\n          name={getFieldName ? getFieldName('telephone') : 'telephone'}\n          defaultValue={telephone}\n          label={_('Telephone')}\n          placeholder={_('Telephone')}\n          required\n          validation={{\n            required: _('Telephone is required')\n          }}\n        />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/components/frontStore/customer/address/addressForm/ProvinceAndPostcode.tsx",
    "content": "import { InputField } from '@components/common/form/InputField.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface ProvinceAndPostcodeProps {\n  provinces: {\n    value: string;\n    label: string;\n  }[];\n  province?: {\n    code: string;\n  };\n  postcode?: string;\n  getFieldName?: (fieldName: string) => string;\n}\nexport function ProvinceAndPostcode({\n  provinces,\n  province,\n  postcode,\n  getFieldName\n}: ProvinceAndPostcodeProps) {\n  return (\n    <div className=\"grid grid-cols-2 gap-2 mt-2\">\n      <div>\n        <SelectField\n          defaultValue={province?.code}\n          name={getFieldName ? getFieldName('province') : 'address.province'}\n          label={_('Province')}\n          placeholder={_('Province')}\n          required\n          validation={{\n            required: _('Province is required')\n          }}\n          options={provinces}\n        />\n      </div>\n      <div>\n        <InputField\n          name={getFieldName ? getFieldName('postcode') : 'postcode'}\n          defaultValue={postcode}\n          label={_('Postcode')}\n          placeholder={_('Postcode')}\n          required\n          validation={{\n            required: _('Postcode is required')\n          }}\n        />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/babel/config.js",
    "content": "module.exports = {\n  parserOpts: { allowReturnOutsideFunction: true },\n  presets: [\n    '@babel/preset-react',\n    [\n      '@babel/preset-env',\n      {\n        exclude: [\n          '@babel/plugin-transform-regenerator',\n          '@babel/plugin-transform-async-to-generator'\n        ]\n      }\n    ]\n  ],\n  ignore: ['node_modules']\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/babel/index.js",
    "content": "import config from './config.js';\nimport '@babel/register';\n\nconfig();\n"
  },
  {
    "path": "packages/evershop/src/lib/componee/getComponentsByRoute.ts",
    "content": "import { resolve } from 'path';\nimport { getEnabledExtensions } from '../../bin/extension/index.js';\nimport { getCoreModules } from '../../bin/lib/loadModules.js';\nimport { getRoutes } from '../router/Router.js';\nimport { getEnabledTheme } from '../util/getEnabledTheme.js';\nimport { getEnabledWidgets } from '../widget/widgetManager.js';\nimport { ComponentsMap, scanRouteComponents } from './scanForComponents.js';\n\nexport function getComponentsByRoute(route) {\n  const modules = [...getCoreModules(), ...getEnabledExtensions()];\n  const theme = getEnabledTheme();\n\n  let components;\n  if (theme) {\n    components = Object.values(\n      scanRouteComponents(route, modules, resolve(theme.path, 'dist'))\n    );\n  } else {\n    components = Object.values(scanRouteComponents(route, modules));\n  }\n  const widgets = getEnabledWidgets();\n  if (!route.isAdmin) {\n    // Add widgets to components\n    return components.concat((widgets || []).map((widget) => widget.component));\n  } else {\n    // Add widgets to components\n    return components.concat(\n      (widgets || []).map((widget) => widget.settingComponent)\n    );\n  }\n}\n\ninterface AllRouteComponentsMap {\n  [routeId: string]: ComponentsMap;\n}\n\n/**\n * Scan components for all routes\n * @returns A map of route IDs to their components\n */\nexport function getAllRouteComponents(isAdmin = false): AllRouteComponentsMap {\n  const allComponents: AllRouteComponentsMap = {};\n  const routes = getRoutes().filter(\n    (route) => route.isApi === false && route.isAdmin === isAdmin\n  );\n  routes.forEach((route) => {\n    allComponents[route.id] = getComponentsByRoute(route);\n  });\n\n  return allComponents;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/componee/scanForComponents.ts",
    "content": "import { existsSync, readdirSync } from 'fs';\nimport { resolve, sep } from 'path';\nimport { Route } from '../../types/route.js';\n\ninterface ComponentsMap {\n  [key: string]: string;\n}\n\nfunction scanForComponents(path: string): string[] {\n  return readdirSync(resolve(path), { withFileTypes: true })\n    .filter(\n      (dirent) =>\n        dirent.isFile() &&\n        /.js$/.test(dirent.name) &&\n        /^[A-Z]/.test(dirent.name[0])\n    )\n    .map((dirent) => resolve(path, dirent.name));\n}\n\nfunction scanRouteComponents(\n  route: Route,\n  modules: {\n    name: string;\n    path: string;\n  }[],\n  themePath: string | null = null\n): ComponentsMap {\n  let components: ComponentsMap = {};\n\n  modules.forEach((module) => {\n    // Scan for 'all' components\n    const rootPath = route.isAdmin\n      ? resolve(module.path, 'pages/admin')\n      : resolve(module.path, 'pages/frontStore');\n    // Get all folders in the rootPath\n    const pages = existsSync(rootPath)\n      ? readdirSync(rootPath, { withFileTypes: true })\n          .filter((dirent) => dirent.isDirectory())\n          .map((dirent) => dirent.name)\n      : [];\n\n    pages.forEach((page) => {\n      let moduleComponents: string[] = [];\n      if (page === 'all' || page === route.id) {\n        moduleComponents = [\n          ...moduleComponents,\n          ...scanForComponents(resolve(rootPath, page))\n        ];\n      }\n      // Check if page include `+ page` or `page+` in the name\n      if (page.includes('+') && page.includes(route.id)) {\n        moduleComponents = [\n          ...moduleComponents,\n          ...scanForComponents(resolve(rootPath, page))\n        ];\n      }\n\n      const componentsObject = moduleComponents.reduce(\n        (a: ComponentsMap, v: string) => {\n          // Split the path by separator and get the 2 last items (routeId and component name)\n          const key = v.split(sep).slice(-2).join('/');\n          return { ...a, [key]: v };\n        },\n        {}\n      );\n\n      components = { ...components, ...componentsObject };\n    });\n  });\n\n  // Scan for theme components, only support frontStore theme\n  if (!route.isAdmin && themePath) {\n    const themePages = existsSync(resolve(themePath, 'pages'))\n      ? readdirSync(resolve(themePath, 'pages'), { withFileTypes: true })\n          .filter((dirent) => dirent.isDirectory())\n          .map((dirent) => dirent.name)\n      : [];\n\n    themePages.forEach((page) => {\n      let themeComponents: string[] = [];\n      if (page === 'all' || page === route.id) {\n        themeComponents = [\n          ...themeComponents,\n          ...scanForComponents(resolve(themePath, 'pages', page))\n        ];\n      }\n      // Check if page include `+ page` or `page+` in the name\n      if (page.includes('+') && page.includes(route.id)) {\n        themeComponents = [\n          ...themeComponents,\n          ...scanForComponents(resolve(themePath, 'pages', page))\n        ];\n      }\n\n      const themeComponentsObject = themeComponents.reduce(\n        (a: ComponentsMap, v: string) => {\n          // Split the path by separator and get the 2 last items (routeId and component name)\n          const key = v.split(sep).slice(-2).join('/');\n          return { ...a, [key]: v };\n        },\n        {}\n      );\n\n      components = { ...components, ...themeComponentsObject };\n    });\n  }\n\n  return components;\n}\n\nexport { scanForComponents, scanRouteComponents };\nexport type { ComponentsMap };\n"
  },
  {
    "path": "packages/evershop/src/lib/componee/scanForRootComponents.ts",
    "content": "import { join, normalize } from 'path';\nimport fg from 'fast-glob';\nimport { getEnabledExtensions } from '../../bin/extension/index.js';\nimport { CONSTANTS } from '../helpers.js';\nimport { getConfig } from '../util/getConfig.js';\n\n/**\n * Scans for files matching a glob pattern within a list of specified locations.\n * This function is optimized for speed by using fast-glob.\n *\n * @param {function} callback - Optional callback to process the found files.\n * @returns {Promise<string[]>} A promise that resolves to an array of absolute file paths.\n */\nexport async function scanForRootComponents(callback): Promise<string[]> {\n  const pattern = '/[A-Z]*.js';\n  const extensions = getEnabledExtensions();\n  const locations = [join(CONSTANTS.MODULESPATH, '/*/pages/*/*/')];\n  for (const ext of extensions) {\n    locations.push(join(ext.path, 'pages/*/*/'));\n  }\n  const theme = getConfig('system.theme') as string | undefined;\n  if (theme) {\n    locations.push(join(CONSTANTS.ROOTPATH, `/themes/${theme}/dist/pages/*/`));\n  }\n  const normalizedLocations = locations.map((loc) => normalize(loc));\n\n  const globPatterns = normalizedLocations.map((loc) => `${loc}/${pattern}`);\n\n  const files = await fg(globPatterns, {\n    absolute: true,\n    onlyFiles: true,\n    unique: true\n  });\n  if (callback) {\n    await callback(files);\n  }\n  return files;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/modules/firstModule/pages/frontStore/all/Menu.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/modules/firstModule/pages/frontStore/productView/Name.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/modules/firstModule/pages/frontStore/productView/Price.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/modules/secondModule/pages/frontStore/all/Banner.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/modules/secondModule/pages/frontStore/productView/Description.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/modules/secondModule/pages/frontStore/productView/Inventory.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/modules/secondModule/pages/frontStore/productView/Name.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/themes/justatheme/pages/all/CommentList.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/themes/justatheme/pages/all/Shipping.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/themes/justatheme/pages/productView/Name.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/themes/justatheme/pages/productView/OutOfStock.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/__mocks__/themes/justatheme/pages/productView/Price.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/componee/tests/unit/scanRouteComponents.test.js",
    "content": "import path from 'path';\nimport { scanRouteComponents } from '../../scanForComponents.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ndescribe('test scanRouteComponents function', () => {\n  const modules = [\n    {\n      path: path.resolve(__dirname, './__mocks__/modules/firstModule'),\n      name: 'firstModule'\n    },\n    {\n      path: path.resolve(__dirname, './__mocks__/modules/secondModule'),\n      name: 'secondModule'\n    }\n  ];\n\n  const extensions = [\n    {\n      path: path.resolve(__dirname, './__mocks__/extensions/secondExtension'),\n      name: 'secondExtension',\n      priority: 2\n    },\n    {\n      path: path.resolve(__dirname, './__mocks__/extensions/firstExtension'),\n      name: 'firstExtension',\n      priority: 1\n    }\n  ];\n\n  const themePath = path.resolve(__dirname, './__mocks__/themes/justathemes');\n\n  it('It should return an object', () => {\n    const components = scanRouteComponents(\n      { id: 'home', isAdmin: false },\n      [...modules, ...extensions],\n      themePath\n    );\n\n    expect(components).toBeInstanceOf(Object);\n  });\n\n  it('It should get only `all` component if route does not exist', () => {\n    const components = scanRouteComponents(\n      { id: 'home', isAdmin: false },\n      [...modules, ...extensions],\n      themePath\n    );\n\n    expect(components).toEqual({\n      'all/Menu.js': path.resolve(\n        __dirname,\n        './__mocks__/modules/firstModule/pages/frontStore/all/Menu.js'\n      ),\n      'all/Banner.js': path.resolve(\n        __dirname,\n        './__mocks__/extensions/firstExtension/pages/frontStore/all/Banner.js'\n      ),\n      'all/CommentList.js': path.resolve(\n        __dirname,\n        './__mocks__/extensions/firstExtension/pages/frontStore/all/CommentList.js'\n      )\n    });\n  });\n\n  it('It should get the component from extension when the component is dublicated with the one in core module', () => {\n    const components = scanRouteComponents(\n      { id: 'productView', isAdmin: false },\n      [...modules, ...extensions],\n      themePath\n    );\n\n    expect(components['productView/Price.js']).toEqual(\n      path.resolve(\n        __dirname,\n        './__mocks__/extensions/firstExtension/pages/frontStore/productView/Price.js'\n      )\n    );\n  });\n\n  it('It should get the component from higher priority extension when the component is dublicated', () => {\n    const components = scanRouteComponents(\n      { id: 'productView', isAdmin: false },\n      [...modules, ...extensions],\n      themePath\n    );\n\n    expect(components['productView/Name.js']).toEqual(\n      path.resolve(\n        __dirname,\n        './__mocks__/extensions/firstExtension/pages/frontStore/productView/Name.js'\n      )\n    );\n  });\n\n  it('It should get the components theme, theme should be higest priority', () => {\n    const components = scanRouteComponents(\n      { id: 'productView', isAdmin: false },\n      [...modules, ...extensions],\n      path.resolve(__dirname, './__mocks__/themes/justatheme')\n    );\n\n    expect(components['all/Shipping.js']).toEqual(\n      path.resolve(\n        __dirname,\n        './__mocks__/themes/justatheme/pages/all/Shipping.js'\n      )\n    );\n\n    expect(components['all/CommentList.js']).toEqual(\n      path.resolve(\n        __dirname,\n        './__mocks__/themes/justatheme/pages/all/CommentList.js'\n      )\n    );\n\n    expect(components['productView/Name.js']).toEqual(\n      path.resolve(\n        __dirname,\n        './__mocks__/themes/justatheme/pages/productView/Name.js'\n      )\n    );\n\n    expect(components['productView/Price.js']).toEqual(\n      path.resolve(\n        __dirname,\n        './__mocks__/themes/justatheme/pages/productView/Price.js'\n      )\n    );\n\n    expect(components['productView/OutOfStock.js']).toEqual(\n      path.resolve(\n        __dirname,\n        './__mocks__/themes/justatheme/pages/productView/OutOfStock.js'\n      )\n    );\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/cronjob/cronjob.ts",
    "content": "import { pathToFileURL } from 'url';\nimport cron from 'node-cron';\nimport { getEnabledExtensions } from '../../bin/extension/index.js';\nimport { loadBootstrapScript } from '../../bin/lib/bootstrap/bootstrap.js';\nimport { getCoreModules } from '../../bin/lib/loadModules.js';\nimport { debug, error } from '../log/logger.js';\nimport { lockHooks } from '../util/hookable.js';\nimport { lockRegistry } from '../util/registry.js';\nimport { getEnabledJobs } from './jobManager.js';\n\nasync function start() {\n  const modules = [...getCoreModules(), ...getEnabledExtensions()];\n  /** Loading bootstrap script from modules */\n  try {\n    for (const module of modules) {\n      await loadBootstrapScript(module, {\n        ...JSON.parse(process.env.bootstrapContext || '{}'),\n        process: 'cronjob'\n      });\n    }\n    lockHooks();\n    lockRegistry();\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n  const jobs = getEnabledJobs();\n\n  // Schedule the jobs\n  jobs.forEach((job) => {\n    cron.schedule(job.schedule, async () => {\n      try {\n        // Load the module\n        const module = await import(pathToFileURL(job.resolve).toString());\n        // Make sure the module is a function or async function\n        if (typeof module.default !== 'function') {\n          throw new Error(\n            `Job ${job.name} is not a function. Make sure the module exports a function as default.`\n          );\n        }\n        // Execute the job\n        await module.default();\n      } catch (e) {\n        error(e);\n      }\n    });\n  });\n}\n\nprocess.on('SIGTERM', async () => {\n  debug('Cron job received SIGTERM, shutting down...');\n  try {\n    process.exit(0);\n  } catch (err) {\n    error('Error during shutdown:');\n    error(err);\n    process.exit(1); // Exit with an error code\n  }\n});\n\nprocess.on('SIGINT', async () => {\n  debug('Cron job received SIGINT, shutting down...');\n  try {\n    process.exit(0);\n  } catch (err) {\n    error('Error during shutdown:');\n    error(err);\n    process.exit(1); // Exit with an error code\n  }\n});\n\nstart();\n"
  },
  {
    "path": "packages/evershop/src/lib/cronjob/jobManager.ts",
    "content": "import * as fs from 'fs';\nimport * as path from 'path';\nimport cron from 'node-cron';\nimport { Job } from '../../types/cronjob.js';\nimport { warning } from '../log/logger.js';\n\n/**\n * Checks if a given path is a valid and resolvable JavaScript file path.\n * A path is considered valid if it's a string, not empty, exists on the filesystem,\n * and has a .js extension.\n *\n * @param {string | undefined} filePath - The path to check. Can be undefined.\n * @returns {boolean} True if the path is a resolvable JavaScript file, false otherwise.\n */\nfunction isValidJsFilePath(filePath: string | undefined): boolean {\n  if (typeof filePath !== 'string' || filePath.trim() === '') {\n    return false;\n  }\n\n  const resolvedPath = path.resolve(filePath);\n  const fileExtension = path.extname(resolvedPath);\n\n  try {\n    if (!fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isFile()) {\n      return false;\n    }\n  } catch (e) {\n    return false;\n  }\n\n  return fileExtension === '.js';\n}\n\nclass JobManager {\n  /**\n   * @private\n   * A private map to store registered jobs. The key is the job's unique name,\n   * and the value is the job object adhering to the Job interface.\n   */\n  private jobs: Map<string, Job> = new Map();\n\n  /**\n   * @private\n   * A flag indicating whether the job manager has entered a read-only state.\n   * Once set to true (after `getAllJobs` is called for the first time),\n   * no further mutations (add, remove, update) are allowed.\n   */\n  private _isFrozen: boolean = false;\n\n  /**\n   * Internal helper to check if mutations are allowed.\n   * Throws an error if the manager is in a frozen (read-only) state.\n   * @private\n   * @throws {Error} If a mutation attempt is made after the manager is frozen.\n   */\n  private _ensureMutable(): void {\n    if (this._isFrozen) {\n      throw new Error(\n        'Job manager is in a read-only state. No further mutations are allowed. We suggest to register, update, or remove a job from the bootstrap file.'\n      );\n    }\n  }\n\n  /**\n   * Registers a new job with the manager.\n   * A job must have a unique 'name' property.\n   * If a job with the same name already exists, it will not be registered.\n   * Additionally, `resolve` must be a resolvable path to a JavaScript file.\n   * @param {Job} job - The job object to register.\n   * @returns {boolean} True if the job was successfully registered, false otherwise.\n   * @throws {Error} If called after the manager has entered a read-only state or on invalid job data/paths.\n   */\n  public registerJob(job: Job): boolean {\n    this._ensureMutable();\n\n    if (!job || typeof job.name !== 'string' || job.name.trim() === '') {\n      throw new Error(\n        'Cannot register job. Job object must have a valid \"name\" property.'\n      );\n    }\n    const jobName = job.name;\n    if (this.jobs.has(jobName)) {\n      warning(\n        `Job with name \"${jobName}\" is already registered. Skipping registration.`\n      );\n      return false;\n    }\n\n    if (!isValidJsFilePath(job.resolve)) {\n      throw new Error(\n        `Cannot register job \"${jobName}\". Invalid or unresolvable path: \"${job.resolve}\". Please ensure it's a valid path to an existing JS file.`\n      );\n    }\n    if (!cron.validate(job.schedule)) {\n      throw new Error(\n        `Cannot register job \"${jobName}\". Invalid cron schedule: \"${job.schedule}\". Please ensure it's a valid cron expression.`\n      );\n    }\n\n    this.jobs.set(jobName, job);\n    return true;\n  }\n\n  /**\n   * Removes a job from the manager based on its unique name.\n   *\n   * @param {string} jobName - The name of the job to remove.\n   * @returns {boolean} True if the job was successfully removed, false otherwise.\n   * @throws {Error} If called after the manager has entered a read-only state or on invalid job name.\n   */\n  public removeJob(jobName: string): boolean {\n    this._ensureMutable();\n\n    if (this.jobs.has(jobName)) {\n      this.jobs.delete(jobName);\n      return true;\n    } else {\n      warning(`Job with name \"${jobName}\" not found. Cannot remove.`);\n      return false;\n    }\n  }\n\n  /**\n   * Updates an existing job's schedule. This method allows you to change the cron schedule of a job.\n   * @param {string} jobName - The name of the job to update.\n   * @param {string} newSchedule - The new cron schedule to set.\n   * @returns {boolean} True if the job was successfully updated, false otherwise.\n   * @throws {Error} If called after the manager has entered a read-only state or on invalid job name.\n   */\n  public updateJobSchedule(jobName: string, newSchedule: string): boolean {\n    this._ensureMutable();\n    const job = this.jobs.get(jobName);\n    if (!cron.validate(newSchedule)) {\n      throw new Error(\n        `Cannot update job \"${jobName}\". Invalid cron schedule: \"${newSchedule}\". Please ensure it's a valid cron expression.`\n      );\n    }\n    if (!job) {\n      warning(`Job with name \"${jobName}\" not found. Cannot update schedule.`);\n      return false;\n    }\n\n    job.schedule = newSchedule;\n    return true;\n  }\n\n  /**\n   * Retrieves a registered job by its unique name.\n   *\n   * @param {string} jobName - The name of the job to retrieve.\n   * @returns {Job | undefined} The job object if found, otherwise undefined.\n   */\n  public getJob(jobName: string): Job | undefined {\n    if (this.jobs.has(jobName)) {\n      return this.jobs.get(jobName);\n    } else {\n      warning(`Job with name \"${jobName}\" not found.`);\n      return undefined;\n    }\n  }\n\n  /**\n   * Retrieves all registered jobs.\n   * Returns a new array containing frozen (immutable) copies of the job objects.\n   * This method also marks the JobManager as 'frozen', preventing any further\n   * calls to mutation methods (register, remove, update).\n   *\n   * @returns {Job[]} An array containing all registered job objects.\n   */\n  public getAllJobs(): Job[] {\n    this._isFrozen = true;\n\n    // Create a new array, and for each job, create a frozen copy.\n    return Array.from(this.jobs.values()).map((job) =>\n      Object.freeze({ ...job })\n    );\n  }\n\n  /**\n   * Checks if a job with the given name is registered.\n   * @param {string} jobName - The name of the job to check.\n   * @returns {boolean} True if the job is registered, false otherwise.\n   */\n  public hasJob(jobName: string): boolean {\n    return this.jobs.has(jobName);\n  }\n}\n\nconst jobManager = new JobManager();\n\n/**\n * Retrieves all registered jobs.\n * Calling this function will also freeze the job manager, preventing any further mutations (register, remove).\n * @returns {Job[]} An array of all registered jobs.\n */\nexport function getAllJobs(): Job[] {\n  const allJobs = jobManager.getAllJobs();\n  return allJobs;\n}\n\n/**\n * Retrieves all enabled jobs. An enabled job is one that has its `enabled` property set to true.\n * This function returns a new array containing only the jobs that are enabled. Calling this function\n * will also freeze the job manager, preventing any further mutations (register, remove).\n * @returns {Job[]} An array of enabled jobs.\n */\nexport function getEnabledJobs(): Job[] {\n  const allJobs = jobManager.getAllJobs();\n  return allJobs.filter((job) => job.enabled);\n}\n\n/**\n * Registers a new job. This function is intended to be called during the\n * bootstrap phase of the application, before the job manager is frozen.\n * @param job - The job object to register.\n * @returns True if the job was successfully registered, false otherwise.\n * @throws Error if the job is invalid or if the manager is in a read-only state.\n */\nexport function registerJob(job: Job): boolean {\n  return jobManager.registerJob(job);\n}\n\n/**\n * Updates the schedule of an existing job. This function allows you to change\n * the cron schedule of a job. It is intended to be called during the bootstrap\n * phase of the application, before the job manager is frozen.\n * @param jobName - The name of the job to update.\n * @param newSchedule - The new cron schedule to set for the job.\n * @returns True if the job schedule was successfully updated, false otherwise.\n * @throws Error if the manager is in a read-only state.\n */\nexport function updateJobSchedule(\n  jobName: string,\n  newSchedule: string\n): boolean {\n  return jobManager.updateJobSchedule(jobName, newSchedule);\n}\n\n/**\n * Removes a job. This function supposed to be called from the bootstrap\n * phase of the application, before the job manager is frozen.\n * @param jobName - The name of the job to remove.\n * @returns True if the job was successfully removed, false otherwise.\n */\nexport function removeJob(jobName: string): boolean {\n  return jobManager.removeJob(jobName);\n}\n/**\n * Retrieves a job by its name.\n * @param jobName - The name of the job to retrieve.\n * @returns The job if found, undefined otherwise.\n */\nexport function getJob(jobName: string): Job | undefined {\n  return jobManager.getJob(jobName);\n}\n\n/**\n * Checks if a job with the given name is registered.\n * @param jobName - The name of the job to check.\n * @returns True if the job is registered, false otherwise.\n */\nexport function hasJob(jobName: string): boolean {\n  return jobManager.hasJob(jobName);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/cronjob/tests/unit/jobManager.test.js",
    "content": "import { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n\nconst getValidJob = (name = 'TestJob') => ({\n  name: name,\n  schedule: '* * * * *',\n  resolve: `./jobs/${name}.js`,\n  enabled: true\n});\n\njest.unstable_mockModule('fs', () => ({\n  existsSync: jest.fn((path) => {\n    if (path.includes('InvalidModule.js')) {\n      return false; // Simulate unresolvable paths\n    }\n    return true; // Simulate valid paths for other components\n  }),\n  statSync: jest.fn(() => ({\n    isFile: () => true\n  }))\n}));\n\nconst realPath = await import('path');\njest.unstable_mockModule('path', () => ({\n  default: true,\n  ...realPath,\n  resolve: jest.fn((...args) => `/mocked/path/${args.join('/')}`)\n}));\n\ndescribe('Job Manager Module', () => {\n  beforeEach(async () => {\n    jest.resetModules(); // Reset modules before each test to ensure fresh mocks\n  });\n\n  afterEach(() => {\n    jest.clearAllMocks(); // Clear mock call history after each test\n  });\n  // --- Test _isFrozen state enforcement ---\n  describe('Mutation after getAllJobs()', () => {\n    it('should throw an error if resolve is not a string', async () => {\n      const jobModule = await import('../../jobManager.js');\n      const invalidJob = {\n        name: 'InvalidJob',\n        schedule: '* * * * *',\n        resolve: 123, // Invalid type\n        enabled: true\n      };\n      expect(() => jobModule.registerJob(invalidJob)).toThrow(\n        'Invalid or unresolvable'\n      );\n    });\n\n    it('should throw an error if the resolve is unresolvable path', async () => {\n      const jobModule = await import('../../jobManager.js');\n      const invalidJob = {\n        name: 'InvalidJob',\n        schedule: '* * * * *',\n        resolve: 'InvalidModule.js', // Unresolvable path\n        enabled: true\n      };\n      expect(() => jobModule.registerJob(invalidJob)).toThrow(\n        'Invalid or unresolvable'\n      );\n    });\n\n    it('should throw an error if registerJob is called with an invalid job schedule', async () => {\n      const jobModule = await import('../../jobManager.js');\n      const invalidJob = {\n        name: 'InvalidJob',\n        schedule: 'invalid_schedule', // Invalid schedule\n        resolve: 'ValidModule.js',\n        enabled: true\n      };\n      expect(() => jobModule.registerJob(invalidJob)).toThrow(\n        'Invalid cron schedule'\n      );\n    });\n\n    it('should throw an error if registerJob is called after getAllJobs', async () => {\n      const jobModule = await import('../../jobManager.js');\n      jobModule.getAllJobs(); // This freezes the manager\n      const job = getValidJob('NewJob');\n      expect(() => jobModule.registerJob(job)).toThrow(\n        'Job manager is in a read-only state. No further mutations are allowed. We suggest to register, update, or remove a job from the bootstrap file.'\n      );\n    });\n\n    it('should throw an error if updateJob is called after getAllJobs', async () => {\n      const jobModule = await import('../../jobManager.js');\n      jobModule.registerJob(getValidJob('ExistingJob'));\n      jobModule.getAllJobs(); // This freezes the manager\n      expect(() =>\n        jobModule.updateJobSchedule('ExistingJob', '0 0 * * *')\n      ).toThrow(\n        'Job manager is in a read-only state. No further mutations are allowed. We suggest to register, update, or remove a job from the bootstrap file.'\n      );\n    });\n\n    it('should throw an error if removeJob is called after getAllJobs', async () => {\n      const jobModule = await import('../../jobManager.js');\n      jobModule.registerJob(getValidJob('JobToRemove'));\n      jobModule.getAllJobs(); // This freezes the manager\n      expect(() => jobModule.removeJob('JobToRemove')).toThrow(\n        'Job manager is in a read-only state. No further mutations are allowed. We suggest to register, update, or remove a job from the bootstrap file.'\n      );\n    });\n\n    it('should allow getJob after getAllJobs', async () => {\n      const jobModule = await import('../../jobManager.js');\n      const job = getValidJob('ReadJob');\n      jobModule.registerJob(job);\n      jobModule.getAllJobs(); // This freezes the manager\n      expect(jobModule.getJob('ReadJob')).toEqual(job);\n    });\n\n    it('should allow hasJob after getAllJobs', async () => {\n      const jobModule = await import('../../jobManager.js');\n      const job = getValidJob('CheckJob');\n      jobModule.registerJob(job);\n      jobModule.getAllJobs(); // This freezes the manager\n      expect(jobModule.hasJob('CheckJob')).toBe(true);\n    });\n\n    it('should allow to updateJobSchedule', async () => {\n      const jobModule = await import('../../jobManager.js');\n      const job = getValidJob('UpdateJob');\n      jobModule.registerJob(job);\n      jobModule.updateJobSchedule('UpdateJob', '0 0 * * *');\n      expect(jobModule.getJob('UpdateJob')).toEqual(job);\n      expect(jobModule.getJob('UpdateJob').schedule).toEqual('0 0 * * *');\n    });\n\n    it('should throw error if trying to updateJobSchedule with invalid schedule', async () => {\n      const jobModule = await import('../../jobManager.js');\n      jobModule.registerJob(getValidJob('JobWithInvalidSchedule'));\n      expect(() =>\n        jobModule.updateJobSchedule(\n          'JobWithInvalidSchedule',\n          'invalid_schedule'\n        )\n      ).toThrow('Invalid cron schedule');\n    });\n\n    it('should thrown an error if trying to update job schedule after calling getAllJobs', async () => {\n      const jobModule = await import('../../jobManager.js');\n      jobModule.registerJob(getValidJob('JobToUpdate'));\n      jobModule.getAllJobs(); // This freezes the manager\n      expect(() =>\n        jobModule.updateJobSchedule('JobToUpdate', '0 0 * * *')\n      ).toThrow(\n        'Job manager is in a read-only state. No further mutations are allowed. We suggest to register, update, or remove a job from the bootstrap file.'\n      );\n    });\n\n    it('should allow removing a job', async () => {\n      const jobModule = await import('../../jobManager.js');\n      const job = getValidJob('JobToRemove');\n      jobModule.registerJob(job);\n      jobModule.removeJob('JobToRemove');\n      expect(jobModule.hasJob('JobToRemove')).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/event/callSubscibers.js",
    "content": "import { error } from '../../lib/log/logger.js';\n\nexport async function callSubscribers(subscribers, eventData) {\n  const promises = subscribers.map(\n    (subscriber) =>\n      new Promise((resolve) => {\n        setTimeout(async () => {\n          try {\n            await subscriber(eventData);\n          } catch (e) {\n            error(e);\n          }\n          resolve();\n        }, 0);\n      })\n  );\n\n  await Promise.all(promises);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/event/emitter.ts",
    "content": "import { insert } from '@evershop/postgres-query-builder';\nimport { EventDataRegistry, EventName } from '../../types/event.js';\nimport { pool } from '../postgres/connection.js';\n\n/**\n * Emit a typed event. The event data type is inferred from the event name.\n * @param name - The name of the event (must be registered in EventDataRegistry)\n * @param data - The data to emit (type is inferred from event name)\n */\nexport async function emit<T extends EventName>(\n  name: T,\n  data: EventDataRegistry[T]\n): Promise<void>;\n\n/**\n * Emit an untyped event. Use this for dynamic events that aren't registered.\n * @param name - The name of the event\n * @param data - The data to emit\n */\nexport async function emit(\n  name: string,\n  data: Record<string, any>\n): Promise<void>;\n\n// Implementation\nexport async function emit(name: string, data: Record<string, any>) {\n  await insert('event')\n    .given({\n      name,\n      data\n    })\n    .execute(pool);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/event/event-manager.js",
    "content": "import { del, select } from '@evershop/postgres-query-builder';\nimport { getEnabledExtensions } from '../../bin/extension/index.js';\nimport { loadBootstrapScript } from '../../bin/lib/bootstrap/bootstrap.js';\nimport { getCoreModules } from '../../bin/lib/loadModules.js';\nimport { pool } from '../../lib/postgres/connection.js';\nimport { debug, error } from '../log/logger.js';\nimport { lockHooks } from '../util/hookable.js';\nimport { lockRegistry } from '../util/registry.js';\nimport { callSubscribers } from './callSubscibers.js';\nimport { loadSubscribers } from './loadSubscribers.js';\n\nconst loadEventInterval = 10000;\nconst syncEventInterval = 2000;\nconst maxEvents = 10;\nlet events = [];\n// Get the modules from the arguments\nconst modules = [...getCoreModules(), ...getEnabledExtensions()];\nconst subscribers = await loadSubscribers(modules);\n\nconst init = async () => {\n  /** Loading bootstrap script from modules */\n  try {\n    for (const module of modules) {\n      await loadBootstrapScript(module, {\n        ...JSON.parse(process.env.bootstrapContext || '{}'),\n        process: 'event'\n      });\n    }\n    lockHooks();\n    lockRegistry();\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n  process.env.ALLOW_CONFIG_MUTATIONS = false;\n  setInterval(async () => {\n    // Load events\n    const newEvents = await loadEvents(maxEvents);\n    // Append the new events to the existing events\n    events = [...events, ...newEvents];\n\n    // Keep only the last 20 events\n    events = events.slice(-maxEvents);\n\n    // Call subscribers for each event\n    events.forEach((event) => {\n      if (event.status !== 'done' && event.status !== 'processing') {\n        executeSubscribers(event);\n      }\n    });\n  }, loadEventInterval);\n};\n\nsetInterval(async () => {\n  // Sync events\n  await syncEvents();\n}, syncEventInterval);\n\nasync function loadEvents(count) {\n  // Only load events if the current events are less than the max events\n  if (events.length >= maxEvents) {\n    return [];\n  }\n  // Only load events that have subscribers\n  const eventNames = subscribers.map((subscriber) => subscriber.event);\n\n  const query = select().from('event');\n  if (eventNames.length > 0) {\n    query.where('name', 'IN', eventNames);\n  }\n\n  if (events.length > 0) {\n    query.andWhere(\n      'uuid',\n      'NOT IN',\n      events.map((event) => event.uuid)\n    );\n  }\n\n  query.orderBy('event_id', 'ASC');\n  query.limit(0, count);\n\n  const results = await query.execute(pool);\n  return results;\n}\n\nasync function syncEvents() {\n  // Delete the events that have been executed\n  const completedEvents = events\n    .filter((event) => event.status === 'done')\n    .map((event) => event.uuid);\n  if (completedEvents.length > 0) {\n    await del('event').where('uuid', 'IN', completedEvents).execute(pool);\n    // Remove the events from the events array\n    events = events.filter((event) => event.status !== 'done');\n  }\n}\n\nasync function executeSubscribers(event) {\n  event.status = 'processing';\n  const eventData = event.data;\n  // get subscribers for the event\n  const matchingSubscribers = subscribers\n    .filter((subscriber) => subscriber.event === event.name)\n    .map((subscriber) => subscriber.subscriber);\n  // Call subscribers\n  await callSubscribers(matchingSubscribers, eventData);\n\n  event.status = 'done';\n}\n\nprocess.on('SIGTERM', async () => {\n  debug('Event manager received SIGTERM, shutting down...');\n  try {\n    process.exit(0);\n  } catch (err) {\n    error('Error during shutdown:');\n    error(err);\n    process.exit(1); // Exit with an error code\n  }\n});\n\nprocess.on('SIGINT', async () => {\n  debug('Event manager received SIGINT, shutting down...');\n  try {\n    process.exit(0);\n  } catch (err) {\n    error('Error during shutdown:');\n    error(err);\n    process.exit(1); // Exit with an error code\n  }\n});\n\ninit();\n"
  },
  {
    "path": "packages/evershop/src/lib/event/loadSubscribers.js",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\nimport { error } from '../../lib/log/logger.js';\n\nasync function loadModuleSubscribers(modulePath) {\n  const subscribers = [];\n  const subscribersDir = path.join(modulePath, 'subscribers');\n\n  if (!fs.existsSync(subscribersDir)) {\n    return subscribers;\n  }\n\n  const eventDirs = fs\n    .readdirSync(subscribersDir, { withFileTypes: true })\n    .filter((dirent) => dirent.isDirectory())\n    .map((dirent) => dirent.name);\n\n  await Promise.all(\n    eventDirs.map(async (eventName) => {\n      const eventSubscribersDir = path.join(subscribersDir, eventName);\n\n      // get only .js files\n      const files = fs\n        .readdirSync(eventSubscribersDir, { withFileTypes: true })\n        .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.js'))\n        .map((dirent) => dirent.name);\n\n      await Promise.all(\n        files.map(async (file) => {\n          const subscriberPath = path.join(eventSubscribersDir, file);\n          const module = await import(pathToFileURL(subscriberPath));\n          subscribers.push({\n            event: eventName,\n            subscriber: module.default\n          });\n        })\n      );\n    })\n  );\n\n  return subscribers;\n}\n\nexport async function loadSubscribers(modules) {\n  const subscribers = [];\n  /** Loading subscriber  */\n  await Promise.all(\n    modules.map(async (module) => {\n      try {\n        // Load subscribers\n        subscribers.push(...(await loadModuleSubscribers(module.path)));\n      } catch (e) {\n        error(e);\n        process.exit(0);\n      }\n    })\n  );\n  return subscribers;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/event/subscriber.ts",
    "content": "import { EventDataRegistry, EventName } from '../../types/event.js';\n\n/**\n * Type-safe event subscriber function.\n * Use this type to ensure your subscriber receives the correct event data type.\n *\n * @example\n * ```typescript\n * import { EventSubscriber } from '@evershop/evershop/lib/event/subscriber';\n *\n * const handler: EventSubscriber<'order.placed'> = async (data) => {\n *   // data is typed as EventDataRegistry['order.placed']\n *   console.log(data.orderId); // TypeScript knows this exists\n * };\n *\n * export default handler;\n * ```\n */\nexport type EventSubscriber<T extends EventName> = (\n  data: EventDataRegistry[T]\n) => Promise<void> | void;\n\n/**\n * Helper function to create a type-safe event subscriber.\n * Provides better IDE autocomplete and type checking.\n *\n * @example\n * ```typescript\n * import { createSubscriber } from '@evershop/evershop/lib/event/subscriber';\n *\n * export default createSubscriber('order_placed', async (data) => {\n *   // data is automatically typed\n *   await sendEmail(data.orderId);\n * });\n * ```\n */\nexport function createSubscriber<T extends EventName>(\n  eventName: T,\n  handler: EventSubscriber<T>\n): (data: EventDataRegistry[T]) => Promise<void> {\n  return async (data: EventDataRegistry[T]) => {\n    await handler(data);\n  };\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/helpers.ts",
    "content": "import path from 'path';\nimport { fileURLToPath } from 'url';\nimport { getConfig } from './util/getConfig.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst rootPath = __dirname.includes(\n  path.join('node_modules', '@evershop', 'evershop')\n)\n  ? process.cwd()\n  : path.resolve(__dirname, '..', '..', '..', '..');\n\nexport const CONSTANTS = Object.freeze({\n  ROOTPATH: rootPath,\n  LIBPATH: path.resolve(__dirname),\n  MODULESPATH: path.resolve(__dirname, '..', 'modules'),\n  PUBLICPATH: path.resolve(rootPath, 'public'),\n  MEDIAPATH: path.resolve(rootPath, 'media'),\n  NODEMODULEPATH: path.resolve(rootPath, 'node_modules'),\n  THEMEPATH: path.resolve(rootPath, 'themes'),\n  CACHEPATH: path.resolve(rootPath, '.evershop'),\n  BUILDPATH: path.resolve(rootPath, '.evershop', 'build'),\n  ADMIN_COLLECTION_SIZE: getConfig('system.admin_collection_size', 20)\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/locale/countries.ts",
    "content": "export interface Country {\n  code: string;\n  name: string;\n}\n\nexport const countries: Country[] = [\n  { code: 'AF', name: 'Afghanistan' },\n  { code: 'AL', name: 'Albania' },\n  { code: 'DZ', name: 'Algeria' },\n  { code: 'AS', name: 'American Samoa' },\n  { code: 'AD', name: 'Andorra' },\n  { code: 'AO', name: 'Angola' },\n  { code: 'AI', name: 'Anguilla' },\n  { code: 'AQ', name: 'Antarctica' },\n  { code: 'AG', name: 'Antigua and Barbuda' },\n  { code: 'AR', name: 'Argentina' },\n  { code: 'AM', name: 'Armenia' },\n  { code: 'AW', name: 'Aruba' },\n  { code: 'AU', name: 'Australia' },\n  { code: 'AT', name: 'Austria' },\n  { code: 'AZ', name: 'Azerbaijan' },\n  { code: 'BS', name: 'Bahamas' },\n  { code: 'BH', name: 'Bahrain' },\n  { code: 'BD', name: 'Bangladesh' },\n  { code: 'BB', name: 'Barbados' },\n  { code: 'BY', name: 'Belarus' },\n  { code: 'BE', name: 'Belgium' },\n  { code: 'BZ', name: 'Belize' },\n  { code: 'BJ', name: 'Benin' },\n  { code: 'BM', name: 'Bermuda' },\n  { code: 'BT', name: 'Bhutan' },\n  { code: 'BO', name: 'Bolivia' },\n  { code: 'BA', name: 'Bosnia and Herzegovina' },\n  { code: 'BW', name: 'Botswana' },\n  { code: 'BV', name: 'Bouvet Island' },\n  { code: 'BR', name: 'Brazil' },\n  { code: 'IO', name: 'British Indian Ocean Territory' },\n  { code: 'VG', name: 'British Virgin Islands' },\n  { code: 'BN', name: 'Brunei' },\n  { code: 'BG', name: 'Bulgaria' },\n  { code: 'BF', name: 'Burkina Faso' },\n  { code: 'BI', name: 'Burundi' },\n  { code: 'KH', name: 'Cambodia' },\n  { code: 'CM', name: 'Cameroon' },\n  { code: 'CA', name: 'Canada' },\n  { code: 'CV', name: 'Cape Verde' },\n  { code: 'KY', name: 'Cayman Islands' },\n  { code: 'CF', name: 'Central African Republic' },\n  { code: 'TD', name: 'Chad' },\n  { code: 'CL', name: 'Chile' },\n  { code: 'CN', name: 'China' },\n  { code: 'CX', name: 'Christmas Island' },\n  { code: 'CC', name: 'Cocos [Keeling] Islands' },\n  { code: 'CO', name: 'Colombia' },\n  { code: 'KM', name: 'Comoros' },\n  { code: 'CG', name: 'Congo - Brazzaville' },\n  { code: 'CD', name: 'Congo - Kinshasa' },\n  { code: 'CK', name: 'Cook Islands' },\n  { code: 'CR', name: 'Costa Rica' },\n  { code: 'HR', name: 'Croatia' },\n  { code: 'CU', name: 'Cuba' },\n  { code: 'CY', name: 'Cyprus' },\n  { code: 'CZ', name: 'Czech Republic' },\n  { code: 'CI', name: 'Côte d’Ivoire' },\n  { code: 'DK', name: 'Denmark' },\n  { code: 'DJ', name: 'Djibouti' },\n  { code: 'DM', name: 'Dominica' },\n  { code: 'DO', name: 'Dominican Republic' },\n  { code: 'EC', name: 'Ecuador' },\n  { code: 'EG', name: 'Egypt' },\n  { code: 'SV', name: 'El Salvador' },\n  { code: 'GQ', name: 'Equatorial Guinea' },\n  { code: 'ER', name: 'Eritrea' },\n  { code: 'EE', name: 'Estonia' },\n  { code: 'ET', name: 'Ethiopia' },\n  { code: 'FK', name: 'Falkland Islands' },\n  { code: 'FO', name: 'Faroe Islands' },\n  { code: 'FJ', name: 'Fiji' },\n  { code: 'FI', name: 'Finland' },\n  { code: 'FR', name: 'France' },\n  { code: 'GF', name: 'French Guiana' },\n  { code: 'PF', name: 'French Polynesia' },\n  { code: 'TF', name: 'French Southern Territories' },\n  { code: 'GA', name: 'Gabon' },\n  { code: 'GM', name: 'Gambia' },\n  { code: 'GE', name: 'Georgia' },\n  { code: 'DE', name: 'Germany' },\n  { code: 'GH', name: 'Ghana' },\n  { code: 'GI', name: 'Gibraltar' },\n  { code: 'GR', name: 'Greece' },\n  { code: 'GL', name: 'Greenland' },\n  { code: 'GD', name: 'Grenada' },\n  { code: 'GP', name: 'Guadeloupe' },\n  { code: 'GU', name: 'Guam' },\n  { code: 'GT', name: 'Guatemala' },\n  { code: 'GG', name: 'Guernsey' },\n  { code: 'GN', name: 'Guinea' },\n  { code: 'GW', name: 'Guinea-Bissau' },\n  { code: 'GY', name: 'Guyana' },\n  { code: 'HT', name: 'Haiti' },\n  { code: 'HM', name: 'Heard Island and McDonald Islands' },\n  { code: 'HN', name: 'Honduras' },\n  { code: 'HK', name: 'Hong Kong SAR China' },\n  { code: 'HU', name: 'Hungary' },\n  { code: 'IS', name: 'Iceland' },\n  { code: 'IN', name: 'India' },\n  { code: 'ID', name: 'Indonesia' },\n  { code: 'IR', name: 'Iran' },\n  { code: 'IQ', name: 'Iraq' },\n  { code: 'IE', name: 'Ireland' },\n  { code: 'IM', name: 'Isle of Man' },\n  { code: 'IL', name: 'Israel' },\n  { code: 'IT', name: 'Italy' },\n  { code: 'JM', name: 'Jamaica' },\n  { code: 'JP', name: 'Japan' },\n  { code: 'JE', name: 'Jersey' },\n  { code: 'JO', name: 'Jordan' },\n  { code: 'KZ', name: 'Kazakhstan' },\n  { code: 'KE', name: 'Kenya' },\n  { code: 'KI', name: 'Kiribati' },\n  { code: 'KW', name: 'Kuwait' },\n  { code: 'KG', name: 'Kyrgyzstan' },\n  { code: 'LA', name: 'Laos' },\n  { code: 'LV', name: 'Latvia' },\n  { code: 'LB', name: 'Lebanon' },\n  { code: 'LS', name: 'Lesotho' },\n  { code: 'LR', name: 'Liberia' },\n  { code: 'LY', name: 'Libya' },\n  { code: 'LI', name: 'Liechtenstein' },\n  { code: 'LT', name: 'Lithuania' },\n  { code: 'LU', name: 'Luxembourg' },\n  { code: 'MO', name: 'Macau SAR China' },\n  { code: 'MK', name: 'Macedonia' },\n  { code: 'MG', name: 'Madagascar' },\n  { code: 'MW', name: 'Malawi' },\n  { code: 'MY', name: 'Malaysia' },\n  { code: 'MV', name: 'Maldives' },\n  { code: 'ML', name: 'Mali' },\n  { code: 'MT', name: 'Malta' },\n  { code: 'MH', name: 'Marshall Islands' },\n  { code: 'MQ', name: 'Martinique' },\n  { code: 'MR', name: 'Mauritania' },\n  { code: 'MU', name: 'Mauritius' },\n  { code: 'YT', name: 'Mayotte' },\n  { code: 'MX', name: 'Mexico' },\n  { code: 'FM', name: 'Micronesia' },\n  { code: 'MD', name: 'Moldova' },\n  { code: 'MC', name: 'Monaco' },\n  { code: 'MN', name: 'Mongolia' },\n  { code: 'ME', name: 'Montenegro' },\n  { code: 'MS', name: 'Montserrat' },\n  { code: 'MA', name: 'Morocco' },\n  { code: 'MZ', name: 'Mozambique' },\n  { code: 'MM', name: 'Myanmar [Burma]' },\n  { code: 'NA', name: 'Namibia' },\n  { code: 'NR', name: 'Nauru' },\n  { code: 'NP', name: 'Nepal' },\n  { code: 'NL', name: 'Netherlands' },\n  { code: 'AN', name: 'Netherlands Antilles' },\n  { code: 'NC', name: 'New Caledonia' },\n  { code: 'NZ', name: 'New Zealand' },\n  { code: 'NI', name: 'Nicaragua' },\n  { code: 'NE', name: 'Niger' },\n  { code: 'NG', name: 'Nigeria' },\n  { code: 'NU', name: 'Niue' },\n  { code: 'NF', name: 'Norfolk Island' },\n  { code: 'KP', name: 'North Korea' },\n  { code: 'MP', name: 'Northern Mariana Islands' },\n  { code: 'NO', name: 'Norway' },\n  { code: 'OM', name: 'Oman' },\n  { code: 'PK', name: 'Pakistan' },\n  { code: 'PW', name: 'Palau' },\n  { code: 'PS', name: 'Palestinian Territories' },\n  { code: 'PA', name: 'Panama' },\n  { code: 'PG', name: 'Papua New Guinea' },\n  { code: 'PY', name: 'Paraguay' },\n  { code: 'PE', name: 'Peru' },\n  { code: 'PH', name: 'Philippines' },\n  { code: 'PN', name: 'Pitcairn Islands' },\n  { code: 'PL', name: 'Poland' },\n  { code: 'PT', name: 'Portugal' },\n  { code: 'PR', name: 'Puerto Rico' },\n  { code: 'QA', name: 'Qatar' },\n  { code: 'RO', name: 'Romania' },\n  { code: 'RU', name: 'Russia' },\n  { code: 'RW', name: 'Rwanda' },\n  { code: 'RE', name: 'Réunion' },\n  { code: 'BL', name: 'Saint Barthélemy' },\n  { code: 'SH', name: 'Saint Helena' },\n  { code: 'KN', name: 'Saint Kitts and Nevis' },\n  { code: 'LC', name: 'Saint Lucia' },\n  { code: 'MF', name: 'Saint Martin' },\n  { code: 'PM', name: 'Saint Pierre and Miquelon' },\n  { code: 'VC', name: 'Saint Vincent and the Grenadines' },\n  { code: 'WS', name: 'Samoa' },\n  { code: 'SM', name: 'San Marino' },\n  { code: 'SA', name: 'Saudi Arabia' },\n  { code: 'SN', name: 'Senegal' },\n  { code: 'RS', name: 'Serbia' },\n  { code: 'SC', name: 'Seychelles' },\n  { code: 'SL', name: 'Sierra Leone' },\n  { code: 'SG', name: 'Singapore' },\n  { code: 'SK', name: 'Slovakia' },\n  { code: 'SI', name: 'Slovenia' },\n  { code: 'SB', name: 'Solomon Islands' },\n  { code: 'SO', name: 'Somalia' },\n  { code: 'ZA', name: 'South Africa' },\n  { code: 'GS', name: 'South Georgia and the South Sandwich Islands' },\n  { code: 'KR', name: 'South Korea' },\n  { code: 'ES', name: 'Spain' },\n  { code: 'LK', name: 'Sri Lanka' },\n  { code: 'SD', name: 'Sudan' },\n  { code: 'SR', name: 'Suriname' },\n  { code: 'SJ', name: 'Svalbard and Jan Mayen' },\n  { code: 'SZ', name: 'Swaziland' },\n  { code: 'SE', name: 'Sweden' },\n  { code: 'CH', name: 'Switzerland' },\n  { code: 'SY', name: 'Syria' },\n  { code: 'ST', name: 'São Tomé and Príncipe' },\n  { code: 'TW', name: 'Taiwan' },\n  { code: 'TJ', name: 'Tajikistan' },\n  { code: 'TZ', name: 'Tanzania' },\n  { code: 'TH', name: 'Thailand' },\n  { code: 'TL', name: 'Timor-Leste' },\n  { code: 'TG', name: 'Togo' },\n  { code: 'TK', name: 'Tokelau' },\n  { code: 'TO', name: 'Tonga' },\n  { code: 'TT', name: 'Trinidad and Tobago' },\n  { code: 'TN', name: 'Tunisia' },\n  { code: 'TR', name: 'Turkey' },\n  { code: 'TM', name: 'Turkmenistan' },\n  { code: 'TC', name: 'Turks and Caicos Islands' },\n  { code: 'TV', name: 'Tuvalu' },\n  { code: 'UM', name: 'U.S. Minor Outlying Islands' },\n  { code: 'VI', name: 'U.S. Virgin Islands' },\n  { code: 'UG', name: 'Uganda' },\n  { code: 'UA', name: 'Ukraine' },\n  { code: 'AE', name: 'United Arab Emirates' },\n  { code: 'GB', name: 'United Kingdom' },\n  { code: 'US', name: 'United States' },\n  { code: 'UY', name: 'Uruguay' },\n  { code: 'UZ', name: 'Uzbekistan' },\n  { code: 'VU', name: 'Vanuatu' },\n  { code: 'VA', name: 'Vatican City' },\n  { code: 'VE', name: 'Venezuela' },\n  { code: 'VN', name: 'Vietnam' },\n  { code: 'WF', name: 'Wallis and Futuna' },\n  { code: 'EH', name: 'Western Sahara' },\n  { code: 'YE', name: 'Yemen' },\n  { code: 'ZM', name: 'Zambia' },\n  { code: 'ZW', name: 'Zimbabwe' },\n  { code: 'AX', name: 'Åland Islands' }\n];\n"
  },
  {
    "path": "packages/evershop/src/lib/locale/currencies.ts",
    "content": "export interface Currency {\n  code: string;\n  name: string;\n}\n\nexport const currencies: Currency[] = [\n  { code: 'AFN', name: 'Afghan Afghani' },\n  { code: 'ALL', name: 'Albanian Lek' },\n  { code: 'DZD', name: 'Algerian Dinar' },\n  { code: 'AOA', name: 'Angolan Kwanza' },\n  { code: 'ARS', name: 'Argentine Peso' },\n  { code: 'AMD', name: 'Armenian Dram' },\n  { code: 'AWG', name: 'Aruban Florin' },\n  { code: 'AUD', name: 'Australian Dollar' },\n  { code: 'AZN', name: 'Azerbaijani Manat' },\n  { code: 'AZM', name: 'Azerbaijani Manat (1993-2006)' },\n  { code: 'BSD', name: 'Bahamian Dollar' },\n  { code: 'BHD', name: 'Bahraini Dinar' },\n  { code: 'BDT', name: 'Bangladeshi Taka' },\n  { code: 'BBD', name: 'Barbadian Dollar' },\n  { code: 'BYR', name: 'Belarusian Ruble' },\n  { code: 'BZD', name: 'Belize Dollar' },\n  { code: 'BMD', name: 'Bermudan Dollar' },\n  { code: 'BTN', name: 'Bhutanese Ngultrum' },\n  { code: 'BOB', name: 'Bolivian Boliviano' },\n  { code: 'BAM', name: 'Bosnia-Herzegovina Convertible Mark' },\n  { code: 'BWP', name: 'Botswanan Pula' },\n  { code: 'BRL', name: 'Brazilian Real' },\n  { code: 'GBP', name: 'British Pound Sterling' },\n  { code: 'BND', name: 'Brunei Dollar' },\n  { code: 'BGN', name: 'Bulgarian Lev' },\n  { code: 'BUK', name: 'Burmese Kyat' },\n  { code: 'BIF', name: 'Burundian Franc' },\n  { code: 'XOF', name: 'CFA Franc BCEAO' },\n  { code: 'XPF', name: 'CFP Franc' },\n  { code: 'KHR', name: 'Cambodian Riel' },\n  { code: 'CAD', name: 'Canadian Dollar' },\n  { code: 'CVE', name: 'Cape Verdean Escudo' },\n  { code: 'KYD', name: 'Cayman Islands Dollar' },\n  { code: 'CLP', name: 'Chilean Peso' },\n  { code: 'CNY', name: 'Chinese Yuan Renminbi' },\n  { code: 'COP', name: 'Colombian Peso' },\n  { code: 'KMF', name: 'Comorian Franc' },\n  { code: 'CDF', name: 'Congolese Franc' },\n  { code: 'CRC', name: 'Costa Rican Colón' },\n  { code: 'HRK', name: 'Croatian Kuna' },\n  { code: 'CUP', name: 'Cuban Peso' },\n  { code: 'CZK', name: 'Czech Republic Koruna' },\n  { code: 'DKK', name: 'Danish Krone' },\n  { code: 'DJF', name: 'Djiboutian Franc' },\n  { code: 'DOP', name: 'Dominican Peso' },\n  { code: 'XCD', name: 'East Caribbean Dollar' },\n  { code: 'EGP', name: 'Egyptian Pound' },\n  { code: 'GQE', name: 'Equatorial Guinean Ekwele' },\n  { code: 'ERN', name: 'Eritrean Nakfa' },\n  { code: 'EEK', name: 'Estonian Kroon' },\n  { code: 'ETB', name: 'Ethiopian Birr' },\n  { code: 'EUR', name: 'Euro' },\n  { code: 'FKP', name: 'Falkland Islands Pound' },\n  { code: 'FJD', name: 'Fijian Dollar' },\n  { code: 'GMD', name: 'Gambian Dalasi' },\n  { code: 'GEK', name: 'Georgian Kupon Larit' },\n  { code: 'GEL', name: 'Georgian Lari' },\n  { code: 'GHS', name: 'Ghanaian Cedi' },\n  { code: 'GIP', name: 'Gibraltar Pound' },\n  { code: 'GTQ', name: 'Guatemalan Quetzal' },\n  { code: 'GNF', name: 'Guinean Franc' },\n  { code: 'GYD', name: 'Guyanaese Dollar' },\n  { code: 'HTG', name: 'Haitian Gourde' },\n  { code: 'HNL', name: 'Honduran Lempira' },\n  { code: 'HKD', name: 'Hong Kong Dollar' },\n  { code: 'HUF', name: 'Hungarian Forint' },\n  { code: 'ISK', name: 'Icelandic Króna' },\n  { code: 'INR', name: 'Indian Rupee' },\n  { code: 'IDR', name: 'Indonesian Rupiah' },\n  { code: 'IRR', name: 'Iranian Rial' },\n  { code: 'IQD', name: 'Iraqi Dinar' },\n  { code: 'ILS', name: 'Israeli New Sheqel' },\n  { code: 'JMD', name: 'Jamaican Dollar' },\n  { code: 'JPY', name: 'Japanese Yen' },\n  { code: 'JOD', name: 'Jordanian Dinar' },\n  { code: 'KZT', name: 'Kazakhstan Tenge' },\n  { code: 'KES', name: 'Kenyan Shilling' },\n  { code: 'KWD', name: 'Kuwaiti Dinar' },\n  { code: 'KGS', name: 'Kyrgystani Som' },\n  { code: 'LAK', name: 'Laotian Kip' },\n  { code: 'LVL', name: 'Latvian Lats' },\n  { code: 'LBP', name: 'Lebanese Pound' },\n  { code: 'LSL', name: 'Lesotho Loti' },\n  { code: 'LRD', name: 'Liberian Dollar' },\n  { code: 'LYD', name: 'Libyan Dinar' },\n  { code: 'LTL', name: 'Lithuanian Litas' },\n  { code: 'MOP', name: 'Macanese Pataca' },\n  { code: 'MKD', name: 'Macedonian Denar' },\n  { code: 'MGA', name: 'Malagasy Ariary' },\n  { code: 'MWK', name: 'Malawian Kwacha' },\n  { code: 'MYR', name: 'Malaysian Ringgit' },\n  { code: 'MVR', name: 'Maldivian Rufiyaa' },\n  { code: 'MRO', name: 'Mauritanian Ouguiya' },\n  { code: 'MUR', name: 'Mauritian Rupee' },\n  { code: 'MXN', name: 'Mexican Peso' },\n  { code: 'MDL', name: 'Moldovan Leu' },\n  { code: 'MNT', name: 'Mongolian Tugrik' },\n  { code: 'MAD', name: 'Moroccan Dirham' },\n  { code: 'MZN', name: 'Mozambican Metical' },\n  { code: 'MMK', name: 'Myanma Kyat' },\n  { code: 'NAD', name: 'Namibian Dollar' },\n  { code: 'NPR', name: 'Nepalese Rupee' },\n  { code: 'ANG', name: 'Netherlands Antillean Guilder' },\n  { code: 'TWD', name: 'New Taiwan Dollar' },\n  { code: 'NZD', name: 'New Zealand Dollar' },\n  { code: 'NIC', name: 'Nicaraguan Cordoba' },\n  { code: 'NGN', name: 'Nigerian Naira' },\n  { code: 'KPW', name: 'North Korean Won' },\n  { code: 'NOK', name: 'Norwegian Krone' },\n  { code: 'ROL', name: 'Old Romanian Leu' },\n  { code: 'TRL', name: 'Old Turkish Lira' },\n  { code: 'OMR', name: 'Omani Rial' },\n  { code: 'PKR', name: 'Pakistani Rupee' },\n  { code: 'PAB', name: 'Panamanian Balboa' },\n  { code: 'PGK', name: 'Papua New Guinean Kina' },\n  { code: 'PYG', name: 'Paraguayan Guarani' },\n  { code: 'PEN', name: 'Peruvian Nuevo Sol' },\n  { code: 'PHP', name: 'Philippine Peso' },\n  { code: 'PLN', name: 'Polish Zloty' },\n  { code: 'QAR', name: 'Qatari Rial' },\n  { code: 'RHD', name: 'Rhodesian Dollar' },\n  { code: 'RON', name: 'Romanian Leu' },\n  { code: 'RUB', name: 'Russian Ruble' },\n  { code: 'RWF', name: 'Rwandan Franc' },\n  { code: 'SHP', name: 'Saint Helena Pound' },\n  { code: 'SVC', name: 'Salvadoran Colón' },\n  { code: 'WST', name: 'Samoan Tala' },\n  { code: 'SAR', name: 'Saudi Riyal' },\n  { code: 'RSD', name: 'Serbian Dinar' },\n  { code: 'SCR', name: 'Seychellois Rupee' },\n  { code: 'SLL', name: 'Sierra Leonean Leone' },\n  { code: 'SGD', name: 'Singapore Dollar' },\n  { code: 'SKK', name: 'Slovak Koruna' },\n  { code: 'SBD', name: 'Solomon Islands Dollar' },\n  { code: 'SOS', name: 'Somali Shilling' },\n  { code: 'ZAR', name: 'South African Rand' },\n  { code: 'KRW', name: 'South Korean Won' },\n  { code: 'LKR', name: 'Sri Lanka Rupee' },\n  { code: 'SDG', name: 'Sudanese Pound' },\n  { code: 'SRD', name: 'Surinamese Dollar' },\n  { code: 'SZL', name: 'Swazi Lilangeni' },\n  { code: 'SEK', name: 'Swedish Krona' },\n  { code: 'CHF', name: 'Swiss Franc' },\n  { code: 'SYP', name: 'Syrian Pound' },\n  { code: 'STD', name: 'São Tomé and Príncipe Dobra' },\n  { code: 'TJS', name: 'Tajikistani Somoni' },\n  { code: 'TZS', name: 'Tanzanian Shilling' },\n  { code: 'THB', name: 'Thai Baht' },\n  { code: 'TOP', name: 'Tongan Paʻanga' },\n  { code: 'TTD', name: 'Trinidad and Tobago Dollar' },\n  { code: 'TND', name: 'Tunisian Dinar' },\n  { code: 'TRY', name: 'Turkish Lira' },\n  { code: 'TMM', name: 'Turkmenistani Manat' },\n  { code: 'USD', name: 'US Dollar' },\n  { code: 'UGX', name: 'Ugandan Shilling' },\n  { code: 'UAH', name: 'Ukrainian Hryvnia' },\n  { code: 'AED', name: 'United Arab Emirates Dirham' },\n  { code: 'UYU', name: 'Uruguayan Peso' },\n  { code: 'UZS', name: 'Uzbekistan Som' },\n  { code: 'VUV', name: 'Vanuatu Vatu' },\n  { code: 'VEB', name: 'Venezuelan Bolívar' },\n  { code: 'VEF', name: 'Venezuelan Bolívar Fuerte' },\n  { code: 'VND', name: 'Vietnamese Dong' },\n  { code: 'CHE', name: 'WIR Euro' },\n  { code: 'CHW', name: 'WIR Franc' },\n  { code: 'YER', name: 'Yemeni Rial' },\n  { code: 'ZMK', name: 'Zambian Kwacha' },\n  { code: 'ZWD', name: 'Zimbabwean Dollar' }\n];\n"
  },
  {
    "path": "packages/evershop/src/lib/locale/index.ts",
    "content": "export * from './countries.js';\nexport * from './currencies.js';\nexport * from './provinces.js';\nexport * from './timezones.js';\n"
  },
  {
    "path": "packages/evershop/src/lib/locale/provinces.ts",
    "content": "export interface Province {\n  code: string;\n  countryCode: string;\n  name: string;\n}\n\nexport const provinces: Province[] = [\n  { code: 'AD-07', countryCode: 'AD', name: 'Andorra la Vella' },\n  { code: 'AD-02', countryCode: 'AD', name: 'Canillo' },\n  { code: 'AD-03', countryCode: 'AD', name: 'Encamp' },\n  { code: 'AD-08', countryCode: 'AD', name: 'Escaldes-Engordany' },\n  { code: 'AD-04', countryCode: 'AD', name: 'La Massana' },\n  { code: 'AD-05', countryCode: 'AD', name: 'Ordino' },\n  { code: 'AD-06', countryCode: 'AD', name: 'Sant Julia de Loria' },\n  { code: 'AE-AJ', countryCode: 'AE', name: \"'Ajman\" },\n  { code: 'AE-AZ', countryCode: 'AE', name: 'Abu Zaby' },\n  { code: 'AE-FU', countryCode: 'AE', name: 'Al Fujayrah' },\n  { code: 'AE-SH', countryCode: 'AE', name: 'Ash Shariqah' },\n  { code: 'AE-DU', countryCode: 'AE', name: 'Dubayy' },\n  { code: 'AE-RK', countryCode: 'AE', name: \"Ra's al Khaymah\" },\n  { code: 'AE-UQ', countryCode: 'AE', name: 'Umm al Qaywayn' },\n  { code: 'AF-BDS', countryCode: 'AF', name: 'Badakhshan' },\n  { code: 'AF-BDG', countryCode: 'AF', name: 'Badghis' },\n  { code: 'AF-BGL', countryCode: 'AF', name: 'Baghlan' },\n  { code: 'AF-BAL', countryCode: 'AF', name: 'Balkh' },\n  { code: 'AF-BAM', countryCode: 'AF', name: 'Bamyan' },\n  { code: 'AF-DAY', countryCode: 'AF', name: 'Daykundi' },\n  { code: 'AF-FRA', countryCode: 'AF', name: 'Farah' },\n  { code: 'AF-FYB', countryCode: 'AF', name: 'Faryab' },\n  { code: 'AF-GHA', countryCode: 'AF', name: 'Ghazni' },\n  { code: 'AF-GHO', countryCode: 'AF', name: 'Ghor' },\n  { code: 'AF-HEL', countryCode: 'AF', name: 'Helmand' },\n  { code: 'AF-HER', countryCode: 'AF', name: 'Herat' },\n  { code: 'AF-JOW', countryCode: 'AF', name: 'Jowzjan' },\n  { code: 'AF-KAB', countryCode: 'AF', name: 'Kabul' },\n  { code: 'AF-KAN', countryCode: 'AF', name: 'Kandahar' },\n  { code: 'AF-KAP', countryCode: 'AF', name: 'Kapisa' },\n  { code: 'AF-KHO', countryCode: 'AF', name: 'Khost' },\n  { code: 'AF-KNR', countryCode: 'AF', name: 'Kunar' },\n  { code: 'AF-KDZ', countryCode: 'AF', name: 'Kunduz' },\n  { code: 'AF-LAG', countryCode: 'AF', name: 'Laghman' },\n  { code: 'AF-LOG', countryCode: 'AF', name: 'Logar' },\n  { code: 'AF-NAN', countryCode: 'AF', name: 'Nangarhar' },\n  { code: 'AF-NIM', countryCode: 'AF', name: 'Nimroz' },\n  { code: 'AF-NUR', countryCode: 'AF', name: 'Nuristan' },\n  { code: 'AF-PKA', countryCode: 'AF', name: 'Paktika' },\n  { code: 'AF-PIA', countryCode: 'AF', name: 'Paktiya' },\n  { code: 'AF-PAN', countryCode: 'AF', name: 'Panjshayr' },\n  { code: 'AF-PAR', countryCode: 'AF', name: 'Parwan' },\n  { code: 'AF-SAM', countryCode: 'AF', name: 'Samangan' },\n  { code: 'AF-SAR', countryCode: 'AF', name: 'Sar-e Pul' },\n  { code: 'AF-TAK', countryCode: 'AF', name: 'Takhar' },\n  { code: 'AF-URU', countryCode: 'AF', name: 'Uruzgan' },\n  { code: 'AF-WAR', countryCode: 'AF', name: 'Wardak' },\n  { code: 'AF-ZAB', countryCode: 'AF', name: 'Zabul' },\n  { code: 'AG-04', countryCode: 'AG', name: 'Saint John' },\n  { code: 'AG-05', countryCode: 'AG', name: 'Saint Mary' },\n  { code: 'AG-06', countryCode: 'AG', name: 'Saint Paul' },\n  { code: 'AL-01', countryCode: 'AL', name: 'Berat' },\n  { code: 'AL-09', countryCode: 'AL', name: 'Diber' },\n  { code: 'AL-02', countryCode: 'AL', name: 'Durres' },\n  { code: 'AL-03', countryCode: 'AL', name: 'Elbasan' },\n  { code: 'AL-04', countryCode: 'AL', name: 'Fier' },\n  { code: 'AL-05', countryCode: 'AL', name: 'Gjirokaster' },\n  { code: 'AL-06', countryCode: 'AL', name: 'Korce' },\n  { code: 'AL-07', countryCode: 'AL', name: 'Kukes' },\n  { code: 'AL-08', countryCode: 'AL', name: 'Lezhe' },\n  { code: 'AL-10', countryCode: 'AL', name: 'Shkoder' },\n  { code: 'AL-11', countryCode: 'AL', name: 'Tirane' },\n  { code: 'AL-12', countryCode: 'AL', name: 'Vlore' },\n  { code: 'AM-AG', countryCode: 'AM', name: 'Aragacotn' },\n  { code: 'AM-AR', countryCode: 'AM', name: 'Ararat' },\n  { code: 'AM-AV', countryCode: 'AM', name: 'Armavir' },\n  { code: 'AM-ER', countryCode: 'AM', name: 'Erevan' },\n  { code: 'AM-GR', countryCode: 'AM', name: \"Gegark'unik'\" },\n  { code: 'AM-KT', countryCode: 'AM', name: \"Kotayk'\" },\n  { code: 'AM-LO', countryCode: 'AM', name: 'Lori' },\n  { code: 'AM-SH', countryCode: 'AM', name: 'Sirak' },\n  { code: 'AM-SU', countryCode: 'AM', name: \"Syunik'\" },\n  { code: 'AM-TV', countryCode: 'AM', name: 'Tavus' },\n  { code: 'AM-VD', countryCode: 'AM', name: 'Vayoc Jor' },\n  { code: 'AO-BGO', countryCode: 'AO', name: 'Bengo' },\n  { code: 'AO-BGU', countryCode: 'AO', name: 'Benguela' },\n  { code: 'AO-BIE', countryCode: 'AO', name: 'Bie' },\n  { code: 'AO-CAB', countryCode: 'AO', name: 'Cabinda' },\n  { code: 'AO-CNN', countryCode: 'AO', name: 'Cunene' },\n  { code: 'AO-HUA', countryCode: 'AO', name: 'Huambo' },\n  { code: 'AO-HUI', countryCode: 'AO', name: 'Huila' },\n  { code: 'AO-CCU', countryCode: 'AO', name: 'Kuando Kubango' },\n  { code: 'AO-CNO', countryCode: 'AO', name: 'Kwanza Norte' },\n  { code: 'AO-CUS', countryCode: 'AO', name: 'Kwanza Sul' },\n  { code: 'AO-LUA', countryCode: 'AO', name: 'Luanda' },\n  { code: 'AO-LNO', countryCode: 'AO', name: 'Lunda Norte' },\n  { code: 'AO-LSU', countryCode: 'AO', name: 'Lunda Sul' },\n  { code: 'AO-MAL', countryCode: 'AO', name: 'Malange' },\n  { code: 'AO-MOX', countryCode: 'AO', name: 'Moxico' },\n  { code: 'AO-NAM', countryCode: 'AO', name: 'Namibe' },\n  { code: 'AO-UIG', countryCode: 'AO', name: 'Uige' },\n  { code: 'AO-ZAI', countryCode: 'AO', name: 'Zaire' },\n  { code: 'AR-B', countryCode: 'AR', name: 'Buenos Aires' },\n  { code: 'AR-K', countryCode: 'AR', name: 'Catamarca' },\n  { code: 'AR-H', countryCode: 'AR', name: 'Chaco' },\n  { code: 'AR-U', countryCode: 'AR', name: 'Chubut' },\n  { code: 'AR-C', countryCode: 'AR', name: 'Ciudad Autonoma de Buenos Aires' },\n  { code: 'AR-X', countryCode: 'AR', name: 'Cordoba' },\n  { code: 'AR-W', countryCode: 'AR', name: 'Corrientes' },\n  { code: 'AR-E', countryCode: 'AR', name: 'Entre Rios' },\n  { code: 'AR-P', countryCode: 'AR', name: 'Formosa' },\n  { code: 'AR-Y', countryCode: 'AR', name: 'Jujuy' },\n  { code: 'AR-L', countryCode: 'AR', name: 'La Pampa' },\n  { code: 'AR-F', countryCode: 'AR', name: 'La Rioja' },\n  { code: 'AR-M', countryCode: 'AR', name: 'Mendoza' },\n  { code: 'AR-N', countryCode: 'AR', name: 'Misiones' },\n  { code: 'AR-Q', countryCode: 'AR', name: 'Neuquen' },\n  { code: 'AR-R', countryCode: 'AR', name: 'Rio Negro' },\n  { code: 'AR-A', countryCode: 'AR', name: 'Salta' },\n  { code: 'AR-J', countryCode: 'AR', name: 'San Juan' },\n  { code: 'AR-D', countryCode: 'AR', name: 'San Luis' },\n  { code: 'AR-Z', countryCode: 'AR', name: 'Santa Cruz' },\n  { code: 'AR-S', countryCode: 'AR', name: 'Santa Fe' },\n  { code: 'AR-G', countryCode: 'AR', name: 'Santiago del Estero' },\n  { code: 'AR-V', countryCode: 'AR', name: 'Tierra del Fuego' },\n  { code: 'AR-T', countryCode: 'AR', name: 'Tucuman' },\n  { code: 'AT-1', countryCode: 'AT', name: 'Burgenland' },\n  { code: 'AT-2', countryCode: 'AT', name: 'Karnten' },\n  { code: 'AT-3', countryCode: 'AT', name: 'Niederosterreich' },\n  { code: 'AT-4', countryCode: 'AT', name: 'Oberosterreich' },\n  { code: 'AT-5', countryCode: 'AT', name: 'Salzburg' },\n  { code: 'AT-6', countryCode: 'AT', name: 'Steiermark' },\n  { code: 'AT-7', countryCode: 'AT', name: 'Tirol' },\n  { code: 'AT-8', countryCode: 'AT', name: 'Vorarlberg' },\n  { code: 'AT-9', countryCode: 'AT', name: 'Wien' },\n  { code: 'AU-ACT', countryCode: 'AU', name: 'Australian Capital Territory' },\n  { code: 'AU-NSW', countryCode: 'AU', name: 'New South Wales' },\n  { code: 'AU-NT', countryCode: 'AU', name: 'Northern Territory' },\n  { code: 'AU-QLD', countryCode: 'AU', name: 'Queensland' },\n  { code: 'AU-SA', countryCode: 'AU', name: 'South Australia' },\n  { code: 'AU-TAS', countryCode: 'AU', name: 'Tasmania' },\n  { code: 'AU-VIC', countryCode: 'AU', name: 'Victoria' },\n  { code: 'AU-WA', countryCode: 'AU', name: 'Western Australia' },\n  { code: 'AZ-ABS', countryCode: 'AZ', name: 'Abseron' },\n  { code: 'AZ-AGC', countryCode: 'AZ', name: 'Agcabadi' },\n  { code: 'AZ-AGM', countryCode: 'AZ', name: 'Agdam' },\n  { code: 'AZ-AGS', countryCode: 'AZ', name: 'Agdas' },\n  { code: 'AZ-AGA', countryCode: 'AZ', name: 'Agstafa' },\n  { code: 'AZ-AGU', countryCode: 'AZ', name: 'Agsu' },\n  { code: 'AZ-AST', countryCode: 'AZ', name: 'Astara' },\n  { code: 'AZ-BA', countryCode: 'AZ', name: 'Baki' },\n  { code: 'AZ-BAL', countryCode: 'AZ', name: 'Balakan' },\n  { code: 'AZ-BAR', countryCode: 'AZ', name: 'Barda' },\n  { code: 'AZ-BEY', countryCode: 'AZ', name: 'Beylaqan' },\n  { code: 'AZ-BIL', countryCode: 'AZ', name: 'Bilasuvar' },\n  { code: 'AZ-CAB', countryCode: 'AZ', name: 'Cabrayil' },\n  { code: 'AZ-CAL', countryCode: 'AZ', name: 'Calilabad' },\n  { code: 'AZ-DAS', countryCode: 'AZ', name: 'Daskasan' },\n  { code: 'AZ-FUZ', countryCode: 'AZ', name: 'Fuzuli' },\n  { code: 'AZ-GAD', countryCode: 'AZ', name: 'Gadabay' },\n  { code: 'AZ-GA', countryCode: 'AZ', name: 'Ganca' },\n  { code: 'AZ-GOR', countryCode: 'AZ', name: 'Goranboy' },\n  { code: 'AZ-GOY', countryCode: 'AZ', name: 'Goycay' },\n  { code: 'AZ-GYG', countryCode: 'AZ', name: 'Goygol' },\n  { code: 'AZ-HAC', countryCode: 'AZ', name: 'Haciqabul' },\n  { code: 'AZ-IMI', countryCode: 'AZ', name: 'Imisli' },\n  { code: 'AZ-ISM', countryCode: 'AZ', name: 'Ismayilli' },\n  { code: 'AZ-KAL', countryCode: 'AZ', name: 'Kalbacar' },\n  { code: 'AZ-LAC', countryCode: 'AZ', name: 'Lacin' },\n  { code: 'AZ-LA', countryCode: 'AZ', name: 'Lankaran' },\n  { code: 'AZ-LER', countryCode: 'AZ', name: 'Lerik' },\n  { code: 'AZ-MAS', countryCode: 'AZ', name: 'Masalli' },\n  { code: 'AZ-MI', countryCode: 'AZ', name: 'Mingacevir' },\n  { code: 'AZ-NA', countryCode: 'AZ', name: 'Naftalan' },\n  { code: 'AZ-NX', countryCode: 'AZ', name: 'Naxcivan' },\n  { code: 'AZ-NEF', countryCode: 'AZ', name: 'Neftcala' },\n  { code: 'AZ-OGU', countryCode: 'AZ', name: 'Oguz' },\n  { code: 'AZ-QAB', countryCode: 'AZ', name: 'Qabala' },\n  { code: 'AZ-QAX', countryCode: 'AZ', name: 'Qax' },\n  { code: 'AZ-QAZ', countryCode: 'AZ', name: 'Qazax' },\n  { code: 'AZ-QOB', countryCode: 'AZ', name: 'Qobustan' },\n  { code: 'AZ-QBA', countryCode: 'AZ', name: 'Quba' },\n  { code: 'AZ-QBI', countryCode: 'AZ', name: 'Qubadli' },\n  { code: 'AZ-QUS', countryCode: 'AZ', name: 'Qusar' },\n  { code: 'AZ-SAT', countryCode: 'AZ', name: 'Saatli' },\n  { code: 'AZ-SAB', countryCode: 'AZ', name: 'Sabirabad' },\n  { code: 'AZ-SA', countryCode: 'AZ', name: 'Saki' },\n  { code: 'AZ-SAL', countryCode: 'AZ', name: 'Salyan' },\n  { code: 'AZ-SMI', countryCode: 'AZ', name: 'Samaxi' },\n  { code: 'AZ-SKR', countryCode: 'AZ', name: 'Samkir' },\n  { code: 'AZ-SMX', countryCode: 'AZ', name: 'Samux' },\n  { code: 'AZ-SR', countryCode: 'AZ', name: 'Sirvan' },\n  { code: 'AZ-SM', countryCode: 'AZ', name: 'Sumqayit' },\n  { code: 'AZ-SUS', countryCode: 'AZ', name: 'Susa' },\n  { code: 'AZ-TAR', countryCode: 'AZ', name: 'Tartar' },\n  { code: 'AZ-TOV', countryCode: 'AZ', name: 'Tovuz' },\n  { code: 'AZ-UCA', countryCode: 'AZ', name: 'Ucar' },\n  { code: 'AZ-XAC', countryCode: 'AZ', name: 'Xacmaz' },\n  { code: 'AZ-XA', countryCode: 'AZ', name: 'Xankandi' },\n  { code: 'AZ-XIZ', countryCode: 'AZ', name: 'Xizi' },\n  { code: 'AZ-XCI', countryCode: 'AZ', name: 'Xocali' },\n  { code: 'AZ-XVD', countryCode: 'AZ', name: 'Xocavand' },\n  { code: 'AZ-YAR', countryCode: 'AZ', name: 'Yardimli' },\n  { code: 'AZ-YE', countryCode: 'AZ', name: 'Yevlax' },\n  { code: 'AZ-ZAN', countryCode: 'AZ', name: 'Zangilan' },\n  { code: 'AZ-ZAQ', countryCode: 'AZ', name: 'Zaqatala' },\n  { code: 'AZ-ZAR', countryCode: 'AZ', name: 'Zardab' },\n  { code: 'BA-BIH', countryCode: 'BA', name: 'Federacija Bosne i Hercegovine' },\n  { code: 'BA-SRP', countryCode: 'BA', name: 'Republika Srpska' },\n  { code: 'BB-01', countryCode: 'BB', name: 'Christ Church' },\n  { code: 'BB-04', countryCode: 'BB', name: 'Saint James' },\n  { code: 'BB-06', countryCode: 'BB', name: 'Saint Joseph' },\n  { code: 'BB-08', countryCode: 'BB', name: 'Saint Michael' },\n  { code: 'BB-09', countryCode: 'BB', name: 'Saint Peter' },\n  { code: 'BD-06', countryCode: 'BD', name: 'Barisal' },\n  { code: 'BD-10', countryCode: 'BD', name: 'Chittagong' },\n  { code: 'BD-13', countryCode: 'BD', name: 'Dhaka' },\n  { code: 'BD-27', countryCode: 'BD', name: 'Khulna' },\n  { code: 'BD-54', countryCode: 'BD', name: 'Rajshahi' },\n  { code: 'BD-55', countryCode: 'BD', name: 'Rangpur' },\n  { code: 'BD-60', countryCode: 'BD', name: 'Sylhet' },\n  { code: 'BE-VAN', countryCode: 'BE', name: 'Antwerpen' },\n  { code: 'BE-WBR', countryCode: 'BE', name: 'Brabant wallon' },\n  { code: 'BE-BRU', countryCode: 'BE', name: 'Brussels Hoofdstedelijk Gewest' },\n  { code: 'BE-WHT', countryCode: 'BE', name: 'Hainaut' },\n  { code: 'BE-WLG', countryCode: 'BE', name: 'Liege' },\n  { code: 'BE-VLI', countryCode: 'BE', name: 'Limburg' },\n  { code: 'BE-WLX', countryCode: 'BE', name: 'Luxembourg' },\n  { code: 'BE-WNA', countryCode: 'BE', name: 'Namur' },\n  { code: 'BE-VOV', countryCode: 'BE', name: 'Oost-Vlaanderen' },\n  { code: 'BE-VBR', countryCode: 'BE', name: 'Vlaams-Brabant' },\n  { code: 'BE-VWV', countryCode: 'BE', name: 'West-Vlaanderen' },\n  { code: 'BF-BAL', countryCode: 'BF', name: 'Bale' },\n  { code: 'BF-BAM', countryCode: 'BF', name: 'Bam' },\n  { code: 'BF-BAN', countryCode: 'BF', name: 'Banwa' },\n  { code: 'BF-BAZ', countryCode: 'BF', name: 'Bazega' },\n  { code: 'BF-BGR', countryCode: 'BF', name: 'Bougouriba' },\n  { code: 'BF-BLG', countryCode: 'BF', name: 'Boulgou' },\n  { code: 'BF-BLK', countryCode: 'BF', name: 'Boulkiemde' },\n  { code: 'BF-COM', countryCode: 'BF', name: 'Comoe' },\n  { code: 'BF-GAN', countryCode: 'BF', name: 'Ganzourgou' },\n  { code: 'BF-GNA', countryCode: 'BF', name: 'Gnagna' },\n  { code: 'BF-GOU', countryCode: 'BF', name: 'Gourma' },\n  { code: 'BF-HOU', countryCode: 'BF', name: 'Houet' },\n  { code: 'BF-IOB', countryCode: 'BF', name: 'Ioba' },\n  { code: 'BF-KAD', countryCode: 'BF', name: 'Kadiogo' },\n  { code: 'BF-KEN', countryCode: 'BF', name: 'Kenedougou' },\n  { code: 'BF-KMD', countryCode: 'BF', name: 'Komondjari' },\n  { code: 'BF-KMP', countryCode: 'BF', name: 'Kompienga' },\n  { code: 'BF-KOS', countryCode: 'BF', name: 'Kossi' },\n  { code: 'BF-KOP', countryCode: 'BF', name: 'Koulpelogo' },\n  { code: 'BF-KOT', countryCode: 'BF', name: 'Kouritenga' },\n  { code: 'BF-KOW', countryCode: 'BF', name: 'Kourweogo' },\n  { code: 'BF-LER', countryCode: 'BF', name: 'Leraba' },\n  { code: 'BF-LOR', countryCode: 'BF', name: 'Loroum' },\n  { code: 'BF-MOU', countryCode: 'BF', name: 'Mouhoun' },\n  { code: 'BF-NAO', countryCode: 'BF', name: 'Nahouri' },\n  { code: 'BF-NAM', countryCode: 'BF', name: 'Namentenga' },\n  { code: 'BF-NAY', countryCode: 'BF', name: 'Nayala' },\n  { code: 'BF-NOU', countryCode: 'BF', name: 'Noumbiel' },\n  { code: 'BF-OUB', countryCode: 'BF', name: 'Oubritenga' },\n  { code: 'BF-OUD', countryCode: 'BF', name: 'Oudalan' },\n  { code: 'BF-PAS', countryCode: 'BF', name: 'Passore' },\n  { code: 'BF-PON', countryCode: 'BF', name: 'Poni' },\n  { code: 'BF-SNG', countryCode: 'BF', name: 'Sanguie' },\n  { code: 'BF-SMT', countryCode: 'BF', name: 'Sanmatenga' },\n  { code: 'BF-SEN', countryCode: 'BF', name: 'Seno' },\n  { code: 'BF-SIS', countryCode: 'BF', name: 'Sissili' },\n  { code: 'BF-SOM', countryCode: 'BF', name: 'Soum' },\n  { code: 'BF-SOR', countryCode: 'BF', name: 'Sourou' },\n  { code: 'BF-TAP', countryCode: 'BF', name: 'Tapoa' },\n  { code: 'BF-TUI', countryCode: 'BF', name: 'Tuy' },\n  { code: 'BF-YAG', countryCode: 'BF', name: 'Yagha' },\n  { code: 'BF-YAT', countryCode: 'BF', name: 'Yatenga' },\n  { code: 'BF-ZIR', countryCode: 'BF', name: 'Ziro' },\n  { code: 'BF-ZON', countryCode: 'BF', name: 'Zondoma' },\n  { code: 'BF-ZOU', countryCode: 'BF', name: 'Zoundweogo' },\n  { code: 'BG-01', countryCode: 'BG', name: 'Blagoevgrad' },\n  { code: 'BG-02', countryCode: 'BG', name: 'Burgas' },\n  { code: 'BG-08', countryCode: 'BG', name: 'Dobrich' },\n  { code: 'BG-07', countryCode: 'BG', name: 'Gabrovo' },\n  { code: 'BG-26', countryCode: 'BG', name: 'Haskovo' },\n  { code: 'BG-09', countryCode: 'BG', name: 'Kardzhali' },\n  { code: 'BG-10', countryCode: 'BG', name: 'Kyustendil' },\n  { code: 'BG-11', countryCode: 'BG', name: 'Lovech' },\n  { code: 'BG-12', countryCode: 'BG', name: 'Montana' },\n  { code: 'BG-13', countryCode: 'BG', name: 'Pazardzhik' },\n  { code: 'BG-14', countryCode: 'BG', name: 'Pernik' },\n  { code: 'BG-15', countryCode: 'BG', name: 'Pleven' },\n  { code: 'BG-16', countryCode: 'BG', name: 'Plovdiv' },\n  { code: 'BG-17', countryCode: 'BG', name: 'Razgrad' },\n  { code: 'BG-18', countryCode: 'BG', name: 'Ruse' },\n  { code: 'BG-27', countryCode: 'BG', name: 'Shumen' },\n  { code: 'BG-19', countryCode: 'BG', name: 'Silistra' },\n  { code: 'BG-20', countryCode: 'BG', name: 'Sliven' },\n  { code: 'BG-21', countryCode: 'BG', name: 'Smolyan' },\n  { code: 'BG-23', countryCode: 'BG', name: 'Sofia' },\n  { code: 'BG-22', countryCode: 'BG', name: 'Sofia (stolitsa)' },\n  { code: 'BG-24', countryCode: 'BG', name: 'Stara Zagora' },\n  { code: 'BG-25', countryCode: 'BG', name: 'Targovishte' },\n  { code: 'BG-03', countryCode: 'BG', name: 'Varna' },\n  { code: 'BG-04', countryCode: 'BG', name: 'Veliko Tarnovo' },\n  { code: 'BG-05', countryCode: 'BG', name: 'Vidin' },\n  { code: 'BG-06', countryCode: 'BG', name: 'Vratsa' },\n  { code: 'BG-28', countryCode: 'BG', name: 'Yambol' },\n  { code: 'BH-13', countryCode: 'BH', name: \"Al 'Asimah\" },\n  { code: 'BH-15', countryCode: 'BH', name: 'Al Muharraq' },\n  { code: 'BH-17', countryCode: 'BH', name: 'Ash Shamaliyah' },\n  { code: 'BI-BB', countryCode: 'BI', name: 'Bubanza' },\n  { code: 'BI-BM', countryCode: 'BI', name: 'Bujumbura Mairie' },\n  { code: 'BI-BR', countryCode: 'BI', name: 'Bururi' },\n  { code: 'BI-CA', countryCode: 'BI', name: 'Cankuzo' },\n  { code: 'BI-CI', countryCode: 'BI', name: 'Cibitoke' },\n  { code: 'BI-GI', countryCode: 'BI', name: 'Gitega' },\n  { code: 'BI-KR', countryCode: 'BI', name: 'Karuzi' },\n  { code: 'BI-KY', countryCode: 'BI', name: 'Kayanza' },\n  { code: 'BI-KI', countryCode: 'BI', name: 'Kirundo' },\n  { code: 'BI-MA', countryCode: 'BI', name: 'Makamba' },\n  { code: 'BI-MU', countryCode: 'BI', name: 'Muramvya' },\n  { code: 'BI-MY', countryCode: 'BI', name: 'Muyinga' },\n  { code: 'BI-MW', countryCode: 'BI', name: 'Mwaro' },\n  { code: 'BI-NG', countryCode: 'BI', name: 'Ngozi' },\n  { code: 'BI-RT', countryCode: 'BI', name: 'Rutana' },\n  { code: 'BI-RY', countryCode: 'BI', name: 'Ruyigi' },\n  { code: 'BJ-AL', countryCode: 'BJ', name: 'Alibori' },\n  { code: 'BJ-AK', countryCode: 'BJ', name: 'Atacora' },\n  { code: 'BJ-AQ', countryCode: 'BJ', name: 'Atlantique' },\n  { code: 'BJ-BO', countryCode: 'BJ', name: 'Borgou' },\n  { code: 'BJ-CO', countryCode: 'BJ', name: 'Collines' },\n  { code: 'BJ-KO', countryCode: 'BJ', name: 'Couffo' },\n  { code: 'BJ-DO', countryCode: 'BJ', name: 'Donga' },\n  { code: 'BJ-LI', countryCode: 'BJ', name: 'Littoral' },\n  { code: 'BJ-MO', countryCode: 'BJ', name: 'Mono' },\n  { code: 'BJ-OU', countryCode: 'BJ', name: 'Oueme' },\n  { code: 'BJ-PL', countryCode: 'BJ', name: 'Plateau' },\n  { code: 'BJ-ZO', countryCode: 'BJ', name: 'Zou' },\n  { code: 'BN-BE', countryCode: 'BN', name: 'Belait' },\n  { code: 'BN-BM', countryCode: 'BN', name: 'Brunei-Muara' },\n  { code: 'BN-TE', countryCode: 'BN', name: 'Temburong' },\n  { code: 'BN-TU', countryCode: 'BN', name: 'Tutong' },\n  { code: 'BO-H', countryCode: 'BO', name: 'Chuquisaca' },\n  { code: 'BO-C', countryCode: 'BO', name: 'Cochabamba' },\n  { code: 'BO-B', countryCode: 'BO', name: 'El Beni' },\n  { code: 'BO-L', countryCode: 'BO', name: 'La Paz' },\n  { code: 'BO-O', countryCode: 'BO', name: 'Oruro' },\n  { code: 'BO-N', countryCode: 'BO', name: 'Pando' },\n  { code: 'BO-P', countryCode: 'BO', name: 'Potosi' },\n  { code: 'BO-S', countryCode: 'BO', name: 'Santa Cruz' },\n  { code: 'BO-T', countryCode: 'BO', name: 'Tarija' },\n  { code: 'BQ-BO', countryCode: 'BQ', name: 'Bonaire' },\n  { code: 'BQ-SA', countryCode: 'BQ', name: 'Saba' },\n  { code: 'BQ-SE', countryCode: 'BQ', name: 'Sint Eustatius' },\n  { code: 'BR-AC', countryCode: 'BR', name: 'Acre' },\n  { code: 'BR-AL', countryCode: 'BR', name: 'Alagoas' },\n  { code: 'BR-AP', countryCode: 'BR', name: 'Amapa' },\n  { code: 'BR-AM', countryCode: 'BR', name: 'Amazonas' },\n  { code: 'BR-BA', countryCode: 'BR', name: 'Bahia' },\n  { code: 'BR-CE', countryCode: 'BR', name: 'Ceara' },\n  { code: 'BR-DF', countryCode: 'BR', name: 'Distrito Federal' },\n  { code: 'BR-ES', countryCode: 'BR', name: 'Espirito Santo' },\n  { code: 'BR-GO', countryCode: 'BR', name: 'Goias' },\n  { code: 'BR-MA', countryCode: 'BR', name: 'Maranhao' },\n  { code: 'BR-MT', countryCode: 'BR', name: 'Mato Grosso' },\n  { code: 'BR-MS', countryCode: 'BR', name: 'Mato Grosso do Sul' },\n  { code: 'BR-MG', countryCode: 'BR', name: 'Minas Gerais' },\n  { code: 'BR-PA', countryCode: 'BR', name: 'Para' },\n  { code: 'BR-PB', countryCode: 'BR', name: 'Paraiba' },\n  { code: 'BR-PR', countryCode: 'BR', name: 'Parana' },\n  { code: 'BR-PE', countryCode: 'BR', name: 'Pernambuco' },\n  { code: 'BR-PI', countryCode: 'BR', name: 'Piaui' },\n  { code: 'BR-RJ', countryCode: 'BR', name: 'Rio de Janeiro' },\n  { code: 'BR-RN', countryCode: 'BR', name: 'Rio Grande do Norte' },\n  { code: 'BR-RS', countryCode: 'BR', name: 'Rio Grande do Sul' },\n  { code: 'BR-RO', countryCode: 'BR', name: 'Rondonia' },\n  { code: 'BR-RR', countryCode: 'BR', name: 'Roraima' },\n  { code: 'BR-SC', countryCode: 'BR', name: 'Santa Catarina' },\n  { code: 'BR-SP', countryCode: 'BR', name: 'Sao Paulo' },\n  { code: 'BR-SE', countryCode: 'BR', name: 'Sergipe' },\n  { code: 'BR-TO', countryCode: 'BR', name: 'Tocantins' },\n  { code: 'BS-CS', countryCode: 'BS', name: 'Central Andros' },\n  { code: 'BS-FP', countryCode: 'BS', name: 'City of Freeport' },\n  { code: 'BS-EG', countryCode: 'BS', name: 'East Grand Bahama' },\n  { code: 'BS-HI', countryCode: 'BS', name: 'Harbour Island' },\n  { code: 'BS-HT', countryCode: 'BS', name: 'Hope Town' },\n  { code: 'BS-LI', countryCode: 'BS', name: 'Long Island' },\n  { code: 'BS-SE', countryCode: 'BS', name: 'South Eleuthera' },\n  { code: 'BT-12', countryCode: 'BT', name: 'Chhukha' },\n  { code: 'BT-22', countryCode: 'BT', name: 'Dagana' },\n  { code: 'BT-GA', countryCode: 'BT', name: 'Gasa' },\n  { code: 'BT-13', countryCode: 'BT', name: 'Haa' },\n  { code: 'BT-42', countryCode: 'BT', name: 'Monggar' },\n  { code: 'BT-11', countryCode: 'BT', name: 'Paro' },\n  { code: 'BT-23', countryCode: 'BT', name: 'Punakha' },\n  { code: 'BT-15', countryCode: 'BT', name: 'Thimphu' },\n  { code: 'BT-TY', countryCode: 'BT', name: 'Trashi Yangtse' },\n  { code: 'BT-32', countryCode: 'BT', name: 'Trongsa' },\n  { code: 'BT-34', countryCode: 'BT', name: 'Zhemgang' },\n  { code: 'BW-CE', countryCode: 'BW', name: 'Central' },\n  { code: 'BW-GH', countryCode: 'BW', name: 'Ghanzi' },\n  { code: 'BW-KG', countryCode: 'BW', name: 'Kgalagadi' },\n  { code: 'BW-KL', countryCode: 'BW', name: 'Kgatleng' },\n  { code: 'BW-KW', countryCode: 'BW', name: 'Kweneng' },\n  { code: 'BW-NE', countryCode: 'BW', name: 'North East' },\n  { code: 'BW-NW', countryCode: 'BW', name: 'North West' },\n  { code: 'BW-SE', countryCode: 'BW', name: 'South East' },\n  { code: 'BW-SO', countryCode: 'BW', name: 'Southern' },\n  { code: 'BY-BR', countryCode: 'BY', name: \"Brestskaya voblasts'\" },\n  { code: 'BY-HO', countryCode: 'BY', name: \"Homyel'skaya voblasts'\" },\n  { code: 'BY-HR', countryCode: 'BY', name: \"Hrodzenskaya voblasts'\" },\n  { code: 'BY-MA', countryCode: 'BY', name: \"Mahilyowskaya voblasts'\" },\n  { code: 'BY-MI', countryCode: 'BY', name: \"Minskaya voblasts'\" },\n  { code: 'BY-VI', countryCode: 'BY', name: \"Vitsyebskaya voblasts'\" },\n  { code: 'BZ-BZ', countryCode: 'BZ', name: 'Belize' },\n  { code: 'BZ-CY', countryCode: 'BZ', name: 'Cayo' },\n  { code: 'BZ-CZL', countryCode: 'BZ', name: 'Corozal' },\n  { code: 'BZ-OW', countryCode: 'BZ', name: 'Orange Walk' },\n  { code: 'BZ-SC', countryCode: 'BZ', name: 'Stann Creek' },\n  { code: 'BZ-TOL', countryCode: 'BZ', name: 'Toledo' },\n  { code: 'CA-AB', countryCode: 'CA', name: 'Alberta' },\n  { code: 'CA-BC', countryCode: 'CA', name: 'British Columbia' },\n  { code: 'CA-MB', countryCode: 'CA', name: 'Manitoba' },\n  { code: 'CA-NB', countryCode: 'CA', name: 'New Brunswick' },\n  { code: 'CA-NL', countryCode: 'CA', name: 'Newfoundland and Labrador' },\n  { code: 'CA-NT', countryCode: 'CA', name: 'Northwest Territories' },\n  { code: 'CA-NS', countryCode: 'CA', name: 'Nova Scotia' },\n  { code: 'CA-NU', countryCode: 'CA', name: 'Nunavut' },\n  { code: 'CA-ON', countryCode: 'CA', name: 'Ontario' },\n  { code: 'CA-PE', countryCode: 'CA', name: 'Prince Edward Island' },\n  { code: 'CA-QC', countryCode: 'CA', name: 'Quebec' },\n  { code: 'CA-SK', countryCode: 'CA', name: 'Saskatchewan' },\n  { code: 'CA-YT', countryCode: 'CA', name: 'Yukon' },\n  { code: 'CD-BU', countryCode: 'CD', name: 'Bas-Uele' },\n  { code: 'CD-EQ', countryCode: 'CD', name: 'Equateur' },\n  { code: 'CD-HK', countryCode: 'CD', name: 'Haut-Katanga' },\n  { code: 'CD-HL', countryCode: 'CD', name: 'Haut-Lomani' },\n  { code: 'CD-HU', countryCode: 'CD', name: 'Haut-Uele' },\n  { code: 'CD-IT', countryCode: 'CD', name: 'Ituri' },\n  { code: 'CD-KS', countryCode: 'CD', name: 'Kasai' },\n  { code: 'CD-KC', countryCode: 'CD', name: 'Kasai Central' },\n  { code: 'CD-KE', countryCode: 'CD', name: 'Kasai Oriental' },\n  { code: 'CD-KN', countryCode: 'CD', name: 'Kinshasa' },\n  { code: 'CD-BC', countryCode: 'CD', name: 'Kongo Central' },\n  { code: 'CD-KG', countryCode: 'CD', name: 'Kwango' },\n  { code: 'CD-KL', countryCode: 'CD', name: 'Kwilu' },\n  { code: 'CD-LO', countryCode: 'CD', name: 'Lomami' },\n  { code: 'CD-LU', countryCode: 'CD', name: 'Lualaba' },\n  { code: 'CD-MN', countryCode: 'CD', name: 'Mai-Ndombe' },\n  { code: 'CD-MA', countryCode: 'CD', name: 'Maniema' },\n  { code: 'CD-MO', countryCode: 'CD', name: 'Mongala' },\n  { code: 'CD-NK', countryCode: 'CD', name: 'Nord-Kivu' },\n  { code: 'CD-NU', countryCode: 'CD', name: 'Nord-Ubangi' },\n  { code: 'CD-SA', countryCode: 'CD', name: 'Sankuru' },\n  { code: 'CD-SK', countryCode: 'CD', name: 'Sud-Kivu' },\n  { code: 'CD-SU', countryCode: 'CD', name: 'Sud-Ubangi' },\n  { code: 'CD-TA', countryCode: 'CD', name: 'Tanganyika' },\n  { code: 'CD-TO', countryCode: 'CD', name: 'Tshopo' },\n  { code: 'CD-TU', countryCode: 'CD', name: 'Tshuapa' },\n  { code: 'CF-BB', countryCode: 'CF', name: 'Bamingui-Bangoran' },\n  { code: 'CF-BGF', countryCode: 'CF', name: 'Bangui' },\n  { code: 'CF-BK', countryCode: 'CF', name: 'Basse-Kotto' },\n  { code: 'CF-KB', countryCode: 'CF', name: 'Gribingui' },\n  { code: 'CF-HM', countryCode: 'CF', name: 'Haut-Mbomou' },\n  { code: 'CF-HK', countryCode: 'CF', name: 'Haute-Kotto' },\n  { code: 'CF-KG', countryCode: 'CF', name: 'Kemo-Gribingui' },\n  { code: 'CF-LB', countryCode: 'CF', name: 'Lobaye' },\n  { code: 'CF-HS', countryCode: 'CF', name: 'Mambere-Kadei' },\n  { code: 'CF-MB', countryCode: 'CF', name: 'Mbomou' },\n  { code: 'CF-NM', countryCode: 'CF', name: 'Nana-Mambere' },\n  { code: 'CF-MP', countryCode: 'CF', name: 'Ombella-Mpoko' },\n  { code: 'CF-UK', countryCode: 'CF', name: 'Ouaka' },\n  { code: 'CF-AC', countryCode: 'CF', name: 'Ouham' },\n  { code: 'CF-OP', countryCode: 'CF', name: 'Ouham-Pende' },\n  { code: 'CF-SE', countryCode: 'CF', name: 'Sangha' },\n  { code: 'CG-11', countryCode: 'CG', name: 'Bouenza' },\n  { code: 'CG-BZV', countryCode: 'CG', name: 'Brazzaville' },\n  { code: 'CG-8', countryCode: 'CG', name: 'Cuvette' },\n  { code: 'CG-15', countryCode: 'CG', name: 'Cuvette-Ouest' },\n  { code: 'CG-2', countryCode: 'CG', name: 'Lekoumou' },\n  { code: 'CG-7', countryCode: 'CG', name: 'Likouala' },\n  { code: 'CG-9', countryCode: 'CG', name: 'Niari' },\n  { code: 'CG-14', countryCode: 'CG', name: 'Plateaux' },\n  { code: 'CG-16', countryCode: 'CG', name: 'Pointe-Noire' },\n  { code: 'CG-12', countryCode: 'CG', name: 'Pool' },\n  { code: 'CG-13', countryCode: 'CG', name: 'Sangha' },\n  { code: 'CH-AG', countryCode: 'CH', name: 'Aargau' },\n  { code: 'CH-AR', countryCode: 'CH', name: 'Appenzell Ausserrhoden' },\n  { code: 'CH-AI', countryCode: 'CH', name: 'Appenzell Innerrhoden' },\n  { code: 'CH-BL', countryCode: 'CH', name: 'Basel-Landschaft' },\n  { code: 'CH-BS', countryCode: 'CH', name: 'Basel-Stadt' },\n  { code: 'CH-BE', countryCode: 'CH', name: 'Bern' },\n  { code: 'CH-FR', countryCode: 'CH', name: 'Fribourg' },\n  { code: 'CH-GE', countryCode: 'CH', name: 'Geneve' },\n  { code: 'CH-GL', countryCode: 'CH', name: 'Glarus' },\n  { code: 'CH-GR', countryCode: 'CH', name: 'Graubunden' },\n  { code: 'CH-JU', countryCode: 'CH', name: 'Jura' },\n  { code: 'CH-LU', countryCode: 'CH', name: 'Luzern' },\n  { code: 'CH-NE', countryCode: 'CH', name: 'Neuchatel' },\n  { code: 'CH-NW', countryCode: 'CH', name: 'Nidwalden' },\n  { code: 'CH-OW', countryCode: 'CH', name: 'Obwalden' },\n  { code: 'CH-SG', countryCode: 'CH', name: 'Sankt Gallen' },\n  { code: 'CH-SH', countryCode: 'CH', name: 'Schaffhausen' },\n  { code: 'CH-SZ', countryCode: 'CH', name: 'Schwyz' },\n  { code: 'CH-SO', countryCode: 'CH', name: 'Solothurn' },\n  { code: 'CH-TG', countryCode: 'CH', name: 'Thurgau' },\n  { code: 'CH-TI', countryCode: 'CH', name: 'Ticino' },\n  { code: 'CH-UR', countryCode: 'CH', name: 'Uri' },\n  { code: 'CH-VS', countryCode: 'CH', name: 'Valais' },\n  { code: 'CH-VD', countryCode: 'CH', name: 'Vaud' },\n  { code: 'CH-ZG', countryCode: 'CH', name: 'Zug' },\n  { code: 'CH-ZH', countryCode: 'CH', name: 'Zurich' },\n  { code: 'CI-AB', countryCode: 'CI', name: 'Abidjan' },\n  { code: 'CI-BS', countryCode: 'CI', name: 'Bas-Sassandra' },\n  { code: 'CI-CM', countryCode: 'CI', name: 'Comoe' },\n  { code: 'CI-DN', countryCode: 'CI', name: 'Denguele' },\n  { code: 'CI-GD', countryCode: 'CI', name: 'Goh-Djiboua' },\n  { code: 'CI-LC', countryCode: 'CI', name: 'Lacs' },\n  { code: 'CI-LG', countryCode: 'CI', name: 'Lagunes' },\n  { code: 'CI-MG', countryCode: 'CI', name: 'Montagnes' },\n  { code: 'CI-SM', countryCode: 'CI', name: 'Sassandra-Marahoue' },\n  { code: 'CI-SV', countryCode: 'CI', name: 'Savanes' },\n  { code: 'CI-VB', countryCode: 'CI', name: 'Vallee du Bandama' },\n  { code: 'CI-WR', countryCode: 'CI', name: 'Woroba' },\n  { code: 'CI-ZZ', countryCode: 'CI', name: 'Zanzan' },\n  {\n    code: 'CL-AI',\n    countryCode: 'CL',\n    name: 'Aisen del General Carlos Ibanez del Campo'\n  },\n  { code: 'CL-AN', countryCode: 'CL', name: 'Antofagasta' },\n  { code: 'CL-AP', countryCode: 'CL', name: 'Arica y Parinacota' },\n  { code: 'CL-AT', countryCode: 'CL', name: 'Atacama' },\n  { code: 'CL-BI', countryCode: 'CL', name: 'Biobio' },\n  { code: 'CL-CO', countryCode: 'CL', name: 'Coquimbo' },\n  { code: 'CL-AR', countryCode: 'CL', name: 'La Araucania' },\n  {\n    code: 'CL-LI',\n    countryCode: 'CL',\n    name: \"Libertador General Bernardo O'Higgins\"\n  },\n  { code: 'CL-LL', countryCode: 'CL', name: 'Los Lagos' },\n  { code: 'CL-LR', countryCode: 'CL', name: 'Los Rios' },\n  { code: 'CL-MA', countryCode: 'CL', name: 'Magallanes' },\n  { code: 'CL-ML', countryCode: 'CL', name: 'Maule' },\n  {\n    code: 'CL-RM',\n    countryCode: 'CL',\n    name: 'Region Metropolitana de Santiago'\n  },\n  { code: 'CL-TA', countryCode: 'CL', name: 'Tarapaca' },\n  { code: 'CL-VS', countryCode: 'CL', name: 'Valparaiso' },\n  { code: 'CM-AD', countryCode: 'CM', name: 'Adamaoua' },\n  { code: 'CM-CE', countryCode: 'CM', name: 'Centre' },\n  { code: 'CM-ES', countryCode: 'CM', name: 'Est' },\n  { code: 'CM-EN', countryCode: 'CM', name: 'Extreme-Nord' },\n  { code: 'CM-LT', countryCode: 'CM', name: 'Littoral' },\n  { code: 'CM-NO', countryCode: 'CM', name: 'Nord' },\n  { code: 'CM-NW', countryCode: 'CM', name: 'Nord-Ouest' },\n  { code: 'CM-OU', countryCode: 'CM', name: 'Ouest' },\n  { code: 'CM-SU', countryCode: 'CM', name: 'Sud' },\n  { code: 'CM-SW', countryCode: 'CM', name: 'Sud-Ouest' },\n  { code: 'CN-34', countryCode: 'CN', name: 'Anhui' },\n  { code: 'CN-11', countryCode: 'CN', name: 'Beijing' },\n  { code: 'CN-50', countryCode: 'CN', name: 'Chongqing' },\n  { code: 'CN-35', countryCode: 'CN', name: 'Fujian' },\n  { code: 'CN-62', countryCode: 'CN', name: 'Gansu' },\n  { code: 'CN-44', countryCode: 'CN', name: 'Guangdong' },\n  { code: 'CN-45', countryCode: 'CN', name: 'Guangxi' },\n  { code: 'CN-52', countryCode: 'CN', name: 'Guizhou' },\n  { code: 'CN-46', countryCode: 'CN', name: 'Hainan' },\n  { code: 'CN-13', countryCode: 'CN', name: 'Hebei' },\n  { code: 'CN-23', countryCode: 'CN', name: 'Heilongjiang' },\n  { code: 'CN-41', countryCode: 'CN', name: 'Henan' },\n  { code: 'CN-42', countryCode: 'CN', name: 'Hubei' },\n  { code: 'CN-43', countryCode: 'CN', name: 'Hunan' },\n  { code: 'CN-32', countryCode: 'CN', name: 'Jiangsu' },\n  { code: 'CN-36', countryCode: 'CN', name: 'Jiangxi' },\n  { code: 'CN-22', countryCode: 'CN', name: 'Jilin' },\n  { code: 'CN-21', countryCode: 'CN', name: 'Liaoning' },\n  { code: 'CN-15', countryCode: 'CN', name: 'Nei Mongol' },\n  { code: 'CN-64', countryCode: 'CN', name: 'Ningxia' },\n  { code: 'CN-63', countryCode: 'CN', name: 'Qinghai' },\n  { code: 'CN-61', countryCode: 'CN', name: 'Shaanxi' },\n  { code: 'CN-37', countryCode: 'CN', name: 'Shandong' },\n  { code: 'CN-31', countryCode: 'CN', name: 'Shanghai' },\n  { code: 'CN-14', countryCode: 'CN', name: 'Shanxi' },\n  { code: 'CN-51', countryCode: 'CN', name: 'Sichuan' },\n  { code: 'CN-12', countryCode: 'CN', name: 'Tianjin' },\n  { code: 'CN-65', countryCode: 'CN', name: 'Xinjiang' },\n  { code: 'CN-54', countryCode: 'CN', name: 'Xizang' },\n  { code: 'CN-53', countryCode: 'CN', name: 'Yunnan' },\n  { code: 'CN-33', countryCode: 'CN', name: 'Zhejiang' },\n  { code: 'CO-AMA', countryCode: 'CO', name: 'Amazonas' },\n  { code: 'CO-ANT', countryCode: 'CO', name: 'Antioquia' },\n  { code: 'CO-ARA', countryCode: 'CO', name: 'Arauca' },\n  { code: 'CO-ATL', countryCode: 'CO', name: 'Atlantico' },\n  { code: 'CO-BOL', countryCode: 'CO', name: 'Bolivar' },\n  { code: 'CO-BOY', countryCode: 'CO', name: 'Boyaca' },\n  { code: 'CO-CAL', countryCode: 'CO', name: 'Caldas' },\n  { code: 'CO-CAQ', countryCode: 'CO', name: 'Caqueta' },\n  { code: 'CO-CAS', countryCode: 'CO', name: 'Casanare' },\n  { code: 'CO-CAU', countryCode: 'CO', name: 'Cauca' },\n  { code: 'CO-CES', countryCode: 'CO', name: 'Cesar' },\n  { code: 'CO-CHO', countryCode: 'CO', name: 'Choco' },\n  { code: 'CO-COR', countryCode: 'CO', name: 'Cordoba' },\n  { code: 'CO-CUN', countryCode: 'CO', name: 'Cundinamarca' },\n  { code: 'CO-DC', countryCode: 'CO', name: 'Distrito Capital de Bogota' },\n  { code: 'CO-GUA', countryCode: 'CO', name: 'Guainia' },\n  { code: 'CO-GUV', countryCode: 'CO', name: 'Guaviare' },\n  { code: 'CO-HUI', countryCode: 'CO', name: 'Huila' },\n  { code: 'CO-LAG', countryCode: 'CO', name: 'La Guajira' },\n  { code: 'CO-MAG', countryCode: 'CO', name: 'Magdalena' },\n  { code: 'CO-MET', countryCode: 'CO', name: 'Meta' },\n  { code: 'CO-NAR', countryCode: 'CO', name: 'Narino' },\n  { code: 'CO-NSA', countryCode: 'CO', name: 'Norte de Santander' },\n  { code: 'CO-PUT', countryCode: 'CO', name: 'Putumayo' },\n  { code: 'CO-QUI', countryCode: 'CO', name: 'Quindio' },\n  { code: 'CO-RIS', countryCode: 'CO', name: 'Risaralda' },\n  {\n    code: 'CO-SAP',\n    countryCode: 'CO',\n    name: 'San Andres, Providencia y Santa Catalina'\n  },\n  { code: 'CO-SAN', countryCode: 'CO', name: 'Santander' },\n  { code: 'CO-SUC', countryCode: 'CO', name: 'Sucre' },\n  { code: 'CO-TOL', countryCode: 'CO', name: 'Tolima' },\n  { code: 'CO-VAC', countryCode: 'CO', name: 'Valle del Cauca' },\n  { code: 'CO-VAU', countryCode: 'CO', name: 'Vaupes' },\n  { code: 'CO-VID', countryCode: 'CO', name: 'Vichada' },\n  { code: 'CR-A', countryCode: 'CR', name: 'Alajuela' },\n  { code: 'CR-C', countryCode: 'CR', name: 'Cartago' },\n  { code: 'CR-G', countryCode: 'CR', name: 'Guanacaste' },\n  { code: 'CR-H', countryCode: 'CR', name: 'Heredia' },\n  { code: 'CR-L', countryCode: 'CR', name: 'Limon' },\n  { code: 'CR-P', countryCode: 'CR', name: 'Puntarenas' },\n  { code: 'CR-SJ', countryCode: 'CR', name: 'San Jose' },\n  { code: 'CU-15', countryCode: 'CU', name: 'Artemisa' },\n  { code: 'CU-09', countryCode: 'CU', name: 'Camaguey' },\n  { code: 'CU-08', countryCode: 'CU', name: 'Ciego de Avila' },\n  { code: 'CU-06', countryCode: 'CU', name: 'Cienfuegos' },\n  { code: 'CU-12', countryCode: 'CU', name: 'Granma' },\n  { code: 'CU-14', countryCode: 'CU', name: 'Guantanamo' },\n  { code: 'CU-11', countryCode: 'CU', name: 'Holguin' },\n  { code: 'CU-99', countryCode: 'CU', name: 'Isla de la Juventud' },\n  { code: 'CU-03', countryCode: 'CU', name: 'La Habana' },\n  { code: 'CU-10', countryCode: 'CU', name: 'Las Tunas' },\n  { code: 'CU-04', countryCode: 'CU', name: 'Matanzas' },\n  { code: 'CU-16', countryCode: 'CU', name: 'Mayabeque' },\n  { code: 'CU-01', countryCode: 'CU', name: 'Pinar del Rio' },\n  { code: 'CU-07', countryCode: 'CU', name: 'Sancti Spiritus' },\n  { code: 'CU-13', countryCode: 'CU', name: 'Santiago de Cuba' },\n  { code: 'CU-05', countryCode: 'CU', name: 'Villa Clara' },\n  { code: 'CV-BV', countryCode: 'CV', name: 'Boa Vista' },\n  { code: 'CV-BR', countryCode: 'CV', name: 'Brava' },\n  { code: 'CV-MA', countryCode: 'CV', name: 'Maio' },\n  { code: 'CV-MO', countryCode: 'CV', name: 'Mosteiros' },\n  { code: 'CV-PA', countryCode: 'CV', name: 'Paul' },\n  { code: 'CV-PN', countryCode: 'CV', name: 'Porto Novo' },\n  { code: 'CV-PR', countryCode: 'CV', name: 'Praia' },\n  { code: 'CV-RB', countryCode: 'CV', name: 'Ribeira Brava' },\n  { code: 'CV-RG', countryCode: 'CV', name: 'Ribeira Grande' },\n  { code: 'CV-RS', countryCode: 'CV', name: 'Ribeira Grande de Santiago' },\n  { code: 'CV-SL', countryCode: 'CV', name: 'Sal' },\n  { code: 'CV-CA', countryCode: 'CV', name: 'Santa Catarina' },\n  { code: 'CV-CF', countryCode: 'CV', name: 'Santa Catarina do Fogo' },\n  { code: 'CV-CR', countryCode: 'CV', name: 'Santa Cruz' },\n  { code: 'CV-SD', countryCode: 'CV', name: 'Sao Domingos' },\n  { code: 'CV-SF', countryCode: 'CV', name: 'Sao Filipe' },\n  { code: 'CV-SM', countryCode: 'CV', name: 'Sao Miguel' },\n  { code: 'CV-SS', countryCode: 'CV', name: 'Sao Salvador do Mundo' },\n  { code: 'CV-SV', countryCode: 'CV', name: 'Sao Vicente' },\n  { code: 'CV-TA', countryCode: 'CV', name: 'Tarrafal' },\n  { code: 'CV-TS', countryCode: 'CV', name: 'Tarrafal de Sao Nicolau' },\n  { code: 'CY-04', countryCode: 'CY', name: 'Ammochostos' },\n  { code: 'CY-06', countryCode: 'CY', name: 'Keryneia' },\n  { code: 'CY-03', countryCode: 'CY', name: 'Larnaka' },\n  { code: 'CY-01', countryCode: 'CY', name: 'Lefkosia' },\n  { code: 'CY-02', countryCode: 'CY', name: 'Lemesos' },\n  { code: 'CY-05', countryCode: 'CY', name: 'Pafos' },\n  { code: 'CZ-JC', countryCode: 'CZ', name: 'Jihocesky kraj' },\n  { code: 'CZ-JM', countryCode: 'CZ', name: 'Jihomoravsky kraj' },\n  { code: 'CZ-KA', countryCode: 'CZ', name: 'Karlovarsky kraj' },\n  { code: 'CZ-63', countryCode: 'CZ', name: 'Kraj Vysocina' },\n  { code: 'CZ-KR', countryCode: 'CZ', name: 'Kralovehradecky kraj' },\n  { code: 'CZ-LI', countryCode: 'CZ', name: 'Liberecky kraj' },\n  { code: 'CZ-MO', countryCode: 'CZ', name: 'Moravskoslezsky kraj' },\n  { code: 'CZ-OL', countryCode: 'CZ', name: 'Olomoucky kraj' },\n  { code: 'CZ-PA', countryCode: 'CZ', name: 'Pardubicky kraj' },\n  { code: 'CZ-PL', countryCode: 'CZ', name: 'Plzensky kraj' },\n  { code: 'CZ-10', countryCode: 'CZ', name: 'Praha, Hlavni mesto' },\n  { code: 'CZ-ST', countryCode: 'CZ', name: 'Stredocesky kraj' },\n  { code: 'CZ-US', countryCode: 'CZ', name: 'Ustecky kraj' },\n  { code: 'CZ-ZL', countryCode: 'CZ', name: 'Zlinsky kraj' },\n  { code: 'DE-BW', countryCode: 'DE', name: 'Baden-Wurttemberg' },\n  { code: 'DE-BY', countryCode: 'DE', name: 'Bayern' },\n  { code: 'DE-BE', countryCode: 'DE', name: 'Berlin' },\n  { code: 'DE-BB', countryCode: 'DE', name: 'Brandenburg' },\n  { code: 'DE-HB', countryCode: 'DE', name: 'Bremen' },\n  { code: 'DE-HH', countryCode: 'DE', name: 'Hamburg' },\n  { code: 'DE-HE', countryCode: 'DE', name: 'Hessen' },\n  { code: 'DE-MV', countryCode: 'DE', name: 'Mecklenburg-Vorpommern' },\n  { code: 'DE-NI', countryCode: 'DE', name: 'Niedersachsen' },\n  { code: 'DE-NW', countryCode: 'DE', name: 'Nordrhein-Westfalen' },\n  { code: 'DE-RP', countryCode: 'DE', name: 'Rheinland-Pfalz' },\n  { code: 'DE-SL', countryCode: 'DE', name: 'Saarland' },\n  { code: 'DE-SN', countryCode: 'DE', name: 'Sachsen' },\n  { code: 'DE-ST', countryCode: 'DE', name: 'Sachsen-Anhalt' },\n  { code: 'DE-SH', countryCode: 'DE', name: 'Schleswig-Holstein' },\n  { code: 'DE-TH', countryCode: 'DE', name: 'Thuringen' },\n  { code: 'DJ-AS', countryCode: 'DJ', name: 'Ali Sabieh' },\n  { code: 'DJ-AR', countryCode: 'DJ', name: 'Arta' },\n  { code: 'DJ-DI', countryCode: 'DJ', name: 'Dikhil' },\n  { code: 'DJ-DJ', countryCode: 'DJ', name: 'Djibouti' },\n  { code: 'DJ-OB', countryCode: 'DJ', name: 'Obock' },\n  { code: 'DJ-TA', countryCode: 'DJ', name: 'Tadjourah' },\n  { code: 'DK-84', countryCode: 'DK', name: 'Hovedstaden' },\n  { code: 'DK-82', countryCode: 'DK', name: 'Midtjylland' },\n  { code: 'DK-81', countryCode: 'DK', name: 'Nordjylland' },\n  { code: 'DK-85', countryCode: 'DK', name: 'Sjelland' },\n  { code: 'DK-83', countryCode: 'DK', name: 'Syddanmark' },\n  { code: 'DM-02', countryCode: 'DM', name: 'Saint Andrew' },\n  { code: 'DM-03', countryCode: 'DM', name: 'Saint David' },\n  { code: 'DM-04', countryCode: 'DM', name: 'Saint George' },\n  { code: 'DM-05', countryCode: 'DM', name: 'Saint John' },\n  { code: 'DM-06', countryCode: 'DM', name: 'Saint Joseph' },\n  { code: 'DM-07', countryCode: 'DM', name: 'Saint Luke' },\n  { code: 'DM-08', countryCode: 'DM', name: 'Saint Mark' },\n  { code: 'DM-09', countryCode: 'DM', name: 'Saint Patrick' },\n  { code: 'DM-10', countryCode: 'DM', name: 'Saint Paul' },\n  { code: 'DO-02', countryCode: 'DO', name: 'Azua' },\n  { code: 'DO-03', countryCode: 'DO', name: 'Baoruco' },\n  { code: 'DO-04', countryCode: 'DO', name: 'Barahona' },\n  { code: 'DO-05', countryCode: 'DO', name: 'Dajabon' },\n  {\n    code: 'DO-01',\n    countryCode: 'DO',\n    name: 'Distrito Nacional (Santo Domingo)'\n  },\n  { code: 'DO-06', countryCode: 'DO', name: 'Duarte' },\n  { code: 'DO-08', countryCode: 'DO', name: 'El Seibo' },\n  { code: 'DO-07', countryCode: 'DO', name: 'Elias Pina' },\n  { code: 'DO-09', countryCode: 'DO', name: 'Espaillat' },\n  { code: 'DO-30', countryCode: 'DO', name: 'Hato Mayor' },\n  { code: 'DO-19', countryCode: 'DO', name: 'Hermanas Mirabal' },\n  { code: 'DO-10', countryCode: 'DO', name: 'Independencia' },\n  { code: 'DO-11', countryCode: 'DO', name: 'La Altagracia' },\n  { code: 'DO-12', countryCode: 'DO', name: 'La Romana' },\n  { code: 'DO-13', countryCode: 'DO', name: 'La Vega' },\n  { code: 'DO-14', countryCode: 'DO', name: 'Maria Trinidad Sanchez' },\n  { code: 'DO-28', countryCode: 'DO', name: 'Monsenor Nouel' },\n  { code: 'DO-15', countryCode: 'DO', name: 'Monte Cristi' },\n  { code: 'DO-29', countryCode: 'DO', name: 'Monte Plata' },\n  { code: 'DO-16', countryCode: 'DO', name: 'Pedernales' },\n  { code: 'DO-17', countryCode: 'DO', name: 'Peravia' },\n  { code: 'DO-18', countryCode: 'DO', name: 'Puerto Plata' },\n  { code: 'DO-20', countryCode: 'DO', name: 'Samana' },\n  { code: 'DO-21', countryCode: 'DO', name: 'San Cristobal' },\n  { code: 'DO-22', countryCode: 'DO', name: 'San Juan' },\n  { code: 'DO-23', countryCode: 'DO', name: 'San Pedro de Macoris' },\n  { code: 'DO-24', countryCode: 'DO', name: 'Sanchez Ramirez' },\n  { code: 'DO-25', countryCode: 'DO', name: 'Santiago' },\n  { code: 'DO-26', countryCode: 'DO', name: 'Santiago Rodriguez' },\n  { code: 'DO-27', countryCode: 'DO', name: 'Valverde' },\n  { code: 'DZ-01', countryCode: 'DZ', name: 'Adrar' },\n  { code: 'DZ-44', countryCode: 'DZ', name: 'Ain Defla' },\n  { code: 'DZ-46', countryCode: 'DZ', name: 'Ain Temouchent' },\n  { code: 'DZ-16', countryCode: 'DZ', name: 'Alger' },\n  { code: 'DZ-23', countryCode: 'DZ', name: 'Annaba' },\n  { code: 'DZ-05', countryCode: 'DZ', name: 'Batna' },\n  { code: 'DZ-08', countryCode: 'DZ', name: 'Bechar' },\n  { code: 'DZ-06', countryCode: 'DZ', name: 'Bejaia' },\n  { code: 'DZ-07', countryCode: 'DZ', name: 'Biskra' },\n  { code: 'DZ-09', countryCode: 'DZ', name: 'Blida' },\n  { code: 'DZ-34', countryCode: 'DZ', name: 'Bordj Bou Arreridj' },\n  { code: 'DZ-10', countryCode: 'DZ', name: 'Bouira' },\n  { code: 'DZ-35', countryCode: 'DZ', name: 'Boumerdes' },\n  { code: 'DZ-02', countryCode: 'DZ', name: 'Chlef' },\n  { code: 'DZ-25', countryCode: 'DZ', name: 'Constantine' },\n  { code: 'DZ-17', countryCode: 'DZ', name: 'Djelfa' },\n  { code: 'DZ-32', countryCode: 'DZ', name: 'El Bayadh' },\n  { code: 'DZ-39', countryCode: 'DZ', name: 'El Oued' },\n  { code: 'DZ-36', countryCode: 'DZ', name: 'El Tarf' },\n  { code: 'DZ-47', countryCode: 'DZ', name: 'Ghardaia' },\n  { code: 'DZ-24', countryCode: 'DZ', name: 'Guelma' },\n  { code: 'DZ-33', countryCode: 'DZ', name: 'Illizi' },\n  { code: 'DZ-40', countryCode: 'DZ', name: 'Khenchela' },\n  { code: 'DZ-03', countryCode: 'DZ', name: 'Laghouat' },\n  { code: 'DZ-28', countryCode: 'DZ', name: \"M'sila\" },\n  { code: 'DZ-29', countryCode: 'DZ', name: 'Mascara' },\n  { code: 'DZ-26', countryCode: 'DZ', name: 'Medea' },\n  { code: 'DZ-43', countryCode: 'DZ', name: 'Mila' },\n  { code: 'DZ-27', countryCode: 'DZ', name: 'Mostaganem' },\n  { code: 'DZ-45', countryCode: 'DZ', name: 'Naama' },\n  { code: 'DZ-31', countryCode: 'DZ', name: 'Oran' },\n  { code: 'DZ-30', countryCode: 'DZ', name: 'Ouargla' },\n  { code: 'DZ-04', countryCode: 'DZ', name: 'Oum el Bouaghi' },\n  { code: 'DZ-48', countryCode: 'DZ', name: 'Relizane' },\n  { code: 'DZ-20', countryCode: 'DZ', name: 'Saida' },\n  { code: 'DZ-19', countryCode: 'DZ', name: 'Setif' },\n  { code: 'DZ-22', countryCode: 'DZ', name: 'Sidi Bel Abbes' },\n  { code: 'DZ-21', countryCode: 'DZ', name: 'Skikda' },\n  { code: 'DZ-41', countryCode: 'DZ', name: 'Souk Ahras' },\n  { code: 'DZ-11', countryCode: 'DZ', name: 'Tamanrasset' },\n  { code: 'DZ-12', countryCode: 'DZ', name: 'Tebessa' },\n  { code: 'DZ-14', countryCode: 'DZ', name: 'Tiaret' },\n  { code: 'DZ-37', countryCode: 'DZ', name: 'Tindouf' },\n  { code: 'DZ-42', countryCode: 'DZ', name: 'Tipaza' },\n  { code: 'DZ-38', countryCode: 'DZ', name: 'Tissemsilt' },\n  { code: 'DZ-15', countryCode: 'DZ', name: 'Tizi Ouzou' },\n  { code: 'DZ-13', countryCode: 'DZ', name: 'Tlemcen' },\n  { code: 'EC-A', countryCode: 'EC', name: 'Azuay' },\n  { code: 'EC-B', countryCode: 'EC', name: 'Bolivar' },\n  { code: 'EC-F', countryCode: 'EC', name: 'Canar' },\n  { code: 'EC-C', countryCode: 'EC', name: 'Carchi' },\n  { code: 'EC-H', countryCode: 'EC', name: 'Chimborazo' },\n  { code: 'EC-X', countryCode: 'EC', name: 'Cotopaxi' },\n  { code: 'EC-O', countryCode: 'EC', name: 'El Oro' },\n  { code: 'EC-E', countryCode: 'EC', name: 'Esmeraldas' },\n  { code: 'EC-W', countryCode: 'EC', name: 'Galapagos' },\n  { code: 'EC-G', countryCode: 'EC', name: 'Guayas' },\n  { code: 'EC-I', countryCode: 'EC', name: 'Imbabura' },\n  { code: 'EC-L', countryCode: 'EC', name: 'Loja' },\n  { code: 'EC-R', countryCode: 'EC', name: 'Los Rios' },\n  { code: 'EC-M', countryCode: 'EC', name: 'Manabi' },\n  { code: 'EC-S', countryCode: 'EC', name: 'Morona-Santiago' },\n  { code: 'EC-N', countryCode: 'EC', name: 'Napo' },\n  { code: 'EC-D', countryCode: 'EC', name: 'Orellana' },\n  { code: 'EC-Y', countryCode: 'EC', name: 'Pastaza' },\n  { code: 'EC-P', countryCode: 'EC', name: 'Pichincha' },\n  { code: 'EC-SE', countryCode: 'EC', name: 'Santa Elena' },\n  { code: 'EC-U', countryCode: 'EC', name: 'Sucumbios' },\n  { code: 'EC-T', countryCode: 'EC', name: 'Tungurahua' },\n  { code: 'EC-Z', countryCode: 'EC', name: 'Zamora-Chinchipe' },\n  { code: 'EE-37', countryCode: 'EE', name: 'Harjumaa' },\n  { code: 'EE-39', countryCode: 'EE', name: 'Hiiumaa' },\n  { code: 'EE-44', countryCode: 'EE', name: 'Ida-Virumaa' },\n  { code: 'EE-51', countryCode: 'EE', name: 'Jarvamaa' },\n  { code: 'EE-49', countryCode: 'EE', name: 'Jogevamaa' },\n  { code: 'EE-59', countryCode: 'EE', name: 'Laane-Virumaa' },\n  { code: 'EE-57', countryCode: 'EE', name: 'Laanemaa' },\n  { code: 'EE-67', countryCode: 'EE', name: 'Parnumaa' },\n  { code: 'EE-65', countryCode: 'EE', name: 'Polvamaa' },\n  { code: 'EE-70', countryCode: 'EE', name: 'Raplamaa' },\n  { code: 'EE-74', countryCode: 'EE', name: 'Saaremaa' },\n  { code: 'EE-78', countryCode: 'EE', name: 'Tartumaa' },\n  { code: 'EE-82', countryCode: 'EE', name: 'Valgamaa' },\n  { code: 'EE-84', countryCode: 'EE', name: 'Viljandimaa' },\n  { code: 'EE-86', countryCode: 'EE', name: 'Vorumaa' },\n  { code: 'EG-DK', countryCode: 'EG', name: 'Ad Daqahliyah' },\n  { code: 'EG-BA', countryCode: 'EG', name: 'Al Bahr al Ahmar' },\n  { code: 'EG-BH', countryCode: 'EG', name: 'Al Buhayrah' },\n  { code: 'EG-FYM', countryCode: 'EG', name: 'Al Fayyum' },\n  { code: 'EG-GH', countryCode: 'EG', name: 'Al Gharbiyah' },\n  { code: 'EG-ALX', countryCode: 'EG', name: 'Al Iskandariyah' },\n  { code: 'EG-IS', countryCode: 'EG', name: \"Al Isma'iliyah\" },\n  { code: 'EG-GZ', countryCode: 'EG', name: 'Al Jizah' },\n  { code: 'EG-MNF', countryCode: 'EG', name: 'Al Minufiyah' },\n  { code: 'EG-MN', countryCode: 'EG', name: 'Al Minya' },\n  { code: 'EG-C', countryCode: 'EG', name: 'Al Qahirah' },\n  { code: 'EG-KB', countryCode: 'EG', name: 'Al Qalyubiyah' },\n  { code: 'EG-LX', countryCode: 'EG', name: 'Al Uqsur' },\n  { code: 'EG-WAD', countryCode: 'EG', name: 'Al Wadi al Jadid' },\n  { code: 'EG-SUZ', countryCode: 'EG', name: 'As Suways' },\n  { code: 'EG-SHR', countryCode: 'EG', name: 'Ash Sharqiyah' },\n  { code: 'EG-ASN', countryCode: 'EG', name: 'Aswan' },\n  { code: 'EG-AST', countryCode: 'EG', name: 'Asyut' },\n  { code: 'EG-BNS', countryCode: 'EG', name: 'Bani Suwayf' },\n  { code: 'EG-PTS', countryCode: 'EG', name: \"Bur Sa'id\" },\n  { code: 'EG-DT', countryCode: 'EG', name: 'Dumyat' },\n  { code: 'EG-JS', countryCode: 'EG', name: \"Janub Sina'\" },\n  { code: 'EG-KFS', countryCode: 'EG', name: 'Kafr ash Shaykh' },\n  { code: 'EG-MT', countryCode: 'EG', name: 'Matruh' },\n  { code: 'EG-KN', countryCode: 'EG', name: 'Qina' },\n  { code: 'EG-SIN', countryCode: 'EG', name: \"Shamal Sina'\" },\n  { code: 'EG-SHG', countryCode: 'EG', name: 'Suhaj' },\n  { code: 'ER-MA', countryCode: 'ER', name: 'Al Awsat' },\n  { code: 'ER-DU', countryCode: 'ER', name: 'Al Janubi' },\n  { code: 'ER-AN', countryCode: 'ER', name: 'Ansaba' },\n  { code: 'ER-DK', countryCode: 'ER', name: 'Janubi al Bahri al Ahmar' },\n  { code: 'ER-GB', countryCode: 'ER', name: 'Qash-Barkah' },\n  { code: 'ER-SK', countryCode: 'ER', name: 'Shimali al Bahri al Ahmar' },\n  { code: 'ES-AN', countryCode: 'ES', name: 'Andalucia' },\n  { code: 'ES-AR', countryCode: 'ES', name: 'Aragon' },\n  { code: 'ES-AS', countryCode: 'ES', name: 'Asturias, Principado de' },\n  { code: 'ES-CN', countryCode: 'ES', name: 'Canarias' },\n  { code: 'ES-CB', countryCode: 'ES', name: 'Cantabria' },\n  { code: 'ES-CL', countryCode: 'ES', name: 'Castilla y Leon' },\n  { code: 'ES-CM', countryCode: 'ES', name: 'Castilla-La Mancha' },\n  { code: 'ES-CT', countryCode: 'ES', name: 'Catalunya' },\n  { code: 'ES-CE', countryCode: 'ES', name: 'Ceuta' },\n  { code: 'ES-EX', countryCode: 'ES', name: 'Extremadura' },\n  { code: 'ES-GA', countryCode: 'ES', name: 'Galicia' },\n  { code: 'ES-IB', countryCode: 'ES', name: 'Illes Balears' },\n  { code: 'ES-RI', countryCode: 'ES', name: 'La Rioja' },\n  { code: 'ES-MD', countryCode: 'ES', name: 'Madrid, Comunidad de' },\n  { code: 'ES-ML', countryCode: 'ES', name: 'Melilla' },\n  { code: 'ES-MC', countryCode: 'ES', name: 'Murcia, Region de' },\n  { code: 'ES-NC', countryCode: 'ES', name: 'Navarra, Comunidad Foral de' },\n  { code: 'ES-PV', countryCode: 'ES', name: 'Pais Vasco' },\n  { code: 'ES-VC', countryCode: 'ES', name: 'Valenciana, Comunidad' },\n  { code: 'ET-AA', countryCode: 'ET', name: 'Adis Abeba' },\n  { code: 'ET-AF', countryCode: 'ET', name: 'Afar' },\n  { code: 'ET-AM', countryCode: 'ET', name: 'Amara' },\n  { code: 'ET-BE', countryCode: 'ET', name: 'Binshangul Gumuz' },\n  { code: 'ET-DD', countryCode: 'ET', name: 'Dire Dawa' },\n  { code: 'ET-GA', countryCode: 'ET', name: 'Gambela Hizboch' },\n  { code: 'ET-HA', countryCode: 'ET', name: 'Hareri Hizb' },\n  { code: 'ET-OR', countryCode: 'ET', name: 'Oromiya' },\n  { code: 'ET-SO', countryCode: 'ET', name: 'Sumale' },\n  { code: 'ET-TI', countryCode: 'ET', name: 'Tigray' },\n  {\n    code: 'ET-SN',\n    countryCode: 'ET',\n    name: 'YeDebub Biheroch Bihereseboch na Hizboch'\n  },\n  { code: 'FI-02', countryCode: 'FI', name: 'Etela-Karjala' },\n  { code: 'FI-03', countryCode: 'FI', name: 'Etela-Pohjanmaa' },\n  { code: 'FI-04', countryCode: 'FI', name: 'Etela-Savo' },\n  { code: 'FI-05', countryCode: 'FI', name: 'Kainuu' },\n  { code: 'FI-06', countryCode: 'FI', name: 'Kanta-Hame' },\n  { code: 'FI-07', countryCode: 'FI', name: 'Keski-Pohjanmaa' },\n  { code: 'FI-08', countryCode: 'FI', name: 'Keski-Suomi' },\n  { code: 'FI-09', countryCode: 'FI', name: 'Kymenlaakso' },\n  { code: 'FI-10', countryCode: 'FI', name: 'Lappi' },\n  { code: 'FI-16', countryCode: 'FI', name: 'Paijat-Hame' },\n  { code: 'FI-11', countryCode: 'FI', name: 'Pirkanmaa' },\n  { code: 'FI-12', countryCode: 'FI', name: 'Pohjanmaa' },\n  { code: 'FI-13', countryCode: 'FI', name: 'Pohjois-Karjala' },\n  { code: 'FI-14', countryCode: 'FI', name: 'Pohjois-Pohjanmaa' },\n  { code: 'FI-15', countryCode: 'FI', name: 'Pohjois-Savo' },\n  { code: 'FI-17', countryCode: 'FI', name: 'Satakunta' },\n  { code: 'FI-18', countryCode: 'FI', name: 'Uusimaa' },\n  { code: 'FI-19', countryCode: 'FI', name: 'Varsinais-Suomi' },\n  { code: 'FJ-C', countryCode: 'FJ', name: 'Central' },\n  { code: 'FJ-N', countryCode: 'FJ', name: 'Northern' },\n  { code: 'FJ-W', countryCode: 'FJ', name: 'Western' },\n  { code: 'FM-TRK', countryCode: 'FM', name: 'Chuuk' },\n  { code: 'FM-KSA', countryCode: 'FM', name: 'Kosrae' },\n  { code: 'FM-PNI', countryCode: 'FM', name: 'Pohnpei' },\n  { code: 'FM-YAP', countryCode: 'FM', name: 'Yap' },\n  { code: 'FR-ARA', countryCode: 'FR', name: 'Auvergne-Rhone-Alpes' },\n  { code: 'FR-BFC', countryCode: 'FR', name: 'Bourgogne-Franche-Comte' },\n  { code: 'FR-E', countryCode: 'FR', name: 'Bretagne' },\n  { code: 'FR-CVL', countryCode: 'FR', name: 'Centre-Val de Loire' },\n  { code: 'FR-H', countryCode: 'FR', name: 'Corse' },\n  { code: 'FR-GES', countryCode: 'FR', name: 'Grand-Est' },\n  { code: 'FR-HDF', countryCode: 'FR', name: 'Hauts-de-France' },\n  { code: 'FR-J', countryCode: 'FR', name: 'Ile-de-France' },\n  { code: 'FR-NOR', countryCode: 'FR', name: 'Normandie' },\n  { code: 'FR-NAQ', countryCode: 'FR', name: 'Nouvelle-Aquitaine' },\n  { code: 'FR-OCC', countryCode: 'FR', name: 'Occitanie' },\n  { code: 'FR-R', countryCode: 'FR', name: 'Pays-de-la-Loire' },\n  { code: 'FR-PAC', countryCode: 'FR', name: \"Provence-Alpes-Cote d'Azur\" },\n  { code: 'GA-1', countryCode: 'GA', name: 'Estuaire' },\n  { code: 'GA-2', countryCode: 'GA', name: 'Haut-Ogooue' },\n  { code: 'GA-3', countryCode: 'GA', name: 'Moyen-Ogooue' },\n  { code: 'GA-4', countryCode: 'GA', name: 'Ngounie' },\n  { code: 'GA-5', countryCode: 'GA', name: 'Nyanga' },\n  { code: 'GA-6', countryCode: 'GA', name: 'Ogooue-Ivindo' },\n  { code: 'GA-7', countryCode: 'GA', name: 'Ogooue-Lolo' },\n  { code: 'GA-8', countryCode: 'GA', name: 'Ogooue-Maritime' },\n  { code: 'GA-9', countryCode: 'GA', name: 'Woleu-Ntem' },\n  { code: 'GB-ENG', countryCode: 'GB', name: 'England' },\n  { code: 'GB-NIR', countryCode: 'GB', name: 'Northern Ireland' },\n  { code: 'GB-SCT', countryCode: 'GB', name: 'Scotland' },\n  { code: 'GB-WLS', countryCode: 'GB', name: 'Wales' },\n  { code: 'GD-01', countryCode: 'GD', name: 'Saint Andrew' },\n  { code: 'GD-02', countryCode: 'GD', name: 'Saint David' },\n  { code: 'GD-03', countryCode: 'GD', name: 'Saint George' },\n  { code: 'GD-04', countryCode: 'GD', name: 'Saint John' },\n  { code: 'GD-05', countryCode: 'GD', name: 'Saint Mark' },\n  { code: 'GD-06', countryCode: 'GD', name: 'Saint Patrick' },\n  { code: 'GE-AB', countryCode: 'GE', name: 'Abkhazia' },\n  { code: 'GE-AJ', countryCode: 'GE', name: 'Ajaria' },\n  { code: 'GE-GU', countryCode: 'GE', name: 'Guria' },\n  { code: 'GE-IM', countryCode: 'GE', name: 'Imereti' },\n  { code: 'GE-KA', countryCode: 'GE', name: \"K'akheti\" },\n  { code: 'GE-KK', countryCode: 'GE', name: 'Kvemo Kartli' },\n  { code: 'GE-MM', countryCode: 'GE', name: 'Mtskheta-Mtianeti' },\n  { code: 'GE-RL', countryCode: 'GE', name: \"Rach'a-Lechkhumi-Kvemo Svaneti\" },\n  { code: 'GE-SZ', countryCode: 'GE', name: 'Samegrelo-Zemo Svaneti' },\n  { code: 'GE-SJ', countryCode: 'GE', name: 'Samtskhe-Javakheti' },\n  { code: 'GE-SK', countryCode: 'GE', name: 'Shida Kartli' },\n  { code: 'GE-TB', countryCode: 'GE', name: 'Tbilisi' },\n  { code: 'GH-AH', countryCode: 'GH', name: 'Ashanti' },\n  { code: 'GH-BA', countryCode: 'GH', name: 'Brong-Ahafo' },\n  { code: 'GH-CP', countryCode: 'GH', name: 'Central' },\n  { code: 'GH-EP', countryCode: 'GH', name: 'Eastern' },\n  { code: 'GH-AA', countryCode: 'GH', name: 'Greater Accra' },\n  { code: 'GH-NP', countryCode: 'GH', name: 'Northern' },\n  { code: 'GH-UE', countryCode: 'GH', name: 'Upper East' },\n  { code: 'GH-UW', countryCode: 'GH', name: 'Upper West' },\n  { code: 'GH-TV', countryCode: 'GH', name: 'Volta' },\n  { code: 'GH-WP', countryCode: 'GH', name: 'Western' },\n  { code: 'GL-KU', countryCode: 'GL', name: 'Kommune Kujalleq' },\n  { code: 'GL-SM', countryCode: 'GL', name: 'Kommuneqarfik Sermersooq' },\n  { code: 'GL-QA', countryCode: 'GL', name: 'Qaasuitsup Kommunia' },\n  { code: 'GL-QE', countryCode: 'GL', name: 'Qeqqata Kommunia' },\n  { code: 'GM-B', countryCode: 'GM', name: 'Banjul' },\n  { code: 'GM-M', countryCode: 'GM', name: 'Central River' },\n  { code: 'GM-L', countryCode: 'GM', name: 'Lower River' },\n  { code: 'GM-N', countryCode: 'GM', name: 'North Bank' },\n  { code: 'GM-U', countryCode: 'GM', name: 'Upper River' },\n  { code: 'GM-W', countryCode: 'GM', name: 'Western' },\n  { code: 'GN-BE', countryCode: 'GN', name: 'Beyla' },\n  { code: 'GN-BF', countryCode: 'GN', name: 'Boffa' },\n  { code: 'GN-B', countryCode: 'GN', name: 'Boke' },\n  { code: 'GN-C', countryCode: 'GN', name: 'Conakry' },\n  { code: 'GN-CO', countryCode: 'GN', name: 'Coyah' },\n  { code: 'GN-DB', countryCode: 'GN', name: 'Dabola' },\n  { code: 'GN-DL', countryCode: 'GN', name: 'Dalaba' },\n  { code: 'GN-DI', countryCode: 'GN', name: 'Dinguiraye' },\n  { code: 'GN-DU', countryCode: 'GN', name: 'Dubreka' },\n  { code: 'GN-F', countryCode: 'GN', name: 'Faranah' },\n  { code: 'GN-FO', countryCode: 'GN', name: 'Forecariah' },\n  { code: 'GN-FR', countryCode: 'GN', name: 'Fria' },\n  { code: 'GN-GA', countryCode: 'GN', name: 'Gaoual' },\n  { code: 'GN-GU', countryCode: 'GN', name: 'Guekedou' },\n  { code: 'GN-K', countryCode: 'GN', name: 'Kankan' },\n  { code: 'GN-KE', countryCode: 'GN', name: 'Kerouane' },\n  { code: 'GN-D', countryCode: 'GN', name: 'Kindia' },\n  { code: 'GN-KS', countryCode: 'GN', name: 'Kissidougou' },\n  { code: 'GN-KB', countryCode: 'GN', name: 'Koubia' },\n  { code: 'GN-KN', countryCode: 'GN', name: 'Koundara' },\n  { code: 'GN-KO', countryCode: 'GN', name: 'Kouroussa' },\n  { code: 'GN-L', countryCode: 'GN', name: 'Labe' },\n  { code: 'GN-LE', countryCode: 'GN', name: 'Lelouma' },\n  { code: 'GN-LO', countryCode: 'GN', name: 'Lola' },\n  { code: 'GN-MC', countryCode: 'GN', name: 'Macenta' },\n  { code: 'GN-ML', countryCode: 'GN', name: 'Mali' },\n  { code: 'GN-M', countryCode: 'GN', name: 'Mamou' },\n  { code: 'GN-MD', countryCode: 'GN', name: 'Mandiana' },\n  { code: 'GN-N', countryCode: 'GN', name: 'Nzerekore' },\n  { code: 'GN-PI', countryCode: 'GN', name: 'Pita' },\n  { code: 'GN-SI', countryCode: 'GN', name: 'Siguiri' },\n  { code: 'GN-TE', countryCode: 'GN', name: 'Telimele' },\n  { code: 'GN-TO', countryCode: 'GN', name: 'Tougue' },\n  { code: 'GN-YO', countryCode: 'GN', name: 'Yomou' },\n  { code: 'GQ-AN', countryCode: 'GQ', name: 'Annobon' },\n  { code: 'GQ-BN', countryCode: 'GQ', name: 'Bioko Norte' },\n  { code: 'GQ-BS', countryCode: 'GQ', name: 'Bioko Sur' },\n  { code: 'GQ-CS', countryCode: 'GQ', name: 'Centro Sur' },\n  { code: 'GQ-KN', countryCode: 'GQ', name: 'Kie-Ntem' },\n  { code: 'GQ-LI', countryCode: 'GQ', name: 'Litoral' },\n  { code: 'GQ-WN', countryCode: 'GQ', name: 'Wele-Nzas' },\n  { code: 'GR-A', countryCode: 'GR', name: 'Anatoliki Makedonia kai Thraki' },\n  { code: 'GR-I', countryCode: 'GR', name: 'Attiki' },\n  { code: 'GR-G', countryCode: 'GR', name: 'Dytiki Ellada' },\n  { code: 'GR-C', countryCode: 'GR', name: 'Dytiki Makedonia' },\n  { code: 'GR-F', countryCode: 'GR', name: 'Ionia Nisia' },\n  { code: 'GR-D', countryCode: 'GR', name: 'Ipeiros' },\n  { code: 'GR-B', countryCode: 'GR', name: 'Kentriki Makedonia' },\n  { code: 'GR-M', countryCode: 'GR', name: 'Kriti' },\n  { code: 'GR-L', countryCode: 'GR', name: 'Notio Aigaio' },\n  { code: 'GR-J', countryCode: 'GR', name: 'Peloponnisos' },\n  { code: 'GR-H', countryCode: 'GR', name: 'Sterea Ellada' },\n  { code: 'GR-E', countryCode: 'GR', name: 'Thessalia' },\n  { code: 'GR-K', countryCode: 'GR', name: 'Voreio Aigaio' },\n  { code: 'GT-AV', countryCode: 'GT', name: 'Alta Verapaz' },\n  { code: 'GT-BV', countryCode: 'GT', name: 'Baja Verapaz' },\n  { code: 'GT-CM', countryCode: 'GT', name: 'Chimaltenango' },\n  { code: 'GT-CQ', countryCode: 'GT', name: 'Chiquimula' },\n  { code: 'GT-PR', countryCode: 'GT', name: 'El Progreso' },\n  { code: 'GT-ES', countryCode: 'GT', name: 'Escuintla' },\n  { code: 'GT-GU', countryCode: 'GT', name: 'Guatemala' },\n  { code: 'GT-HU', countryCode: 'GT', name: 'Huehuetenango' },\n  { code: 'GT-IZ', countryCode: 'GT', name: 'Izabal' },\n  { code: 'GT-JA', countryCode: 'GT', name: 'Jalapa' },\n  { code: 'GT-JU', countryCode: 'GT', name: 'Jutiapa' },\n  { code: 'GT-PE', countryCode: 'GT', name: 'Peten' },\n  { code: 'GT-QZ', countryCode: 'GT', name: 'Quetzaltenango' },\n  { code: 'GT-QC', countryCode: 'GT', name: 'Quiche' },\n  { code: 'GT-RE', countryCode: 'GT', name: 'Retalhuleu' },\n  { code: 'GT-SA', countryCode: 'GT', name: 'Sacatepequez' },\n  { code: 'GT-SM', countryCode: 'GT', name: 'San Marcos' },\n  { code: 'GT-SR', countryCode: 'GT', name: 'Santa Rosa' },\n  { code: 'GT-SO', countryCode: 'GT', name: 'Solola' },\n  { code: 'GT-SU', countryCode: 'GT', name: 'Suchitepequez' },\n  { code: 'GT-TO', countryCode: 'GT', name: 'Totonicapan' },\n  { code: 'GT-ZA', countryCode: 'GT', name: 'Zacapa' },\n  { code: 'GW-BA', countryCode: 'GW', name: 'Bafata' },\n  { code: 'GW-BM', countryCode: 'GW', name: 'Biombo' },\n  { code: 'GW-BS', countryCode: 'GW', name: 'Bissau' },\n  { code: 'GW-BL', countryCode: 'GW', name: 'Bolama' },\n  { code: 'GW-CA', countryCode: 'GW', name: 'Cacheu' },\n  { code: 'GW-GA', countryCode: 'GW', name: 'Gabu' },\n  { code: 'GW-OI', countryCode: 'GW', name: 'Oio' },\n  { code: 'GW-QU', countryCode: 'GW', name: 'Quinara' },\n  { code: 'GW-TO', countryCode: 'GW', name: 'Tombali' },\n  { code: 'GY-CU', countryCode: 'GY', name: 'Cuyuni-Mazaruni' },\n  { code: 'GY-DE', countryCode: 'GY', name: 'Demerara-Mahaica' },\n  { code: 'GY-EB', countryCode: 'GY', name: 'East Berbice-Corentyne' },\n  { code: 'GY-ES', countryCode: 'GY', name: 'Essequibo Islands-West Demerara' },\n  { code: 'GY-MA', countryCode: 'GY', name: 'Mahaica-Berbice' },\n  { code: 'GY-PM', countryCode: 'GY', name: 'Pomeroon-Supenaam' },\n  { code: 'GY-UD', countryCode: 'GY', name: 'Upper Demerara-Berbice' },\n  { code: 'HN-AT', countryCode: 'HN', name: 'Atlantida' },\n  { code: 'HN-CH', countryCode: 'HN', name: 'Choluteca' },\n  { code: 'HN-CL', countryCode: 'HN', name: 'Colon' },\n  { code: 'HN-CM', countryCode: 'HN', name: 'Comayagua' },\n  { code: 'HN-CP', countryCode: 'HN', name: 'Copan' },\n  { code: 'HN-CR', countryCode: 'HN', name: 'Cortes' },\n  { code: 'HN-EP', countryCode: 'HN', name: 'El Paraiso' },\n  { code: 'HN-FM', countryCode: 'HN', name: 'Francisco Morazan' },\n  { code: 'HN-GD', countryCode: 'HN', name: 'Gracias a Dios' },\n  { code: 'HN-IN', countryCode: 'HN', name: 'Intibuca' },\n  { code: 'HN-IB', countryCode: 'HN', name: 'Islas de la Bahia' },\n  { code: 'HN-LP', countryCode: 'HN', name: 'La Paz' },\n  { code: 'HN-LE', countryCode: 'HN', name: 'Lempira' },\n  { code: 'HN-OC', countryCode: 'HN', name: 'Ocotepeque' },\n  { code: 'HN-OL', countryCode: 'HN', name: 'Olancho' },\n  { code: 'HN-SB', countryCode: 'HN', name: 'Santa Barbara' },\n  { code: 'HN-VA', countryCode: 'HN', name: 'Valle' },\n  { code: 'HN-YO', countryCode: 'HN', name: 'Yoro' },\n  { code: 'HR-07', countryCode: 'HR', name: 'Bjelovarsko-bilogorska zupanija' },\n  { code: 'HR-12', countryCode: 'HR', name: 'Brodsko-posavska zupanija' },\n  { code: 'HR-19', countryCode: 'HR', name: 'Dubrovacko-neretvanska zupanija' },\n  { code: 'HR-21', countryCode: 'HR', name: 'Grad Zagreb' },\n  { code: 'HR-18', countryCode: 'HR', name: 'Istarska zupanija' },\n  { code: 'HR-04', countryCode: 'HR', name: 'Karlovacka zupanija' },\n  { code: 'HR-06', countryCode: 'HR', name: 'Koprivnicko-krizevacka zupanija' },\n  { code: 'HR-02', countryCode: 'HR', name: 'Krapinsko-zagorska zupanija' },\n  { code: 'HR-09', countryCode: 'HR', name: 'Licko-senjska zupanija' },\n  { code: 'HR-20', countryCode: 'HR', name: 'Medimurska zupanija' },\n  { code: 'HR-14', countryCode: 'HR', name: 'Osjecko-baranjska zupanija' },\n  { code: 'HR-11', countryCode: 'HR', name: 'Pozesko-slavonska zupanija' },\n  { code: 'HR-08', countryCode: 'HR', name: 'Primorsko-goranska zupanija' },\n  { code: 'HR-15', countryCode: 'HR', name: 'Sibensko-kninska zupanija' },\n  { code: 'HR-03', countryCode: 'HR', name: 'Sisacko-moslavacka zupanija' },\n  { code: 'HR-17', countryCode: 'HR', name: 'Splitsko-dalmatinska zupanija' },\n  { code: 'HR-05', countryCode: 'HR', name: 'Varazdinska zupanija' },\n  { code: 'HR-10', countryCode: 'HR', name: 'Viroviticko-podravska zupanija' },\n  { code: 'HR-16', countryCode: 'HR', name: 'Vukovarsko-srijemska zupanija' },\n  { code: 'HR-13', countryCode: 'HR', name: 'Zadarska zupanija' },\n  { code: 'HR-01', countryCode: 'HR', name: 'Zagrebacka zupanija' },\n  { code: 'HT-AR', countryCode: 'HT', name: 'Artibonite' },\n  { code: 'HT-CE', countryCode: 'HT', name: 'Centre' },\n  { code: 'HT-GA', countryCode: 'HT', name: \"Grande'Anse\" },\n  { code: 'HT-NI', countryCode: 'HT', name: 'Nippes' },\n  { code: 'HT-ND', countryCode: 'HT', name: 'Nord' },\n  { code: 'HT-NE', countryCode: 'HT', name: 'Nord-Est' },\n  { code: 'HT-NO', countryCode: 'HT', name: 'Nord-Ouest' },\n  { code: 'HT-OU', countryCode: 'HT', name: 'Ouest' },\n  { code: 'HT-SD', countryCode: 'HT', name: 'Sud' },\n  { code: 'HT-SE', countryCode: 'HT', name: 'Sud-Est' },\n  { code: 'HU-BK', countryCode: 'HU', name: 'Bacs-Kiskun' },\n  { code: 'HU-BA', countryCode: 'HU', name: 'Baranya' },\n  { code: 'HU-BE', countryCode: 'HU', name: 'Bekes' },\n  { code: 'HU-BZ', countryCode: 'HU', name: 'Borsod-Abauj-Zemplen' },\n  { code: 'HU-BU', countryCode: 'HU', name: 'Budapest' },\n  { code: 'HU-CS', countryCode: 'HU', name: 'Csongrad' },\n  { code: 'HU-FE', countryCode: 'HU', name: 'Fejer' },\n  { code: 'HU-GS', countryCode: 'HU', name: 'Gyor-Moson-Sopron' },\n  { code: 'HU-HB', countryCode: 'HU', name: 'Hajdu-Bihar' },\n  { code: 'HU-HE', countryCode: 'HU', name: 'Heves' },\n  { code: 'HU-JN', countryCode: 'HU', name: 'Jasz-Nagykun-Szolnok' },\n  { code: 'HU-KE', countryCode: 'HU', name: 'Komarom-Esztergom' },\n  { code: 'HU-NO', countryCode: 'HU', name: 'Nograd' },\n  { code: 'HU-PE', countryCode: 'HU', name: 'Pest' },\n  { code: 'HU-SO', countryCode: 'HU', name: 'Somogy' },\n  { code: 'HU-SZ', countryCode: 'HU', name: 'Szabolcs-Szatmar-Bereg' },\n  { code: 'HU-TO', countryCode: 'HU', name: 'Tolna' },\n  { code: 'HU-VA', countryCode: 'HU', name: 'Vas' },\n  { code: 'HU-VM', countryCode: 'HU', name: 'Veszprem' },\n  { code: 'HU-ZA', countryCode: 'HU', name: 'Zala' },\n  { code: 'ID-AC', countryCode: 'ID', name: 'Aceh' },\n  { code: 'ID-BA', countryCode: 'ID', name: 'Bali' },\n  { code: 'ID-BT', countryCode: 'ID', name: 'Banten' },\n  { code: 'ID-BE', countryCode: 'ID', name: 'Bengkulu' },\n  { code: 'ID-GO', countryCode: 'ID', name: 'Gorontalo' },\n  { code: 'ID-JK', countryCode: 'ID', name: 'Jakarta Raya' },\n  { code: 'ID-JA', countryCode: 'ID', name: 'Jambi' },\n  { code: 'ID-JB', countryCode: 'ID', name: 'Jawa Barat' },\n  { code: 'ID-JT', countryCode: 'ID', name: 'Jawa Tengah' },\n  { code: 'ID-JI', countryCode: 'ID', name: 'Jawa Timur' },\n  { code: 'ID-KB', countryCode: 'ID', name: 'Kalimantan Barat' },\n  { code: 'ID-KS', countryCode: 'ID', name: 'Kalimantan Selatan' },\n  { code: 'ID-KT', countryCode: 'ID', name: 'Kalimantan Tengah' },\n  { code: 'ID-KI', countryCode: 'ID', name: 'Kalimantan Timur' },\n  { code: 'ID-BB', countryCode: 'ID', name: 'Kepulauan Bangka Belitung' },\n  { code: 'ID-KR', countryCode: 'ID', name: 'Kepulauan Riau' },\n  { code: 'ID-LA', countryCode: 'ID', name: 'Lampung' },\n  { code: 'ID-ML', countryCode: 'ID', name: 'Maluku' },\n  { code: 'ID-MU', countryCode: 'ID', name: 'Maluku Utara' },\n  { code: 'ID-NB', countryCode: 'ID', name: 'Nusa Tenggara Barat' },\n  { code: 'ID-NT', countryCode: 'ID', name: 'Nusa Tenggara Timur' },\n  { code: 'ID-PP', countryCode: 'ID', name: 'Papua' },\n  { code: 'ID-PB', countryCode: 'ID', name: 'Papua Barat' },\n  { code: 'ID-RI', countryCode: 'ID', name: 'Riau' },\n  { code: 'ID-SR', countryCode: 'ID', name: 'Sulawesi Barat' },\n  { code: 'ID-SN', countryCode: 'ID', name: 'Sulawesi Selatan' },\n  { code: 'ID-ST', countryCode: 'ID', name: 'Sulawesi Tengah' },\n  { code: 'ID-SG', countryCode: 'ID', name: 'Sulawesi Tenggara' },\n  { code: 'ID-SA', countryCode: 'ID', name: 'Sulawesi Utara' },\n  { code: 'ID-SB', countryCode: 'ID', name: 'Sumatera Barat' },\n  { code: 'ID-SS', countryCode: 'ID', name: 'Sumatera Selatan' },\n  { code: 'ID-SU', countryCode: 'ID', name: 'Sumatera Utara' },\n  { code: 'ID-YO', countryCode: 'ID', name: 'Yogyakarta' },\n  { code: 'IE-CW', countryCode: 'IE', name: 'Carlow' },\n  { code: 'IE-CN', countryCode: 'IE', name: 'Cavan' },\n  { code: 'IE-CE', countryCode: 'IE', name: 'Clare' },\n  { code: 'IE-CO', countryCode: 'IE', name: 'Cork' },\n  { code: 'IE-DL', countryCode: 'IE', name: 'Donegal' },\n  { code: 'IE-D', countryCode: 'IE', name: 'Dublin' },\n  { code: 'IE-G', countryCode: 'IE', name: 'Galway' },\n  { code: 'IE-KY', countryCode: 'IE', name: 'Kerry' },\n  { code: 'IE-KE', countryCode: 'IE', name: 'Kildare' },\n  { code: 'IE-KK', countryCode: 'IE', name: 'Kilkenny' },\n  { code: 'IE-LS', countryCode: 'IE', name: 'Laois' },\n  { code: 'IE-LM', countryCode: 'IE', name: 'Leitrim' },\n  { code: 'IE-LK', countryCode: 'IE', name: 'Limerick' },\n  { code: 'IE-LD', countryCode: 'IE', name: 'Longford' },\n  { code: 'IE-LH', countryCode: 'IE', name: 'Louth' },\n  { code: 'IE-MO', countryCode: 'IE', name: 'Mayo' },\n  { code: 'IE-MH', countryCode: 'IE', name: 'Meath' },\n  { code: 'IE-MN', countryCode: 'IE', name: 'Monaghan' },\n  { code: 'IE-OY', countryCode: 'IE', name: 'Offaly' },\n  { code: 'IE-RN', countryCode: 'IE', name: 'Roscommon' },\n  { code: 'IE-SO', countryCode: 'IE', name: 'Sligo' },\n  { code: 'IE-TA', countryCode: 'IE', name: 'Tipperary' },\n  { code: 'IE-WD', countryCode: 'IE', name: 'Waterford' },\n  { code: 'IE-WH', countryCode: 'IE', name: 'Westmeath' },\n  { code: 'IE-WX', countryCode: 'IE', name: 'Wexford' },\n  { code: 'IE-WW', countryCode: 'IE', name: 'Wicklow' },\n  { code: 'IL-D', countryCode: 'IL', name: 'HaDarom' },\n  { code: 'IL-M', countryCode: 'IL', name: 'HaMerkaz' },\n  { code: 'IL-Z', countryCode: 'IL', name: 'HaTsafon' },\n  { code: 'IL-HA', countryCode: 'IL', name: 'Hefa' },\n  { code: 'IL-TA', countryCode: 'IL', name: 'Tel Aviv' },\n  { code: 'IL-JM', countryCode: 'IL', name: 'Yerushalayim' },\n  { code: 'IN-AN', countryCode: 'IN', name: 'Andaman and Nicobar Islands' },\n  { code: 'IN-AP', countryCode: 'IN', name: 'Andhra Pradesh' },\n  { code: 'IN-AR', countryCode: 'IN', name: 'Arunachal Pradesh' },\n  { code: 'IN-AS', countryCode: 'IN', name: 'Assam' },\n  { code: 'IN-BR', countryCode: 'IN', name: 'Bihar' },\n  { code: 'IN-CH', countryCode: 'IN', name: 'Chandigarh' },\n  { code: 'IN-CT', countryCode: 'IN', name: 'Chhattisgarh' },\n  { code: 'IN-DN', countryCode: 'IN', name: 'Dadra and Nagar Haveli' },\n  { code: 'IN-DD', countryCode: 'IN', name: 'Daman and Diu' },\n  { code: 'IN-DL', countryCode: 'IN', name: 'Delhi' },\n  { code: 'IN-GA', countryCode: 'IN', name: 'Goa' },\n  { code: 'IN-GJ', countryCode: 'IN', name: 'Gujarat' },\n  { code: 'IN-HR', countryCode: 'IN', name: 'Haryana' },\n  { code: 'IN-HP', countryCode: 'IN', name: 'Himachal Pradesh' },\n  { code: 'IN-JK', countryCode: 'IN', name: 'Jammu and Kashmir' },\n  { code: 'IN-JH', countryCode: 'IN', name: 'Jharkhand' },\n  { code: 'IN-KA', countryCode: 'IN', name: 'Karnataka' },\n  { code: 'IN-KL', countryCode: 'IN', name: 'Kerala' },\n  { code: 'IN-LD', countryCode: 'IN', name: 'Lakshadweep' },\n  { code: 'IN-MP', countryCode: 'IN', name: 'Madhya Pradesh' },\n  { code: 'IN-MH', countryCode: 'IN', name: 'Maharashtra' },\n  { code: 'IN-MN', countryCode: 'IN', name: 'Manipur' },\n  { code: 'IN-ML', countryCode: 'IN', name: 'Meghalaya' },\n  { code: 'IN-MZ', countryCode: 'IN', name: 'Mizoram' },\n  { code: 'IN-NL', countryCode: 'IN', name: 'Nagaland' },\n  { code: 'IN-OR', countryCode: 'IN', name: 'Odisha' },\n  { code: 'IN-PY', countryCode: 'IN', name: 'Puducherry' },\n  { code: 'IN-PB', countryCode: 'IN', name: 'Punjab' },\n  { code: 'IN-RJ', countryCode: 'IN', name: 'Rajasthan' },\n  { code: 'IN-SK', countryCode: 'IN', name: 'Sikkim' },\n  { code: 'IN-TN', countryCode: 'IN', name: 'Tamil Nadu' },\n  { code: 'IN-TG', countryCode: 'IN', name: 'Telangana' },\n  { code: 'IN-TR', countryCode: 'IN', name: 'Tripura' },\n  { code: 'IN-UP', countryCode: 'IN', name: 'Uttar Pradesh' },\n  { code: 'IN-UT', countryCode: 'IN', name: 'Uttarakhand' },\n  { code: 'IN-WB', countryCode: 'IN', name: 'West Bengal' },\n  { code: 'IQ-AN', countryCode: 'IQ', name: 'Al Anbar' },\n  { code: 'IQ-BA', countryCode: 'IQ', name: 'Al Basrah' },\n  { code: 'IQ-MU', countryCode: 'IQ', name: 'Al Muthanna' },\n  { code: 'IQ-QA', countryCode: 'IQ', name: 'Al Qadisiyah' },\n  { code: 'IQ-NA', countryCode: 'IQ', name: 'An Najaf' },\n  { code: 'IQ-AR', countryCode: 'IQ', name: 'Arbil' },\n  { code: 'IQ-SU', countryCode: 'IQ', name: 'As Sulaymaniyah' },\n  { code: 'IQ-BB', countryCode: 'IQ', name: 'Babil' },\n  { code: 'IQ-BG', countryCode: 'IQ', name: 'Baghdad' },\n  { code: 'IQ-DA', countryCode: 'IQ', name: 'Dahuk' },\n  { code: 'IQ-DQ', countryCode: 'IQ', name: 'Dhi Qar' },\n  { code: 'IQ-DI', countryCode: 'IQ', name: 'Diyala' },\n  { code: 'IQ-KA', countryCode: 'IQ', name: \"Karbala'\" },\n  { code: 'IQ-KI', countryCode: 'IQ', name: 'Kirkuk' },\n  { code: 'IQ-MA', countryCode: 'IQ', name: 'Maysan' },\n  { code: 'IQ-NI', countryCode: 'IQ', name: 'Ninawa' },\n  { code: 'IQ-SD', countryCode: 'IQ', name: 'Salah ad Din' },\n  { code: 'IQ-WA', countryCode: 'IQ', name: 'Wasit' },\n  { code: 'IR-32', countryCode: 'IR', name: 'Alborz' },\n  { code: 'IR-03', countryCode: 'IR', name: 'Ardabil' },\n  { code: 'IR-02', countryCode: 'IR', name: 'Azarbayjan-e Gharbi' },\n  { code: 'IR-01', countryCode: 'IR', name: 'Azarbayjan-e Sharqi' },\n  { code: 'IR-06', countryCode: 'IR', name: 'Bushehr' },\n  { code: 'IR-08', countryCode: 'IR', name: 'Chahar Mahal va Bakhtiari' },\n  { code: 'IR-04', countryCode: 'IR', name: 'Esfahan' },\n  { code: 'IR-14', countryCode: 'IR', name: 'Fars' },\n  { code: 'IR-19', countryCode: 'IR', name: 'Gilan' },\n  { code: 'IR-27', countryCode: 'IR', name: 'Golestan' },\n  { code: 'IR-24', countryCode: 'IR', name: 'Hamadan' },\n  { code: 'IR-23', countryCode: 'IR', name: 'Hormozgan' },\n  { code: 'IR-05', countryCode: 'IR', name: 'Ilam' },\n  { code: 'IR-15', countryCode: 'IR', name: 'Kerman' },\n  { code: 'IR-17', countryCode: 'IR', name: 'Kermanshah' },\n  { code: 'IR-29', countryCode: 'IR', name: 'Khorasan-e Jonubi' },\n  { code: 'IR-30', countryCode: 'IR', name: 'Khorasan-e Razavi' },\n  { code: 'IR-31', countryCode: 'IR', name: 'Khorasan-e Shomali' },\n  { code: 'IR-10', countryCode: 'IR', name: 'Khuzestan' },\n  { code: 'IR-18', countryCode: 'IR', name: 'Kohgiluyeh va Bowyer Ahmad' },\n  { code: 'IR-16', countryCode: 'IR', name: 'Kordestan' },\n  { code: 'IR-20', countryCode: 'IR', name: 'Lorestan' },\n  { code: 'IR-22', countryCode: 'IR', name: 'Markazi' },\n  { code: 'IR-21', countryCode: 'IR', name: 'Mazandaran' },\n  { code: 'IR-28', countryCode: 'IR', name: 'Qazvin' },\n  { code: 'IR-26', countryCode: 'IR', name: 'Qom' },\n  { code: 'IR-12', countryCode: 'IR', name: 'Semnan' },\n  { code: 'IR-13', countryCode: 'IR', name: 'Sistan va Baluchestan' },\n  { code: 'IR-07', countryCode: 'IR', name: 'Tehran' },\n  { code: 'IR-25', countryCode: 'IR', name: 'Yazd' },\n  { code: 'IR-11', countryCode: 'IR', name: 'Zanjan' },\n  { code: 'IS-7', countryCode: 'IS', name: 'Austurland' },\n  {\n    code: 'IS-1',\n    countryCode: 'IS',\n    name: 'Hofudborgarsvaedi utan Reykjavikur'\n  },\n  { code: 'IS-6', countryCode: 'IS', name: 'Nordurland eystra' },\n  { code: 'IS-5', countryCode: 'IS', name: 'Nordurland vestra' },\n  { code: 'IS-8', countryCode: 'IS', name: 'Sudurland' },\n  { code: 'IS-2', countryCode: 'IS', name: 'Sudurnes' },\n  { code: 'IS-4', countryCode: 'IS', name: 'Vestfirdir' },\n  { code: 'IS-3', countryCode: 'IS', name: 'Vesturland' },\n  { code: 'IT-65', countryCode: 'IT', name: 'Abruzzo' },\n  { code: 'IT-77', countryCode: 'IT', name: 'Basilicata' },\n  { code: 'IT-78', countryCode: 'IT', name: 'Calabria' },\n  { code: 'IT-72', countryCode: 'IT', name: 'Campania' },\n  { code: 'IT-45', countryCode: 'IT', name: 'Emilia-Romagna' },\n  { code: 'IT-36', countryCode: 'IT', name: 'Friuli-Venezia Giulia' },\n  { code: 'IT-62', countryCode: 'IT', name: 'Lazio' },\n  { code: 'IT-42', countryCode: 'IT', name: 'Liguria' },\n  { code: 'IT-25', countryCode: 'IT', name: 'Lombardia' },\n  { code: 'IT-57', countryCode: 'IT', name: 'Marche' },\n  { code: 'IT-67', countryCode: 'IT', name: 'Molise' },\n  { code: 'IT-21', countryCode: 'IT', name: 'Piemonte' },\n  { code: 'IT-75', countryCode: 'IT', name: 'Puglia' },\n  { code: 'IT-88', countryCode: 'IT', name: 'Sardegna' },\n  { code: 'IT-82', countryCode: 'IT', name: 'Sicilia' },\n  { code: 'IT-52', countryCode: 'IT', name: 'Toscana' },\n  { code: 'IT-32', countryCode: 'IT', name: 'Trentino-Alto Adige' },\n  { code: 'IT-55', countryCode: 'IT', name: 'Umbria' },\n  { code: 'IT-23', countryCode: 'IT', name: \"Valle d'Aosta\" },\n  { code: 'IT-34', countryCode: 'IT', name: 'Veneto' },\n  { code: 'JM-13', countryCode: 'JM', name: 'Clarendon' },\n  { code: 'JM-09', countryCode: 'JM', name: 'Hanover' },\n  { code: 'JM-01', countryCode: 'JM', name: 'Kingston' },\n  { code: 'JM-12', countryCode: 'JM', name: 'Manchester' },\n  { code: 'JM-04', countryCode: 'JM', name: 'Portland' },\n  { code: 'JM-02', countryCode: 'JM', name: 'Saint Andrew' },\n  { code: 'JM-06', countryCode: 'JM', name: 'Saint Ann' },\n  { code: 'JM-14', countryCode: 'JM', name: 'Saint Catherine' },\n  { code: 'JM-11', countryCode: 'JM', name: 'Saint Elizabeth' },\n  { code: 'JM-08', countryCode: 'JM', name: 'Saint James' },\n  { code: 'JM-05', countryCode: 'JM', name: 'Saint Mary' },\n  { code: 'JM-03', countryCode: 'JM', name: 'Saint Thomas' },\n  { code: 'JM-07', countryCode: 'JM', name: 'Trelawny' },\n  { code: 'JM-10', countryCode: 'JM', name: 'Westmoreland' },\n  { code: 'JO-AQ', countryCode: 'JO', name: \"Al 'Aqabah\" },\n  { code: 'JO-AM', countryCode: 'JO', name: \"Al 'Asimah\" },\n  { code: 'JO-BA', countryCode: 'JO', name: \"Al Balqa'\" },\n  { code: 'JO-KA', countryCode: 'JO', name: 'Al Karak' },\n  { code: 'JO-MA', countryCode: 'JO', name: 'Al Mafraq' },\n  { code: 'JO-AT', countryCode: 'JO', name: 'At Tafilah' },\n  { code: 'JO-AZ', countryCode: 'JO', name: \"Az Zarqa'\" },\n  { code: 'JO-IR', countryCode: 'JO', name: 'Irbid' },\n  { code: 'JO-MN', countryCode: 'JO', name: \"Ma'an\" },\n  { code: 'JO-MD', countryCode: 'JO', name: 'Madaba' },\n  { code: 'JP-23', countryCode: 'JP', name: 'Aichi' },\n  { code: 'JP-05', countryCode: 'JP', name: 'Akita' },\n  { code: 'JP-02', countryCode: 'JP', name: 'Aomori' },\n  { code: 'JP-12', countryCode: 'JP', name: 'Chiba' },\n  { code: 'JP-38', countryCode: 'JP', name: 'Ehime' },\n  { code: 'JP-18', countryCode: 'JP', name: 'Fukui' },\n  { code: 'JP-40', countryCode: 'JP', name: 'Fukuoka' },\n  { code: 'JP-07', countryCode: 'JP', name: 'Fukushima' },\n  { code: 'JP-21', countryCode: 'JP', name: 'Gifu' },\n  { code: 'JP-10', countryCode: 'JP', name: 'Gunma' },\n  { code: 'JP-34', countryCode: 'JP', name: 'Hiroshima' },\n  { code: 'JP-01', countryCode: 'JP', name: 'Hokkaido' },\n  { code: 'JP-28', countryCode: 'JP', name: 'Hyogo' },\n  { code: 'JP-08', countryCode: 'JP', name: 'Ibaraki' },\n  { code: 'JP-17', countryCode: 'JP', name: 'Ishikawa' },\n  { code: 'JP-03', countryCode: 'JP', name: 'Iwate' },\n  { code: 'JP-37', countryCode: 'JP', name: 'Kagawa' },\n  { code: 'JP-46', countryCode: 'JP', name: 'Kagoshima' },\n  { code: 'JP-14', countryCode: 'JP', name: 'Kanagawa' },\n  { code: 'JP-39', countryCode: 'JP', name: 'Kochi' },\n  { code: 'JP-43', countryCode: 'JP', name: 'Kumamoto' },\n  { code: 'JP-26', countryCode: 'JP', name: 'Kyoto' },\n  { code: 'JP-24', countryCode: 'JP', name: 'Mie' },\n  { code: 'JP-04', countryCode: 'JP', name: 'Miyagi' },\n  { code: 'JP-45', countryCode: 'JP', name: 'Miyazaki' },\n  { code: 'JP-20', countryCode: 'JP', name: 'Nagano' },\n  { code: 'JP-42', countryCode: 'JP', name: 'Nagasaki' },\n  { code: 'JP-29', countryCode: 'JP', name: 'Nara' },\n  { code: 'JP-15', countryCode: 'JP', name: 'Niigata' },\n  { code: 'JP-44', countryCode: 'JP', name: 'Oita' },\n  { code: 'JP-33', countryCode: 'JP', name: 'Okayama' },\n  { code: 'JP-47', countryCode: 'JP', name: 'Okinawa' },\n  { code: 'JP-27', countryCode: 'JP', name: 'Osaka' },\n  { code: 'JP-41', countryCode: 'JP', name: 'Saga' },\n  { code: 'JP-11', countryCode: 'JP', name: 'Saitama' },\n  { code: 'JP-25', countryCode: 'JP', name: 'Shiga' },\n  { code: 'JP-32', countryCode: 'JP', name: 'Shimane' },\n  { code: 'JP-22', countryCode: 'JP', name: 'Shizuoka' },\n  { code: 'JP-09', countryCode: 'JP', name: 'Tochigi' },\n  { code: 'JP-36', countryCode: 'JP', name: 'Tokushima' },\n  { code: 'JP-13', countryCode: 'JP', name: 'Tokyo' },\n  { code: 'JP-31', countryCode: 'JP', name: 'Tottori' },\n  { code: 'JP-16', countryCode: 'JP', name: 'Toyama' },\n  { code: 'JP-30', countryCode: 'JP', name: 'Wakayama' },\n  { code: 'JP-06', countryCode: 'JP', name: 'Yamagata' },\n  { code: 'JP-35', countryCode: 'JP', name: 'Yamaguchi' },\n  { code: 'JP-19', countryCode: 'JP', name: 'Yamanashi' },\n  { code: 'KE-01', countryCode: 'KE', name: 'Baringo' },\n  { code: 'KE-02', countryCode: 'KE', name: 'Bomet' },\n  { code: 'KE-03', countryCode: 'KE', name: 'Bungoma' },\n  { code: 'KE-04', countryCode: 'KE', name: 'Busia' },\n  { code: 'KE-06', countryCode: 'KE', name: 'Embu' },\n  { code: 'KE-07', countryCode: 'KE', name: 'Garissa' },\n  { code: 'KE-08', countryCode: 'KE', name: 'Homa Bay' },\n  { code: 'KE-09', countryCode: 'KE', name: 'Isiolo' },\n  { code: 'KE-10', countryCode: 'KE', name: 'Kajiado' },\n  { code: 'KE-11', countryCode: 'KE', name: 'Kakamega' },\n  { code: 'KE-12', countryCode: 'KE', name: 'Kericho' },\n  { code: 'KE-13', countryCode: 'KE', name: 'Kiambu' },\n  { code: 'KE-14', countryCode: 'KE', name: 'Kilifi' },\n  { code: 'KE-15', countryCode: 'KE', name: 'Kirinyaga' },\n  { code: 'KE-16', countryCode: 'KE', name: 'Kisii' },\n  { code: 'KE-17', countryCode: 'KE', name: 'Kisumu' },\n  { code: 'KE-18', countryCode: 'KE', name: 'Kitui' },\n  { code: 'KE-19', countryCode: 'KE', name: 'Kwale' },\n  { code: 'KE-20', countryCode: 'KE', name: 'Laikipia' },\n  { code: 'KE-21', countryCode: 'KE', name: 'Lamu' },\n  { code: 'KE-22', countryCode: 'KE', name: 'Machakos' },\n  { code: 'KE-23', countryCode: 'KE', name: 'Makueni' },\n  { code: 'KE-24', countryCode: 'KE', name: 'Mandera' },\n  { code: 'KE-25', countryCode: 'KE', name: 'Marsabit' },\n  { code: 'KE-26', countryCode: 'KE', name: 'Meru' },\n  { code: 'KE-27', countryCode: 'KE', name: 'Migori' },\n  { code: 'KE-28', countryCode: 'KE', name: 'Mombasa' },\n  { code: 'KE-29', countryCode: 'KE', name: \"Murang'a\" },\n  { code: 'KE-30', countryCode: 'KE', name: 'Nairobi City' },\n  { code: 'KE-31', countryCode: 'KE', name: 'Nakuru' },\n  { code: 'KE-32', countryCode: 'KE', name: 'Nandi' },\n  { code: 'KE-33', countryCode: 'KE', name: 'Narok' },\n  { code: 'KE-34', countryCode: 'KE', name: 'Nyamira' },\n  { code: 'KE-36', countryCode: 'KE', name: 'Nyeri' },\n  { code: 'KE-37', countryCode: 'KE', name: 'Samburu' },\n  { code: 'KE-38', countryCode: 'KE', name: 'Siaya' },\n  { code: 'KE-39', countryCode: 'KE', name: 'Taita/Taveta' },\n  { code: 'KE-40', countryCode: 'KE', name: 'Tana River' },\n  { code: 'KE-41', countryCode: 'KE', name: 'Tharaka-Nithi' },\n  { code: 'KE-42', countryCode: 'KE', name: 'Trans Nzoia' },\n  { code: 'KE-43', countryCode: 'KE', name: 'Turkana' },\n  { code: 'KE-44', countryCode: 'KE', name: 'Uasin Gishu' },\n  { code: 'KE-45', countryCode: 'KE', name: 'Vihiga' },\n  { code: 'KE-46', countryCode: 'KE', name: 'Wajir' },\n  { code: 'KE-47', countryCode: 'KE', name: 'West Pokot' },\n  { code: 'KG-B', countryCode: 'KG', name: 'Batken' },\n  { code: 'KG-GB', countryCode: 'KG', name: 'Bishkek' },\n  { code: 'KG-C', countryCode: 'KG', name: 'Chuy' },\n  { code: 'KG-J', countryCode: 'KG', name: 'Jalal-Abad' },\n  { code: 'KG-N', countryCode: 'KG', name: 'Naryn' },\n  { code: 'KG-GO', countryCode: 'KG', name: 'Osh' },\n  { code: 'KG-T', countryCode: 'KG', name: 'Talas' },\n  { code: 'KG-Y', countryCode: 'KG', name: 'Ysyk-Kol' },\n  { code: 'KH-2', countryCode: 'KH', name: 'Baat Dambang' },\n  { code: 'KH-1', countryCode: 'KH', name: 'Banteay Mean Chey' },\n  { code: 'KH-3', countryCode: 'KH', name: 'Kampong Chaam' },\n  { code: 'KH-4', countryCode: 'KH', name: 'Kampong Chhnang' },\n  { code: 'KH-5', countryCode: 'KH', name: 'Kampong Spueu' },\n  { code: 'KH-6', countryCode: 'KH', name: 'Kampong Thum' },\n  { code: 'KH-7', countryCode: 'KH', name: 'Kampot' },\n  { code: 'KH-8', countryCode: 'KH', name: 'Kandaal' },\n  { code: 'KH-9', countryCode: 'KH', name: 'Kaoh Kong' },\n  { code: 'KH-10', countryCode: 'KH', name: 'Kracheh' },\n  { code: 'KH-23', countryCode: 'KH', name: 'Krong Kaeb' },\n  { code: 'KH-24', countryCode: 'KH', name: 'Krong Pailin' },\n  { code: 'KH-18', countryCode: 'KH', name: 'Krong Preah Sihanouk' },\n  { code: 'KH-11', countryCode: 'KH', name: 'Mondol Kiri' },\n  { code: 'KH-22', countryCode: 'KH', name: 'Otdar Mean Chey' },\n  { code: 'KH-12', countryCode: 'KH', name: 'Phnom Penh' },\n  { code: 'KH-15', countryCode: 'KH', name: 'Pousaat' },\n  { code: 'KH-13', countryCode: 'KH', name: 'Preah Vihear' },\n  { code: 'KH-14', countryCode: 'KH', name: 'Prey Veaeng' },\n  { code: 'KH-16', countryCode: 'KH', name: 'Rotanak Kiri' },\n  { code: 'KH-17', countryCode: 'KH', name: 'Siem Reab' },\n  { code: 'KH-19', countryCode: 'KH', name: 'Stueng Traeng' },\n  { code: 'KH-20', countryCode: 'KH', name: 'Svaay Rieng' },\n  { code: 'KH-21', countryCode: 'KH', name: 'Taakaev' },\n  { code: 'KI-G', countryCode: 'KI', name: 'Gilbert Islands' },\n  { code: 'KI-L', countryCode: 'KI', name: 'Line Islands' },\n  { code: 'KM-A', countryCode: 'KM', name: 'Anjouan' },\n  { code: 'KM-G', countryCode: 'KM', name: 'Grande Comore' },\n  { code: 'KM-M', countryCode: 'KM', name: 'Moheli' },\n  { code: 'KN-03', countryCode: 'KN', name: 'Saint George Basseterre' },\n  { code: 'KN-10', countryCode: 'KN', name: 'Saint Paul Charlestown' },\n  { code: 'KP-04', countryCode: 'KP', name: 'Chagang-do' },\n  { code: 'KP-09', countryCode: 'KP', name: 'Hamgyong-bukto' },\n  { code: 'KP-08', countryCode: 'KP', name: 'Hamgyong-namdo' },\n  { code: 'KP-06', countryCode: 'KP', name: 'Hwanghae-bukto' },\n  { code: 'KP-05', countryCode: 'KP', name: 'Hwanghae-namdo' },\n  { code: 'KP-07', countryCode: 'KP', name: 'Kangwon-do' },\n  { code: 'KP-13', countryCode: 'KP', name: 'Nason' },\n  { code: 'KP-03', countryCode: 'KP', name: \"P'yongan-bukto\" },\n  { code: 'KP-02', countryCode: 'KP', name: \"P'yongan-namdo\" },\n  { code: 'KP-01', countryCode: 'KP', name: \"P'yongyang\" },\n  { code: 'KP-10', countryCode: 'KP', name: 'Yanggang-do' },\n  { code: 'KR-26', countryCode: 'KR', name: 'Busan-gwangyeoksi' },\n  { code: 'KR-43', countryCode: 'KR', name: 'Chungcheongbuk-do' },\n  { code: 'KR-44', countryCode: 'KR', name: 'Chungcheongnam-do' },\n  { code: 'KR-27', countryCode: 'KR', name: 'Daegu-gwangyeoksi' },\n  { code: 'KR-30', countryCode: 'KR', name: 'Daejeon-gwangyeoksi' },\n  { code: 'KR-42', countryCode: 'KR', name: 'Gangwon-do' },\n  { code: 'KR-29', countryCode: 'KR', name: 'Gwangju-gwangyeoksi' },\n  { code: 'KR-41', countryCode: 'KR', name: 'Gyeonggi-do' },\n  { code: 'KR-47', countryCode: 'KR', name: 'Gyeongsangbuk-do' },\n  { code: 'KR-48', countryCode: 'KR', name: 'Gyeongsangnam-do' },\n  { code: 'KR-28', countryCode: 'KR', name: 'Incheon-gwangyeoksi' },\n  { code: 'KR-49', countryCode: 'KR', name: 'Jeju-teukbyeoljachido' },\n  { code: 'KR-45', countryCode: 'KR', name: 'Jeollabuk-do' },\n  { code: 'KR-46', countryCode: 'KR', name: 'Jeollanam-do' },\n  { code: 'KR-11', countryCode: 'KR', name: 'Seoul-teukbyeolsi' },\n  { code: 'KR-31', countryCode: 'KR', name: 'Ulsan-gwangyeoksi' },\n  { code: 'KW-KU', countryCode: 'KW', name: \"Al 'Asimah\" },\n  { code: 'KW-AH', countryCode: 'KW', name: 'Al Ahmadi' },\n  { code: 'KW-FA', countryCode: 'KW', name: 'Al Farwaniyah' },\n  { code: 'KW-JA', countryCode: 'KW', name: 'Al Jahra' },\n  { code: 'KW-HA', countryCode: 'KW', name: 'Hawalli' },\n  { code: 'KW-MU', countryCode: 'KW', name: 'Mubarak al Kabir' },\n  { code: 'KZ-ALA', countryCode: 'KZ', name: 'Almaty' },\n  { code: 'KZ-ALM', countryCode: 'KZ', name: 'Almaty oblysy' },\n  { code: 'KZ-AKM', countryCode: 'KZ', name: 'Aqmola oblysy' },\n  { code: 'KZ-AKT', countryCode: 'KZ', name: 'Aqtobe oblysy' },\n  { code: 'KZ-AST', countryCode: 'KZ', name: 'Astana' },\n  { code: 'KZ-ATY', countryCode: 'KZ', name: 'Atyrau oblysy' },\n  { code: 'KZ-ZAP', countryCode: 'KZ', name: 'Batys Qazaqstan oblysy' },\n  { code: 'KZ-BAY', countryCode: 'KZ', name: 'Bayqongyr' },\n  { code: 'KZ-MAN', countryCode: 'KZ', name: 'Mangghystau oblysy' },\n  { code: 'KZ-YUZ', countryCode: 'KZ', name: 'Ongtustik Qazaqstan oblysy' },\n  { code: 'KZ-PAV', countryCode: 'KZ', name: 'Pavlodar oblysy' },\n  { code: 'KZ-KAR', countryCode: 'KZ', name: 'Qaraghandy oblysy' },\n  { code: 'KZ-KUS', countryCode: 'KZ', name: 'Qostanay oblysy' },\n  { code: 'KZ-KZY', countryCode: 'KZ', name: 'Qyzylorda oblysy' },\n  { code: 'KZ-VOS', countryCode: 'KZ', name: 'Shyghys Qazaqstan oblysy' },\n  { code: 'KZ-SEV', countryCode: 'KZ', name: 'Soltustik Qazaqstan oblysy' },\n  { code: 'KZ-ZHA', countryCode: 'KZ', name: 'Zhambyl oblysy' },\n  { code: 'LA-AT', countryCode: 'LA', name: 'Attapu' },\n  { code: 'LA-BK', countryCode: 'LA', name: 'Bokeo' },\n  { code: 'LA-BL', countryCode: 'LA', name: 'Bolikhamxai' },\n  { code: 'LA-CH', countryCode: 'LA', name: 'Champasak' },\n  { code: 'LA-HO', countryCode: 'LA', name: 'Houaphan' },\n  { code: 'LA-KH', countryCode: 'LA', name: 'Khammouan' },\n  { code: 'LA-LM', countryCode: 'LA', name: 'Louang Namtha' },\n  { code: 'LA-LP', countryCode: 'LA', name: 'Louangphabang' },\n  { code: 'LA-OU', countryCode: 'LA', name: 'Oudomxai' },\n  { code: 'LA-PH', countryCode: 'LA', name: 'Phongsali' },\n  { code: 'LA-SL', countryCode: 'LA', name: 'Salavan' },\n  { code: 'LA-SV', countryCode: 'LA', name: 'Savannakhet' },\n  { code: 'LA-VI', countryCode: 'LA', name: 'Viangchan' },\n  { code: 'LA-XA', countryCode: 'LA', name: 'Xaignabouli' },\n  { code: 'LA-XE', countryCode: 'LA', name: 'Xekong' },\n  { code: 'LA-XI', countryCode: 'LA', name: 'Xiangkhouang' },\n  { code: 'LB-AK', countryCode: 'LB', name: 'Aakkar' },\n  { code: 'LB-BH', countryCode: 'LB', name: 'Baalbek-Hermel' },\n  { code: 'LB-BI', countryCode: 'LB', name: 'Beqaa' },\n  { code: 'LB-BA', countryCode: 'LB', name: 'Beyrouth' },\n  { code: 'LB-AS', countryCode: 'LB', name: 'Liban-Nord' },\n  { code: 'LB-JA', countryCode: 'LB', name: 'Liban-Sud' },\n  { code: 'LB-JL', countryCode: 'LB', name: 'Mont-Liban' },\n  { code: 'LB-NA', countryCode: 'LB', name: 'Nabatiye' },\n  { code: 'LC-01', countryCode: 'LC', name: 'Anse la Raye' },\n  { code: 'LC-02', countryCode: 'LC', name: 'Castries' },\n  { code: 'LC-05', countryCode: 'LC', name: 'Dennery' },\n  { code: 'LC-06', countryCode: 'LC', name: 'Gros Islet' },\n  { code: 'LC-07', countryCode: 'LC', name: 'Laborie' },\n  { code: 'LC-08', countryCode: 'LC', name: 'Micoud' },\n  { code: 'LC-10', countryCode: 'LC', name: 'Soufriere' },\n  { code: 'LC-11', countryCode: 'LC', name: 'Vieux Fort' },\n  { code: 'LI-01', countryCode: 'LI', name: 'Balzers' },\n  { code: 'LI-02', countryCode: 'LI', name: 'Eschen' },\n  { code: 'LI-03', countryCode: 'LI', name: 'Gamprin' },\n  { code: 'LI-04', countryCode: 'LI', name: 'Mauren' },\n  { code: 'LI-05', countryCode: 'LI', name: 'Planken' },\n  { code: 'LI-06', countryCode: 'LI', name: 'Ruggell' },\n  { code: 'LI-07', countryCode: 'LI', name: 'Schaan' },\n  { code: 'LI-08', countryCode: 'LI', name: 'Schellenberg' },\n  { code: 'LI-09', countryCode: 'LI', name: 'Triesen' },\n  { code: 'LI-10', countryCode: 'LI', name: 'Triesenberg' },\n  { code: 'LI-11', countryCode: 'LI', name: 'Vaduz' },\n  { code: 'LK-2', countryCode: 'LK', name: 'Central Province' },\n  { code: 'LK-5', countryCode: 'LK', name: 'Eastern Province' },\n  { code: 'LK-7', countryCode: 'LK', name: 'North Central Province' },\n  { code: 'LK-6', countryCode: 'LK', name: 'North Western Province' },\n  { code: 'LK-4', countryCode: 'LK', name: 'Northern Province' },\n  { code: 'LK-9', countryCode: 'LK', name: 'Sabaragamuwa Province' },\n  { code: 'LK-3', countryCode: 'LK', name: 'Southern Province' },\n  { code: 'LK-8', countryCode: 'LK', name: 'Uva Province' },\n  { code: 'LK-1', countryCode: 'LK', name: 'Western Province' },\n  { code: 'LR-BM', countryCode: 'LR', name: 'Bomi' },\n  { code: 'LR-BG', countryCode: 'LR', name: 'Bong' },\n  { code: 'LR-GP', countryCode: 'LR', name: 'Gbarpolu' },\n  { code: 'LR-GB', countryCode: 'LR', name: 'Grand Bassa' },\n  { code: 'LR-CM', countryCode: 'LR', name: 'Grand Cape Mount' },\n  { code: 'LR-GG', countryCode: 'LR', name: 'Grand Gedeh' },\n  { code: 'LR-GK', countryCode: 'LR', name: 'Grand Kru' },\n  { code: 'LR-LO', countryCode: 'LR', name: 'Lofa' },\n  { code: 'LR-MG', countryCode: 'LR', name: 'Margibi' },\n  { code: 'LR-MY', countryCode: 'LR', name: 'Maryland' },\n  { code: 'LR-MO', countryCode: 'LR', name: 'Montserrado' },\n  { code: 'LR-NI', countryCode: 'LR', name: 'Nimba' },\n  { code: 'LR-RI', countryCode: 'LR', name: 'River Cess' },\n  { code: 'LR-RG', countryCode: 'LR', name: 'River Gee' },\n  { code: 'LR-SI', countryCode: 'LR', name: 'Sinoe' },\n  { code: 'LS-D', countryCode: 'LS', name: 'Berea' },\n  { code: 'LS-B', countryCode: 'LS', name: 'Butha-Buthe' },\n  { code: 'LS-C', countryCode: 'LS', name: 'Leribe' },\n  { code: 'LS-E', countryCode: 'LS', name: 'Mafeteng' },\n  { code: 'LS-A', countryCode: 'LS', name: 'Maseru' },\n  { code: 'LS-F', countryCode: 'LS', name: \"Mohale's Hoek\" },\n  { code: 'LS-J', countryCode: 'LS', name: 'Mokhotlong' },\n  { code: 'LS-H', countryCode: 'LS', name: \"Qacha's Nek\" },\n  { code: 'LS-G', countryCode: 'LS', name: 'Quthing' },\n  { code: 'LS-K', countryCode: 'LS', name: 'Thaba-Tseka' },\n  { code: 'LT-AL', countryCode: 'LT', name: 'Alytaus apskritis' },\n  { code: 'LT-KU', countryCode: 'LT', name: 'Kauno apskritis' },\n  { code: 'LT-KL', countryCode: 'LT', name: 'Klaipedos apskritis' },\n  { code: 'LT-MR', countryCode: 'LT', name: 'Marijampoles apskritis' },\n  { code: 'LT-PN', countryCode: 'LT', name: 'Panevezio apskritis' },\n  { code: 'LT-SA', countryCode: 'LT', name: 'Siauliu apskritis' },\n  { code: 'LT-TA', countryCode: 'LT', name: 'Taurages apskritis' },\n  { code: 'LT-TE', countryCode: 'LT', name: 'Telsiu apskritis' },\n  { code: 'LT-UT', countryCode: 'LT', name: 'Utenos apskritis' },\n  { code: 'LT-VL', countryCode: 'LT', name: 'Vilniaus apskritis' },\n  { code: 'LU-DI', countryCode: 'LU', name: 'Diekirch' },\n  { code: 'LU-GR', countryCode: 'LU', name: 'Grevenmacher' },\n  { code: 'LU-LU', countryCode: 'LU', name: 'Luxembourg' },\n  { code: 'LV-011', countryCode: 'LV', name: 'Adazu novads' },\n  { code: 'LV-001', countryCode: 'LV', name: 'Aglonas novads' },\n  { code: 'LV-002', countryCode: 'LV', name: 'Aizkraukles novads' },\n  { code: 'LV-003', countryCode: 'LV', name: 'Aizputes novads' },\n  { code: 'LV-005', countryCode: 'LV', name: 'Alojas novads' },\n  { code: 'LV-007', countryCode: 'LV', name: 'Aluksnes novads' },\n  { code: 'LV-012', countryCode: 'LV', name: 'Babites novads' },\n  { code: 'LV-014', countryCode: 'LV', name: 'Baltinavas novads' },\n  { code: 'LV-015', countryCode: 'LV', name: 'Balvu novads' },\n  { code: 'LV-016', countryCode: 'LV', name: 'Bauskas novads' },\n  { code: 'LV-017', countryCode: 'LV', name: 'Beverinas novads' },\n  { code: 'LV-018', countryCode: 'LV', name: 'Brocenu novads' },\n  { code: 'LV-020', countryCode: 'LV', name: 'Carnikavas novads' },\n  { code: 'LV-022', countryCode: 'LV', name: 'Cesu novads' },\n  { code: 'LV-021', countryCode: 'LV', name: 'Cesvaines novads' },\n  { code: 'LV-023', countryCode: 'LV', name: 'Ciblas novads' },\n  { code: 'LV-025', countryCode: 'LV', name: 'Daugavpils novads' },\n  { code: 'LV-026', countryCode: 'LV', name: 'Dobeles novads' },\n  { code: 'LV-027', countryCode: 'LV', name: 'Dundagas novads' },\n  { code: 'LV-033', countryCode: 'LV', name: 'Gulbenes novads' },\n  { code: 'LV-034', countryCode: 'LV', name: 'Iecavas novads' },\n  { code: 'LV-037', countryCode: 'LV', name: 'Incukalna novads' },\n  { code: 'LV-038', countryCode: 'LV', name: 'Jaunjelgavas novads' },\n  { code: 'LV-039', countryCode: 'LV', name: 'Jaunpiebalgas novads' },\n  { code: 'LV-040', countryCode: 'LV', name: 'Jaunpils novads' },\n  { code: 'LV-042', countryCode: 'LV', name: 'Jekabpils novads' },\n  { code: 'LV-JEL', countryCode: 'LV', name: 'Jelgava' },\n  { code: 'LV-041', countryCode: 'LV', name: 'Jelgavas novads' },\n  { code: 'LV-JUR', countryCode: 'LV', name: 'Jurmala' },\n  { code: 'LV-052', countryCode: 'LV', name: 'Kekavas novads' },\n  { code: 'LV-046', countryCode: 'LV', name: 'Kokneses novads' },\n  { code: 'LV-047', countryCode: 'LV', name: 'Kraslavas novads' },\n  { code: 'LV-050', countryCode: 'LV', name: 'Kuldigas novads' },\n  { code: 'LV-LPX', countryCode: 'LV', name: 'Liepaja' },\n  { code: 'LV-054', countryCode: 'LV', name: 'Limbazu novads' },\n  { code: 'LV-057', countryCode: 'LV', name: 'Lubanas novads' },\n  { code: 'LV-058', countryCode: 'LV', name: 'Ludzas novads' },\n  { code: 'LV-059', countryCode: 'LV', name: 'Madonas novads' },\n  { code: 'LV-061', countryCode: 'LV', name: 'Malpils novads' },\n  { code: 'LV-067', countryCode: 'LV', name: 'Ogres novads' },\n  { code: 'LV-068', countryCode: 'LV', name: 'Olaines novads' },\n  { code: 'LV-069', countryCode: 'LV', name: 'Ozolnieku novads' },\n  { code: 'LV-073', countryCode: 'LV', name: 'Preilu novads' },\n  { code: 'LV-077', countryCode: 'LV', name: 'Rezeknes novads' },\n  { code: 'LV-RIX', countryCode: 'LV', name: 'Riga' },\n  { code: 'LV-079', countryCode: 'LV', name: 'Rojas novads' },\n  { code: 'LV-080', countryCode: 'LV', name: 'Ropazu novads' },\n  { code: 'LV-082', countryCode: 'LV', name: 'Rugaju novads' },\n  { code: 'LV-083', countryCode: 'LV', name: 'Rundales novads' },\n  { code: 'LV-086', countryCode: 'LV', name: 'Salacgrivas novads' },\n  { code: 'LV-088', countryCode: 'LV', name: 'Saldus novads' },\n  { code: 'LV-090', countryCode: 'LV', name: 'Sejas novads' },\n  { code: 'LV-091', countryCode: 'LV', name: 'Siguldas novads' },\n  { code: 'LV-093', countryCode: 'LV', name: 'Skrundas novads' },\n  { code: 'LV-095', countryCode: 'LV', name: 'Stopinu novads' },\n  { code: 'LV-096', countryCode: 'LV', name: 'Strencu novads' },\n  { code: 'LV-097', countryCode: 'LV', name: 'Talsu novads' },\n  { code: 'LV-099', countryCode: 'LV', name: 'Tukuma novads' },\n  { code: 'LV-100', countryCode: 'LV', name: 'Vainodes novads' },\n  { code: 'LV-101', countryCode: 'LV', name: 'Valkas novads' },\n  { code: 'LV-VMR', countryCode: 'LV', name: 'Valmiera' },\n  { code: 'LV-103', countryCode: 'LV', name: 'Varkavas novads' },\n  { code: 'LV-105', countryCode: 'LV', name: 'Vecumnieku novads' },\n  { code: 'LV-106', countryCode: 'LV', name: 'Ventspils novads' },\n  { code: 'LY-BU', countryCode: 'LY', name: 'Al Butnan' },\n  { code: 'LY-JA', countryCode: 'LY', name: 'Al Jabal al Akhdar' },\n  { code: 'LY-JG', countryCode: 'LY', name: 'Al Jabal al Gharbi' },\n  { code: 'LY-JI', countryCode: 'LY', name: 'Al Jafarah' },\n  { code: 'LY-JU', countryCode: 'LY', name: 'Al Jufrah' },\n  { code: 'LY-KF', countryCode: 'LY', name: 'Al Kufrah' },\n  { code: 'LY-MJ', countryCode: 'LY', name: 'Al Marj' },\n  { code: 'LY-MB', countryCode: 'LY', name: 'Al Marqab' },\n  { code: 'LY-WA', countryCode: 'LY', name: 'Al Wahat' },\n  { code: 'LY-NQ', countryCode: 'LY', name: 'An Nuqat al Khams' },\n  { code: 'LY-ZA', countryCode: 'LY', name: 'Az Zawiyah' },\n  { code: 'LY-BA', countryCode: 'LY', name: 'Banghazi' },\n  { code: 'LY-DR', countryCode: 'LY', name: 'Darnah' },\n  { code: 'LY-GT', countryCode: 'LY', name: 'Ghat' },\n  { code: 'LY-MI', countryCode: 'LY', name: 'Misratah' },\n  { code: 'LY-MQ', countryCode: 'LY', name: 'Murzuq' },\n  { code: 'LY-NL', countryCode: 'LY', name: 'Nalut' },\n  { code: 'LY-SB', countryCode: 'LY', name: 'Sabha' },\n  { code: 'LY-SR', countryCode: 'LY', name: 'Surt' },\n  { code: 'LY-TB', countryCode: 'LY', name: 'Tarabulus' },\n  { code: 'LY-WD', countryCode: 'LY', name: 'Wadi al Hayat' },\n  { code: 'LY-WS', countryCode: 'LY', name: \"Wadi ash Shati'\" },\n  { code: 'MA-09', countryCode: 'MA', name: 'Chaouia-Ouardigha' },\n  { code: 'MA-10', countryCode: 'MA', name: 'Doukhala-Abda' },\n  { code: 'MA-05', countryCode: 'MA', name: 'Fes-Boulemane' },\n  { code: 'MA-02', countryCode: 'MA', name: 'Gharb-Chrarda-Beni Hssen' },\n  { code: 'MA-08', countryCode: 'MA', name: 'Grand Casablanca' },\n  { code: 'MA-14', countryCode: 'MA', name: 'Guelmim-Es Semara' },\n  { code: 'MA-04', countryCode: 'MA', name: \"L'Oriental\" },\n  { code: 'MA-11', countryCode: 'MA', name: 'Marrakech-Tensift-Al Haouz' },\n  { code: 'MA-06', countryCode: 'MA', name: 'Meknes-Tafilalet' },\n  { code: 'MA-07', countryCode: 'MA', name: 'Rabat-Sale-Zemmour-Zaer' },\n  { code: 'MA-13', countryCode: 'MA', name: 'Souss-Massa-Draa' },\n  { code: 'MA-12', countryCode: 'MA', name: 'Tadla-Azilal' },\n  { code: 'MA-01', countryCode: 'MA', name: 'Tanger-Tetouan' },\n  { code: 'MA-03', countryCode: 'MA', name: 'Taza-Al Hoceima-Taounate' },\n  { code: 'MC-FO', countryCode: 'MC', name: 'Fontvieille' },\n  { code: 'MC-CO', countryCode: 'MC', name: 'La Condamine' },\n  { code: 'MC-MO', countryCode: 'MC', name: 'Monaco-Ville' },\n  { code: 'MC-MG', countryCode: 'MC', name: 'Moneghetti' },\n  { code: 'MC-MC', countryCode: 'MC', name: 'Monte-Carlo' },\n  { code: 'MC-SR', countryCode: 'MC', name: 'Saint-Roman' },\n  { code: 'MD-AN', countryCode: 'MD', name: 'Anenii Noi' },\n  { code: 'MD-BA', countryCode: 'MD', name: 'Balti' },\n  { code: 'MD-BS', countryCode: 'MD', name: 'Basarabeasca' },\n  { code: 'MD-BD', countryCode: 'MD', name: 'Bender' },\n  { code: 'MD-BR', countryCode: 'MD', name: 'Briceni' },\n  { code: 'MD-CA', countryCode: 'MD', name: 'Cahul' },\n  { code: 'MD-CL', countryCode: 'MD', name: 'Calarasi' },\n  { code: 'MD-CT', countryCode: 'MD', name: 'Cantemir' },\n  { code: 'MD-CS', countryCode: 'MD', name: 'Causeni' },\n  { code: 'MD-CU', countryCode: 'MD', name: 'Chisinau' },\n  { code: 'MD-CM', countryCode: 'MD', name: 'Cimislia' },\n  { code: 'MD-CR', countryCode: 'MD', name: 'Criuleni' },\n  { code: 'MD-DO', countryCode: 'MD', name: 'Donduseni' },\n  { code: 'MD-DR', countryCode: 'MD', name: 'Drochia' },\n  { code: 'MD-DU', countryCode: 'MD', name: 'Dubasari' },\n  { code: 'MD-ED', countryCode: 'MD', name: 'Edinet' },\n  { code: 'MD-FA', countryCode: 'MD', name: 'Falesti' },\n  { code: 'MD-FL', countryCode: 'MD', name: 'Floresti' },\n  {\n    code: 'MD-GA',\n    countryCode: 'MD',\n    name: 'Gagauzia, Unitatea teritoriala autonoma'\n  },\n  { code: 'MD-GL', countryCode: 'MD', name: 'Glodeni' },\n  { code: 'MD-HI', countryCode: 'MD', name: 'Hincesti' },\n  { code: 'MD-IA', countryCode: 'MD', name: 'Ialoveni' },\n  { code: 'MD-LE', countryCode: 'MD', name: 'Leova' },\n  { code: 'MD-NI', countryCode: 'MD', name: 'Nisporeni' },\n  { code: 'MD-OC', countryCode: 'MD', name: 'Ocnita' },\n  { code: 'MD-OR', countryCode: 'MD', name: 'Orhei' },\n  { code: 'MD-RE', countryCode: 'MD', name: 'Rezina' },\n  { code: 'MD-RI', countryCode: 'MD', name: 'Riscani' },\n  { code: 'MD-SI', countryCode: 'MD', name: 'Singerei' },\n  { code: 'MD-SD', countryCode: 'MD', name: 'Soldanesti' },\n  { code: 'MD-SO', countryCode: 'MD', name: 'Soroca' },\n  { code: 'MD-SV', countryCode: 'MD', name: 'Stefan Voda' },\n  {\n    code: 'MD-SN',\n    countryCode: 'MD',\n    name: 'Stinga Nistrului, unitatea teritoriala din'\n  },\n  { code: 'MD-ST', countryCode: 'MD', name: 'Straseni' },\n  { code: 'MD-TA', countryCode: 'MD', name: 'Taraclia' },\n  { code: 'MD-TE', countryCode: 'MD', name: 'Telenesti' },\n  { code: 'MD-UN', countryCode: 'MD', name: 'Ungheni' },\n  { code: 'ME-02', countryCode: 'ME', name: 'Bar' },\n  { code: 'ME-05', countryCode: 'ME', name: 'Budva' },\n  { code: 'ME-06', countryCode: 'ME', name: 'Cetinje' },\n  { code: 'ME-07', countryCode: 'ME', name: 'Danilovgrad' },\n  { code: 'ME-08', countryCode: 'ME', name: 'Herceg-Novi' },\n  { code: 'ME-09', countryCode: 'ME', name: 'Kolasin' },\n  { code: 'ME-10', countryCode: 'ME', name: 'Kotor' },\n  { code: 'ME-11', countryCode: 'ME', name: 'Mojkovac' },\n  { code: 'ME-12', countryCode: 'ME', name: 'Niksic' },\n  { code: 'ME-16', countryCode: 'ME', name: 'Podgorica' },\n  { code: 'ME-19', countryCode: 'ME', name: 'Tivat' },\n  { code: 'ME-20', countryCode: 'ME', name: 'Ulcinj' },\n  { code: 'ME-21', countryCode: 'ME', name: 'Zabljak' },\n  { code: 'MG-T', countryCode: 'MG', name: 'Antananarivo' },\n  { code: 'MG-D', countryCode: 'MG', name: 'Antsiranana' },\n  { code: 'MG-F', countryCode: 'MG', name: 'Fianarantsoa' },\n  { code: 'MG-M', countryCode: 'MG', name: 'Mahajanga' },\n  { code: 'MG-A', countryCode: 'MG', name: 'Toamasina' },\n  { code: 'MG-U', countryCode: 'MG', name: 'Toliara' },\n  { code: 'MH-ALL', countryCode: 'MH', name: 'Ailinglaplap' },\n  { code: 'MH-ALK', countryCode: 'MH', name: 'Ailuk' },\n  { code: 'MH-ARN', countryCode: 'MH', name: 'Arno' },\n  { code: 'MH-AUR', countryCode: 'MH', name: 'Aur' },\n  { code: 'MH-KIL', countryCode: 'MH', name: 'Bikini and Kili' },\n  { code: 'MH-EBO', countryCode: 'MH', name: 'Ebon' },\n  { code: 'MH-ENI', countryCode: 'MH', name: 'Enewetak and Ujelang' },\n  { code: 'MH-JAB', countryCode: 'MH', name: 'Jabat' },\n  { code: 'MH-JAL', countryCode: 'MH', name: 'Jaluit' },\n  { code: 'MH-KWA', countryCode: 'MH', name: 'Kwajalein' },\n  { code: 'MH-LAE', countryCode: 'MH', name: 'Lae' },\n  { code: 'MH-LIB', countryCode: 'MH', name: 'Lib' },\n  { code: 'MH-LIK', countryCode: 'MH', name: 'Likiep' },\n  { code: 'MH-MAJ', countryCode: 'MH', name: 'Majuro' },\n  { code: 'MH-MAL', countryCode: 'MH', name: 'Maloelap' },\n  { code: 'MH-MEJ', countryCode: 'MH', name: 'Mejit' },\n  { code: 'MH-MIL', countryCode: 'MH', name: 'Mili' },\n  { code: 'MH-NMK', countryCode: 'MH', name: 'Namdrik' },\n  { code: 'MH-NMU', countryCode: 'MH', name: 'Namu' },\n  { code: 'MH-RON', countryCode: 'MH', name: 'Rongelap' },\n  { code: 'MH-UJA', countryCode: 'MH', name: 'Ujae' },\n  { code: 'MH-UTI', countryCode: 'MH', name: 'Utrik' },\n  { code: 'MH-WTH', countryCode: 'MH', name: 'Wotho' },\n  { code: 'MH-WTJ', countryCode: 'MH', name: 'Wotje' },\n  { code: 'MK-02', countryCode: 'MK', name: 'Aracinovo' },\n  { code: 'MK-03', countryCode: 'MK', name: 'Berovo' },\n  { code: 'MK-04', countryCode: 'MK', name: 'Bitola' },\n  { code: 'MK-05', countryCode: 'MK', name: 'Bogdanci' },\n  { code: 'MK-06', countryCode: 'MK', name: 'Bogovinje' },\n  { code: 'MK-07', countryCode: 'MK', name: 'Bosilovo' },\n  { code: 'MK-08', countryCode: 'MK', name: 'Brvenica' },\n  { code: 'MK-80', countryCode: 'MK', name: 'Caska' },\n  { code: 'MK-78', countryCode: 'MK', name: 'Centar Zupa' },\n  { code: 'MK-81', countryCode: 'MK', name: 'Cesinovo-Oblesevo' },\n  { code: 'MK-82', countryCode: 'MK', name: 'Cucer Sandevo' },\n  { code: 'MK-21', countryCode: 'MK', name: 'Debar' },\n  { code: 'MK-22', countryCode: 'MK', name: 'Debarca' },\n  { code: 'MK-23', countryCode: 'MK', name: 'Delcevo' },\n  { code: 'MK-25', countryCode: 'MK', name: 'Demir Hisar' },\n  { code: 'MK-24', countryCode: 'MK', name: 'Demir Kapija' },\n  { code: 'MK-26', countryCode: 'MK', name: 'Dojran' },\n  { code: 'MK-27', countryCode: 'MK', name: 'Dolneni' },\n  { code: 'MK-18', countryCode: 'MK', name: 'Gevgelija' },\n  { code: 'MK-19', countryCode: 'MK', name: 'Gostivar' },\n  { code: 'MK-20', countryCode: 'MK', name: 'Gradsko' },\n  { code: 'MK-34', countryCode: 'MK', name: 'Ilinden' },\n  { code: 'MK-35', countryCode: 'MK', name: 'Jegunovce' },\n  { code: 'MK-37', countryCode: 'MK', name: 'Karbinci' },\n  { code: 'MK-36', countryCode: 'MK', name: 'Kavadarci' },\n  { code: 'MK-40', countryCode: 'MK', name: 'Kicevo' },\n  { code: 'MK-42', countryCode: 'MK', name: 'Kocani' },\n  { code: 'MK-41', countryCode: 'MK', name: 'Konce' },\n  { code: 'MK-43', countryCode: 'MK', name: 'Kratovo' },\n  { code: 'MK-44', countryCode: 'MK', name: 'Kriva Palanka' },\n  { code: 'MK-45', countryCode: 'MK', name: 'Krivogastani' },\n  { code: 'MK-46', countryCode: 'MK', name: 'Krusevo' },\n  { code: 'MK-47', countryCode: 'MK', name: 'Kumanovo' },\n  { code: 'MK-48', countryCode: 'MK', name: 'Lipkovo' },\n  { code: 'MK-49', countryCode: 'MK', name: 'Lozovo' },\n  { code: 'MK-51', countryCode: 'MK', name: 'Makedonska Kamenica' },\n  { code: 'MK-52', countryCode: 'MK', name: 'Makedonski Brod' },\n  { code: 'MK-50', countryCode: 'MK', name: 'Mavrovo i Rostusa' },\n  { code: 'MK-53', countryCode: 'MK', name: 'Mogila' },\n  { code: 'MK-54', countryCode: 'MK', name: 'Negotino' },\n  { code: 'MK-55', countryCode: 'MK', name: 'Novaci' },\n  { code: 'MK-56', countryCode: 'MK', name: 'Novo Selo' },\n  { code: 'MK-58', countryCode: 'MK', name: 'Ohrid' },\n  { code: 'MK-60', countryCode: 'MK', name: 'Pehcevo' },\n  { code: 'MK-59', countryCode: 'MK', name: 'Petrovec' },\n  { code: 'MK-61', countryCode: 'MK', name: 'Plasnica' },\n  { code: 'MK-62', countryCode: 'MK', name: 'Prilep' },\n  { code: 'MK-63', countryCode: 'MK', name: 'Probistip' },\n  { code: 'MK-64', countryCode: 'MK', name: 'Radovis' },\n  { code: 'MK-65', countryCode: 'MK', name: 'Rankovce' },\n  { code: 'MK-66', countryCode: 'MK', name: 'Resen' },\n  { code: 'MK-67', countryCode: 'MK', name: 'Rosoman' },\n  { code: 'MK-85', countryCode: 'MK', name: 'Skopje' },\n  { code: 'MK-70', countryCode: 'MK', name: 'Sopiste' },\n  { code: 'MK-71', countryCode: 'MK', name: 'Staro Nagoricane' },\n  { code: 'MK-83', countryCode: 'MK', name: 'Stip' },\n  { code: 'MK-72', countryCode: 'MK', name: 'Struga' },\n  { code: 'MK-73', countryCode: 'MK', name: 'Strumica' },\n  { code: 'MK-74', countryCode: 'MK', name: 'Studenicani' },\n  { code: 'MK-69', countryCode: 'MK', name: 'Sveti Nikole' },\n  { code: 'MK-75', countryCode: 'MK', name: 'Tearce' },\n  { code: 'MK-76', countryCode: 'MK', name: 'Tetovo' },\n  { code: 'MK-10', countryCode: 'MK', name: 'Valandovo' },\n  { code: 'MK-11', countryCode: 'MK', name: 'Vasilevo' },\n  { code: 'MK-13', countryCode: 'MK', name: 'Veles' },\n  { code: 'MK-12', countryCode: 'MK', name: 'Vevcani' },\n  { code: 'MK-14', countryCode: 'MK', name: 'Vinica' },\n  { code: 'MK-16', countryCode: 'MK', name: 'Vrapciste' },\n  { code: 'MK-32', countryCode: 'MK', name: 'Zelenikovo' },\n  { code: 'MK-30', countryCode: 'MK', name: 'Zelino' },\n  { code: 'MK-33', countryCode: 'MK', name: 'Zrnovci' },\n  { code: 'ML-BKO', countryCode: 'ML', name: 'Bamako' },\n  { code: 'ML-7', countryCode: 'ML', name: 'Gao' },\n  { code: 'ML-1', countryCode: 'ML', name: 'Kayes' },\n  { code: 'ML-8', countryCode: 'ML', name: 'Kidal' },\n  { code: 'ML-2', countryCode: 'ML', name: 'Koulikoro' },\n  { code: 'ML-5', countryCode: 'ML', name: 'Mopti' },\n  { code: 'ML-4', countryCode: 'ML', name: 'Segou' },\n  { code: 'ML-3', countryCode: 'ML', name: 'Sikasso' },\n  { code: 'ML-6', countryCode: 'ML', name: 'Tombouctou' },\n  { code: 'MM-07', countryCode: 'MM', name: 'Ayeyarwady' },\n  { code: 'MM-02', countryCode: 'MM', name: 'Bago' },\n  { code: 'MM-14', countryCode: 'MM', name: 'Chin' },\n  { code: 'MM-11', countryCode: 'MM', name: 'Kachin' },\n  { code: 'MM-12', countryCode: 'MM', name: 'Kayah' },\n  { code: 'MM-13', countryCode: 'MM', name: 'Kayin' },\n  { code: 'MM-03', countryCode: 'MM', name: 'Magway' },\n  { code: 'MM-04', countryCode: 'MM', name: 'Mandalay' },\n  { code: 'MM-15', countryCode: 'MM', name: 'Mon' },\n  { code: 'MM-18', countryCode: 'MM', name: 'Nay Pyi Taw' },\n  { code: 'MM-16', countryCode: 'MM', name: 'Rakhine' },\n  { code: 'MM-01', countryCode: 'MM', name: 'Sagaing' },\n  { code: 'MM-17', countryCode: 'MM', name: 'Shan' },\n  { code: 'MM-05', countryCode: 'MM', name: 'Tanintharyi' },\n  { code: 'MM-06', countryCode: 'MM', name: 'Yangon' },\n  { code: 'MN-073', countryCode: 'MN', name: 'Arhangay' },\n  { code: 'MN-071', countryCode: 'MN', name: 'Bayan-Olgiy' },\n  { code: 'MN-069', countryCode: 'MN', name: 'Bayanhongor' },\n  { code: 'MN-067', countryCode: 'MN', name: 'Bulgan' },\n  { code: 'MN-037', countryCode: 'MN', name: 'Darhan uul' },\n  { code: 'MN-061', countryCode: 'MN', name: 'Dornod' },\n  { code: 'MN-063', countryCode: 'MN', name: 'Dornogovi' },\n  { code: 'MN-059', countryCode: 'MN', name: 'Dundgovi' },\n  { code: 'MN-057', countryCode: 'MN', name: 'Dzavhan' },\n  { code: 'MN-065', countryCode: 'MN', name: 'Govi-Altay' },\n  { code: 'MN-064', countryCode: 'MN', name: 'Govi-Sumber' },\n  { code: 'MN-039', countryCode: 'MN', name: 'Hentiy' },\n  { code: 'MN-043', countryCode: 'MN', name: 'Hovd' },\n  { code: 'MN-041', countryCode: 'MN', name: 'Hovsgol' },\n  { code: 'MN-053', countryCode: 'MN', name: 'Omnogovi' },\n  { code: 'MN-035', countryCode: 'MN', name: 'Orhon' },\n  { code: 'MN-055', countryCode: 'MN', name: 'Ovorhangay' },\n  { code: 'MN-049', countryCode: 'MN', name: 'Selenge' },\n  { code: 'MN-051', countryCode: 'MN', name: 'Suhbaatar' },\n  { code: 'MN-047', countryCode: 'MN', name: 'Tov' },\n  { code: 'MN-1', countryCode: 'MN', name: 'Ulaanbaatar' },\n  { code: 'MN-046', countryCode: 'MN', name: 'Uvs' },\n  { code: 'MR-07', countryCode: 'MR', name: 'Adrar' },\n  { code: 'MR-03', countryCode: 'MR', name: 'Assaba' },\n  { code: 'MR-05', countryCode: 'MR', name: 'Brakna' },\n  { code: 'MR-08', countryCode: 'MR', name: 'Dakhlet Nouadhibou' },\n  { code: 'MR-04', countryCode: 'MR', name: 'Gorgol' },\n  { code: 'MR-10', countryCode: 'MR', name: 'Guidimaka' },\n  { code: 'MR-01', countryCode: 'MR', name: 'Hodh ech Chargui' },\n  { code: 'MR-02', countryCode: 'MR', name: 'Hodh el Gharbi' },\n  { code: 'MR-12', countryCode: 'MR', name: 'Inchiri' },\n  { code: 'MR-14', countryCode: 'MR', name: 'Nouakchott Nord' },\n  { code: 'MR-09', countryCode: 'MR', name: 'Tagant' },\n  { code: 'MR-11', countryCode: 'MR', name: 'Tiris Zemmour' },\n  { code: 'MR-06', countryCode: 'MR', name: 'Trarza' },\n  { code: 'MT-01', countryCode: 'MT', name: 'Attard' },\n  { code: 'MT-02', countryCode: 'MT', name: 'Balzan' },\n  { code: 'MT-04', countryCode: 'MT', name: 'Birkirkara' },\n  { code: 'MT-05', countryCode: 'MT', name: 'Birzebbuga' },\n  { code: 'MT-06', countryCode: 'MT', name: 'Bormla' },\n  { code: 'MT-07', countryCode: 'MT', name: 'Dingli' },\n  { code: 'MT-13', countryCode: 'MT', name: 'Ghajnsielem' },\n  { code: 'MT-15', countryCode: 'MT', name: 'Gharghur' },\n  { code: 'MT-17', countryCode: 'MT', name: 'Ghaxaq' },\n  { code: 'MT-64', countryCode: 'MT', name: 'Haz-Zabbar' },\n  { code: 'MT-60', countryCode: 'MT', name: 'Valletta' },\n  { code: 'MT-03', countryCode: 'MT', name: 'Birgu' },\n  { code: 'MT-08', countryCode: 'MT', name: 'Fgura' },\n  { code: 'MT-09', countryCode: 'MT', name: 'Floriana' },\n  { code: 'MT-11', countryCode: 'MT', name: 'Gudja' },\n  { code: 'MT-18', countryCode: 'MT', name: 'Hamrun' },\n  { code: 'MT-21', countryCode: 'MT', name: 'Kalkara' },\n  { code: 'MT-26', countryCode: 'MT', name: 'Marsa' },\n  { code: 'MT-30', countryCode: 'MT', name: 'Mellieha' },\n  { code: 'MT-32', countryCode: 'MT', name: 'Mosta' },\n  { code: 'MT-42', countryCode: 'MT', name: 'Qala' },\n  { code: 'MT-44', countryCode: 'MT', name: 'Qrendi' },\n  { code: 'MT-37', countryCode: 'MT', name: 'Nadur' },\n  { code: 'MT-38', countryCode: 'MT', name: 'Naxxar' },\n  { code: 'MT-46', countryCode: 'MT', name: 'Rabat Malta' },\n  { code: 'MT-55', countryCode: 'MT', name: 'Siggiewi' },\n  { code: 'MT-57', countryCode: 'MT', name: 'Swieqi' },\n  { code: 'MT-61', countryCode: 'MT', name: 'Xaghra' },\n  { code: 'MT-62', countryCode: 'MT', name: 'Xewkija' },\n  { code: 'MT-65', countryCode: 'MT', name: 'Zebbug Gozo' },\n  { code: 'MT-67', countryCode: 'MT', name: 'Zejtun' },\n  { code: 'MT-68', countryCode: 'MT', name: 'Zurrieq' },\n  { code: 'MT-23', countryCode: 'MT', name: 'Kirkop' },\n  { code: 'MT-19', countryCode: 'MT', name: 'Iklin' },\n  { code: 'MT-33', countryCode: 'MT', name: 'Mqabba' },\n  { code: 'MT-34', countryCode: 'MT', name: 'Msida' },\n  { code: 'MT-20', countryCode: 'MT', name: 'Isla' },\n  { code: 'MT-24', countryCode: 'MT', name: 'Lija' },\n  { code: 'MT-25', countryCode: 'MT', name: 'Luqa' },\n  { code: 'MT-28', countryCode: 'MT', name: 'Marsaxlokk' },\n  { code: 'MT-39', countryCode: 'MT', name: 'Paola' },\n  { code: 'MT-43', countryCode: 'MT', name: 'Qormi' },\n  { code: 'MT-47', countryCode: 'MT', name: 'Safi' },\n  { code: 'MT-49', countryCode: 'MT', name: 'Saint John' },\n  { code: 'MT-48', countryCode: 'MT', name: 'Saint Julian' },\n  { code: 'MT-53', countryCode: 'MT', name: 'Saint Lucia' },\n  { code: 'MT-51', countryCode: 'MT', name: \"Saint Paul's Bay\" },\n  { code: 'MT-54', countryCode: 'MT', name: 'Saint Venera' },\n  { code: 'MT-52', countryCode: 'MT', name: 'Sannat' },\n  { code: 'MT-22', countryCode: 'MT', name: 'Kercem' },\n  { code: 'MT-58', countryCode: 'MT', name: \"Ta' Xbiex\" },\n  { code: 'MT-59', countryCode: 'MT', name: 'Tarxien' },\n  { code: 'MT-56', countryCode: 'MT', name: 'Sliema' },\n  { code: 'MT-45', countryCode: 'MT', name: 'Rabat Gozo' },\n  { code: 'MU-BL', countryCode: 'MU', name: 'Black River' },\n  { code: 'MU-FL', countryCode: 'MU', name: 'Flacq' },\n  { code: 'MU-GP', countryCode: 'MU', name: 'Grand Port' },\n  { code: 'MU-MO', countryCode: 'MU', name: 'Moka' },\n  { code: 'MU-PA', countryCode: 'MU', name: 'Pamplemousses' },\n  { code: 'MU-PW', countryCode: 'MU', name: 'Plaines Wilhems' },\n  { code: 'MU-PU', countryCode: 'MU', name: 'Port Louis' },\n  { code: 'MU-RR', countryCode: 'MU', name: 'Riviere du Rempart' },\n  { code: 'MU-SA', countryCode: 'MU', name: 'Savanne' },\n  { code: 'MV-02', countryCode: 'MV', name: 'Alifu Alifu' },\n  { code: 'MV-20', countryCode: 'MV', name: 'Baa' },\n  { code: 'MV-17', countryCode: 'MV', name: 'Dhaalu' },\n  { code: 'MV-28', countryCode: 'MV', name: 'Gaafu Dhaalu' },\n  { code: 'MV-07', countryCode: 'MV', name: 'Haa Alifu' },\n  { code: 'MV-23', countryCode: 'MV', name: 'Haa Dhaalu' },\n  { code: 'MV-26', countryCode: 'MV', name: 'Kaafu' },\n  { code: 'MV-05', countryCode: 'MV', name: 'Laamu' },\n  { code: 'MV-MLE', countryCode: 'MV', name: 'Maale' },\n  { code: 'MV-12', countryCode: 'MV', name: 'Meemu' },\n  { code: 'MV-25', countryCode: 'MV', name: 'Noonu' },\n  { code: 'MV-13', countryCode: 'MV', name: 'Raa' },\n  { code: 'MV-01', countryCode: 'MV', name: 'Seenu' },\n  { code: 'MV-24', countryCode: 'MV', name: 'Shaviyani' },\n  { code: 'MV-08', countryCode: 'MV', name: 'Thaa' },\n  { code: 'MW-BA', countryCode: 'MW', name: 'Balaka' },\n  { code: 'MW-BL', countryCode: 'MW', name: 'Blantyre' },\n  { code: 'MW-CK', countryCode: 'MW', name: 'Chikwawa' },\n  { code: 'MW-CR', countryCode: 'MW', name: 'Chiradzulu' },\n  { code: 'MW-CT', countryCode: 'MW', name: 'Chitipa' },\n  { code: 'MW-DE', countryCode: 'MW', name: 'Dedza' },\n  { code: 'MW-DO', countryCode: 'MW', name: 'Dowa' },\n  { code: 'MW-KR', countryCode: 'MW', name: 'Karonga' },\n  { code: 'MW-KS', countryCode: 'MW', name: 'Kasungu' },\n  { code: 'MW-LK', countryCode: 'MW', name: 'Likoma' },\n  { code: 'MW-LI', countryCode: 'MW', name: 'Lilongwe' },\n  { code: 'MW-MH', countryCode: 'MW', name: 'Machinga' },\n  { code: 'MW-MG', countryCode: 'MW', name: 'Mangochi' },\n  { code: 'MW-MC', countryCode: 'MW', name: 'Mchinji' },\n  { code: 'MW-MU', countryCode: 'MW', name: 'Mulanje' },\n  { code: 'MW-MW', countryCode: 'MW', name: 'Mwanza' },\n  { code: 'MW-MZ', countryCode: 'MW', name: 'Mzimba' },\n  { code: 'MW-NE', countryCode: 'MW', name: 'Neno' },\n  { code: 'MW-NB', countryCode: 'MW', name: 'Nkhata Bay' },\n  { code: 'MW-NK', countryCode: 'MW', name: 'Nkhotakota' },\n  { code: 'MW-NS', countryCode: 'MW', name: 'Nsanje' },\n  { code: 'MW-NU', countryCode: 'MW', name: 'Ntcheu' },\n  { code: 'MW-NI', countryCode: 'MW', name: 'Ntchisi' },\n  { code: 'MW-PH', countryCode: 'MW', name: 'Phalombe' },\n  { code: 'MW-RU', countryCode: 'MW', name: 'Rumphi' },\n  { code: 'MW-SA', countryCode: 'MW', name: 'Salima' },\n  { code: 'MW-TH', countryCode: 'MW', name: 'Thyolo' },\n  { code: 'MW-ZO', countryCode: 'MW', name: 'Zomba' },\n  { code: 'MX-AGU', countryCode: 'MX', name: 'Aguascalientes' },\n  { code: 'MX-BCN', countryCode: 'MX', name: 'Baja California' },\n  { code: 'MX-BCS', countryCode: 'MX', name: 'Baja California Sur' },\n  { code: 'MX-CAM', countryCode: 'MX', name: 'Campeche' },\n  { code: 'MX-CHP', countryCode: 'MX', name: 'Chiapas' },\n  { code: 'MX-CHH', countryCode: 'MX', name: 'Chihuahua' },\n  { code: 'MX-CMX', countryCode: 'MX', name: 'Ciudad de Mexico' },\n  { code: 'MX-COA', countryCode: 'MX', name: 'Coahuila de Zaragoza' },\n  { code: 'MX-COL', countryCode: 'MX', name: 'Colima' },\n  { code: 'MX-DUR', countryCode: 'MX', name: 'Durango' },\n  { code: 'MX-GUA', countryCode: 'MX', name: 'Guanajuato' },\n  { code: 'MX-GRO', countryCode: 'MX', name: 'Guerrero' },\n  { code: 'MX-HID', countryCode: 'MX', name: 'Hidalgo' },\n  { code: 'MX-JAL', countryCode: 'MX', name: 'Jalisco' },\n  { code: 'MX-MEX', countryCode: 'MX', name: 'Mexico' },\n  { code: 'MX-MIC', countryCode: 'MX', name: 'Michoacan de Ocampo' },\n  { code: 'MX-MOR', countryCode: 'MX', name: 'Morelos' },\n  { code: 'MX-NAY', countryCode: 'MX', name: 'Nayarit' },\n  { code: 'MX-NLE', countryCode: 'MX', name: 'Nuevo Leon' },\n  { code: 'MX-OAX', countryCode: 'MX', name: 'Oaxaca' },\n  { code: 'MX-PUE', countryCode: 'MX', name: 'Puebla' },\n  { code: 'MX-QUE', countryCode: 'MX', name: 'Queretaro' },\n  { code: 'MX-ROO', countryCode: 'MX', name: 'Quintana Roo' },\n  { code: 'MX-SLP', countryCode: 'MX', name: 'San Luis Potosi' },\n  { code: 'MX-SIN', countryCode: 'MX', name: 'Sinaloa' },\n  { code: 'MX-SON', countryCode: 'MX', name: 'Sonora' },\n  { code: 'MX-TAB', countryCode: 'MX', name: 'Tabasco' },\n  { code: 'MX-TAM', countryCode: 'MX', name: 'Tamaulipas' },\n  { code: 'MX-TLA', countryCode: 'MX', name: 'Tlaxcala' },\n  {\n    code: 'MX-VER',\n    countryCode: 'MX',\n    name: 'Veracruz de Ignacio de la Llave'\n  },\n  { code: 'MX-YUC', countryCode: 'MX', name: 'Yucatan' },\n  { code: 'MX-ZAC', countryCode: 'MX', name: 'Zacatecas' },\n  { code: 'MY-01', countryCode: 'MY', name: 'Johor' },\n  { code: 'MY-02', countryCode: 'MY', name: 'Kedah' },\n  { code: 'MY-03', countryCode: 'MY', name: 'Kelantan' },\n  { code: 'MY-04', countryCode: 'MY', name: 'Melaka' },\n  { code: 'MY-05', countryCode: 'MY', name: 'Negeri Sembilan' },\n  { code: 'MY-06', countryCode: 'MY', name: 'Pahang' },\n  { code: 'MY-08', countryCode: 'MY', name: 'Perak' },\n  { code: 'MY-09', countryCode: 'MY', name: 'Perlis' },\n  { code: 'MY-07', countryCode: 'MY', name: 'Pulau Pinang' },\n  { code: 'MY-12', countryCode: 'MY', name: 'Sabah' },\n  { code: 'MY-13', countryCode: 'MY', name: 'Sarawak' },\n  { code: 'MY-10', countryCode: 'MY', name: 'Selangor' },\n  { code: 'MY-11', countryCode: 'MY', name: 'Terengganu' },\n  {\n    code: 'MY-14',\n    countryCode: 'MY',\n    name: 'Wilayah Persekutuan Kuala Lumpur'\n  },\n  { code: 'MY-15', countryCode: 'MY', name: 'Wilayah Persekutuan Labuan' },\n  { code: 'MY-16', countryCode: 'MY', name: 'Wilayah Persekutuan Putrajaya' },\n  { code: 'MZ-P', countryCode: 'MZ', name: 'Cabo Delgado' },\n  { code: 'MZ-G', countryCode: 'MZ', name: 'Gaza' },\n  { code: 'MZ-I', countryCode: 'MZ', name: 'Inhambane' },\n  { code: 'MZ-B', countryCode: 'MZ', name: 'Manica' },\n  { code: 'MZ-MPM', countryCode: 'MZ', name: 'Maputo' },\n  { code: 'MZ-N', countryCode: 'MZ', name: 'Nampula' },\n  { code: 'MZ-A', countryCode: 'MZ', name: 'Niassa' },\n  { code: 'MZ-S', countryCode: 'MZ', name: 'Sofala' },\n  { code: 'MZ-T', countryCode: 'MZ', name: 'Tete' },\n  { code: 'MZ-Q', countryCode: 'MZ', name: 'Zambezia' },\n  { code: 'NA-ER', countryCode: 'NA', name: 'Erongo' },\n  { code: 'NA-HA', countryCode: 'NA', name: 'Hardap' },\n  { code: 'NA-KA', countryCode: 'NA', name: 'Karas' },\n  { code: 'NA-KE', countryCode: 'NA', name: 'Kavango East' },\n  { code: 'NA-KH', countryCode: 'NA', name: 'Khomas' },\n  { code: 'NA-KU', countryCode: 'NA', name: 'Kunene' },\n  { code: 'NA-OW', countryCode: 'NA', name: 'Ohangwena' },\n  { code: 'NA-OH', countryCode: 'NA', name: 'Omaheke' },\n  { code: 'NA-OS', countryCode: 'NA', name: 'Omusati' },\n  { code: 'NA-ON', countryCode: 'NA', name: 'Oshana' },\n  { code: 'NA-OT', countryCode: 'NA', name: 'Oshikoto' },\n  { code: 'NA-OD', countryCode: 'NA', name: 'Otjozondjupa' },\n  { code: 'NA-CA', countryCode: 'NA', name: 'Zambezi' },\n  { code: 'NE-1', countryCode: 'NE', name: 'Agadez' },\n  { code: 'NE-2', countryCode: 'NE', name: 'Diffa' },\n  { code: 'NE-3', countryCode: 'NE', name: 'Dosso' },\n  { code: 'NE-4', countryCode: 'NE', name: 'Maradi' },\n  { code: 'NE-8', countryCode: 'NE', name: 'Niamey' },\n  { code: 'NE-5', countryCode: 'NE', name: 'Tahoua' },\n  { code: 'NE-6', countryCode: 'NE', name: 'Tillaberi' },\n  { code: 'NE-7', countryCode: 'NE', name: 'Zinder' },\n  { code: 'NG-AB', countryCode: 'NG', name: 'Abia' },\n  { code: 'NG-FC', countryCode: 'NG', name: 'Abuja Federal Capital Territory' },\n  { code: 'NG-AD', countryCode: 'NG', name: 'Adamawa' },\n  { code: 'NG-AK', countryCode: 'NG', name: 'Akwa Ibom' },\n  { code: 'NG-AN', countryCode: 'NG', name: 'Anambra' },\n  { code: 'NG-BA', countryCode: 'NG', name: 'Bauchi' },\n  { code: 'NG-BY', countryCode: 'NG', name: 'Bayelsa' },\n  { code: 'NG-BE', countryCode: 'NG', name: 'Benue' },\n  { code: 'NG-BO', countryCode: 'NG', name: 'Borno' },\n  { code: 'NG-CR', countryCode: 'NG', name: 'Cross River' },\n  { code: 'NG-DE', countryCode: 'NG', name: 'Delta' },\n  { code: 'NG-EB', countryCode: 'NG', name: 'Ebonyi' },\n  { code: 'NG-ED', countryCode: 'NG', name: 'Edo' },\n  { code: 'NG-EK', countryCode: 'NG', name: 'Ekiti' },\n  { code: 'NG-EN', countryCode: 'NG', name: 'Enugu' },\n  { code: 'NG-GO', countryCode: 'NG', name: 'Gombe' },\n  { code: 'NG-IM', countryCode: 'NG', name: 'Imo' },\n  { code: 'NG-JI', countryCode: 'NG', name: 'Jigawa' },\n  { code: 'NG-KD', countryCode: 'NG', name: 'Kaduna' },\n  { code: 'NG-KN', countryCode: 'NG', name: 'Kano' },\n  { code: 'NG-KT', countryCode: 'NG', name: 'Katsina' },\n  { code: 'NG-KE', countryCode: 'NG', name: 'Kebbi' },\n  { code: 'NG-KO', countryCode: 'NG', name: 'Kogi' },\n  { code: 'NG-KW', countryCode: 'NG', name: 'Kwara' },\n  { code: 'NG-LA', countryCode: 'NG', name: 'Lagos' },\n  { code: 'NG-NA', countryCode: 'NG', name: 'Nasarawa' },\n  { code: 'NG-NI', countryCode: 'NG', name: 'Niger' },\n  { code: 'NG-OG', countryCode: 'NG', name: 'Ogun' },\n  { code: 'NG-ON', countryCode: 'NG', name: 'Ondo' },\n  { code: 'NG-OS', countryCode: 'NG', name: 'Osun' },\n  { code: 'NG-OY', countryCode: 'NG', name: 'Oyo' },\n  { code: 'NG-PL', countryCode: 'NG', name: 'Plateau' },\n  { code: 'NG-RI', countryCode: 'NG', name: 'Rivers' },\n  { code: 'NG-SO', countryCode: 'NG', name: 'Sokoto' },\n  { code: 'NG-TA', countryCode: 'NG', name: 'Taraba' },\n  { code: 'NG-YO', countryCode: 'NG', name: 'Yobe' },\n  { code: 'NG-ZA', countryCode: 'NG', name: 'Zamfara' },\n  { code: 'NI-AN', countryCode: 'NI', name: 'Atlantico Norte' },\n  { code: 'NI-AS', countryCode: 'NI', name: 'Atlantico Sur' },\n  { code: 'NI-BO', countryCode: 'NI', name: 'Boaco' },\n  { code: 'NI-CA', countryCode: 'NI', name: 'Carazo' },\n  { code: 'NI-CI', countryCode: 'NI', name: 'Chinandega' },\n  { code: 'NI-CO', countryCode: 'NI', name: 'Chontales' },\n  { code: 'NI-ES', countryCode: 'NI', name: 'Esteli' },\n  { code: 'NI-GR', countryCode: 'NI', name: 'Granada' },\n  { code: 'NI-JI', countryCode: 'NI', name: 'Jinotega' },\n  { code: 'NI-LE', countryCode: 'NI', name: 'Leon' },\n  { code: 'NI-MD', countryCode: 'NI', name: 'Madriz' },\n  { code: 'NI-MN', countryCode: 'NI', name: 'Managua' },\n  { code: 'NI-MS', countryCode: 'NI', name: 'Masaya' },\n  { code: 'NI-MT', countryCode: 'NI', name: 'Matagalpa' },\n  { code: 'NI-NS', countryCode: 'NI', name: 'Nueva Segovia' },\n  { code: 'NI-SJ', countryCode: 'NI', name: 'Rio San Juan' },\n  { code: 'NI-RI', countryCode: 'NI', name: 'Rivas' },\n  { code: 'NL-DR', countryCode: 'NL', name: 'Drenthe' },\n  { code: 'NL-FL', countryCode: 'NL', name: 'Flevoland' },\n  { code: 'NL-FR', countryCode: 'NL', name: 'Fryslan' },\n  { code: 'NL-GE', countryCode: 'NL', name: 'Gelderland' },\n  { code: 'NL-GR', countryCode: 'NL', name: 'Groningen' },\n  { code: 'NL-LI', countryCode: 'NL', name: 'Limburg' },\n  { code: 'NL-NB', countryCode: 'NL', name: 'Noord-Brabant' },\n  { code: 'NL-NH', countryCode: 'NL', name: 'Noord-Holland' },\n  { code: 'NL-OV', countryCode: 'NL', name: 'Overijssel' },\n  { code: 'NL-UT', countryCode: 'NL', name: 'Utrecht' },\n  { code: 'NL-ZE', countryCode: 'NL', name: 'Zeeland' },\n  { code: 'NL-ZH', countryCode: 'NL', name: 'Zuid-Holland' },\n  { code: 'NO-02', countryCode: 'NO', name: 'Akershus' },\n  { code: 'NO-09', countryCode: 'NO', name: 'Aust-Agder' },\n  { code: 'NO-06', countryCode: 'NO', name: 'Buskerud' },\n  { code: 'NO-20', countryCode: 'NO', name: 'Finnmark' },\n  { code: 'NO-04', countryCode: 'NO', name: 'Hedmark' },\n  { code: 'NO-12', countryCode: 'NO', name: 'Hordaland' },\n  { code: 'NO-15', countryCode: 'NO', name: 'More og Romsdal' },\n  { code: 'NO-17', countryCode: 'NO', name: 'Nord-Trondelag' },\n  { code: 'NO-18', countryCode: 'NO', name: 'Nordland' },\n  { code: 'NO-05', countryCode: 'NO', name: 'Oppland' },\n  { code: 'NO-03', countryCode: 'NO', name: 'Oslo' },\n  { code: 'NO-01', countryCode: 'NO', name: 'Ostfold' },\n  { code: 'NO-11', countryCode: 'NO', name: 'Rogaland' },\n  { code: 'NO-14', countryCode: 'NO', name: 'Sogn og Fjordane' },\n  { code: 'NO-16', countryCode: 'NO', name: 'Sor-Trondelag' },\n  { code: 'NO-08', countryCode: 'NO', name: 'Telemark' },\n  { code: 'NO-19', countryCode: 'NO', name: 'Troms' },\n  { code: 'NO-10', countryCode: 'NO', name: 'Vest-Agder' },\n  { code: 'NO-07', countryCode: 'NO', name: 'Vestfold' },\n  { code: 'NP-BA', countryCode: 'NP', name: 'Bagmati' },\n  { code: 'NP-BH', countryCode: 'NP', name: 'Bheri' },\n  { code: 'NP-DH', countryCode: 'NP', name: 'Dhawalagiri' },\n  { code: 'NP-GA', countryCode: 'NP', name: 'Gandaki' },\n  { code: 'NP-JA', countryCode: 'NP', name: 'Janakpur' },\n  { code: 'NP-KA', countryCode: 'NP', name: 'Karnali' },\n  { code: 'NP-KO', countryCode: 'NP', name: 'Kosi' },\n  { code: 'NP-LU', countryCode: 'NP', name: 'Lumbini' },\n  { code: 'NP-MA', countryCode: 'NP', name: 'Mahakali' },\n  { code: 'NP-ME', countryCode: 'NP', name: 'Mechi' },\n  { code: 'NP-NA', countryCode: 'NP', name: 'Narayani' },\n  { code: 'NP-RA', countryCode: 'NP', name: 'Rapti' },\n  { code: 'NP-SA', countryCode: 'NP', name: 'Sagarmatha' },\n  { code: 'NP-SE', countryCode: 'NP', name: 'Seti' },\n  { code: 'NR-14', countryCode: 'NR', name: 'Yaren' },\n  { code: 'NZ-AUK', countryCode: 'NZ', name: 'Auckland' },\n  { code: 'NZ-BOP', countryCode: 'NZ', name: 'Bay of Plenty' },\n  { code: 'NZ-CAN', countryCode: 'NZ', name: 'Canterbury' },\n  { code: 'NZ-CIT', countryCode: 'NZ', name: 'Chatham Islands Territory' },\n  { code: 'NZ-GIS', countryCode: 'NZ', name: 'Gisborne' },\n  { code: 'NZ-HKB', countryCode: 'NZ', name: \"Hawke's Bay\" },\n  { code: 'NZ-MWT', countryCode: 'NZ', name: 'Manawatu-Wanganui' },\n  { code: 'NZ-MBH', countryCode: 'NZ', name: 'Marlborough' },\n  { code: 'NZ-NSN', countryCode: 'NZ', name: 'Nelson' },\n  { code: 'NZ-NTL', countryCode: 'NZ', name: 'Northland' },\n  { code: 'NZ-OTA', countryCode: 'NZ', name: 'Otago' },\n  { code: 'NZ-STL', countryCode: 'NZ', name: 'Southland' },\n  { code: 'NZ-TKI', countryCode: 'NZ', name: 'Taranaki' },\n  { code: 'NZ-TAS', countryCode: 'NZ', name: 'Tasman' },\n  { code: 'NZ-WKO', countryCode: 'NZ', name: 'Waikato' },\n  { code: 'NZ-WGN', countryCode: 'NZ', name: 'Wellington' },\n  { code: 'NZ-WTC', countryCode: 'NZ', name: 'West Coast' },\n  { code: 'OM-DA', countryCode: 'OM', name: 'Ad Dakhiliyah' },\n  { code: 'OM-BU', countryCode: 'OM', name: 'Al Buraymi' },\n  { code: 'OM-WU', countryCode: 'OM', name: 'Al Wusta' },\n  { code: 'OM-ZA', countryCode: 'OM', name: 'Az Zahirah' },\n  { code: 'OM-BJ', countryCode: 'OM', name: 'Janub al Batinah' },\n  { code: 'OM-SJ', countryCode: 'OM', name: 'Janub ash Sharqiyah' },\n  { code: 'OM-MA', countryCode: 'OM', name: 'Masqat' },\n  { code: 'OM-MU', countryCode: 'OM', name: 'Musandam' },\n  { code: 'OM-BS', countryCode: 'OM', name: 'Shamal al Batinah' },\n  { code: 'OM-SS', countryCode: 'OM', name: 'Shamal ash Sharqiyah' },\n  { code: 'OM-ZU', countryCode: 'OM', name: 'Zufar' },\n  { code: 'PA-1', countryCode: 'PA', name: 'Bocas del Toro' },\n  { code: 'PA-4', countryCode: 'PA', name: 'Chiriqui' },\n  { code: 'PA-2', countryCode: 'PA', name: 'Cocle' },\n  { code: 'PA-3', countryCode: 'PA', name: 'Colon' },\n  { code: 'PA-5', countryCode: 'PA', name: 'Darien' },\n  { code: 'PA-6', countryCode: 'PA', name: 'Herrera' },\n  { code: 'PA-7', countryCode: 'PA', name: 'Los Santos' },\n  { code: 'PA-8', countryCode: 'PA', name: 'Panama' },\n  { code: 'PA-9', countryCode: 'PA', name: 'Veraguas' },\n  { code: 'PE-AMA', countryCode: 'PE', name: 'Amazonas' },\n  { code: 'PE-ANC', countryCode: 'PE', name: 'Ancash' },\n  { code: 'PE-APU', countryCode: 'PE', name: 'Apurimac' },\n  { code: 'PE-ARE', countryCode: 'PE', name: 'Arequipa' },\n  { code: 'PE-AYA', countryCode: 'PE', name: 'Ayacucho' },\n  { code: 'PE-CAJ', countryCode: 'PE', name: 'Cajamarca' },\n  { code: 'PE-CUS', countryCode: 'PE', name: 'Cusco' },\n  { code: 'PE-CAL', countryCode: 'PE', name: 'El Callao' },\n  { code: 'PE-HUV', countryCode: 'PE', name: 'Huancavelica' },\n  { code: 'PE-HUC', countryCode: 'PE', name: 'Huanuco' },\n  { code: 'PE-ICA', countryCode: 'PE', name: 'Ica' },\n  { code: 'PE-JUN', countryCode: 'PE', name: 'Junin' },\n  { code: 'PE-LAL', countryCode: 'PE', name: 'La Libertad' },\n  { code: 'PE-LAM', countryCode: 'PE', name: 'Lambayeque' },\n  { code: 'PE-LIM', countryCode: 'PE', name: 'Lima' },\n  { code: 'PE-LOR', countryCode: 'PE', name: 'Loreto' },\n  { code: 'PE-MDD', countryCode: 'PE', name: 'Madre de Dios' },\n  { code: 'PE-MOQ', countryCode: 'PE', name: 'Moquegua' },\n  { code: 'PE-PAS', countryCode: 'PE', name: 'Pasco' },\n  { code: 'PE-PIU', countryCode: 'PE', name: 'Piura' },\n  { code: 'PE-PUN', countryCode: 'PE', name: 'Puno' },\n  { code: 'PE-SAM', countryCode: 'PE', name: 'San Martin' },\n  { code: 'PE-TAC', countryCode: 'PE', name: 'Tacna' },\n  { code: 'PE-TUM', countryCode: 'PE', name: 'Tumbes' },\n  { code: 'PE-UCA', countryCode: 'PE', name: 'Ucayali' },\n  { code: 'PG-NSB', countryCode: 'PG', name: 'Bougainville' },\n  { code: 'PG-CPK', countryCode: 'PG', name: 'Chimbu' },\n  { code: 'PG-EBR', countryCode: 'PG', name: 'East New Britain' },\n  { code: 'PG-ESW', countryCode: 'PG', name: 'East Sepik' },\n  { code: 'PG-EHG', countryCode: 'PG', name: 'Eastern Highlands' },\n  { code: 'PG-EPW', countryCode: 'PG', name: 'Enga' },\n  { code: 'PG-GPK', countryCode: 'PG', name: 'Gulf' },\n  { code: 'PG-MPM', countryCode: 'PG', name: 'Madang' },\n  { code: 'PG-MRL', countryCode: 'PG', name: 'Manus' },\n  { code: 'PG-MBA', countryCode: 'PG', name: 'Milne Bay' },\n  { code: 'PG-MPL', countryCode: 'PG', name: 'Morobe' },\n  {\n    code: 'PG-NCD',\n    countryCode: 'PG',\n    name: 'National Capital District (Port Moresby)'\n  },\n  { code: 'PG-NIK', countryCode: 'PG', name: 'New Ireland' },\n  { code: 'PG-NPP', countryCode: 'PG', name: 'Northern' },\n  { code: 'PG-SHM', countryCode: 'PG', name: 'Southern Highlands' },\n  { code: 'PG-WBK', countryCode: 'PG', name: 'West New Britain' },\n  { code: 'PG-SAN', countryCode: 'PG', name: 'West Sepik' },\n  { code: 'PG-WPD', countryCode: 'PG', name: 'Western' },\n  { code: 'PG-WHM', countryCode: 'PG', name: 'Western Highlands' },\n  { code: 'PH-ABR', countryCode: 'PH', name: 'Abra' },\n  { code: 'PH-AGN', countryCode: 'PH', name: 'Agusan del Norte' },\n  { code: 'PH-AGS', countryCode: 'PH', name: 'Agusan del Sur' },\n  { code: 'PH-AKL', countryCode: 'PH', name: 'Aklan' },\n  { code: 'PH-ALB', countryCode: 'PH', name: 'Albay' },\n  { code: 'PH-ANT', countryCode: 'PH', name: 'Antique' },\n  { code: 'PH-APA', countryCode: 'PH', name: 'Apayao' },\n  { code: 'PH-AUR', countryCode: 'PH', name: 'Aurora' },\n  { code: 'PH-BAS', countryCode: 'PH', name: 'Basilan' },\n  { code: 'PH-BAN', countryCode: 'PH', name: 'Bataan' },\n  { code: 'PH-BTN', countryCode: 'PH', name: 'Batanes' },\n  { code: 'PH-BTG', countryCode: 'PH', name: 'Batangas' },\n  { code: 'PH-BEN', countryCode: 'PH', name: 'Benguet' },\n  { code: 'PH-BOH', countryCode: 'PH', name: 'Bohol' },\n  { code: 'PH-BUK', countryCode: 'PH', name: 'Bukidnon' },\n  { code: 'PH-BUL', countryCode: 'PH', name: 'Bulacan' },\n  { code: 'PH-CAG', countryCode: 'PH', name: 'Cagayan' },\n  { code: 'PH-CAN', countryCode: 'PH', name: 'Camarines Norte' },\n  { code: 'PH-CAS', countryCode: 'PH', name: 'Camarines Sur' },\n  { code: 'PH-CAM', countryCode: 'PH', name: 'Camiguin' },\n  { code: 'PH-CAP', countryCode: 'PH', name: 'Capiz' },\n  { code: 'PH-CAT', countryCode: 'PH', name: 'Catanduanes' },\n  { code: 'PH-CAV', countryCode: 'PH', name: 'Cavite' },\n  { code: 'PH-CEB', countryCode: 'PH', name: 'Cebu' },\n  { code: 'PH-NCO', countryCode: 'PH', name: 'Cotabato' },\n  { code: 'PH-DAS', countryCode: 'PH', name: 'Davao del Sur' },\n  { code: 'PH-DAO', countryCode: 'PH', name: 'Davao Oriental' },\n  { code: 'PH-EAS', countryCode: 'PH', name: 'Eastern Samar' },\n  { code: 'PH-IFU', countryCode: 'PH', name: 'Ifugao' },\n  { code: 'PH-ILN', countryCode: 'PH', name: 'Ilocos Norte' },\n  { code: 'PH-ILS', countryCode: 'PH', name: 'Ilocos Sur' },\n  { code: 'PH-ILI', countryCode: 'PH', name: 'Iloilo' },\n  { code: 'PH-ISA', countryCode: 'PH', name: 'Isabela' },\n  { code: 'PH-KAL', countryCode: 'PH', name: 'Kalinga' },\n  { code: 'PH-LUN', countryCode: 'PH', name: 'La Union' },\n  { code: 'PH-LAG', countryCode: 'PH', name: 'Laguna' },\n  { code: 'PH-LAN', countryCode: 'PH', name: 'Lanao del Norte' },\n  { code: 'PH-LAS', countryCode: 'PH', name: 'Lanao del Sur' },\n  { code: 'PH-LEY', countryCode: 'PH', name: 'Leyte' },\n  { code: 'PH-MAG', countryCode: 'PH', name: 'Maguindanao' },\n  { code: 'PH-MAD', countryCode: 'PH', name: 'Marinduque' },\n  { code: 'PH-MAS', countryCode: 'PH', name: 'Masbate' },\n  { code: 'PH-MDC', countryCode: 'PH', name: 'Mindoro Occidental' },\n  { code: 'PH-MDR', countryCode: 'PH', name: 'Mindoro Oriental' },\n  { code: 'PH-MSC', countryCode: 'PH', name: 'Misamis Occidental' },\n  { code: 'PH-MSR', countryCode: 'PH', name: 'Misamis Oriental' },\n  { code: 'PH-MOU', countryCode: 'PH', name: 'Mountain Province' },\n  { code: 'PH-00', countryCode: 'PH', name: 'National Capital Region' },\n  { code: 'PH-NEC', countryCode: 'PH', name: 'Negros Occidental' },\n  { code: 'PH-NER', countryCode: 'PH', name: 'Negros Oriental' },\n  { code: 'PH-NSA', countryCode: 'PH', name: 'Northern Samar' },\n  { code: 'PH-NUE', countryCode: 'PH', name: 'Nueva Ecija' },\n  { code: 'PH-NUV', countryCode: 'PH', name: 'Nueva Vizcaya' },\n  { code: 'PH-PLW', countryCode: 'PH', name: 'Palawan' },\n  { code: 'PH-PAM', countryCode: 'PH', name: 'Pampanga' },\n  { code: 'PH-PAN', countryCode: 'PH', name: 'Pangasinan' },\n  { code: 'PH-QUE', countryCode: 'PH', name: 'Quezon' },\n  { code: 'PH-QUI', countryCode: 'PH', name: 'Quirino' },\n  { code: 'PH-RIZ', countryCode: 'PH', name: 'Rizal' },\n  { code: 'PH-ROM', countryCode: 'PH', name: 'Romblon' },\n  { code: 'PH-WSA', countryCode: 'PH', name: 'Samar' },\n  { code: 'PH-SIG', countryCode: 'PH', name: 'Siquijor' },\n  { code: 'PH-SOR', countryCode: 'PH', name: 'Sorsogon' },\n  { code: 'PH-SCO', countryCode: 'PH', name: 'South Cotabato' },\n  { code: 'PH-SLE', countryCode: 'PH', name: 'Southern Leyte' },\n  { code: 'PH-SUK', countryCode: 'PH', name: 'Sultan Kudarat' },\n  { code: 'PH-SLU', countryCode: 'PH', name: 'Sulu' },\n  { code: 'PH-SUN', countryCode: 'PH', name: 'Surigao del Norte' },\n  { code: 'PH-SUR', countryCode: 'PH', name: 'Surigao del Sur' },\n  { code: 'PH-TAR', countryCode: 'PH', name: 'Tarlac' },\n  { code: 'PH-TAW', countryCode: 'PH', name: 'Tawi-Tawi' },\n  { code: 'PH-ZMB', countryCode: 'PH', name: 'Zambales' },\n  { code: 'PH-ZAN', countryCode: 'PH', name: 'Zamboanga del Norte' },\n  { code: 'PH-ZAS', countryCode: 'PH', name: 'Zamboanga del Sur' },\n  { code: 'PK-JK', countryCode: 'PK', name: 'Azad Kashmir' },\n  { code: 'PK-BA', countryCode: 'PK', name: 'Balochistan' },\n  {\n    code: 'PK-TA',\n    countryCode: 'PK',\n    name: 'Federally Administered Tribal Areas'\n  },\n  { code: 'PK-GB', countryCode: 'PK', name: 'Gilgit-Baltistan' },\n  { code: 'PK-IS', countryCode: 'PK', name: 'Islamabad' },\n  { code: 'PK-KP', countryCode: 'PK', name: 'Khyber Pakhtunkhwa' },\n  { code: 'PK-PB', countryCode: 'PK', name: 'Punjab' },\n  { code: 'PK-SD', countryCode: 'PK', name: 'Sindh' },\n  { code: 'PL-DS', countryCode: 'PL', name: 'Dolnoslaskie' },\n  { code: 'PL-KP', countryCode: 'PL', name: 'Kujawsko-pomorskie' },\n  { code: 'PL-LD', countryCode: 'PL', name: 'Lodzkie' },\n  { code: 'PL-LU', countryCode: 'PL', name: 'Lubelskie' },\n  { code: 'PL-LB', countryCode: 'PL', name: 'Lubuskie' },\n  { code: 'PL-MA', countryCode: 'PL', name: 'Malopolskie' },\n  { code: 'PL-MZ', countryCode: 'PL', name: 'Mazowieckie' },\n  { code: 'PL-OP', countryCode: 'PL', name: 'Opolskie' },\n  { code: 'PL-PK', countryCode: 'PL', name: 'Podkarpackie' },\n  { code: 'PL-PD', countryCode: 'PL', name: 'Podlaskie' },\n  { code: 'PL-PM', countryCode: 'PL', name: 'Pomorskie' },\n  { code: 'PL-SL', countryCode: 'PL', name: 'Slaskie' },\n  { code: 'PL-SK', countryCode: 'PL', name: 'Swietokrzyskie' },\n  { code: 'PL-WN', countryCode: 'PL', name: 'Warminsko-mazurskie' },\n  { code: 'PL-WP', countryCode: 'PL', name: 'Wielkopolskie' },\n  { code: 'PL-ZP', countryCode: 'PL', name: 'Zachodniopomorskie' },\n  { code: 'PS-BTH', countryCode: 'PS', name: 'Bethlehem' },\n  { code: 'PS-GZA', countryCode: 'PS', name: 'Gaza' },\n  { code: 'PS-HBN', countryCode: 'PS', name: 'Hebron' },\n  { code: 'PS-JEN', countryCode: 'PS', name: 'Jenin' },\n  { code: 'PS-JRH', countryCode: 'PS', name: 'Jericho and Al Aghwar' },\n  { code: 'PS-JEM', countryCode: 'PS', name: 'Jerusalem' },\n  { code: 'PS-NBS', countryCode: 'PS', name: 'Nablus' },\n  { code: 'PS-QQA', countryCode: 'PS', name: 'Qalqilya' },\n  { code: 'PS-RBH', countryCode: 'PS', name: 'Ramallah' },\n  { code: 'PS-SLT', countryCode: 'PS', name: 'Salfit' },\n  { code: 'PS-TBS', countryCode: 'PS', name: 'Tubas' },\n  { code: 'PS-TKM', countryCode: 'PS', name: 'Tulkarm' },\n  { code: 'PT-01', countryCode: 'PT', name: 'Aveiro' },\n  { code: 'PT-02', countryCode: 'PT', name: 'Beja' },\n  { code: 'PT-03', countryCode: 'PT', name: 'Braga' },\n  { code: 'PT-04', countryCode: 'PT', name: 'Braganca' },\n  { code: 'PT-05', countryCode: 'PT', name: 'Castelo Branco' },\n  { code: 'PT-06', countryCode: 'PT', name: 'Coimbra' },\n  { code: 'PT-07', countryCode: 'PT', name: 'Evora' },\n  { code: 'PT-08', countryCode: 'PT', name: 'Faro' },\n  { code: 'PT-09', countryCode: 'PT', name: 'Guarda' },\n  { code: 'PT-10', countryCode: 'PT', name: 'Leiria' },\n  { code: 'PT-11', countryCode: 'PT', name: 'Lisboa' },\n  { code: 'PT-12', countryCode: 'PT', name: 'Portalegre' },\n  { code: 'PT-13', countryCode: 'PT', name: 'Porto' },\n  { code: 'PT-30', countryCode: 'PT', name: 'Regiao Autonoma da Madeira' },\n  { code: 'PT-20', countryCode: 'PT', name: 'Regiao Autonoma dos Acores' },\n  { code: 'PT-14', countryCode: 'PT', name: 'Santarem' },\n  { code: 'PT-15', countryCode: 'PT', name: 'Setubal' },\n  { code: 'PT-16', countryCode: 'PT', name: 'Viana do Castelo' },\n  { code: 'PT-17', countryCode: 'PT', name: 'Vila Real' },\n  { code: 'PT-18', countryCode: 'PT', name: 'Viseu' },\n  { code: 'PW-002', countryCode: 'PW', name: 'Aimeliik' },\n  { code: 'PW-004', countryCode: 'PW', name: 'Airai' },\n  { code: 'PW-010', countryCode: 'PW', name: 'Angaur' },\n  { code: 'PW-100', countryCode: 'PW', name: 'Kayangel' },\n  { code: 'PW-150', countryCode: 'PW', name: 'Koror' },\n  { code: 'PW-212', countryCode: 'PW', name: 'Melekeok' },\n  { code: 'PW-214', countryCode: 'PW', name: 'Ngaraard' },\n  { code: 'PW-218', countryCode: 'PW', name: 'Ngarchelong' },\n  { code: 'PW-222', countryCode: 'PW', name: 'Ngardmau' },\n  { code: 'PW-224', countryCode: 'PW', name: 'Ngatpang' },\n  { code: 'PW-228', countryCode: 'PW', name: 'Ngiwal' },\n  { code: 'PW-350', countryCode: 'PW', name: 'Peleliu' },\n  { code: 'PY-16', countryCode: 'PY', name: 'Alto Paraguay' },\n  { code: 'PY-10', countryCode: 'PY', name: 'Alto Parana' },\n  { code: 'PY-13', countryCode: 'PY', name: 'Amambay' },\n  { code: 'PY-ASU', countryCode: 'PY', name: 'Asuncion' },\n  { code: 'PY-19', countryCode: 'PY', name: 'Boqueron' },\n  { code: 'PY-5', countryCode: 'PY', name: 'Caaguazu' },\n  { code: 'PY-6', countryCode: 'PY', name: 'Caazapa' },\n  { code: 'PY-14', countryCode: 'PY', name: 'Canindeyu' },\n  { code: 'PY-11', countryCode: 'PY', name: 'Central' },\n  { code: 'PY-1', countryCode: 'PY', name: 'Concepcion' },\n  { code: 'PY-3', countryCode: 'PY', name: 'Cordillera' },\n  { code: 'PY-4', countryCode: 'PY', name: 'Guaira' },\n  { code: 'PY-7', countryCode: 'PY', name: 'Itapua' },\n  { code: 'PY-8', countryCode: 'PY', name: 'Misiones' },\n  { code: 'PY-12', countryCode: 'PY', name: 'Neembucu' },\n  { code: 'PY-9', countryCode: 'PY', name: 'Paraguari' },\n  { code: 'PY-15', countryCode: 'PY', name: 'Presidente Hayes' },\n  { code: 'PY-2', countryCode: 'PY', name: 'San Pedro' },\n  { code: 'QA-DA', countryCode: 'QA', name: 'Ad Dawhah' },\n  { code: 'QA-KH', countryCode: 'QA', name: 'Al Khawr wa adh Dhakhirah' },\n  { code: 'QA-WA', countryCode: 'QA', name: 'Al Wakrah' },\n  { code: 'QA-RA', countryCode: 'QA', name: 'Ar Rayyan' },\n  { code: 'QA-MS', countryCode: 'QA', name: 'Ash Shamal' },\n  { code: 'QA-ZA', countryCode: 'QA', name: \"Az Za'ayin\" },\n  { code: 'QA-US', countryCode: 'QA', name: 'Umm Salal' },\n  { code: 'RO-AB', countryCode: 'RO', name: 'Alba' },\n  { code: 'RO-AR', countryCode: 'RO', name: 'Arad' },\n  { code: 'RO-AG', countryCode: 'RO', name: 'Arges' },\n  { code: 'RO-BC', countryCode: 'RO', name: 'Bacau' },\n  { code: 'RO-BH', countryCode: 'RO', name: 'Bihor' },\n  { code: 'RO-BN', countryCode: 'RO', name: 'Bistrita-Nasaud' },\n  { code: 'RO-BT', countryCode: 'RO', name: 'Botosani' },\n  { code: 'RO-BR', countryCode: 'RO', name: 'Braila' },\n  { code: 'RO-BV', countryCode: 'RO', name: 'Brasov' },\n  { code: 'RO-B', countryCode: 'RO', name: 'Bucuresti' },\n  { code: 'RO-BZ', countryCode: 'RO', name: 'Buzau' },\n  { code: 'RO-CL', countryCode: 'RO', name: 'Calarasi' },\n  { code: 'RO-CS', countryCode: 'RO', name: 'Caras-Severin' },\n  { code: 'RO-CJ', countryCode: 'RO', name: 'Cluj' },\n  { code: 'RO-CT', countryCode: 'RO', name: 'Constanta' },\n  { code: 'RO-CV', countryCode: 'RO', name: 'Covasna' },\n  { code: 'RO-DB', countryCode: 'RO', name: 'Dambovita' },\n  { code: 'RO-DJ', countryCode: 'RO', name: 'Dolj' },\n  { code: 'RO-GL', countryCode: 'RO', name: 'Galati' },\n  { code: 'RO-GR', countryCode: 'RO', name: 'Giurgiu' },\n  { code: 'RO-GJ', countryCode: 'RO', name: 'Gorj' },\n  { code: 'RO-HR', countryCode: 'RO', name: 'Harghita' },\n  { code: 'RO-HD', countryCode: 'RO', name: 'Hunedoara' },\n  { code: 'RO-IL', countryCode: 'RO', name: 'Ialomita' },\n  { code: 'RO-IS', countryCode: 'RO', name: 'Iasi' },\n  { code: 'RO-IF', countryCode: 'RO', name: 'Ilfov' },\n  { code: 'RO-MM', countryCode: 'RO', name: 'Maramures' },\n  { code: 'RO-MH', countryCode: 'RO', name: 'Mehedinti' },\n  { code: 'RO-MS', countryCode: 'RO', name: 'Mures' },\n  { code: 'RO-NT', countryCode: 'RO', name: 'Neamt' },\n  { code: 'RO-OT', countryCode: 'RO', name: 'Olt' },\n  { code: 'RO-PH', countryCode: 'RO', name: 'Prahova' },\n  { code: 'RO-SJ', countryCode: 'RO', name: 'Salaj' },\n  { code: 'RO-SM', countryCode: 'RO', name: 'Satu Mare' },\n  { code: 'RO-SB', countryCode: 'RO', name: 'Sibiu' },\n  { code: 'RO-SV', countryCode: 'RO', name: 'Suceava' },\n  { code: 'RO-TR', countryCode: 'RO', name: 'Teleorman' },\n  { code: 'RO-TM', countryCode: 'RO', name: 'Timis' },\n  { code: 'RO-TL', countryCode: 'RO', name: 'Tulcea' },\n  { code: 'RO-VL', countryCode: 'RO', name: 'Valcea' },\n  { code: 'RO-VS', countryCode: 'RO', name: 'Vaslui' },\n  { code: 'RO-VN', countryCode: 'RO', name: 'Vrancea' },\n  { code: 'RS-00', countryCode: 'RS', name: 'Beograd' },\n  { code: 'RS-14', countryCode: 'RS', name: 'Borski okrug' },\n  { code: 'RS-11', countryCode: 'RS', name: 'Branicevski okrug' },\n  { code: 'RS-23', countryCode: 'RS', name: 'Jablanicki okrug' },\n  { code: 'RS-06', countryCode: 'RS', name: 'Juznobacki okrug' },\n  { code: 'RS-04', countryCode: 'RS', name: 'Juznobanatski okrug' },\n  { code: 'RS-09', countryCode: 'RS', name: 'Kolubarski okrug' },\n  { code: 'RS-28', countryCode: 'RS', name: 'Kosovsko-Mitrovacki okrug' },\n  { code: 'RS-08', countryCode: 'RS', name: 'Macvanski okrug' },\n  { code: 'RS-17', countryCode: 'RS', name: 'Moravicki okrug' },\n  { code: 'RS-20', countryCode: 'RS', name: 'Nisavski okrug' },\n  { code: 'RS-24', countryCode: 'RS', name: 'Pcinjski okrug' },\n  { code: 'RS-26', countryCode: 'RS', name: 'Pecki okrug' },\n  { code: 'RS-22', countryCode: 'RS', name: 'Pirotski okrug' },\n  { code: 'RS-10', countryCode: 'RS', name: 'Podunavski okrug' },\n  { code: 'RS-27', countryCode: 'RS', name: 'Prizrenski okrug' },\n  { code: 'RS-19', countryCode: 'RS', name: 'Rasinski okrug' },\n  { code: 'RS-18', countryCode: 'RS', name: 'Raski okrug' },\n  { code: 'RS-01', countryCode: 'RS', name: 'Severnobacki okrug' },\n  { code: 'RS-03', countryCode: 'RS', name: 'Severnobanatski okrug' },\n  { code: 'RS-02', countryCode: 'RS', name: 'Srednjebanatski okrug' },\n  { code: 'RS-07', countryCode: 'RS', name: 'Sremski okrug' },\n  { code: 'RS-12', countryCode: 'RS', name: 'Sumadijski okrug' },\n  { code: 'RS-21', countryCode: 'RS', name: 'Toplicki okrug' },\n  { code: 'RS-15', countryCode: 'RS', name: 'Zajecarski okrug' },\n  { code: 'RS-05', countryCode: 'RS', name: 'Zapadnobacki okrug' },\n  { code: 'RS-16', countryCode: 'RS', name: 'Zlatiborski okrug' },\n  { code: 'RU-AD', countryCode: 'RU', name: 'Adygeya, Respublika' },\n  { code: 'RU-AL', countryCode: 'RU', name: 'Altay, Respublika' },\n  { code: 'RU-ALT', countryCode: 'RU', name: 'Altayskiy kray' },\n  { code: 'RU-AMU', countryCode: 'RU', name: \"Amurskaya oblast'\" },\n  { code: 'RU-ARK', countryCode: 'RU', name: \"Arkhangel'skaya oblast'\" },\n  { code: 'RU-AST', countryCode: 'RU', name: \"Astrakhanskaya oblast'\" },\n  { code: 'RU-BA', countryCode: 'RU', name: 'Bashkortostan, Respublika' },\n  { code: 'RU-BEL', countryCode: 'RU', name: \"Belgorodskaya oblast'\" },\n  { code: 'RU-BRY', countryCode: 'RU', name: \"Bryanskaya oblast'\" },\n  { code: 'RU-BU', countryCode: 'RU', name: 'Buryatiya, Respublika' },\n  { code: 'RU-CE', countryCode: 'RU', name: 'Chechenskaya Respublika' },\n  { code: 'RU-CHE', countryCode: 'RU', name: \"Chelyabinskaya oblast'\" },\n  { code: 'RU-CHU', countryCode: 'RU', name: 'Chukotskiy avtonomnyy okrug' },\n  { code: 'RU-CU', countryCode: 'RU', name: 'Chuvashskaya Respublika' },\n  { code: 'RU-DA', countryCode: 'RU', name: 'Dagestan, Respublika' },\n  { code: 'RU-IN', countryCode: 'RU', name: 'Ingushetiya, Respublika' },\n  { code: 'RU-IRK', countryCode: 'RU', name: \"Irkutskaya oblast'\" },\n  { code: 'RU-IVA', countryCode: 'RU', name: \"Ivanovskaya oblast'\" },\n  {\n    code: 'RU-KB',\n    countryCode: 'RU',\n    name: 'Kabardino-Balkarskaya Respublika'\n  },\n  { code: 'RU-KGD', countryCode: 'RU', name: \"Kaliningradskaya oblast'\" },\n  { code: 'RU-KL', countryCode: 'RU', name: 'Kalmykiya, Respublika' },\n  { code: 'RU-KLU', countryCode: 'RU', name: \"Kaluzhskaya oblast'\" },\n  { code: 'RU-KAM', countryCode: 'RU', name: 'Kamchatskiy kray' },\n  {\n    code: 'RU-KC',\n    countryCode: 'RU',\n    name: 'Karachayevo-Cherkesskaya Respublika'\n  },\n  { code: 'RU-KR', countryCode: 'RU', name: 'Kareliya, Respublika' },\n  { code: 'RU-KEM', countryCode: 'RU', name: \"Kemerovskaya oblast'\" },\n  { code: 'RU-KHA', countryCode: 'RU', name: 'Khabarovskiy kray' },\n  { code: 'RU-KK', countryCode: 'RU', name: 'Khakasiya, Respublika' },\n  {\n    code: 'RU-KHM',\n    countryCode: 'RU',\n    name: 'Khanty-Mansiyskiy avtonomnyy okrug'\n  },\n  { code: 'RU-KIR', countryCode: 'RU', name: \"Kirovskaya oblast'\" },\n  { code: 'RU-KO', countryCode: 'RU', name: 'Komi, Respublika' },\n  { code: 'RU-KOS', countryCode: 'RU', name: \"Kostromskaya oblast'\" },\n  { code: 'RU-KDA', countryCode: 'RU', name: 'Krasnodarskiy kray' },\n  { code: 'RU-KYA', countryCode: 'RU', name: 'Krasnoyarskiy kray' },\n  { code: 'RU-KGN', countryCode: 'RU', name: \"Kurganskaya oblast'\" },\n  { code: 'RU-KRS', countryCode: 'RU', name: \"Kurskaya oblast'\" },\n  { code: 'RU-LEN', countryCode: 'RU', name: \"Leningradskaya oblast'\" },\n  { code: 'RU-LIP', countryCode: 'RU', name: \"Lipetskaya oblast'\" },\n  { code: 'RU-MAG', countryCode: 'RU', name: \"Magadanskaya oblast'\" },\n  { code: 'RU-ME', countryCode: 'RU', name: 'Mariy El, Respublika' },\n  { code: 'RU-MO', countryCode: 'RU', name: 'Mordoviya, Respublika' },\n  { code: 'RU-MOS', countryCode: 'RU', name: \"Moskovskaya oblast'\" },\n  { code: 'RU-MOW', countryCode: 'RU', name: 'Moskva' },\n  { code: 'RU-MUR', countryCode: 'RU', name: \"Murmanskaya oblast'\" },\n  { code: 'RU-NEN', countryCode: 'RU', name: 'Nenetskiy avtonomnyy okrug' },\n  { code: 'RU-NIZ', countryCode: 'RU', name: \"Nizhegorodskaya oblast'\" },\n  { code: 'RU-NGR', countryCode: 'RU', name: \"Novgorodskaya oblast'\" },\n  { code: 'RU-NVS', countryCode: 'RU', name: \"Novosibirskaya oblast'\" },\n  { code: 'RU-OMS', countryCode: 'RU', name: \"Omskaya oblast'\" },\n  { code: 'RU-ORE', countryCode: 'RU', name: \"Orenburgskaya oblast'\" },\n  { code: 'RU-ORL', countryCode: 'RU', name: \"Orlovskaya oblast'\" },\n  { code: 'RU-PNZ', countryCode: 'RU', name: \"Penzenskaya oblast'\" },\n  { code: 'RU-PER', countryCode: 'RU', name: 'Permskiy kray' },\n  { code: 'RU-PRI', countryCode: 'RU', name: 'Primorskiy kray' },\n  { code: 'RU-PSK', countryCode: 'RU', name: \"Pskovskaya oblast'\" },\n  { code: 'RU-ROS', countryCode: 'RU', name: \"Rostovskaya oblast'\" },\n  { code: 'RU-RYA', countryCode: 'RU', name: \"Ryazanskaya oblast'\" },\n  { code: 'RU-SA', countryCode: 'RU', name: 'Saha, Respublika' },\n  { code: 'RU-SAK', countryCode: 'RU', name: \"Sakhalinskaya oblast'\" },\n  { code: 'RU-SAM', countryCode: 'RU', name: \"Samarskaya oblast'\" },\n  { code: 'RU-SPE', countryCode: 'RU', name: 'Sankt-Peterburg' },\n  { code: 'RU-SAR', countryCode: 'RU', name: \"Saratovskaya oblast'\" },\n  { code: 'RU-SE', countryCode: 'RU', name: 'Severnaya Osetiya, Respublika' },\n  { code: 'RU-SMO', countryCode: 'RU', name: \"Smolenskaya oblast'\" },\n  { code: 'RU-STA', countryCode: 'RU', name: \"Stavropol'skiy kray\" },\n  { code: 'RU-SVE', countryCode: 'RU', name: \"Sverdlovskaya oblast'\" },\n  { code: 'RU-TAM', countryCode: 'RU', name: \"Tambovskaya oblast'\" },\n  { code: 'RU-TA', countryCode: 'RU', name: 'Tatarstan, Respublika' },\n  { code: 'RU-TOM', countryCode: 'RU', name: \"Tomskaya oblast'\" },\n  { code: 'RU-TUL', countryCode: 'RU', name: \"Tul'skaya oblast'\" },\n  { code: 'RU-TVE', countryCode: 'RU', name: \"Tverskaya oblast'\" },\n  { code: 'RU-TYU', countryCode: 'RU', name: \"Tyumenskaya oblast'\" },\n  { code: 'RU-TY', countryCode: 'RU', name: 'Tyva, Respublika' },\n  { code: 'RU-UD', countryCode: 'RU', name: 'Udmurtskaya Respublika' },\n  { code: 'RU-ULY', countryCode: 'RU', name: \"Ul'yanovskaya oblast'\" },\n  { code: 'RU-VLA', countryCode: 'RU', name: \"Vladimirskaya oblast'\" },\n  { code: 'RU-VGG', countryCode: 'RU', name: \"Volgogradskaya oblast'\" },\n  { code: 'RU-VLG', countryCode: 'RU', name: \"Vologodskaya oblast'\" },\n  { code: 'RU-VOR', countryCode: 'RU', name: \"Voronezhskaya oblast'\" },\n  {\n    code: 'RU-YAN',\n    countryCode: 'RU',\n    name: 'Yamalo-Nenetskiy avtonomnyy okrug'\n  },\n  { code: 'RU-YAR', countryCode: 'RU', name: \"Yaroslavskaya oblast'\" },\n  {\n    code: 'RU-YEV',\n    countryCode: 'RU',\n    name: \"Yevreyskaya avtonomnaya oblast'\"\n  },\n  { code: 'RU-ZAB', countryCode: 'RU', name: \"Zabaykal'skiy kray\" },\n  { code: 'RW-02', countryCode: 'RW', name: 'Est' },\n  { code: 'RW-03', countryCode: 'RW', name: 'Nord' },\n  { code: 'RW-04', countryCode: 'RW', name: 'Ouest' },\n  { code: 'RW-05', countryCode: 'RW', name: 'Sud' },\n  { code: 'RW-01', countryCode: 'RW', name: 'Ville de Kigali' },\n  { code: 'SA-14', countryCode: 'SA', name: \"'Asir\" },\n  { code: 'SA-11', countryCode: 'SA', name: 'Al Bahah' },\n  { code: 'SA-08', countryCode: 'SA', name: 'Al Hudud ash Shamaliyah' },\n  { code: 'SA-12', countryCode: 'SA', name: 'Al Jawf' },\n  { code: 'SA-03', countryCode: 'SA', name: 'Al Madinah al Munawwarah' },\n  { code: 'SA-05', countryCode: 'SA', name: 'Al Qasim' },\n  { code: 'SA-01', countryCode: 'SA', name: 'Ar Riyad' },\n  { code: 'SA-04', countryCode: 'SA', name: 'Ash Sharqiyah' },\n  { code: 'SA-06', countryCode: 'SA', name: \"Ha'il\" },\n  { code: 'SA-09', countryCode: 'SA', name: 'Jazan' },\n  { code: 'SA-02', countryCode: 'SA', name: 'Makkah al Mukarramah' },\n  { code: 'SA-10', countryCode: 'SA', name: 'Najran' },\n  { code: 'SA-07', countryCode: 'SA', name: 'Tabuk' },\n  { code: 'SB-CE', countryCode: 'SB', name: 'Central' },\n  { code: 'SB-GU', countryCode: 'SB', name: 'Guadalcanal' },\n  { code: 'SB-IS', countryCode: 'SB', name: 'Isabel' },\n  { code: 'SB-MK', countryCode: 'SB', name: 'Makira-Ulawa' },\n  { code: 'SB-ML', countryCode: 'SB', name: 'Malaita' },\n  { code: 'SB-WE', countryCode: 'SB', name: 'Western' },\n  { code: 'SC-16', countryCode: 'SC', name: 'English River' },\n  { code: 'SD-NB', countryCode: 'SD', name: 'Blue Nile' },\n  { code: 'SD-GD', countryCode: 'SD', name: 'Gedaref' },\n  { code: 'SD-GZ', countryCode: 'SD', name: 'Gezira' },\n  { code: 'SD-KA', countryCode: 'SD', name: 'Kassala' },\n  { code: 'SD-KH', countryCode: 'SD', name: 'Khartoum' },\n  { code: 'SD-DN', countryCode: 'SD', name: 'North Darfur' },\n  { code: 'SD-KN', countryCode: 'SD', name: 'North Kordofan' },\n  { code: 'SD-NO', countryCode: 'SD', name: 'Northern' },\n  { code: 'SD-RS', countryCode: 'SD', name: 'Red Sea' },\n  { code: 'SD-NR', countryCode: 'SD', name: 'River Nile' },\n  { code: 'SD-SI', countryCode: 'SD', name: 'Sennar' },\n  { code: 'SD-DS', countryCode: 'SD', name: 'South Darfur' },\n  { code: 'SD-KS', countryCode: 'SD', name: 'South Kordofan' },\n  { code: 'SD-DW', countryCode: 'SD', name: 'West Darfur' },\n  { code: 'SD-NW', countryCode: 'SD', name: 'White Nile' },\n  { code: 'SE-K', countryCode: 'SE', name: 'Blekinge lan' },\n  { code: 'SE-W', countryCode: 'SE', name: 'Dalarnas lan' },\n  { code: 'SE-X', countryCode: 'SE', name: 'Gavleborgs lan' },\n  { code: 'SE-I', countryCode: 'SE', name: 'Gotlands lan' },\n  { code: 'SE-N', countryCode: 'SE', name: 'Hallands lan' },\n  { code: 'SE-Z', countryCode: 'SE', name: 'Jamtlands lan' },\n  { code: 'SE-F', countryCode: 'SE', name: 'Jonkopings lan' },\n  { code: 'SE-H', countryCode: 'SE', name: 'Kalmar lan' },\n  { code: 'SE-G', countryCode: 'SE', name: 'Kronobergs lan' },\n  { code: 'SE-BD', countryCode: 'SE', name: 'Norrbottens lan' },\n  { code: 'SE-T', countryCode: 'SE', name: 'Orebro lan' },\n  { code: 'SE-E', countryCode: 'SE', name: 'Ostergotlands lan' },\n  { code: 'SE-M', countryCode: 'SE', name: 'Skane lan' },\n  { code: 'SE-D', countryCode: 'SE', name: 'Sodermanlands lan' },\n  { code: 'SE-AB', countryCode: 'SE', name: 'Stockholms lan' },\n  { code: 'SE-C', countryCode: 'SE', name: 'Uppsala lan' },\n  { code: 'SE-S', countryCode: 'SE', name: 'Varmlands lan' },\n  { code: 'SE-AC', countryCode: 'SE', name: 'Vasterbottens lan' },\n  { code: 'SE-Y', countryCode: 'SE', name: 'Vasternorrlands lan' },\n  { code: 'SE-U', countryCode: 'SE', name: 'Vastmanlands lan' },\n  { code: 'SE-O', countryCode: 'SE', name: 'Vastra Gotalands lan' },\n  { code: 'SH-AC', countryCode: 'SH', name: 'Ascension' },\n  { code: 'SH-HL', countryCode: 'SH', name: 'Saint Helena' },\n  { code: 'SH-TA', countryCode: 'SH', name: 'Tristan da Cunha' },\n  { code: 'SI-001', countryCode: 'SI', name: 'Ajdovscina' },\n  { code: 'SI-003', countryCode: 'SI', name: 'Bled' },\n  { code: 'SI-004', countryCode: 'SI', name: 'Bohinj' },\n  { code: 'SI-005', countryCode: 'SI', name: 'Borovnica' },\n  { code: 'SI-006', countryCode: 'SI', name: 'Bovec' },\n  { code: 'SI-009', countryCode: 'SI', name: 'Brezice' },\n  { code: 'SI-008', countryCode: 'SI', name: 'Brezovica' },\n  { code: 'SI-011', countryCode: 'SI', name: 'Celje' },\n  { code: 'SI-013', countryCode: 'SI', name: 'Cerknica' },\n  { code: 'SI-014', countryCode: 'SI', name: 'Cerkno' },\n  { code: 'SI-015', countryCode: 'SI', name: 'Crensovci' },\n  { code: 'SI-017', countryCode: 'SI', name: 'Crnomelj' },\n  { code: 'SI-018', countryCode: 'SI', name: 'Destrnik' },\n  { code: 'SI-019', countryCode: 'SI', name: 'Divaca' },\n  { code: 'SI-023', countryCode: 'SI', name: 'Domzale' },\n  { code: 'SI-025', countryCode: 'SI', name: 'Dravograd' },\n  { code: 'SI-029', countryCode: 'SI', name: 'Gornja Radgona' },\n  { code: 'SI-032', countryCode: 'SI', name: 'Grosuplje' },\n  { code: 'SI-160', countryCode: 'SI', name: 'Hoce-Slivnica' },\n  { code: 'SI-162', countryCode: 'SI', name: 'Horjul' },\n  { code: 'SI-034', countryCode: 'SI', name: 'Hrastnik' },\n  { code: 'SI-036', countryCode: 'SI', name: 'Idrija' },\n  { code: 'SI-037', countryCode: 'SI', name: 'Ig' },\n  { code: 'SI-038', countryCode: 'SI', name: 'Ilirska Bistrica' },\n  { code: 'SI-039', countryCode: 'SI', name: 'Ivancna Gorica' },\n  { code: 'SI-040', countryCode: 'SI', name: 'Izola' },\n  { code: 'SI-041', countryCode: 'SI', name: 'Jesenice' },\n  { code: 'SI-043', countryCode: 'SI', name: 'Kamnik' },\n  { code: 'SI-044', countryCode: 'SI', name: 'Kanal' },\n  { code: 'SI-045', countryCode: 'SI', name: 'Kidricevo' },\n  { code: 'SI-046', countryCode: 'SI', name: 'Kobarid' },\n  { code: 'SI-048', countryCode: 'SI', name: 'Kocevje' },\n  { code: 'SI-050', countryCode: 'SI', name: 'Koper' },\n  { code: 'SI-052', countryCode: 'SI', name: 'Kranj' },\n  { code: 'SI-053', countryCode: 'SI', name: 'Kranjska Gora' },\n  { code: 'SI-054', countryCode: 'SI', name: 'Krsko' },\n  { code: 'SI-057', countryCode: 'SI', name: 'Lasko' },\n  { code: 'SI-058', countryCode: 'SI', name: 'Lenart' },\n  { code: 'SI-059', countryCode: 'SI', name: 'Lendava' },\n  { code: 'SI-060', countryCode: 'SI', name: 'Litija' },\n  { code: 'SI-061', countryCode: 'SI', name: 'Ljubljana' },\n  { code: 'SI-063', countryCode: 'SI', name: 'Ljutomer' },\n  { code: 'SI-208', countryCode: 'SI', name: 'Log-Dragomer' },\n  { code: 'SI-064', countryCode: 'SI', name: 'Logatec' },\n  { code: 'SI-167', countryCode: 'SI', name: 'Lovrenc na Pohorju' },\n  { code: 'SI-070', countryCode: 'SI', name: 'Maribor' },\n  { code: 'SI-071', countryCode: 'SI', name: 'Medvode' },\n  { code: 'SI-072', countryCode: 'SI', name: 'Menges' },\n  { code: 'SI-073', countryCode: 'SI', name: 'Metlika' },\n  { code: 'SI-074', countryCode: 'SI', name: 'Mezica' },\n  { code: 'SI-169', countryCode: 'SI', name: 'Miklavz na Dravskem Polju' },\n  { code: 'SI-075', countryCode: 'SI', name: 'Miren-Kostanjevica' },\n  { code: 'SI-076', countryCode: 'SI', name: 'Mislinja' },\n  { code: 'SI-079', countryCode: 'SI', name: 'Mozirje' },\n  { code: 'SI-080', countryCode: 'SI', name: 'Murska Sobota' },\n  { code: 'SI-081', countryCode: 'SI', name: 'Muta' },\n  { code: 'SI-084', countryCode: 'SI', name: 'Nova Gorica' },\n  { code: 'SI-085', countryCode: 'SI', name: 'Novo Mesto' },\n  { code: 'SI-086', countryCode: 'SI', name: 'Odranci' },\n  { code: 'SI-171', countryCode: 'SI', name: 'Oplotnica' },\n  { code: 'SI-087', countryCode: 'SI', name: 'Ormoz' },\n  { code: 'SI-090', countryCode: 'SI', name: 'Piran' },\n  { code: 'SI-091', countryCode: 'SI', name: 'Pivka' },\n  { code: 'SI-200', countryCode: 'SI', name: 'Poljcane' },\n  { code: 'SI-173', countryCode: 'SI', name: 'Polzela' },\n  { code: 'SI-094', countryCode: 'SI', name: 'Postojna' },\n  { code: 'SI-174', countryCode: 'SI', name: 'Prebold' },\n  { code: 'SI-175', countryCode: 'SI', name: 'Prevalje' },\n  { code: 'SI-096', countryCode: 'SI', name: 'Ptuj' },\n  { code: 'SI-098', countryCode: 'SI', name: 'Race-Fram' },\n  { code: 'SI-099', countryCode: 'SI', name: 'Radece' },\n  { code: 'SI-100', countryCode: 'SI', name: 'Radenci' },\n  { code: 'SI-101', countryCode: 'SI', name: 'Radlje ob Dravi' },\n  { code: 'SI-102', countryCode: 'SI', name: 'Radovljica' },\n  { code: 'SI-103', countryCode: 'SI', name: 'Ravne na Koroskem' },\n  { code: 'SI-104', countryCode: 'SI', name: 'Ribnica' },\n  { code: 'SI-106', countryCode: 'SI', name: 'Rogaska Slatina' },\n  { code: 'SI-108', countryCode: 'SI', name: 'Ruse' },\n  { code: 'SI-183', countryCode: 'SI', name: 'Sempeter-Vrtojba' },\n  { code: 'SI-117', countryCode: 'SI', name: 'Sencur' },\n  { code: 'SI-118', countryCode: 'SI', name: 'Sentilj' },\n  { code: 'SI-120', countryCode: 'SI', name: 'Sentjur' },\n  { code: 'SI-110', countryCode: 'SI', name: 'Sevnica' },\n  { code: 'SI-111', countryCode: 'SI', name: 'Sezana' },\n  { code: 'SI-122', countryCode: 'SI', name: 'Skofja Loka' },\n  { code: 'SI-123', countryCode: 'SI', name: 'Skofljica' },\n  { code: 'SI-112', countryCode: 'SI', name: 'Slovenj Gradec' },\n  { code: 'SI-113', countryCode: 'SI', name: 'Slovenska Bistrica' },\n  { code: 'SI-114', countryCode: 'SI', name: 'Slovenske Konjice' },\n  { code: 'SI-126', countryCode: 'SI', name: 'Sostanj' },\n  { code: 'SI-127', countryCode: 'SI', name: 'Store' },\n  { code: 'SI-203', countryCode: 'SI', name: 'Straza' },\n  { code: 'SI-128', countryCode: 'SI', name: 'Tolmin' },\n  { code: 'SI-129', countryCode: 'SI', name: 'Trbovlje' },\n  { code: 'SI-130', countryCode: 'SI', name: 'Trebnje' },\n  { code: 'SI-131', countryCode: 'SI', name: 'Trzic' },\n  { code: 'SI-186', countryCode: 'SI', name: 'Trzin' },\n  { code: 'SI-132', countryCode: 'SI', name: 'Turnisce' },\n  { code: 'SI-133', countryCode: 'SI', name: 'Velenje' },\n  { code: 'SI-136', countryCode: 'SI', name: 'Vipava' },\n  { code: 'SI-138', countryCode: 'SI', name: 'Vodice' },\n  { code: 'SI-139', countryCode: 'SI', name: 'Vojnik' },\n  { code: 'SI-140', countryCode: 'SI', name: 'Vrhnika' },\n  { code: 'SI-141', countryCode: 'SI', name: 'Vuzenica' },\n  { code: 'SI-142', countryCode: 'SI', name: 'Zagorje ob Savi' },\n  { code: 'SI-190', countryCode: 'SI', name: 'Zalec' },\n  { code: 'SI-146', countryCode: 'SI', name: 'Zelezniki' },\n  { code: 'SI-147', countryCode: 'SI', name: 'Ziri' },\n  { code: 'SI-144', countryCode: 'SI', name: 'Zrece' },\n  { code: 'SI-193', countryCode: 'SI', name: 'Zuzemberk' },\n  { code: 'SK-BC', countryCode: 'SK', name: 'Banskobystricky kraj' },\n  { code: 'SK-BL', countryCode: 'SK', name: 'Bratislavsky kraj' },\n  { code: 'SK-KI', countryCode: 'SK', name: 'Kosicky kraj' },\n  { code: 'SK-NI', countryCode: 'SK', name: 'Nitriansky kraj' },\n  { code: 'SK-PV', countryCode: 'SK', name: 'Presovsky kraj' },\n  { code: 'SK-TC', countryCode: 'SK', name: 'Trenciansky kraj' },\n  { code: 'SK-TA', countryCode: 'SK', name: 'Trnavsky kraj' },\n  { code: 'SK-ZI', countryCode: 'SK', name: 'Zilinsky kraj' },\n  { code: 'SL-E', countryCode: 'SL', name: 'Eastern' },\n  { code: 'SL-N', countryCode: 'SL', name: 'Northern' },\n  { code: 'SL-S', countryCode: 'SL', name: 'Southern' },\n  { code: 'SL-W', countryCode: 'SL', name: 'Western Area' },\n  { code: 'SM-01', countryCode: 'SM', name: 'Acquaviva' },\n  { code: 'SM-02', countryCode: 'SM', name: 'Chiesanuova' },\n  { code: 'SM-07', countryCode: 'SM', name: 'San Marino' },\n  { code: 'SM-09', countryCode: 'SM', name: 'Serravalle' },\n  { code: 'SN-DK', countryCode: 'SN', name: 'Dakar' },\n  { code: 'SN-DB', countryCode: 'SN', name: 'Diourbel' },\n  { code: 'SN-FK', countryCode: 'SN', name: 'Fatick' },\n  { code: 'SN-KA', countryCode: 'SN', name: 'Kaffrine' },\n  { code: 'SN-KL', countryCode: 'SN', name: 'Kaolack' },\n  { code: 'SN-KE', countryCode: 'SN', name: 'Kedougou' },\n  { code: 'SN-KD', countryCode: 'SN', name: 'Kolda' },\n  { code: 'SN-LG', countryCode: 'SN', name: 'Louga' },\n  { code: 'SN-MT', countryCode: 'SN', name: 'Matam' },\n  { code: 'SN-SL', countryCode: 'SN', name: 'Saint-Louis' },\n  { code: 'SN-SE', countryCode: 'SN', name: 'Sedhiou' },\n  { code: 'SN-TC', countryCode: 'SN', name: 'Tambacounda' },\n  { code: 'SN-TH', countryCode: 'SN', name: 'Thies' },\n  { code: 'SN-ZG', countryCode: 'SN', name: 'Ziguinchor' },\n  { code: 'SO-AW', countryCode: 'SO', name: 'Awdal' },\n  { code: 'SO-BK', countryCode: 'SO', name: 'Bakool' },\n  { code: 'SO-BN', countryCode: 'SO', name: 'Banaadir' },\n  { code: 'SO-BR', countryCode: 'SO', name: 'Bari' },\n  { code: 'SO-BY', countryCode: 'SO', name: 'Bay' },\n  { code: 'SO-GA', countryCode: 'SO', name: 'Galguduud' },\n  { code: 'SO-GE', countryCode: 'SO', name: 'Gedo' },\n  { code: 'SO-HI', countryCode: 'SO', name: 'Hiiraan' },\n  { code: 'SO-JD', countryCode: 'SO', name: 'Jubbada Dhexe' },\n  { code: 'SO-JH', countryCode: 'SO', name: 'Jubbada Hoose' },\n  { code: 'SO-MU', countryCode: 'SO', name: 'Mudug' },\n  { code: 'SO-NU', countryCode: 'SO', name: 'Nugaal' },\n  { code: 'SO-SA', countryCode: 'SO', name: 'Sanaag' },\n  { code: 'SO-SD', countryCode: 'SO', name: 'Shabeellaha Dhexe' },\n  { code: 'SO-SH', countryCode: 'SO', name: 'Shabeellaha Hoose' },\n  { code: 'SO-SO', countryCode: 'SO', name: 'Sool' },\n  { code: 'SO-TO', countryCode: 'SO', name: 'Togdheer' },\n  { code: 'SO-WO', countryCode: 'SO', name: 'Woqooyi Galbeed' },\n  { code: 'SR-BR', countryCode: 'SR', name: 'Brokopondo' },\n  { code: 'SR-CM', countryCode: 'SR', name: 'Commewijne' },\n  { code: 'SR-CR', countryCode: 'SR', name: 'Coronie' },\n  { code: 'SR-MA', countryCode: 'SR', name: 'Marowijne' },\n  { code: 'SR-NI', countryCode: 'SR', name: 'Nickerie' },\n  { code: 'SR-PR', countryCode: 'SR', name: 'Para' },\n  { code: 'SR-PM', countryCode: 'SR', name: 'Paramaribo' },\n  { code: 'SR-SA', countryCode: 'SR', name: 'Saramacca' },\n  { code: 'SR-WA', countryCode: 'SR', name: 'Wanica' },\n  { code: 'SS-EC', countryCode: 'SS', name: 'Central Equatoria' },\n  { code: 'SS-EE', countryCode: 'SS', name: 'Eastern Equatoria' },\n  { code: 'SS-JG', countryCode: 'SS', name: 'Jonglei' },\n  { code: 'SS-LK', countryCode: 'SS', name: 'Lakes' },\n  { code: 'SS-BN', countryCode: 'SS', name: 'Northern Bahr el Ghazal' },\n  { code: 'SS-UY', countryCode: 'SS', name: 'Unity' },\n  { code: 'SS-NU', countryCode: 'SS', name: 'Upper Nile' },\n  { code: 'SS-WR', countryCode: 'SS', name: 'Warrap' },\n  { code: 'SS-BW', countryCode: 'SS', name: 'Western Bahr el Ghazal' },\n  { code: 'SS-EW', countryCode: 'SS', name: 'Western Equatoria' },\n  { code: 'ST-P', countryCode: 'ST', name: 'Principe' },\n  { code: 'ST-S', countryCode: 'ST', name: 'Sao Tome' },\n  { code: 'SV-AH', countryCode: 'SV', name: 'Ahuachapan' },\n  { code: 'SV-CA', countryCode: 'SV', name: 'Cabanas' },\n  { code: 'SV-CH', countryCode: 'SV', name: 'Chalatenango' },\n  { code: 'SV-CU', countryCode: 'SV', name: 'Cuscatlan' },\n  { code: 'SV-LI', countryCode: 'SV', name: 'La Libertad' },\n  { code: 'SV-PA', countryCode: 'SV', name: 'La Paz' },\n  { code: 'SV-UN', countryCode: 'SV', name: 'La Union' },\n  { code: 'SV-MO', countryCode: 'SV', name: 'Morazan' },\n  { code: 'SV-SM', countryCode: 'SV', name: 'San Miguel' },\n  { code: 'SV-SS', countryCode: 'SV', name: 'San Salvador' },\n  { code: 'SV-SV', countryCode: 'SV', name: 'San Vicente' },\n  { code: 'SV-SA', countryCode: 'SV', name: 'Santa Ana' },\n  { code: 'SV-SO', countryCode: 'SV', name: 'Sonsonate' },\n  { code: 'SV-US', countryCode: 'SV', name: 'Usulutan' },\n  { code: 'SY-HA', countryCode: 'SY', name: 'Al Hasakah' },\n  { code: 'SY-LA', countryCode: 'SY', name: 'Al Ladhiqiyah' },\n  { code: 'SY-QU', countryCode: 'SY', name: 'Al Qunaytirah' },\n  { code: 'SY-RA', countryCode: 'SY', name: 'Ar Raqqah' },\n  { code: 'SY-SU', countryCode: 'SY', name: \"As Suwayda'\" },\n  { code: 'SY-DR', countryCode: 'SY', name: \"Dar'a\" },\n  { code: 'SY-DY', countryCode: 'SY', name: 'Dayr az Zawr' },\n  { code: 'SY-DI', countryCode: 'SY', name: 'Dimashq' },\n  { code: 'SY-HL', countryCode: 'SY', name: 'Halab' },\n  { code: 'SY-HM', countryCode: 'SY', name: 'Hamah' },\n  { code: 'SY-HI', countryCode: 'SY', name: 'Hims' },\n  { code: 'SY-ID', countryCode: 'SY', name: 'Idlib' },\n  { code: 'SY-RD', countryCode: 'SY', name: 'Rif Dimashq' },\n  { code: 'SY-TA', countryCode: 'SY', name: 'Tartus' },\n  { code: 'SZ-HH', countryCode: 'SZ', name: 'Hhohho' },\n  { code: 'SZ-LU', countryCode: 'SZ', name: 'Lubombo' },\n  { code: 'SZ-MA', countryCode: 'SZ', name: 'Manzini' },\n  { code: 'SZ-SH', countryCode: 'SZ', name: 'Shiselweni' },\n  { code: 'TD-BG', countryCode: 'TD', name: 'Bahr el Gazel' },\n  { code: 'TD-BA', countryCode: 'TD', name: 'Batha' },\n  { code: 'TD-BO', countryCode: 'TD', name: 'Borkou' },\n  { code: 'TD-CB', countryCode: 'TD', name: 'Chari-Baguirmi' },\n  { code: 'TD-GR', countryCode: 'TD', name: 'Guera' },\n  { code: 'TD-HL', countryCode: 'TD', name: 'Hadjer Lamis' },\n  { code: 'TD-KA', countryCode: 'TD', name: 'Kanem' },\n  { code: 'TD-LC', countryCode: 'TD', name: 'Lac' },\n  { code: 'TD-LO', countryCode: 'TD', name: 'Logone-Occidental' },\n  { code: 'TD-LR', countryCode: 'TD', name: 'Logone-Oriental' },\n  { code: 'TD-MA', countryCode: 'TD', name: 'Mandoul' },\n  { code: 'TD-ME', countryCode: 'TD', name: 'Mayo-Kebbi-Est' },\n  { code: 'TD-MO', countryCode: 'TD', name: 'Mayo-Kebbi-Ouest' },\n  { code: 'TD-MC', countryCode: 'TD', name: 'Moyen-Chari' },\n  { code: 'TD-OD', countryCode: 'TD', name: 'Ouaddai' },\n  { code: 'TD-SA', countryCode: 'TD', name: 'Salamat' },\n  { code: 'TD-TA', countryCode: 'TD', name: 'Tandjile' },\n  { code: 'TD-TI', countryCode: 'TD', name: 'Tibesti' },\n  { code: 'TD-WF', countryCode: 'TD', name: 'Wadi Fira' },\n  { code: 'TG-C', countryCode: 'TG', name: 'Centrale' },\n  { code: 'TG-K', countryCode: 'TG', name: 'Kara' },\n  { code: 'TG-M', countryCode: 'TG', name: 'Maritime' },\n  { code: 'TG-P', countryCode: 'TG', name: 'Plateaux' },\n  { code: 'TG-S', countryCode: 'TG', name: 'Savannes' },\n  { code: 'TH-37', countryCode: 'TH', name: 'Amnat Charoen' },\n  { code: 'TH-15', countryCode: 'TH', name: 'Ang Thong' },\n  { code: 'TH-31', countryCode: 'TH', name: 'Buri Ram' },\n  { code: 'TH-24', countryCode: 'TH', name: 'Chachoengsao' },\n  { code: 'TH-18', countryCode: 'TH', name: 'Chai Nat' },\n  { code: 'TH-36', countryCode: 'TH', name: 'Chaiyaphum' },\n  { code: 'TH-22', countryCode: 'TH', name: 'Chanthaburi' },\n  { code: 'TH-50', countryCode: 'TH', name: 'Chiang Mai' },\n  { code: 'TH-57', countryCode: 'TH', name: 'Chiang Rai' },\n  { code: 'TH-20', countryCode: 'TH', name: 'Chon Buri' },\n  { code: 'TH-86', countryCode: 'TH', name: 'Chumphon' },\n  { code: 'TH-46', countryCode: 'TH', name: 'Kalasin' },\n  { code: 'TH-62', countryCode: 'TH', name: 'Kamphaeng Phet' },\n  { code: 'TH-71', countryCode: 'TH', name: 'Kanchanaburi' },\n  { code: 'TH-40', countryCode: 'TH', name: 'Khon Kaen' },\n  { code: 'TH-81', countryCode: 'TH', name: 'Krabi' },\n  { code: 'TH-10', countryCode: 'TH', name: 'Krung Thep Maha Nakhon' },\n  { code: 'TH-52', countryCode: 'TH', name: 'Lampang' },\n  { code: 'TH-51', countryCode: 'TH', name: 'Lamphun' },\n  { code: 'TH-42', countryCode: 'TH', name: 'Loei' },\n  { code: 'TH-16', countryCode: 'TH', name: 'Lop Buri' },\n  { code: 'TH-58', countryCode: 'TH', name: 'Mae Hong Son' },\n  { code: 'TH-44', countryCode: 'TH', name: 'Maha Sarakham' },\n  { code: 'TH-49', countryCode: 'TH', name: 'Mukdahan' },\n  { code: 'TH-26', countryCode: 'TH', name: 'Nakhon Nayok' },\n  { code: 'TH-73', countryCode: 'TH', name: 'Nakhon Pathom' },\n  { code: 'TH-48', countryCode: 'TH', name: 'Nakhon Phanom' },\n  { code: 'TH-30', countryCode: 'TH', name: 'Nakhon Ratchasima' },\n  { code: 'TH-60', countryCode: 'TH', name: 'Nakhon Sawan' },\n  { code: 'TH-80', countryCode: 'TH', name: 'Nakhon Si Thammarat' },\n  { code: 'TH-55', countryCode: 'TH', name: 'Nan' },\n  { code: 'TH-96', countryCode: 'TH', name: 'Narathiwat' },\n  { code: 'TH-39', countryCode: 'TH', name: 'Nong Bua Lam Phu' },\n  { code: 'TH-43', countryCode: 'TH', name: 'Nong Khai' },\n  { code: 'TH-12', countryCode: 'TH', name: 'Nonthaburi' },\n  { code: 'TH-13', countryCode: 'TH', name: 'Pathum Thani' },\n  { code: 'TH-94', countryCode: 'TH', name: 'Pattani' },\n  { code: 'TH-82', countryCode: 'TH', name: 'Phangnga' },\n  { code: 'TH-93', countryCode: 'TH', name: 'Phatthalung' },\n  { code: 'TH-56', countryCode: 'TH', name: 'Phayao' },\n  { code: 'TH-67', countryCode: 'TH', name: 'Phetchabun' },\n  { code: 'TH-76', countryCode: 'TH', name: 'Phetchaburi' },\n  { code: 'TH-66', countryCode: 'TH', name: 'Phichit' },\n  { code: 'TH-65', countryCode: 'TH', name: 'Phitsanulok' },\n  { code: 'TH-14', countryCode: 'TH', name: 'Phra Nakhon Si Ayutthaya' },\n  { code: 'TH-54', countryCode: 'TH', name: 'Phrae' },\n  { code: 'TH-83', countryCode: 'TH', name: 'Phuket' },\n  { code: 'TH-25', countryCode: 'TH', name: 'Prachin Buri' },\n  { code: 'TH-77', countryCode: 'TH', name: 'Prachuap Khiri Khan' },\n  { code: 'TH-85', countryCode: 'TH', name: 'Ranong' },\n  { code: 'TH-70', countryCode: 'TH', name: 'Ratchaburi' },\n  { code: 'TH-21', countryCode: 'TH', name: 'Rayong' },\n  { code: 'TH-45', countryCode: 'TH', name: 'Roi Et' },\n  { code: 'TH-27', countryCode: 'TH', name: 'Sa Kaeo' },\n  { code: 'TH-47', countryCode: 'TH', name: 'Sakon Nakhon' },\n  { code: 'TH-11', countryCode: 'TH', name: 'Samut Prakan' },\n  { code: 'TH-74', countryCode: 'TH', name: 'Samut Sakhon' },\n  { code: 'TH-75', countryCode: 'TH', name: 'Samut Songkhram' },\n  { code: 'TH-19', countryCode: 'TH', name: 'Saraburi' },\n  { code: 'TH-91', countryCode: 'TH', name: 'Satun' },\n  { code: 'TH-33', countryCode: 'TH', name: 'Si Sa Ket' },\n  { code: 'TH-17', countryCode: 'TH', name: 'Sing Buri' },\n  { code: 'TH-90', countryCode: 'TH', name: 'Songkhla' },\n  { code: 'TH-64', countryCode: 'TH', name: 'Sukhothai' },\n  { code: 'TH-72', countryCode: 'TH', name: 'Suphan Buri' },\n  { code: 'TH-84', countryCode: 'TH', name: 'Surat Thani' },\n  { code: 'TH-32', countryCode: 'TH', name: 'Surin' },\n  { code: 'TH-63', countryCode: 'TH', name: 'Tak' },\n  { code: 'TH-92', countryCode: 'TH', name: 'Trang' },\n  { code: 'TH-23', countryCode: 'TH', name: 'Trat' },\n  { code: 'TH-34', countryCode: 'TH', name: 'Ubon Ratchathani' },\n  { code: 'TH-41', countryCode: 'TH', name: 'Udon Thani' },\n  { code: 'TH-61', countryCode: 'TH', name: 'Uthai Thani' },\n  { code: 'TH-53', countryCode: 'TH', name: 'Uttaradit' },\n  { code: 'TH-95', countryCode: 'TH', name: 'Yala' },\n  { code: 'TH-35', countryCode: 'TH', name: 'Yasothon' },\n  { code: 'TJ-DU', countryCode: 'TJ', name: 'Dushanbe' },\n  { code: 'TJ-KT', countryCode: 'TJ', name: 'Khatlon' },\n  { code: 'TJ-GB', countryCode: 'TJ', name: 'Kuhistoni Badakhshon' },\n  { code: 'TJ-RA', countryCode: 'TJ', name: 'Nohiyahoi Tobei Jumhuri' },\n  { code: 'TJ-SU', countryCode: 'TJ', name: 'Sughd' },\n  { code: 'TL-DI', countryCode: 'TL', name: 'Dili' },\n  { code: 'TM-A', countryCode: 'TM', name: 'Ahal' },\n  { code: 'TM-B', countryCode: 'TM', name: 'Balkan' },\n  { code: 'TM-D', countryCode: 'TM', name: 'Dasoguz' },\n  { code: 'TM-L', countryCode: 'TM', name: 'Lebap' },\n  { code: 'TM-M', countryCode: 'TM', name: 'Mary' },\n  { code: 'TN-31', countryCode: 'TN', name: 'Beja' },\n  { code: 'TN-13', countryCode: 'TN', name: 'Ben Arous' },\n  { code: 'TN-23', countryCode: 'TN', name: 'Bizerte' },\n  { code: 'TN-81', countryCode: 'TN', name: 'Gabes' },\n  { code: 'TN-71', countryCode: 'TN', name: 'Gafsa' },\n  { code: 'TN-32', countryCode: 'TN', name: 'Jendouba' },\n  { code: 'TN-41', countryCode: 'TN', name: 'Kairouan' },\n  { code: 'TN-42', countryCode: 'TN', name: 'Kasserine' },\n  { code: 'TN-73', countryCode: 'TN', name: 'Kebili' },\n  { code: 'TN-12', countryCode: 'TN', name: \"L'Ariana\" },\n  { code: 'TN-14', countryCode: 'TN', name: 'La Manouba' },\n  { code: 'TN-33', countryCode: 'TN', name: 'Le Kef' },\n  { code: 'TN-53', countryCode: 'TN', name: 'Mahdia' },\n  { code: 'TN-82', countryCode: 'TN', name: 'Medenine' },\n  { code: 'TN-52', countryCode: 'TN', name: 'Monastir' },\n  { code: 'TN-21', countryCode: 'TN', name: 'Nabeul' },\n  { code: 'TN-61', countryCode: 'TN', name: 'Sfax' },\n  { code: 'TN-43', countryCode: 'TN', name: 'Sidi Bouzid' },\n  { code: 'TN-34', countryCode: 'TN', name: 'Siliana' },\n  { code: 'TN-51', countryCode: 'TN', name: 'Sousse' },\n  { code: 'TN-83', countryCode: 'TN', name: 'Tataouine' },\n  { code: 'TN-72', countryCode: 'TN', name: 'Tozeur' },\n  { code: 'TN-11', countryCode: 'TN', name: 'Tunis' },\n  { code: 'TN-22', countryCode: 'TN', name: 'Zaghouan' },\n  { code: 'TO-02', countryCode: 'TO', name: \"Ha'apai\" },\n  { code: 'TO-04', countryCode: 'TO', name: 'Tongatapu' },\n  { code: 'TO-05', countryCode: 'TO', name: \"Vava'u\" },\n  { code: 'TR-01', countryCode: 'TR', name: 'Adana' },\n  { code: 'TR-02', countryCode: 'TR', name: 'Adiyaman' },\n  { code: 'TR-03', countryCode: 'TR', name: 'Afyonkarahisar' },\n  { code: 'TR-04', countryCode: 'TR', name: 'Agri' },\n  { code: 'TR-68', countryCode: 'TR', name: 'Aksaray' },\n  { code: 'TR-05', countryCode: 'TR', name: 'Amasya' },\n  { code: 'TR-06', countryCode: 'TR', name: 'Ankara' },\n  { code: 'TR-07', countryCode: 'TR', name: 'Antalya' },\n  { code: 'TR-75', countryCode: 'TR', name: 'Ardahan' },\n  { code: 'TR-08', countryCode: 'TR', name: 'Artvin' },\n  { code: 'TR-09', countryCode: 'TR', name: 'Aydin' },\n  { code: 'TR-10', countryCode: 'TR', name: 'Balikesir' },\n  { code: 'TR-74', countryCode: 'TR', name: 'Bartin' },\n  { code: 'TR-72', countryCode: 'TR', name: 'Batman' },\n  { code: 'TR-69', countryCode: 'TR', name: 'Bayburt' },\n  { code: 'TR-11', countryCode: 'TR', name: 'Bilecik' },\n  { code: 'TR-12', countryCode: 'TR', name: 'Bingol' },\n  { code: 'TR-13', countryCode: 'TR', name: 'Bitlis' },\n  { code: 'TR-14', countryCode: 'TR', name: 'Bolu' },\n  { code: 'TR-15', countryCode: 'TR', name: 'Burdur' },\n  { code: 'TR-16', countryCode: 'TR', name: 'Bursa' },\n  { code: 'TR-17', countryCode: 'TR', name: 'Canakkale' },\n  { code: 'TR-18', countryCode: 'TR', name: 'Cankiri' },\n  { code: 'TR-19', countryCode: 'TR', name: 'Corum' },\n  { code: 'TR-20', countryCode: 'TR', name: 'Denizli' },\n  { code: 'TR-21', countryCode: 'TR', name: 'Diyarbakir' },\n  { code: 'TR-81', countryCode: 'TR', name: 'Duzce' },\n  { code: 'TR-22', countryCode: 'TR', name: 'Edirne' },\n  { code: 'TR-23', countryCode: 'TR', name: 'Elazig' },\n  { code: 'TR-24', countryCode: 'TR', name: 'Erzincan' },\n  { code: 'TR-25', countryCode: 'TR', name: 'Erzurum' },\n  { code: 'TR-26', countryCode: 'TR', name: 'Eskisehir' },\n  { code: 'TR-27', countryCode: 'TR', name: 'Gaziantep' },\n  { code: 'TR-28', countryCode: 'TR', name: 'Giresun' },\n  { code: 'TR-29', countryCode: 'TR', name: 'Gumushane' },\n  { code: 'TR-30', countryCode: 'TR', name: 'Hakkari' },\n  { code: 'TR-31', countryCode: 'TR', name: 'Hatay' },\n  { code: 'TR-76', countryCode: 'TR', name: 'Igdir' },\n  { code: 'TR-32', countryCode: 'TR', name: 'Isparta' },\n  { code: 'TR-34', countryCode: 'TR', name: 'Istanbul' },\n  { code: 'TR-35', countryCode: 'TR', name: 'Izmir' },\n  { code: 'TR-46', countryCode: 'TR', name: 'Kahramanmaras' },\n  { code: 'TR-78', countryCode: 'TR', name: 'Karabuk' },\n  { code: 'TR-70', countryCode: 'TR', name: 'Karaman' },\n  { code: 'TR-36', countryCode: 'TR', name: 'Kars' },\n  { code: 'TR-37', countryCode: 'TR', name: 'Kastamonu' },\n  { code: 'TR-38', countryCode: 'TR', name: 'Kayseri' },\n  { code: 'TR-79', countryCode: 'TR', name: 'Kilis' },\n  { code: 'TR-71', countryCode: 'TR', name: 'Kirikkale' },\n  { code: 'TR-39', countryCode: 'TR', name: 'Kirklareli' },\n  { code: 'TR-40', countryCode: 'TR', name: 'Kirsehir' },\n  { code: 'TR-41', countryCode: 'TR', name: 'Kocaeli' },\n  { code: 'TR-42', countryCode: 'TR', name: 'Konya' },\n  { code: 'TR-43', countryCode: 'TR', name: 'Kutahya' },\n  { code: 'TR-44', countryCode: 'TR', name: 'Malatya' },\n  { code: 'TR-45', countryCode: 'TR', name: 'Manisa' },\n  { code: 'TR-47', countryCode: 'TR', name: 'Mardin' },\n  { code: 'TR-33', countryCode: 'TR', name: 'Mersin' },\n  { code: 'TR-48', countryCode: 'TR', name: 'Mugla' },\n  { code: 'TR-49', countryCode: 'TR', name: 'Mus' },\n  { code: 'TR-50', countryCode: 'TR', name: 'Nevsehir' },\n  { code: 'TR-51', countryCode: 'TR', name: 'Nigde' },\n  { code: 'TR-52', countryCode: 'TR', name: 'Ordu' },\n  { code: 'TR-80', countryCode: 'TR', name: 'Osmaniye' },\n  { code: 'TR-53', countryCode: 'TR', name: 'Rize' },\n  { code: 'TR-54', countryCode: 'TR', name: 'Sakarya' },\n  { code: 'TR-55', countryCode: 'TR', name: 'Samsun' },\n  { code: 'TR-63', countryCode: 'TR', name: 'Sanliurfa' },\n  { code: 'TR-56', countryCode: 'TR', name: 'Siirt' },\n  { code: 'TR-57', countryCode: 'TR', name: 'Sinop' },\n  { code: 'TR-73', countryCode: 'TR', name: 'Sirnak' },\n  { code: 'TR-58', countryCode: 'TR', name: 'Sivas' },\n  { code: 'TR-59', countryCode: 'TR', name: 'Tekirdag' },\n  { code: 'TR-60', countryCode: 'TR', name: 'Tokat' },\n  { code: 'TR-61', countryCode: 'TR', name: 'Trabzon' },\n  { code: 'TR-62', countryCode: 'TR', name: 'Tunceli' },\n  { code: 'TR-64', countryCode: 'TR', name: 'Usak' },\n  { code: 'TR-65', countryCode: 'TR', name: 'Van' },\n  { code: 'TR-77', countryCode: 'TR', name: 'Yalova' },\n  { code: 'TR-66', countryCode: 'TR', name: 'Yozgat' },\n  { code: 'TR-67', countryCode: 'TR', name: 'Zonguldak' },\n  { code: 'TT-ARI', countryCode: 'TT', name: 'Arima' },\n  { code: 'TT-CHA', countryCode: 'TT', name: 'Chaguanas' },\n  { code: 'TT-CTT', countryCode: 'TT', name: 'Couva-Tabaquite-Talparo' },\n  { code: 'TT-DMN', countryCode: 'TT', name: 'Diego Martin' },\n  { code: 'TT-MRC', countryCode: 'TT', name: 'Mayaro-Rio Claro' },\n  { code: 'TT-PED', countryCode: 'TT', name: 'Penal-Debe' },\n  { code: 'TT-PTF', countryCode: 'TT', name: 'Point Fortin' },\n  { code: 'TT-POS', countryCode: 'TT', name: 'Port of Spain' },\n  { code: 'TT-PRT', countryCode: 'TT', name: 'Princes Town' },\n  { code: 'TT-SFO', countryCode: 'TT', name: 'San Fernando' },\n  { code: 'TT-SJL', countryCode: 'TT', name: 'San Juan-Laventille' },\n  { code: 'TT-SGE', countryCode: 'TT', name: 'Sangre Grande' },\n  { code: 'TT-SIP', countryCode: 'TT', name: 'Siparia' },\n  { code: 'TT-TOB', countryCode: 'TT', name: 'Tobago' },\n  { code: 'TT-TUP', countryCode: 'TT', name: 'Tunapuna-Piarco' },\n  { code: 'TV-FUN', countryCode: 'TV', name: 'Funafuti' },\n  { code: 'TW-CHA', countryCode: 'TW', name: 'Changhua' },\n  { code: 'TW-CYQ', countryCode: 'TW', name: 'Chiayi' },\n  { code: 'TW-HSQ', countryCode: 'TW', name: 'Hsinchu' },\n  { code: 'TW-HUA', countryCode: 'TW', name: 'Hualien' },\n  { code: 'TW-KHH', countryCode: 'TW', name: 'Kaohsiung' },\n  { code: 'TW-KEE', countryCode: 'TW', name: 'Keelung' },\n  { code: 'TW-KIN', countryCode: 'TW', name: 'Kinmen' },\n  { code: 'TW-LIE', countryCode: 'TW', name: 'Lienchiang' },\n  { code: 'TW-MIA', countryCode: 'TW', name: 'Miaoli' },\n  { code: 'TW-NAN', countryCode: 'TW', name: 'Nantou' },\n  { code: 'TW-NWT', countryCode: 'TW', name: 'New Taipei' },\n  { code: 'TW-PEN', countryCode: 'TW', name: 'Penghu' },\n  { code: 'TW-PIF', countryCode: 'TW', name: 'Pingtung' },\n  { code: 'TW-TXG', countryCode: 'TW', name: 'Taichung' },\n  { code: 'TW-TNN', countryCode: 'TW', name: 'Tainan' },\n  { code: 'TW-TPE', countryCode: 'TW', name: 'Taipei' },\n  { code: 'TW-TTT', countryCode: 'TW', name: 'Taitung' },\n  { code: 'TW-TAO', countryCode: 'TW', name: 'Taoyuan' },\n  { code: 'TW-ILA', countryCode: 'TW', name: 'Yilan' },\n  { code: 'TW-YUN', countryCode: 'TW', name: 'Yunlin' },\n  { code: 'TZ-01', countryCode: 'TZ', name: 'Arusha' },\n  { code: 'TZ-02', countryCode: 'TZ', name: 'Dar es Salaam' },\n  { code: 'TZ-03', countryCode: 'TZ', name: 'Dodoma' },\n  { code: 'TZ-04', countryCode: 'TZ', name: 'Iringa' },\n  { code: 'TZ-05', countryCode: 'TZ', name: 'Kagera' },\n  { code: 'TZ-06', countryCode: 'TZ', name: 'Kaskazini Pemba' },\n  { code: 'TZ-07', countryCode: 'TZ', name: 'Kaskazini Unguja' },\n  { code: 'TZ-08', countryCode: 'TZ', name: 'Kigoma' },\n  { code: 'TZ-09', countryCode: 'TZ', name: 'Kilimanjaro' },\n  { code: 'TZ-10', countryCode: 'TZ', name: 'Kusini Pemba' },\n  { code: 'TZ-11', countryCode: 'TZ', name: 'Kusini Unguja' },\n  { code: 'TZ-12', countryCode: 'TZ', name: 'Lindi' },\n  { code: 'TZ-26', countryCode: 'TZ', name: 'Manyara' },\n  { code: 'TZ-13', countryCode: 'TZ', name: 'Mara' },\n  { code: 'TZ-14', countryCode: 'TZ', name: 'Mbeya' },\n  { code: 'TZ-15', countryCode: 'TZ', name: 'Mjini Magharibi' },\n  { code: 'TZ-16', countryCode: 'TZ', name: 'Morogoro' },\n  { code: 'TZ-17', countryCode: 'TZ', name: 'Mtwara' },\n  { code: 'TZ-18', countryCode: 'TZ', name: 'Mwanza' },\n  { code: 'TZ-19', countryCode: 'TZ', name: 'Pwani' },\n  { code: 'TZ-20', countryCode: 'TZ', name: 'Rukwa' },\n  { code: 'TZ-21', countryCode: 'TZ', name: 'Ruvuma' },\n  { code: 'TZ-22', countryCode: 'TZ', name: 'Shinyanga' },\n  { code: 'TZ-23', countryCode: 'TZ', name: 'Singida' },\n  { code: 'TZ-24', countryCode: 'TZ', name: 'Tabora' },\n  { code: 'TZ-25', countryCode: 'TZ', name: 'Tanga' },\n  { code: 'UA-43', countryCode: 'UA', name: 'Avtonomna Respublika Krym' },\n  { code: 'UA-71', countryCode: 'UA', name: 'Cherkaska oblast' },\n  { code: 'UA-74', countryCode: 'UA', name: 'Chernihivska oblast' },\n  { code: 'UA-77', countryCode: 'UA', name: 'Chernivetska oblast' },\n  { code: 'UA-12', countryCode: 'UA', name: 'Dnipropetrovska oblast' },\n  { code: 'UA-14', countryCode: 'UA', name: 'Donetska oblast' },\n  { code: 'UA-26', countryCode: 'UA', name: 'Ivano-Frankivska oblast' },\n  { code: 'UA-63', countryCode: 'UA', name: 'Kharkivska oblast' },\n  { code: 'UA-65', countryCode: 'UA', name: 'Khersonska oblast' },\n  { code: 'UA-68', countryCode: 'UA', name: 'Khmelnytska oblast' },\n  { code: 'UA-35', countryCode: 'UA', name: 'Kirovohradska oblast' },\n  { code: 'UA-30', countryCode: 'UA', name: 'Kyiv' },\n  { code: 'UA-32', countryCode: 'UA', name: 'Kyivska oblast' },\n  { code: 'UA-09', countryCode: 'UA', name: 'Luhanska oblast' },\n  { code: 'UA-46', countryCode: 'UA', name: 'Lvivska oblast' },\n  { code: 'UA-48', countryCode: 'UA', name: 'Mykolaivska oblast' },\n  { code: 'UA-51', countryCode: 'UA', name: 'Odeska oblast' },\n  { code: 'UA-53', countryCode: 'UA', name: 'Poltavska oblast' },\n  { code: 'UA-56', countryCode: 'UA', name: 'Rivnenska oblast' },\n  { code: 'UA-40', countryCode: 'UA', name: \"Sevastopol'\" },\n  { code: 'UA-59', countryCode: 'UA', name: 'Sumska oblast' },\n  { code: 'UA-61', countryCode: 'UA', name: 'Ternopilska oblast' },\n  { code: 'UA-05', countryCode: 'UA', name: 'Vinnytska oblast' },\n  { code: 'UA-07', countryCode: 'UA', name: 'Volynska oblast' },\n  { code: 'UA-21', countryCode: 'UA', name: 'Zakarpatska oblast' },\n  { code: 'UA-23', countryCode: 'UA', name: 'Zaporizka oblast' },\n  { code: 'UA-18', countryCode: 'UA', name: 'Zhytomyrska oblast' },\n  { code: 'UG-317', countryCode: 'UG', name: 'Abim' },\n  { code: 'UG-301', countryCode: 'UG', name: 'Adjumani' },\n  { code: 'UG-322', countryCode: 'UG', name: 'Agago' },\n  { code: 'UG-323', countryCode: 'UG', name: 'Alebtong' },\n  { code: 'UG-314', countryCode: 'UG', name: 'Amolatar' },\n  { code: 'UG-324', countryCode: 'UG', name: 'Amudat' },\n  { code: 'UG-216', countryCode: 'UG', name: 'Amuria' },\n  { code: 'UG-319', countryCode: 'UG', name: 'Amuru' },\n  { code: 'UG-302', countryCode: 'UG', name: 'Apac' },\n  { code: 'UG-303', countryCode: 'UG', name: 'Arua' },\n  { code: 'UG-217', countryCode: 'UG', name: 'Budaka' },\n  { code: 'UG-223', countryCode: 'UG', name: 'Bududa' },\n  { code: 'UG-201', countryCode: 'UG', name: 'Bugiri' },\n  { code: 'UG-117', countryCode: 'UG', name: 'Buikwe' },\n  { code: 'UG-224', countryCode: 'UG', name: 'Bukedea' },\n  { code: 'UG-118', countryCode: 'UG', name: 'Bukomansibi' },\n  { code: 'UG-218', countryCode: 'UG', name: 'Bukwa' },\n  { code: 'UG-225', countryCode: 'UG', name: 'Bulambuli' },\n  { code: 'UG-419', countryCode: 'UG', name: 'Buliisa' },\n  { code: 'UG-401', countryCode: 'UG', name: 'Bundibugyo' },\n  { code: 'UG-402', countryCode: 'UG', name: 'Bushenyi' },\n  { code: 'UG-202', countryCode: 'UG', name: 'Busia' },\n  { code: 'UG-219', countryCode: 'UG', name: 'Butaleja' },\n  { code: 'UG-120', countryCode: 'UG', name: 'Buvuma' },\n  { code: 'UG-226', countryCode: 'UG', name: 'Buyende' },\n  { code: 'UG-318', countryCode: 'UG', name: 'Dokolo' },\n  { code: 'UG-121', countryCode: 'UG', name: 'Gomba' },\n  { code: 'UG-304', countryCode: 'UG', name: 'Gulu' },\n  { code: 'UG-403', countryCode: 'UG', name: 'Hoima' },\n  { code: 'UG-203', countryCode: 'UG', name: 'Iganga' },\n  { code: 'UG-417', countryCode: 'UG', name: 'Isingiro' },\n  { code: 'UG-204', countryCode: 'UG', name: 'Jinja' },\n  { code: 'UG-315', countryCode: 'UG', name: 'Kaabong' },\n  { code: 'UG-404', countryCode: 'UG', name: 'Kabale' },\n  { code: 'UG-405', countryCode: 'UG', name: 'Kabarole' },\n  { code: 'UG-213', countryCode: 'UG', name: 'Kaberamaido' },\n  { code: 'UG-101', countryCode: 'UG', name: 'Kalangala' },\n  { code: 'UG-220', countryCode: 'UG', name: 'Kaliro' },\n  { code: 'UG-102', countryCode: 'UG', name: 'Kampala' },\n  { code: 'UG-205', countryCode: 'UG', name: 'Kamuli' },\n  { code: 'UG-413', countryCode: 'UG', name: 'Kamwenge' },\n  { code: 'UG-414', countryCode: 'UG', name: 'Kanungu' },\n  { code: 'UG-206', countryCode: 'UG', name: 'Kapchorwa' },\n  { code: 'UG-406', countryCode: 'UG', name: 'Kasese' },\n  { code: 'UG-207', countryCode: 'UG', name: 'Katakwi' },\n  { code: 'UG-112', countryCode: 'UG', name: 'Kayunga' },\n  { code: 'UG-407', countryCode: 'UG', name: 'Kibaale' },\n  { code: 'UG-103', countryCode: 'UG', name: 'Kiboga' },\n  { code: 'UG-227', countryCode: 'UG', name: 'Kibuku' },\n  { code: 'UG-420', countryCode: 'UG', name: 'Kiryandongo' },\n  { code: 'UG-408', countryCode: 'UG', name: 'Kisoro' },\n  { code: 'UG-305', countryCode: 'UG', name: 'Kitgum' },\n  { code: 'UG-316', countryCode: 'UG', name: 'Koboko' },\n  { code: 'UG-326', countryCode: 'UG', name: 'Kole' },\n  { code: 'UG-306', countryCode: 'UG', name: 'Kotido' },\n  { code: 'UG-208', countryCode: 'UG', name: 'Kumi' },\n  { code: 'UG-228', countryCode: 'UG', name: 'Kween' },\n  { code: 'UG-123', countryCode: 'UG', name: 'Kyankwanzi' },\n  { code: 'UG-421', countryCode: 'UG', name: 'Kyegegwa' },\n  { code: 'UG-415', countryCode: 'UG', name: 'Kyenjojo' },\n  { code: 'UG-307', countryCode: 'UG', name: 'Lira' },\n  { code: 'UG-229', countryCode: 'UG', name: 'Luuka' },\n  { code: 'UG-104', countryCode: 'UG', name: 'Luwero' },\n  { code: 'UG-124', countryCode: 'UG', name: 'Lwengo' },\n  { code: 'UG-116', countryCode: 'UG', name: 'Lyantonde' },\n  { code: 'UG-221', countryCode: 'UG', name: 'Manafwa' },\n  { code: 'UG-320', countryCode: 'UG', name: 'Maracha' },\n  { code: 'UG-105', countryCode: 'UG', name: 'Masaka' },\n  { code: 'UG-409', countryCode: 'UG', name: 'Masindi' },\n  { code: 'UG-214', countryCode: 'UG', name: 'Mayuge' },\n  { code: 'UG-209', countryCode: 'UG', name: 'Mbale' },\n  { code: 'UG-410', countryCode: 'UG', name: 'Mbarara' },\n  { code: 'UG-422', countryCode: 'UG', name: 'Mitooma' },\n  { code: 'UG-114', countryCode: 'UG', name: 'Mityana' },\n  { code: 'UG-308', countryCode: 'UG', name: 'Moroto' },\n  { code: 'UG-309', countryCode: 'UG', name: 'Moyo' },\n  { code: 'UG-106', countryCode: 'UG', name: 'Mpigi' },\n  { code: 'UG-107', countryCode: 'UG', name: 'Mubende' },\n  { code: 'UG-108', countryCode: 'UG', name: 'Mukono' },\n  { code: 'UG-311', countryCode: 'UG', name: 'Nakapiripirit' },\n  { code: 'UG-115', countryCode: 'UG', name: 'Nakaseke' },\n  { code: 'UG-109', countryCode: 'UG', name: 'Nakasongola' },\n  { code: 'UG-230', countryCode: 'UG', name: 'Namayingo' },\n  { code: 'UG-222', countryCode: 'UG', name: 'Namutumba' },\n  { code: 'UG-328', countryCode: 'UG', name: 'Napak' },\n  { code: 'UG-310', countryCode: 'UG', name: 'Nebbi' },\n  { code: 'UG-231', countryCode: 'UG', name: 'Ngora' },\n  { code: 'UG-423', countryCode: 'UG', name: 'Ntoroko' },\n  { code: 'UG-411', countryCode: 'UG', name: 'Ntungamo' },\n  { code: 'UG-330', countryCode: 'UG', name: 'Otuke' },\n  { code: 'UG-321', countryCode: 'UG', name: 'Oyam' },\n  { code: 'UG-312', countryCode: 'UG', name: 'Pader' },\n  { code: 'UG-210', countryCode: 'UG', name: 'Pallisa' },\n  { code: 'UG-110', countryCode: 'UG', name: 'Rakai' },\n  { code: 'UG-424', countryCode: 'UG', name: 'Rubirizi' },\n  { code: 'UG-412', countryCode: 'UG', name: 'Rukungiri' },\n  { code: 'UG-111', countryCode: 'UG', name: 'Sembabule' },\n  { code: 'UG-232', countryCode: 'UG', name: 'Serere' },\n  { code: 'UG-425', countryCode: 'UG', name: 'Sheema' },\n  { code: 'UG-215', countryCode: 'UG', name: 'Sironko' },\n  { code: 'UG-211', countryCode: 'UG', name: 'Soroti' },\n  { code: 'UG-212', countryCode: 'UG', name: 'Tororo' },\n  { code: 'UG-113', countryCode: 'UG', name: 'Wakiso' },\n  { code: 'UG-313', countryCode: 'UG', name: 'Yumbe' },\n  { code: 'UG-331', countryCode: 'UG', name: 'Zombo' },\n  { code: 'UM-95', countryCode: 'UM', name: 'Palmyra Atoll' },\n  { code: 'US-AL', countryCode: 'US', name: 'Alabama' },\n  { code: 'US-AK', countryCode: 'US', name: 'Alaska' },\n  { code: 'US-AZ', countryCode: 'US', name: 'Arizona' },\n  { code: 'US-AR', countryCode: 'US', name: 'Arkansas' },\n  { code: 'US-CA', countryCode: 'US', name: 'California' },\n  { code: 'US-CO', countryCode: 'US', name: 'Colorado' },\n  { code: 'US-CT', countryCode: 'US', name: 'Connecticut' },\n  { code: 'US-DE', countryCode: 'US', name: 'Delaware' },\n  { code: 'US-DC', countryCode: 'US', name: 'District of Columbia' },\n  { code: 'US-FL', countryCode: 'US', name: 'Florida' },\n  { code: 'US-GA', countryCode: 'US', name: 'Georgia' },\n  { code: 'US-HI', countryCode: 'US', name: 'Hawaii' },\n  { code: 'US-ID', countryCode: 'US', name: 'Idaho' },\n  { code: 'US-IL', countryCode: 'US', name: 'Illinois' },\n  { code: 'US-IN', countryCode: 'US', name: 'Indiana' },\n  { code: 'US-IA', countryCode: 'US', name: 'Iowa' },\n  { code: 'US-KS', countryCode: 'US', name: 'Kansas' },\n  { code: 'US-KY', countryCode: 'US', name: 'Kentucky' },\n  { code: 'US-LA', countryCode: 'US', name: 'Louisiana' },\n  { code: 'US-ME', countryCode: 'US', name: 'Maine' },\n  { code: 'US-MD', countryCode: 'US', name: 'Maryland' },\n  { code: 'US-MA', countryCode: 'US', name: 'Massachusetts' },\n  { code: 'US-MI', countryCode: 'US', name: 'Michigan' },\n  { code: 'US-MN', countryCode: 'US', name: 'Minnesota' },\n  { code: 'US-MS', countryCode: 'US', name: 'Mississippi' },\n  { code: 'US-MO', countryCode: 'US', name: 'Missouri' },\n  { code: 'US-MT', countryCode: 'US', name: 'Montana' },\n  { code: 'US-NE', countryCode: 'US', name: 'Nebraska' },\n  { code: 'US-NV', countryCode: 'US', name: 'Nevada' },\n  { code: 'US-NH', countryCode: 'US', name: 'New Hampshire' },\n  { code: 'US-NJ', countryCode: 'US', name: 'New Jersey' },\n  { code: 'US-NM', countryCode: 'US', name: 'New Mexico' },\n  { code: 'US-NY', countryCode: 'US', name: 'New York' },\n  { code: 'US-NC', countryCode: 'US', name: 'North Carolina' },\n  { code: 'US-ND', countryCode: 'US', name: 'North Dakota' },\n  { code: 'US-OH', countryCode: 'US', name: 'Ohio' },\n  { code: 'US-OK', countryCode: 'US', name: 'Oklahoma' },\n  { code: 'US-OR', countryCode: 'US', name: 'Oregon' },\n  { code: 'US-PA', countryCode: 'US', name: 'Pennsylvania' },\n  { code: 'US-RI', countryCode: 'US', name: 'Rhode Island' },\n  { code: 'US-SC', countryCode: 'US', name: 'South Carolina' },\n  { code: 'US-SD', countryCode: 'US', name: 'South Dakota' },\n  { code: 'US-TN', countryCode: 'US', name: 'Tennessee' },\n  { code: 'US-TX', countryCode: 'US', name: 'Texas' },\n  { code: 'US-UT', countryCode: 'US', name: 'Utah' },\n  { code: 'US-VT', countryCode: 'US', name: 'Vermont' },\n  { code: 'US-VA', countryCode: 'US', name: 'Virginia' },\n  { code: 'US-WA', countryCode: 'US', name: 'Washington' },\n  { code: 'US-WV', countryCode: 'US', name: 'West Virginia' },\n  { code: 'US-WI', countryCode: 'US', name: 'Wisconsin' },\n  { code: 'US-WY', countryCode: 'US', name: 'Wyoming' },\n  { code: 'UY-AR', countryCode: 'UY', name: 'Artigas' },\n  { code: 'UY-CA', countryCode: 'UY', name: 'Canelones' },\n  { code: 'UY-CL', countryCode: 'UY', name: 'Cerro Largo' },\n  { code: 'UY-CO', countryCode: 'UY', name: 'Colonia' },\n  { code: 'UY-DU', countryCode: 'UY', name: 'Durazno' },\n  { code: 'UY-FS', countryCode: 'UY', name: 'Flores' },\n  { code: 'UY-FD', countryCode: 'UY', name: 'Florida' },\n  { code: 'UY-LA', countryCode: 'UY', name: 'Lavalleja' },\n  { code: 'UY-MA', countryCode: 'UY', name: 'Maldonado' },\n  { code: 'UY-MO', countryCode: 'UY', name: 'Montevideo' },\n  { code: 'UY-PA', countryCode: 'UY', name: 'Paysandu' },\n  { code: 'UY-RN', countryCode: 'UY', name: 'Rio Negro' },\n  { code: 'UY-RV', countryCode: 'UY', name: 'Rivera' },\n  { code: 'UY-RO', countryCode: 'UY', name: 'Rocha' },\n  { code: 'UY-SA', countryCode: 'UY', name: 'Salto' },\n  { code: 'UY-SJ', countryCode: 'UY', name: 'San Jose' },\n  { code: 'UY-SO', countryCode: 'UY', name: 'Soriano' },\n  { code: 'UY-TA', countryCode: 'UY', name: 'Tacuarembo' },\n  { code: 'UY-TT', countryCode: 'UY', name: 'Treinta y Tres' },\n  { code: 'UZ-AN', countryCode: 'UZ', name: 'Andijon' },\n  { code: 'UZ-BU', countryCode: 'UZ', name: 'Buxoro' },\n  { code: 'UZ-FA', countryCode: 'UZ', name: \"Farg'ona\" },\n  { code: 'UZ-JI', countryCode: 'UZ', name: 'Jizzax' },\n  { code: 'UZ-NG', countryCode: 'UZ', name: 'Namangan' },\n  { code: 'UZ-NW', countryCode: 'UZ', name: 'Navoiy' },\n  { code: 'UZ-QA', countryCode: 'UZ', name: 'Qashqadaryo' },\n  { code: 'UZ-QR', countryCode: 'UZ', name: \"Qoraqalpog'iston Respublikasi\" },\n  { code: 'UZ-SA', countryCode: 'UZ', name: 'Samarqand' },\n  { code: 'UZ-SI', countryCode: 'UZ', name: 'Sirdaryo' },\n  { code: 'UZ-SU', countryCode: 'UZ', name: 'Surxondaryo' },\n  { code: 'UZ-TK', countryCode: 'UZ', name: 'Toshkent' },\n  { code: 'UZ-XO', countryCode: 'UZ', name: 'Xorazm' },\n  { code: 'VC-01', countryCode: 'VC', name: 'Charlotte' },\n  { code: 'VC-04', countryCode: 'VC', name: 'Saint George' },\n  { code: 'VE-Z', countryCode: 'VE', name: 'Amazonas' },\n  { code: 'VE-B', countryCode: 'VE', name: 'Anzoategui' },\n  { code: 'VE-C', countryCode: 'VE', name: 'Apure' },\n  { code: 'VE-D', countryCode: 'VE', name: 'Aragua' },\n  { code: 'VE-E', countryCode: 'VE', name: 'Barinas' },\n  { code: 'VE-F', countryCode: 'VE', name: 'Bolivar' },\n  { code: 'VE-G', countryCode: 'VE', name: 'Carabobo' },\n  { code: 'VE-H', countryCode: 'VE', name: 'Cojedes' },\n  { code: 'VE-Y', countryCode: 'VE', name: 'Delta Amacuro' },\n  { code: 'VE-A', countryCode: 'VE', name: 'Distrito Capital' },\n  { code: 'VE-I', countryCode: 'VE', name: 'Falcon' },\n  { code: 'VE-J', countryCode: 'VE', name: 'Guarico' },\n  { code: 'VE-K', countryCode: 'VE', name: 'Lara' },\n  { code: 'VE-L', countryCode: 'VE', name: 'Merida' },\n  { code: 'VE-M', countryCode: 'VE', name: 'Miranda' },\n  { code: 'VE-N', countryCode: 'VE', name: 'Monagas' },\n  { code: 'VE-O', countryCode: 'VE', name: 'Nueva Esparta' },\n  { code: 'VE-P', countryCode: 'VE', name: 'Portuguesa' },\n  { code: 'VE-R', countryCode: 'VE', name: 'Sucre' },\n  { code: 'VE-S', countryCode: 'VE', name: 'Tachira' },\n  { code: 'VE-T', countryCode: 'VE', name: 'Trujillo' },\n  { code: 'VE-X', countryCode: 'VE', name: 'Vargas' },\n  { code: 'VE-U', countryCode: 'VE', name: 'Yaracuy' },\n  { code: 'VE-V', countryCode: 'VE', name: 'Zulia' },\n  { code: 'VN-44', countryCode: 'VN', name: 'An Giang' },\n  { code: 'VN-54', countryCode: 'VN', name: 'Bac Giang' },\n  { code: 'VN-53', countryCode: 'VN', name: 'Bac Kan' },\n  { code: 'VN-55', countryCode: 'VN', name: 'Bac Lieu' },\n  { code: 'VN-56', countryCode: 'VN', name: 'Bac Ninh' },\n  { code: 'VN-50', countryCode: 'VN', name: 'Ben Tre' },\n  { code: 'VN-31', countryCode: 'VN', name: 'Binh Dinh' },\n  { code: 'VN-57', countryCode: 'VN', name: 'Binh Duong' },\n  { code: 'VN-58', countryCode: 'VN', name: 'Binh Phuoc' },\n  { code: 'VN-40', countryCode: 'VN', name: 'Binh Thuan' },\n  { code: 'VN-59', countryCode: 'VN', name: 'Ca Mau' },\n  { code: 'VN-CT', countryCode: 'VN', name: 'Can Tho' },\n  { code: 'VN-04', countryCode: 'VN', name: 'Cao Bang' },\n  { code: 'VN-DN', countryCode: 'VN', name: 'Da Nang' },\n  { code: 'VN-33', countryCode: 'VN', name: 'Dak Lak' },\n  { code: 'VN-71', countryCode: 'VN', name: 'Dien Bien' },\n  { code: 'VN-39', countryCode: 'VN', name: 'Dong Nai' },\n  { code: 'VN-45', countryCode: 'VN', name: 'Dong Thap' },\n  { code: 'VN-30', countryCode: 'VN', name: 'Gia Lai' },\n  { code: 'VN-03', countryCode: 'VN', name: 'Ha Giang' },\n  { code: 'VN-63', countryCode: 'VN', name: 'Ha Nam' },\n  { code: 'VN-HN', countryCode: 'VN', name: 'Ha Noi' },\n  { code: 'VN-23', countryCode: 'VN', name: 'Ha Tinh' },\n  { code: 'VN-61', countryCode: 'VN', name: 'Hai Duong' },\n  { code: 'VN-HP', countryCode: 'VN', name: 'Hai Phong' },\n  { code: 'VN-SG', countryCode: 'VN', name: 'Ho Chi Minh' },\n  { code: 'VN-14', countryCode: 'VN', name: 'Hoa Binh' },\n  { code: 'VN-66', countryCode: 'VN', name: 'Hung Yen' },\n  { code: 'VN-34', countryCode: 'VN', name: 'Khanh Hoa' },\n  { code: 'VN-47', countryCode: 'VN', name: 'Kien Giang' },\n  { code: 'VN-01', countryCode: 'VN', name: 'Lai Chau' },\n  { code: 'VN-35', countryCode: 'VN', name: 'Lam Dong' },\n  { code: 'VN-09', countryCode: 'VN', name: 'Lang Son' },\n  { code: 'VN-02', countryCode: 'VN', name: 'Lao Cai' },\n  { code: 'VN-41', countryCode: 'VN', name: 'Long An' },\n  { code: 'VN-67', countryCode: 'VN', name: 'Nam Dinh' },\n  { code: 'VN-22', countryCode: 'VN', name: 'Nghe An' },\n  { code: 'VN-18', countryCode: 'VN', name: 'Ninh Binh' },\n  { code: 'VN-36', countryCode: 'VN', name: 'Ninh Thuan' },\n  { code: 'VN-68', countryCode: 'VN', name: 'Phu Tho' },\n  { code: 'VN-32', countryCode: 'VN', name: 'Phu Yen' },\n  { code: 'VN-24', countryCode: 'VN', name: 'Quang Binh' },\n  { code: 'VN-27', countryCode: 'VN', name: 'Quang Nam' },\n  { code: 'VN-29', countryCode: 'VN', name: 'Quang Ngai' },\n  { code: 'VN-13', countryCode: 'VN', name: 'Quang Ninh' },\n  { code: 'VN-25', countryCode: 'VN', name: 'Quang Tri' },\n  { code: 'VN-52', countryCode: 'VN', name: 'Soc Trang' },\n  { code: 'VN-05', countryCode: 'VN', name: 'Son La' },\n  { code: 'VN-37', countryCode: 'VN', name: 'Tay Ninh' },\n  { code: 'VN-20', countryCode: 'VN', name: 'Thai Binh' },\n  { code: 'VN-69', countryCode: 'VN', name: 'Thai Nguyen' },\n  { code: 'VN-21', countryCode: 'VN', name: 'Thanh Hoa' },\n  { code: 'VN-26', countryCode: 'VN', name: 'Thua Thien-Hue' },\n  { code: 'VN-46', countryCode: 'VN', name: 'Tien Giang' },\n  { code: 'VN-51', countryCode: 'VN', name: 'Tra Vinh' },\n  { code: 'VN-07', countryCode: 'VN', name: 'Tuyen Quang' },\n  { code: 'VN-49', countryCode: 'VN', name: 'Vinh Long' },\n  { code: 'VN-70', countryCode: 'VN', name: 'Vinh Phuc' },\n  { code: 'VN-06', countryCode: 'VN', name: 'Yen Bai' },\n  { code: 'VU-MAP', countryCode: 'VU', name: 'Malampa' },\n  { code: 'VU-SAM', countryCode: 'VU', name: 'Sanma' },\n  { code: 'VU-SEE', countryCode: 'VU', name: 'Shefa' },\n  { code: 'VU-TAE', countryCode: 'VU', name: 'Tafea' },\n  { code: 'VU-TOB', countryCode: 'VU', name: 'Torba' },\n  { code: 'WF-UV', countryCode: 'WF', name: 'Uvea' },\n  { code: 'WS-AA', countryCode: 'WS', name: \"A'ana\" },\n  { code: 'WS-AT', countryCode: 'WS', name: 'Atua' },\n  { code: 'WS-GI', countryCode: 'WS', name: 'Gagaifomauga' },\n  { code: 'WS-PA', countryCode: 'WS', name: 'Palauli' },\n  { code: 'WS-TU', countryCode: 'WS', name: 'Tuamasaga' },\n  { code: 'YE-AD', countryCode: 'YE', name: \"'Adan\" },\n  { code: 'YE-AM', countryCode: 'YE', name: \"'Amran\" },\n  { code: 'YE-AB', countryCode: 'YE', name: 'Abyan' },\n  { code: 'YE-DA', countryCode: 'YE', name: \"Ad Dali'\" },\n  { code: 'YE-BA', countryCode: 'YE', name: \"Al Bayda'\" },\n  { code: 'YE-HU', countryCode: 'YE', name: 'Al Hudaydah' },\n  { code: 'YE-JA', countryCode: 'YE', name: 'Al Jawf' },\n  { code: 'YE-MR', countryCode: 'YE', name: 'Al Mahrah' },\n  { code: 'YE-MW', countryCode: 'YE', name: 'Al Mahwit' },\n  { code: 'YE-SA', countryCode: 'YE', name: \"Amanat al 'Asimah\" },\n  { code: 'YE-DH', countryCode: 'YE', name: 'Dhamar' },\n  { code: 'YE-HD', countryCode: 'YE', name: 'Hadramawt' },\n  { code: 'YE-HJ', countryCode: 'YE', name: 'Hajjah' },\n  { code: 'YE-IB', countryCode: 'YE', name: 'Ibb' },\n  { code: 'YE-LA', countryCode: 'YE', name: 'Lahij' },\n  { code: 'YE-MA', countryCode: 'YE', name: \"Ma'rib\" },\n  { code: 'YE-RA', countryCode: 'YE', name: 'Raymah' },\n  { code: 'YE-SD', countryCode: 'YE', name: \"Sa'dah\" },\n  { code: 'YE-SN', countryCode: 'YE', name: \"San'a'\" },\n  { code: 'YE-SH', countryCode: 'YE', name: 'Shabwah' },\n  { code: 'YE-TA', countryCode: 'YE', name: \"Ta'izz\" },\n  { code: 'ZA-EC', countryCode: 'ZA', name: 'Eastern Cape' },\n  { code: 'ZA-FS', countryCode: 'ZA', name: 'Free State' },\n  { code: 'ZA-GT', countryCode: 'ZA', name: 'Gauteng' },\n  { code: 'ZA-NL', countryCode: 'ZA', name: 'Kwazulu-Natal' },\n  { code: 'ZA-LP', countryCode: 'ZA', name: 'Limpopo' },\n  { code: 'ZA-MP', countryCode: 'ZA', name: 'Mpumalanga' },\n  { code: 'ZA-NW', countryCode: 'ZA', name: 'North-West' },\n  { code: 'ZA-NC', countryCode: 'ZA', name: 'Northern Cape' },\n  { code: 'ZA-WC', countryCode: 'ZA', name: 'Western Cape' },\n  { code: 'ZM-02', countryCode: 'ZM', name: 'Central' },\n  { code: 'ZM-08', countryCode: 'ZM', name: 'Copperbelt' },\n  { code: 'ZM-03', countryCode: 'ZM', name: 'Eastern' },\n  { code: 'ZM-04', countryCode: 'ZM', name: 'Luapula' },\n  { code: 'ZM-09', countryCode: 'ZM', name: 'Lusaka' },\n  { code: 'ZM-06', countryCode: 'ZM', name: 'North-Western' },\n  { code: 'ZM-05', countryCode: 'ZM', name: 'Northern' },\n  { code: 'ZM-07', countryCode: 'ZM', name: 'Southern' },\n  { code: 'ZM-01', countryCode: 'ZM', name: 'Western' },\n  { code: 'ZW-BU', countryCode: 'ZW', name: 'Bulawayo' },\n  { code: 'ZW-HA', countryCode: 'ZW', name: 'Harare' },\n  { code: 'ZW-MA', countryCode: 'ZW', name: 'Manicaland' },\n  { code: 'ZW-MC', countryCode: 'ZW', name: 'Mashonaland Central' },\n  { code: 'ZW-ME', countryCode: 'ZW', name: 'Mashonaland East' },\n  { code: 'ZW-MW', countryCode: 'ZW', name: 'Mashonaland West' },\n  { code: 'ZW-MV', countryCode: 'ZW', name: 'Masvingo' },\n  { code: 'ZW-MN', countryCode: 'ZW', name: 'Matabeleland North' },\n  { code: 'ZW-MS', countryCode: 'ZW', name: 'Matabeleland South' },\n  { code: 'ZW-MI', countryCode: 'ZW', name: 'Midlands' }\n];\n"
  },
  {
    "path": "packages/evershop/src/lib/locale/timezones.ts",
    "content": "export interface Timezone {\n  code: string;\n  name: string;\n}\n\nexport const timezones: Timezone[] = [\n  {\n    code: 'Australia/Darwin',\n    name: 'AUS Central Standard Time (Australia/Darwin)'\n  },\n  {\n    code: 'Australia/Melbourne',\n    name: 'AUS Eastern Standard Time (Australia/Melbourne)'\n  },\n  {\n    code: 'Australia/Sydney',\n    name: 'AUS Eastern Standard Time (Australia/Sydney)'\n  },\n  { code: 'Asia/Kabul', name: 'Afghanistan Standard Time (Asia/Kabul)' },\n  {\n    code: 'America/Anchorage',\n    name: 'Alaskan Standard Time (America/Anchorage)'\n  },\n  { code: 'America/Juneau', name: 'Alaskan Standard Time (America/Juneau)' },\n  { code: 'America/Nome', name: 'Alaskan Standard Time (America/Nome)' },\n  { code: 'America/Sitka', name: 'Alaskan Standard Time (America/Sitka)' },\n  { code: 'America/Yakutat', name: 'Alaskan Standard Time (America/Yakutat)' },\n  { code: 'Asia/Aden', name: 'Arab Standard Time (Asia/Aden)' },\n  { code: 'Asia/Bahrain', name: 'Arab Standard Time (Asia/Bahrain)' },\n  { code: 'Asia/Kuwait', name: 'Arab Standard Time (Asia/Kuwait)' },\n  { code: 'Asia/Qatar', name: 'Arab Standard Time (Asia/Qatar)' },\n  { code: 'Asia/Riyadh', name: 'Arab Standard Time (Asia/Riyadh)' },\n  { code: 'Asia/Dubai', name: 'Arabian Standard Time (Asia/Dubai)' },\n  { code: 'Asia/Muscat', name: 'Arabian Standard Time (Asia/Muscat)' },\n  { code: 'Etc/GMT-4', name: 'Arabian Standard Time (Etc/GMT-4)' },\n  { code: 'Asia/Baghdad', name: 'Arabic Standard Time (Asia/Baghdad)' },\n  {\n    code: 'America/Argentina/La_Rioja',\n    name: 'Argentina Standard Time (America/Argentina/La_Rioja)'\n  },\n  {\n    code: 'America/Argentina/Rio_Gallegos',\n    name: 'Argentina Standard Time (America/Argentina/Rio_Gallegos)'\n  },\n  {\n    code: 'America/Argentina/Salta',\n    name: 'Argentina Standard Time (America/Argentina/Salta)'\n  },\n  {\n    code: 'America/Argentina/San_Juan',\n    name: 'Argentina Standard Time (America/Argentina/San_Juan)'\n  },\n  {\n    code: 'America/Argentina/San_Luis',\n    name: 'Argentina Standard Time (America/Argentina/San_Luis)'\n  },\n  {\n    code: 'America/Argentina/Tucuman',\n    name: 'Argentina Standard Time (America/Argentina/Tucuman)'\n  },\n  {\n    code: 'America/Argentina/Ushuaia',\n    name: 'Argentina Standard Time (America/Argentina/Ushuaia)'\n  },\n  {\n    code: 'America/Buenos_Aires',\n    name: 'Argentina Standard Time (America/Buenos_Aires)'\n  },\n  {\n    code: 'America/Catamarca',\n    name: 'Argentina Standard Time (America/Catamarca)'\n  },\n  {\n    code: 'America/Cordoba',\n    name: 'Argentina Standard Time (America/Cordoba)'\n  },\n  { code: 'America/Jujuy', name: 'Argentina Standard Time (America/Jujuy)' },\n  {\n    code: 'America/Mendoza',\n    name: 'Argentina Standard Time (America/Mendoza)'\n  },\n  {\n    code: 'America/Glace_Bay',\n    name: 'Atlantic Standard Time (America/Glace_Bay)'\n  },\n  {\n    code: 'America/Goose_Bay',\n    name: 'Atlantic Standard Time (America/Goose_Bay)'\n  },\n  { code: 'America/Halifax', name: 'Atlantic Standard Time (America/Halifax)' },\n  { code: 'America/Moncton', name: 'Atlantic Standard Time (America/Moncton)' },\n  { code: 'America/Thule', name: 'Atlantic Standard Time (America/Thule)' },\n  {\n    code: 'Atlantic/Bermuda',\n    name: 'Atlantic Standard Time (Atlantic/Bermuda)'\n  },\n  { code: 'Asia/Baku', name: 'Azerbaijan Standard Time (Asia/Baku)' },\n  {\n    code: 'America/Scoresbysund',\n    name: 'Azores Standard Time (America/Scoresbysund)'\n  },\n  { code: 'Atlantic/Azores', name: 'Azores Standard Time (Atlantic/Azores)' },\n  { code: 'America/Bahia', name: 'Bahia Standard Time (America/Bahia)' },\n  { code: 'Asia/Dhaka', name: 'Bangladesh Standard Time (Asia/Dhaka)' },\n  { code: 'Asia/Thimphu', name: 'Bangladesh Standard Time (Asia/Thimphu)' },\n  {\n    code: 'America/Regina',\n    name: 'Canada Central Standard Time (America/Regina)'\n  },\n  {\n    code: 'America/Swift_Current',\n    name: 'Canada Central Standard Time (America/Swift_Current)'\n  },\n  {\n    code: 'Atlantic/Cape_Verde',\n    name: 'Cape Verde Standard Time (Atlantic/Cape_Verde)'\n  },\n  { code: 'Etc/GMT+1', name: 'Cape Verde Standard Time (Etc/GMT+1)' },\n  { code: 'Asia/Yerevan', name: 'Caucasus Standard Time (Asia/Yerevan)' },\n  {\n    code: 'Australia/Adelaide',\n    name: 'Cen. Australia Standard Time (Australia/Adelaide)'\n  },\n  {\n    code: 'Australia/Broken_Hill',\n    name: 'Cen. Australia Standard Time (Australia/Broken_Hill)'\n  },\n  {\n    code: 'America/Belize',\n    name: 'Central America Standard Time (America/Belize)'\n  },\n  {\n    code: 'America/Costa_Rica',\n    name: 'Central America Standard Time (America/Costa_Rica)'\n  },\n  {\n    code: 'America/El_Salvador',\n    name: 'Central America Standard Time (America/El_Salvador)'\n  },\n  {\n    code: 'America/Guatemala',\n    name: 'Central America Standard Time (America/Guatemala)'\n  },\n  {\n    code: 'America/Managua',\n    name: 'Central America Standard Time (America/Managua)'\n  },\n  {\n    code: 'America/Tegucigalpa',\n    name: 'Central America Standard Time (America/Tegucigalpa)'\n  },\n  { code: 'Etc/GMT+6', name: 'Central America Standard Time (Etc/GMT+6)' },\n  {\n    code: 'Pacific/Galapagos',\n    name: 'Central America Standard Time (Pacific/Galapagos)'\n  },\n  {\n    code: 'Antarctica/Vostok',\n    name: 'Central Asia Standard Time (Antarctica/Vostok)'\n  },\n  { code: 'Asia/Almaty', name: 'Central Asia Standard Time (Asia/Almaty)' },\n  { code: 'Asia/Bishkek', name: 'Central Asia Standard Time (Asia/Bishkek)' },\n  {\n    code: 'Asia/Qyzylorda',\n    name: 'Central Asia Standard Time (Asia/Qyzylorda)'\n  },\n  { code: 'Etc/GMT-6', name: 'Central Asia Standard Time (Etc/GMT-6)' },\n  { code: 'Indian/Chagos', name: 'Central Asia Standard Time (Indian/Chagos)' },\n  {\n    code: 'America/Campo_Grande',\n    name: 'Central Brazilian Standard Time (America/Campo_Grande)'\n  },\n  {\n    code: 'America/Cuiaba',\n    name: 'Central Brazilian Standard Time (America/Cuiaba)'\n  },\n  {\n    code: 'Europe/Belgrade',\n    name: 'Central Europe Standard Time (Europe/Belgrade)'\n  },\n  {\n    code: 'Europe/Bratislava',\n    name: 'Central Europe Standard Time (Europe/Bratislava)'\n  },\n  {\n    code: 'Europe/Budapest',\n    name: 'Central Europe Standard Time (Europe/Budapest)'\n  },\n  {\n    code: 'Europe/Ljubljana',\n    name: 'Central Europe Standard Time (Europe/Ljubljana)'\n  },\n  {\n    code: 'Europe/Podgorica',\n    name: 'Central Europe Standard Time (Europe/Podgorica)'\n  },\n  {\n    code: 'Europe/Prague',\n    name: 'Central Europe Standard Time (Europe/Prague)'\n  },\n  {\n    code: 'Europe/Tirane',\n    name: 'Central Europe Standard Time (Europe/Tirane)'\n  },\n  {\n    code: 'Europe/Sarajevo',\n    name: 'Central European Standard Time (Europe/Sarajevo)'\n  },\n  {\n    code: 'Europe/Skopje',\n    name: 'Central European Standard Time (Europe/Skopje)'\n  },\n  {\n    code: 'Europe/Warsaw',\n    name: 'Central European Standard Time (Europe/Warsaw)'\n  },\n  {\n    code: 'Europe/Zagreb',\n    name: 'Central European Standard Time (Europe/Zagreb)'\n  },\n  {\n    code: 'Antarctica/Macquarie',\n    name: 'Central Pacific Standard Time (Antarctica/Macquarie)'\n  },\n  { code: 'Etc/GMT-11', name: 'Central Pacific Standard Time (Etc/GMT-11)' },\n  {\n    code: 'Pacific/Efate',\n    name: 'Central Pacific Standard Time (Pacific/Efate)'\n  },\n  {\n    code: 'Pacific/Guadalcanal',\n    name: 'Central Pacific Standard Time (Pacific/Guadalcanal)'\n  },\n  {\n    code: 'Pacific/Kosrae',\n    name: 'Central Pacific Standard Time (Pacific/Kosrae)'\n  },\n  {\n    code: 'Pacific/Noumea',\n    name: 'Central Pacific Standard Time (Pacific/Noumea)'\n  },\n  {\n    code: 'Pacific/Ponape',\n    name: 'Central Pacific Standard Time (Pacific/Ponape)'\n  },\n  { code: 'America/Chicago', name: 'Central Standard Time (America/Chicago)' },\n  {\n    code: 'America/Indiana/Knox',\n    name: 'Central Standard Time (America/Indiana/Knox)'\n  },\n  {\n    code: 'America/Indiana/Tell_City',\n    name: 'Central Standard Time (America/Indiana/Tell_City)'\n  },\n  {\n    code: 'America/Matamoros',\n    name: 'Central Standard Time (America/Matamoros)'\n  },\n  {\n    code: 'America/Menominee',\n    name: 'Central Standard Time (America/Menominee)'\n  },\n  {\n    code: 'America/North_Dakota/Beulah',\n    name: 'Central Standard Time (America/North_Dakota/Beulah)'\n  },\n  {\n    code: 'America/North_Dakota/Center',\n    name: 'Central Standard Time (America/North_Dakota/Center)'\n  },\n  {\n    code: 'America/North_Dakota/New_Salem',\n    name: 'Central Standard Time (America/North_Dakota/New_Salem)'\n  },\n  {\n    code: 'America/Rainy_River',\n    name: 'Central Standard Time (America/Rainy_River)'\n  },\n  {\n    code: 'America/Rankin_Inlet',\n    name: 'Central Standard Time (America/Rankin_Inlet)'\n  },\n  {\n    code: 'America/Resolute',\n    name: 'Central Standard Time (America/Resolute)'\n  },\n  {\n    code: 'America/Winnipeg',\n    name: 'Central Standard Time (America/Winnipeg)'\n  },\n  { code: 'CST6CDT', name: 'Central Standard Time (CST6CDT)' },\n  {\n    code: 'America/Bahia_Banderas',\n    name: 'Central Standard Time (Mexico) (America/Bahia_Banderas)'\n  },\n  {\n    code: 'America/Cancun',\n    name: 'Central Standard Time (Mexico) (America/Cancun)'\n  },\n  {\n    code: 'America/Merida',\n    name: 'Central Standard Time (Mexico) (America/Merida)'\n  },\n  {\n    code: 'America/Mexico_City',\n    name: 'Central Standard Time (Mexico) (America/Mexico_City)'\n  },\n  {\n    code: 'America/Monterrey',\n    name: 'Central Standard Time (Mexico) (America/Monterrey)'\n  },\n  { code: 'Asia/Chongqing', name: 'China Standard Time (Asia/Chongqing)' },\n  { code: 'Asia/Harbin', name: 'China Standard Time (Asia/Harbin)' },\n  { code: 'Asia/Hong_Kong', name: 'China Standard Time (Asia/Hong_Kong)' },\n  { code: 'Asia/Kashgar', name: 'China Standard Time (Asia/Kashgar)' },\n  { code: 'Asia/Macau', name: 'China Standard Time (Asia/Macau)' },\n  { code: 'Asia/Shanghai', name: 'China Standard Time (Asia/Shanghai)' },\n  { code: 'Asia/Urumqi', name: 'China Standard Time (Asia/Urumqi)' },\n  { code: 'Etc/GMT+12', name: 'Dateline Standard Time (Etc/GMT+12)' },\n  {\n    code: 'Africa/Addis_Ababa',\n    name: 'E. Africa Standard Time (Africa/Addis_Ababa)'\n  },\n  { code: 'Africa/Asmera', name: 'E. Africa Standard Time (Africa/Asmera)' },\n  {\n    code: 'Africa/Dar_es_Salaam',\n    name: 'E. Africa Standard Time (Africa/Dar_es_Salaam)'\n  },\n  {\n    code: 'Africa/Djibouti',\n    name: 'E. Africa Standard Time (Africa/Djibouti)'\n  },\n  { code: 'Africa/Juba', name: 'E. Africa Standard Time (Africa/Juba)' },\n  { code: 'Africa/Kampala', name: 'E. Africa Standard Time (Africa/Kampala)' },\n  {\n    code: 'Africa/Khartoum',\n    name: 'E. Africa Standard Time (Africa/Khartoum)'\n  },\n  {\n    code: 'Africa/Mogadishu',\n    name: 'E. Africa Standard Time (Africa/Mogadishu)'\n  },\n  { code: 'Africa/Nairobi', name: 'E. Africa Standard Time (Africa/Nairobi)' },\n  {\n    code: 'Antarctica/Syowa',\n    name: 'E. Africa Standard Time (Antarctica/Syowa)'\n  },\n  { code: 'Etc/GMT-3', name: 'E. Africa Standard Time (Etc/GMT-3)' },\n  {\n    code: 'Indian/Antananarivo',\n    name: 'E. Africa Standard Time (Indian/Antananarivo)'\n  },\n  { code: 'Indian/Comoro', name: 'E. Africa Standard Time (Indian/Comoro)' },\n  { code: 'Indian/Mayotte', name: 'E. Africa Standard Time (Indian/Mayotte)' },\n  {\n    code: 'Australia/Brisbane',\n    name: 'E. Australia Standard Time (Australia/Brisbane)'\n  },\n  {\n    code: 'Australia/Lindeman',\n    name: 'E. Australia Standard Time (Australia/Lindeman)'\n  },\n  {\n    code: 'America/Sao_Paulo',\n    name: 'E. South America Standard Time (America/Sao_Paulo)'\n  },\n  { code: 'America/Detroit', name: 'Eastern Standard Time (America/Detroit)' },\n  {\n    code: 'America/Grand_Turk',\n    name: 'Eastern Standard Time (America/Grand_Turk)'\n  },\n  { code: 'America/Havana', name: 'Eastern Standard Time (America/Havana)' },\n  {\n    code: 'America/Indiana/Petersburg',\n    name: 'Eastern Standard Time (America/Indiana/Petersburg)'\n  },\n  {\n    code: 'America/Indiana/Vincennes',\n    name: 'Eastern Standard Time (America/Indiana/Vincennes)'\n  },\n  {\n    code: 'America/Indiana/Winamac',\n    name: 'Eastern Standard Time (America/Indiana/Winamac)'\n  },\n  { code: 'America/Iqaluit', name: 'Eastern Standard Time (America/Iqaluit)' },\n  {\n    code: 'America/Kentucky/Monticello',\n    name: 'Eastern Standard Time (America/Kentucky/Monticello)'\n  },\n  {\n    code: 'America/Louisville',\n    name: 'Eastern Standard Time (America/Louisville)'\n  },\n  {\n    code: 'America/Montreal',\n    name: 'Eastern Standard Time (America/Montreal)'\n  },\n  { code: 'America/Nassau', name: 'Eastern Standard Time (America/Nassau)' },\n  {\n    code: 'America/New_York',\n    name: 'Eastern Standard Time (America/New_York)'\n  },\n  { code: 'America/Nipigon', name: 'Eastern Standard Time (America/Nipigon)' },\n  {\n    code: 'America/Pangnirtung',\n    name: 'Eastern Standard Time (America/Pangnirtung)'\n  },\n  {\n    code: 'America/Port-au-Prince',\n    name: 'Eastern Standard Time (America/Port-au-Prince)'\n  },\n  {\n    code: 'America/Thunder_Bay',\n    name: 'Eastern Standard Time (America/Thunder_Bay)'\n  },\n  { code: 'America/Toronto', name: 'Eastern Standard Time (America/Toronto)' },\n  { code: 'EST5EDT', name: 'Eastern Standard Time (EST5EDT)' },\n  { code: 'Africa/Cairo', name: 'Egypt Standard Time (Africa/Cairo)' },\n  {\n    code: 'Asia/Yekaterinburg',\n    name: 'Ekaterinburg Standard Time (Asia/Yekaterinburg)'\n  },\n  { code: 'Europe/Helsinki', name: 'FLE Standard Time (Europe/Helsinki)' },\n  { code: 'Europe/Kiev', name: 'FLE Standard Time (Europe/Kiev)' },\n  { code: 'Europe/Mariehamn', name: 'FLE Standard Time (Europe/Mariehamn)' },\n  { code: 'Europe/Riga', name: 'FLE Standard Time (Europe/Riga)' },\n  { code: 'Europe/Simferopol', name: 'FLE Standard Time (Europe/Simferopol)' },\n  { code: 'Europe/Sofia', name: 'FLE Standard Time (Europe/Sofia)' },\n  { code: 'Europe/Tallinn', name: 'FLE Standard Time (Europe/Tallinn)' },\n  { code: 'Europe/Uzhgorod', name: 'FLE Standard Time (Europe/Uzhgorod)' },\n  { code: 'Europe/Vilnius', name: 'FLE Standard Time (Europe/Vilnius)' },\n  { code: 'Europe/Zaporozhye', name: 'FLE Standard Time (Europe/Zaporozhye)' },\n  { code: 'Pacific/Fiji', name: 'Fiji Standard Time (Pacific/Fiji)' },\n  { code: 'Atlantic/Canary', name: 'GMT Standard Time (Atlantic/Canary)' },\n  { code: 'Atlantic/Faeroe', name: 'GMT Standard Time (Atlantic/Faeroe)' },\n  { code: 'Atlantic/Madeira', name: 'GMT Standard Time (Atlantic/Madeira)' },\n  { code: 'Europe/Dublin', name: 'GMT Standard Time (Europe/Dublin)' },\n  { code: 'Europe/Guernsey', name: 'GMT Standard Time (Europe/Guernsey)' },\n  {\n    code: 'Europe/Isle_of_Man',\n    name: 'GMT Standard Time (Europe/Isle_of_Man)'\n  },\n  { code: 'Europe/Jersey', name: 'GMT Standard Time (Europe/Jersey)' },\n  { code: 'Europe/Lisbon', name: 'GMT Standard Time (Europe/Lisbon)' },\n  { code: 'Europe/London', name: 'GMT Standard Time (Europe/London)' },\n  { code: 'Asia/Nicosia', name: 'GTB Standard Time (Asia/Nicosia)' },\n  { code: 'Europe/Athens', name: 'GTB Standard Time (Europe/Athens)' },\n  { code: 'Europe/Bucharest', name: 'GTB Standard Time (Europe/Bucharest)' },\n  { code: 'Europe/Chisinau', name: 'GTB Standard Time (Europe/Chisinau)' },\n  { code: 'Asia/Tbilisi', name: 'Georgian Standard Time (Asia/Tbilisi)' },\n  {\n    code: 'America/Godthab',\n    name: 'Greenland Standard Time (America/Godthab)'\n  },\n  { code: 'Africa/Abidjan', name: 'Greenwich Standard Time (Africa/Abidjan)' },\n  { code: 'Africa/Accra', name: 'Greenwich Standard Time (Africa/Accra)' },\n  { code: 'Africa/Bamako', name: 'Greenwich Standard Time (Africa/Bamako)' },\n  { code: 'Africa/Banjul', name: 'Greenwich Standard Time (Africa/Banjul)' },\n  { code: 'Africa/Bissau', name: 'Greenwich Standard Time (Africa/Bissau)' },\n  { code: 'Africa/Conakry', name: 'Greenwich Standard Time (Africa/Conakry)' },\n  { code: 'Africa/Dakar', name: 'Greenwich Standard Time (Africa/Dakar)' },\n  {\n    code: 'Africa/Freetown',\n    name: 'Greenwich Standard Time (Africa/Freetown)'\n  },\n  { code: 'Africa/Lome', name: 'Greenwich Standard Time (Africa/Lome)' },\n  {\n    code: 'Africa/Monrovia',\n    name: 'Greenwich Standard Time (Africa/Monrovia)'\n  },\n  {\n    code: 'Africa/Nouakchott',\n    name: 'Greenwich Standard Time (Africa/Nouakchott)'\n  },\n  {\n    code: 'Africa/Ouagadougou',\n    name: 'Greenwich Standard Time (Africa/Ouagadougou)'\n  },\n  {\n    code: 'Africa/Sao_Tome',\n    name: 'Greenwich Standard Time (Africa/Sao_Tome)'\n  },\n  {\n    code: 'Atlantic/Reykjavik',\n    name: 'Greenwich Standard Time (Atlantic/Reykjavik)'\n  },\n  {\n    code: 'Atlantic/St_Helena',\n    name: 'Greenwich Standard Time (Atlantic/St_Helena)'\n  },\n  { code: 'Etc/GMT+10', name: 'Hawaiian Standard Time (Etc/GMT+10)' },\n  {\n    code: 'Pacific/Honolulu',\n    name: 'Hawaiian Standard Time (Pacific/Honolulu)'\n  },\n  {\n    code: 'Pacific/Johnston',\n    name: 'Hawaiian Standard Time (Pacific/Johnston)'\n  },\n  {\n    code: 'Pacific/Rarotonga',\n    name: 'Hawaiian Standard Time (Pacific/Rarotonga)'\n  },\n  { code: 'Pacific/Tahiti', name: 'Hawaiian Standard Time (Pacific/Tahiti)' },\n  { code: 'Asia/Calcutta', name: 'India Standard Time (Asia/Calcutta)' },\n  { code: 'Asia/Tehran', name: 'Iran Standard Time (Asia/Tehran)' },\n  { code: 'Asia/Jerusalem', name: 'Israel Standard Time (Asia/Jerusalem)' },\n  { code: 'Asia/Amman', name: 'Jordan Standard Time (Asia/Amman)' },\n  {\n    code: 'Europe/Kaliningrad',\n    name: 'Kaliningrad Standard Time (Europe/Kaliningrad)'\n  },\n  { code: 'Europe/Minsk', name: 'Kaliningrad Standard Time (Europe/Minsk)' },\n  { code: 'Asia/Pyongyang', name: 'Korea Standard Time (Asia/Pyongyang)' },\n  { code: 'Asia/Seoul', name: 'Korea Standard Time (Asia/Seoul)' },\n  { code: 'Africa/Tripoli', name: 'Libya Standard Time (Africa/Tripoli)' },\n  { code: 'Asia/Anadyr', name: 'Magadan Standard Time (Asia/Anadyr)' },\n  { code: 'Asia/Kamchatka', name: 'Magadan Standard Time (Asia/Kamchatka)' },\n  { code: 'Asia/Magadan', name: 'Magadan Standard Time (Asia/Magadan)' },\n  { code: 'Indian/Mahe', name: 'Mauritius Standard Time (Indian/Mahe)' },\n  {\n    code: 'Indian/Mauritius',\n    name: 'Mauritius Standard Time (Indian/Mauritius)'\n  },\n  { code: 'Indian/Reunion', name: 'Mauritius Standard Time (Indian/Reunion)' },\n  { code: 'Asia/Beirut', name: 'Middle East Standard Time (Asia/Beirut)' },\n  {\n    code: 'America/Montevideo',\n    name: 'Montevideo Standard Time (America/Montevideo)'\n  },\n  {\n    code: 'Africa/Casablanca',\n    name: 'Morocco Standard Time (Africa/Casablanca)'\n  },\n  { code: 'Africa/El_Aaiun', name: 'Morocco Standard Time (Africa/El_Aaiun)' },\n  { code: 'America/Boise', name: 'Mountain Standard Time (America/Boise)' },\n  {\n    code: 'America/Cambridge_Bay',\n    name: 'Mountain Standard Time (America/Cambridge_Bay)'\n  },\n  { code: 'America/Denver', name: 'Mountain Standard Time (America/Denver)' },\n  {\n    code: 'America/Edmonton',\n    name: 'Mountain Standard Time (America/Edmonton)'\n  },\n  { code: 'America/Inuvik', name: 'Mountain Standard Time (America/Inuvik)' },\n  { code: 'America/Ojinaga', name: 'Mountain Standard Time (America/Ojinaga)' },\n  {\n    code: 'America/Shiprock',\n    name: 'Mountain Standard Time (America/Shiprock)'\n  },\n  {\n    code: 'America/Yellowknife',\n    name: 'Mountain Standard Time (America/Yellowknife)'\n  },\n  { code: 'MST7MDT', name: 'Mountain Standard Time (MST7MDT)' },\n  {\n    code: 'America/Chihuahua',\n    name: 'Mountain Standard Time (Mexico) (America/Chihuahua)'\n  },\n  {\n    code: 'America/Mazatlan',\n    name: 'Mountain Standard Time (Mexico) (America/Mazatlan)'\n  },\n  { code: 'Asia/Rangoon', name: 'Myanmar Standard Time (Asia/Rangoon)' },\n  { code: 'Indian/Cocos', name: 'Myanmar Standard Time (Indian/Cocos)' },\n  {\n    code: 'Asia/Novokuznetsk',\n    name: 'N. Central Asia Standard Time (Asia/Novokuznetsk)'\n  },\n  {\n    code: 'Asia/Novosibirsk',\n    name: 'N. Central Asia Standard Time (Asia/Novosibirsk)'\n  },\n  { code: 'Asia/Omsk', name: 'N. Central Asia Standard Time (Asia/Omsk)' },\n  { code: 'Africa/Windhoek', name: 'Namibia Standard Time (Africa/Windhoek)' },\n  { code: 'Asia/Katmandu', name: 'Nepal Standard Time (Asia/Katmandu)' },\n  {\n    code: 'Antarctica/McMurdo',\n    name: 'New Zealand Standard Time (Antarctica/McMurdo)'\n  },\n  {\n    code: 'Antarctica/South_Pole',\n    name: 'New Zealand Standard Time (Antarctica/South_Pole)'\n  },\n  {\n    code: 'Pacific/Auckland',\n    name: 'New Zealand Standard Time (Pacific/Auckland)'\n  },\n  {\n    code: 'America/St_Johns',\n    name: 'Newfoundland Standard Time (America/St_Johns)'\n  },\n  {\n    code: 'Asia/Irkutsk',\n    name: 'North Asia East Standard Time (Asia/Irkutsk)'\n  },\n  {\n    code: 'Asia/Krasnoyarsk',\n    name: 'North Asia Standard Time (Asia/Krasnoyarsk)'\n  },\n  {\n    code: 'America/Santiago',\n    name: 'Pacific SA Standard Time (America/Santiago)'\n  },\n  {\n    code: 'Antarctica/Palmer',\n    name: 'Pacific SA Standard Time (Antarctica/Palmer)'\n  },\n  { code: 'America/Dawson', name: 'Pacific Standard Time (America/Dawson)' },\n  {\n    code: 'America/Los_Angeles',\n    name: 'Pacific Standard Time (America/Los_Angeles)'\n  },\n  { code: 'America/Tijuana', name: 'Pacific Standard Time (America/Tijuana)' },\n  {\n    code: 'America/Vancouver',\n    name: 'Pacific Standard Time (America/Vancouver)'\n  },\n  {\n    code: 'America/Whitehorse',\n    name: 'Pacific Standard Time (America/Whitehorse)'\n  },\n  {\n    code: 'America/Santa_Isabel',\n    name: 'Pacific Standard Time (Mexico) (America/Santa_Isabel)'\n  },\n  { code: 'PST8PDT', name: 'Pacific Standard Time (PST8PDT)' },\n  { code: 'Asia/Karachi', name: 'Pakistan Standard Time (Asia/Karachi)' },\n  {\n    code: 'America/Asuncion',\n    name: 'Paraguay Standard Time (America/Asuncion)'\n  },\n  { code: 'Africa/Ceuta', name: 'Romance Standard Time (Africa/Ceuta)' },\n  { code: 'Europe/Brussels', name: 'Romance Standard Time (Europe/Brussels)' },\n  {\n    code: 'Europe/Copenhagen',\n    name: 'Romance Standard Time (Europe/Copenhagen)'\n  },\n  { code: 'Europe/Madrid', name: 'Romance Standard Time (Europe/Madrid)' },\n  { code: 'Europe/Paris', name: 'Romance Standard Time (Europe/Paris)' },\n  { code: 'Europe/Moscow', name: 'Russian Standard Time (Europe/Moscow)' },\n  { code: 'Europe/Samara', name: 'Russian Standard Time (Europe/Samara)' },\n  {\n    code: 'Europe/Volgograd',\n    name: 'Russian Standard Time (Europe/Volgograd)'\n  },\n  {\n    code: 'America/Araguaina',\n    name: 'SA Eastern Standard Time (America/Araguaina)'\n  },\n  { code: 'America/Belem', name: 'SA Eastern Standard Time (America/Belem)' },\n  {\n    code: 'America/Cayenne',\n    name: 'SA Eastern Standard Time (America/Cayenne)'\n  },\n  {\n    code: 'America/Fortaleza',\n    name: 'SA Eastern Standard Time (America/Fortaleza)'\n  },\n  { code: 'America/Maceio', name: 'SA Eastern Standard Time (America/Maceio)' },\n  {\n    code: 'America/Paramaribo',\n    name: 'SA Eastern Standard Time (America/Paramaribo)'\n  },\n  { code: 'America/Recife', name: 'SA Eastern Standard Time (America/Recife)' },\n  {\n    code: 'America/Santarem',\n    name: 'SA Eastern Standard Time (America/Santarem)'\n  },\n  {\n    code: 'Antarctica/Rothera',\n    name: 'SA Eastern Standard Time (Antarctica/Rothera)'\n  },\n  {\n    code: 'Atlantic/Stanley',\n    name: 'SA Eastern Standard Time (Atlantic/Stanley)'\n  },\n  { code: 'Etc/GMT+3', name: 'SA Eastern Standard Time (Etc/GMT+3)' },\n  { code: 'America/Bogota', name: 'SA Pacific Standard Time (America/Bogota)' },\n  { code: 'America/Cayman', name: 'SA Pacific Standard Time (America/Cayman)' },\n  {\n    code: 'America/Coral_Harbour',\n    name: 'SA Pacific Standard Time (America/Coral_Harbour)'\n  },\n  {\n    code: 'America/Eirunepe',\n    name: 'SA Pacific Standard Time (America/Eirunepe)'\n  },\n  {\n    code: 'America/Guayaquil',\n    name: 'SA Pacific Standard Time (America/Guayaquil)'\n  },\n  {\n    code: 'America/Jamaica',\n    name: 'SA Pacific Standard Time (America/Jamaica)'\n  },\n  { code: 'America/Lima', name: 'SA Pacific Standard Time (America/Lima)' },\n  { code: 'America/Panama', name: 'SA Pacific Standard Time (America/Panama)' },\n  {\n    code: 'America/Rio_Branco',\n    name: 'SA Pacific Standard Time (America/Rio_Branco)'\n  },\n  { code: 'Etc/GMT+5', name: 'SA Pacific Standard Time (Etc/GMT+5)' },\n  {\n    code: 'America/Anguilla',\n    name: 'SA Western Standard Time (America/Anguilla)'\n  },\n  {\n    code: 'America/Antigua',\n    name: 'SA Western Standard Time (America/Antigua)'\n  },\n  { code: 'America/Aruba', name: 'SA Western Standard Time (America/Aruba)' },\n  {\n    code: 'America/Barbados',\n    name: 'SA Western Standard Time (America/Barbados)'\n  },\n  {\n    code: 'America/Blanc-Sablon',\n    name: 'SA Western Standard Time (America/Blanc-Sablon)'\n  },\n  {\n    code: 'America/Boa_Vista',\n    name: 'SA Western Standard Time (America/Boa_Vista)'\n  },\n  {\n    code: 'America/Curacao',\n    name: 'SA Western Standard Time (America/Curacao)'\n  },\n  {\n    code: 'America/Dominica',\n    name: 'SA Western Standard Time (America/Dominica)'\n  },\n  {\n    code: 'America/Grenada',\n    name: 'SA Western Standard Time (America/Grenada)'\n  },\n  {\n    code: 'America/Guadeloupe',\n    name: 'SA Western Standard Time (America/Guadeloupe)'\n  },\n  { code: 'America/Guyana', name: 'SA Western Standard Time (America/Guyana)' },\n  {\n    code: 'America/Kralendijk',\n    name: 'SA Western Standard Time (America/Kralendijk)'\n  },\n  { code: 'America/La_Paz', name: 'SA Western Standard Time (America/La_Paz)' },\n  {\n    code: 'America/Lower_Princes',\n    name: 'SA Western Standard Time (America/Lower_Princes)'\n  },\n  { code: 'America/Manaus', name: 'SA Western Standard Time (America/Manaus)' },\n  {\n    code: 'America/Marigot',\n    name: 'SA Western Standard Time (America/Marigot)'\n  },\n  {\n    code: 'America/Martinique',\n    name: 'SA Western Standard Time (America/Martinique)'\n  },\n  {\n    code: 'America/Montserrat',\n    name: 'SA Western Standard Time (America/Montserrat)'\n  },\n  {\n    code: 'America/Port_of_Spain',\n    name: 'SA Western Standard Time (America/Port_of_Spain)'\n  },\n  {\n    code: 'America/Porto_Velho',\n    name: 'SA Western Standard Time (America/Porto_Velho)'\n  },\n  {\n    code: 'America/Puerto_Rico',\n    name: 'SA Western Standard Time (America/Puerto_Rico)'\n  },\n  {\n    code: 'America/Santo_Domingo',\n    name: 'SA Western Standard Time (America/Santo_Domingo)'\n  },\n  {\n    code: 'America/St_Barthelemy',\n    name: 'SA Western Standard Time (America/St_Barthelemy)'\n  },\n  {\n    code: 'America/St_Kitts',\n    name: 'SA Western Standard Time (America/St_Kitts)'\n  },\n  {\n    code: 'America/St_Lucia',\n    name: 'SA Western Standard Time (America/St_Lucia)'\n  },\n  {\n    code: 'America/St_Thomas',\n    name: 'SA Western Standard Time (America/St_Thomas)'\n  },\n  {\n    code: 'America/St_Vincent',\n    name: 'SA Western Standard Time (America/St_Vincent)'\n  },\n  {\n    code: 'America/Tortola',\n    name: 'SA Western Standard Time (America/Tortola)'\n  },\n  { code: 'Etc/GMT+4', name: 'SA Western Standard Time (Etc/GMT+4)' },\n  {\n    code: 'Antarctica/Davis',\n    name: 'SE Asia Standard Time (Antarctica/Davis)'\n  },\n  { code: 'Asia/Bangkok', name: 'SE Asia Standard Time (Asia/Bangkok)' },\n  { code: 'Asia/Hovd', name: 'SE Asia Standard Time (Asia/Hovd)' },\n  { code: 'Asia/Jakarta', name: 'SE Asia Standard Time (Asia/Jakarta)' },\n  { code: 'Asia/Phnom_Penh', name: 'SE Asia Standard Time (Asia/Phnom_Penh)' },\n  { code: 'Asia/Pontianak', name: 'SE Asia Standard Time (Asia/Pontianak)' },\n  { code: 'Asia/Saigon', name: 'SE Asia Standard Time (Asia/Saigon)' },\n  { code: 'Asia/Vientiane', name: 'SE Asia Standard Time (Asia/Vientiane)' },\n  { code: 'Etc/GMT-7', name: 'SE Asia Standard Time (Etc/GMT-7)' },\n  {\n    code: 'Indian/Christmas',\n    name: 'SE Asia Standard Time (Indian/Christmas)'\n  },\n  { code: 'Pacific/Apia', name: 'Samoa Standard Time (Pacific/Apia)' },\n  { code: 'Asia/Brunei', name: 'Singapore Standard Time (Asia/Brunei)' },\n  {\n    code: 'Asia/Kuala_Lumpur',\n    name: 'Singapore Standard Time (Asia/Kuala_Lumpur)'\n  },\n  { code: 'Asia/Kuching', name: 'Singapore Standard Time (Asia/Kuching)' },\n  { code: 'Asia/Makassar', name: 'Singapore Standard Time (Asia/Makassar)' },\n  { code: 'Asia/Manila', name: 'Singapore Standard Time (Asia/Manila)' },\n  { code: 'Asia/Singapore', name: 'Singapore Standard Time (Asia/Singapore)' },\n  { code: 'Etc/GMT-8', name: 'Singapore Standard Time (Etc/GMT-8)' },\n  {\n    code: 'Africa/Blantyre',\n    name: 'South Africa Standard Time (Africa/Blantyre)'\n  },\n  {\n    code: 'Africa/Bujumbura',\n    name: 'South Africa Standard Time (Africa/Bujumbura)'\n  },\n  {\n    code: 'Africa/Gaborone',\n    name: 'South Africa Standard Time (Africa/Gaborone)'\n  },\n  { code: 'Africa/Harare', name: 'South Africa Standard Time (Africa/Harare)' },\n  {\n    code: 'Africa/Johannesburg',\n    name: 'South Africa Standard Time (Africa/Johannesburg)'\n  },\n  { code: 'Africa/Kigali', name: 'South Africa Standard Time (Africa/Kigali)' },\n  {\n    code: 'Africa/Lubumbashi',\n    name: 'South Africa Standard Time (Africa/Lubumbashi)'\n  },\n  { code: 'Africa/Lusaka', name: 'South Africa Standard Time (Africa/Lusaka)' },\n  { code: 'Africa/Maputo', name: 'South Africa Standard Time (Africa/Maputo)' },\n  { code: 'Africa/Maseru', name: 'South Africa Standard Time (Africa/Maseru)' },\n  {\n    code: 'Africa/Mbabane',\n    name: 'South Africa Standard Time (Africa/Mbabane)'\n  },\n  { code: 'Etc/GMT-2', name: 'South Africa Standard Time (Etc/GMT-2)' },\n  { code: 'Asia/Colombo', name: 'Sri Lanka Standard Time (Asia/Colombo)' },\n  { code: 'Asia/Damascus', name: 'Syria Standard Time (Asia/Damascus)' },\n  { code: 'Asia/Taipei', name: 'Taipei Standard Time (Asia/Taipei)' },\n  {\n    code: 'Australia/Currie',\n    name: 'Tasmania Standard Time (Australia/Currie)'\n  },\n  {\n    code: 'Australia/Hobart',\n    name: 'Tasmania Standard Time (Australia/Hobart)'\n  },\n  { code: 'Asia/Dili', name: 'Tokyo Standard Time (Asia/Dili)' },\n  { code: 'Asia/Jayapura', name: 'Tokyo Standard Time (Asia/Jayapura)' },\n  { code: 'Asia/Tokyo', name: 'Tokyo Standard Time (Asia/Tokyo)' },\n  { code: 'Etc/GMT-9', name: 'Tokyo Standard Time (Etc/GMT-9)' },\n  { code: 'Pacific/Palau', name: 'Tokyo Standard Time (Pacific/Palau)' },\n  { code: 'Etc/GMT-13', name: 'Tonga Standard Time (Etc/GMT-13)' },\n  {\n    code: 'Pacific/Enderbury',\n    name: 'Tonga Standard Time (Pacific/Enderbury)'\n  },\n  { code: 'Pacific/Fakaofo', name: 'Tonga Standard Time (Pacific/Fakaofo)' },\n  {\n    code: 'Pacific/Tongatapu',\n    name: 'Tonga Standard Time (Pacific/Tongatapu)'\n  },\n  { code: 'Europe/Istanbul', name: 'Turkey Standard Time (Europe/Istanbul)' },\n  {\n    code: 'America/Indiana/Marengo',\n    name: 'US Eastern Standard Time (America/Indiana/Marengo)'\n  },\n  {\n    code: 'America/Indiana/Vevay',\n    name: 'US Eastern Standard Time (America/Indiana/Vevay)'\n  },\n  {\n    code: 'America/Indianapolis',\n    name: 'US Eastern Standard Time (America/Indianapolis)'\n  },\n  {\n    code: 'America/Creston',\n    name: 'US Mountain Standard Time (America/Creston)'\n  },\n  {\n    code: 'America/Dawson_Creek',\n    name: 'US Mountain Standard Time (America/Dawson_Creek)'\n  },\n  {\n    code: 'America/Hermosillo',\n    name: 'US Mountain Standard Time (America/Hermosillo)'\n  },\n  {\n    code: 'America/Phoenix',\n    name: 'US Mountain Standard Time (America/Phoenix)'\n  },\n  { code: 'Etc/GMT+7', name: 'US Mountain Standard Time (Etc/GMT+7)' },\n  { code: 'America/Danmarkshavn', name: 'UTC (America/Danmarkshavn)' },\n  { code: 'Etc/GMT', name: 'UTC (Etc/GMT)' },\n  { code: 'Etc/GMT-12', name: 'UTC+12 (Etc/GMT-12)' },\n  { code: 'Pacific/Funafuti', name: 'UTC+12 (Pacific/Funafuti)' },\n  { code: 'Pacific/Kwajalein', name: 'UTC+12 (Pacific/Kwajalein)' },\n  { code: 'Pacific/Majuro', name: 'UTC+12 (Pacific/Majuro)' },\n  { code: 'Pacific/Nauru', name: 'UTC+12 (Pacific/Nauru)' },\n  { code: 'Pacific/Tarawa', name: 'UTC+12 (Pacific/Tarawa)' },\n  { code: 'Pacific/Wake', name: 'UTC+12 (Pacific/Wake)' },\n  { code: 'Pacific/Wallis', name: 'UTC+12 (Pacific/Wallis)' },\n  { code: 'America/Noronha', name: 'UTC-02 (America/Noronha)' },\n  { code: 'Atlantic/South_Georgia', name: 'UTC-02 (Atlantic/South_Georgia)' },\n  { code: 'Etc/GMT+2', name: 'UTC-02 (Etc/GMT+2)' },\n  { code: 'Etc/GMT+11', name: 'UTC-11 (Etc/GMT+11)' },\n  { code: 'Pacific/Midway', name: 'UTC-11 (Pacific/Midway)' },\n  { code: 'Pacific/Niue', name: 'UTC-11 (Pacific/Niue)' },\n  { code: 'Pacific/Pago_Pago', name: 'UTC-11 (Pacific/Pago_Pago)' },\n  {\n    code: 'Asia/Choibalsan',\n    name: 'Ulaanbaatar Standard Time (Asia/Choibalsan)'\n  },\n  {\n    code: 'Asia/Ulaanbaatar',\n    name: 'Ulaanbaatar Standard Time (Asia/Ulaanbaatar)'\n  },\n  {\n    code: 'America/Caracas',\n    name: 'Venezuela Standard Time (America/Caracas)'\n  },\n  { code: 'Asia/Sakhalin', name: 'Vladivostok Standard Time (Asia/Sakhalin)' },\n  { code: 'Asia/Ust-Nera', name: 'Vladivostok Standard Time (Asia/Ust-Nera)' },\n  {\n    code: 'Asia/Vladivostok',\n    name: 'Vladivostok Standard Time (Asia/Vladivostok)'\n  },\n  {\n    code: 'Antarctica/Casey',\n    name: 'W. Australia Standard Time (Antarctica/Casey)'\n  },\n  {\n    code: 'Australia/Perth',\n    name: 'W. Australia Standard Time (Australia/Perth)'\n  },\n  {\n    code: 'Africa/Algiers',\n    name: 'W. Central Africa Standard Time (Africa/Algiers)'\n  },\n  {\n    code: 'Africa/Bangui',\n    name: 'W. Central Africa Standard Time (Africa/Bangui)'\n  },\n  {\n    code: 'Africa/Brazzaville',\n    name: 'W. Central Africa Standard Time (Africa/Brazzaville)'\n  },\n  {\n    code: 'Africa/Douala',\n    name: 'W. Central Africa Standard Time (Africa/Douala)'\n  },\n  {\n    code: 'Africa/Kinshasa',\n    name: 'W. Central Africa Standard Time (Africa/Kinshasa)'\n  },\n  {\n    code: 'Africa/Lagos',\n    name: 'W. Central Africa Standard Time (Africa/Lagos)'\n  },\n  {\n    code: 'Africa/Libreville',\n    name: 'W. Central Africa Standard Time (Africa/Libreville)'\n  },\n  {\n    code: 'Africa/Luanda',\n    name: 'W. Central Africa Standard Time (Africa/Luanda)'\n  },\n  {\n    code: 'Africa/Malabo',\n    name: 'W. Central Africa Standard Time (Africa/Malabo)'\n  },\n  {\n    code: 'Africa/Ndjamena',\n    name: 'W. Central Africa Standard Time (Africa/Ndjamena)'\n  },\n  {\n    code: 'Africa/Niamey',\n    name: 'W. Central Africa Standard Time (Africa/Niamey)'\n  },\n  {\n    code: 'Africa/Porto-Novo',\n    name: 'W. Central Africa Standard Time (Africa/Porto-Novo)'\n  },\n  {\n    code: 'Africa/Tunis',\n    name: 'W. Central Africa Standard Time (Africa/Tunis)'\n  },\n  { code: 'Etc/GMT-1', name: 'W. Central Africa Standard Time (Etc/GMT-1)' },\n  {\n    code: 'Arctic/Longyearbyen',\n    name: 'W. Europe Standard Time (Arctic/Longyearbyen)'\n  },\n  {\n    code: 'Europe/Amsterdam',\n    name: 'W. Europe Standard Time (Europe/Amsterdam)'\n  },\n  { code: 'Europe/Andorra', name: 'W. Europe Standard Time (Europe/Andorra)' },\n  { code: 'Europe/Berlin', name: 'W. Europe Standard Time (Europe/Berlin)' },\n  {\n    code: 'Europe/Busingen',\n    name: 'W. Europe Standard Time (Europe/Busingen)'\n  },\n  {\n    code: 'Europe/Gibraltar',\n    name: 'W. Europe Standard Time (Europe/Gibraltar)'\n  },\n  {\n    code: 'Europe/Luxembourg',\n    name: 'W. Europe Standard Time (Europe/Luxembourg)'\n  },\n  { code: 'Europe/Malta', name: 'W. Europe Standard Time (Europe/Malta)' },\n  { code: 'Europe/Monaco', name: 'W. Europe Standard Time (Europe/Monaco)' },\n  { code: 'Europe/Oslo', name: 'W. Europe Standard Time (Europe/Oslo)' },\n  { code: 'Europe/Rome', name: 'W. Europe Standard Time (Europe/Rome)' },\n  {\n    code: 'Europe/San_Marino',\n    name: 'W. Europe Standard Time (Europe/San_Marino)'\n  },\n  {\n    code: 'Europe/Stockholm',\n    name: 'W. Europe Standard Time (Europe/Stockholm)'\n  },\n  { code: 'Europe/Vaduz', name: 'W. Europe Standard Time (Europe/Vaduz)' },\n  { code: 'Europe/Vatican', name: 'W. Europe Standard Time (Europe/Vatican)' },\n  { code: 'Europe/Vienna', name: 'W. Europe Standard Time (Europe/Vienna)' },\n  { code: 'Europe/Zurich', name: 'W. Europe Standard Time (Europe/Zurich)' },\n  {\n    code: 'Antarctica/Mawson',\n    name: 'West Asia Standard Time (Antarctica/Mawson)'\n  },\n  { code: 'Asia/Aqtau', name: 'West Asia Standard Time (Asia/Aqtau)' },\n  { code: 'Asia/Aqtobe', name: 'West Asia Standard Time (Asia/Aqtobe)' },\n  { code: 'Asia/Ashgabat', name: 'West Asia Standard Time (Asia/Ashgabat)' },\n  { code: 'Asia/Dushanbe', name: 'West Asia Standard Time (Asia/Dushanbe)' },\n  { code: 'Asia/Oral', name: 'West Asia Standard Time (Asia/Oral)' },\n  { code: 'Asia/Samarkand', name: 'West Asia Standard Time (Asia/Samarkand)' },\n  { code: 'Asia/Tashkent', name: 'West Asia Standard Time (Asia/Tashkent)' },\n  { code: 'Etc/GMT-5', name: 'West Asia Standard Time (Etc/GMT-5)' },\n  {\n    code: 'Indian/Kerguelen',\n    name: 'West Asia Standard Time (Indian/Kerguelen)'\n  },\n  {\n    code: 'Indian/Maldives',\n    name: 'West Asia Standard Time (Indian/Maldives)'\n  },\n  {\n    code: 'Antarctica/DumontDUrville',\n    name: 'West Pacific Standard Time (Antarctica/DumontDUrville)'\n  },\n  { code: 'Etc/GMT-10', name: 'West Pacific Standard Time (Etc/GMT-10)' },\n  { code: 'Pacific/Guam', name: 'West Pacific Standard Time (Pacific/Guam)' },\n  {\n    code: 'Pacific/Port_Moresby',\n    name: 'West Pacific Standard Time (Pacific/Port_Moresby)'\n  },\n  {\n    code: 'Pacific/Saipan',\n    name: 'West Pacific Standard Time (Pacific/Saipan)'\n  },\n  { code: 'Pacific/Truk', name: 'West Pacific Standard Time (Pacific/Truk)' },\n  { code: 'Asia/Khandyga', name: 'Yakutsk Standard Time (Asia/Khandyga)' },\n  { code: 'Asia/Yakutsk', name: 'Yakutsk Standard Time (Asia/Yakutsk)' }\n];\n"
  },
  {
    "path": "packages/evershop/src/lib/locale/translate/_.ts",
    "content": "export function _(text: string, values?: Record<string, string>): string {\n  // Check if the data is null, undefined or empty object\n  if (!values || Object.keys(values).length === 0) {\n    return text;\n  }\n  const template = `${text}`;\n  return template.replace(/\\${(.*?)}/g, (match, key) =>\n    values[key.trim()] !== undefined ? values[key.trim()] : match\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/locale/translate/translate.ts",
    "content": "import { loadCsvTranslationFiles } from '../../webpack/loaders/loadTranslationFromCsv.js';\n\nlet csvData: Record<string, string> | undefined;\n\n/**\n * This function is used to translate the text form server side, like from middleware. For templating use the _ function\n */\nexport function translate(enText: string, values: Record<string, string> = {}) {\n  const translatedText =\n    csvData && csvData[enText] !== undefined ? csvData[enText] : enText;\n  // Check if the data is null, undefined or empty object\n  if (!values || Object.keys(values).length === 0) {\n    return translatedText;\n  } else {\n    const template = `${translatedText}`;\n    return template.replace(/\\${(.*?)}/g, (match, key) =>\n      values[key.trim()] !== undefined ? values[key.trim()] : match\n    );\n  }\n}\n\nexport async function loadCsv(): Promise<Record<string, string>> {\n  // Only load the csv files once\n  if (csvData === undefined) {\n    csvData = await loadCsvTranslationFiles();\n  }\n  return csvData;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/log/CustomColorize.js",
    "content": "// Extend the default winston colorize format and add color to the stack trace\nimport { LEVEL } from 'triple-beam';\nimport { format } from 'winston';\n\nconst { colorize } = format;\n\nclass CustomColorize extends colorize.Colorizer {\n  constructor(opts = {}) {\n    super(opts);\n  }\n\n  transform(info, opts) {\n    super.transform(info, opts);\n    if (info.stack) {\n      info.stack = this.colorize(info[LEVEL], info.level, info.stack);\n    }\n    return info;\n  }\n}\n\nexport default (opts) => new CustomColorize(opts);\n"
  },
  {
    "path": "packages/evershop/src/lib/log/logger.js",
    "content": "import winston from 'winston';\nimport { getEnv } from '../util/getEnv.js';\nimport isDevelopmentMode from '../util/isDevelopmentMode.js';\nimport { addProcessor, getValueSync } from '../util/registry.js';\nimport CustomColorize from './CustomColorize.js';\n\nfunction createLogger() {\n  return getValueSync('logger', null, {\n    isDebugging: isDevelopmentMode() || process.argv.includes('--debug')\n  });\n}\n\n// Define logger function\nexport function debug(message) {\n  const logger = createLogger();\n  logger.debug(message);\n}\n\nexport function error(e) {\n  const logger = createLogger();\n  logger.error(e);\n}\n\nexport function warning(message) {\n  const logger = createLogger();\n  logger.warn(message);\n}\n\nexport function info(message) {\n  const logger = createLogger();\n  logger.info(message);\n}\n\nexport function success(message) {\n  const logger = createLogger();\n  logger.info(message);\n}\n\naddProcessor(\n  'logger',\n  () => {\n    const config = getValueSync(\n      'logger_configuration',\n      () => {\n        const { errors } = winston.format;\n        const isDebugging =\n          isDevelopmentMode() || process.argv.includes('--debug');\n        const format = winston.format.combine(\n          errors({ stack: true }),\n          CustomColorize({\n            colors: {\n              error: 'red',\n              warn: 'yellow',\n              info: 'green',\n              http: 'blue',\n              verbose: 'cyan',\n              debug: 'magenta',\n              silly: 'gray'\n            },\n            level: false,\n            message: true\n          }),\n          winston.format.printf(({ level, message, stack }) => {\n            let icon;\n            switch (level) {\n              case 'error':\n                icon = '❌'; // Error icon\n                break;\n              case 'warn':\n                icon = '⚠️ '; // Warning icon\n                break;\n              case 'info':\n                icon = 'ℹ️'; // Info icon\n                break;\n              case 'http':\n                icon = '🌐'; // HTTP icon\n                break;\n              case 'verbose':\n                icon = '🔍'; // Verbose icon\n                break;\n              case 'debug':\n                icon = '🐛'; // Debug icon\n                break;\n              case 'silly':\n                icon = '🤪'; // Silly icon\n                break;\n              default:\n                icon = '';\n                break;\n            }\n            // Now apply color to the icon and level\n            switch (level) {\n              case 'error':\n                level = `\\x1b[31m${level}\\x1b[0m`; // Red color\n                icon = `\\x1b[31m${icon}\\x1b[0m`; // Red color\n                break;\n              case 'warn':\n                level = `\\x1b[33m${level}\\x1b[0m`; // Yellow color\n                icon = `\\x1b[33m${icon}\\x1b[0m`; // Yellow color\n                break;\n              case 'info':\n                level = `\\x1b[32m${level}\\x1b[0m`; // Green color\n                icon = `\\x1b[32m${icon}\\x1b[0m`; // Green color\n                break;\n              case 'http':\n                level = `\\x1b[34m${level}\\x1b[0m`; // Blue color\n                icon = `\\x1b[34m${icon}\\x1b[0m`; // Blue color\n                break;\n              case 'verbose':\n                level = `\\x1b[36m${level}\\x1b[0m`; // Cyan color\n                icon = `\\x1b[36m${icon}\\x1b[0m`; // Cyan color\n                break;\n              case 'debug':\n                level = `\\x1b[35m${level}\\x1b[0m`; // Magenta color\n                icon = `\\x1b[35m${icon}\\x1b[0m`; // Magenta color\n                break;\n              case 'silly':\n                level = `\\x1b[37m${level}\\x1b[0m`; // Gray color\n                icon = `\\x1b[37m${icon}\\x1b[0m`; // Gray color\n                break;\n              default:\n                break;\n            }\n            if (stack) {\n              message = `${message}\\n${stack}`;\n            }\n            return `${icon} ${level}: \\n${message}`;\n          })\n        );\n        const consoleTransport = new winston.transports.Console();\n        const logFile = getEnv('LOG_FILE', undefined);\n        // Default transports\n        const DEFAULT_CONFIG = {\n          level: isDebugging ? 'silly' : getEnv('LOGGER_LEVEL', 'warn'),\n          format,\n          // By default, log to console\n          transports:\n            isDebugging || !logFile\n              ? [consoleTransport]\n              : [new winston.transports.File({ filename: logFile })],\n          exceptionHandlers:\n            isDebugging || !logFile\n              ? [consoleTransport]\n              : [new winston.transports.File({ filename: logFile })]\n        };\n        return DEFAULT_CONFIG;\n      },\n      {\n        isDebugging: isDevelopmentMode() || process.argv.includes('--debug')\n      }\n    );\n    return winston.createLogger(config);\n  },\n  0\n);\n"
  },
  {
    "path": "packages/evershop/src/lib/mail/emailHelper.ts",
    "content": "import Handlebars from 'handlebars';\nimport { getSetting } from '../../modules/setting/services/setting.js';\nimport { countries } from '../locale/countries.js';\nimport { provinces } from '../locale/provinces.js';\nimport { getBaseUrl } from '../util/getBaseUrl.js';\nimport { getConfig } from '../util/getConfig.js';\nimport { addProcessor, getValue, getValueSync } from '../util/registry.js';\n\nHandlebars.registerHelper('currency', function (value) {\n  if (value == null) return '';\n\n  const number = Number(value);\n\n  return new Intl.NumberFormat('en-US', {\n    style: 'currency',\n    currency: getConfig('shop.currency', 'USD'),\n    minimumFractionDigits: 0,\n    maximumFractionDigits: 2\n  }).format(number);\n});\n\nHandlebars.registerHelper('date', function (value, format = 'MMM DD, YYYY') {\n  if (!value) return '';\n\n  let date;\n\n  // handle seconds vs milliseconds\n  if (typeof value === 'number' || /^\\d+$/.test(value)) {\n    const ts = Number(value);\n    date = new Date(ts < 1e12 ? ts * 1000 : ts);\n  } else {\n    date = new Date(value);\n  }\n\n  if (isNaN(date.getTime())) return '';\n\n  return new Intl.DateTimeFormat(getConfig('shop.language', 'en'), {\n    year: 'numeric',\n    month: 'short',\n    day: '2-digit'\n  }).format(date);\n});\n\nexport type SendEmailArguments = {\n  from?: string;\n  to: string;\n  subject: string;\n  body?: string;\n  template: string;\n  data: EmailData;\n  [key: string]: unknown;\n};\n\n/**\n * Validates email arguments to ensure they meet the required format.\n * @param args - The arguments to validate\n * @throws Will throw an error if validation fails\n */\nexport function validateSendEmailArguments(\n  args: unknown\n): asserts args is SendEmailArguments {\n  // Validate args is an object\n  if (typeof args !== 'object' || args === null) {\n    throw new Error('Email arguments must be an object.');\n  }\n\n  const typedArgs = args as Record<string, unknown>;\n\n  // Validate required fields exist and are non-empty strings\n  if (typeof typedArgs.to !== 'string' || typedArgs.to.trim() === '') {\n    throw new Error('\"to\" field must be a non-empty string.');\n  }\n\n  if (\n    typeof typedArgs.subject !== 'string' ||\n    typedArgs.subject.trim() === ''\n  ) {\n    throw new Error('\"subject\" field must be a non-empty string.');\n  }\n\n  if (\n    typeof typedArgs.template !== 'string' ||\n    typedArgs.template.trim() === ''\n  ) {\n    throw new Error('\"template\" field must be a non-empty string.');\n  }\n  // Body is optional, but it must be a string if provided\n  if (\n    typedArgs.body !== undefined &&\n    (typeof typedArgs.body !== 'string' || typedArgs.body.trim() === '')\n  ) {\n    throw new Error('\"body\" field must be a non-empty string if provided.');\n  }\n\n  // Validate optional fields if present\n  if (\n    typedArgs.template !== undefined &&\n    typeof typedArgs.template !== 'string'\n  ) {\n    throw new Error('\"template\" field must be a string if provided.');\n  }\n\n  if (typedArgs.cc !== undefined && !Array.isArray(typedArgs.cc)) {\n    throw new Error('\"cc\" field must be an array if provided.');\n  }\n\n  // Validate cc array contains only strings\n  if (Array.isArray(typedArgs.cc)) {\n    if (!typedArgs.cc.every((email) => typeof email === 'string')) {\n      throw new Error('\"cc\" array must contain only strings.');\n    }\n  }\n}\n\nexport interface EmailService {\n  sendEmail: (args: SendEmailArguments) => Promise<void>;\n}\n\n/**\n * Validates if the given service implements the EmailService interface.\n * @param service - The service to validate\n * @returns True if valid, false otherwise\n */\nfunction isValidEmailService(service: unknown): service is EmailService {\n  return (\n    typeof service === 'object' &&\n    service !== null &&\n    'sendEmail' in service &&\n    typeof (service as EmailService).sendEmail === 'function'\n  );\n}\n\n/**\n * Retrieves the registered email service from the registry.\n * @returns The email service object.\n */\nexport function getEmailService(): EmailService | undefined {\n  const emailService = getValueSync<EmailService | undefined>(\n    'emailService',\n    undefined,\n    {},\n    isValidEmailService\n  );\n  return emailService;\n}\n\n/** Registers a new email service.\n * @param service - The email service to register.\n * @throws Will throw an error if the service does not implement the EmailService interface.\n */\nexport function registerEmailService(service: EmailService): void {\n  if (!isValidEmailService(service)) {\n    throw new Error(\n      'Invalid email service. It must be an object with a sendEmail method.'\n    );\n  }\n  addProcessor('emailService', () => {\n    return service;\n  });\n}\n\n/**\n * Sends an email using the registered email service.\n * @param id - The identifier for the email type, e.g., 'order_confirmation'\n * @param args - The email arguments\n * @returns A promise that resolves when the email is sent.\n */\nexport async function sendEmail(\n  id: string,\n  args: SendEmailArguments\n): Promise<void> {\n  const emailService = getEmailService();\n  if (!emailService) {\n    return Promise.reject(\n      new Error('No email service registered to send emails.')\n    );\n  }\n  const finalArgs = await getValue('emailArguments', args, { id });\n  if (!finalArgs?.from) {\n    finalArgs.from = getConfig('system.notification_emails.from', undefined);\n  }\n  validateSendEmailArguments(finalArgs);\n  if (!finalArgs.body) {\n    const body = await buildEmailBodyFromTemplate(\n      finalArgs.template,\n      finalArgs.data || {}\n    );\n    finalArgs.body = body;\n  }\n  return await emailService.sendEmail(finalArgs);\n}\n\nexport interface EmailData {\n  storeInfo?: {\n    logo?: {\n      src?: string;\n      alt?: string;\n      height?: string;\n      width?: string;\n    };\n    storeName: string;\n    storeEmail: string;\n    storeDescription: string;\n    phone: string;\n    homeUrl: string;\n    address: {\n      country?: string;\n      province?: string;\n      city?: string;\n      street?: string;\n      postalCode?: string;\n    };\n  };\n  [key: string]: unknown;\n}\n/**\n * Builds email body from a template by replacing placeholders with actual data.\n * @param template - The email template string with placeholders in {{key}} format.\n * @param data - An object containing key-value pairs to replace in the template.\n * @returns The final email body string with placeholders replaced by actual data.\n */\nexport async function buildEmailBodyFromTemplate(\n  template: string,\n  data: EmailData\n): Promise<string> {\n  try {\n    const preparedData = await prepareData(data);\n    const body = Handlebars.compile(template)(preparedData);\n    return body;\n  } catch (error) {\n    throw new Error(`Failed to build email body from template: ${error}`);\n  }\n}\n\n/** Prepares email data by adding store information and processing through registry.\n * @param data - The initial email data.\n * @returns The prepared email data with store information.\n */\nasync function prepareData(data: EmailData): Promise<EmailData> {\n  const logoConfig = getConfig('themeConfig.logo');\n  let logo;\n  if (logoConfig) {\n    const url = logoConfig.src || '';\n    // check if url is absolute\n    if (url && !/^https?:\\/\\//i.test(url)) {\n      logo = {\n        src: `${getBaseUrl()}${url}`,\n        alt: logoConfig?.alt || '',\n        height: logoConfig?.height ? String(logoConfig.height) : undefined,\n        width: logoConfig?.width ? String(logoConfig.width) : undefined\n      };\n    } else {\n      logo = {\n        src: url,\n        alt: logoConfig?.alt || '',\n        height: logoConfig?.height ? String(logoConfig.height) : undefined,\n        width: logoConfig?.width ? String(logoConfig.width) : undefined\n      };\n    }\n  }\n  const addressCountry = await getSetting('storeCountry', 'US');\n  const addressProvince = await getSetting('storeProvince', '');\n  const addressCity = await getSetting('storeCity', '');\n  const addressStreet = await getSetting('storeAddress', '');\n  const addressPostalCode = await getSetting('storePostalCode', '');\n  const storeInformation = {\n    logo,\n    storeName: await getSetting('storeName', 'Evershop'),\n    storeEmail: await getSetting('storeEmail', ''),\n    storeDescription: await getSetting('storeDescription', ''),\n    phone: await getSetting('storePhoneNumber', ''),\n    homeUrl: getBaseUrl(),\n    address: {\n      country: countries.find((c) => c.code === addressCountry)?.name,\n      province: provinces.find((p) => p.code === addressProvince)?.name,\n      city: addressCity,\n      street: addressStreet,\n      postalCode: addressPostalCode\n    }\n  };\n  data.storeInfo = storeInformation;\n  const finalData = await getValue('emailTemplateData', data, {});\n  return finalData;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/Handler.js",
    "content": "import { existsSync } from 'fs';\nimport path, { dirname } from 'path';\nimport { error } from '../log/logger.js';\nimport { getRoutes } from '../router/Router.js';\nimport isDevelopmentMode from '../util/isDevelopmentMode.js';\nimport isProductionMode from '../util/isProductionMode.js';\nimport isErrorHandlerTriggered from './isErrorHandlerTriggered.js';\nimport { noDublicateId } from './noDuplicateId.js';\nimport { parseFromFile } from './parseFromFile.js';\nimport { sortMiddlewares } from './sort.js';\n\nexport class Handler {\n  constructor(routeId) {\n    this.routeId = routeId;\n  }\n\n  static addMiddleware(middleware) {\n    this.middlewares.push(middleware);\n  }\n\n  static getMiddlewares() {\n    return this.middlewares;\n  }\n\n  static getMiddleware(id) {\n    return this.middlewares.find((m) => m.id === id);\n  }\n\n  static getMiddlewareByRoute(route) {\n    const routeId = route.id;\n    if (isProductionMode() && this.sortedMiddlewarePerRoute[routeId]) {\n      return this.sortedMiddlewarePerRoute[routeId];\n    }\n    const region = route.isApi ? 'api' : 'pages';\n    let middlewares = this.middlewares.filter(\n      (m) =>\n        (m.routeId === route.id || m.scope === 'app') && m.region === region\n    );\n    if (route.isAdmin === true) {\n      middlewares = middlewares.concat(\n        this.middlewares.filter(\n          (m) => m.routeId === 'admin' && m.region === region\n        )\n      );\n    } else {\n      middlewares = middlewares.concat(\n        this.middlewares.filter(\n          (m) => m.routeId === 'frontStore' && m.region === region\n        )\n      );\n    }\n    middlewares = sortMiddlewares(middlewares);\n    if (isDevelopmentMode()) {\n      middlewares.unshift({\n        middleware: (request, response, next) => {\n          if (!existsSync(route.folder)) {\n            response.statusCode = 404;\n            const routes = getRoutes();\n            request.currentRoute = routes.find((r) => r.id === 'notFound');\n          }\n          next();\n        }\n      });\n    }\n    this.sortedMiddlewarePerRoute[routeId] = middlewares;\n    return middlewares;\n  }\n\n  static getAppLevelMiddlewares(region) {\n    return sortMiddlewares(\n      this.middlewares.filter((m) => m.scope === 'app' && m.region === region)\n    );\n  }\n\n  static removeMiddleware(path) {\n    this.middlewares = this.middlewares.filter((m) => m.path !== path);\n  }\n\n  static removeMiddlewares(basePath) {\n    this.middlewares = this.middlewares.filter((m) => {\n      if (m.path === basePath) {\n        return false;\n      } else if (dirname(m.path) === basePath) {\n        return false;\n      } else {\n        return true;\n      }\n    });\n  }\n\n  static addMiddlewareFromPath(path) {\n    if (!existsSync(path) || !path.endsWith('.js')) {\n      throw new Error(`Middleware file ${path} does not exist`);\n    } else {\n      const middlewares = parseFromFile(path);\n      middlewares.forEach((middleware) => {\n        if (noDublicateId(this.middlewares, middleware)) {\n          this.addMiddleware(middleware);\n        } else {\n          error(`Duplicate middleware id: ${middleware.id}`);\n        }\n      });\n    }\n  }\n\n  static middleware() {\n    return (request, response, next) => {\n      var _a;\n      request.params = {\n        ...(((_a = request.locals) === null || _a === void 0\n          ? void 0\n          : _a.customParams) || {}),\n        ...request.params\n      };\n      const { currentRoute } = request;\n      let middlewares;\n      if (!currentRoute) {\n        middlewares = this.getAppLevelMiddlewares('pages');\n      } else {\n        middlewares = this.getMiddlewareByRoute(currentRoute);\n      }\n      const goodHandlers = middlewares.filter((m) => m.middleware.length === 3);\n      const errorHandlers = middlewares.filter(\n        (m) => m.middleware.length === 4\n      );\n      let currentGood = 0;\n      let currentError = -1;\n      const eNext = function eNext() {\n        if (arguments.length === 0 && currentGood === goodHandlers.length - 1) {\n          next();\n        } else if (currentError === errorHandlers.length - 1) {\n          next(arguments[0]);\n        } else if (arguments.length > 0) {\n          // Call the error handler middleware if it is not called yet\n          if (!isErrorHandlerTriggered(response)) {\n            currentError += 1;\n            const middlewareFunc = errorHandlers[currentError].middleware;\n            middlewareFunc(arguments[0], request, response, eNext);\n          }\n        } else {\n          currentGood += 1;\n          const middlewareFunc = goodHandlers[currentGood].middleware;\n          middlewareFunc(request, response, eNext);\n        }\n      };\n      // Run the middlewares\n      const { middleware } = goodHandlers[0];\n      middleware(request, response, eNext);\n    };\n  }\n}\nHandler.middlewares = [];\nHandler.sortedMiddlewarePerRoute = {};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/addMiddleware.js",
    "content": "import { findDublicatedMiddleware } from './findDublicatedMiddleware.js';\nimport { Handler } from './Handler.js';\n\nexport function addMiddleware(middleware) {\n  const index = findDublicatedMiddleware(Handler.middlewares, middleware);\n  if (index === -1) {\n    Handler.addMiddleware(middleware);\n  } else {\n    const addedMiddleware = Handler.middlewares[index];\n    throw new Error(\n      `Found two middleware with the same id: ${middleware.path} and ${addedMiddleware.path}`\n    );\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/buildMiddlewareFunction.js",
    "content": "import { existsSync } from 'fs';\nimport { sep } from 'path';\nimport { pathToFileURL } from 'url';\nimport { debug, error } from '../log/logger.js';\nimport isDevelopmentMode from '../util/isDevelopmentMode.js';\nimport isProductionMode from '../util/isProductionMode.js';\nimport { hasDelegate, setDelegate } from './delegate.js';\nimport eNext from './eNext.js';\nimport isErrorHandlerTriggered from './isErrorHandlerTriggered.js';\n\n/**\n * This function takes the defined middleware function and return a new one with wrapper\n *\n * @param {string} id\n * @param {function} middleware: The middleware function\n * @param {string} routeId: The route Id\n * @param {string} before: The middleware function that executes after this one\n * @param {string} after: The middleware function that executes before this one\n * @returns {object} the middleware object\n * @throws\n */\nexport function buildMiddlewareFunction(id, path) {\n  if (!/^[a-zA-Z0-9_]+$/.test(id)) {\n    throw new TypeError(`Middleware ID ${id} is invalid`);\n  }\n\n  const isRoutedLevel = !['all', 'global'].includes(\n    path.split(sep).reverse()[1]\n  );\n  // Check if the middleware is an error handler.\n  // TODO: fix me\n  if (id === 'errorHandler' || id === 'apiErrorHandler') {\n    return async (error, request, response, next) => {\n      const m = isDevelopmentMode()\n        ? await import(`${pathToFileURL(path)}?t=${Date.now()}`)\n        : await import(pathToFileURL(path));\n      const func = m.default;\n      if (request.currentRoute) {\n        await func(error, request, response, next);\n      } else {\n        await func(error, request, response, next);\n      }\n    };\n  } else {\n    return async (request, response, next) => {\n      const startTime = process.hrtime();\n      const debuging = {\n        id\n      };\n      response.debugMiddlewares.push(debuging);\n      // If there response status is 404. We skip routed middlewares\n      if (response.statusCode === 404 && isRoutedLevel) {\n        next();\n      } else {\n        try {\n          const m = isDevelopmentMode()\n            ? await import(`${pathToFileURL(path)}?t=${Date.now()}`)\n            : await import(pathToFileURL(path));\n          let func = m.default;\n          if (!func) {\n            if (isProductionMode()) {\n              throw new Error(\n                `Middleware ${id} is invalid. It should provide a function as default export.`\n              );\n            } else {\n              func = () => {\n                debug(\n                  `Middleware ${id} is not implemented yet. Please implement it.`\n                );\n              };\n            }\n          }\n          if (func.length === 3) {\n            await func(request, response, (err) => {\n              const endTime = process.hrtime(startTime);\n              debuging.time = endTime[1] / 1000000;\n              eNext(request, response, next)(err);\n            });\n          } else {\n            const returnValue = await func(request, response);\n            if (!hasDelegate(id, request)) {\n              setDelegate(id, returnValue, request);\n            }\n            const endTime = process.hrtime(startTime);\n            debuging.time = endTime[1] / 1000000;\n            eNext(request, response, next)();\n          }\n        } catch (e) {\n          // Log the error\n          e.message = `Exception in middleware ${id}: ${e.message}`;\n          error(e);\n          // Call error handler middleware if it is not called yet\n          next(e);\n        }\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/delegate.ts",
    "content": "import { EvershopRequest } from '../../types/request.js';\n\nfunction createWriteOnceMap<K, V>() {\n  const _map = new Map<K, V>();\n\n  return {\n    /**\n     * Set a value once. Throws if the key already exists.\n     */\n    setOnce(key: K, value: V): void {\n      if (_map.has(key)) {\n        throw new Error(`Key \"${String(key)}\" is already set.`);\n      }\n      _map.set(key, value);\n    },\n\n    /**\n     * Get a **cloned copy** of the value. This ensures that the original value\n     * cannot be modified outside of this map.\n     */\n    get(key: K): V | undefined {\n      const val = _map.get(key);\n      return val !== undefined ? structuredClone(val) : undefined;\n    },\n\n    has(key: K): boolean {\n      return _map.has(key);\n    },\n\n    keys(): string[] {\n      return Array.from(_map.keys()).map((key) => String(key));\n    },\n\n    getAll(): Record<string, V> {\n      const result: Record<string, V> = {};\n      for (const [key, value] of _map.entries()) {\n        result[String(key)] = structuredClone(value);\n      }\n      return result;\n    }\n  };\n}\n\n/**\n * Retrieves the delegate manager for the given request.\n * @param request The request object containing the delegate manager.\n * @template T The type of the delegate.\n * @returns The delegate manager.\n */\nexport function getDelegateManager(request: EvershopRequest) {\n  return request?.locals?.delegates || createWriteOnceMap();\n}\n\n/**\n * Checks if a delegate exists for the given ID in the request.\n * @param id The delegate ID to check.\n * @template T The type of the delegate.\n * @param request The request object.\n * @returns True if the delegate exists, false otherwise.\n */\nexport function hasDelegate(id: string, request: EvershopRequest): boolean {\n  return getDelegateManager(request).has(id);\n}\n\n/**\n * Retrieves a delegate value for the given ID.\n * @param id The delegate ID to retrieve.\n * @template T The type of the delegate.\n * @param request The request object.\n * @returns The delegate value or undefined if not found.\n */\nexport function getDelegate<T>(\n  id: string,\n  request: EvershopRequest\n): T | undefined {\n  return getDelegateManager(request).get(id) as T | undefined;\n}\n\nexport function getDelegates(request: EvershopRequest): Record<string, any> {\n  return getDelegateManager(request).getAll();\n}\n\n/**\n * Sets a delegate for the given request object.\n * @param id The delegate ID.\n * @param value The delegate value.\n * @param request The request object.\n */\nexport function setDelegate<T>(\n  id: string,\n  value: T,\n  request: EvershopRequest\n): void {\n  if (!request.locals) {\n    request.locals = {\n      user: null,\n      customer: null,\n      context: {},\n      delegates: createWriteOnceMap(),\n      sessionID: null,\n      webpackMatchedRoute: null\n    };\n  }\n  if (!request.locals.delegates) {\n    request.locals.delegates = createWriteOnceMap();\n  }\n  request.locals.delegates.setOnce(id, value);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/eNext.js",
    "content": "import isErrorHandlerTriggered from './isErrorHandlerTriggered.js';\n\nfunction noop() {}\n\nfunction eNext(request, response, next) {\n  return (error) => {\n    if (!isErrorHandlerTriggered(response)) {\n      error ? next(error) : next();\n    } else {\n      noop();\n    }\n  };\n}\n\nexport default eNext;\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/findDublicatedMiddleware.js",
    "content": "export function findDublicatedMiddleware(registeredMiddlewares, newMiddleware) {\n  return registeredMiddlewares.findIndex(\n    (middleware) =>\n      middleware.id === newMiddleware.id &&\n      (middleware.routeId === null ||\n        middleware.scope === 'app' ||\n        newMiddleware.routeId === null ||\n        middleware.routeId === newMiddleware.routeId ||\n        (middleware.scope === newMiddleware.scope &&\n          ([middleware.routeId, newMiddleware.routeId].includes('admin') ||\n            [middleware.routeId, newMiddleware.routeId].includes(\n              'frontStore'\n            )))) &&\n      middleware.region === newMiddleware.region\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/getRouteFromPath.js",
    "content": "import { sep } from 'path';\n\nexport function getRouteFromPath(path) {\n  const parts = path.split(sep).reverse();\n\n  // Check if current path is an api path\n  if (parts[2] === 'api') {\n    return {\n      region: 'api',\n      scope: parts[1] === 'global' ? 'app' : parts[1],\n      routeId: parts[1] === 'global' ? null : parts[1]\n    };\n  }\n\n  // Current path is a page path\n  let scope;\n  let routeId;\n  let region;\n  region = parts[3];\n  if (parts[1] === 'global') {\n    scope = 'app';\n    routeId = null;\n    region = parts[2];\n  } else if (parts[1] === 'all' && ['frontStore', 'admin'].includes(parts[2])) {\n    scope = routeId = parts[2];\n  } else if (\n    /^[A-Za-z+.]+$/.test(parts[1]) &&\n    ['frontStore', 'admin'].includes(parts[2])\n  ) {\n    scope = parts[2];\n    const routes = parts[1].split('+');\n    if (routes.length > 1) {\n      routeId = routes.filter((r) => r !== '');\n    } else {\n      routeId = parts[1];\n    }\n  } else {\n    throw new Error(`Path ${path} is not valid for a route`);\n  }\n\n  return {\n    region,\n    scope,\n    routeId\n  };\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/index.js",
    "content": "import { existsSync, readdirSync } from 'fs';\nimport { resolve } from 'path';\nimport { addMiddleware } from './addMiddleware.js';\nimport { Handler } from './Handler.js';\nimport { scanForMiddlewareFunctions } from './scanForMiddlewareFunctions.js';\nimport { sortMiddlewares } from './sort.js';\n\nconst middlewareList = Handler.middlewares;\n\nexport function getAdminMiddlewares(routeId) {\n  return sortMiddlewares(\n    middlewareList.filter(\n      (m) =>\n        m.routeId === 'admin' || m.routeId === routeId || m.routeId === null\n    )\n  );\n}\n\nexport function getFrontMiddlewares(routeId) {\n  return sortMiddlewares(\n    middlewareList.filter(\n      (m) =>\n        m.routeId === 'frontStore' ||\n        m.routeId === routeId ||\n        m.routeId === null\n    )\n  );\n}\n\n/**\n * This function scan and load all middleware function of a module base on module path\n *\n * @param   {string}  path  The path of the module\n *\n */\nexport function getModuleMiddlewares(path) {\n  if (existsSync(resolve(path, 'pages'))) {\n    // Scan for the application level middleware\n    if (existsSync(resolve(path, 'pages', 'global'))) {\n      scanForMiddlewareFunctions(resolve(path, 'pages', 'global')).forEach(\n        (m) => {\n          addMiddleware(m);\n        }\n      );\n    }\n    // Scan for the admin level middleware\n    if (existsSync(resolve(path, 'pages', 'admin'))) {\n      const routes = readdirSync(resolve(path, 'pages', 'admin'), {\n        withFileTypes: true\n      })\n        .filter((dirent) => dirent.isDirectory())\n        .map((dirent) => dirent.name);\n      routes.forEach((route) => {\n        scanForMiddlewareFunctions(\n          resolve(path, 'pages', 'admin', route)\n        ).forEach((m) => {\n          addMiddleware(m);\n        });\n      });\n    }\n\n    // Scan for the frontStore level middleware\n    if (existsSync(resolve(path, 'pages', 'frontStore'))) {\n      const routes = readdirSync(resolve(path, 'pages', 'frontStore'), {\n        withFileTypes: true\n      })\n        .filter((dirent) => dirent.isDirectory())\n        .map((dirent) => dirent.name);\n      routes.forEach((route) => {\n        scanForMiddlewareFunctions(\n          resolve(path, 'pages', 'frontStore', route)\n        ).forEach((m) => {\n          addMiddleware(m);\n        });\n      });\n    }\n  }\n\n  // Scan for the api middleware\n  if (existsSync(resolve(path, 'api'))) {\n    const routes = readdirSync(resolve(path, 'api'), { withFileTypes: true })\n      .filter((dirent) => dirent.isDirectory())\n      .map((dirent) => dirent.name);\n    routes.forEach((route) => {\n      scanForMiddlewareFunctions(resolve(path, 'api', route)).forEach((m) => {\n        addMiddleware(m);\n      });\n    });\n  }\n}\n\n/**\n * This function return a list of sorted middleware functions (all)\n *\n * @return  {array}  List of sorted middleware functions\n */\nexport function getAllSortedMiddlewares() {\n  return sortMiddlewares(middlewareList);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/isErrorHandlerTriggered.js",
    "content": "export default (response) => {\n  if (!response.locals) {\n    return false;\n  } else {\n    return response.locals.errorHandlerTriggered === true;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/isNextRequired.js",
    "content": "import { readFileSync } from 'fs';\n\nexport default function isNextRequired(path) {\n  const code = readFileSync(path, 'utf8');\n  return code.includes('next');\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/noDuplicateId.js",
    "content": "import { findDublicatedMiddleware } from './findDublicatedMiddleware.js';\n\n/**\n * This function check if the new middleware function has dublicated ID or not\n *\n * @param   {array}  registeredMiddlewares  The list of registered middleware functions\n * @param   {object}  newMiddleware         The new middleware\n *\n * @return  {boolean}\n */\nexport function noDublicateId(registeredMiddlewares, newMiddleware) {\n  if (findDublicatedMiddleware(registeredMiddlewares, newMiddleware) !== -1) {\n    return false;\n  } else {\n    return true;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/parseFromFile.js",
    "content": "import { basename } from 'path';\nimport { buildMiddlewareFunction } from './buildMiddlewareFunction.js';\nimport { getRouteFromPath } from './getRouteFromPath.js';\n\nexport function parseFromFile(path) {\n  const name = basename(path);\n  let m = {};\n  let id;\n  if (/^(\\[)[a-zA-Z1-9.,]+(\\])[a-zA-Z1-9]+.js$/.test(name)) {\n    const split = name.split(/[\\[\\]]+/);\n    id = split[2].substr(0, split[2].indexOf('.')).trim();\n    m = {\n      id,\n      middleware: buildMiddlewareFunction(id, path),\n      after: split[1].split(',').filter((a) => a.trim() !== ''),\n      path\n    };\n  } else if (/^[a-zA-Z1-9]+(\\[)[a-zA-Z1-9,]+(\\]).js$/.test(name)) {\n    const split = name.split(/[\\[\\]]+/);\n    id = split[0].trim();\n    m = {\n      id,\n      middleware: buildMiddlewareFunction(id, path),\n      before: split[1].split(',').filter((a) => a.trim() !== ''),\n      path\n    };\n  } else if (\n    /^(\\[)[a-zA-Z1-9,]+(\\])[a-zA-Z1-9]+(\\[)[a-zA-Z1-9,]+(\\]).js$/.test(name)\n  ) {\n    const split = name.split(/[\\[\\]]+/);\n    id = split[2].trim();\n    m = {\n      id,\n      middleware: buildMiddlewareFunction(id, path),\n      after: split[1].split(',').filter((a) => a.trim() !== ''),\n      before: split[3].split(',').filter((a) => a.trim() !== ''),\n      path\n    };\n  } else {\n    const split = name.split('.');\n    id = split[0].trim();\n    m = {\n      id,\n      middleware: buildMiddlewareFunction(id, path),\n      path\n    };\n  }\n\n  const route = getRouteFromPath(path);\n  if (route.region === 'api') {\n    if (m.id !== 'context' && m.id !== 'apiErrorHandler') {\n      m.before = !m.before ? ['apiResponse'] : m.before;\n      m.after = !m.after ? ['escapeHtml', 'auth'] : m.after;\n    }\n  } else if (m.id !== 'context' && m.id !== 'errorHandler') {\n    m.before = !m.before ? ['notFound'] : m.before;\n    m.after = !m.after ? ['auth'] : m.after;\n  }\n\n  // Check if routeId is an array of routeIds or a single routeId\n  if (Array.isArray(route.routeId)) {\n    return route.routeId.map((r) => ({\n      ...m,\n      region: route.region,\n      scope: route.scope,\n      routeId: r\n    }));\n  } else {\n    return [\n      {\n        ...m,\n        ...route\n      }\n    ];\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/scanForMiddlewareFunctions.js",
    "content": "import { readdirSync } from 'fs';\nimport { resolve } from 'path';\nimport { parseFromFile } from './parseFromFile.js';\n\n/**\n * This function take a path and scan for the middleware functions\n *\n * @param {string} path  The path of the folder where middleware functions are located\n *\n * @return {array}  List of middleware function\n */\n\nexport function scanForMiddlewareFunctions(path) {\n  let middlewares = [];\n  readdirSync(resolve(path), { withFileTypes: true })\n    .filter(\n      (dirent) =>\n        dirent.isFile() &&\n        /\\.js$/.test(dirent.name) &&\n        !/^[A-Z]/.test(dirent.name[0])\n    )\n    .forEach((dirent) => {\n      const middlewareFunc = resolve(path, dirent.name);\n      middlewares = middlewares.concat(parseFromFile(middlewareFunc));\n    });\n\n  return middlewares;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/sort.js",
    "content": "import Topo from '@hapi/topo';\n\n/**\n * This function take a path and scan for the middleware functions\n *\n * @param {array} middlewares  The list of middleware functions\n *\n * @return {array} List of sorted middleware functions\n */\nexport function sortMiddlewares(middlewares = []) {\n  const middlewareFunctions = middlewares.filter((m) => {\n    if ((m.before === m.after) === null) return true;\n    const dependencies = (m.before || []).concat(m.after || []);\n    let flag = true;\n    dependencies.forEach((d) => {\n      if (\n        flag === false ||\n        middlewares.findIndex(\n          (e) =>\n            e.id === d &&\n            (e.scope === 'app' ||\n              e.scope === 'admin' ||\n              e.scope === 'frontStore' ||\n              e.routeId === null ||\n              e.routeId === m.scope ||\n              e.routeId === m.routeId)\n        ) === -1\n      ) {\n        flag = false;\n      }\n    });\n\n    return flag;\n  });\n  const sorter = new Topo.Sorter();\n  middlewareFunctions.forEach((m) => {\n    sorter.add(m.id, { before: m.before, after: m.after, group: m.id });\n  });\n\n  return sorter.nodes.map((n) => {\n    const index = middlewareFunctions.findIndex((m) => m.id === n);\n    const m = middlewareFunctions[index];\n    middlewareFunctions.splice(index, 1);\n    return m;\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/app.js",
    "content": "import path from 'path';\nimport { addDefaultMiddlewareFuncs } from '../../../../bin/lib/addDefaultMiddlewareFuncs.js';\nimport express from 'express';\nimport { loadModuleRoutes } from '../../../../lib/router/loadModuleRoutes.js';\nimport { once } from 'events';\nimport { getModuleMiddlewares } from '../../index.js';\nimport { getRoutes } from '../../../router/Router.js';\nimport { Handler } from '../../Handler.js';\nimport { error } from '../../../log/logger.js';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/** Create express app */\nconst app = express();\n\n/* Loading modules and initilize routes, components and services */\nconst modules = [\n  {\n    name: 'api',\n    path: path.resolve(__dirname, './modules/api')\n  },\n  {\n    name: 'authcopy',\n    path: path.resolve(__dirname, './modules/authcopy')\n  },\n  {\n    name: 'basecopy',\n    path: path.resolve(__dirname, './modules/basecopy')\n  },\n  {\n    name: 'graphqlcopy',\n    path: path.resolve(__dirname, './modules/graphqlcopy')\n  },\n  {\n    name: '404page',\n    path: path.resolve(__dirname, './modules/404page')\n  },\n  {\n    name: 'error',\n    path: path.resolve(__dirname, './modules/error')\n  },\n  {\n    name: 'delegate',\n    path: path.resolve(__dirname, './modules/delegate')\n  },\n  {\n    name: 'middleware',\n    path: path.resolve(__dirname, './modules/handler')\n  }\n];\n\n// Load routes and middleware functions\nmodules.forEach((module) => {\n  try {\n    // Load middleware functions\n    getModuleMiddlewares(module.path);\n    // Load routes\n    loadModuleRoutes(module.path);\n  } catch (e) {\n    error(e);\n    process.exit(0);\n  }\n});\n\n// TODO: load extensions, themes\n\nconst routes = getRoutes();\n\n// Adding default middlewares\naddDefaultMiddlewareFuncs(app, routes);\n\n/** Hack for 'no route' case */\n// routes.push({\n//   id: 'noRoute',\n//   path: '/*',\n//   method: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']\n// });\n\n// routes.forEach((route) => {\n//   app.all(route.path, Handler.middleware());\n// });\napp.use(Handler.middleware());\n\nconst bootstrap = async (server) => {\n  server.listen();\n  await once(server, 'listening');\n  return server.address().port;\n};\n\nconst close = (server, done) => {\n  server.close(done);\n};\n\nexport { app, bootstrap, close };\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/404page/pages/frontStore/product/[loadProduct]loadCategory.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/404page/pages/frontStore/product/[loadProduct]loadProductImage.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn(async (request, response, next) => {\n  next();\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/404page/pages/frontStore/product/loadProduct.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn(async (request, response, next) => {\n  try {\n    response.status(404);\n    next();\n  } catch (e) {\n    next(e);\n  }\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/404page/pages/frontStore/product/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/product/:id\",\n  \"name\": \"Product single page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/api/api/createA/index.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/api/api/createA/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/as\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/api/api/global/apiGlobal.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/authcopy/api/createA/[index]afterIndex.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/authcopy/api/global/[context]auth.js",
    "content": "export default (request, response) => {\n  // Do nothing\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/authcopy/api/global/apiAuthGlobal.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/authcopy/pages/global/[context]auth.js",
    "content": "export default (request, response) => {};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/api/global/[apiResponse]apiErrorHandler.js",
    "content": "import apiErrorHandler from '../../../../../../../../modules/base/api/global/[apiResponse]apiErrorHandler.js';\n\nexport default apiErrorHandler;\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/api/global/[auth]apiResponse[apiErrorHandler].js",
    "content": "import apiResponse from '../../../../../../../../modules/base/api/global/[auth]apiResponse[apiErrorHandler].js';\n\nexport default apiResponse;\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/api/global/[auth]payloadValidate.js",
    "content": "import payloadValidate from '../../../../../../../../modules/base/api/global/[auth]payloadValidate.js';\n\nexport default payloadValidate;\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/api/global/[payloadValidate]escapeHtml.js",
    "content": "import escapeHtml from '../../../../../../../../modules/base/api/global/[payloadValidate]escapeHtml.js';\n\nexport default escapeHtml;\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/api/global/context.js",
    "content": "import { setContextValue } from '../../../../../../../../modules/graphql/services/contextHelper.js';\n\nexport default (request, response) => {\n  response.context = {}; // TODO: Fix this\n\n  setContextValue(\n    request,\n    'homeUrl',\n    `${request.protocol}://${request.get('host')}`\n  );\n\n  setContextValue(\n    request,\n    'currentUrl',\n    `${request.protocol}://${request.get('host')}${request.originalUrl}`\n  );\n  setContextValue(request, 'baseUrl', request.baseUrl);\n  setContextValue(request, 'body', request.body);\n  setContextValue(request, 'cookies', request.cookies);\n  setContextValue(request, 'fresh', request.fresh);\n  setContextValue(request, 'hostname', request.hostname);\n  setContextValue(request, 'ip', request.ip);\n  setContextValue(request, 'ips', request.ips);\n  setContextValue(request, 'method', request.method);\n  setContextValue(request, 'originalUrl', request.originalUrl);\n  setContextValue(request, 'params', request.params);\n  setContextValue(request, 'path', request.path);\n  setContextValue(request, 'protocol', request.protocol);\n  setContextValue(request, 'query', request.query);\n  setContextValue(request, 'route', request.route);\n  setContextValue(request, 'secure', request.secure);\n  setContextValue(request, 'signedCookies', request.signedCookies);\n  setContextValue(request, 'stale', request.stale);\n  setContextValue(request, 'subdomains', request.subdomains);\n  setContextValue(request, 'xhr', request.xhr);\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/admin/adminStaticAsset/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/assets/*\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/admin/adminStaticAsset/staticAssets.js",
    "content": "import staticMiddleware from '../../../../../../../../../modules/cms/pages/admin/adminStaticAsset/staticAssets.js';\n\nexport default staticMiddleware;\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/admin/all/adminTitle.js",
    "content": "export default (request, response, next) => {\n  next();\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/frontStore/all/title.js",
    "content": "export default (request, response, next) => {\n  next();\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/frontStore/notFound/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/notFound\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/frontStore/staticAsset/[context]staticAssets[auth].js",
    "content": "import staticMiddleware from '../../../../../../../../../modules/cms/pages/frontStore/staticAsset/[context]staticAssets[auth].js';\n\nexport default staticMiddleware;\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/frontStore/staticAsset/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/assets/*\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/global/[auth]notFound[response].js",
    "content": "import jest from 'jest-mock';\nimport notFound from '../../../../../../../../modules/base/pages/global/[auth]notFound[response].js';\n\nexport default jest.fn(notFound);\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/global/[notFound]dummy[response].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/global/[response]errorHandler.js",
    "content": "import jest from 'jest-mock';\nimport errorHandler from '../../../../../../../../modules/base/pages/global/[response]errorHandler.js';\n\nexport default jest.fn(errorHandler);\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/global/context.js",
    "content": "import { setContextValue } from '../../../../../../../../modules/graphql/services/contextHelper.js';\n\nexport default (request, response) => {\n  response.context = {}; // TODO: Fix this\n\n  setContextValue(\n    request,\n    'homeUrl',\n    `${request.protocol}://${request.get('host')}`\n  );\n  setContextValue(\n    request,\n    'currentUrl',\n    `${request.protocol}://${request.get('host')}${request.originalUrl}`\n  );\n  setContextValue(request, 'baseUrl', request.baseUrl);\n  setContextValue(request, 'body', request.body);\n  setContextValue(request, 'cookies', request.cookies);\n  setContextValue(request, 'fresh', request.fresh);\n  setContextValue(request, 'hostname', request.hostname);\n  setContextValue(request, 'ip', request.ip);\n  setContextValue(request, 'ips', request.ips);\n  setContextValue(request, 'method', request.method);\n  setContextValue(request, 'originalUrl', request.originalUrl);\n  setContextValue(request, 'params', request.params);\n  setContextValue(request, 'path', request.path);\n  setContextValue(request, 'protocol', request.protocol);\n  setContextValue(request, 'query', request.query);\n  setContextValue(request, 'route', request.route);\n  setContextValue(request, 'secure', request.secure);\n  setContextValue(request, 'signedCookies', request.signedCookies);\n  setContextValue(request, 'stale', request.stale);\n  setContextValue(request, 'subdomains', request.subdomains);\n  setContextValue(request, 'xhr', request.xhr);\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/basecopy/pages/global/response[errorHandler].js",
    "content": "import jest from 'jest-mock';\nimport response from '../../../../../../../../modules/base/pages/global/response[errorHandler].js';\n\nexport default jest.fn(response);\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/delegate/pages/frontStore/delegateTest/asyncWithNext[collection].js",
    "content": "import axios from 'axios';\n\nexport default async (request, response, next) => {\n  const content = await axios.get(\n    'https://jsonplaceholder.typicode.com/todos/1'\n  );\n  next();\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/delegate/pages/frontStore/delegateTest/async[collection].js",
    "content": "import axios from 'axios';\n\nexport default async (request, response) => {\n  const content = await axios.get(\n    'https://jsonplaceholder.typicode.com/todos/1'\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/delegate/pages/frontStore/delegateTest/collection.js",
    "content": "import { getDelegates, setDelegate } from '../../../../../../../delegate.js';\n\nlet delegates;\nfunction collection(request, response, next) {\n  delegates = getDelegates(request);\n  return response.status(200).json({\n    ok: true\n  });\n}\nexport default collection;\nexport { delegates };\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/delegate/pages/frontStore/delegateTest/returnOne[returnTwo].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => 1);\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/delegate/pages/frontStore/delegateTest/returnThree[collection].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => 3);\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/delegate/pages/frontStore/delegateTest/returnTwo[returnThree].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response, next) => {\n  next();\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/delegate/pages/frontStore/delegateTest/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/delegateTest\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/delegate/pages/frontStore/delegateTest/syncOne.js",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/delegate/pages/frontStore/delegateTest/syncWithNext[collection].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response, next) => {\n  next();\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/delegate/pages/frontStore/delegateTest/sync[collection].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {\n  const a = 1;\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/error/pages/frontStore/errorHandlerTest/errorInAsync.js",
    "content": "export default async (request, response) => {\n  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n  await delay(3000);\n  undefined.b = 1;\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/error/pages/frontStore/errorHandlerTest/errorInAsyncWithNext.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn(async (request, response, next) => {\n  try {\n    const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n    await delay(3000);\n    undefined.a = 1;\n    next();\n  } catch (e) {\n    next(e);\n  }\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/error/pages/frontStore/errorHandlerTest/errorInSync.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {\n  throw new Error('Error in sync');\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/error/pages/frontStore/errorHandlerTest/errorInSyncWithNext.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response, next) => {\n  next(new Error('Error in sync with next'));\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/error/pages/frontStore/errorHandlerTest/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/errorHandlerTest\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/graphqlcopy/pages/global/[bodyParser]buildQuery[graphql].js",
    "content": "export default (request, response) => {};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/graphqlcopy/pages/global/[buildQuery]graphql[notFound].js",
    "content": "export default (request, response) => {};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/graphqlcopy/pages/global/bodyParser[buildQuery].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, () => {\n    bodyParser.urlencoded({ extended: true })(request, response, next);\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/admin/productEdit/[loadProduct]loadCategory.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/admin/productEdit/[loadProduct]loadProductImage.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn(async (request, response, next) => {\n  next();\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/admin/productEdit/loadProduct.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn(async (request, response, next) => {\n  try {\n    response.status(404);\n    next();\n  } catch (e) {\n    next(e);\n  }\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/admin/productEdit/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/product/edit/:id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/frontStore/middleware/[loadAttribute]loadOptions.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/frontStore/middleware/[loadProductImage]loadAttribute.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {\n  throw new Error('this is an error');\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/frontStore/middleware/[loadProduct]loadCategory.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/frontStore/middleware/[loadProduct]loadProductImage.js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn(async (request, response, next) => {\n  next();\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/frontStore/middleware/[syncOne,asyncOne]checkExecutionOrderAsync[loadAttribute].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn(async (request, response) => {\n  if (!request.syncOneCompleted) {\n    throw new Error('syncOne middleware should be completed first');\n  }\n  if (!request.asyncOneCompleted) {\n    throw new Error('asyncOne middleware should be completed first');\n  }\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/frontStore/middleware/[syncOne,asyncOne]checkExecutionOrder[loadAttribute].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {\n  if (!request.syncOneCompleted) {\n    throw new Error('syncOne middleware should be completed first');\n  }\n  if (!request.asyncOneCompleted) {\n    throw new Error('asyncOne middleware should be completed first');\n  }\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/frontStore/middleware/asyncOne[loadAttribute].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn(async (request, response) => {\n  await new Promise((r) => setTimeout(r, 200));\n  request.asyncOneCompleted = true;\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/frontStore/middleware/loadProduct[loadAttribute].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn(async (request, response, next) => {\n  next();\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/frontStore/middleware/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/middleware\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/app/modules/handler/pages/frontStore/middleware/syncOne[loadAttribute].js",
    "content": "import jest from 'jest-mock';\n\nexport default jest.fn((request, response) => {\n  request.syncOneCompleted = true;\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/unit/404page.handling.test.js",
    "content": "import http from 'http';\nimport axios from 'axios';\nimport { app, bootstrap, close } from '../app/app.js';\nimport notFound from '../app/modules/basecopy/pages/global/[auth]notFound[response].js';\nimport dummy from '../app/modules/basecopy/pages/global/[notFound]dummy[response].js';\nimport response from '../app/modules/basecopy/pages/global/response[errorHandler].js';\nimport loadProductImage from '../app/modules/404page/pages/frontStore/product/[loadProduct]loadProductImage.js';\nimport loadCategory from '../app/modules/404page/pages/frontStore/product/[loadProduct]loadCategory.js';\nimport loadProduct from '../app/modules/404page/pages/frontStore/product/loadProduct.js';\n\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n\njest.setTimeout(80000);\ndescribe('buildMiddlewareFunction', () => {\n  const server = http.createServer(app);\n\n  let port;\n  beforeAll(async () => {\n    port = await bootstrap(server);\n  });\n\n  it('It should return 404 page when route is not exist', async () => {\n    // Visit a url\n    const response = await axios.get(\n      `http://localhost:${port}/noexistedroute`,\n      {\n        validateStatus(status) {\n          return status >= 200 && status < 600;\n        }\n      }\n    );\n    expect(response.status).toEqual(404);\n    expect(notFound).toHaveBeenCalledTimes(1);\n  });\n\n  it('It should return 404 page when middleware sets status to 404', async () => {\n    // Visit a url\n    const response = await axios.get(`http://localhost:${port}/product/404`, {\n      validateStatus(status) {\n        return status >= 200 && status < 500;\n      }\n    });\n    expect(response.status).toEqual(404);\n  });\n\n  it('It should bypass the rouded middleware when status is 404', async () => {\n    expect(loadProductImage).toHaveBeenCalledTimes(0);\n    expect(loadCategory).toHaveBeenCalledTimes(0);\n    expect(loadProduct).toHaveBeenCalledTimes(1);\n  });\n\n  it('It should not bypass the app level middleware when status is 404', async () => {\n    expect(notFound).toHaveBeenCalledTimes(2);\n    expect(dummy).toHaveBeenCalledTimes(2);\n    expect(response).toHaveBeenCalledTimes(2);\n  });\n\n  afterAll((done) => {\n    close(server, done);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/unit/500error.handling.test.js",
    "content": "import { app, bootstrap, close } from '../app/app.js';\nimport axios from 'axios';\nimport http from 'http';\nimport errorHandler from '../app/modules/basecopy/pages/global/[response]errorHandler.js';\n\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n\njest.setTimeout(800000);\ndescribe('buildMiddlewareFunction', () => {\n  const server = http.createServer(app);\n\n  let port;\n  beforeAll(async () => {\n    port = await bootstrap(server);\n  });\n\n  it('It should return 500 error when a error occurred', async () => {\n    // Visit a url\n    const response = await axios.get(\n      `http://localhost:${port}/errorHandlerTest`,\n      {\n        validateStatus(status) {\n          return status >= 200 && status < 600;\n        }\n      }\n    );\n    expect(response.status).toEqual(500);\n    expect(response.data.split(/\\r\\n|\\r|\\n/).length).toEqual(1);\n  });\n\n  it('The error handler middleware should be executed only one time per request', async () => {\n    expect(errorHandler).toHaveBeenCalledTimes(1);\n  });\n\n  afterAll((done) => {\n    close(server, done);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/unit/apiHandler.middleware.test.js",
    "content": "import http from 'http';\nimport axios from 'axios';\nimport { app, bootstrap, close } from '../app/app.js';\nimport createA from '../app/modules/api/api/createA/index.js';\nimport afterIndex from '../app/modules/authcopy/api/createA/[index]afterIndex.js';\nimport createAGlobal from '../app/modules/api/api/global/apiGlobal.js';\nimport authApiGlobal from '../app/modules/authcopy/api/global/apiAuthGlobal.js';\n\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\njest.setTimeout(80000);\ndescribe('test API middleware', () => {\n  const server = http.createServer(app);\n\n  let port;\n  beforeAll(async () => {\n    port = await bootstrap(server);\n  });\n\n  it('It should execute the valid middleware functions', async () => {\n    try {\n      await axios.post(`http://localhost:${port}/api/as`, {\n        validateStatus(status) {\n          return status >= 200 && status < 600;\n        }\n      });\n    } catch (e) {\n      console.log(e.response.data);\n    }\n    expect(createA).toHaveBeenCalledTimes(1);\n    expect(afterIndex).toHaveBeenCalledTimes(1);\n    expect(createAGlobal).toHaveBeenCalledTimes(1);\n    expect(authApiGlobal).toHaveBeenCalledTimes(1);\n  });\n\n  afterAll((done) => {\n    close(server, done);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/unit/delegate.test.js",
    "content": "import http from 'http';\nimport axios from 'axios';\nimport { app, bootstrap, close } from '../app/app.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\nimport { delegates } from '../app/modules/delegate/pages/frontStore/delegateTest/collection.js';\nimport { getDelegateManager } from '../../delegate.js';\n\njest.setTimeout(80000);\ndescribe('test delegate', () => {\n  const server = http.createServer(app);\n  let port;\n  beforeAll(async () => {\n    port = await bootstrap(server);\n  });\n\n  it('It should allow set delegate the fist time', () => {\n    const delegate = getDelegateManager({ locals: {} });\n    delegate.setOnce('returnOne', 1);\n    expect(delegate.get('returnOne')).toEqual(1);\n  });\n\n  it('It should throw error when set delegate again', () => {\n    const delegate = getDelegateManager({ locals: {} });\n    delegate.setOnce('returnOne', 1);\n    expect(() => {\n      delegate.setOnce('returnOne', 2);\n    }).toThrowError('is already set');\n  });\n\n  it('It should allow to get all values', () => {\n    const delegate = getDelegateManager({ locals: {} });\n    delegate.setOnce('returnTwo', 2);\n    delegate.setOnce('returnThree', 3);\n    expect(delegate.getAll()).toEqual({\n      returnTwo: 2,\n      returnThree: 3\n    });\n    expect(delegate.keys()).toEqual(['returnTwo', 'returnThree']);\n  });\n\n  it('It should give a cloned value instead of the original value', () => {\n    const delegate = getDelegateManager({ locals: {} });\n    delegate.setOnce('returnOne', 1);\n    delegate.setOnce('returnTwo', { value: 2 });\n    let value = delegate.get('returnOne');\n    value += 1; // modify the value\n    const objectValue = delegate.get('returnTwo');\n    objectValue.value += 1; // modify the value\n    expect(value).toEqual(2); // cloned value should not change\n    expect(objectValue.value).toEqual(3);\n    expect(delegate.get('returnOne')).toEqual(1); // original value should not change\n  });\n\n  it('Middleware function return desired value', async () => {\n    // Visit a url\n    await axios.get(`http://localhost:${port}/delegateTest`, {\n      validateStatus(status) {\n        return status >= 200 && status < 600;\n      }\n    });\n    expect(delegates.returnOne).toEqual(1);\n    expect(delegates.returnTwo).toEqual(undefined);\n    expect(delegates.returnThree).toEqual(3);\n  });\n\n  afterAll((done) => {\n    close(server, done);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/unit/handler.getMiddlewaresByRoute.test.js",
    "content": "import { Handler } from '../../Handler.js';\nimport '../app/app.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n\ndescribe('test getMiddlewaresByRoute', () => {\n  const productMiddleweres = Handler.getMiddlewareByRoute({\n    id: 'product',\n    isAdmin: false\n  });\n  it('should contains middlewares for product route', () => {\n    expect(\n      productMiddleweres.findIndex((m) => m.id === 'loadCategory')\n    ).not.toBe(-1);\n    expect(\n      productMiddleweres.findIndex((m) => m.id === 'loadProductImage')\n    ).not.toBe(-1);\n  });\n\n  it('should contains frontStore level middleware', () => {\n    expect(productMiddleweres.findIndex((m) => m.id === 'title')).not.toBe(-1);\n  });\n\n  it('should contains application level middleware', () => {\n    expect(productMiddleweres.findIndex((m) => m.id === 'auth')).not.toBe(-1);\n    expect(\n      productMiddleweres.findIndex((m) => m.id === 'errorHandler')\n    ).not.toBe(-1);\n    expect(productMiddleweres.findIndex((m) => m.id === 'context')).not.toBe(\n      -1\n    );\n    expect(productMiddleweres.findIndex((m) => m.id === 'response')).not.toBe(\n      -1\n    );\n  });\n\n  const productEditMiddleweres = Handler.getMiddlewareByRoute({\n    id: 'productEdit',\n    isAdmin: true\n  });\n\n  it('should contains middlewares for product route', () => {\n    expect(\n      productEditMiddleweres.findIndex((m) => m.id === 'loadCategory')\n    ).not.toBe(-1);\n    expect(\n      productEditMiddleweres.findIndex((m) => m.id === 'loadProductImage')\n    ).not.toBe(-1);\n  });\n\n  it('should contains admin level middleware', () => {\n    expect(\n      productEditMiddleweres.findIndex((m) => m.id === 'adminTitle')\n    ).not.toBe(-1);\n  });\n\n  it('should contains application level middleware', () => {\n    expect(productEditMiddleweres.findIndex((m) => m.id === 'auth')).not.toBe(\n      -1\n    );\n    expect(\n      productEditMiddleweres.findIndex((m) => m.id === 'errorHandler')\n    ).not.toBe(-1);\n    expect(\n      productEditMiddleweres.findIndex((m) => m.id === 'context')\n    ).not.toBe(-1);\n    expect(\n      productEditMiddleweres.findIndex((m) => m.id === 'response')\n    ).not.toBe(-1);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/unit/handlers.middleware.test.js",
    "content": "import { app, bootstrap, close } from '../app/app.js';\nimport axios from 'axios';\nimport http from 'http';\nimport loadProductAttribute from '../app/modules/handler/pages/frontStore/middleware/[loadProductImage]loadAttribute.js';\nimport loadProductImage from '../app/modules/handler/pages/frontStore/middleware/[loadProduct]loadProductImage.js';\nimport loadCategory from '../app/modules/handler/pages/frontStore/middleware/[loadProduct]loadCategory.js';\nimport loadProduct from '../app/modules/handler/pages/frontStore/middleware/loadProduct[loadAttribute].js';\nimport loadProductOption from '../app/modules/handler/pages/frontStore/middleware/[loadAttribute]loadOptions.js';\nimport syncOne from '../app/modules/handler/pages/frontStore/middleware/syncOne[loadAttribute].js';\nimport asyncOne from '../app/modules/handler/pages/frontStore/middleware/asyncOne[loadAttribute].js';\nimport checkExecutionOrder from '../app/modules/handler/pages/frontStore/middleware/[syncOne,asyncOne]checkExecutionOrder[loadAttribute].js';\nimport checkExecutionOrderAsync from '../app/modules/handler/pages/frontStore/middleware/[syncOne,asyncOne]checkExecutionOrderAsync[loadAttribute].js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n\njest.setTimeout(80000);\ndescribe('test middleware', () => {\n  const server = http.createServer(app);\n\n  let port;\n  beforeAll(async () => {\n    port = await bootstrap(server);\n  });\n\n  it('It should only execute the next middleware function when the previous one is completed', async () => {\n    // Visit a url\n    const response = await axios.get(`http://localhost:${port}/middleware`, {\n      validateStatus(status) {\n        return status >= 200 && status <= 500;\n      }\n    });\n    expect(syncOne).toHaveBeenCalledTimes(1);\n    expect(asyncOne).toHaveBeenCalledTimes(1);\n    expect(checkExecutionOrder).toHaveBeenCalledTimes(1);\n    expect(checkExecutionOrderAsync).toHaveBeenCalledTimes(1);\n    expect(loadProductAttribute).toHaveBeenCalledTimes(1);\n  });\n\n  it('It should execute the good middleware functions', async () => {\n    const response = await axios.get(`http://localhost:${port}/middleware`, {\n      validateStatus(status) {\n        return status >= 200 && status <= 500;\n      }\n    });\n    expect(loadProductAttribute).toHaveBeenCalledTimes(2);\n    expect(loadProductImage).toHaveBeenCalledTimes(2);\n    expect(loadCategory).toHaveBeenCalledTimes(2);\n    expect(loadProduct).toHaveBeenCalledTimes(2);\n  });\n\n  it('It should not execute the middleware functions after error occurred', async () => {\n    expect(loadProductOption).toHaveBeenCalledTimes(0);\n  });\n\n  afterAll((done) => {\n    close(server, done);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/unit/middleware.buildMiddlewareFunction.test.js",
    "content": "import { buildMiddlewareFunction } from '../../buildMiddlewareFunction.js';\nimport path from 'path';\nimport { jest, describe, it, expect } from '@jest/globals';\n\nexpect.extend({\n  nullOrAny(received, expected) {\n    if (received === null) {\n      return {\n        pass: true,\n        message: () =>\n          `expected null or instance of ${this.utils.printExpected(\n            expected\n          )}, but received ${this.utils.printReceived(received)}`\n      };\n    }\n\n    if (expected == String) {\n      return {\n        pass: typeof received === 'string' || received instanceof String,\n        message: () =>\n          `expected null or instance of ${this.utils.printExpected(\n            expected\n          )}, but received ${this.utils.printReceived(received)}`\n      };\n    }\n\n    if (expected == Number) {\n      return {\n        pass: typeof received === 'number' || received instanceof Number,\n        message: () =>\n          `expected null or instance of ${this.utils.printExpected(\n            expected\n          )}, but received ${this.utils.printReceived(received)}`\n      };\n    }\n\n    if (expected == Function) {\n      return {\n        pass: typeof received === 'function' || received instanceof Function,\n        message: () =>\n          `expected null or instance of ${this.utils.printExpected(\n            expected\n          )}, but received ${this.utils.printReceived(received)}`\n      };\n    }\n\n    if (expected == Object) {\n      return {\n        pass: received !== null && typeof received === 'object',\n        message: () =>\n          `expected null or instance of ${this.utils.printExpected(\n            expected\n          )}, but received ${this.utils.printReceived(received)}`\n      };\n    }\n\n    if (expected == Boolean) {\n      return {\n        pass: typeof received === 'boolean',\n        message: () =>\n          `expected null or instance of ${this.utils.printExpected(\n            expected\n          )}, but received ${this.utils.printReceived(received)}`\n      };\n    }\n\n    /* jshint -W122 */\n    /* global Symbol */\n    if (typeof Symbol !== 'undefined' && this.expectedObject == Symbol) {\n      return {\n        pass: typeof received === 'symbol',\n        message: () =>\n          `expected null or instance of ${this.utils.printExpected(\n            expected\n          )}, but received ${this.utils.printReceived(received)}`\n      };\n    }\n    /* jshint +W122 */\n\n    return {\n      pass: received instanceof expected,\n      message: () =>\n        `expected null or instance of ${this.utils.printExpected(\n          expected\n        )}, but received ${this.utils.printReceived(received)}`\n    };\n  }\n});\n\ndescribe('buildMiddlewareFunction', () => {\n  it('It should thrown an exception if id is not valid', () => {\n    expect(() =>\n      buildMiddlewareFunction('a b', '/catalog/controllers/product.js')\n    ).toThrow(Error);\n  });\n\n  it('It should return a function if id is valid', () => {\n    const middleware = buildMiddlewareFunction(\n      'abc',\n      '/catalog/controllers/product.js'\n    );\n    expect(typeof middleware).toBe('function');\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/unit/middleware.getRouteFromPath.test.js",
    "content": "import { resolve } from 'path';\nimport { getRouteFromPath } from '../../getRouteFromPath.js';\n\ndescribe('Test getRouteFromPath function', () => {\n  it('Parse app level route', () => {\n    expect(getRouteFromPath(resolve('/catalog/pages/global/title.js'))).toEqual(\n      {\n        region: 'pages',\n        scope: 'app',\n        routeId: null\n      }\n    );\n\n    expect(getRouteFromPath(resolve('/cms/api/global/title.js'))).toEqual({\n      region: 'api',\n      scope: 'app',\n      routeId: null\n    });\n  });\n\n  it('Parse admin level route', () => {\n    expect(\n      getRouteFromPath(resolve('/catalog/pages/admin/all/title.js'))\n    ).toEqual({\n      region: 'pages',\n      scope: 'admin',\n      routeId: 'admin'\n    });\n\n    expect(getRouteFromPath(resolve('/cms/api/admin/all/title.js'))).toEqual({\n      region: 'api',\n      scope: 'admin',\n      routeId: 'admin'\n    });\n  });\n\n  it('Parse frontStore level route', () => {\n    expect(\n      getRouteFromPath(resolve('/catalog/pages/frontStore/all/title.js'))\n    ).toEqual({\n      region: 'pages',\n      scope: 'frontStore',\n      routeId: 'frontStore'\n    });\n\n    expect(\n      getRouteFromPath(resolve('/cms/api/frontStore/all/title.js'))\n    ).toEqual({\n      region: 'api',\n      scope: 'frontStore',\n      routeId: 'frontStore'\n    });\n  });\n\n  it('Parse admin routed level route', () => {\n    expect(\n      getRouteFromPath(resolve('/catalog/pages/admin/product/title.js'))\n    ).toEqual({\n      region: 'pages',\n      scope: 'admin',\n      routeId: 'product'\n    });\n\n    expect(\n      getRouteFromPath(resolve('/cms/api/admin/product/title.js'))\n    ).toEqual({\n      region: 'api',\n      scope: 'admin',\n      routeId: 'product'\n    });\n  });\n\n  it('Parse frontStore routed level route', () => {\n    expect(\n      getRouteFromPath(resolve('/catalog/pages/frontStore/product/title.js'))\n    ).toEqual({\n      region: 'pages',\n      scope: 'frontStore',\n      routeId: 'product'\n    });\n\n    expect(\n      getRouteFromPath(resolve('/cms/api/frontStore/product/title.js'))\n    ).toEqual({\n      region: 'api',\n      scope: 'frontStore',\n      routeId: 'product'\n    });\n  });\n\n  it('Parse multi admin routed level route', () => {\n    expect(\n      getRouteFromPath(\n        resolve('/catalog/pages/admin/product+category/title.js')\n      )\n    ).toEqual({\n      region: 'pages',\n      scope: 'admin',\n      routeId: ['product', 'category']\n    });\n\n    expect(\n      getRouteFromPath(resolve('/cms/api/admin/product+category/title.js'))\n    ).toEqual({\n      region: 'api',\n      scope: 'admin',\n      routeId: ['product', 'category']\n    });\n  });\n\n  it('Parse multi frontStore routed level route', () => {\n    expect(\n      getRouteFromPath(\n        resolve('/catalog/pages/frontStore/product+category/title.js')\n      )\n    ).toEqual({\n      region: 'pages',\n      scope: 'frontStore',\n      routeId: ['product', 'category']\n    });\n\n    expect(\n      getRouteFromPath(resolve('/cms/api/frontStore/product+category/title.js'))\n    ).toEqual({\n      region: 'api',\n      scope: 'frontStore',\n      routeId: ['product', 'category']\n    });\n  });\n\n  it('Parse invalid path', () => {\n    expect(() =>\n      getRouteFromPath(\n        resolve('/catalog/controllers/fro ntStore/product/title.js')\n      )\n    ).toThrow();\n\n    expect(() =>\n      getRouteFromPath(\n        resolve('/catalog/controllers/frontStore/pro2uct/title.js')\n      )\n    ).toThrow();\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/unit/middleware.noDublicateId.test.js",
    "content": "import { noDublicateId } from '../../noDuplicateId.js';\n\ndescribe('Test noDublicateId function', () => {\n  it('It should return false if routed middlewareID is existed', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'home'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'home'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if admin level middlewareID is existed', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'admin'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'admin'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if admin level middlewareID is existed', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'admin',\n            scope: 'admin'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'admin'\n        },\n        'admin'\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if frontStore level middlewareID is existed', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'frontStore'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'frontStore'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if application level middlewareID is existed', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: null\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: null\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if routeId is null', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: null\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'home'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if routeId is admin', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'admin'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'home'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if routeId is frontStore', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'frontStore'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'home'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return true if routeId is different', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'home'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'category'\n        }\n      )\n    ).toEqual(true);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middleware/tests/unit/middleware.scanForMiddlewareFunctions.test.js",
    "content": "import { noDublicateId } from '../../noDuplicateId.js';\n\ndescribe('Test scanForMiddlewareFunctions function', () => {\n  it('It should return false if routed middlewareID is existed', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'home'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'home'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if admin middlewareID is existed', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'admin'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'admin'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if frontStore middlewareID is existed', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: null\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: null\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if routeId is null', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: null\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'home'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if routeId is admin', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'admin'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'home'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return false if routeId is frontStore', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'frontStore'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'home'\n        }\n      )\n    ).toEqual(false);\n  });\n\n  it('It should return true if routeId is different', () => {\n    expect(\n      noDublicateId(\n        [\n          {\n            id: 'routeOne',\n            routeId: 'home'\n          }\n        ],\n        {\n          id: 'routeOne',\n          routeId: 'category'\n        }\n      )\n    ).toEqual(true);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/middlewares/bodyJson.ts",
    "content": "import bodyParser from 'body-parser';\nimport { EvershopRequest } from '../../types/request.js';\nimport { EvershopResponse } from '../../types/response.js';\n\nexport default (request: EvershopRequest, response: EvershopResponse, next) => {\n  bodyParser.json()(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middlewares/multerNone.ts",
    "content": "import multer from 'multer';\n\nconst upload = multer();\n\nexport default (request, response, next) => {\n  upload.none()(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middlewares/publicStatic.ts",
    "content": "import fs from 'fs/promises';\nimport { join } from 'path';\nimport staticMiddleware from 'serve-static';\nimport { EvershopRequest } from '../../types/request.js';\nimport { EvershopResponse } from '../../types/response.js';\nimport { CONSTANTS } from '../helpers.js';\n\nexport default async function publicStatic(\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) {\n  // Get the request path\n  const { path } = request;\n  try {\n    if (!path.includes('.')) {\n      throw new Error('No file extension');\n    }\n    // Asynchoronously check if the path is a file and exists in the public folder\n    const test = await fs.stat(join(CONSTANTS.ROOTPATH, 'public', path));\n    if (test.isFile()) {\n      // If it is a file, serve it\n      staticMiddleware(join(CONSTANTS.ROOTPATH, 'public'))(\n        request,\n        response,\n        next\n      );\n    }\n  } catch (e) {\n    // If the path is not a file or does not exist in the public folder, call next\n    next();\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/middlewares/static.ts",
    "content": "import { constants } from 'fs';\nimport { access, stat } from 'fs/promises';\nimport { join, normalize, extname } from 'path';\nimport staticMiddleware from 'serve-static';\nimport { EvershopRequest } from '../..//types/request.js';\nimport { EvershopResponse } from '../../types/response.js';\nimport { CONSTANTS } from '../helpers.js';\n\n// Define allowed file extensions (whitelist)\nconst ALLOWED_EXTENSIONS = [\n  // Images\n  '.jpg',\n  '.jpeg',\n  '.png',\n  '.gif',\n  '.svg',\n  '.webp',\n  '.avif',\n  '.ico',\n\n  // Styles\n  '.css',\n  '.scss',\n  '.less',\n\n  // Scripts\n  '.js',\n  '.jsx',\n  '.mjs',\n  '.ts',\n  '.tsx',\n\n  // Fonts\n  '.woff',\n  '.woff2',\n  '.eot',\n  '.ttf',\n  '.otf',\n\n  // Documents\n  '.pdf',\n\n  // Data\n  '.json',\n  '.map'\n];\n\n/**\n * Checks if a path exists and is accessible\n * @param {string} path - Path to check\n * @returns {Promise<boolean>} True if the path exists and is accessible\n */\nconst pathExists = async (path: string): Promise<boolean> => {\n  try {\n    await access(path, constants.F_OK);\n    return true;\n  } catch {\n    return false;\n  }\n};\n\n/**\n * Validates if the path is a valid file and its extension is allowed\n * @param {string} fullPath - Full path to the file\n * @returns {Promise<boolean>} True if the path is valid and allowed\n */\nconst isValidFile = async (fullPath: string): Promise<boolean> => {\n  try {\n    // Check if file exists and is a file (not a directory)\n    const stats = await stat(fullPath);\n    if (!stats.isFile()) {\n      return false;\n    }\n\n    // Check if the file extension is in the allowed list\n    const ext = extname(fullPath).toLowerCase();\n    return ALLOWED_EXTENSIONS.includes(ext);\n  } catch (error) {\n    // File doesn't exist or other error\n    return false;\n  }\n};\n\nconst staticMiddlewareOptions = {\n  maxAge: '1y',\n  immutable: true,\n  setHeaders: (res) => {\n    res.setHeader('X-Content-Type-Options', 'nosniff');\n  }\n};\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  let path;\n  if (request.isAdmin === true) {\n    path = normalize(request.originalUrl.replace('/admin/assets/', ''));\n  } else {\n    path = normalize(request.originalUrl.replace('/assets/', ''));\n  }\n\n  // Prevent path traversal attacks\n  if (path.includes('..')) {\n    return response.status(403).send('Forbidden');\n  }\n\n  if (request.isAdmin === true) {\n    request.originalUrl = request.originalUrl.replace('/admin/assets', '');\n    request.url = request.originalUrl.replace('/admin/assets', '');\n  } else {\n    request.originalUrl = request.originalUrl.replace('/assets', '');\n    request.url = request.originalUrl.replace('/assets', '');\n  }\n\n  if (path.endsWith('/')) {\n    return response.status(404).send('Not Found');\n  }\n\n  // Check build path\n  const buildPath = join(CONSTANTS.ROOTPATH, '.evershop/build', path);\n  if ((await pathExists(buildPath)) && (await isValidFile(buildPath))) {\n    return staticMiddleware(\n      join(CONSTANTS.ROOTPATH, '.evershop/build'),\n      staticMiddlewareOptions\n    )(request, response, next);\n  }\n\n  // Check media path\n  const mediaPath = join(CONSTANTS.MEDIAPATH, path);\n  if ((await pathExists(mediaPath)) && (await isValidFile(mediaPath))) {\n    return staticMiddleware(CONSTANTS.MEDIAPATH, staticMiddlewareOptions)(\n      request,\n      response,\n      next\n    );\n  }\n\n  // Check public path\n  const publicPath = join(CONSTANTS.ROOTPATH, 'public', path);\n  if ((await pathExists(publicPath)) && (await isValidFile(publicPath))) {\n    return staticMiddleware(\n      join(CONSTANTS.ROOTPATH, 'public'),\n      staticMiddlewareOptions\n    )(request, response, next);\n  }\n\n  // If none of the above conditions are met\n  return response.status(404).send('Not Found');\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/middlewares/themePublicStatic.ts",
    "content": "import fs from 'fs/promises';\nimport { join } from 'path';\nimport staticMiddleware from 'serve-static';\nimport { EvershopRequest } from '../../types/request.js';\nimport { EvershopResponse } from '../../types/response.js';\nimport { getEnabledTheme } from '../util/getEnabledTheme.js';\n\nexport default async function themePublicStatic(\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) {\n  // Get the request path\n  const { path } = request;\n  const theme = getEnabledTheme();\n  if (!theme) {\n    next();\n  } else {\n    try {\n      if (!path.includes('.')) {\n        throw new Error('No file extension');\n      }\n      // Asynchoronously check if the path is a file and exists in the public folder\n      const test = await fs.stat(join(theme.path, 'public', path));\n      if (test.isFile()) {\n        // If it is a file, serve it\n        staticMiddleware(join(theme.path, 'public'))(request, response, next);\n      }\n    } catch (e) {\n      // If the path is not a file or does not exist in the public folder, call next\n      next();\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/pathToRegexp.js",
    "content": "const isarray = (e) => Array.isArray(e);\n\nfunction parse(e, t) {\n  for (\n    var r, n = [], o = 0, a = 0, i = '', p = (t && t.delimiter) || '/';\n    (r = PATH_REGEXP.exec(e)) != null;\n\n  ) {\n    const s = r[0];\n    const c = r[1];\n    const u = r.index;\n    if (((i += e.slice(a, u)), (a = u + s.length), c)) i += c[1];\n    else {\n      const l = e[a];\n      const g = r[2];\n      const f = r[3];\n      const x = r[4];\n      const h = r[5];\n      const d = r[6];\n      const m = r[7];\n      i && (n.push(i), (i = ''));\n      const y = g != null && l != null && l !== g;\n      const R = d === '+' || d === '*';\n      const T = d === '?' || d === '*';\n      const E = r[2] || p;\n      const v = x || h;\n      n.push({\n        name: f || o++,\n        prefix: g || '',\n        delimiter: E,\n        optional: T,\n        repeat: R,\n        partial: y,\n        asterisk: !!m,\n        pattern: v ? escapeGroup(v) : m ? '.*' : `[^${escapeString(E)}]+?`\n      });\n    }\n  }\n  return a < e.length && (i += e.substr(a)), i && n.push(i), n;\n}\nfunction compile(e, t) {\n  return tokensToFunction(parse(e, t));\n}\nfunction encodeURIComponentPretty(e) {\n  return encodeURI(e).replace(\n    /[\\/?#]/g,\n    (e) => `%${e.charCodeAt(0).toString(16).toUpperCase()}`\n  );\n}\nfunction encodeAsterisk(e) {\n  return encodeURI(e).replace(\n    /[?#]/g,\n    (e) => `%${e.charCodeAt(0).toString(16).toUpperCase()}`\n  );\n}\nfunction tokensToFunction(e) {\n  for (var t = new Array(e.length), r = 0; r < e.length; r++)\n    typeof e[r] === 'object' && (t[r] = new RegExp(`^(?:${e[r].pattern})$`));\n  return function (r, n) {\n    for (\n      var o = '',\n        a = r || {},\n        i = n || {},\n        p = i.pretty ? encodeURIComponentPretty : encodeURIComponent,\n        s = 0;\n      s < e.length;\n      s++\n    ) {\n      const c = e[s];\n      if (typeof c !== 'string') {\n        var u;\n        const l = a[c.name];\n        if (l == null) {\n          if (c.optional) {\n            c.partial && (o += c.prefix);\n            continue;\n          }\n          throw new TypeError(`Expected \"${c.name}\" to be defined`);\n        }\n        if (isarray(l)) {\n          if (!c.repeat)\n            throw new TypeError(\n              `Expected \"${\n                c.name\n              }\" to not repeat, but received \\`${JSON.stringify(l)}\\``\n            );\n          if (l.length === 0) {\n            if (c.optional) continue;\n            throw new TypeError(`Expected \"${c.name}\" to not be empty`);\n          }\n          for (let g = 0; g < l.length; g++) {\n            if (((u = p(l[g])), !t[s].test(u)))\n              throw new TypeError(\n                `Expected all \"${c.name}\" to match \"${\n                  c.pattern\n                }\", but received \\`${JSON.stringify(u)}\\``\n              );\n            o += (g === 0 ? c.prefix : c.delimiter) + u;\n          }\n        } else {\n          if (((u = c.asterisk ? encodeAsterisk(l) : p(l)), !t[s].test(u)))\n            throw new TypeError(\n              `Expected \"${c.name}\" to match \"${c.pattern}\", but received \"${u}\"`\n            );\n          o += c.prefix + u;\n        }\n      } else o += c;\n    }\n    return o;\n  };\n}\nfunction escapeString(e) {\n  return e.replace(/([.+*?=^!:${}()[\\]|\\/\\\\])/g, '\\\\$1');\n}\nfunction escapeGroup(e) {\n  return e.replace(/([=!:$\\/()])/g, '\\\\$1');\n}\nfunction attachKeys(e, t) {\n  return (e.keys = t), e;\n}\nfunction flags(e) {\n  return e.sensitive ? '' : 'i';\n}\nfunction regexpToRegexp(e, t) {\n  const r = e.source.match(/\\((?!\\?)/g);\n  if (r) {\n    for (let n = 0; n < r.length; n++) {\n      t.push({\n        name: n,\n        prefix: null,\n        delimiter: null,\n        optional: !1,\n        repeat: !1,\n        partial: !1,\n        asterisk: !1,\n        pattern: null\n      });\n    }\n  }\n  return attachKeys(e, t);\n}\nfunction arrayToRegexp(e, t, r) {\n  for (var n = [], o = 0; o < e.length; o++)\n    n.push(pathToRegexp(e[o], t, r).source);\n  return attachKeys(new RegExp(`(?:${n.join('|')})`, flags(r)), t);\n}\nfunction stringToRegexp(e, t, r) {\n  return tokensToRegExp(parse(e, r), t, r);\n}\nfunction tokensToRegExp(e, t, r) {\n  isarray(t) || ((r = t || r), (t = [])), (r = r || {});\n  for (var n = r.strict, o = !1 !== r.end, a = '', i = 0; i < e.length; i++) {\n    const p = e[i];\n    if (typeof p === 'string') a += escapeString(p);\n    else {\n      const s = escapeString(p.prefix);\n      let c = `(?:${p.pattern})`;\n      t.push(p),\n        p.repeat && (c += `(?:${s}${c})*`),\n        (c = p.optional\n          ? p.partial\n            ? `${s}(${c})?`\n            : `(?:${s}(${c}))?`\n          : `${s}(${c})`),\n        (a += c);\n    }\n  }\n  const u = escapeString(r.delimiter || '/');\n  const l = a.slice(-u.length) === u;\n  return (\n    n || (a = `${l ? a.slice(0, -u.length) : a}(?:${u}(?=$))?`),\n    (a += o ? '$' : n && l ? '' : `(?=${u}|$)`),\n    attachKeys(new RegExp(`^${a}`, flags(r)), t)\n  );\n}\nfunction pathToRegexp(e, t, r) {\n  return (\n    isarray(t) || ((r = t || r), (t = [])),\n    (r = r || {}),\n    e instanceof RegExp\n      ? regexpToRegexp(e, t)\n      : isarray(e)\n      ? arrayToRegexp(e, t, r)\n      : stringToRegexp(e, t, r)\n  );\n}\n\nvar PATH_REGEXP = new RegExp(\n  [\n    '(\\\\\\\\.)',\n    '([\\\\/.])?(?:(?:\\\\:(\\\\w+)(?:\\\\(((?:\\\\\\\\.|[^\\\\\\\\()])+)\\\\))?|\\\\(((?:\\\\\\\\.|[^\\\\\\\\()])+)\\\\))([+*?])?|(\\\\*))'\n  ].join('|'),\n  'g'\n);\n\nexport { pathToRegexp, parse, compile, tokensToFunction, tokensToRegExp };\n"
  },
  {
    "path": "packages/evershop/src/lib/postgres/connection.ts",
    "content": "import fs from 'fs';\nimport { PoolClient } from '@evershop/postgres-query-builder';\nimport { Pool } from 'pg';\nimport type { PoolConfig } from 'pg';\nimport { getConfig } from '../util/getConfig.js';\n\n// Use env for the database connection, maintain the backward compatibility\nconst connectionSetting: PoolConfig = {\n  host: process.env.DB_HOST,\n  port: process.env.DB_PORT as unknown as number,\n  user: process.env.DB_USER,\n  password: process.env.DB_PASSWORD,\n  database: process.env.DB_NAME,\n  max: 20\n};\n\n// Support SSL\nconst sslMode = process.env.DB_SSLMODE;\nswitch (sslMode) {\n  case 'disable': {\n    connectionSetting.ssl = false;\n    break;\n  }\n  case 'require':\n  case 'prefer':\n  case 'verify-ca':\n  case 'verify-full': {\n    const ssl: PoolConfig['ssl'] = {\n      rejectUnauthorized: true\n    };\n    const ca = process.env.DB_SSLROOTCERT;\n    if (ca) {\n      ssl.ca = fs.readFileSync(ca).toString();\n    }\n    const cert = process.env.DB_SSLCERT;\n    if (cert) {\n      ssl.cert = fs.readFileSync(cert).toString();\n    }\n    const key = process.env.DB_SSLKEY;\n    if (key) {\n      ssl.key = fs.readFileSync(key).toString();\n    }\n    connectionSetting.ssl = ssl;\n    break;\n  }\n  case 'no-verify': {\n    connectionSetting.ssl = {\n      rejectUnauthorized: false\n    };\n    break;\n  }\n  default: {\n    connectionSetting.ssl = false;\n    break;\n  }\n}\n\nconst pool = new Pool(connectionSetting);\n// Set the timezone\npool.on('connect', (client) => {\n  const timeZone = getConfig('shop.timezone', 'UTC');\n  client.query(`SET TIMEZONE TO \"${timeZone}\";`);\n});\n\nasync function getConnection(): Promise<PoolClient> {\n  return await pool.connect();\n}\n\nexport { pool, getConnection };\n"
  },
  {
    "path": "packages/evershop/src/lib/response/render.ts",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\nimport jsesc from 'jsesc';\nimport { getNotifications } from '../../modules/base/services/notifications.js';\nimport { getPageMetaInfo } from '../../modules/cms/services/pageMetaInfo.js';\nimport { Config } from '../../types/appContext.js';\nimport { EvershopRequest } from '../../types/request.js';\nimport { EvershopResponse } from '../../types/response.js';\nimport { error } from '../log/logger.js';\nimport { get } from '../util/get.js';\nimport { getConfig } from '../util/getConfig.js';\nimport isProductionMode from '../util/isProductionMode.js';\nimport { processPreloadImages } from '../util/preloadScan.js';\nimport { getValueSync } from '../util/registry.js';\nimport { getRouteBuildPath } from '../webpack/getRouteBuildPath.js';\n\nfunction normalizeAssets(assets) {\n  if (typeof assets === 'object' && !Array.isArray(assets) && assets !== null) {\n    return Object.values(assets);\n  }\n\n  return Array.isArray(assets) ? assets : [assets];\n}\n\nfunction buildContextData(\n  request: EvershopRequest,\n  response: EvershopResponse\n) {\n  const pageMeta = getPageMetaInfo(request);\n  const appConfig = getValueSync<Config>(\n    'appConfig',\n    {\n      tax: {\n        priceIncludingTax: getConfig('pricing.tax.price_including_tax', false)\n      },\n      catalog: {\n        imageDimensions: {\n          width: getConfig('catalog.product.image.width', 1200),\n          height: getConfig('catalog.product.image.height', 1200)\n        }\n      },\n      pageMeta: pageMeta\n    },\n    { request, response },\n    (value) => value && typeof value === 'object' && !Array.isArray(value)\n  );\n  const config = Object.assign({}, appConfig, { pageMeta });\n  const contextValue = {\n    graphqlResponse: get(response, 'locals.graphqlResponse', {}),\n    config: config,\n    propsMap: get(response, 'locals.propsMap', {}),\n    widgets: get(response, 'locals.widgets', []),\n    notifications: getNotifications(request)\n  };\n  return contextValue;\n}\n\nfunction renderDevelopment(\n  request: EvershopRequest,\n  response: EvershopResponse\n) {\n  const route = request.currentRoute;\n  const classes = route.isAdmin\n    ? `admin ${route.id}`\n    : `frontStore ${route.id}`;\n  const language = getConfig('shop.language', 'en');\n  if (!route) {\n    // In testing mode, we do not have devMiddleware\n    response.send(`\n            <html>\n              <head>\n                <title>Sample Html Response</title>\n                <script>Sample Html Response</script>\n              </head>\n              <body>\n              </body>\n            </html>\n            `);\n    return;\n  }\n  const contextValue = buildContextData(request, response);\n  const safeContextValue = jsesc(contextValue, {\n    json: true,\n    isScriptContext: true\n  });\n  const langCode = request.currentRoute?.isAdmin ? 'en' : language;\n  const scriptPath = route.isAdmin ? '/backend/admin-main.js' : '/main.js';\n  response.send(`\n            <!doctype html><html lang=\"${langCode}\">\n                <head>\n                  <script>var eContext = ${safeContextValue}</script>\n                </head>\n                <body class=\"${classes}\">\n                <div id=\"app\"></div>\n                 <script defer src=\"${scriptPath}\"></script>\n                </body >\n            </html >\n  `);\n}\n\nfunction renderProduction(request, response) {\n  const language = getConfig('shop.language', 'en');\n  const route = request.currentRoute;\n  const langCode = route.isAdmin === true ? 'en' : language;\n  const serverIndexPath = path.resolve(\n    getRouteBuildPath(route),\n    'server',\n    'index.js'\n  );\n  const assetsPath = path.resolve(\n    getRouteBuildPath(route),\n    'client',\n    'index.json'\n  );\n  const assets = JSON.parse(fs.readFileSync(assetsPath, 'utf8'));\n  const cssList = [] as string[];\n  for (let i = 0; i < assets.css.length; i++) {\n    const cssFilePath = path.resolve(\n      getRouteBuildPath(route),\n      'client',\n      path.basename(assets.css[i])\n    );\n    if (fs.existsSync(cssFilePath)) {\n      const cssContent = fs.readFileSync(cssFilePath, 'utf8');\n      // Inline the css content to reduce the number of requests\n      cssList.push(cssContent);\n    }\n  }\n  const contextValue = buildContextData(request, response);\n  const safeContextValue = jsesc(contextValue, {\n    json: true,\n    isScriptContext: true\n  });\n  import(pathToFileURL(serverIndexPath).toString())\n    .then((module) => {\n      const source = processPreloadImages(\n        module.default(\n          request.currentRoute,\n          assets.js,\n          cssList,\n          safeContextValue,\n          langCode\n        )\n      );\n      response.send(source);\n    })\n    .catch((e) => {\n      error(e);\n    });\n}\n\nexport function render(request, response) {\n  if (isProductionMode()) {\n    renderProduction(request, response);\n  } else {\n    renderDevelopment(request, response);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/router/Router.js",
    "content": "import { sortRoutes } from './sortRoutes.js';\n\nclass Router {\n  constructor() {\n    this.routes = [];\n  }\n\n  getFrontStoreRoutes() {\n    return this.routes.filter((r) => r.isAdmin === false);\n  }\n\n  getAdminRoutes() {\n    return this.routes.filter((r) => r.isAdmin === true);\n  }\n\n  getRoutes() {\n    return sortRoutes(this.routes);\n  }\n\n  addRoute(route) {\n    const r = this.routes.find((rt) => rt.id === route.id);\n    if (r !== undefined) {\n      Object.assign(r, route);\n    } else {\n      this.routes.push(route);\n    }\n  }\n\n  hasRoute(id) {\n    return this.routes.some((r) => r.id === id);\n  }\n\n  deleteRoute(id) {\n    this.routes = this.routes.filter((r) => r.id !== id);\n  }\n\n  empty() {\n    this.routes = [];\n  }\n}\n\nconst router = new Router();\nexport const addRoute = (route) => router.addRoute(route);\nexport const getFrontStoreRoutes = () => router.getFrontStoreRoutes();\nexport const getAdminRoutes = () => router.getAdminRoutes();\nexport const getRoutes = () => router.getRoutes();\nexport const hasRoute = (id) => router.hasRoute(id);\nexport const deleteRoute = (id) => router.deleteRoute(id);\nexport const getRoute = (id) => router.getRoutes().find((r) => r.id === id);\nexport const empty = () => router.empty();\n"
  },
  {
    "path": "packages/evershop/src/lib/router/buildAbsoluteUrl.ts",
    "content": "import { normalizePort } from '../../bin/lib/normalizePort.js';\nimport { getBaseUrl } from '../../lib/util/getBaseUrl.js';\nimport { buildUrl } from './buildUrl.js';\n\nconst port = normalizePort();\n\n/**\n * This function take a route ID, list of params and return the absolute url\n *\n * @param   {string}  routeId\n * @param   {object}  params   Key-Pair value of route params\n *\n * @return  {string} The Url\n */\nexport const buildAbsoluteUrl = (\n  routeId: string,\n  params: Record<string, any> = {}\n) => {\n  const url = buildUrl(routeId, params).replace(/^\\/|\\/$/g, '');\n  const homeUrl = getBaseUrl();\n  return `${homeUrl}/${url}`;\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/router/buildUrl.ts",
    "content": "import { compile } from '../pathToRegexp.js';\nimport { getRoutes } from './Router.js';\n\n/**\n * This function take a route ID, list of params and return the url\n *\n * @param   {string}  routeId\n * @param   {object}  params   Key-Pair value of route params\n * @param   {object}  query    Key-Pair value of query parameters\n *\n * @return  {string} The Url\n */\nexport const buildUrl = (\n  routeId: string,\n  params: Record<string, any> = {},\n  query: Record<string, any> = {}\n): string => {\n  const routes = getRoutes();\n  const route = routes.find((r) => r.id === routeId);\n  if (route === undefined) {\n    throw new Error(`Route ${routeId} is not existed`);\n  }\n\n  const toPath = compile(route.path);\n  try {\n    const url = toPath(params);\n\n    if (Object.keys(query).length > 0) {\n      const queryPairs: string[] = [];\n\n      for (const [key, value] of Object.entries(query)) {\n        if (Array.isArray(value)) {\n          value.forEach((item) => {\n            queryPairs.push(\n              `${encodeURIComponent(key)}[]=${encodeURIComponent(String(item))}`\n            );\n          });\n        } else if (value !== null && value !== undefined) {\n          // Handle simple values\n          queryPairs.push(\n            `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`\n          );\n        }\n      }\n\n      if (queryPairs.length > 0) {\n        return `${url}?${queryPairs.join('&')}`;\n      }\n    }\n\n    return url;\n  } catch (e) {\n    throw new Error(`Could not build url for route ${routeId}. ${e.message}`);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/router/index.ts",
    "content": "export { buildUrl } from './buildUrl.js';\nexport { buildAbsoluteUrl } from './buildAbsoluteUrl.js';\n"
  },
  {
    "path": "packages/evershop/src/lib/router/loadModuleRoutes.js",
    "content": "import { existsSync } from 'fs';\nimport path from 'path';\nimport { registerAdminRoute } from './registerAdminRoute.js';\nimport { registerFrontStoreRoute } from './registerFrontStoreRoute.js';\nimport { scanForRoutes } from './scanForRoutes.js';\n\nexport const loadModuleRoutes = (modulePath) => {\n  // Check for routes\n  if (existsSync(path.resolve(modulePath, 'pages', 'admin'))) {\n    const adminControllerRoutes = scanForRoutes(\n      path.resolve(modulePath, 'pages', 'admin'),\n      true,\n      false\n    );\n    adminControllerRoutes.forEach((route) => {\n      registerAdminRoute(\n        route.id,\n        route.method,\n        route.path,\n        route.name,\n        route.isApi,\n        route.folder\n      );\n    });\n  }\n\n  if (existsSync(path.resolve(modulePath, 'pages', 'frontStore'))) {\n    const frontStoreControllerRoutes = scanForRoutes(\n      path.resolve(modulePath, 'pages', 'frontStore'),\n      false,\n      false\n    );\n    frontStoreControllerRoutes.forEach((route) => {\n      registerFrontStoreRoute(\n        route.id,\n        route.method,\n        route.path,\n        route.name,\n        route.isApi,\n        route.folder\n      );\n    });\n  }\n\n  // Wiwth API, we do not have admin and frontStore folders\n  if (existsSync(path.resolve(modulePath, 'api'))) {\n    const routes = scanForRoutes(path.resolve(modulePath, 'api'), false, true);\n    routes.forEach((route) => {\n      registerFrontStoreRoute(\n        route.id,\n        route.method,\n        route.path,\n        route.name,\n        route.isApi,\n        route.folder,\n        route.payloadSchema,\n        route.access\n      );\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/router/registerAdminRoute.js",
    "content": "import { addRoute } from './Router.js';\n\n/**\n * Register an admin route\n *\n * @param   {string}  id      Id of route, this must be unique\n * @param   {string|array} method  HTTP method, can be string like \"GET\", array like [\"GET\", \"POST\"]\n * @param   {string}  path    The path of route\n *\n */\nexport function registerAdminRoute(\n  id,\n  method,\n  path,\n  name,\n  isApi = false,\n  folder = ''\n) {\n  // const route = validateRoute(id, method, path);\n  const route = {\n    id: String(id),\n    method,\n    path\n  };\n  route.isAdmin = true;\n  route.isApi = isApi;\n  route.path = route.path === '/' ? '/admin' : `/admin${path}`;\n  route.folder = folder;\n  route.name = name;\n  addRoute(route);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/router/registerFrontStoreRoute.js",
    "content": "import { addRoute } from './Router.js';\n\n/**\n * Register a frontStore route\n *\n * @param   {string}  id      Id of route, this must be unique\n * @param   {string|array} method  HTTP method, can be string like \"GET\", array like [\"GET\", \"POST\"]\n * @param   {string}  path    The path of route\n *\n */\nexport function registerFrontStoreRoute(\n  id,\n  method,\n  path,\n  name,\n  isApi = false,\n  folder = '',\n  payloadSchema = null,\n  access = 'private'\n) {\n  // const route = validateRoute(id, method, path);\n  const route = {\n    id: String(id),\n    method,\n    path,\n    payloadSchema,\n    access\n  };\n  route.isAdmin = false;\n  route.isApi = isApi;\n  route.folder = folder;\n  route.name = name;\n  addRoute(route);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/router/scanForRoutes.js",
    "content": "import { existsSync, readdirSync } from 'fs';\nimport { basename, dirname, join } from 'path';\nimport { jsonParse } from '../util/jsonParse.js';\n\nfunction startWith(str, prefix) {\n  return str.slice(0, prefix.length) === prefix;\n}\n\nfunction validateRoute(methods, path, routePath) {\n  if (methods.length === 0) {\n    throw new Error(\n      `Method is required. Please check the route defined at ${routePath}`\n    );\n  }\n  const check = methods.find(\n    (m) =>\n      ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'].includes(\n        m\n      ) === false\n  );\n  if (check !== undefined) {\n    throw new Error(\n      `Method ${check} is invalid. Please check the route defined at ${routePath}`\n    );\n  }\n  if (startWith(path, '/') === false) {\n    throw new Error(\n      `Path ${path} must be started with '/'. Please check the route defined at ${routePath}`\n    );\n  }\n\n  return true;\n}\n\nexport function parseRoute(jsonPath, isAdmin = false, isApi = false) {\n  const routeId = basename(dirname(jsonPath));\n  if (/^[a-zA-Z]+$/.test(routeId) === false) {\n    throw new Error(\n      `Route folder ${routeId} is invalid. It must contains only characters.`\n    );\n  }\n  const routeJson = jsonParse(jsonPath);\n  const methods = routeJson?.methods.map((m) => m.toUpperCase()) || [];\n  let routePath = routeJson?.path;\n  if (validateRoute(methods, routePath, routePath) === true) {\n    if (isApi === true) {\n      routePath = `/api${routePath}`;\n    }\n    // Load the validation schema\n    let payloadSchema;\n    if (existsSync(join(dirname(jsonPath), 'payloadSchema.json'))) {\n      payloadSchema = jsonParse(join(dirname(jsonPath), 'payloadSchema.json'));\n    }\n\n    return {\n      id: routeId,\n      name: routeJson?.name || routeId,\n      method: methods,\n      path: routePath,\n      isAdmin,\n      isApi,\n      folder: dirname(jsonPath),\n      payloadSchema,\n      access: routeJson?.access || 'private'\n    };\n  } else {\n    return null;\n  }\n}\n\n/**\n * Scan for routes base on module path.\n */\nexport function scanForRoutes(path, isAdmin, isApi) {\n  const scanedRoutes = readdirSync(path, { withFileTypes: true })\n    .filter((dirent) => dirent.isDirectory())\n    .map((dirent) => dirent.name);\n  return scanedRoutes\n    .map((r) => {\n      if (/^[A-Za-z.]+$/.test(r) === true) {\n        if (existsSync(join(path, r, 'route.json'))) {\n          return (\n            parseRoute(join(path, r, 'route.json'), isAdmin, isApi) || false\n          );\n        } else {\n          return false;\n        }\n      } else {\n        return false;\n      }\n    })\n    .filter((e) => e !== false);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/router/sortRoutes.js",
    "content": "export function sortRoutes(routes) {\n  return routes.sort((a, b) => {\n    const aSpecificity = calculateRouteSpecificity(a.path);\n    const bSpecificity = calculateRouteSpecificity(b.path);\n\n    return bSpecificity - aSpecificity;\n  });\n}\n\nfunction calculateRouteSpecificity(path) {\n  let specificity = 0;\n\n  if (!path.includes(':')) {\n    specificity += 100;\n  }\n\n  specificity += path\n    .split('/')\n    .filter((segment) => !segment.startsWith(':')).length;\n\n  specificity -= path\n    .split('/')\n    .filter((segment) => segment.startsWith(':')).length;\n\n  if (path.includes('(') && path.includes(')')) {\n    specificity -= 5;\n  }\n\n  return specificity;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/router/tests/unit/a/invalidMethod/routeOne/route.json",
    "content": "{\n  \"methods\": [\"GET\", \"INVALID\"],\n  \"path\": \"/a/:url_key\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/router/tests/unit/a/invalidPath/routeTwo/route.json",
    "content": "{\n  \"methods\": [\"GET\", \"POST\"],\n  \"path\": \"invalidPath\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/router/tests/unit/b/routeOne/route.json",
    "content": "{\n  \"methods\": [\"GET\", \"POST\"],\n  \"path\": \"/a/:url_key\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/router/tests/unit/b/routeThree/route.json",
    "content": "{\n  \"methods\": [\"GET\", \"PUT\"],\n  \"path\": \"/b/:url_key\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/router/tests/unit/b/routeTwo/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/a/:url_key\"\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/router/tests/unit/unit.scanForRoutes.test.js",
    "content": "import { scanForRoutes } from '../../scanForRoutes.js';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ndescribe('Test scanForRoutes', () => {\n  it('It should thrown an exception if path is not valid', () => {\n    expect(() =>\n      scanForRoutes(path.resolve(__dirname, 'a/invalidPath'), true, false)\n    ).toThrow(Error);\n  });\n\n  it('It should thrown an exception if methods are not valid', () => {\n    expect(() =>\n      scanForRoutes(path.resolve(__dirname, 'a/invalidMethod'), true, false)\n    ).toThrow(Error);\n  });\n\n  it('It should return an array of routes', () => {\n    const routes = scanForRoutes(path.resolve(__dirname, 'b'), true, false);\n    expect(Array.isArray(routes)).toBe(true);\n    expect(routes.length).toBe(3);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/router/tests/unit/unit.validateRoute.test.js",
    "content": "import path from 'path';\nimport { scanForRoutes } from '../../scanForRoutes.js';\nimport { registerAdminRoute } from '../../registerAdminRoute.js';\nimport { validateRoute } from '../../validateRoute.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ndescribe('Test validateRoute', () => {\n  beforeAll(() => {\n    const routes = scanForRoutes(path.resolve(__dirname, 'b'), true, false);\n    routes.forEach((route) => {\n      registerAdminRoute(\n        route.id,\n        route.method,\n        route.path,\n        route.name,\n        route.isApi\n      );\n    });\n  });\n\n  it('It should thrown an exception if route is already existed', () => {\n    expect(() => validateRoute('routeOne', ['GET'], '/')).toThrow(Error);\n  });\n\n  it('It should return a route object if id is valid', () => {\n    const route = validateRoute('newRoute', ['GET'], '/');\n    expect(route.id).toBeTruthy();\n    expect(route.method).toBeTruthy();\n    expect(route.path).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/router/validateRoute.js",
    "content": "import { getRoutes } from './Router.js';\n\n/**\n * This function validates if the id of a route already exists or not.\n * It returns a route object\n *\n * @param   {string}  id      Route ID\n * @param {string||array} method HTTP method, can be string like \"GET\", array like [\"GET\", \"POST\"]\n * @param   {string}  path    The route path\n *\n * @return  {object}          The Route object\n */\nexport const validateRoute = (id, method, path) => {\n  const routes = getRoutes();\n  if (routes.find((r) => r.id === id) !== undefined) {\n    throw new Error(`Route with ID ${String(id)} already exists`);\n  }\n  return {\n    id: String(id),\n    method,\n    path\n  };\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/util/assign.js",
    "content": "/**\n * This function take 2 objects and merge the second one to the first one\n *\n * @param   {object}  object  The main object\n * @param   {object}  data    The object to be merged\n *\n */\nexport function assign(object, data) {\n  if (typeof object !== 'object' || object === null) {\n    throw new Error('`object` must be an object');\n  }\n\n  if (typeof data !== 'object' || data === null) {\n    throw new Error('`data` must be an object');\n  }\n\n  Object.keys(data).forEach((key) => {\n    if (\n      data[key] &&\n      data[key].constructor === Array &&\n      object[key] &&\n      object[key].constructor === Array\n    ) {\n      object[key] = object[key].concat(data[key]);\n    } else if (\n      typeof object[key] !== 'object' ||\n      typeof data[key] !== 'object' ||\n      object[key] === null\n    ) {\n      object[key] = data[key];\n    } else {\n      assign(object[key], data[key]);\n    }\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/buildFilterFromUrl.ts",
    "content": "export interface FilterInput {\n  key: string;\n  operation:\n    | 'eq'\n    | 'neq'\n    | 'gt'\n    | 'gteq'\n    | 'lt'\n    | 'lteq'\n    | 'like'\n    | 'nlike'\n    | 'in'\n    | 'nin';\n  value: string;\n}\n\n/**\n * Build filter inputs from URL parameters\n *\n * Supports both complete URLs and relative URL paths (like request.originalUrl).\n * For relative URLs, a dummy domain is added to make them valid for URL parsing.\n *\n * @param url - Complete URL, relative URL path, or URL object\n * @returns Array of FilterInput objects\n *\n * @example\n * // Complete URL\n * buildFilterFromUrl('https://example.com/products?color=red&size=large')\n *\n * // Relative URL (from request.originalUrl)\n * buildFilterFromUrl('/products?color=red&size=large')\n *\n * // Multi-value filters\n * buildFilterFromUrl('/products?color=red&color=blue') // Creates 'in' operation\n *\n * // Complex filters\n * buildFilterFromUrl('/products?price[operation]=range&price[value]=10,100')\n */\nexport const buildFilterFromUrl = (url: string | URL): FilterInput[] => {\n  let urlObj: URL;\n\n  if (typeof url === 'string') {\n    // Check if it's a relative URL (starts with / or doesn't contain ://)\n    if (url.startsWith('/') || !url.includes('://')) {\n      // Add dummy domain to make it a valid URL for URL constructor\n      urlObj = new URL(url, 'https://example.com');\n    } else {\n      // It's already a complete URL\n      urlObj = new URL(url);\n    }\n  } else {\n    // It's already a URL object\n    urlObj = url;\n  }\n\n  // Get the search parameters\n  const searchParams = urlObj.searchParams;\n\n  if (!searchParams) {\n    return [];\n  }\n\n  const filtersFromUrl: FilterInput[] = [];\n  const processedKeys = new Set<string>();\n\n  // Iterate through all search parameters\n  for (const [key, value] of searchParams.entries()) {\n    // Handle operation-based filters like price[operation]=range&price[value]=10,100\n    if (key.includes('[operation]')) {\n      const filterKey = key.replace('[operation]', '');\n      const filterValue = searchParams.get(`${filterKey}[value]`);\n\n      if (filterValue && isValidOperation(value)) {\n        filtersFromUrl.push({\n          key: filterKey,\n          operation: value as FilterInput['operation'],\n          value: filterValue\n        });\n        processedKeys.add(filterKey);\n      }\n    }\n    // Handle simple equality filters like color=red&size=large\n    // Also handle multi-value case like color=black&color=blue\n    else if (!key.includes('[value]') && !processedKeys.has(key)) {\n      const allValues = searchParams.getAll(key);\n\n      if (allValues.length > 1) {\n        // Multiple values for the same key, use 'in' operation\n        filtersFromUrl.push({\n          key,\n          operation: 'in',\n          value: allValues.join(',')\n        });\n      } else {\n        // Single value, use 'eq' operation\n        filtersFromUrl.push({\n          key,\n          operation: 'eq',\n          value\n        });\n      }\n      processedKeys.add(key);\n    }\n  }\n\n  return filtersFromUrl;\n};\n\n// Helper function to validate operation\nconst isValidOperation = (\n  operation: string\n): operation is FilterInput['operation'] => {\n  return [\n    'eq',\n    'neq',\n    'gt',\n    'gteq',\n    'lt',\n    'lteq',\n    'like',\n    'nlike',\n    'in',\n    'nin'\n  ].includes(operation);\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/util/camelCase.ts",
    "content": "export const camelCase = (object: Record<string, any>): Record<string, any> => {\n  // Throw error if the object is not an object\n  if (typeof object !== 'object' && object !== null) {\n    throw new Error('The object must be an object');\n  }\n  const newObject: Record<string, any> = {};\n  Object.keys(object).forEach((key) => {\n    // Convert the key to camelCase\n    const newKey = key.replace(/([-_][a-zA-Z0-9])/gi, ($1) =>\n      $1.toUpperCase().replace('-', '').replace('_', '')\n    );\n    // Add the new key to the new object\n    newObject[newKey] = object[key];\n  });\n\n  return newObject;\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/util/cn.ts",
    "content": "import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/defaultPaginationFilters.js",
    "content": "import { CONSTANTS } from '../helpers.js';\nimport { getConfig } from './getConfig.js';\n\nexport const defaultPaginationFilters = [\n  {\n    key: 'od',\n    operation: ['eq'],\n    callback: (query, operation, value, currentFilters) => {\n      if (['ASC', 'DESC', 'asc', 'desc'].includes(value)) {\n        query.orderDirection(value.toUpperCase());\n        currentFilters.push({\n          key: 'od',\n          operation,\n          value\n        });\n      }\n    }\n  },\n  {\n    key: 'page',\n    operation: ['eq'],\n    callback: (query, operation, value, currentFilters) => {\n      if (parseInt(value, 10) > 0) {\n        query.limit(\n          (parseInt(value, 10) - 1) * CONSTANTS.ADMIN_COLLECTION_SIZE,\n          CONSTANTS.ADMIN_COLLECTION_SIZE\n        );\n        currentFilters.push({\n          key: 'page',\n          operation,\n          value\n        });\n      } else {\n        query.limit(0, CONSTANTS.ADMIN_COLLECTION_SIZE);\n        currentFilters.push({\n          key: 'page',\n          operation,\n          value: 1\n        });\n      }\n    }\n  },\n  {\n    key: 'limit',\n    operation: ['eq'],\n    callback: (query, operation, value, currentFilters) => {\n      if (parseInt(value, 10) > 0) {\n        // Get the current page from the current filters\n        const page = currentFilters.find((f) => f.key === 'page');\n        if (page) {\n          query.limit(\n            (parseInt(page.value, 10) - 1) * parseInt(value, 10),\n            parseInt(value, 10)\n          );\n        } else {\n          query.limit(0, parseInt(value, 10));\n          currentFilters.push({\n            key: 'limit',\n            operation: 'eq',\n            value\n          });\n        }\n        currentFilters.push({\n          key: 'limit',\n          operation,\n          value\n        });\n      } else {\n        currentFilters.push({\n          key: 'limit',\n          operation,\n          value: CONSTANTS.ADMIN_COLLECTION_SIZE\n        });\n      }\n    }\n  },\n  {\n    key: '*',\n    operation: ['eq'],\n    callback: function (query, operation, value, currentFilters) {\n      const page = currentFilters.find((f) => f.key === 'page');\n      const limit = currentFilters.find((f) => f.key === 'limit');\n      const defaultPage = 1;\n      const defaultLimit = this.isAdmin\n        ? CONSTANTS.ADMIN_COLLECTION_SIZE\n        : getConfig('catalog.collectionPageSize', 12);\n      currentFilters.push({\n        key: 'page',\n        operation: 'eq',\n        value: defaultPage\n      });\n      if (!limit) {\n        currentFilters.push({\n          key: 'limit',\n          operation: 'eq',\n          value: defaultLimit\n        });\n      }\n      query.limit(\n        (parseInt(page?.value || defaultPage, 10) - 1) *\n          parseInt(limit?.value || defaultLimit, 10),\n        parseInt(limit?.value || defaultLimit, 10)\n      );\n    }\n  }\n];\n"
  },
  {
    "path": "packages/evershop/src/lib/util/events.ts",
    "content": "export const FORM_VALIDATED = 'FORM_VALIDATED';\nexport const FORM_SUBMIT = 'FORM_SUBMIT';\nexport const FORM_FIELD_UPDATED = 'FORM_FIELD_UPDATED';\n"
  },
  {
    "path": "packages/evershop/src/lib/util/filterOperationMap.ts",
    "content": "export enum SQLFilterOperation {\n  eq = '=',\n  neq = '<>',\n  gt = '>',\n  gteq = '>=',\n  lt = '<',\n  lteq = '<=',\n  like = 'ILIKE',\n  nlike = 'NOT ILIKE',\n  in = 'IN',\n  nin = 'NOT IN'\n}\n// Map the operation to the SQL operation\nexport const OPERATION_MAP: Record<string, SQLFilterOperation> = {\n  eq: SQLFilterOperation.eq,\n  neq: SQLFilterOperation.neq,\n  gt: SQLFilterOperation.gt,\n  gteq: SQLFilterOperation.gteq,\n  lt: SQLFilterOperation.lt,\n  lteq: SQLFilterOperation.lteq,\n  like: SQLFilterOperation.like,\n  nlike: SQLFilterOperation.nlike,\n  in: SQLFilterOperation.in,\n  nin: SQLFilterOperation.nin\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/util/formToJson.js",
    "content": "function update(data, keys, value) {\n  if (keys.length === 0) {\n    // Leaf node\n    return value;\n  }\n\n  let key = keys.shift();\n  if (!key) {\n    data = data || [];\n    if (Array.isArray(data)) {\n      key = data.length;\n    }\n  }\n\n  // Try converting key to a numeric value\n  const index = +key;\n  if (!Number.isNaN(index)) {\n    // We have a numeric index, make data a numeric array\n    // This will not work if this is a associative array\n    // with numeric keys\n    data = data || [];\n    key = index;\n  }\n\n  // If none of the above matched, we have an associative array\n  data = data || {};\n\n  const val = update(data[key], keys, value);\n  data[key] = val;\n\n  return data;\n}\n\nexport function serializeForm(formDataEntries, dataFilter) {\n  const data = Array.from(formDataEntries).reduce((data, [field, value]) => {\n    // eslint-disable-next-line prefer-const\n    let [_, prefix, keys] = field.match(/^([^\\[]+)((?:\\[[^\\]]*\\])*)/);\n\n    if (keys) {\n      keys = Array.from(keys.matchAll(/\\[([^\\]]*)\\]/g), (m) => m[1]);\n      value = update(data[prefix], keys, value);\n    }\n    data[prefix] = value;\n    return data;\n  }, {});\n\n  // Check if dataFilter is a function\n  if (typeof dataFilter === 'function') {\n    return dataFilter(data);\n  } else {\n    return data;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/get.ts",
    "content": "/**\n * Get the value base on the path\n *\n * @param   {object}  obj           The Data object\n * @param   {string}  path          The path of the property \"a.b.c\"\n * @param   {any}  defaultValue     The default value in case the path is not existed\n *\n * @return  {any}                   The value\n */\nexport function get<T = any, D = any>(\n  obj: Record<string, any> | null | undefined,\n  path: string,\n  defaultValue?: D\n): T | D {\n  const pathSplit = path.split('.');\n  let current: any = obj;\n\n  while (pathSplit.length) {\n    if (typeof current !== 'object' || current === null) {\n      return defaultValue as D;\n    }\n\n    const key = pathSplit.shift()!;\n    if (!(key in current)) {\n      return defaultValue as D;\n    }\n\n    current = current[key];\n  }\n\n  return current === undefined || current === null\n    ? (defaultValue as D)\n    : current;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/getBaseUrl.ts",
    "content": "import { normalizePort } from '../../bin/lib/normalizePort.js';\nimport { getConfig } from './getConfig.js';\n\nexport function getBaseUrl(): string {\n  const port = normalizePort();\n  const baseUrl = getConfig('shop.homeUrl', `http://localhost:${port}`);\n  return baseUrl.replace(/\\/+$/, ''); // Remove trailing slashes\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/getConfig.ts",
    "content": "import config from 'config';\n\ntype ConfigStructure = {\n  shop: {\n    language: string;\n    timezone: string;\n    currency: string;\n    weightUnit: string;\n    homeUrl: string;\n  };\n  system: {\n    file_storage: string;\n    admin_collection_size?: number;\n    upload_allowed_mime_types: string[];\n    theme?: string;\n    extensions: Array<{\n      name: string;\n      resolve: string;\n      enabled: boolean;\n    }>;\n    session: {\n      maxAge: number;\n      resave: boolean;\n      saveUninitialized: boolean;\n      cookieSecret: string;\n      cookieName: string;\n      adminCookieName: string;\n    };\n    notification_emails: {\n      from?: string;\n      order_confirmation?: {\n        enabled: boolean;\n        templatePath?: string | null;\n        [key: string]: unknown;\n      };\n      customer_welcome?: {\n        enabled: boolean;\n        templatePath?: string | null;\n        [key: string]: unknown;\n      };\n      reset_password?: {\n        enabled: boolean;\n        templatePath?: string | null;\n        [key: string]: unknown;\n      };\n    };\n    stripe?: {\n      secretKey?: string;\n      publishableKey?: string;\n      [key: string]: unknown;\n    };\n    paypal?: {\n      [key: string]: unknown;\n    };\n    cod?: {\n      status?: number;\n      [key: string]: unknown;\n    };\n  };\n  catalog: {\n    collectionPageSize: number;\n    product: {\n      image: {\n        width: number;\n        height: number;\n      };\n    };\n    showOutOfStockProduct: boolean;\n  };\n  checkout: {\n    showShippingNote: boolean;\n  };\n  pricing: {\n    rounding: string;\n    precision: number;\n    tax: {\n      rounding: string;\n      precision: number;\n      round_level: string;\n      price_including_tax: boolean;\n    };\n  };\n  themeConfig: {\n    logo: {\n      alt: string | undefined;\n      src: string | undefined;\n      width: number | undefined;\n      height: number | undefined;\n    };\n    headTags: {\n      links: any[];\n      metas: any[];\n      scripts: any[];\n      bases: any[];\n    };\n    copyRight: string;\n  };\n  oms: {\n    order: {\n      shipmentStatus: Record<\n        string,\n        {\n          name: string;\n          badge: string;\n          progress?: string;\n          isDefault?: boolean;\n          isCancelable?: boolean;\n        }\n      >;\n      paymentStatus: Record<\n        string,\n        {\n          name: string;\n          badge: string;\n          progress?: string;\n          isDefault?: boolean;\n          isCancelable?: boolean;\n        }\n      >;\n      status: Record<\n        string,\n        {\n          name: string;\n          badge: string;\n          progress?: string;\n          isDefault?: boolean;\n          next: string[];\n        }\n      >;\n      psoMapping: Record<string, string>;\n      reStockAfterCancellation: boolean;\n    };\n    carriers: Record<\n      string,\n      {\n        name: string;\n        trackingUrl?: string;\n      }\n    >;\n  };\n};\n\ntype PathValue<T, P extends string> = P extends keyof T\n  ? T[P]\n  : P extends `${infer K}.${infer Rest}`\n  ? K extends keyof T\n    ? PathValue<T[K], Rest>\n    : never\n  : never;\n\ntype ConfigPath =\n  | keyof ConfigStructure\n  | {\n      [K in keyof ConfigStructure]: K extends string\n        ?\n            | `${K}.${Extract<keyof ConfigStructure[K], string>}`\n            | {\n                [K2 in keyof ConfigStructure[K]]: K2 extends string\n                  ?\n                      | `${K}.${K2}.${Extract<\n                          keyof ConfigStructure[K][K2],\n                          string\n                        >}`\n                      | {\n                          [K3 in keyof ConfigStructure[K][K2]]: K3 extends string\n                            ? `${K}.${K2}.${K3}.${Extract<\n                                keyof ConfigStructure[K][K2][K3],\n                                string\n                              >}`\n                            : never;\n                        }[keyof ConfigStructure[K][K2]]\n                  : never;\n              }[keyof ConfigStructure[K]]\n        : never;\n    }[keyof ConfigStructure];\n\n/**\n * Get the configuration value base on path. Return the default value if the path is not found.\n */\nexport function getConfig<P extends ConfigPath>(\n  path: P,\n  defaultValue?: PathValue<ConfigStructure, P & string>\n): PathValue<ConfigStructure, P & string> {\n  return config.has(path as string)\n    ? config.get<PathValue<ConfigStructure, P & string>>(path as string)\n    : (defaultValue as PathValue<ConfigStructure, P & string>);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/getEnabledTheme.ts",
    "content": "import { existsSync } from 'fs';\nimport { resolve } from 'path';\nimport { CONSTANTS } from '../helpers.js';\nimport { error } from '../log/logger.js';\nimport { getConfig } from './getConfig.js';\nimport isDevelopmentMode from './isDevelopmentMode.js';\nimport isProductionMode from './isProductionMode.js';\n\nexport type Theme = {\n  name: string;\n  path: string;\n  srcPath?: string;\n};\n\nexport function getEnabledTheme(): Theme | null {\n  const themeConfig = getConfig('system.theme') as string | undefined;\n  if (!themeConfig) {\n    return null;\n  }\n  if (!existsSync(resolve(CONSTANTS.THEMEPATH, themeConfig))) {\n    error(\n      `Theme '${themeConfig}' does not exist in ${CONSTANTS.THEMEPATH}. \n      Please check your theme configuration in the system settings.`\n    );\n    process.exit(1);\n  } else if (\n    isDevelopmentMode() &&\n    !existsSync(resolve(CONSTANTS.THEMEPATH, themeConfig, 'src'))\n  ) {\n    error(\n      `Theme '${themeConfig}' must have a 'src' directory at ${resolve(\n        CONSTANTS.THEMEPATH,\n        themeConfig,\n        'src'\n      )}. This is required for development mode.`\n    );\n    process.exit(1);\n  } else if (\n    isProductionMode() &&\n    !existsSync(resolve(CONSTANTS.THEMEPATH, themeConfig, 'dist'))\n  ) {\n    error(\n      `Theme '${themeConfig}' must have a 'dist' directory at ${resolve(\n        CONSTANTS.THEMEPATH,\n        themeConfig,\n        'dist'\n      )}. This is required for production mode. Please run the compile command to generate the dist directory.`\n    );\n    process.exit(1);\n  } else {\n    return {\n      name: themeConfig,\n      path: resolve(CONSTANTS.THEMEPATH, themeConfig),\n      srcPath: isDevelopmentMode()\n        ? resolve(CONSTANTS.THEMEPATH, themeConfig, 'src')\n        : undefined\n    };\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/getEnv.ts",
    "content": "/**\n * Get environment variable value\n *\n * @param name - Environment variable name\n * @param defaultValue - Default value if environment variable is not set\n * @returns The environment variable value or default value\n */\nexport function getEnv(name: string, defaultValue?: string): string {\n  const value = process.env[name];\n  return value !== undefined ? (value as string) : (defaultValue as string);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/hookable.ts",
    "content": "type Hook = {\n  callback: Function;\n  priority: number;\n};\n\nexport enum HookPosition {\n  BEFORE = 'before',\n  AFTER = 'after'\n}\n\ntype HookStorage = Map<string, Hook[]>;\n\nconst beforeHooks: HookStorage = new Map();\nconst afterHooks: HookStorage = new Map();\nlet locked = false;\n\nfunction isAsyncFunction(func: Function): boolean {\n  return func.constructor.name === 'AsyncFunction';\n}\n\nfunction hook(\n  funcName: string,\n  callback: Function,\n  priority: number = 10,\n  position: HookPosition = HookPosition.BEFORE\n): void {\n  if (locked) {\n    throw new Error(\n      'Hooks are locked. You should consider adding hooks using the bootstrap function'\n    );\n  }\n  if (typeof callback !== 'function') {\n    throw new Error('Callback must be a function');\n  }\n\n  if (typeof priority !== 'number') {\n    throw new Error('Priority must be a number');\n  }\n\n  const storage = position === HookPosition.BEFORE ? beforeHooks : afterHooks;\n\n  if (!storage.has(funcName)) {\n    storage.set(funcName, []);\n  }\n\n  const hooks = storage.get(funcName)!;\n  hooks.push({ callback, priority });\n  hooks.sort((a, b) => a.priority - b.priority);\n}\n\nexport function hookAfter<\n  TContext = any,\n  TResult = any,\n  TArgs extends any[] = any[]\n>(\n  funcName: string,\n  callback: (\n    this: TContext,\n    result: TResult,\n    ...args: TArgs\n  ) => void | Promise<void>,\n  priority: number = 10\n): void {\n  hook(funcName, callback, priority, HookPosition.AFTER);\n}\n\nexport function hookBefore<TContext = any, TArgs extends any[] = any[]>(\n  funcName: string,\n  callback: (this: TContext, ...args: TArgs) => void | Promise<void>,\n  priority: number = 10\n): void {\n  hook(funcName, callback, priority, HookPosition.BEFORE);\n}\n\nexport function hookable<T extends Function>(\n  originalFunction: T,\n  context?: any\n): T {\n  // Make sure the original function is a named function\n  const funcName = originalFunction.name.replace('bound ', '');\n  if (!funcName) {\n    throw new Error('The original function must be a named function');\n  }\n  return new Proxy(originalFunction, {\n    apply: isAsyncFunction(originalFunction)\n      ? async function (target, thisArg, argumentsList) {\n          const beforeHookFunctions = beforeHooks.get(funcName) || [];\n          const afterHookFunctions = afterHooks.get(funcName) || [];\n\n          for (let index = 0; index < beforeHookFunctions.length; index += 1) {\n            const callbackFunc = beforeHookFunctions[index].callback;\n            // Call the callback function with the cloned arguments\n            await callbackFunc.call(context, ...argumentsList);\n          }\n\n          const result = await Reflect.apply(target, thisArg, argumentsList);\n\n          for (let index = 0; index < afterHookFunctions.length; index += 1) {\n            const callbackFunc = afterHookFunctions[index].callback;\n            await callbackFunc.call(context, result, ...argumentsList);\n          }\n          return result;\n        }\n      : function (target, thisArg, argumentsList) {\n          const beforeHookFunctions = beforeHooks.get(funcName) || [];\n          const afterHookFunctions = afterHooks.get(funcName) || [];\n          // Clone the argumentsList to avoid mutation\n          beforeHookFunctions.forEach((hook) => {\n            hook.callback.call(context, ...argumentsList);\n          });\n\n          const result = Reflect.apply(target, thisArg, argumentsList);\n\n          afterHookFunctions.forEach((hook) => {\n            hook.callback.call(context, result, ...argumentsList);\n          });\n          return result;\n        }\n  }) as T;\n}\n\nexport function getHooks(): {\n  beforeHooks: HookStorage;\n  afterHooks: HookStorage;\n} {\n  return {\n    beforeHooks,\n    afterHooks\n  };\n}\n\nexport function clearHooks(): void {\n  beforeHooks.clear();\n  afterHooks.clear();\n}\n\nexport function lockHooks(): void {\n  locked = true;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/httpStatus.ts",
    "content": "export const OK = 200;\nexport const INVALID_PAYLOAD = 400;\nexport const INTERNAL_SERVER_ERROR = 500;\nexport const NOT_FOUND = 404;\nexport const UNAUTHORIZED = 401;\nexport const FORBIDDEN = 403;\nexport const CONFLICT = 409;\nexport const UNPROCESSABLE_ENTITY = 422;\nexport const TOO_MANY_REQUESTS = 429;\nexport const GATEWAY_TIMEOUT = 504;\n"
  },
  {
    "path": "packages/evershop/src/lib/util/isAjax.ts",
    "content": "import type { EvershopRequest } from '../../types/request.js';\n\nexport function isAjax(request: EvershopRequest) {\n  return request.get('X-Requested-With') === 'XMLHttpRequest';\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/isDevelopmentMode.ts",
    "content": "export default (): boolean => process.env.NODE_ENV === 'development';\nexport const isDevelopmentMode = (): boolean =>\n  process.env.NODE_ENV === 'development';\n"
  },
  {
    "path": "packages/evershop/src/lib/util/isPlainObject.ts",
    "content": "/**\n * Checks if a value is a plain object (i.e., an object created by the Object constructor).\n * @param obj The object to check\n * @returns True if the value is a plain object, false otherwise\n */\nexport function isPlainObject(obj: unknown): boolean {\n  if (typeof obj !== 'object' || obj === null) {\n    return false;\n  }\n\n  const prototype = Object.getPrototypeOf(obj);\n  return prototype === Object.prototype || prototype === null;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/isProductionMode.ts",
    "content": "export default (): boolean => process.env.NODE_ENV === 'production';\nexport const isProductionMode = (): boolean =>\n  process.env.NODE_ENV === 'production';\n"
  },
  {
    "path": "packages/evershop/src/lib/util/isResolvable.ts",
    "content": "import * as fs from 'fs';\nimport * as path from 'path';\n\ntype AliasConfig = Record<string, string[]>;\n\n/**\n * Checks whether a file exists for a given aliased path using the alias configuration.\n *\n * @param aliasedPath - The path starting with an alias (e.g., \"@components/Button\")\n * @param aliasConfig - The alias configuration mapping aliases to base paths\n * @returns true if the file exists or no alias matched; false if alias matched but file doesn't exist\n */\nexport function isResolvable(\n  aliasedPath: string,\n  aliasConfig: AliasConfig\n): boolean {\n  return true;\n  for (const alias in aliasConfig) {\n    if (aliasedPath.startsWith(alias)) {\n      const relativePath = aliasedPath.slice(alias.length).replace(/^\\/+/, '');\n\n      for (const basePath of aliasConfig[alias]) {\n        const fullPath = path.join(basePath, `${relativePath}.jsx`);\n        if (fs.existsSync(fullPath)) {\n          return true; // File exists\n        }\n      }\n\n      return false; // Alias matched, but no file found\n    }\n  }\n\n  return true; // No alias matched\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/jsonParse.ts",
    "content": "import { PathLike, readFileSync } from 'node:fs';\n\n/**\n * Reads a JSON file and parses its content into an object.\n *\n * @param {PathLike} path - The path to the JSON file.\n * @returns {T} - The parsed object from the JSON file.\n * @throws {Error} - If the file cannot be read or the content is not valid JSON.\n */\nexport function jsonParse<T>(path: PathLike): T {\n  try {\n    return JSON.parse(readFileSync(path, 'utf8')) as T;\n  } catch (error) {\n    throw new Error(`Failed to parse JSON from file ${path}: ${error.message}`);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/jwt.ts",
    "content": "import jwt from 'jsonwebtoken';\nimport { CurrentCustomer, CurrentUser } from '../../types/request.js';\n\nexport const TOKEN_TYPES = {\n  ADMIN: 'admin',\n  CUSTOMER: 'customer'\n} as const;\n\nexport type TokenType = (typeof TOKEN_TYPES)[keyof typeof TOKEN_TYPES];\n\n/**\n * Get JWT configuration (lazy initialization)\n * This allows environment variables to be set after module import\n */\nfunction getJwtConfig() {\n  return {\n    issuer: process.env.JWT_ISSUER || 'evershop',\n    admin: {\n      secret: process.env.JWT_ADMIN_SECRET,\n      refreshSecret: process.env.JWT_ADMIN_REFRESH_SECRET,\n      expiry: process.env.JWT_ADMIN_TOKEN_EXPIRY || 900,\n      refreshExpiry: process.env.JWT_ADMIN_REFRESH_TOKEN_EXPIRY || 54000\n    },\n    customer: {\n      secret: process.env.JWT_CUSTOMER_SECRET,\n      refreshSecret: process.env.JWT_CUSTOMER_REFRESH_SECRET,\n      expiry: process.env.JWT_CUSTOMER_TOKEN_EXPIRY || 1800,\n      refreshExpiry: process.env.JWT_CUSTOMER_REFRESH_TOKEN_EXPIRY || 108000\n    }\n  };\n}\n\ninterface DecodedAccessToken extends UserPayload, CustomerPayload {\n  tokenType: TokenType;\n  tokenKind: 'access';\n  iat: number;\n  exp: number;\n  aud: string;\n  iss: string;\n}\n\ninterface DecodedRefreshToken extends UserPayload, CustomerPayload {\n  tokenType: TokenType;\n  tokenKind: 'refresh';\n  iat: number;\n  exp: number;\n  aud: string;\n  iss: string;\n}\n\ninterface UserPayload {\n  user: CurrentUser;\n}\n\ninterface CustomerPayload {\n  customer: CurrentCustomer;\n}\n\n/**\n * Generate JWT access token\n */\nexport function generateToken(\n  payload: CustomerPayload | UserPayload,\n  tokenType: TokenType = TOKEN_TYPES.CUSTOMER,\n  expiresIn?: number\n) {\n  // Use separate secret for access tokens\n  const jwtConfig = getJwtConfig();\n  const secret = jwtConfig[tokenType].secret;\n\n  if (!secret) {\n    throw new Error(`JWT secret for ${tokenType} is not configured`);\n  }\n\n  const defaultExpiry =\n    tokenType === TOKEN_TYPES.ADMIN\n      ? jwtConfig.admin.expiry\n      : jwtConfig.customer.expiry;\n\n  return jwt.sign(\n    {\n      ...payload,\n      tokenType,\n      tokenKind: 'access' // Explicitly mark as access token\n    },\n    secret,\n    {\n      expiresIn: expiresIn || (defaultExpiry as number),\n      issuer: jwtConfig.issuer,\n      audience: tokenType\n    }\n  );\n}\n\n/**\n * Verify JWT access token\n */\nexport function verifyToken(token: string, tokenType: TokenType) {\n  const jwtConfig = getJwtConfig();\n  const secret = jwtConfig[tokenType].secret;\n\n  if (!secret) {\n    throw new Error(`JWT secret for ${tokenType} is not configured`);\n  }\n\n  try {\n    const decoded = jwt.verify(token, secret, {\n      issuer: jwtConfig.issuer,\n      audience: tokenType\n    }) as DecodedAccessToken;\n\n    // Verify token type matches\n    if (decoded.tokenType !== tokenType) {\n      throw new Error(`Invalid token type. Expected ${tokenType}`);\n    }\n\n    // Verify this is an access token, not a refresh token\n    if (decoded.tokenKind !== 'access') {\n      throw new Error('Invalid token kind. Expected access token');\n    }\n\n    return decoded;\n  } catch (error: any) {\n    if (error.name === 'TokenExpiredError') {\n      throw new Error('Token has expired');\n    } else if (error.name === 'JsonWebTokenError') {\n      throw new Error('Invalid token');\n    }\n    throw error;\n  }\n}\n\n/**\n * Generate refresh token (longer expiration, different secret)\n */\nexport function generateRefreshToken(\n  payload: UserPayload | CustomerPayload,\n  tokenType: TokenType = TOKEN_TYPES.CUSTOMER\n) {\n  // Use separate secret for refresh tokens\n  const jwtConfig = getJwtConfig();\n  const secret = jwtConfig[tokenType].refreshSecret;\n\n  if (!secret) {\n    throw new Error(`JWT refresh secret for ${tokenType} is not configured`);\n  }\n\n  const refreshExpiry =\n    tokenType === TOKEN_TYPES.ADMIN\n      ? jwtConfig.admin.refreshExpiry\n      : jwtConfig.customer.refreshExpiry;\n\n  return jwt.sign(\n    {\n      ...payload,\n      tokenType,\n      tokenKind: 'refresh' // Explicitly mark as refresh token\n    },\n    secret,\n    {\n      expiresIn: refreshExpiry as number,\n      issuer: jwtConfig.issuer,\n      audience: tokenType\n    }\n  );\n}\n\n/**\n * Verify JWT refresh token\n */\nexport function verifyRefreshToken(token: string, tokenType: TokenType) {\n  // Use separate secret for refresh tokens\n  const jwtConfig = getJwtConfig();\n  const secret = jwtConfig[tokenType].refreshSecret;\n\n  if (!secret) {\n    throw new Error(`JWT refresh secret for ${tokenType} is not configured`);\n  }\n\n  try {\n    const decoded = jwt.verify(token, secret, {\n      issuer: jwtConfig.issuer,\n      audience: tokenType\n    }) as DecodedRefreshToken;\n\n    // Verify token type matches\n    if (decoded.tokenType !== tokenType) {\n      throw new Error(`Invalid token type. Expected ${tokenType}`);\n    }\n\n    // Verify this is a refresh token, not an access token\n    if (decoded.tokenKind !== 'refresh') {\n      throw new Error('Invalid token kind. Expected refresh token');\n    }\n\n    return decoded;\n  } catch (error: any) {\n    if (error.name === 'TokenExpiredError') {\n      throw new Error('Refresh token has expired');\n    } else if (error.name === 'JsonWebTokenError') {\n      throw new Error('Invalid refresh token');\n    }\n    throw error;\n  }\n}\n\n/**\n * Decode token without verification\n */\nexport function decodeToken(\n  token: string\n): DecodedAccessToken | DecodedRefreshToken | null {\n  return jwt.decode(token, { json: true }) as\n    | DecodedAccessToken\n    | DecodedRefreshToken\n    | null;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/keyGenerator.ts",
    "content": "// Simple hash function that works identically in both browser and Node.js\nfunction simpleHash(str: string): string {\n  let hash = 0;\n  if (str.length === 0) return hash.toString(16);\n\n  for (let i = 0; i < str.length; i++) {\n    const char = str.charCodeAt(i);\n    hash = (hash << 5) - hash + char;\n    hash = hash & hash; // Convert to 32bit integer\n  }\n\n  // Convert to positive hex string with consistent length\n  return Math.abs(hash).toString(16).padStart(8, '0');\n}\n\nexport function generateComponentKey(text: string): string {\n  // Remove everything before '/src/' or '/dist/'\n  const subPath = text.split('/src/')[1] || text.split('/dist/')[1];\n  if (!subPath) {\n    return `e${simpleHash(text)}`;\n  }\n  return `e${simpleHash(subPath)}`;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/merge.js",
    "content": "/**\n * This function take 2 objects: target and source, and merge them together recursively\n * This function will not merge built-in objects like Date, RegExp, Map, Set\n * This function will overwrite the value from the first object by the value from the second object if they have the same key and the value is primitive\n * This function will merge array property from 2 objects\n *\n * @param   {object}  target  The target object\n * @param   {object}  source  The source object\n *\n * @return  {object}  The new target object\n */\nexport function merge(target, source, maxDepth = 20, currentDepth = 0) {\n  function isBuiltInObject(obj) {\n    return (\n      obj instanceof Date ||\n      obj instanceof RegExp ||\n      obj instanceof Map ||\n      obj instanceof Set\n    );\n  }\n\n  if (isBuiltInObject(target) || isBuiltInObject(source)) {\n    throw new Error(\n      'deepMerge cannot merge built-in objects like Date or RegExp'\n    );\n  }\n\n  if (\n    typeof target !== 'object' ||\n    target === null ||\n    typeof source !== 'object' ||\n    source === null\n  ) {\n    throw new Error('merge function can only merge plain objects');\n  }\n\n  if (currentDepth > maxDepth) {\n    throw new Error(`Maximum depth of ${maxDepth} exceeded`);\n  }\n\n  function getAllKeys(obj) {\n    let keys = [];\n    Object.getOwnPropertyNames(obj).forEach((key) => {\n      if (!keys.includes(key)) {\n        keys.push(key);\n      }\n    });\n    keys = keys.concat(Object.getOwnPropertySymbols(obj));\n    return keys;\n  }\n\n  getAllKeys(source).forEach((key) => {\n    if (key in target) {\n      if (Array.isArray(target[key]) && Array.isArray(source[key])) {\n        target[key] = Array.from(new Set([...target[key], ...source[key]]));\n      } else if (\n        typeof target[key] === 'object' &&\n        typeof source[key] === 'object'\n      ) {\n        if (isBuiltInObject(target[key]) || isBuiltInObject(source[key])) {\n          target[key] = source[key];\n        } else {\n          merge(target[key], source[key], maxDepth, currentDepth + 1);\n        }\n      } else {\n        target[key] = source[key];\n      }\n    } else {\n      Object.defineProperty(\n        target,\n        key,\n        Object.getOwnPropertyDescriptor(source, key)\n      );\n    }\n  });\n\n  return target;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/parseImageSizes.ts",
    "content": "// Define your desired image breakpoints. Consider putting this in a config file.\nconst deviceSizes = [320, 640, 750, 828, 1080, 1200, 1920, 2048, 3840];\n\nconst isValidCondition = (condition: string): boolean => {\n  if (!condition || typeof condition !== 'string') {\n    return false;\n  }\n\n  const trimmed = condition.trim();\n  if (!trimmed) {\n    return false;\n  }\n\n  // Special case: handle 'auto' keyword\n  if (trimmed === 'auto') {\n    return true;\n  }\n\n  // Check for valid CSS units pattern - allow some whitespace between value and unit\n  const validUnitsPattern =\n    /(\\d+(?:\\.\\d+)?)\\s*(vw|vh|px|rem|em|%|ch|vmin|vmax|pt|pc|in|cm|mm|ex|ic|lh|vi|vb|cqw|cqh|cqi|cqb|cqmin|cqmax)\\s*$/;\n\n  // If it has parentheses, it should be a media query\n  if (trimmed.includes('(')) {\n    // Basic media query validation - must have closing parenthesis and valid value\n    const hasClosingParen = trimmed.includes(')');\n    const hasValidValue = validUnitsPattern.test(trimmed);\n    // Also check that it has proper media query structure\n    const hasProperMediaQuery = /\\([^)]*\\)/.test(trimmed);\n    return hasClosingParen && hasValidValue && hasProperMediaQuery;\n  } else {\n    // Simple value without media query - but shouldn't have unmatched closing parenthesis\n    const hasUnmatchedClosingParen = trimmed.includes(')');\n    const hasValidValue = validUnitsPattern.test(trimmed);\n    return !hasUnmatchedClosingParen && hasValidValue;\n  }\n};\n\n// Parse sizes string to estimate actual image sizes\nexport const parseImageSizes = (sizes: string): number[] => {\n  // Validate input\n  if (!sizes || typeof sizes !== 'string') {\n    throw new Error('Invalid sizes attribute: must be a non-empty string');\n  }\n\n  const trimmedSizes = sizes.trim();\n  if (!trimmedSizes) {\n    throw new Error(\n      'Invalid sizes attribute: cannot be empty or whitespace only'\n    );\n  }\n\n  // Handle fixed pixel values first\n  if (\n    trimmedSizes.endsWith('px') &&\n    !trimmedSizes.includes(',') &&\n    !trimmedSizes.includes('(')\n  ) {\n    const pixelValue = parseInt(trimmedSizes);\n    if (!isNaN(pixelValue) && pixelValue > 0) {\n      // For fixed pixel values, generate a few sizes around that value\n      return deviceSizes\n        .filter((size) => size >= pixelValue * 0.5) // Include smaller sizes for efficiency\n        .slice(0, 4); // Limit to 4 sizes to keep srcset reasonable\n    } else {\n      throw new Error(\n        `Invalid pixel value in sizes attribute: \"${trimmedSizes}\" must be a positive number followed by \"px\"`\n      );\n    }\n  }\n\n  // Parse complex sizes string with media queries\n  const conditions = trimmedSizes\n    .split(',')\n    .map((s) => s.trim())\n    .filter(Boolean);\n\n  if (conditions.length === 0) {\n    throw new Error(\n      'Invalid sizes attribute: no valid conditions found after parsing'\n    );\n  }\n\n  // Validate that each condition has a proper format\n  for (const condition of conditions) {\n    if (!isValidCondition(condition)) {\n      throw new Error(\n        `Invalid condition in sizes attribute: \"${condition}\" - must contain a valid CSS length value or media query`\n      );\n    }\n  }\n\n  // For each device size, determine what actual image size would be used\n  const imageSizes = deviceSizes.map((deviceSize) => {\n    // Go through conditions in order until we find a match\n    for (const condition of conditions) {\n      const result = evaluateCondition(condition, deviceSize);\n      if (result !== null) {\n        return result;\n      }\n    }\n\n    // If no conditions matched, assume full width\n    return deviceSize;\n  });\n\n  // Remove duplicates, sort, and ensure we have reasonable variety\n  const uniqueSizes = [...new Set(imageSizes)].sort((a, b) => a - b);\n  // Ensure minimum variety for better responsive behavior\n  if (uniqueSizes.length < 3) {\n    // Add some intermediate sizes for better coverage\n    const minSize = Math.min(...uniqueSizes);\n    const maxSize = Math.max(...uniqueSizes);\n    const midSize = Math.round((minSize + maxSize) / 2);\n    uniqueSizes.push(midSize);\n  }\n\n  return [...new Set(uniqueSizes)].sort((a, b) => a - b);\n};\n\nexport const evaluateCondition = (\n  condition: string,\n  deviceSize: number\n): number | null => {\n  // Remove extra whitespace\n  condition = condition.trim();\n\n  // Check if this condition has a media query\n  if (condition.includes('(')) {\n    // Extract media query and value parts - comprehensive regex for all CSS units\n    const mediaQueryMatch = condition.match(/\\(([^)]+)\\)/g);\n    const valueMatch = condition.match(\n      /(\\d+(?:\\.\\d+)?)\\s*(vw|vh|px|rem|em|%|ch|vmin|vmax|pt|pc|in|cm|mm|ex|ic|lh|vi|vb|cqw|cqh|cqi|cqb|cqmin|cqmax)\\s*$/\n    );\n\n    if (!mediaQueryMatch || !valueMatch) {\n      return null;\n    }\n\n    const mediaQueries = mediaQueryMatch.map((mq) => mq.slice(1, -1)); // Remove parentheses\n    const value = parseFloat(valueMatch[1]);\n    const unit = valueMatch[2];\n\n    // Check if all media queries match for this device size\n    const allMatch = mediaQueries.every((mq) => {\n      const matches = evaluateMediaQuery(mq, deviceSize);\n      return matches;\n    });\n\n    if (allMatch) {\n      const result = convertToPixels(value, unit, deviceSize);\n      return result;\n    }\n\n    return null; // Media query doesn't match\n  } else {\n    // Special case: handle 'auto' keyword\n    if (condition.trim() === 'auto') {\n      // For 'auto', use a reasonable default based on the device size\n      // We'll use 25% of the viewport width as a reasonable approximation\n      return Math.round(deviceSize * 0.25);\n    }\n\n    // No media query, this is a fallback value - comprehensive regex for all CSS units\n    const valueMatch = condition.match(\n      /(\\d+(?:\\.\\d+)?)\\s*(vw|vh|px|rem|em|%|ch|vmin|vmax|pt|pc|in|cm|mm|ex|ic|lh|vi|vb|cqw|cqh|cqi|cqb|cqmin|cqmax)\\s*$/\n    );\n    if (valueMatch) {\n      const value = parseFloat(valueMatch[1]);\n      const unit = valueMatch[2];\n      const result = convertToPixels(value, unit, deviceSize);\n      return result;\n    }\n  }\n\n  return null;\n};\n\nexport const evaluateMediaQuery = (\n  mediaQuery: string,\n  deviceSize: number\n): boolean => {\n  // Handle different media query types with improved regex patterns\n  if (mediaQuery.includes('max-width')) {\n    const maxWidth = parseFloat(\n      mediaQuery.match(/max-width\\s*:\\s*(\\d+(?:\\.\\d+)?)px/)?.[1] || '0'\n    );\n    return deviceSize <= maxWidth;\n  }\n\n  if (mediaQuery.includes('min-width')) {\n    const minWidth = parseFloat(\n      mediaQuery.match(/min-width\\s*:\\s*(\\d+(?:\\.\\d+)?)px/)?.[1] || '0'\n    );\n    return deviceSize >= minWidth;\n  }\n\n  if (mediaQuery.includes('max-device-width')) {\n    const maxDeviceWidth = parseFloat(\n      mediaQuery.match(/max-device-width\\s*:\\s*(\\d+(?:\\.\\d+)?)px/)?.[1] || '0'\n    );\n    return deviceSize <= maxDeviceWidth;\n  }\n\n  if (mediaQuery.includes('min-device-width')) {\n    const minDeviceWidth = parseFloat(\n      mediaQuery.match(/min-device-width\\s*:\\s*(\\d+(?:\\.\\d+)?)px/)?.[1] || '0'\n    );\n    return deviceSize >= minDeviceWidth;\n  }\n\n  // Handle orientation\n  if (mediaQuery.includes('orientation')) {\n    if (mediaQuery.includes('landscape')) {\n      return deviceSize >= 768; // Assume landscape for wider screens\n    }\n    if (mediaQuery.includes('portrait')) {\n      return deviceSize < 768; // Assume portrait for narrower screens\n    }\n  }\n\n  // Handle aspect-ratio (simplified)\n  if (mediaQuery.includes('aspect-ratio')) {\n    // For simplicity, assume most common aspect ratios match\n    return true;\n  }\n\n  // Handle resolution/pixel density\n  if (\n    mediaQuery.includes('resolution') ||\n    mediaQuery.includes('-webkit-device-pixel-ratio')\n  ) {\n    // For srcset calculation, we generally assume 2x displays are common\n    return true;\n  }\n\n  // Handle prefers-color-scheme, prefers-reduced-motion etc.\n  if (mediaQuery.includes('prefers-')) {\n    // These don't affect image sizing, so return true\n    return true;\n  }\n\n  // Default: if we can't parse it, assume it matches\n  return true;\n};\n\nexport const convertToPixels = (\n  value: number,\n  unit: string,\n  deviceSize: number\n): number => {\n  switch (unit) {\n    // Viewport units\n    case 'vw':\n      return Math.round((deviceSize * value) / 100);\n    case 'vh':\n      // Assume viewport height is roughly 1.5x viewport width for mobile, 0.6x for desktop\n      const assumedHeight =\n        deviceSize <= 768 ? deviceSize * 1.5 : deviceSize * 0.6;\n      return Math.round((assumedHeight * value) / 100);\n    case 'vmin':\n      // vmin is the smaller of vw or vh\n      const vminHeight =\n        deviceSize <= 768 ? deviceSize * 1.5 : deviceSize * 0.6;\n      const minDimension = Math.min(deviceSize, vminHeight);\n      return Math.round((minDimension * value) / 100);\n    case 'vmax':\n      // vmax is the larger of vw or vh\n      const vmaxHeight =\n        deviceSize <= 768 ? deviceSize * 1.5 : deviceSize * 0.6;\n      const maxDimension = Math.max(deviceSize, vmaxHeight);\n      return Math.round((maxDimension * value) / 100);\n    case 'vi':\n      // Viewport inline (same as vw in horizontal writing mode)\n      return Math.round((deviceSize * value) / 100);\n    case 'vb':\n      // Viewport block (same as vh in horizontal writing mode)\n      const vbHeight = deviceSize <= 768 ? deviceSize * 1.5 : deviceSize * 0.6;\n      return Math.round((vbHeight * value) / 100);\n\n    // Absolute length units\n    case 'px':\n      return Math.round(value);\n    case 'pt':\n      // 1pt = 1.33px (approximately)\n      return Math.round(value * 1.33);\n    case 'pc':\n      // 1pc = 16px (1 pica = 12 points)\n      return Math.round(value * 16);\n    case 'in':\n      // 1in = 96px (CSS reference pixel)\n      return Math.round(value * 96);\n    case 'cm':\n      // 1cm = 37.8px (96px/2.54)\n      return Math.round(value * 37.8);\n    case 'mm':\n      // 1mm = 3.78px (37.8px/10)\n      return Math.round(value * 3.78);\n\n    // Relative length units\n    case '%':\n      // Assume % is relative to viewport width (same as vw in most contexts)\n      return Math.round((deviceSize * value) / 100);\n    case 'rem':\n      // Assume 1rem = 16px (default browser font size)\n      return Math.round(value * 16);\n    case 'em':\n      // Assume 1em = 16px (in absence of parent context)\n      return Math.round(value * 16);\n    case 'ex':\n      // Assume 1ex = 8px (approximately 0.5em)\n      return Math.round(value * 8);\n    case 'ch':\n      // Assume 1ch = 8px (approximate character width in monospace font)\n      return Math.round(value * 8);\n    case 'ic':\n      // Assume 1ic = 16px (ideographic character width, similar to em)\n      return Math.round(value * 16);\n    case 'lh':\n      // Assume 1lh = 24px (typical line height is 1.5em)\n      return Math.round(value * 24);\n\n    // Container query units (treat similar to viewport units for now)\n    case 'cqw':\n      // Container query width (fallback to viewport width)\n      return Math.round((deviceSize * value) / 100);\n    case 'cqh':\n      // Container query height (fallback to viewport height)\n      const cqhHeight = deviceSize <= 768 ? deviceSize * 1.5 : deviceSize * 0.6;\n      return Math.round((cqhHeight * value) / 100);\n    case 'cqi':\n      // Container query inline (fallback to viewport width)\n      return Math.round((deviceSize * value) / 100);\n    case 'cqb':\n      // Container query block (fallback to viewport height)\n      const cqbHeight = deviceSize <= 768 ? deviceSize * 1.5 : deviceSize * 0.6;\n      return Math.round((cqbHeight * value) / 100);\n    case 'cqmin':\n      // Container query min (fallback to vmin)\n      const cqminHeight =\n        deviceSize <= 768 ? deviceSize * 1.5 : deviceSize * 0.6;\n      const cqMinDimension = Math.min(deviceSize, cqminHeight);\n      return Math.round((cqMinDimension * value) / 100);\n    case 'cqmax':\n      // Container query max (fallback to vmax)\n      const cqmaxHeight =\n        deviceSize <= 768 ? deviceSize * 1.5 : deviceSize * 0.6;\n      const cqMaxDimension = Math.max(deviceSize, cqmaxHeight);\n      return Math.round((cqMaxDimension * value) / 100);\n\n    default:\n      // Fallback to treating as pixels\n      return Math.round(value);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/util/passwordHelper.ts",
    "content": "import bcrypt from 'bcryptjs';\nimport { addProcessor, getValueSync } from './registry.js';\nimport { Validator, ValidatorManager } from './validator.js';\n\nexport function hashPassword(password: string): string {\n  const salt = bcrypt.genSaltSync(10);\n  const hash = bcrypt.hashSync(password, salt);\n  return hash;\n}\n\nexport function comparePassword(password: string, hash: string): boolean {\n  return bcrypt.compareSync(password, hash);\n}\n\nexport function verifyPassword(password: string): boolean {\n  const validator = getValueSync<ValidatorManager<string>>(\n    'passwordValidator',\n    () =>\n      new ValidatorManager([\n        {\n          id: 'passwordLength',\n          func: (password) => password.length >= 6,\n          errorMessage: 'Password must be at least 6 characters'\n        }\n      ]),\n    {},\n    (value) => value instanceof ValidatorManager\n  );\n\n  const { valid, errors } = validator.validateSync(password);\n  if (!valid) {\n    throw new Error(`${errors.join('\\n\\r')}`);\n  }\n  return true;\n}\n\nexport function addPasswordValidationRule(rule: Validator<string>): void {\n  addProcessor('passwordValidator', (validatorManager) => {\n    if (validatorManager instanceof ValidatorManager) {\n      validatorManager.add(rule);\n      return validatorManager;\n    } else {\n      throw new Error(\n        'passwordValidator must be an instance of ValidatorManager'\n      );\n    }\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/preloadScan.ts",
    "content": "interface PreloadImage {\n  src: string;\n  srcset?: string;\n  sizes?: string;\n  crossorigin?: string;\n}\n\nfunction extractPreloadImages(html: string): PreloadImage[] {\n  const imgRegex = /<img[^>]*itemProp=[\"']preload[\"'][^>]*>/gi;\n  const images: PreloadImage[] = [];\n\n  let match;\n  while ((match = imgRegex.exec(html)) !== null) {\n    const imgTag = match[0];\n\n    // Extract src attribute\n    const srcMatch = imgTag.match(/src=[\"']([^\"']*)[\"']/i);\n    if (!srcMatch) continue;\n\n    const preloadImage: PreloadImage = {\n      src: srcMatch[1]\n    };\n\n    const srcsetMatch = imgTag.match(/srcSet=[\"']([^\"']*)[\"']/i);\n    if (srcsetMatch) {\n      preloadImage.srcset = srcsetMatch[1];\n    }\n\n    const sizesMatch = imgTag.match(/sizes=[\"']([^\"']*)[\"']/i);\n    if (sizesMatch) {\n      preloadImage.sizes = sizesMatch[1];\n    }\n\n    const crossoriginMatch = imgTag.match(/crossorigin=[\"']([^\"']*)[\"']/i);\n    if (crossoriginMatch) {\n      preloadImage.crossorigin = crossoriginMatch[1];\n    }\n\n    images.push(preloadImage);\n  }\n\n  return images;\n}\n\nfunction generatePreloadLinks(images: PreloadImage[]): string {\n  return images\n    .map((image) => {\n      const attributes = [\n        'rel=\"preload\"',\n        'as=\"image\"',\n        `href=\"${image.src}\"`,\n        'fetchpriority=\"high\"'\n      ];\n\n      if (image.srcset) {\n        attributes.push(`imagesrcset=\"${image.srcset}\"`);\n      }\n\n      if (image.sizes) {\n        attributes.push(`imagesizes=\"${image.sizes}\"`);\n      }\n\n      if (image.crossorigin) {\n        attributes.push(`crossorigin=\"${image.crossorigin}\"`);\n      }\n\n      return `    <link ${attributes.join(' ')} />`;\n    })\n    .join('\\n');\n}\n\nexport function injectPreloadLinks(html: string): string {\n  const preloadImages = extractPreloadImages(html);\n\n  if (preloadImages.length === 0) {\n    return html;\n  }\n\n  const preloadLinks = generatePreloadLinks(preloadImages);\n\n  const headCloseRegex = /(<\\/head>)/i;\n\n  const modifiedHtml = html.replace(headCloseRegex, `${preloadLinks}\\n$1`);\n\n  return modifiedHtml;\n}\n\nexport function injectPreloadLinksAfterCharset(html: string): string {\n  const preloadImages = extractPreloadImages(html);\n\n  if (preloadImages.length === 0) {\n    return html;\n  }\n\n  const preloadLinks = generatePreloadLinks(preloadImages);\n\n  const charsetRegex = /(<meta\\s+charSet=[\"'][^\"']*[\"'][^>]*>)/i;\n\n  if (charsetRegex.test(html)) {\n    const modifiedHtml = html.replace(charsetRegex, `$1\\n${preloadLinks}`);\n    return modifiedHtml;\n  }\n\n  return injectPreloadLinks(html);\n}\n\nexport function cleanupPreloadAttributes(html: string): string {\n  return html.replace(/\\s*itemProp=[\"']preload[\"']/gi, '');\n}\n\nexport function processPreloadImages(html: string): string {\n  let processedHtml = injectPreloadLinksAfterCharset(html);\n  processedHtml = cleanupPreloadAttributes(processedHtml);\n  return processedHtml;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/readCsvFile.ts",
    "content": "import fs, { PathLike } from 'fs';\nimport csv from 'csv-parser';\n\nexport async function readCsvFile<T>(\n  filePath: PathLike | string\n): Promise<Record<string, T>> {\n  return new Promise((resolve, reject) => {\n    const results: Record<string, T> = {};\n    fs.createReadStream(filePath)\n      .pipe(csv({ headers: false }))\n      .on('data', (data) => {\n        // Skip the first row (headers)\n        if (!data[0].startsWith('#')) {\n          results[data[0]] = data[1] as T;\n        }\n      })\n      .on('end', () => {\n        resolve(results);\n      })\n      .on('error', (err) => {\n        reject(err);\n      });\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/registry.ts",
    "content": "import isEqual from 'react-fast-compare';\n\nlet locked = false;\n\nexport type RegistryValue<T> = {\n  initValue: T;\n  context: Record<string, any>;\n  value: T;\n  processors: {\n    callback: SyncProcessor<T> | AsyncProcessor<T>;\n    priority: number;\n  }[];\n};\n\nexport type SyncProcessor<T> = (value: T) => T;\nexport type AsyncProcessor<T> = (value: T) => Promise<T>;\n\nclass Registry {\n  values: Record<string, Partial<RegistryValue<any>>> = {};\n\n  async get<T>(\n    name: string,\n    initValue: T,\n    context?: Record<string, any>,\n    validator?: (value: T) => boolean\n  ) {\n    if (this.values[name]) {\n      // If the initValue and the context are identical, return the cached value. Skip the processors\n      if (\n        isEqual(initValue, this.values[name].initValue) &&\n        isEqual(this.values[name].context, context) &&\n        Object.prototype.hasOwnProperty.call(this.values[name], 'value')\n      ) {\n        return this.values[name].value;\n      }\n    }\n\n    // Cache the initValue and the context\n    this.values[name] = this.values[name] || ({} as RegistryValue<T>);\n    this.values[name].initValue = initValue;\n    this.values[name].context = context;\n\n    // If there is no processor, return the init value\n    if (!this.values[name].processors) {\n      this.values[name].value = initValue;\n      return initValue;\n    }\n\n    const { processors } = this.values[name];\n    // Call the list of processors, returned value will be passed to the next processor. Start with the init value\n    let value = initValue;\n    for (let i = 0; i < processors.length; i += 1) {\n      const { callback } = processors[i];\n      value = await callback.call(context, value);\n      if (value === undefined) {\n        // eslint-disable-next-line no-console\n        console.log(\n          `\\x1b[33m⚠️ The processor for the value '${name}' is not returning anything. This may cause unexpected behavior.\\x1b[0m`\n        );\n      }\n      // Validate the value if the validator is provided and it is a function\n      if (typeof validator === 'function') {\n        const validateResult = validator(value);\n        if (validateResult !== true) {\n          throw new Error(`Value ${name} is invalid: ${validateResult}`);\n        }\n      }\n    }\n\n    // Cache the value\n    this.values[name].value = value;\n    return value;\n  }\n\n  getSync<T>(\n    name: string,\n    initValue: T,\n    context?: Record<string, any>,\n    validator?: (value: T) => boolean\n  ) {\n    const validateFunc = (value: T) => {\n      // Check if value is a promise\n      if (\n        value !== null &&\n        typeof value === 'object' &&\n        typeof (value as unknown as Promise<any>).then === 'function'\n      ) {\n        throw new Error(\n          `The 'getSync' function does not support async processor. Please use 'get' function instead`\n        );\n      } else if (typeof validator === 'function') {\n        return validator(value);\n      } else {\n        return true;\n      }\n    };\n\n    if (this.values[name]) {\n      // If the initValue and the context are identical, return the cached value. Skip the processors\n      if (\n        isEqual(initValue, this.values[name].initValue) &&\n        isEqual(this.values[name].context, context) &&\n        Object.prototype.hasOwnProperty.call(this.values[name], 'value')\n      ) {\n        return this.values[name].value;\n      }\n    }\n\n    // Cache the initValue and the context\n    this.values[name] = this.values[name] || ({} as RegistryValue<T>);\n    this.values[name].initValue = initValue;\n    this.values[name].context = context;\n\n    // If there is no processor, return the init value\n    if (!this.values[name].processors) {\n      this.values[name].value = initValue;\n      return initValue;\n    }\n\n    const { processors } = this.values[name];\n    // Call the list of processors, returned value will be passed to the next processor. Start with the init value\n    let value = initValue;\n    for (let i = 0; i < processors.length; i += 1) {\n      const { callback } = processors[i];\n      value = callback.call(context, value);\n      // Check if the callback function not returning anything\n      if (value === undefined) {\n        // eslint-disable-next-line no-console\n        console.log(\n          `\\x1b[33m⚠️ The processor for the value '${name}' is not returning anything. This may cause unexpected behavior.\\x1b[0m`\n        );\n      }\n      // Validate the value if the validator is provided and it is a function\n      const validateResult = validateFunc(value);\n      if (validateResult !== true) {\n        throw new Error(`Value ${name} is invalid`);\n      }\n    }\n\n    // Cache the value\n    this.values[name].value = value;\n    return value;\n  }\n\n  addProcessor<T>(\n    name: string,\n    callback: SyncProcessor<T> | AsyncProcessor<T>,\n    priority?: number\n  ) {\n    if (locked) {\n      throw new Error(\n        'Registry is locked. Most likely you are trying to add a processor from a middleware. Consider using a bootstrap file to add processors'\n      );\n    }\n    if (typeof priority === 'undefined') {\n      priority = 10;\n    }\n    // Throw error if priority is not a number\n    if (typeof priority !== 'number') {\n      throw new Error('Priority must be a number');\n    }\n\n    // Throw error if the priority is bigger than 1000\n    if (priority > 1000) {\n      throw new Error('Priority must be smaller than 1000');\n    }\n\n    // Throw error if callback is not a function or async function\n    if (typeof callback !== 'function') {\n      throw new Error('Callback must be a function');\n    }\n    if (!this.values[name]) {\n      this.values[name] = {\n        processors: []\n      } as Partial<RegistryValue<any>>;\n    }\n    this.values[name].processors = this.values[name].processors || [];\n    // Add the callback to the processors, sort by priority\n    const { processors } = this.values[name];\n    processors.push({\n      callback,\n      priority\n    });\n    processors.sort((a, b) => a.priority - b.priority);\n  }\n\n  addFinalProcessor<T>(\n    name: string,\n    callback: SyncProcessor<T> | AsyncProcessor<T>\n  ): void {\n    // Check if there is already a final processor base on the priority\n    const processors = this.values[name]?.processors || [];\n    if (processors.find((p) => p.priority === 1000)) {\n      throw new Error(\n        `There is already a final processor for the value ${name}`\n      );\n    }\n    this.addProcessor(name, callback, 1000);\n  }\n\n  getProcessors(name: string): {\n    callback: SyncProcessor<any> | AsyncProcessor<any>;\n    priority: number;\n  }[] {\n    if (!this.values[name]) {\n      throw new Error(`The value ${name} is not registered`);\n    }\n    return this.values[name].processors || [];\n  }\n}\n\nconst registry = new Registry();\n\n/**\n * Get the value from the registry\n * @param name - The name of the value\n * @param initialization - The initialization value or a function that returns the value\n * @param context - The context of the value\n * @param validator - The validator function\n * @returns The value from the registry\n */\nexport async function getValue<T>(\n  name: string,\n  initialization: T | AsyncProcessor<T> | SyncProcessor<T>,\n  context?: Record<string, any>,\n  validator?: (value: T) => boolean\n): Promise<T> {\n  let initValue;\n  const value = registry.values[name] || ({} as RegistryValue<T>);\n  // Check if the initValue is a function, then add this function to the processors as the first processor\n  if (typeof initialization === 'function') {\n    // Add this function to the biginning of the processors\n    const processors = value.processors || [];\n    processors.unshift({\n      callback: initialization as SyncProcessor<T> | AsyncProcessor<T>,\n      priority: 0\n    });\n    registry.values[name] = {\n      ...value,\n      processors\n    };\n    initValue = value.initValue;\n  } else {\n    initValue = initialization as T;\n  }\n  const val = await registry.get(name, initValue, context, validator);\n  return val;\n}\n\n/**\n * Get the value from the registry\n * @param name - The name of the value\n * @param initialization - The initialization value or a function that returns the value\n * @param context - The context of the value\n * @param validator - The validator function\n * @returns The value from the registry\n */\nexport function getValueSync<T>(\n  name: string,\n  initialization: T | SyncProcessor<T>,\n  context: Record<string, any>,\n  validator?: (value: T) => boolean\n): T {\n  let initValue;\n  // Check if the initValue is a function, then add this function to the processors as the first processor\n  if (typeof initialization === 'function') {\n    // Add this function to the processors, add this to the biginning of the processors\n    const processors = registry.values[name]?.processors || [];\n    processors.unshift({\n      callback: initialization as SyncProcessor<T>,\n      priority: 0\n    });\n    registry.values[name] = registry.values[name] || ({} as RegistryValue<T>);\n    registry.values[name].processors = processors;\n    initValue = registry.values[name].initValue;\n  } else {\n    initValue = initialization;\n  }\n  const val = registry.getSync(name, initValue, context, validator);\n  return val;\n}\n\nexport function addProcessor<T>(\n  name: string,\n  callback: SyncProcessor<T> | AsyncProcessor<T>,\n  priority?: number\n): void {\n  return registry.addProcessor(name, callback, priority);\n}\n\nexport function addFinalProcessor(\n  name: string,\n  callback: SyncProcessor<any> | AsyncProcessor<any>\n): void {\n  return registry.addFinalProcessor(name, callback);\n}\n\nexport function getProcessors<T>(name: string): {\n  callback: SyncProcessor<T> | AsyncProcessor<T>;\n  priority: number;\n}[] {\n  return registry.getProcessors(name);\n}\n\nexport function lockRegistry(): void {\n  // Reset the values cache by removing all values from all properties in the registry values\n  Object.keys(registry.values).forEach((key) => {\n    if (Object.prototype.hasOwnProperty.call(registry.values, key)) {\n      delete registry.values[key].value;\n    }\n  });\n  locked = true;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/sanitizeHtml.ts",
    "content": "import sanitizeHtml from 'sanitize-html';\n\n// Extend the default allowed attributes to preserve CSS classes on all tags\nconst sanitizeOptions: sanitizeHtml.IOptions = {\n  allowedTags: sanitizeHtml.defaults.allowedTags.concat([\n    'img',\n    'figure',\n    'figcaption',\n    'video',\n    'source',\n    'iframe'\n  ]),\n  allowedAttributes: {\n    ...sanitizeHtml.defaults.allowedAttributes,\n    '*': ['class', 'id', 'style']\n  }\n};\n\nexport interface Row {\n  id: string;\n  size: number;\n  columns: {\n    id: string;\n    size: number;\n    data: any;\n  }[];\n}\n\n/**\n * Sanitizes the HTML content in all EditorJS raw HTML blocks within the page content.\n * Each column's `data` is an EditorJS block: { type, data: { ... } }.\n * For \"raw\" type blocks, `data.html` is sanitized via sanitize-html.\n */\nfunction sanitizeRawHtml(editorJSData: Row[]) {\n  if (!Array.isArray(editorJSData)) {\n    return;\n  }\n  editorJSData.forEach((row) => {\n    if (!Array.isArray(row.columns)) {\n      return;\n    }\n    row.columns.forEach((column) => {\n      if (!column.data || !Array.isArray(column.data.blocks)) {\n        return;\n      }\n      column.data.blocks.forEach((block) => {\n        if (\n          block.type === 'raw' &&\n          block.data &&\n          typeof block.data.html === 'string'\n        ) {\n          block.data.html = sanitizeHtml(block.data.html, sanitizeOptions);\n        }\n      });\n    });\n  });\n}\n\nexport { sanitizeRawHtml };\n"
  },
  {
    "path": "packages/evershop/src/lib/util/tests/unit/util.assign.test.js",
    "content": "import { assign } from '../../assign.js';\n\ndescribe('assign', () => {\n  it('It should assign an object to the main object', () => {\n    const a = { a: 1 };\n    const b = { b: 1 };\n    assign(a, b);\n    expect(a).toEqual({ a: 1, b: 1 });\n  });\n\n  it('It should thrown an exception if `object` is not an object or null', () => {\n    const a = 1;\n    const b = { b: 1 };\n    expect(() => assign(a, b)).toThrow(Error);\n  });\n\n  it('It should thrown an exception if `object` is not an object or null', () => {\n    const a = null;\n    const b = { b: 1 };\n    expect(() => assign(a, b)).toThrow(Error);\n  });\n\n  it('It should thrown an exception if data is not an object or null', () => {\n    const a = { a: 1 };\n    const b = 1;\n    expect(() => assign(a, b)).toThrow(Error);\n  });\n\n  it('It should thrown an exception if data is not an object or null', () => {\n    const a = { a: 1 };\n    const b = null;\n    expect(() => assign(a, b)).toThrow(Error);\n  });\n\n  it('It should overwrite if the property is already existed', () => {\n    const a = { a: 1, b: 1 };\n    const b = { b: 2 };\n    assign(a, b);\n    expect(a).toEqual({ a: 1, b: 2 });\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/util/tests/unit/util.get.test.js",
    "content": "import { get } from '../../get.js';\n\ndescribe('Test until get', () => {\n  it('It should return the value if path is valid', () => {\n    const object = { a: 1, b: { c: 1 } };\n    const result = get(object, 'b.c');\n    expect(result).toEqual(1);\n  });\n\n  it('It should return undefined if the object is not an object', () => {\n    const object = 1;\n    expect(get(object, 'a.b')).toEqual(undefined);\n  });\n\n  it('It should return undefined if the path is not found', () => {\n    const object = { a: 1, b: { c: 1 } };\n    expect(get(object, 'a.b.c')).toEqual(undefined);\n  });\n\n  it('It should return default value if the path is not found', () => {\n    const object = { a: 1, b: { c: 1 } };\n    const defaultValue = 10;\n    expect(get(object, 'a.b.d', defaultValue)).toEqual(10);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/util/tests/unit/util.getConfig.test.js",
    "content": "import { getConfig } from '../../getConfig.js';\n\ndescribe('Test until getConfig', () => {\n  it('It should return the default value if path is invalid', () => {\n    expect(getConfig('a.b', 1)).toEqual(1);\n    expect(getConfig('a.b')).toEqual(undefined);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/util/tests/unit/util.hookable.test.js",
    "content": "import {\n  hookable,\n  hookBefore,\n  getHooks,\n  clearHooks,\n  lockHooks\n} from '../../hookable.js';\nimport { jest, describe, it, expect } from '@jest/globals';\n\ndescribe('hookBefore', () => {\n  it('It should add before hook to the registry', () => {\n    const callback = () => {};\n    hookBefore('test', callback);\n    const { beforeHooks } = getHooks();\n    expect(beforeHooks.get('test')).toEqual([\n      {\n        callback,\n        priority: 10\n      }\n    ]);\n  });\n\n  it('It should throw error if priority is not a number', () => {\n    const callback = () => {};\n    expect(() => hookBefore('test', callback, 'abc')).toThrow(Error);\n  });\n\n  it('It should add before hook to the registry with priority', () => {\n    const negativeCallback = () => {};\n    const beforeCallback = () => {};\n    const afterCallback = () => {};\n\n    hookBefore('test2', beforeCallback, 5);\n    hookBefore('test2', negativeCallback, -5);\n    hookBefore('test2', afterCallback, 20);\n    const { beforeHooks } = getHooks();\n    expect(JSON.stringify(beforeHooks.get('test2'))).toEqual(\n      JSON.stringify([\n        {\n          callback: negativeCallback,\n          priority: -5\n        },\n        {\n          callback: beforeCallback,\n          priority: 5\n        },\n        {\n          callback: afterCallback,\n          priority: 20\n        }\n      ])\n    );\n  });\n});\n\ndescribe('hookable', () => {\n  it('It should throw error if the original function is not a named function', () => {\n    expect(() => hookable(() => {})).toThrow(Error);\n  });\n\n  it('It should return a function', () => {\n    const func = function test() {};\n    expect(typeof hookable(func)).toEqual('function');\n  });\n\n  it('It should call the original function', () => {\n    const func = jest.fn();\n    const hookedFunc = hookable(func);\n    hookedFunc();\n    expect(func).toHaveBeenCalled();\n  });\n\n  it('It should throw error if one of the callback throws error', () => {\n    const test = jest.fn();\n    hookBefore('mockConstructor', () => {\n      throw new Error('Error');\n    });\n    const hookedFunc = hookable(test);\n    expect(() => hookedFunc()).toThrow(Error);\n  });\n\n  it('It should throw error if one of the callback throws error', async () => {\n    const test = jest.fn();\n    hookBefore('mockConstructor', async () => {\n      throw new Error('Error');\n    });\n    const hookedFunc = hookable(test);\n    await expect(async () => await hookedFunc()).rejects.toThrow('Error');\n  });\n\n  it('It should call the before hook in correct order', () => {\n    clearHooks();\n    const data = [];\n    const test = jest.fn();\n    const beforeCallback1 = jest.fn(() => data.push(1));\n    const beforeCallback2 = jest.fn(() => data.push(2));\n    const beforeCallback3 = jest.fn(() => data.push(3));\n    hookBefore('mockConstructor', beforeCallback1);\n    hookBefore('mockConstructor', beforeCallback2);\n    hookBefore('mockConstructor', beforeCallback3, 1);\n    const hookedFunc = hookable(test);\n    hookedFunc();\n    expect(beforeCallback1).toHaveBeenCalled();\n    expect(beforeCallback2).toHaveBeenCalled();\n    expect(beforeCallback3).toHaveBeenCalled();\n    expect(data).toEqual([3, 1, 2]);\n  });\n\n  it('It should call the before hook in correct order async', () => {\n    clearHooks();\n    const data = [];\n    const test = jest.fn(async () => new Promise((resolve) => resolve()));\n    const beforeCallback1 = jest.fn(\n      async () => new Promise((resolve) => resolve(data.push(1)))\n    );\n    const beforeCallback2 = jest.fn(\n      async () => new Promise((resolve) => resolve(data.push(2)))\n    );\n    const beforeCallback3 = jest.fn(\n      async () => new Promise((resolve) => resolve(data.push(3)))\n    );\n    hookBefore('mockConstructor', beforeCallback1);\n    hookBefore('mockConstructor', beforeCallback2);\n    hookBefore('mockConstructor', beforeCallback3, 1);\n    const hookedFunc = hookable(test);\n    hookedFunc();\n    expect(beforeCallback1).toHaveBeenCalled();\n    expect(beforeCallback2).toHaveBeenCalled();\n    expect(beforeCallback3).toHaveBeenCalled();\n    expect(data).toEqual([3, 1, 2]);\n  });\n\n  it('It should call the before hook in correct order async', async () => {\n    clearHooks();\n    const data = [];\n    const test = jest.fn(\n      async () =>\n        new Promise((resolve) => {\n          data.push(0);\n          setTimeout(() => {\n            data.push(4);\n            resolve();\n          }, 1000);\n        })\n    );\n    const beforeCallback1 = jest.fn(\n      async () => new Promise((resolve) => resolve(data.push(1)))\n    );\n    const beforeCallback2 = jest.fn(\n      async () => new Promise((resolve) => resolve(data.push(2)))\n    );\n    const beforeCallback3 = jest.fn(() => data.push(3));\n    hookBefore('mockConstructor', beforeCallback1);\n    hookBefore('mockConstructor', beforeCallback2);\n    hookBefore('mockConstructor', beforeCallback3);\n    const hookedFunc = hookable(test);\n    await hookedFunc();\n    expect(beforeCallback1).toHaveBeenCalled();\n    expect(beforeCallback2).toHaveBeenCalled();\n    expect(beforeCallback3).toHaveBeenCalled();\n    expect(data).toEqual([1, 2, 3, 0, 4]);\n  });\n\n  it('It should call the original function with correct argument', () => {\n    const test = jest.fn();\n    const hookedFunc = hookable(test);\n    hookedFunc(1, 2, 3);\n    expect(test).toHaveBeenCalledWith(1, 2, 3);\n  });\n\n  it('It should call the callback with correct context', () => {\n    const test = jest.fn();\n    const beforeCallback = jest.fn(function () {\n      expect(this).toEqual({ test: 1 });\n    });\n    hookBefore('mockConstructor', beforeCallback);\n    const hookedFunc = hookable(test, { test: 1 });\n    hookedFunc();\n    expect(beforeCallback).toHaveBeenCalled();\n  });\n});\n\ndescribe('lockHooks', () => {\n  it('It should throw error if the hook is locked', () => {\n    lockHooks();\n    const callback = () => {};\n    expect(() => hookBefore('test', callback)).toThrow(Error);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/util/tests/unit/util.jwt.test.js",
    "content": "import {\n  generateToken,\n  generateRefreshToken,\n  verifyToken,\n  verifyRefreshToken,\n  decodeToken,\n  TOKEN_TYPES\n} from '../../jwt.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n\ndescribe('JWT Utility Functions', () => {\n  // Set up environment variables for testing\n  beforeAll(() => {\n    process.env.JWT_ISSUER = 'evershop-test';\n    process.env.JWT_ADMIN_SECRET = 'test-admin-secret-key-at-least-32-chars';\n    process.env.JWT_ADMIN_REFRESH_SECRET =\n      'test-admin-refresh-secret-key-at-least-32-chars';\n    process.env.JWT_ADMIN_TOKEN_EXPIRY = '3600'; // 1 hour in seconds\n    process.env.JWT_ADMIN_REFRESH_TOKEN_EXPIRY = '604800'; // 7 days in seconds\n    process.env.JWT_CUSTOMER_SECRET =\n      'test-customer-secret-key-at-least-32-chars';\n    process.env.JWT_CUSTOMER_REFRESH_SECRET =\n      'test-customer-refresh-secret-key-at-least-32-chars';\n    process.env.JWT_CUSTOMER_TOKEN_EXPIRY = '7200'; // 2 hours in seconds\n    process.env.JWT_CUSTOMER_REFRESH_TOKEN_EXPIRY = '2592000'; // 30 days in seconds\n  });\n\n  describe('generateToken', () => {\n    it('should generate a valid admin access token', () => {\n      const payload = {\n        user: {\n          userId: 1,\n          email: 'admin@example.com',\n          fullName: 'Admin User'\n        }\n      };\n\n      const token = generateToken(payload, TOKEN_TYPES.ADMIN);\n\n      expect(token).toBeDefined();\n      expect(typeof token).toBe('string');\n      expect(token.split('.').length).toBe(3); // JWT has 3 parts\n    });\n\n    it('should generate a valid customer access token', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n\n      expect(token).toBeDefined();\n      expect(typeof token).toBe('string');\n    });\n\n    it('should include tokenType and tokenKind in payload', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n      const decoded = decodeToken(token);\n\n      expect(decoded.tokenType).toBe(TOKEN_TYPES.CUSTOMER);\n      expect(decoded.tokenKind).toBe('access');\n    });\n\n    it('should throw error if secret is not configured', () => {\n      const originalSecret = process.env.JWT_ADMIN_SECRET;\n      delete process.env.JWT_ADMIN_SECRET;\n\n      const payload = {\n        user: {\n          userId: 1,\n          email: 'admin@example.com',\n          fullName: 'Admin User'\n        }\n      };\n\n      expect(() => {\n        generateToken(payload, TOKEN_TYPES.ADMIN);\n      }).toThrow('JWT secret for admin is not configured');\n\n      process.env.JWT_ADMIN_SECRET = originalSecret;\n    });\n\n    it('should use custom expiry when provided', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateToken(payload, TOKEN_TYPES.CUSTOMER, 300); // 5 minutes\n      const decoded = decodeToken(token);\n\n      const expiryTime = decoded.exp - decoded.iat;\n      expect(expiryTime).toBe(300);\n    });\n  });\n\n  describe('verifyToken', () => {\n    it('should verify a valid admin access token', () => {\n      const payload = {\n        user: {\n          userId: 1,\n          email: 'admin@example.com',\n          fullName: 'Admin User'\n        }\n      };\n\n      const token = generateToken(payload, TOKEN_TYPES.ADMIN);\n      const decoded = verifyToken(token, TOKEN_TYPES.ADMIN);\n\n      expect(decoded).toBeDefined();\n      expect(decoded.user.userId).toBe(1);\n      expect(decoded.user.email).toBe('admin@example.com');\n      expect(decoded.tokenType).toBe(TOKEN_TYPES.ADMIN);\n      expect(decoded.tokenKind).toBe('access');\n    });\n\n    it('should verify a valid customer access token', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n      const decoded = verifyToken(token, TOKEN_TYPES.CUSTOMER);\n\n      expect(decoded).toBeDefined();\n      expect(decoded.customer.customerId).toBe(1);\n      expect(decoded.customer.email).toBe('customer@example.com');\n      expect(decoded.tokenType).toBe(TOKEN_TYPES.CUSTOMER);\n    });\n\n    it('should reject token with wrong token type', () => {\n      const payload = {\n        user: {\n          userId: 1,\n          email: 'admin@example.com',\n          fullName: 'Admin User'\n        }\n      };\n\n      const adminToken = generateToken(payload, TOKEN_TYPES.ADMIN);\n\n      expect(() => {\n        verifyToken(adminToken, TOKEN_TYPES.CUSTOMER);\n      }).toThrow('Invalid token');\n    });\n\n    it('should reject refresh token used as access token', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const refreshToken = generateRefreshToken(payload, TOKEN_TYPES.CUSTOMER);\n\n      expect(() => {\n        verifyToken(refreshToken, TOKEN_TYPES.CUSTOMER);\n      }).toThrow('Invalid token');\n    });\n\n    it('should reject token signed with wrong secret', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n\n      // Try to verify with admin secret (wrong token type)\n      expect(() => {\n        verifyToken(token, TOKEN_TYPES.ADMIN);\n      }).toThrow();\n    });\n\n    it('should reject expired token', (done) => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      // Generate token with 1 second expiry\n      const token = generateToken(payload, TOKEN_TYPES.CUSTOMER, 1);\n\n      // Wait for token to expire\n      setTimeout(() => {\n        expect(() => {\n          verifyToken(token, TOKEN_TYPES.CUSTOMER);\n        }).toThrow('Token has expired');\n        done();\n      }, 1500);\n    }, 3000);\n\n    it('should reject malformed token', () => {\n      expect(() => {\n        verifyToken('invalid.token.string', TOKEN_TYPES.CUSTOMER);\n      }).toThrow('Invalid token');\n    });\n\n    it('should throw error if secret is not configured', () => {\n      const originalSecret = process.env.JWT_CUSTOMER_SECRET;\n      delete process.env.JWT_CUSTOMER_SECRET;\n\n      const token = 'some.jwt.token';\n\n      expect(() => {\n        verifyToken(token, TOKEN_TYPES.CUSTOMER);\n      }).toThrow('JWT secret for customer is not configured');\n\n      process.env.JWT_CUSTOMER_SECRET = originalSecret;\n    });\n  });\n\n  describe('generateRefreshToken', () => {\n    it('should generate a valid admin refresh token', () => {\n      const payload = {\n        user: {\n          userId: 1,\n          email: 'admin@example.com',\n          fullName: 'Admin User'\n        }\n      };\n\n      const token = generateRefreshToken(payload, TOKEN_TYPES.ADMIN);\n\n      expect(token).toBeDefined();\n      expect(typeof token).toBe('string');\n      expect(token.split('.').length).toBe(3);\n    });\n\n    it('should generate a valid customer refresh token', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateRefreshToken(payload, TOKEN_TYPES.CUSTOMER);\n\n      expect(token).toBeDefined();\n      expect(typeof token).toBe('string');\n    });\n\n    it('should include tokenKind as refresh in payload', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateRefreshToken(payload, TOKEN_TYPES.CUSTOMER);\n      const decoded = decodeToken(token);\n\n      expect(decoded.tokenType).toBe(TOKEN_TYPES.CUSTOMER);\n      expect(decoded.tokenKind).toBe('refresh');\n    });\n\n    it('should have longer expiry than access token', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const accessToken = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n      const refreshToken = generateRefreshToken(payload, TOKEN_TYPES.CUSTOMER);\n\n      const decodedAccess = decodeToken(accessToken);\n      const decodedRefresh = decodeToken(refreshToken);\n\n      const accessExpiry = decodedAccess.exp - decodedAccess.iat;\n      const refreshExpiry = decodedRefresh.exp - decodedRefresh.iat;\n\n      expect(refreshExpiry).toBeGreaterThan(accessExpiry);\n    });\n\n    it('should throw error if refresh secret is not configured', () => {\n      const originalSecret = process.env.JWT_ADMIN_REFRESH_SECRET;\n      delete process.env.JWT_ADMIN_REFRESH_SECRET;\n\n      const payload = {\n        user: {\n          userId: 1,\n          email: 'admin@example.com',\n          fullName: 'Admin User'\n        }\n      };\n\n      expect(() => {\n        generateRefreshToken(payload, TOKEN_TYPES.ADMIN);\n      }).toThrow('JWT refresh secret for admin is not configured');\n\n      process.env.JWT_ADMIN_REFRESH_SECRET = originalSecret;\n    });\n  });\n\n  describe('verifyRefreshToken', () => {\n    it('should verify a valid admin refresh token', () => {\n      const payload = {\n        user: {\n          userId: 1,\n          email: 'admin@example.com',\n          fullName: 'Admin User'\n        }\n      };\n\n      const token = generateRefreshToken(payload, TOKEN_TYPES.ADMIN);\n      const decoded = verifyRefreshToken(token, TOKEN_TYPES.ADMIN);\n\n      expect(decoded).toBeDefined();\n      expect(decoded.user.userId).toBe(1);\n      expect(decoded.tokenType).toBe(TOKEN_TYPES.ADMIN);\n      expect(decoded.tokenKind).toBe('refresh');\n    });\n\n    it('should verify a valid customer refresh token', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateRefreshToken(payload, TOKEN_TYPES.CUSTOMER);\n      const decoded = verifyRefreshToken(token, TOKEN_TYPES.CUSTOMER);\n\n      expect(decoded).toBeDefined();\n      expect(decoded.customer.customerId).toBe(1);\n      expect(decoded.tokenKind).toBe('refresh');\n    });\n\n    it('should reject access token used as refresh token', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const accessToken = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n\n      expect(() => {\n        verifyRefreshToken(accessToken, TOKEN_TYPES.CUSTOMER);\n      }).toThrow('Invalid refresh token');\n    });\n\n    it('should reject refresh token with wrong token type', () => {\n      const payload = {\n        user: {\n          userId: 1,\n          email: 'admin@example.com',\n          fullName: 'Admin User'\n        }\n      };\n\n      const adminRefreshToken = generateRefreshToken(\n        payload,\n        TOKEN_TYPES.ADMIN\n      );\n\n      expect(() => {\n        verifyRefreshToken(adminRefreshToken, TOKEN_TYPES.CUSTOMER);\n      }).toThrow('Invalid refresh token');\n    });\n\n    it('should reject expired refresh token', (done) => {\n      // Temporarily set a short expiry\n      const originalExpiry = process.env.JWT_CUSTOMER_REFRESH_TOKEN_EXPIRY;\n      process.env.JWT_CUSTOMER_REFRESH_TOKEN_EXPIRY = '1';\n\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateRefreshToken(payload, TOKEN_TYPES.CUSTOMER);\n\n      // Wait for token to expire\n      setTimeout(() => {\n        expect(() => {\n          verifyRefreshToken(token, TOKEN_TYPES.CUSTOMER);\n        }).toThrow('Refresh token has expired');\n\n        process.env.JWT_CUSTOMER_REFRESH_TOKEN_EXPIRY = originalExpiry;\n        done();\n      }, 1500);\n    }, 3000);\n\n    it('should throw error if refresh secret is not configured', () => {\n      const originalSecret = process.env.JWT_CUSTOMER_REFRESH_SECRET;\n      delete process.env.JWT_CUSTOMER_REFRESH_SECRET;\n\n      const token = 'some.jwt.token';\n\n      expect(() => {\n        verifyRefreshToken(token, TOKEN_TYPES.CUSTOMER);\n      }).toThrow('JWT refresh secret for customer is not configured');\n\n      process.env.JWT_CUSTOMER_REFRESH_SECRET = originalSecret;\n    });\n  });\n\n  describe('decodeToken', () => {\n    it('should decode token without verification', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n      const decoded = decodeToken(token);\n\n      expect(decoded).toBeDefined();\n      expect(decoded.customer.customerId).toBe(1);\n      expect(decoded.customer.email).toBe('customer@example.com');\n      expect(decoded.tokenType).toBe(TOKEN_TYPES.CUSTOMER);\n      expect(decoded.tokenKind).toBe('access');\n    });\n\n    it('should decode expired token without throwing', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateToken(payload, TOKEN_TYPES.CUSTOMER, 1);\n\n      // Decode immediately should work\n      const decoded = decodeToken(token);\n      expect(decoded).toBeDefined();\n      expect(decoded.customer.customerId).toBe(1);\n    });\n\n    it('should return null for invalid token', () => {\n      const decoded = decodeToken('invalid.token');\n      expect(decoded).toBeNull();\n    });\n  });\n\n  describe('Token Security', () => {\n    it('should use different secrets for access and refresh tokens', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const accessToken = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n      const refreshToken = generateRefreshToken(payload, TOKEN_TYPES.CUSTOMER);\n\n      // Access token should fail when verified with refresh secret\n      expect(() => {\n        verifyRefreshToken(accessToken, TOKEN_TYPES.CUSTOMER);\n      }).toThrow();\n\n      // Refresh token should fail when verified with access secret\n      expect(() => {\n        verifyToken(refreshToken, TOKEN_TYPES.CUSTOMER);\n      }).toThrow();\n    });\n\n    it('should prevent token type confusion between admin and customer', () => {\n      const adminPayload = {\n        user: {\n          userId: 1,\n          email: 'admin@example.com',\n          fullName: 'Admin User'\n        }\n      };\n\n      const customerPayload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const adminToken = generateToken(adminPayload, TOKEN_TYPES.ADMIN);\n      const customerToken = generateToken(\n        customerPayload,\n        TOKEN_TYPES.CUSTOMER\n      );\n\n      // Admin token should not verify as customer token\n      expect(() => {\n        verifyToken(adminToken, TOKEN_TYPES.CUSTOMER);\n      }).toThrow();\n\n      // Customer token should not verify as admin token\n      expect(() => {\n        verifyToken(customerToken, TOKEN_TYPES.ADMIN);\n      }).toThrow();\n    });\n\n    it('should include audience and issuer in token', () => {\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const token = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n      const decoded = decodeToken(token);\n\n      expect(decoded.aud).toBe(TOKEN_TYPES.CUSTOMER);\n      expect(decoded.iss).toBe('evershop-test');\n    });\n  });\n\n  describe('Integration Tests', () => {\n    it('should support full token refresh flow', () => {\n      // 1. Generate initial tokens\n      const payload = {\n        customer: {\n          customerId: 1,\n          email: 'customer@example.com',\n          fullName: 'Customer User',\n          groupId: 1\n        }\n      };\n\n      const accessToken = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n      const refreshToken = generateRefreshToken(payload, TOKEN_TYPES.CUSTOMER);\n\n      // 2. Verify access token\n      const decodedAccess = verifyToken(accessToken, TOKEN_TYPES.CUSTOMER);\n      expect(decodedAccess.customer.customerId).toBe(1);\n\n      // 3. Verify refresh token\n      const decodedRefresh = verifyRefreshToken(\n        refreshToken,\n        TOKEN_TYPES.CUSTOMER\n      );\n      expect(decodedRefresh.customer.customerId).toBe(1);\n\n      // 4. Generate new access token using refresh token data\n      const newAccessToken = generateToken(\n        { customer: decodedRefresh.customer },\n        TOKEN_TYPES.CUSTOMER\n      );\n      const decodedNewAccess = verifyToken(\n        newAccessToken,\n        TOKEN_TYPES.CUSTOMER\n      );\n      expect(decodedNewAccess.customer.customerId).toBe(1);\n    });\n\n    it('should handle admin login/refresh flow', () => {\n      const payload = {\n        user: {\n          userId: 123,\n          email: 'admin@example.com',\n          fullName: 'Admin User'\n        }\n      };\n\n      // Login - generate both tokens\n      const accessToken = generateToken(payload, TOKEN_TYPES.ADMIN);\n      const refreshToken = generateRefreshToken(payload, TOKEN_TYPES.ADMIN);\n\n      // Verify access token works\n      const verifiedAccess = verifyToken(accessToken, TOKEN_TYPES.ADMIN);\n      expect(verifiedAccess.user.userId).toBe(123);\n\n      // Use refresh token to get new access token\n      const verifiedRefresh = verifyRefreshToken(\n        refreshToken,\n        TOKEN_TYPES.ADMIN\n      );\n      const newAccessToken = generateToken(\n        { user: verifiedRefresh.user },\n        TOKEN_TYPES.ADMIN\n      );\n\n      // Verify new access token\n      const verifiedNewAccess = verifyToken(newAccessToken, TOKEN_TYPES.ADMIN);\n      expect(verifiedNewAccess.user.userId).toBe(123);\n    });\n  });\n\n  describe('Environment Configuration Tests', () => {\n    let originalEnv;\n\n    beforeAll(() => {\n      // Save original environment variables\n      originalEnv = {\n        JWT_ADMIN_SECRET: process.env.JWT_ADMIN_SECRET,\n        JWT_ADMIN_REFRESH_SECRET: process.env.JWT_ADMIN_REFRESH_SECRET,\n        JWT_CUSTOMER_SECRET: process.env.JWT_CUSTOMER_SECRET,\n        JWT_CUSTOMER_REFRESH_SECRET: process.env.JWT_CUSTOMER_REFRESH_SECRET\n      };\n    });\n\n    afterAll(() => {\n      // Restore original environment variables\n      process.env.JWT_ADMIN_SECRET = originalEnv.JWT_ADMIN_SECRET;\n      process.env.JWT_ADMIN_REFRESH_SECRET =\n        originalEnv.JWT_ADMIN_REFRESH_SECRET;\n      process.env.JWT_CUSTOMER_SECRET = originalEnv.JWT_CUSTOMER_SECRET;\n      process.env.JWT_CUSTOMER_REFRESH_SECRET =\n        originalEnv.JWT_CUSTOMER_REFRESH_SECRET;\n    });\n\n    describe('Missing Access Token Secrets', () => {\n      it('should throw error when admin access token secret is missing', () => {\n        delete process.env.JWT_ADMIN_SECRET;\n\n        const payload = {\n          user: {\n            userId: 1,\n            email: 'admin@example.com',\n            fullName: 'Admin User'\n          }\n        };\n\n        expect(() => {\n          generateToken(payload, TOKEN_TYPES.ADMIN);\n        }).toThrow('JWT secret for admin is not configured');\n\n        // Restore for other tests\n        process.env.JWT_ADMIN_SECRET = originalEnv.JWT_ADMIN_SECRET;\n      });\n\n      it('should throw error when customer access token secret is missing', () => {\n        delete process.env.JWT_CUSTOMER_SECRET;\n\n        const payload = {\n          customer: {\n            customerId: 1,\n            email: 'customer@example.com',\n            fullName: 'Customer User',\n            groupId: 1\n          }\n        };\n\n        expect(() => {\n          generateToken(payload, TOKEN_TYPES.CUSTOMER);\n        }).toThrow('JWT secret for customer is not configured');\n\n        // Restore for other tests\n        process.env.JWT_CUSTOMER_SECRET = originalEnv.JWT_CUSTOMER_SECRET;\n      });\n\n      it('should throw error when verifying token with missing admin secret', () => {\n        // First generate a token with valid secret\n        const payload = {\n          user: {\n            userId: 1,\n            email: 'admin@example.com',\n            fullName: 'Admin User'\n          }\n        };\n        const token = generateToken(payload, TOKEN_TYPES.ADMIN);\n\n        // Now delete the secret\n        delete process.env.JWT_ADMIN_SECRET;\n\n        expect(() => {\n          verifyToken(token, TOKEN_TYPES.ADMIN);\n        }).toThrow('JWT secret for admin is not configured');\n\n        // Restore for other tests\n        process.env.JWT_ADMIN_SECRET = originalEnv.JWT_ADMIN_SECRET;\n      });\n\n      it('should throw error when verifying token with missing customer secret', () => {\n        // First generate a token with valid secret\n        const payload = {\n          customer: {\n            customerId: 1,\n            email: 'customer@example.com',\n            fullName: 'Customer User',\n            groupId: 1\n          }\n        };\n        const token = generateToken(payload, TOKEN_TYPES.CUSTOMER);\n\n        // Now delete the secret\n        delete process.env.JWT_CUSTOMER_SECRET;\n\n        expect(() => {\n          verifyToken(token, TOKEN_TYPES.CUSTOMER);\n        }).toThrow('JWT secret for customer is not configured');\n\n        // Restore for other tests\n        process.env.JWT_CUSTOMER_SECRET = originalEnv.JWT_CUSTOMER_SECRET;\n      });\n    });\n\n    describe('Missing Refresh Token Secrets', () => {\n      it('should throw error when admin refresh token secret is missing', () => {\n        delete process.env.JWT_ADMIN_REFRESH_SECRET;\n\n        const payload = {\n          user: {\n            userId: 1,\n            email: 'admin@example.com',\n            fullName: 'Admin User'\n          }\n        };\n\n        expect(() => {\n          generateRefreshToken(payload, TOKEN_TYPES.ADMIN);\n        }).toThrow('JWT refresh secret for admin is not configured');\n\n        // Restore for other tests\n        process.env.JWT_ADMIN_REFRESH_SECRET =\n          originalEnv.JWT_ADMIN_REFRESH_SECRET;\n      });\n\n      it('should throw error when customer refresh token secret is missing', () => {\n        delete process.env.JWT_CUSTOMER_REFRESH_SECRET;\n\n        const payload = {\n          customer: {\n            customerId: 1,\n            email: 'customer@example.com',\n            fullName: 'Customer User',\n            groupId: 1\n          }\n        };\n\n        expect(() => {\n          generateRefreshToken(payload, TOKEN_TYPES.CUSTOMER);\n        }).toThrow('JWT refresh secret for customer is not configured');\n\n        // Restore for other tests\n        process.env.JWT_CUSTOMER_REFRESH_SECRET =\n          originalEnv.JWT_CUSTOMER_REFRESH_SECRET;\n      });\n\n      it('should throw error when verifying refresh token with missing admin refresh secret', () => {\n        // First generate a refresh token with valid secret\n        const payload = {\n          user: {\n            userId: 1,\n            email: 'admin@example.com',\n            fullName: 'Admin User'\n          }\n        };\n        const token = generateRefreshToken(payload, TOKEN_TYPES.ADMIN);\n\n        // Now delete the refresh secret\n        delete process.env.JWT_ADMIN_REFRESH_SECRET;\n\n        expect(() => {\n          verifyRefreshToken(token, TOKEN_TYPES.ADMIN);\n        }).toThrow('JWT refresh secret for admin is not configured');\n\n        // Restore for other tests\n        process.env.JWT_ADMIN_REFRESH_SECRET =\n          originalEnv.JWT_ADMIN_REFRESH_SECRET;\n      });\n\n      it('should throw error when verifying refresh token with missing customer refresh secret', () => {\n        // First generate a refresh token with valid secret\n        const payload = {\n          customer: {\n            customerId: 1,\n            email: 'customer@example.com',\n            fullName: 'Customer User',\n            groupId: 1\n          }\n        };\n        const token = generateRefreshToken(payload, TOKEN_TYPES.CUSTOMER);\n\n        // Now delete the refresh secret\n        delete process.env.JWT_CUSTOMER_REFRESH_SECRET;\n\n        expect(() => {\n          verifyRefreshToken(token, TOKEN_TYPES.CUSTOMER);\n        }).toThrow('JWT refresh secret for customer is not configured');\n\n        // Restore for other tests\n        process.env.JWT_CUSTOMER_REFRESH_SECRET =\n          originalEnv.JWT_CUSTOMER_REFRESH_SECRET;\n      });\n    });\n\n    describe('Missing All Secrets', () => {\n      it('should fail gracefully when all secrets are missing', () => {\n        // Remove all secrets\n        delete process.env.JWT_ADMIN_SECRET;\n        delete process.env.JWT_ADMIN_REFRESH_SECRET;\n        delete process.env.JWT_CUSTOMER_SECRET;\n        delete process.env.JWT_CUSTOMER_REFRESH_SECRET;\n\n        const adminPayload = {\n          user: { userId: 1, email: 'admin@example.com', fullName: 'Admin' }\n        };\n        const customerPayload = {\n          customer: {\n            customerId: 1,\n            email: 'customer@example.com',\n            fullName: 'Customer',\n            groupId: 1\n          }\n        };\n\n        // All token generation should fail\n        expect(() => generateToken(adminPayload, TOKEN_TYPES.ADMIN)).toThrow();\n        expect(() =>\n          generateToken(customerPayload, TOKEN_TYPES.CUSTOMER)\n        ).toThrow();\n        expect(() =>\n          generateRefreshToken(adminPayload, TOKEN_TYPES.ADMIN)\n        ).toThrow();\n        expect(() =>\n          generateRefreshToken(customerPayload, TOKEN_TYPES.CUSTOMER)\n        ).toThrow();\n\n        // Restore all secrets\n        process.env.JWT_ADMIN_SECRET = originalEnv.JWT_ADMIN_SECRET;\n        process.env.JWT_ADMIN_REFRESH_SECRET =\n          originalEnv.JWT_ADMIN_REFRESH_SECRET;\n        process.env.JWT_CUSTOMER_SECRET = originalEnv.JWT_CUSTOMER_SECRET;\n        process.env.JWT_CUSTOMER_REFRESH_SECRET =\n          originalEnv.JWT_CUSTOMER_REFRESH_SECRET;\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/util/tests/unit/util.merge.test.js",
    "content": "import { merge } from '../../merge.js';\n\ndescribe('Test until merge', () => {\n  it('It should thrown an exception if `object` is not an object or null', () => {\n    const a = 1;\n    const b = { b: 1 };\n    expect(() => merge(a, b)).toThrow(Error);\n  });\n\n  it('It should thrown an exception if `object` is not an object or null', () => {\n    const a = { a: 1 };\n    const b = null;\n    expect(() => merge(a, b)).toThrow(Error);\n  });\n\n  it('It should return an object contains all property from 2 provided object', () => {\n    const a = { a: 1 };\n    const b = { b: 1, c: 1 };\n    expect(merge(a, b)).toEqual({ a: 1, b: 1, c: 1 });\n  });\n\n  it('It should not overwrite the value from the first object if it is existed', () => {\n    const a = { a: 1 };\n    const b = { a: 2, c: 1 };\n    const c = merge(a, b);\n    expect(c.a).toEqual(2);\n  });\n\n  it('It should overwrite the value from the first object', () => {\n    const a = { a: '', c: null, d: [] };\n    const b = { a: 2, c: 1, d: 1 };\n    const c = merge(a, b);\n    expect(c.a).toEqual(2);\n    expect(c.c).toEqual(1);\n    expect(c.d).toEqual(1);\n  });\n\n  it('It should merge array property from 2 objects', () => {\n    const a = { a: [1, 2] };\n    const b = { a: [2, 3], c: 1 };\n    const c = merge(a, b);\n    expect(c.a).toEqual([1, 2, 3]);\n  });\n\n  it('It should thrown an exception if the maximum depth is exceeded', () => {\n    const a = {\n      a: { b: { c: { d: { e: { f: { g: { h: { i: { j: { k: 1 } } } } } } } } } }\n    };\n    const b = {\n      a: { b: { c: { d: { e: { f: { g: { h: { i: { j: { k: 2 } } } } } } } } } }\n    };\n    expect(() => merge(a, b, 5)).toThrow(Error);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/util/tests/unit/util.parseImageSizes.test.js",
    "content": "import {\n  parseImageSizes,\n  evaluateCondition,\n  evaluateMediaQuery,\n  convertToPixels\n} from '../../parseImageSizes.js';\n\ndescribe('parseImageSizes', () => {\n  describe('parseImageSizes function', () => {\n    test('should handle fixed pixel values', () => {\n      const result = parseImageSizes('500px');\n      expect(result).toEqual([320, 640, 750, 828]); // Based on actual implementation: sizes >= 500*0.5, slice(0,4)\n      expect(result.length).toBe(4);\n    });\n\n    test('should handle simple 100vw value', () => {\n      const result = parseImageSizes('100vw');\n      expect(result).toEqual([\n        320, 640, 750, 828, 1080, 1200, 1920, 2048, 3840\n      ]);\n      expect(result.length).toBe(9);\n    });\n\n    test('should handle complex media queries with multiple conditions', () => {\n      const sizes = '(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n      expect(result.length).toBeGreaterThanOrEqual(3);\n      expect(result.every((size) => typeof size === 'number')).toBe(true);\n    });\n\n    test('should handle single condition with viewport width', () => {\n      const sizes = '(max-width: 750px) 100vw, 50vw';\n      const result = parseImageSizes(sizes);\n      expect(result).toContain(320);\n      expect(result).toContain(640);\n      expect(result).toContain(750);\n    });\n\n    test('should ensure minimum variety of sizes', () => {\n      const result = parseImageSizes('300px');\n      expect(result.length).toBeGreaterThanOrEqual(3);\n    });\n\n    test('should remove duplicates and sort results', () => {\n      const result = parseImageSizes('100vw');\n      const sorted = [...result].sort((a, b) => a - b);\n      expect(result).toEqual(sorted);\n      expect(new Set(result).size).toBe(result.length);\n    });\n\n    test('should handle empty or invalid sizes', () => {\n      expect(() => parseImageSizes('')).toThrow(\n        'Invalid sizes attribute: must be a non-empty string'\n      );\n      expect(() => parseImageSizes('   ')).toThrow(\n        'Invalid sizes attribute: cannot be empty or whitespace only'\n      );\n      expect(() => parseImageSizes(null)).toThrow(\n        'Invalid sizes attribute: must be a non-empty string'\n      );\n      expect(() => parseImageSizes(undefined)).toThrow(\n        'Invalid sizes attribute: must be a non-empty string'\n      );\n      expect(() => parseImageSizes(123)).toThrow(\n        'Invalid sizes attribute: must be a non-empty string'\n      );\n    });\n\n    test('should throw for invalid pixel values', () => {\n      expect(() => parseImageSizes('0px')).toThrow(\n        'Invalid pixel value in sizes attribute: \"0px\" must be a positive number followed by \"px\"'\n      );\n      expect(() => parseImageSizes('-100px')).toThrow(\n        'Invalid pixel value in sizes attribute: \"-100px\" must be a positive number followed by \"px\"'\n      );\n      expect(() => parseImageSizes('abcpx')).toThrow(\n        'Invalid pixel value in sizes attribute: \"abcpx\" must be a positive number followed by \"px\"'\n      );\n    });\n\n    test('should throw for invalid conditions', () => {\n      expect(() => parseImageSizes('invalid')).toThrow(\n        'Invalid condition in sizes attribute: \"invalid\" - must contain a valid CSS length value or media query'\n      );\n      expect(() => parseImageSizes('100invalid')).toThrow(\n        'Invalid condition in sizes attribute: \"100invalid\" - must contain a valid CSS length value or media query'\n      );\n      expect(() => parseImageSizes('(max-width: 640px 100vw')).toThrow(\n        'Invalid condition in sizes attribute: \"(max-width: 640px 100vw\" - must contain a valid CSS length value or media query'\n      );\n    });\n  });\n\n  describe('evaluateCondition function', () => {\n    test('should handle condition with media query', () => {\n      const condition = '(max-width: 640px) 100vw';\n      const result = evaluateCondition(condition, 500);\n      expect(result).toBe(500); // 100vw at 500px device = 500px\n    });\n\n    test('should handle condition without media query', () => {\n      const condition = '50vw';\n      const result = evaluateCondition(condition, 1000);\n      expect(result).toBe(500); // 50vw at 1000px device = 500px\n    });\n\n    test('should handle fixed pixel values', () => {\n      const condition = '300px';\n      const result = evaluateCondition(condition, 1000);\n      expect(result).toBe(300);\n    });\n\n    test('should return null for non-matching media query', () => {\n      const condition = '(max-width: 500px) 100vw';\n      const result = evaluateCondition(condition, 800);\n      expect(result).toBeNull();\n    });\n\n    test('should handle em units', () => {\n      const condition = '20em';\n      const result = evaluateCondition(condition, 1000);\n      expect(result).toBe(320); // 20em * 16px = 320px\n    });\n\n    test('should handle rem units', () => {\n      const condition = '25rem';\n      const result = evaluateCondition(condition, 1000);\n      expect(result).toBe(400); // 25rem * 16px = 400px\n    });\n\n    test('should handle ch units', () => {\n      const condition = '50ch';\n      const result = evaluateCondition(condition, 1000);\n      expect(result).toBe(400); // 50ch * 8px = 400px\n    });\n\n    test('should handle vw units', () => {\n      const condition = '75vw';\n      const result = evaluateCondition(condition, 800);\n      expect(result).toBe(600); // 75% of 800px = 600px\n    });\n\n    test('should handle vh units', () => {\n      const condition = '50vh';\n      const result = evaluateCondition(condition, 1000);\n      // For 1000px device, assumed height is 1000 * 0.6 = 600px (desktop), so 50vh = 300px\n      expect(result).toBe(300);\n    });\n\n    test('should trim whitespace', () => {\n      const condition = '  50vw  ';\n      const result = evaluateCondition(condition, 1000);\n      expect(result).toBe(500);\n    });\n  });\n\n  describe('evaluateMediaQuery function', () => {\n    test('should handle max-width queries', () => {\n      expect(evaluateMediaQuery('max-width: 640px', 500)).toBe(true);\n      expect(evaluateMediaQuery('max-width: 640px', 640)).toBe(true);\n      expect(evaluateMediaQuery('max-width: 640px', 800)).toBe(false);\n    });\n\n    test('should handle min-width queries', () => {\n      expect(evaluateMediaQuery('min-width: 640px', 800)).toBe(true);\n      expect(evaluateMediaQuery('min-width: 640px', 640)).toBe(true);\n      expect(evaluateMediaQuery('min-width: 640px', 500)).toBe(false);\n    });\n\n    test('should handle max-device-width queries', () => {\n      expect(evaluateMediaQuery('max-device-width: 750px', 600)).toBe(true);\n      expect(evaluateMediaQuery('max-device-width: 750px', 750)).toBe(true);\n      expect(evaluateMediaQuery('max-device-width: 750px', 800)).toBe(false);\n    });\n\n    test('should handle min-device-width queries', () => {\n      expect(evaluateMediaQuery('min-device-width: 750px', 1000)).toBe(true);\n      expect(evaluateMediaQuery('min-device-width: 750px', 750)).toBe(true);\n      expect(evaluateMediaQuery('min-device-width: 750px', 600)).toBe(false);\n    });\n\n    test('should handle decimal values in media queries', () => {\n      expect(evaluateMediaQuery('max-width: 749.5px', 749)).toBe(true);\n      expect(evaluateMediaQuery('max-width: 749.5px', 750)).toBe(false);\n    });\n\n    test('should handle orientation landscape', () => {\n      // Assuming landscape is when width >= height (simplified)\n      expect(evaluateMediaQuery('orientation: landscape', 1920)).toBe(true);\n    });\n\n    test('should handle orientation portrait', () => {\n      // Assuming portrait is when width < height (simplified)\n      expect(evaluateMediaQuery('orientation: portrait', 375)).toBe(true);\n    });\n\n    test('should handle aspect-ratio queries', () => {\n      // Basic aspect-ratio test\n      const result = evaluateMediaQuery('aspect-ratio: 16/9', 1920);\n      expect(typeof result).toBe('boolean');\n    });\n\n    test('should handle whitespace in queries', () => {\n      expect(evaluateMediaQuery('  max-width  :  640px  ', 500)).toBe(true);\n    });\n\n    test('should return true for unsupported queries', () => {\n      const result = evaluateMediaQuery('unsupported-feature: value', 1000);\n      expect(result).toBe(true); // Implementation returns true for unknown queries\n    });\n  });\n\n  describe('convertToPixels function', () => {\n    test('should convert px values', () => {\n      expect(convertToPixels(100, 'px', 1000)).toBe(100);\n    });\n\n    test('should convert em values', () => {\n      expect(convertToPixels(2, 'em', 1000)).toBe(32); // 2em * 16px = 32px\n    });\n\n    test('should convert rem values', () => {\n      expect(convertToPixels(1.5, 'rem', 1000)).toBe(24); // 1.5rem * 16px = 24px\n    });\n\n    test('should convert ch values', () => {\n      expect(convertToPixels(10, 'ch', 1000)).toBe(80); // 10ch * 8px = 80px\n    });\n\n    test('should convert vw values', () => {\n      expect(convertToPixels(50, 'vw', 1000)).toBe(500); // 50vw of 1000px = 500px\n    });\n\n    test('should convert vh values', () => {\n      expect(convertToPixels(25, 'vh', 800)).toBe(120); // For 800px: assumedHeight = 800*1.5=1200, 25vh = 300, but for desktop (>750) it's 800*0.6=480, so 25vh=120\n    });\n\n    test('should convert vmin values', () => {\n      expect(convertToPixels(50, 'vmin', 1000)).toBe(300); // For 1000px desktop: min(1000, 600) = 600, so 50vmin = 300px\n    });\n\n    test('should convert vmax values', () => {\n      expect(convertToPixels(50, 'vmax', 1000)).toBe(500); // For 1000px desktop: max(1000, 600) = 1000, so 50vmax = 500px\n    });\n\n    test('should convert percentage values', () => {\n      expect(convertToPixels(75, '%', 800)).toBe(600); // 75% of 800px = 600px\n    });\n\n    test('should handle edge cases', () => {\n      expect(convertToPixels(0, 'px', 1000)).toBe(0);\n      expect(convertToPixels(100, 'unknown', 1000)).toBe(100); // fallback\n    });\n\n    test('should handle decimal values', () => {\n      expect(convertToPixels(1.5, 'em', 1000)).toBe(24);\n      expect(convertToPixels(33.33, 'vw', 900)).toBe(300); // ~33.33vw of 900px\n    });\n  });\n\n  describe('integration tests', () => {\n    test('should handle realistic mobile-first responsive sizes', () => {\n      const sizes = '(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw';\n      const result = parseImageSizes(sizes);\n\n      expect(result).toContain(320); // Mobile full width\n      expect(result).toContain(640); // Mobile breakpoint\n      // More flexible checks based on actual implementation\n      expect(result.some((size) => size >= 300 && size <= 700)).toBe(true); // Some tablet/desktop sizes\n      expect(result.some((size) => size >= 200 && size <= 500)).toBe(true); // Some smaller sizes\n    });\n\n    test('should handle complex breakpoints with em units', () => {\n      const sizes = '(max-width: 40em) 100vw, (max-width: 64em) 50vw, 25vw';\n      const result = parseImageSizes(sizes);\n\n      expect(Array.isArray(result)).toBe(true);\n      expect(result.length).toBeGreaterThanOrEqual(3);\n    });\n\n    test('should maintain performance with many conditions', () => {\n      const sizes =\n        '(max-width: 320px) 100vw, (max-width: 640px) 90vw, (max-width: 750px) 80vw, (max-width: 1080px) 70vw, (max-width: 1200px) 60vw, 50vw';\n\n      const startTime = performance.now();\n      const result = parseImageSizes(sizes);\n      const endTime = performance.now();\n\n      expect(endTime - startTime).toBeLessThan(10); // Should complete in less than 10ms\n      expect(Array.isArray(result)).toBe(true);\n    });\n  });\n\n  describe('comprehensive CSS units coverage', () => {\n    describe('absolute length units', () => {\n      test('should handle px units', () => {\n        expect(convertToPixels(100, 'px', 1000)).toBe(100);\n      });\n\n      test('should handle pt units (points)', () => {\n        // Note: Implementation should support pt (1pt = 1.33px)\n        const result = convertToPixels(12, 'pt', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle pc units (picas)', () => {\n        // Note: Implementation should support pc (1pc = 16px)\n        const result = convertToPixels(1, 'pc', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle in units (inches)', () => {\n        // Note: Implementation should support in (1in = 96px)\n        const result = convertToPixels(1, 'in', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle cm units (centimeters)', () => {\n        // Note: Implementation should support cm (1cm = 37.8px)\n        const result = convertToPixels(1, 'cm', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle mm units (millimeters)', () => {\n        // Note: Implementation should support mm (1mm = 3.78px)\n        const result = convertToPixels(10, 'mm', 1000);\n        expect(typeof result).toBe('number');\n      });\n    });\n\n    describe('relative length units', () => {\n      test('should handle em units', () => {\n        expect(convertToPixels(2, 'em', 1000)).toBe(32); // 2em * 16px = 32px\n      });\n\n      test('should handle rem units', () => {\n        expect(convertToPixels(1.5, 'rem', 1000)).toBe(24); // 1.5rem * 16px = 24px\n      });\n\n      test('should handle ex units', () => {\n        // Note: Implementation should support ex (x-height, typically ~0.5em)\n        const result = convertToPixels(2, 'ex', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle ch units', () => {\n        expect(convertToPixels(10, 'ch', 1000)).toBe(80); // 10ch * 8px = 80px\n      });\n\n      test('should handle ic units', () => {\n        // Note: Implementation should support ic (ideographic character width)\n        const result = convertToPixels(5, 'ic', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle lh units', () => {\n        // Note: Implementation should support lh (line height)\n        const result = convertToPixels(2, 'lh', 1000);\n        expect(typeof result).toBe('number');\n      });\n    });\n\n    describe('viewport units', () => {\n      test('should handle vw units', () => {\n        expect(convertToPixels(50, 'vw', 1000)).toBe(500); // 50vw of 1000px = 500px\n      });\n\n      test('should handle vh units', () => {\n        expect(convertToPixels(25, 'vh', 800)).toBe(120); // Based on implementation logic\n      });\n\n      test('should handle vmin units', () => {\n        expect(convertToPixels(50, 'vmin', 1000)).toBe(300); // Based on implementation logic\n      });\n\n      test('should handle vmax units', () => {\n        expect(convertToPixels(50, 'vmax', 1000)).toBe(500); // Based on implementation logic\n      });\n\n      test('should handle vi units (inline viewport)', () => {\n        // Note: Implementation should support vi\n        const result = convertToPixels(50, 'vi', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle vb units (block viewport)', () => {\n        // Note: Implementation should support vb\n        const result = convertToPixels(50, 'vb', 1000);\n        expect(typeof result).toBe('number');\n      });\n    });\n\n    describe('container query units', () => {\n      test('should handle cqw units', () => {\n        // Note: Implementation should support cqw (container query width)\n        const result = convertToPixels(50, 'cqw', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle cqh units', () => {\n        // Note: Implementation should support cqh (container query height)\n        const result = convertToPixels(50, 'cqh', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle cqi units', () => {\n        // Note: Implementation should support cqi (container query inline)\n        const result = convertToPixels(50, 'cqi', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle cqb units', () => {\n        // Note: Implementation should support cqb (container query block)\n        const result = convertToPixels(50, 'cqb', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle cqmin units', () => {\n        // Note: Implementation should support cqmin\n        const result = convertToPixels(50, 'cqmin', 1000);\n        expect(typeof result).toBe('number');\n      });\n\n      test('should handle cqmax units', () => {\n        // Note: Implementation should support cqmax\n        const result = convertToPixels(50, 'cqmax', 1000);\n        expect(typeof result).toBe('number');\n      });\n    });\n  });\n\n  describe('complex media query combinations', () => {\n    test('should handle multiple media features', () => {\n      const sizes = '(min-width: 750px) and (max-width: 1080px) 50vw, 100vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n\n    test('should handle orientation-based queries', () => {\n      const sizes =\n        '(orientation: landscape) 50vw, (orientation: portrait) 100vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n\n    test('should handle aspect-ratio queries', () => {\n      const sizes = '(min-aspect-ratio: 16/9) 33vw, 50vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n\n    test('should handle resolution queries', () => {\n      const sizes = '(min-resolution: 2dppx) 50vw, 100vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n\n    test('should handle hover capability queries', () => {\n      const sizes = '(hover: hover) 25vw, 50vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n\n    test('should handle pointer queries', () => {\n      const sizes = '(pointer: fine) 33vw, 100vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n\n    test('should handle prefers-color-scheme queries', () => {\n      const sizes = '(prefers-color-scheme: dark) 40vw, 50vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n\n    test('should handle prefers-reduced-motion queries', () => {\n      const sizes = '(prefers-reduced-motion: reduce) 100vw, 50vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n  });\n\n  describe('edge cases and special values', () => {\n    test('should handle calc() expressions', () => {\n      const condition = 'calc(100vw - 2rem)';\n      // Note: Implementation should handle calc() expressions\n      const result = evaluateCondition(condition, 1000);\n      expect(typeof result === 'number' || result === null).toBe(true);\n    });\n\n    test('should handle min() function', () => {\n      const condition = 'min(100vw, 800px)';\n      // Note: Implementation should handle min() function\n      const result = evaluateCondition(condition, 1000);\n      expect(typeof result === 'number' || result === null).toBe(true);\n    });\n\n    test('should handle max() function', () => {\n      const condition = 'max(50vw, 400px)';\n      // Note: Implementation should handle max() function\n      const result = evaluateCondition(condition, 1000);\n      expect(typeof result === 'number' || result === null).toBe(true);\n    });\n\n    test('should handle clamp() function', () => {\n      const condition = 'clamp(200px, 50vw, 800px)';\n      // Note: Implementation should handle clamp() function\n      const result = evaluateCondition(condition, 1000);\n      expect(typeof result === 'number' || result === null).toBe(true);\n    });\n\n    test('should handle zero values', () => {\n      expect(convertToPixels(0, 'px', 1000)).toBe(0);\n      expect(convertToPixels(0, 'vw', 1000)).toBe(0);\n      expect(convertToPixels(0, 'rem', 1000)).toBe(0);\n    });\n\n    test('should handle very large values', () => {\n      expect(convertToPixels(9999, 'px', 1000)).toBe(9999);\n      expect(convertToPixels(100, 'vw', 10000)).toBe(10000);\n    });\n\n    test('should handle decimal values with many decimal places', () => {\n      expect(convertToPixels(33.333333, 'vw', 900)).toBe(300);\n      expect(convertToPixels(1.5625, 'rem', 1000)).toBe(25);\n    });\n\n    test('should handle negative values gracefully', () => {\n      expect(convertToPixels(-10, 'px', 1000)).toBe(-10);\n      expect(convertToPixels(-5, 'vw', 1000)).toBe(-50);\n    });\n  });\n\n  describe('real-world sizes attribute examples', () => {\n    test('should handle typical responsive image sizes', () => {\n      const sizes =\n        '(max-width: 320px) 280px, (max-width: 640px) 600px, (max-width: 1024px) 960px, 1200px';\n      const result = parseImageSizes(sizes);\n      expect(result).toContain(280);\n      expect(result).toContain(600);\n      expect(result).toContain(960);\n      expect(result).toContain(1200);\n    });\n\n    test('should handle Bootstrap-style breakpoints', () => {\n      const sizes =\n        '(max-width: 575.98px) 100vw, (max-width: 767.98px) 100vw, (max-width: 991.98px) 50vw, (max-width: 1199.98px) 33vw, 25vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n      expect(result.length).toBeGreaterThanOrEqual(4);\n    });\n\n    test('should handle Tailwind CSS breakpoints', () => {\n      const sizes =\n        '(max-width: 640px) 100vw, (max-width: 750px) 100vw, (max-width: 1080px) 50vw, (max-width: 1200px) 33vw, 25vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n      expect(result.length).toBeGreaterThanOrEqual(4);\n    });\n\n    test('should handle art direction scenarios', () => {\n      const sizes =\n        '(orientation: portrait) and (max-width: 480px) 100vw, (orientation: landscape) and (max-height: 480px) 100vh, 50vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n\n    test('should handle print media queries', () => {\n      const sizes = 'print 100%, screen and (max-width: 600px) 100vw, 50vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n  });\n\n  describe('error handling and malformed input', () => {\n    test('should handle missing closing parentheses', () => {\n      expect(() => parseImageSizes('(max-width: 640px 100vw, 50vw')).toThrow(\n        'Invalid condition in sizes attribute: \"(max-width: 640px 100vw\" - must contain a valid CSS length value or media query'\n      );\n    });\n\n    test('should handle missing opening parentheses', () => {\n      // This should actually be valid as it's just \"max-width: 640px) 100vw\" which isn't a media query\n      const sizes = 'max-width: 640px) 100vw, 50vw';\n      expect(() => parseImageSizes(sizes)).toThrow(\n        'Invalid condition in sizes attribute: \"max-width: 640px) 100vw\" - must contain a valid CSS length value or media query'\n      );\n    });\n\n    test('should handle invalid CSS units', () => {\n      expect(() => parseImageSizes('100invalid')).toThrow(\n        'Invalid condition in sizes attribute: \"100invalid\" - must contain a valid CSS length value or media query'\n      );\n    });\n\n    test('should handle mixed valid and invalid conditions', () => {\n      expect(() =>\n        parseImageSizes(\n          '(max-width: 640px) 100invalidunit, (max-width: 1024px) 50vw, 25vw'\n        )\n      ).toThrow(\n        'Invalid condition in sizes attribute: \"(max-width: 640px) 100invalidunit\" - must contain a valid CSS length value or media query'\n      );\n    });\n\n    test('should handle extra commas', () => {\n      // Extra commas should be filtered out, but valid conditions should still work\n      const sizes = '(max-width: 640px) 100vw,, 50vw,';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n\n    test('should handle spaces in unexpected places', () => {\n      // Should handle reasonable spacing variations\n      const sizes = '( max-width : 640px ) 100 vw , 50 vw';\n      const result = parseImageSizes(sizes);\n      expect(Array.isArray(result)).toBe(true);\n    });\n\n    test('should throw for completely empty conditions after filtering', () => {\n      expect(() => parseImageSizes(',,,   ,,')).toThrow(\n        'Invalid sizes attribute: no valid conditions found after parsing'\n      );\n    });\n\n    test('should throw for unmatched closing parenthesis without media query', () => {\n      expect(() => parseImageSizes('100vw) something')).toThrow(\n        'Invalid condition in sizes attribute: \"100vw) something\" - must contain a valid CSS length value or media query'\n      );\n    });\n\n    test('should throw for incomplete media queries', () => {\n      expect(() => parseImageSizes('(max-width')).toThrow(\n        'Invalid condition in sizes attribute: \"(max-width\" - must contain a valid CSS length value or media query'\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/util/tests/unit/util.preloadScan.test.js",
    "content": "import {\n  injectPreloadLinks,\n  injectPreloadLinksAfterCharset,\n  cleanupPreloadAttributes,\n  processPreloadImages\n} from '../../preloadScan.js';\n\ndescribe('preloadScan', () => {\n  describe('injectPreloadLinks function', () => {\n    test('should inject preload links before closing head tag', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Test</title>\n</head>\n<body>\n  <img src=\"/test-image.jpg\" srcSet=\"/test-image.jpg 500w, /test-image.jpg 1000w\" sizes=\"100vw\" alt=\"Test\" itemProp=\"preload\" />\n</body>\n</html>`;\n\n      const result = injectPreloadLinks(html);\n\n      expect(result).toContain(\n        '<link rel=\"preload\" as=\"image\" href=\"/test-image.jpg\" fetchpriority=\"high\"'\n      );\n      expect(result).toContain(\n        'imagesrcset=\"/test-image.jpg 500w, /test-image.jpg 1000w\"'\n      );\n      expect(result).toContain('imagesizes=\"100vw\"');\n      expect(result).toMatch(/<link[^>]*>\\s*<\\/head>/);\n    });\n\n    test('should handle multiple preload images', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Test</title>\n</head>\n<body>\n  <img src=\"/image1.jpg\" srcSet=\"/image1.jpg 500w\" sizes=\"50vw\" alt=\"Image 1\" itemProp=\"preload\" />\n  <img src=\"/image2.jpg\" srcSet=\"/image2.jpg 800w\" sizes=\"100vw\" alt=\"Image 2\" itemProp=\"preload\" />\n  <img src=\"/normal.jpg\" alt=\"Normal\" />\n</body>\n</html>`;\n\n      const result = injectPreloadLinks(html);\n\n      expect(result).toContain('href=\"/image1.jpg\"');\n      expect(result).toContain('href=\"/image2.jpg\"');\n      expect(result).not.toContain('href=\"/normal.jpg\"');\n      expect((result.match(/<link rel=\"preload\"/g) || []).length).toBe(2);\n    });\n\n    test('should return original HTML if no preload images found', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Test</title>\n</head>\n<body>\n  <img src=\"/normal-image.jpg\" alt=\"Normal\" />\n</body>\n</html>`;\n\n      const result = injectPreloadLinks(html);\n      expect(result).toBe(html);\n    });\n\n    test('should handle missing head tag gracefully', () => {\n      const html = `<body>\n  <img src=\"/test-image.jpg\" itemProp=\"preload\" />\n</body>`;\n\n      const result = injectPreloadLinks(html);\n      expect(result).toBe(html);\n    });\n\n    test('should handle images with crossorigin attribute', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n</head>\n<body>\n  <img src=\"/test-image.jpg\" crossorigin=\"anonymous\" itemProp=\"preload\" />\n</body>\n</html>`;\n\n      const result = injectPreloadLinks(html);\n      expect(result).toContain('crossorigin=\"anonymous\"');\n    });\n  });\n\n  describe('injectPreloadLinksAfterCharset function', () => {\n    test('should inject preload links after charset meta tag', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n  <title>Test</title>\n</head>\n<body>\n  <img src=\"/test-image.jpg\" srcSet=\"/test-image.jpg 500w\" sizes=\"100vw\" itemProp=\"preload\" />\n</body>\n</html>`;\n\n      const result = injectPreloadLinksAfterCharset(html);\n\n      expect(result).toContain('<meta charSet=\"utf-8\" />');\n      expect(result).toMatch(/<meta charSet=\"utf-8\" \\/>\\s*<link rel=\"preload\"/);\n      expect(result).toContain('href=\"/test-image.jpg\"');\n    });\n\n    test('should fallback to head injection if no charset meta found', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <title>Test</title>\n</head>\n<body>\n  <img src=\"/test-image.jpg\" itemProp=\"preload\" />\n</body>\n</html>`;\n\n      const result = injectPreloadLinksAfterCharset(html);\n\n      expect(result).toContain('<link rel=\"preload\"');\n      expect(result).toMatch(/<link[^>]*>\\s*<\\/head>/);\n    });\n\n    test('should handle charset with different quote styles', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charset='utf-8'>\n  <title>Test</title>\n</head>\n<body>\n  <img src=\"/test-image.jpg\" itemProp=\"preload\" />\n</body>\n</html>`;\n\n      const result = injectPreloadLinksAfterCharset(html);\n      expect(result).toContain('<link rel=\"preload\"');\n    });\n  });\n\n  describe('cleanupPreloadAttributes function', () => {\n    test('should remove itemProp=\"preload\" attributes', () => {\n      const html = `<img src=\"/test.jpg\" itemProp=\"preload\" alt=\"Test\" />\n<img src=\"/normal.jpg\" alt=\"Normal\" />\n<img src=\"/another.jpg\" itemProp=\"preload\" sizes=\"100vw\" />`;\n\n      const result = cleanupPreloadAttributes(html);\n\n      expect(result).not.toContain('itemProp=\"preload\"');\n      expect(result).toContain('src=\"/test.jpg\"');\n      expect(result).toContain('alt=\"Test\"');\n      expect(result).toContain('src=\"/normal.jpg\"');\n      expect(result).toContain('sizes=\"100vw\"');\n    });\n\n    test('should handle single quotes', () => {\n      const html = `<img src=\"/test.jpg\" itemProp='preload' alt=\"Test\" />`;\n      const result = cleanupPreloadAttributes(html);\n\n      expect(result).not.toContain(\"itemProp='preload'\");\n      expect(result).toContain('src=\"/test.jpg\"');\n    });\n\n    test('should handle extra whitespace around attributes', () => {\n      const html = `<img src=\"/test.jpg\"  itemProp=\"preload\"  alt=\"Test\" />`;\n      const result = cleanupPreloadAttributes(html);\n\n      expect(result).not.toContain('itemProp=\"preload\"');\n      expect(result).toContain('src=\"/test.jpg\"');\n    });\n\n    test('should return unchanged HTML if no preload attributes found', () => {\n      const html = `<img src=\"/test.jpg\" alt=\"Test\" />\n<img src=\"/normal.jpg\" alt=\"Normal\" />`;\n\n      const result = cleanupPreloadAttributes(html);\n      expect(result).toBe(html);\n    });\n  });\n\n  describe('processPreloadImages function (complete pipeline)', () => {\n    test('should process complete pipeline: inject and cleanup', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n  <title>Test</title>\n</head>\n<body>\n  <img src=\"/hero-image.jpg\" srcSet=\"/hero-image.jpg 500w, /hero-image.jpg 1000w\" sizes=\"100vw\" alt=\"Hero\" itemProp=\"preload\" />\n  <img src=\"/normal-image.jpg\" alt=\"Normal\" />\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n\n      // Should have preload link\n      expect(result).toContain(\n        '<link rel=\"preload\" as=\"image\" href=\"/hero-image.jpg\" fetchpriority=\"high\"'\n      );\n      expect(result).toContain(\n        'imagesrcset=\"/hero-image.jpg 500w, /hero-image.jpg 1000w\"'\n      );\n      expect(result).toContain('imagesizes=\"100vw\"');\n\n      // Should have cleaned up itemProp attributes\n      expect(result).not.toContain('itemProp=\"preload\"');\n\n      // Should preserve other attributes\n      expect(result).toContain('src=\"/hero-image.jpg\"');\n      expect(result).toContain('alt=\"Hero\"');\n      expect(result).toContain('src=\"/normal-image.jpg\"');\n    });\n\n    test('should handle multiple images with different attributes', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n</head>\n<body>\n  <img src=\"/image1.jpg\" srcSet=\"/image1.jpg 500w\" sizes=\"50vw\" crossorigin=\"anonymous\" itemProp=\"preload\" />\n  <img src=\"/image2.jpg\" sizes=\"100vw\" itemProp=\"preload\" />\n  <img src=\"/image3.jpg\" alt=\"Normal\" />\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n\n      // Should have two preload links\n      expect((result.match(/<link rel=\"preload\"/g) || []).length).toBe(2);\n\n      // Should include crossorigin for first image\n      expect(result).toContain('crossorigin=\"anonymous\"');\n\n      // Should not have preload links for normal image\n      expect(result).not.toContain('href=\"/image3.jpg\"');\n\n      // Should clean up all itemProp attributes\n      expect(result).not.toContain('itemProp=\"preload\"');\n    });\n\n    test('should return original HTML if no preload images', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n  <title>Test</title>\n</head>\n<body>\n  <img src=\"/normal1.jpg\" alt=\"Normal 1\" />\n  <img src=\"/normal2.jpg\" alt=\"Normal 2\" />\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n      expect(result).toBe(html);\n    });\n  });\n\n  describe('edge cases and error handling', () => {\n    test('should handle malformed img tags', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n</head>\n<body>\n  <img src=\"/test.jpg\" itemProp=\"preload\" >\n  <img src=\"/test2.jpg\" itemProp=\"preload\" alt=\"Test\"\n  <img src=\"/test3.jpg\" itemProp=\"preload\" />\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n\n      // Should handle valid tags and skip malformed ones gracefully\n      expect(result).toContain('<link rel=\"preload\"');\n      expect(result).not.toContain('itemProp=\"preload\"');\n    });\n\n    test('should handle img tags without src attribute', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n</head>\n<body>\n  <img alt=\"No src\" itemProp=\"preload\" />\n  <img src=\"/valid.jpg\" itemProp=\"preload\" />\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n\n      // Should only create preload link for image with src\n      expect((result.match(/<link rel=\"preload\"/g) || []).length).toBe(1);\n      expect(result).toContain('href=\"/valid.jpg\"');\n    });\n\n    test('should handle empty HTML', () => {\n      const result = processPreloadImages('');\n      expect(result).toBe('');\n    });\n\n    test('should handle HTML with no body', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n  <title>Test</title>\n</head>\n</html>`;\n\n      const result = processPreloadImages(html);\n      expect(result).toBe(html);\n    });\n\n    test('should handle images with special characters in attributes', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n</head>\n<body>\n  <img src=\"/test image.jpg\" srcSet=\"/test%20image.jpg 500w\" sizes=\"(max-width: 600px) 100vw, 50vw\" itemProp=\"preload\" />\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n\n      expect(result).toContain('href=\"/test image.jpg\"');\n      expect(result).toContain('imagesrcset=\"/test%20image.jpg 500w\"');\n      expect(result).toContain('imagesizes=\"(max-width: 600px) 100vw, 50vw\"');\n    });\n\n    test('should handle case-insensitive itemProp matching', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n</head>\n<body>\n  <img src=\"/test1.jpg\" ITEMPROP=\"preload\" />\n  <img src=\"/test2.jpg\" itemProp=\"PRELOAD\" />\n  <img src=\"/test3.jpg\" ItemProp=\"Preload\" />\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n\n      // Should match all variations\n      expect((result.match(/<link rel=\"preload\"/g) || []).length).toBe(3);\n      expect(result).toContain('href=\"/test1.jpg\"');\n      expect(result).toContain('href=\"/test2.jpg\"');\n      expect(result).toContain('href=\"/test3.jpg\"');\n    });\n\n    test('should handle images with data attributes', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n</head>\n<body>\n  <img src=\"/test.jpg\" data-lazy=\"true\" data-src=\"/fallback.jpg\" itemProp=\"preload\" />\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n\n      expect(result).toContain('<link rel=\"preload\"');\n      expect(result).toContain('href=\"/test.jpg\"'); // Should use src, not data-src\n      expect(result).toContain('data-lazy=\"true\"'); // Should preserve other attributes\n    });\n  });\n\n  describe('attribute extraction accuracy', () => {\n    test('should correctly extract all preload-relevant attributes', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n</head>\n<body>\n  <img \n    src=\"/complex-image.jpg\" \n    srcSet=\"/complex-image.jpg 320w, /complex-image.jpg 640w, /complex-image.jpg 1024w\" \n    sizes=\"(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw\" \n    crossorigin=\"use-credentials\"\n    alt=\"Complex Image\"\n    class=\"hero-image\"\n    itemProp=\"preload\"\n    loading=\"eager\"\n  />\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n\n      expect(result).toContain('href=\"/complex-image.jpg\"');\n      expect(result).toContain(\n        'imagesrcset=\"/complex-image.jpg 320w, /complex-image.jpg 640w, /complex-image.jpg 1024w\"'\n      );\n      expect(result).toContain(\n        'imagesizes=\"(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw\"'\n      );\n      expect(result).toContain('crossorigin=\"use-credentials\"');\n      expect(result).toContain('fetchpriority=\"high\"');\n\n      // Should preserve non-preload attributes in original img tag\n      expect(result).toContain('alt=\"Complex Image\"');\n      expect(result).toContain('class=\"hero-image\"');\n      expect(result).toContain('loading=\"eager\"');\n\n      // Should clean up itemProp\n      expect(result).not.toContain('itemProp=\"preload\"');\n    });\n\n    test('should handle missing optional attributes gracefully', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n</head>\n<body>\n  <img src=\"/minimal.jpg\" itemProp=\"preload\" />\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n\n      expect(result).toContain(\n        '<link rel=\"preload\" as=\"image\" href=\"/minimal.jpg\" fetchpriority=\"high\" />'\n      );\n      expect(result).not.toContain('imagesrcset');\n      expect(result).not.toContain('imagesizes');\n      expect(result).not.toContain('crossorigin');\n    });\n  });\n\n  describe('performance and scalability', () => {\n    test('should handle large HTML documents efficiently', () => {\n      // Create a large HTML document with many images\n      const manyImages = Array.from(\n        { length: 100 },\n        (_, i) =>\n          `<img src=\"/image${i}.jpg\" alt=\"Image ${i}\" ${\n            i % 10 === 0 ? 'itemProp=\"preload\"' : ''\n          } />`\n      ).join('\\n');\n\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n</head>\n<body>\n  ${manyImages}\n</body>\n</html>`;\n\n      const startTime = performance.now();\n      const result = processPreloadImages(html);\n      const endTime = performance.now();\n\n      // Should complete in reasonable time (less than 50ms for 100 images)\n      expect(endTime - startTime).toBeLessThan(50);\n\n      // Should find correct number of preload images (every 10th image)\n      expect((result.match(/<link rel=\"preload\"/g) || []).length).toBe(10);\n    });\n\n    test('should handle deeply nested HTML structure', () => {\n      const html = `<!DOCTYPE html>\n<html>\n<head>\n  <meta charSet=\"utf-8\" />\n</head>\n<body>\n  <div>\n    <section>\n      <article>\n        <div>\n          <figure>\n            <img src=\"/nested-image.jpg\" itemProp=\"preload\" />\n          </figure>\n        </div>\n      </article>\n    </section>\n  </div>\n</body>\n</html>`;\n\n      const result = processPreloadImages(html);\n\n      expect(result).toContain('<link rel=\"preload\"');\n      expect(result).toContain('href=\"/nested-image.jpg\"');\n      expect(result).not.toContain('itemProp=\"preload\"');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/util/tests/unit/util.registry.test.js",
    "content": "import {\n  getValue,\n  getValueSync,\n  addProcessor,\n  getProcessors\n} from '../../registry.js';\nimport { jest, describe, it, expect } from '@jest/globals';\n\ndescribe('registry', () => {\n  it('It should return the init value if no processor provided', async () => {\n    const initValue = 1;\n    const value = await getValue('test', initValue);\n    expect(value).toEqual(initValue);\n  });\n\n  it('It should add processor to the registry', () => {\n    const callback = () => {};\n    addProcessor('test', callback);\n    const processors = getProcessors('test');\n    expect(processors).toEqual([\n      {\n        callback,\n        priority: 10\n      }\n    ]);\n  });\n\n  it('It should throw error if priority is not a number', () => {\n    const callback = () => {};\n    expect(() => addProcessor('test', callback, 'abc')).toThrow(Error);\n  });\n\n  it('It should add processor to the registry with priority', () => {\n    const negativeCallback = () => {};\n    const beforeCallback = () => {};\n    const afterCallback = () => {};\n\n    addProcessor('test2', negativeCallback, -5);\n    addProcessor('test2', beforeCallback, 5);\n    addProcessor('test2', afterCallback, 20);\n    const processors = getProcessors('test2');\n    expect(JSON.stringify(processors)).toEqual(\n      JSON.stringify([\n        {\n          callback: negativeCallback,\n          priority: -5\n        },\n        {\n          callback: beforeCallback,\n          priority: 5\n        },\n        {\n          callback: afterCallback,\n          priority: 20\n        }\n      ])\n    );\n  });\n\n  it('It should throw error if callback is not a function', () => {\n    expect(() => addProcessor('test', 'abc')).toThrow(Error);\n  });\n\n  it('It should accept async function as callback', () => {\n    const callback = async () => {};\n    addProcessor('testasync', callback);\n    const processors = getProcessors('testasync');\n    expect(processors).toEqual([\n      {\n        callback,\n        priority: 10\n      }\n    ]);\n  });\n\n  it('It should execute the processor function in order', async () => {\n    const callback1 = jest.fn(async () => {\n      return 1;\n    });\n    const callback2 = jest.fn(async () => {\n      return 2;\n    });\n    const callback3 = jest.fn(async () => {\n      return 3;\n    });\n    addProcessor('test3', callback1, 10);\n    addProcessor('test3', callback2, 5);\n    addProcessor('test3', callback3, 20);\n    const value = await getValue('test3', 1);\n    expect(value).toEqual(3);\n    expect(callback3).toHaveBeenCalled();\n    expect(callback2).toHaveBeenCalled();\n    expect(callback1).toHaveBeenCalled();\n  });\n\n  it('It should skip the processors if the init value and the context are identical', async () => {\n    const callback1 = jest.fn(async () => {\n      return 1;\n    });\n    const callback2 = jest.fn(async () => {\n      return 2;\n    });\n    const callback3 = jest.fn(async () => {\n      return 3;\n    });\n    addProcessor('test4', callback1, 10);\n    addProcessor('test4', callback2, 5);\n    addProcessor('test4', callback3, 20);\n    const value = await getValue('test4', 1, { a: 1 });\n    expect(value).toEqual(3);\n    expect(callback3).toHaveBeenCalled();\n    expect(callback2).toHaveBeenCalled();\n    expect(callback1).toHaveBeenCalled();\n\n    const value2 = await getValue('test4', 1, { a: 1 });\n    expect(value2).toEqual(3);\n    expect(callback3).toHaveBeenCalledTimes(1);\n    expect(callback2).toHaveBeenCalledTimes(1);\n    expect(callback1).toHaveBeenCalledTimes(1);\n  });\n\n  it('It should overwrite the init value and the context if the init value and the context are not identical', async () => {\n    const callback = jest.fn(async (value) => {\n      return ++value;\n    });\n    addProcessor('test5', callback, 10);\n    const value = await getValue('test5', 1, { a: 1 });\n    expect(value).toEqual(2);\n    expect(callback).toHaveBeenCalled();\n    const valueAgain = await getValue('test5', 1, { a: 1 });\n    expect(valueAgain).toEqual(2);\n    expect(callback).toHaveBeenCalledTimes(1);\n\n    const value2 = await getValue('test5', 2, { a: 2 });\n    expect(value2).toEqual(3);\n    expect(callback).toHaveBeenCalledTimes(2);\n\n    const value3 = await getValue('test5', 1, { a: 1 });\n    expect(value3).toEqual(2);\n    expect(callback).toHaveBeenCalledTimes(3);\n  });\n\n  it('It should throw an error if the value does not pass the validator', async () => {\n    const callback = jest.fn(async (value) => {\n      return ++value;\n    });\n    addProcessor('test6', callback, 10);\n    expect(async () => {\n      await getValue('test6', 1, {}, (value) => {\n        return value > 3;\n      });\n    }).rejects.toThrow(Error);\n  });\n\n  it('The getValueSync function should throw if the processor is async', () => {\n    const callback = async () => {};\n    addProcessor('test7', callback);\n    expect(() => {\n      getValueSync('test7', 1);\n    }).toThrow(Error);\n  });\n\n  it('It should throw an error if one of the processor throws an error', async () => {\n    const callback1 = jest.fn(async () => {\n      return 1;\n    });\n    const callback2 = jest.fn(async () => {\n      throw new Error('error');\n    });\n    const callback3 = jest.fn(async () => {\n      return 3;\n    });\n    addProcessor('test8', callback1, 2);\n    addProcessor('test8', callback2, 5);\n    addProcessor('test8', callback3, 20);\n    expect(async () => {\n      await getValue('test8', 1);\n    }).rejects.toThrow(Error);\n    expect(callback3).not.toHaveBeenCalled();\n    expect(callback1).toHaveBeenCalled();\n  });\n\n  it('It should throw an error if one of the processor throws an error', () => {\n    const callback1 = jest.fn(() => {\n      return 1;\n    });\n    const callback2 = jest.fn(() => {\n      throw new Error('error');\n    });\n    const callback3 = jest.fn(() => {\n      return 3;\n    });\n    addProcessor('test9', callback1, 2);\n    addProcessor('test9', callback2, 5);\n    addProcessor('test9', callback3, 20);\n    expect(() => {\n      getValueSync('test9', 1);\n    }).toThrow(Error);\n    expect(callback3).not.toHaveBeenCalled();\n    expect(callback1).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/util/validateConfiguration.js",
    "content": "import { getAjv } from '../../modules/base/services/getAjv.js';\nimport { getValueSync } from './registry.js';\n\nexport function validateConfiguration(config) {\n  const ajv = getAjv();\n  const configSchema = getValueSync(\n    'configurationSchema',\n    { type: 'object' },\n    null,\n    (schema) => {\n      ajv.validateSchema(schema);\n      if (ajv.errors) {\n        throw new Error(\n          `Your configuration schema is not valid: ${ajv.errors[0].instancePath}`\n        );\n      } else {\n        return true;\n      }\n    }\n  );\n\n  // Validate by using Ajv\n  const validate = ajv.compile(configSchema);\n  const reservedKeys = [\n    'get',\n    'has',\n    'util',\n    'getConfigSources',\n    'makeHidden',\n    'makeImmutable',\n    'setModuleDefaults',\n    'watch',\n    '_attachProtoDeep',\n    '_cloneDeep',\n    '_diffDeep',\n    '_equalsDeep',\n    '_extendDeep',\n    '_get',\n    '_getCmdLineArg',\n    '_initParam',\n    '_isObject',\n    '_loadFileConfigs',\n    '_parseFile',\n    '_stripComments',\n    '_stripYamlComments'\n  ];\n  const configuration = Object.keys(config).reduce((acc, key) => {\n    if (configSchema.properties[key] || !reservedKeys.includes(key)) {\n      acc[key] = config[key];\n    }\n    return acc;\n  }, {});\n  const valid = validate(configuration);\n  if (!valid) {\n    throw new Error(errorFormatter(validate.errors));\n  } else {\n    return true;\n  }\n}\n\nfunction errorFormatter(errors) {\n  const messages = ['Invalid configuration:'];\n  errors.forEach((error) => {\n    if (error.keyword === 'errorMessage') {\n      messages.push(`${error.message}. ${error.instancePath}`);\n    } else if (error.keyword === 'additionalProperties') {\n      messages.push(\n        `${error.instancePath}/${error.params.additionalProperty} is not allowed.`\n      );\n    } else if (error.keyword === 'enum') {\n      messages.push(\n        `${\n          error.instancePath\n        } must be one of the following values: ${error.params.allowedValues.join(\n          ', '\n        )}`\n      );\n    } else {\n      messages.push(`${error.instancePath} ${error.message}`);\n    }\n  });\n  return messages.join('\\n');\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/util/validator.ts",
    "content": "export type Validator<T> = {\n  id: string;\n  func: (input: T) => boolean | Promise<boolean>;\n  errorMessage: string;\n};\n\nexport class ValidatorManager<T> {\n  private validators = new Map<string, Validator<T>>();\n\n  constructor(initial: Validator<T>[] = []) {\n    for (const v of initial) {\n      this.add(v);\n    }\n  }\n\n  add(validator: Validator<T>) {\n    this.validators.set(validator.id, validator);\n  }\n\n  async validate(input: T) {\n    const results = await Promise.allSettled(\n      Array.from(this.validators.values()).map(async (validator) => {\n        try {\n          const isValid = await validator.func(input);\n          return isValid ? null : validator.errorMessage;\n        } catch (err: any) {\n          return `${validator.errorMessage} (exception occurred)`;\n        }\n      })\n    );\n\n    const errors = results\n      .map((r) =>\n        r.status === 'fulfilled' ? r.value : 'Unknown validation error'\n      )\n      .filter((msg): msg is string => !!msg);\n\n    return {\n      valid: errors.length === 0,\n      errors\n    };\n  }\n\n  validateSync(input: T) {\n    const errors: string[] = [];\n    for (const validator of this.validators.values()) {\n      try {\n        const isValid = validator.func(input);\n        if (isValid instanceof Promise) {\n          throw new Error(\n            'Synchronous validation expected, but got async function'\n          );\n        }\n        if (!isValid) {\n          errors.push(validator.errorMessage);\n        }\n      } catch (err: any) {\n        errors.push(err.message || 'Unknown validation error');\n      }\n    }\n    return {\n      valid: errors.length === 0,\n      errors\n    };\n  }\n\n  getAllIds() {\n    return Array.from(this.validators.keys());\n  }\n\n  getValidator(id: string) {\n    return this.validators.get(id);\n  }\n\n  remove(id: string) {\n    this.validators.delete(id);\n  }\n\n  clear() {\n    this.validators.clear();\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/createBaseConfig.js",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { SwcMinifyWebpackPlugin } from 'swc-minify-webpack-plugin';\nimport { getEnabledExtensions } from '../../bin/extension/index.js';\nimport { getCoreModules } from '../../bin/lib/loadModules.js';\nimport { CONSTANTS } from '../helpers.js';\nimport { getEnabledTheme } from '../util/getEnabledTheme.js';\nimport isProductionMode from '../util/isProductionMode.js';\nimport { loadCsvTranslationFiles } from './loaders/loadTranslationFromCsv.js';\n\n// Get the directory name of the current module\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nfunction isRealDirectorySync(path) {\n  try {\n    const stats = fs.lstatSync(path);\n    if (stats.isSymbolicLink()) {\n      return false;\n    }\n    return stats.isDirectory();\n  } catch (err) {\n    if (err.code === 'ENOENT') {\n      return false;\n    }\n    throw err;\n  }\n}\n\nexport function createBaseConfig(isServer) {\n  const extenions = getEnabledExtensions();\n  const coreModules = getCoreModules();\n  const theme = getEnabledTheme();\n\n  const loaders = [\n    {\n      test: /\\.m?js$/,\n      resolve: {\n        fullySpecified: false\n      }\n    },\n    {\n      test: /\\.js$/,\n      exclude: {\n        and: [/node_modules/],\n        not: [\n          /@evershop[\\\\/]evershop/,\n          ...extenions.map((ext) => {\n            const regex = new RegExp(\n              ext.resolve.replace(/\\\\/g, '[\\\\\\\\\\\\]').replace(/\\//g, '[\\\\\\\\/]')\n            );\n            return regex;\n          })\n        ]\n      },\n      use: [\n        {\n          loader: path.resolve(\n            CONSTANTS.LIBPATH,\n            'webpack/loaders/LayoutLoader.js'\n          )\n        },\n        {\n          loader: path.resolve(\n            CONSTANTS.LIBPATH,\n            'webpack/loaders/GraphqlLoader.js'\n          )\n        },\n        {\n          loader: path.resolve(\n            CONSTANTS.LIBPATH,\n            'webpack/loaders/TranslationLoader.js'\n          ),\n          options: {\n            getTranslateData: async () => {\n              const result = await loadCsvTranslationFiles();\n              return result;\n            }\n          }\n        }\n      ]\n    }\n  ];\n\n  const output = isServer\n    ? {\n        path: CONSTANTS.BUILDPATH,\n        publicPath: CONSTANTS.BUILDPATH,\n        filename:\n          isServer === true\n            ? '[name]/server/index.js'\n            : '[name]/client/index.js',\n        pathinfo: false\n      }\n    : {\n        path: CONSTANTS.BUILDPATH,\n        publicPath: isProductionMode() ? '/assets/' : '/',\n        pathinfo: false\n      };\n\n  if (!isProductionMode()) {\n    Object.assign(output, {\n      chunkFilename: (pathData) =>\n        `${pathData.chunk.renderedHash}/client/${pathData.chunk.runtime}.js`\n    });\n  } else {\n    Object.assign(output, {\n      chunkFilename: (pathData) => `chunks/${pathData.chunk.renderedHash}.js`\n    });\n  }\n\n  if (isServer) {\n    output.library = {\n      type: 'module'\n    };\n    output.module = true;\n    output.chunkFormat = 'module';\n    output.environment = { module: true };\n    output.iife = false;\n    output.scriptType = 'module';\n  }\n  const config = {\n    mode: isProductionMode() ? 'production' : 'development',\n    module: {\n      rules: loaders\n    },\n    target: isServer === true ? 'node' : 'web',\n    output,\n    plugins: [],\n    cache: { type: 'memory' }\n  };\n\n  if (isServer) {\n    config.experiments = { outputModule: true };\n  }\n\n  // Resolve aliases\n  const alias = {\n    '@evershop/evershop/components': path.resolve(__dirname, '../../components')\n  };\n  if (theme) {\n    alias['@components'] = [path.resolve(theme.path, 'dist/components')];\n  } else {\n    alias['@components'] = [];\n  }\n\n  if (\n    !isRealDirectorySync(\n      path.resolve(CONSTANTS.ROOTPATH, 'node_modules', '@evershop', 'evershop')\n    )\n  ) {\n    alias['@evershop/evershop'] = path.resolve(\n      CONSTANTS.ROOTPATH,\n      'packages',\n      'evershop',\n      'dist'\n    );\n  }\n\n  // Resolve alias for extensions\n  extenions.forEach((ext) => {\n    alias['@components'].push(path.resolve(ext.resolve, 'dist/components'));\n  });\n  alias['@components'].push(path.resolve(__dirname, '../../components'));\n  // Avoid multiple react instances\n  alias['react'] = path.resolve(CONSTANTS.ROOTPATH, 'node_modules/react');\n  alias['react-dom'] = path.resolve(\n    CONSTANTS.ROOTPATH,\n    'node_modules/react-dom'\n  );\n  alias['webpack-hot-middleware'] = path.resolve(\n    CONSTANTS.ROOTPATH,\n    'node_modules/webpack-hot-middleware'\n  );\n  config.resolve = {\n    alias,\n    extensions: ['.js', '.json', '.wasm'],\n    extensionAlias: {\n      '.jsx': ['.js']\n    },\n    fullySpecified: true\n  };\n\n  config.optimization = {};\n\n  // Check if the flag --skip-minify is set\n  const skipMinify = process.argv.includes('--skip-minify');\n  if (isProductionMode()) {\n    config.optimization = Object.assign(config.optimization, {\n      minimize: !skipMinify,\n      minimizer: [\n        new SwcMinifyWebpackPlugin({\n          compress: true,\n          mangle: true,\n          module: true,\n          sourceMap: true,\n          keep_classnames: false,\n          keep_fnames: false,\n          safari10: true,\n          sourceMap: true\n        })\n      ]\n    });\n  }\n\n  return config;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/dev/createConfigClient.js",
    "content": "import path from 'path';\nimport ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';\nimport webpack from 'webpack';\nimport { getEnabledExtensions } from '../../../bin/extension/index.js';\nimport { CONSTANTS } from '../../helpers.js';\nimport { createBaseConfig } from '../createBaseConfig.js';\nimport { GraphqlPlugin } from '../plugins/GraphqlPlugin.js';\nimport { InjectTailwindSources } from '../plugins/InjectTailwindSources.js';\nimport { ThemeWatcherPlugin } from '../plugins/ThemeWatcherPlugin.js';\nimport { getTailwindSources } from '../util/getTailwindSources.js';\n\nexport function createConfigClient(isAdmin = false) {\n  const extensions = getEnabledExtensions();\n  const tailwindSources = getTailwindSources();\n  const config = createBaseConfig(false);\n  config.name = isAdmin ? 'bundle-client-admin' : 'bundle-client-frontstore';\n\n  // Set different output filenames for admin and frontstore to avoid conflicts\n  config.output.filename = isAdmin ? 'admin-[name].js' : '[name].js';\n  config.output.publicPath = isAdmin ? '/backend/' : '/';\n\n  const loaders = config.module.rules;\n  loaders.unshift({\n    test: /common[\\\\/]react[\\\\/]client[\\\\/]Index\\.js$/i,\n    use: [\n      {\n        loader: path.resolve(\n          CONSTANTS.LIBPATH,\n          'webpack/loaders/AreaLoader.js'\n        ),\n        options: { isAdmin }\n      }\n    ]\n  });\n\n  loaders.push({\n    test: /\\.css$/i,\n    use: [\n      {\n        loader: 'style-loader'\n      },\n      {\n        loader: 'css-loader',\n        options: {\n          url: false\n        }\n      },\n      {\n        loader: 'postcss-loader',\n        options: {\n          postcssOptions: {\n            plugins: [\n              InjectTailwindSources(tailwindSources),\n              '@tailwindcss/postcss',\n              'autoprefixer'\n            ]\n          }\n        }\n      }\n    ]\n  });\n\n  loaders.push({\n    test: /\\.scss$/i,\n    use: [\n      {\n        loader: 'style-loader'\n      },\n      {\n        loader: 'css-loader',\n        options: {\n          url: false\n        }\n      },\n      {\n        loader: 'postcss-loader',\n        options: {\n          postcssOptions: {\n            plugins: [\n              InjectTailwindSources(tailwindSources),\n              '@tailwindcss/postcss',\n              'autoprefixer'\n            ]\n          }\n        }\n      },\n      {\n        loader: 'sass-loader',\n        options: {\n          implementation: 'sass',\n          api: 'modern'\n        }\n      }\n    ]\n  });\n\n  const { plugins } = config;\n  plugins.push(new GraphqlPlugin(isAdmin));\n  plugins.push(new webpack.ProgressPlugin());\n  plugins.push(new webpack.HotModuleReplacementPlugin());\n  plugins.push(\n    new ReactRefreshWebpackPlugin({\n      overlay: false\n    })\n  );\n  plugins.push(new ThemeWatcherPlugin());\n\n  config.entry = () => {\n    const entry = [\n      path.resolve(\n        CONSTANTS.MODULESPATH,\n        '../components/common/react/client/Index.js'\n      ),\n      isAdmin\n        ? `webpack-hot-middleware/client?path=/__webpack_hmr_admin&reload=true&overlay=true`\n        : `webpack-hot-middleware/client?path=/__webpack_hmr_frontstore&reload=true&overlay=true`\n    ];\n    return entry;\n  };\n  config.watchOptions = {\n    aggregateTimeout: 300,\n    ignored: new RegExp('(^|/)[a-z][^/]*.js$'),\n    poll: 1000\n  };\n\n  // Enable source maps\n  config.devtool = 'eval-cheap-module-source-map';\n\n  // Configure snapshot management for better caching\n  // Exclude @evershop/evershop core and extensions in node_modules from managed paths\n  // This ensures webpack watches for changes in these paths\n  const nodeModuleExtensions = extensions\n    .filter((ext) => ext.path && ext.path.includes('node_modules'))\n    .map((ext) => {\n      // Extract package name from path (e.g., @vendor/package or package-name)\n      const match = ext.path.match(\n        /node_modules[\\\\/](@[^/\\\\]+[\\\\/][^/\\\\]+|[^/\\\\]+)/\n      );\n      return match ? match[1].replace(/\\\\/g, '[\\\\\\\\/]') : null;\n    })\n    .filter(Boolean)\n    .join('|');\n\n  const managedPathsPattern = nodeModuleExtensions\n    ? `^(.+?[\\\\\\\\/]node_modules[\\\\\\\\/](?!(@evershop[\\\\\\\\/]evershop|${nodeModuleExtensions}))(@.+?[\\\\\\\\/])?.+?)[\\\\\\\\/]`\n    : `^(.+?[\\\\\\\\/]node_modules[\\\\\\\\/](?!(@evershop[\\\\\\\\/]evershop))(@.+?[\\\\\\\\/])?.+?)[\\\\\\\\/]`;\n\n  config.snapshot = {\n    managedPaths: [new RegExp(managedPathsPattern)]\n  };\n\n  return config;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/getRouteBuildPath.js",
    "content": "import path from 'path';\nimport { CONSTANTS } from '../helpers.js';\nimport { getRouteBuildSubPath } from './getRouteBuildSubPath.js';\n\nexport function getRouteBuildPath(route) {\n  const subPath = getRouteBuildSubPath(route);\n  return path.resolve(CONSTANTS.BUILDPATH, subPath);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/getRouteBuildSubPath.js",
    "content": "export function getRouteBuildSubPath(route) {\n  const { id, isAdmin } = route;\n  return isAdmin === true ? `admin/${id}` : `frontStore/${id}`;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/isBuildRequired.ts",
    "content": "import { Route } from '../../types/route.js';\n\nexport const isBuildRequired = (route: Route) => {\n  if (!route) {\n    return false;\n  }\n  if (route.isApi || ['staticAsset', 'adminStaticAsset'].includes(route.id)) {\n    return false;\n  } else {\n    return true;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/loaders/AreaLoader.js",
    "content": "import fs from 'fs';\nimport { pathToFileURL } from 'url';\nimport { inspect } from 'util';\nimport JSON5 from 'json5';\nimport { getEnabledWidgets } from '../../../lib/widget/widgetManager.js';\nimport { getAllRouteComponents } from '../../componee/getComponentsByRoute.js';\nimport { error } from '../../log/logger.js';\nimport { getRoutes } from '../../router/Router.js';\nimport { generateComponentKey } from '../../util/keyGenerator.js';\n\nfunction buildComponentsPerRoute(components, imports) {\n  const areas = {};\n  components.forEach((module) => {\n    if (!fs.existsSync(module)) {\n      return;\n    }\n    const source = fs.readFileSync(module, 'utf8');\n    // Regex matching 'export const layout = { ... }'\n    const layoutRegex =\n      /export\\s+const\\s+layout\\s*=\\s*{\\s*areaId\\s*:\\s*['\"]([^'\"]+)['\"],\\s*sortOrder\\s*:\\s*(\\d+)\\s*,*\\s*}/;\n    const match = source.match(layoutRegex);\n    if (match) {\n      // Remove everything before '{' from the beginning of the match\n      const check = match[0]\n        .replace(/^[^{]*/, '')\n        .replace(/(['\"])?([a-zA-Z0-9_]+)(['\"])?:/g, '\"$2\": ');\n      try {\n        const layout = JSON5.parse(check);\n        const id = generateComponentKey(module);\n        const url = pathToFileURL(module).toString();\n        // Check if this import already exists by url\n        // Get all key of current imports\n        const keys = Array.from(imports.keys());\n        const exists = keys.find((key) => key.url === url);\n        if (!exists) {\n          imports.set({ id, url }, `import ${id} from '${url}';`);\n        }\n        areas[layout.areaId] = areas[layout.areaId] || {};\n        areas[layout.areaId][id] = {\n          id,\n          sortOrder: layout.sortOrder,\n          component: {\n            default: `---${id}---`\n          }\n        };\n      } catch (e) {\n        error(`Error parsing layout from ${module}`);\n        error(e);\n      }\n    }\n  });\n\n  return areas;\n}\n\nconst buildWidgetComponentsPerRoute = (route, widgets, imports) => {\n  const components = {};\n  widgets.forEach((widget) => {\n    const componentPath = route.isAdmin\n      ? widget.settingComponent\n      : widget.component;\n    const url = pathToFileURL(componentPath).toString();\n    // Check if this import already exists by url\n    // Get all key of current imports\n    const keys = Array.from(imports.keys());\n    const exists = keys.find((key) => key.url === url);\n    const id = generateComponentKey(\n      route.isAdmin ? `admin_widget_${widget.type}` : `widget_${widget.type}`\n    );\n    if (!exists) {\n      imports.set({ id: id, url }, `import ${id} from '${url}';`);\n    }\n    components[id] = {\n      id: id,\n      sortOrder: widget.sortOrder || 0,\n      component: {\n        default: `---${id}---`\n      }\n    };\n  });\n  return components;\n};\n\nexport default function AreaLoader(c) {\n  const isAdmin = this.getOptions().isAdmin;\n  this.cacheable(false);\n  const components = getAllRouteComponents(isAdmin);\n  const routes = getRoutes().filter(\n    (route) => route.isApi === false && route.isAdmin === isAdmin\n  );\n  const allRootComponents = {};\n  const widgets = getEnabledWidgets();\n  const imports = new Map(); // This map has a key as an object {id, url} to avoid duplicate imports\n\n  try {\n    Object.keys(components).forEach((routeId) => {\n      allRootComponents[routeId] = buildComponentsPerRoute(\n        components[routeId],\n        imports\n      );\n      const route = routes.find((r) => r.id === routeId);\n      const widgetComponents = buildWidgetComponentsPerRoute(\n        route,\n        widgets,\n        imports\n      );\n      Object.assign(allRootComponents[routeId], { '*': widgetComponents });\n    });\n  } catch (e) {\n    error('Error in AreaLoader:');\n    error(e);\n  }\n  const content = `${Array.from(imports.values()).join(\n    '\\r\\n'\n  )}\\r\\nconst components = ${inspect(allRootComponents, { depth: 5 })\n    .replace(/\"---/g, '')\n    .replace(/---\"/g, '')\n    .replace(/'---/g, '')\n    .replace(\n      /---'/g,\n      ''\n    )}\\r\\nArea.defaultProps.components = components[window.eContext.config.pageMeta.route.id] ;\\r\\n`;\n  const result = c.replace('/** render */', content);\n  return result;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/loaders/GraphQLAPILoader.js",
    "content": "export default function GraphqlAPILoader(source) {\n  // Get options\n  const options = this.getOptions();\n  const isAdmin = options.isAdmin || false;\n  // Replace the specified code with an empty string\n  if (isAdmin) {\n    const newSource = source.replace(\n      \"url: '/api/graphql\",\n      \"url: '/api/admin/graphql\"\n    );\n    return newSource;\n  } else {\n    return source;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/loaders/GraphqlLoader.js",
    "content": "export default function GraphqlLoader(content) {\n  // Regex matching 'export var query = `query { ... }`'\n  const queryRegex = /export\\s+var\\s+query\\s*=\\s*`([^`]+)`/;\n\n  const fragmentRegex = /export\\s+var\\s+fragment\\s*=\\s*`([^`]+)`/;\n\n  const variablesRegex = /export\\s+var\\s+variables\\s*=\\s*`([^`]+)`/;\n\n  return content\n    .replace(queryRegex, '')\n    .replace(fragmentRegex, '')\n    .replace(variablesRegex, '');\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/loaders/LayoutLoader.js",
    "content": "export default function LayoutLoader(content) {\n  // Regex matching 'export const layout = { ... }'\n  const layoutRegex =\n    /export\\s+var\\s+layout\\s*=\\s*{\\s*areaId\\s*:\\s*['\"]([^'\"]+)['\"],\\s*sortOrder\\s*:\\s*(\\d+)\\s*,*\\s*}/;\n\n  return content.replace(layoutRegex, '');\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/loaders/StyleLoader.js",
    "content": "export default function StyleLoader() {\n  return '';\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/loaders/TailwindLoader.js",
    "content": "import { join } from 'path';\nimport autoprefixer from 'autoprefixer';\nimport postcss from 'postcss';\nimport tailwindcss from 'tailwindcss';\nimport { getEnabledExtensions } from '../../../bin/extension/index.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { getEnabledTheme } from '../../../lib/util/getEnabledTheme.js';\nimport { CONSTANTS } from '../../helpers.js';\nimport { getTailwindConfig } from '../util/getTailwindConfig.js';\n\nexport default async function TailwindLoader(c) {\n  this.cacheable(false);\n  if (this.mode === 'production') {\n    if (this.resourcePath.includes('tailwind.scss')) {\n      return `/*beginTailwind*/${c}/*endTailwind*/`;\n    } else {\n      return c;\n    }\n  }\n  const components = this.getOptions().getComponents();\n  const { route } = this.getOptions();\n  components.forEach((module) => {\n    this.addDependency(module);\n  });\n\n  if (!this.resourcePath.includes('tailwind.scss')) {\n    return c;\n  }\n  const mergedTailwindConfig = await getTailwindConfig(route);\n  const enabledExtensions = getEnabledExtensions();\n  mergedTailwindConfig.content = [\n    // All file in packages/evershop/dist and name is capitalized\n    join(CONSTANTS.ROOTPATH, 'packages', 'evershop', 'dist', '**', '[A-Z]*.js'),\n    // All file in node_modules/@evershop/evershop/dist and name is capitalized\n    join(\n      CONSTANTS.ROOTPATH,\n      'node_modules',\n      '@evershop',\n      'evershop',\n      'dist',\n      '**',\n      '[A-Z]*.js'\n    ),\n    ...enabledExtensions.map((extension) =>\n      join(extension.path, 'dist', '**', '[A-Z]*.js')\n    )\n  ];\n  const theme = getEnabledTheme();\n  if (theme) {\n    mergedTailwindConfig.content.push(\n      join(theme.path, 'dist', '**', '[A-Z]*.js')\n    );\n  }\n\n  return postcss([tailwindcss(mergedTailwindConfig), autoprefixer])\n    .process(c, { from: undefined })\n    .then((result) => result.css);\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/loaders/TranslationLoader.js",
    "content": "export default async function TranslationLoader(c) {\n  const csvData = await this.getOptions().getTranslateData();\n  // Use regex to find all function call `_()` in the template string\n  const regex =\n    /_\\s*\\(\\s*(?<arg1>\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|\\w+)\\s*(?:,\\s*(?<arg2>null|\\{[\\s\\S]*?\\}|\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|\\w+)\\s*)?\\)/g;\n\n  let result = c;\n  // Loop through each function call and get the template string\n  let match;\n\n  while ((match = regex.exec(c)) !== null) {\n    let template = match.groups.arg1;\n    // Remove the quote from the start and end of the template string\n    template = template.replace(/^[\"']/, '').replace(/[\"']$/, '');\n    const newValue = csvData[template];\n    // Check if the template is exist in the csvData\n    if (newValue) {\n      result = result.replace(match[0], `_(\"${newValue}\",${match[2] || null})`);\n    }\n  }\n  return result;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/loaders/loadTranslationFromCsv.ts",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport { CONSTANTS } from '../../helpers.js';\nimport { error } from '../../log/logger.js';\nimport { getConfig } from '../../util/getConfig.js';\nimport { readCsvFile } from '../../util/readCsvFile.js';\n\nexport async function loadCsvTranslationFiles(): Promise<\n  Record<string, string>\n> {\n  try {\n    const language = getConfig('shop.language', 'en');\n    const folderPath = path.resolve(\n      CONSTANTS.ROOTPATH,\n      'translations',\n      language\n    );\n\n    // Check if path exists\n    if (!fs.existsSync(folderPath)) {\n      return {};\n    }\n\n    const results = {};\n\n    const files = await fs.promises.readdir(folderPath);\n    const csvFiles = files.filter((file) => path.extname(file) === '.csv');\n    const filePromises = csvFiles.map((file) => {\n      const filePath = path.join(folderPath, file);\n      return readCsvFile(filePath);\n    });\n\n    const fileDataList = await Promise.all(filePromises);\n\n    for (const fileData of fileDataList) {\n      for (const [key, value] of Object.entries(fileData)) {\n        results[key] = value;\n      }\n    }\n\n    return results;\n  } catch (err) {\n    error(err);\n    return {};\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/plugins/FileListPlugin.js",
    "content": "import fs from 'fs';\n\nexport const FileListPlugin = class FileListPlugin {\n  apply(compiler) {\n    compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => {\n      const list = compilation._modules;\n      const modules = [];\n      list.forEach((element) => {\n        modules.push(element.resource);\n      });\n      // Create a header string for the generated file:\n      let filelist = 'CSS:\\n\\n';\n      // Loop through all compiled assets,\n      // adding a new line item for each filename.\n\n      modules.forEach((m) => {\n        if (m) {\n          const path = m.replace('.js', '.css');\n          if (fs.existsSync(path))\n            filelist += `${fs.readFileSync(path, 'utf-8')}\\n`;\n        }\n      });\n\n      // Insert this list into the webpack build as a new file asset:\n\n      compilation.assets['filelist.md'] = {\n        source() {\n          return filelist;\n        },\n        size() {\n          return filelist.length;\n        }\n      };\n\n      callback();\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/plugins/GraphqlPlugin.js",
    "content": "import {\n  getAllRouteComponents,\n  getComponentsByRoute\n} from '../../componee/getComponentsByRoute.js';\nimport { parseGraphql } from '../util/parseGraphql.js';\n\nexport const GraphqlPlugin = class GraphqlPlugin {\n  constructor(isAdmin = false) {\n    this.isAdmin = isAdmin;\n    this.query = {};\n    this.fragments = {};\n    this.variables = [];\n  }\n\n  apply(compiler) {\n    const { webpack } = compiler;\n    const { RawSource } = webpack.sources;\n\n    compiler.hooks.thisCompilation.tap('GraphqlPlugin', (compilation) => {\n      // TODO: Can we get list of module without calling getComponentsByRoute again?\n      const components = getAllRouteComponents(this.isAdmin);\n\n      // Store one file per route instead of a single file\n      Object.keys(components).forEach((routeId) => {\n        const routeGraphqlQueries = parseGraphql(components[routeId]);\n        const filename = `query-${routeId}.graphql`;\n\n        compilation.emitAsset(\n          filename,\n          new RawSource(JSON.stringify(routeGraphqlQueries, null, 2))\n        );\n      });\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/plugins/InjectTailwindSources.ts",
    "content": "// Inject Tailwind @source directives for Tailwind v4\nconst InjectTailwindSources = (sources) => {\n  const uniqueSources = Array.from(new Set(sources));\n  const plugin = () => ({\n    postcssPlugin: 'inject-tailwind-sources',\n    Once(root) {\n      // Only inject if this CSS imports Tailwind\n      const hasTailwindImport = root.nodes.some(\n        (node) =>\n          node.type === 'atrule' &&\n          node.name === 'import' &&\n          node.params.includes('tailwindcss')\n      );\n\n      if (!hasTailwindImport) return;\n\n      // Prepend @source entries so Tailwind scans the intended files\n      uniqueSources.forEach((src) => {\n        root.prepend(`@source \"${src}\";`);\n      });\n    }\n  });\n  plugin.postcss = true;\n  return plugin;\n};\n\nexport { InjectTailwindSources };\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/plugins/Tailwindcss.ts",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport tailwindcss from '@tailwindcss/postcss';\nimport CleanCSS from 'clean-css';\nimport postcss from 'postcss';\nimport webpack from 'webpack';\nimport { Route } from '../../../types/route.js';\nimport { error } from '../../log/logger.js';\n\ntype WebpackCompiler = webpack.Compiler;\ntype WebpackCompilation = webpack.Compilation;\ntype WebpackAssets = webpack.Compilation['assets'];\n\nexport class Tailwindcss {\n  private route: Route;\n\n  constructor(route: Route) {\n    this.route = route;\n  }\n\n  apply(compiler: WebpackCompiler): void {\n    compiler.hooks.afterEmit.tapAsync(\n      'Tailwindcss',\n      async (compilation: WebpackCompilation, callback) => {\n        try {\n          await this.processRouteAssetsAfterEmit(compilation);\n          callback();\n        } catch (err) {\n          callback(err as Error);\n        }\n      }\n    );\n  }\n\n  async processRouteAssetsAfterEmit(\n    compilation: WebpackCompilation\n  ): Promise<void> {\n    const outputPath = compilation.outputOptions.path;\n    if (!outputPath) {\n      return;\n    }\n\n    const processingPromises: Promise<void>[] = [];\n    const routeCSSAssets = this.getRouteCSSAssets(\n      compilation.assets,\n      this.route\n    );\n\n    for (const cssAsset of routeCSSAssets) {\n      processingPromises.push(\n        this.processCSSWithTailwindFromDisk(cssAsset, outputPath)\n      );\n    }\n\n    await Promise.all(processingPromises);\n  }\n\n  getRouteCSSAssets(assets: WebpackAssets, route: Route): string[] {\n    const results = Object.keys(assets).filter((name) => {\n      // Normalize path separators for cross-platform compatibility\n      const normalizedName = name.replace(/\\\\/g, '/');\n      return (\n        (normalizedName.includes(route.id + '/') ||\n          /^\\d/.test(normalizedName)) &&\n        normalizedName.endsWith('.css')\n      );\n    });\n\n    return results;\n  }\n\n  async processCSSWithTailwindFromDisk(\n    cssAssetName: string,\n    outputPath: string\n  ): Promise<void> {\n    const cssFilePath = path.resolve(outputPath, cssAssetName);\n\n    // Read the CSS file from disk\n    if (!fs.existsSync(cssFilePath)) {\n      error(`CSS file not found: ${cssFilePath}`);\n      return;\n    }\n\n    const originalCSS = fs.readFileSync(cssFilePath, 'utf-8');\n\n    // Process CSS with Tailwind\n    let processedCSS = originalCSS;\n\n    if (cssAssetName.includes(this.route.id)) {\n      // Get the directory where the CSS file is located\n      const cssDir = path.dirname(cssFilePath);\n\n      // Find all JS files in the same directory (already on disk)\n      const jsFiles = fs\n        .readdirSync(cssDir)\n        .filter((file) => file.endsWith('.js'));\n\n      if (jsFiles.length === 0) {\n        error(`No JS files found in ${cssDir}`);\n        return;\n      }\n\n      // Reference the JS files for Tailwind scanning\n      const sourceDirectives = jsFiles\n        .map((file) => `@source \"./${file}\";`)\n        .join('\\n');\n\n      processedCSS = `${sourceDirectives}\n${originalCSS}`;\n    }\n\n    try {\n      // Process with Tailwind\n      const result = await postcss([tailwindcss()]).process(processedCSS, {\n        from: cssFilePath\n      });\n\n      // Minify the result\n      const cleanCSS = new CleanCSS({\n        level: 2,\n        returnPromise: true\n      });\n\n      const minified = await cleanCSS.minify(result.css);\n\n      // Write the processed CSS back to disk\n      fs.writeFileSync(cssFilePath, minified.styles, 'utf-8');\n    } catch (e) {\n      error(e);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/plugins/ThemeWatcherPlugin.ts",
    "content": "import path from 'path';\nimport watcher from '@parcel/watcher';\nimport touch from 'touch';\nimport type { Compiler, WebpackPluginInstance } from 'webpack';\nimport { getEnabledExtensions } from '../../../bin/extension/index.js';\nimport { CONSTANTS } from '../../helpers.js';\nimport { debug } from '../../log/logger.js';\nimport { getEnabledTheme } from '../../util/getEnabledTheme.js';\n\ninterface AsyncWebpackSubscription {\n  unsubscribe(): Promise<void>;\n}\n\ndeclare module 'webpack' {\n  interface Module {\n    resource?: string;\n  }\n}\n\nlet globalWatcher: AsyncWebpackSubscription | null = null;\nconst watcherSubscribers = new Set<Compiler>();\n\nexport class ThemeWatcherPlugin implements WebpackPluginInstance {\n  private pendingFiles: Set<string>;\n\n  constructor() {\n    this.pendingFiles = new Set<string>();\n  }\n\n  apply(compiler: Compiler): void {\n    if (compiler.options.mode !== 'development') {\n      return;\n    }\n\n    const theme = getEnabledTheme();\n    if (!theme) {\n      return;\n    }\n\n    watcherSubscribers.add(compiler);\n\n    if (!globalWatcher) {\n      this.initializeGlobalWatcher();\n    }\n\n    compiler.hooks.compilation.tap('ThemeWatcherPlugin', (compilation) => {\n      compilation.hooks.finishModules.tap(\n        'ThemeWatcherPlugin',\n        (modules: Set<any>) => {\n          if (this.pendingFiles.size === 0) {\n            return;\n          }\n\n          const extensions = getEnabledExtensions();\n          const watchPath = path.join(theme.path, 'dist', 'components');\n          const filesToProcess = Array.from(this.pendingFiles);\n          this.pendingFiles.clear(); // Clear immediately to prevent loops\n\n          filesToProcess.forEach((filePath: string) => {\n            const relativePath = path.relative(watchPath, filePath);\n\n            let targetModule: any = null;\n            let targetPath: string | null = null;\n\n            for (const extension of extensions) {\n              const extensionComponentPath = path.resolve(\n                extension.resolve,\n                'dist/components',\n                relativePath\n              );\n\n              targetModule = Array.from(modules).find(\n                (module) =>\n                  module.resource && module.resource === extensionComponentPath\n              );\n\n              if (targetModule) {\n                targetPath = extensionComponentPath;\n                break;\n              }\n            }\n\n            if (!targetModule) {\n              const coreComponentPath = path.resolve(\n                CONSTANTS.MODULESPATH,\n                '../components',\n                relativePath\n              );\n\n              targetModule = Array.from(modules).find(\n                (module) =>\n                  module.resource && module.resource === coreComponentPath\n              );\n\n              if (targetModule) {\n                targetPath = coreComponentPath;\n              }\n            }\n\n            if (targetModule) {\n              const issuers: any[] = [];\n\n              for (const module of modules) {\n                if (module.dependencies) {\n                  for (const dependency of module.dependencies) {\n                    const depModule =\n                      compilation.moduleGraph.getModule(dependency);\n                    if (depModule === targetModule) {\n                      issuers.push(module);\n                      break;\n                    }\n                  }\n                }\n              }\n              if (issuers.length > 0) {\n                for (const issuer of issuers) {\n                  if (issuer.resource) {\n                    touch.sync(issuer.resource);\n                  }\n                }\n              }\n            }\n          });\n        }\n      );\n    });\n\n    compiler.hooks.watchClose.tap('ThemeWatcherPlugin', () => {\n      watcherSubscribers.delete(compiler);\n\n      if (watcherSubscribers.size === 0) {\n        this.cleanupGlobalWatcher();\n      }\n    });\n  }\n\n  private initializeGlobalWatcher(): void {\n    const theme = getEnabledTheme();\n    if (!theme) return;\n\n    const watchPath = path.join(theme.path, 'dist', 'components');\n\n    watcher\n      .subscribe(watchPath, (err: Error | null, events: any[]) => {\n        if (err) {\n          debug(err);\n          return;\n        }\n\n        const createEvents = events.filter((event) => event.type === 'create');\n\n        if (createEvents.length > 0) {\n          watcherSubscribers.forEach((compiler: Compiler) => {\n            const plugin = compiler.options.plugins?.find(\n              (p: any) => p instanceof ThemeWatcherPlugin\n            ) as ThemeWatcherPlugin | undefined;\n            if (plugin) {\n              createEvents.forEach((event) => {\n                plugin.pendingFiles.add(event.path);\n              });\n              if (compiler.watching) {\n                compiler.watching.invalidate();\n              }\n            }\n          });\n        }\n      })\n      .then((subscription: AsyncWebpackSubscription) => {\n        globalWatcher = subscription;\n      })\n      .catch((error: Error) => {\n        debug(error);\n      });\n  }\n\n  private cleanupGlobalWatcher(): void {\n    if (globalWatcher) {\n      globalWatcher.unsubscribe();\n      globalWatcher = null;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/prod/createConfigClient.js",
    "content": "import path from 'path';\nimport HtmlWebpackPlugin from 'html-webpack-plugin';\nimport MiniCssExtractPlugin from 'mini-css-extract-plugin';\nimport WebpackBar from 'webpackbar';\nimport { getEnabledExtensions } from '../../../bin/extension/index.js';\nimport { CONSTANTS } from '../../helpers.js';\nimport { createBaseConfig } from '../createBaseConfig.js';\nimport { getRouteBuildPath } from '../getRouteBuildPath.js';\nimport { getRouteBuildSubPath } from '../getRouteBuildSubPath.js';\nimport { isBuildRequired } from '../isBuildRequired.js';\nimport { InjectTailwindSources } from '../plugins/InjectTailwindSources.js';\nimport { getTailwindSources } from '../util/getTailwindSources.js';\n\nexport function createConfigClient(routes) {\n  const extenions = getEnabledExtensions();\n  const config = createBaseConfig(false);\n  const tailwindSources = getTailwindSources();\n  const { plugins } = config;\n  const entry = {};\n  routes.forEach((route) => {\n    if (!isBuildRequired(route)) {\n      return;\n    }\n    const subPath = getRouteBuildSubPath(route);\n    entry[subPath] = [\n      path.resolve(CONSTANTS.BUILDPATH, subPath, 'client', 'entry.js')\n    ];\n    plugins.push(\n      new HtmlWebpackPlugin({\n        templateContent: ({ htmlWebpackPlugin }) => {\n          const jsFiles = htmlWebpackPlugin.files.js;\n          const cssFiles = htmlWebpackPlugin.files.css;\n\n          // Filter out the incorrect vendor chunk based on route type\n          const filteredJsFiles = jsFiles.filter((file) => {\n            if (route.isAdmin) {\n              // For admin routes, exclude frontstore-vendor\n              return !file.includes('/frontstore-vendor/');\n            } else {\n              // For frontStore routes, exclude admin-vendor\n              return !file.includes('/admin-vendor/');\n            }\n          });\n\n          return JSON.stringify({\n            js: filteredJsFiles,\n            css: cssFiles\n          });\n        },\n        filename: path.resolve(\n          getRouteBuildPath(route),\n          'client',\n          'index.json'\n        ),\n        chunks: (() => {\n          // Only 2 vendor chunks now - no shared vendor\n          const chunks = ['common', subPath];\n\n          // Add context-specific vendor and components\n          if (route.isAdmin) {\n            chunks.unshift('admin-vendor'); // Add admin vendor first\n            chunks.splice(-1, 0, 'admin-components'); // Insert admin components before route-specific chunk\n          } else {\n            chunks.unshift('frontstore-vendor'); // Add frontstore vendor first\n            chunks.splice(-1, 0, 'frontstore-components'); // Insert frontstore components before route-specific chunk\n          }\n\n          return chunks;\n        })(),\n        chunksSortMode: 'manual',\n        inject: false,\n        publicPath: '/assets/'\n      })\n    );\n    //plugins.push(new Tailwindcss(route));\n  });\n\n  const loaders = config.module.rules;\n  loaders.push({\n    test: /\\.css$/i,\n    use: [\n      MiniCssExtractPlugin.loader,\n      {\n        loader: 'css-loader',\n        options: {\n          url: false\n        }\n      },\n      {\n        loader: 'postcss-loader',\n        options: {\n          postcssOptions: {\n            plugins: [\n              InjectTailwindSources(tailwindSources),\n              '@tailwindcss/postcss',\n              'autoprefixer'\n            ]\n          }\n        }\n      }\n    ]\n  });\n\n  loaders.push({\n    test: /\\.scss$/i,\n    use: [\n      MiniCssExtractPlugin.loader,\n      {\n        loader: 'css-loader',\n        options: {\n          url: false\n        }\n      },\n      {\n        loader: 'postcss-loader',\n        options: {\n          postcssOptions: {\n            plugins: [\n              InjectTailwindSources(tailwindSources),\n              '@tailwindcss/postcss',\n              'autoprefixer'\n            ]\n          }\n        }\n      },\n      {\n        loader: 'sass-loader',\n        options: {\n          implementation: 'sass',\n          api: 'modern'\n        }\n      }\n    ]\n  });\n\n  plugins.push(new WebpackBar({ name: 'Client' }));\n  plugins.push(\n    new MiniCssExtractPlugin({\n      filename: '[name]/client/[contenthash].css',\n      chunkFilename: '[name]/client/[id].[contenthash].css'\n    })\n  );\n\n  config.entry = entry;\n  config.output.filename = '[name]/client/[fullhash].js';\n  config.name = 'Client';\n\n  config.optimization = {\n    ...config.optimization,\n    splitChunks: {\n      chunks: 'all',\n      cacheGroups: {\n        // Admin vendor chunk - includes third-party node_modules used by admin routes\n        // Excludes @evershop/evershop core and extensions\n        adminVendor: {\n          test: (module) => {\n            // Only match JS modules from node_modules, exclude CSS\n            if (module.type === 'css/mini-extract') {\n              return false;\n            }\n\n            // Must be from node_modules\n            if (!module.resource) {\n              return false;\n            }\n\n            // Normalize path separators for cross-platform compatibility\n            const normalizedResource = module.resource.replace(/\\\\/g, '/');\n\n            if (!normalizedResource.includes('node_modules')) {\n              return false;\n            }\n\n            // Exclude @evershop/evershop core package\n            if (\n              normalizedResource.includes('node_modules/@evershop/evershop')\n            ) {\n              return false;\n            }\n\n            // Exclude extensions (they may be npm packages in node_modules)\n            for (const ext of extenions) {\n              if (ext.resolve) {\n                const normalizedExtResolve = ext.resolve.replace(/\\\\/g, '/');\n                if (normalizedResource.includes(normalizedExtResolve)) {\n                  return false;\n                }\n              }\n            }\n\n            return true;\n          },\n          chunks: (chunk) => {\n            // Only split from admin route chunks\n            const route = routes.find((r) => {\n              const subPath = getRouteBuildSubPath(r);\n              return chunk.name === subPath;\n            });\n            return route && route.isAdmin;\n          },\n          name: 'admin-vendor',\n          enforce: true,\n          priority: 20\n        },\n        // FrontStore vendor chunk - includes third-party node_modules used by frontStore routes\n        // Excludes @evershop/evershop core and extensions\n        frontStoreVendor: {\n          test: (module) => {\n            // Only match JS modules from node_modules, exclude CSS\n            if (module.type === 'css/mini-extract') {\n              return false;\n            }\n\n            // Must be from node_modules\n            if (!module.resource) {\n              return false;\n            }\n\n            // Normalize path separators for cross-platform compatibility\n            const normalizedResource = module.resource.replace(/\\\\/g, '/');\n\n            if (!normalizedResource.includes('node_modules')) {\n              return false;\n            }\n\n            // Exclude @evershop/evershop core package\n            if (\n              normalizedResource.includes('node_modules/@evershop/evershop')\n            ) {\n              return false;\n            }\n\n            // Exclude extensions (they may be npm packages in node_modules)\n            for (const ext of extenions) {\n              if (ext.resolve) {\n                const normalizedExtResolve = ext.resolve.replace(/\\\\/g, '/');\n                if (normalizedResource.includes(normalizedExtResolve)) {\n                  return false;\n                }\n              }\n            }\n\n            return true;\n          },\n          chunks: (chunk) => {\n            // Only split from frontStore route chunks\n            const route = routes.find((r) => {\n              const subPath = getRouteBuildSubPath(r);\n              return chunk.name === subPath;\n            });\n            return route && !route.isAdmin;\n          },\n          name: 'frontstore-vendor',\n          enforce: true,\n          priority: 20\n        },\n        // Common chunk for @components/common (shared across all routes)\n        common: {\n          test: /[\\\\/]@components[\\\\/]common[\\\\/]/,\n          name: 'common',\n          chunks: 'all',\n          enforce: true,\n          priority: 15\n        },\n        // Admin components chunk (only for admin routes)\n        adminComponents: {\n          test: /[\\\\/]@components[\\\\/]admin[\\\\/]/,\n          name: 'admin-components',\n          chunks: 'all',\n          enforce: true,\n          priority: 10\n        },\n        // FrontStore components chunk (only for frontStore routes)\n        frontStoreComponents: {\n          test: /[\\\\/]@components[\\\\/]frontStore[\\\\/]/,\n          name: 'frontstore-components',\n          chunks: 'all',\n          enforce: true,\n          priority: 10\n        },\n        // Default group to prevent CSS duplication across chunks\n        default: false,\n        defaultVendors: false\n      }\n    }\n  };\n\n  return config;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/prod/createConfigServer.js",
    "content": "import path from 'path';\nimport WebpackBar from 'webpackbar';\nimport { CONSTANTS } from '../../helpers.js';\nimport { createBaseConfig } from '../createBaseConfig.js';\nimport { getRouteBuildSubPath } from '../getRouteBuildSubPath.js';\nimport { isBuildRequired } from '../isBuildRequired.js';\n\nexport function createConfigServer(routes) {\n  const entry = {};\n  routes.forEach((route) => {\n    if (!isBuildRequired(route)) {\n      return;\n    }\n    const subPath = getRouteBuildSubPath(route);\n    entry[subPath] = [\n      path.resolve(CONSTANTS.BUILDPATH, subPath, 'server', 'entry.js')\n    ];\n  });\n  const config = createBaseConfig(true);\n  const { plugins } = config;\n  plugins.push(new WebpackBar({ name: 'Server', color: 'orange' }));\n\n  const loaders = config.module.rules;\n  loaders.push({\n    test: /\\.(css|scss)$/i,\n    use: [\n      {\n        loader: path.resolve(\n          CONSTANTS.LIBPATH,\n          'webpack/loaders/StyleLoader.js'\n        )\n      }\n    ]\n  });\n  config.entry = entry;\n  config.name = 'Server';\n\n  return config;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/resolveAlias.js",
    "content": "import fs from 'fs';\nimport path from 'path';\n\nfunction getAllFilesInFolder(folderPath) {\n  if (!fs.existsSync(folderPath)) {\n    return {};\n  }\n\n  let results = {};\n\n  // Get the contents of the folder\n  const contents = fs.readdirSync(folderPath);\n\n  // Loop through each item in the folder\n\n  for (const item of contents) {\n    const itemPath = path.join(folderPath, item);\n\n    // Check if the item is a directory\n    if (fs.lstatSync(itemPath).isDirectory()) {\n      // Recursively call the function to get the files in the subdirectory\n      const subResults = getAllFilesInFolder(itemPath);\n      results = { ...results, ...subResults };\n    } else if (\n      (/.js$/.test(item) && /^[A-Z]/.test(item[0])) ||\n      /\\.(css|scss)$/i.test(item)\n    ) {\n      const pathParts = itemPath.split(path.sep);\n\n      // Find the index of the \"components\" directory\n      const componentsIndex = pathParts.indexOf('components');\n\n      // Return the part of the path after the \"components\" directory\n      const alias = path\n        .join('@components', ...pathParts.slice(componentsIndex + 1))\n        .replace('.js', '')\n        .replace('.scss', '')\n        .replace('.css', '');\n      results[alias] = itemPath;\n    }\n  }\n\n  return results;\n}\n\nexport function resolveAlias(extensions = [], themePath = null) {\n  let resolves = {};\n\n  if (themePath) {\n    resolves = getAllFilesInFolder(path.resolve(themePath, 'components'));\n  }\n\n  // loop through the extensions and get the files\n  extensions.forEach((extension) => {\n    const extensionFiles = getAllFilesInFolder(\n      path.resolve(extension.path, 'components')\n    );\n\n    resolves = { ...extensionFiles, ...resolves };\n  });\n\n  return resolves;\n}\n\nexport const alias = {};\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/tests/unit/resolveAlias.test.js",
    "content": "import path from 'path';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\nimport { resolveAlias } from '../../resolveAlias.js';\n\n// Get the directory name for this file\nconst currentFilePath = fileURLToPath(import.meta.url);\nconst currentDirPath = dirname(currentFilePath);\n\ndescribe('resolveAlias', () => {\n  it('It should get the components and css file with correct priority', () => {\n    const resolves = resolveAlias(\n      [\n        {\n          path: path.resolve(currentDirPath, 'extensions/extensionA'),\n          priority: 1\n        },\n        {\n          path: path.resolve(currentDirPath, 'extensions/extensionB'),\n          priority: 2\n        }\n      ],\n      path.resolve(currentDirPath, 'theme')\n    );\n    expect(resolves[path.join('@components', 'a', 'A')])\n      .toString()\n      .includes('theme');\n\n    expect(resolves[path.join('@components', 'a', 'a')])\n      .toString()\n      .includes('theme');\n\n    expect(resolves[path.join('@components', 'b', 'bb')])\n      .toString()\n      .includes('theme');\n\n    expect(resolves[path.join('@components', 'b', 'bb', 'BB')])\n      .toString()\n      .includes('theme');\n\n    expect(resolves[path.join('@components', 'd', 'D')])\n      .toString()\n      .includes('extensionA');\n\n    expect(resolves[path.join('@components', 'd', 'dd', 'DD')])\n      .toString()\n      .includes('extensionA');\n\n    expect(resolves[path.join('@components', 'e', 'E')])\n      .toString()\n      .includes('extensionB');\n\n    expect(resolves[path.join('@components', 'e', 'ee', 'EE')])\n      .toString()\n      .includes('extensionB');\n  });\n}); "
  },
  {
    "path": "packages/evershop/src/lib/webpack/tests/unit/theme/components/a/A.jsx",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/webpack/tests/unit/theme/components/a/a.scss",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/webpack/tests/unit/theme/components/b/B.jsx",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/webpack/tests/unit/theme/components/b/B.scss",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/webpack/tests/unit/theme/components/b/bb/BB.jsx",
    "content": ""
  },
  {
    "path": "packages/evershop/src/lib/webpack/util/getTailwindConfig.js",
    "content": "import fs from 'fs';\nimport { join } from 'path';\nimport { pathToFileURL } from 'url';\nimport { getEnabledTheme } from '../../../lib/util/getEnabledTheme.js';\n\n/**\n * @deprecated This function is deprecated and will be removed in future versions.\n */\nexport async function getTailwindConfig(isAdmin = false) {\n  const defaultTailwindConfig = isAdmin\n    ? await import('../../../modules/cms/services/tailwind.admin.config.js')\n    : await import(\n        '../../../modules/cms/services/tailwind.frontStore.config.js'\n      );\n\n  let tailwindConfig = {};\n  if (!isAdmin) {\n    // Get the current theme\n    const theme = getEnabledTheme();\n    if (\n      theme &&\n      fs.existsSync(join(theme.path, 'dist', 'tailwind.config.js'))\n    ) {\n      tailwindConfig = await import(\n        pathToFileURL(join(theme.path, 'dist', 'tailwind.config.js'))\n      );\n    }\n  }\n  // Merge defaultTailwindConfig with tailwindConfigJs\n  const mergedTailwindConfig = Object.assign(\n    defaultTailwindConfig.default,\n    tailwindConfig.default\n  );\n\n  return mergedTailwindConfig;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/util/getTailwindSources.ts",
    "content": "import path from 'path';\nimport { getEnabledExtensions } from '../../../bin/extension/index.js';\nimport { CONSTANTS } from '../../helpers.js';\nimport { getEnabledTheme } from '../../util/getEnabledTheme.js';\n\nexport function getTailwindSources(): string[] {\n  const sources: string[] = [];\n\n  // Add the core source\n  sources.push(\n    path.resolve(CONSTANTS.MODULESPATH, '..', '**/*.{js,jsx,ts,tsx}')\n  );\n\n  // Add enabled extensions\n  const extensions = getEnabledExtensions();\n  for (const extension of extensions) {\n    sources.push(path.resolve(extension.path, '**/*.{js,jsx,ts,tsx}'));\n  }\n\n  // Add enabled theme\n  const theme = getEnabledTheme();\n  if (theme) {\n    sources.push(path.resolve(theme.path, '**/*.{js,jsx,ts,tsx}'));\n  }\n\n  return sources.map((s) => s.replace(/\\\\/g, '/'));\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/util/parseGraphql.js",
    "content": "import fs from 'fs';\nimport JSON5 from 'json5';\nimport uniqid from 'uniqid';\nimport { isResolvable } from '../../util/isResolvable.js';\nimport { generateComponentKey } from '../../util/keyGenerator.js';\nimport { parseGraphqlByFile } from './parseGraphqlByFile.js';\n\nexport function parseGraphql(modules) {\n  let inUsedFragments = [];\n  const propsMap = {};\n  let queries = {};\n  let fragmentStr = '';\n  const variableList = {};\n  modules.forEach((module) => {\n    if (!fs.existsSync(module) && !isResolvable(module)) {\n      return;\n    }\n    const variables = {\n      values: {},\n      defs: []\n    };\n    let modulePath;\n    let moduleKey;\n    // If the module is resolvable, get the apsolute path\n    if (!fs.existsSync(module)) {\n      modulePath = new URL(import.meta.resolve(module)).pathname;\n      moduleKey = generateComponentKey(module);\n    } else {\n      modulePath = module;\n      moduleKey = generateComponentKey(modulePath);\n    }\n    const moduleGraphqlData = parseGraphqlByFile(modulePath);\n    queries[moduleKey] = moduleGraphqlData.query.source;\n    fragmentStr += `\\n${moduleGraphqlData.fragments.source}`;\n    Object.assign(\n      variables.values,\n      JSON5.parse(moduleGraphqlData.variables.source)\n    );\n    variables.defs = variables.defs.concat(\n      moduleGraphqlData.variables.definitions\n    );\n    variableList[moduleKey] = variables;\n    propsMap[moduleKey] = moduleGraphqlData.query.props;\n    inUsedFragments = inUsedFragments.concat(moduleGraphqlData.fragments.pairs);\n  });\n\n  // Process fragments\n  const extraFragments = [];\n  inUsedFragments.forEach((fragment) => {\n    // Check if there was a fragment with same name and type already processed\n    const f = extraFragments.find(\n      (ar) => ar.name === fragment.name && ar.type === fragment.type\n    );\n    if (f) {\n      // Replace fragment alias with the one already processed\n      const regex = new RegExp(`\\\\.\\\\.\\\\.([ ]+)?${fragment.alias}`, 'g');\n      queries = Object.keys(queries).reduce((acc, key) => {\n        acc[key] = queries[key].replace(regex, `...${f.alias}`);\n        return acc;\n      }, {});\n      fragmentStr = fragmentStr.replace(regex, `...${f.alias}`);\n    } else {\n      const regex = new RegExp(\n        `fragment([ ]+)${fragment.name}([ ]+)on([ ]+)${fragment.type}`,\n        'g'\n      );\n      fragmentStr = fragmentStr.replace(regex, () => {\n        const alias = `${fragment.name}_${uniqid()}`;\n        // Check if there is a fragment with the same name and type\n        const frm = extraFragments.find(\n          (ar) => ar.name === fragment.name && ar.type === fragment.type\n        );\n        if (frm) {\n          frm.child.push(alias);\n        } else {\n          extraFragments.push({\n            name: fragment.name,\n            alias: fragment.alias,\n            type: fragment.type,\n            child: [alias]\n          });\n        }\n        return `fragment ${alias} on ${fragment.type}`;\n      });\n    }\n  });\n  extraFragments.forEach((fragment) => {\n    fragmentStr += `\\nfragment ${fragment.alias} on ${\n      fragment.type\n    } {\\n ${fragment.child.map((c) => `...${c}`).join('\\n')} \\n}`;\n  });\n\n  return {\n    queries,\n    fragments: fragmentStr,\n    variables: variableList,\n    propsMap\n  };\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/webpack/util/parseGraphqlByFile.js",
    "content": "import fs from 'fs';\nimport { parse } from 'graphql';\nimport { print } from 'graphql/language/printer.js';\nimport JSON5 from 'json5';\nimport uniqid from 'uniqid';\n\n// This function should return an object { query, fragments, variables }.\nexport function parseGraphqlByFile(module) {\n  const result = {\n    query: {},\n    fragments: {},\n    variables: {}\n  };\n\n  const variables = [];\n\n  const fileSource = fs.readFileSync(module, 'utf8');\n  /** Process query */\n  // Regex matching export const query = `...` or export const query = \"\" or export const query = ''\n  const queryRegex =\n    /export\\s+const\\s+query\\s*=\\s*`([^`]+)`|export\\s+const\\s+query\\s*=\\s*[\"']([^\"']+)[\"']/g;\n  const queryMatch = fileSource.match(queryRegex);\n  if (queryMatch) {\n    let queryBody = queryMatch[0].replace(\n      queryRegex,\n      (match, p1, p2) => p1 || p2\n    );\n    queryBody = queryBody.replace(\n      /getContextValue\\(([^)]+)\\)/g,\n      (match, p1) => {\n        const base64 = Buffer.from(p1).toString('base64');\n        return `\"getContextValue_${base64}\"`;\n      }\n    );\n\n    queryBody = queryBody.replace(\n      /getWidgetSetting\\(([^)]+)\\)/g,\n      (match, p1) => {\n        const base64 = Buffer.from(p1).toString('base64');\n        return `\"getWidgetSetting_${base64}\"`;\n      }\n    );\n\n    const queryAst = parse(queryBody);\n    const map = queryAst.definitions[0].selectionSet.selections.map(\n      (selection) => {\n        const name = selection.name.value;\n        const alias = selection.alias ? selection.alias.value : name;\n        const newAlias = `e${uniqid()}`;\n        if (!selection.alias) {\n          selection.alias = {\n            kind: 'Name',\n            value: newAlias\n          };\n        } else {\n          selection.alias.value = newAlias;\n        }\n\n        return {\n          origin: alias,\n          alias: newAlias\n        };\n      }\n    );\n\n    // Get back the new query string\n    queryBody = print(queryAst);\n\n    // Regex to find all variable name and type ($name: Type!) in graphql query\n\n    const variableRegex = /\\$([a-zA-Z0-9]+)\\s*:\\s*([a-zA-Z0-9\\[\\]!]+)/g;\n    const variableMatch = queryBody.match(variableRegex);\n    if (variableMatch) {\n      variableMatch.forEach((variable) => {\n        const varRegex = /\\$([a-zA-Z0-9]+)\\s*:\\s*([a-zA-Z0-9\\[\\]!]+)/;\n        const varMatch = varRegex.exec(variable);\n        const name = varMatch[1];\n        const type = varMatch[2];\n        variables.push({\n          origin: name,\n          type,\n          alias: `variable_${uniqid()}`\n        });\n      });\n    }\n    // Replace all variable in graphql query\n    variables.forEach((variable) => {\n      // Use word boundary to ensure we match the complete variable name only\n      // This prevents partial matches like 'count' matching inside 'countPerRow'\n      const regex = new RegExp(`\\\\$${variable.origin}\\\\b`, 'g');\n      queryBody = queryBody.replace(regex, `$${variable.alias}`);\n    });\n    // Use slice function to get everything between the first '{' and the last '}' in the query\n    queryBody = queryBody.slice(\n      queryBody.indexOf('{') + 1,\n      queryBody.lastIndexOf('}')\n    );\n\n    result.query.source = queryBody;\n    result.query.props = map;\n  } else {\n    result.query.source = '';\n    result.query.props = [];\n  }\n\n  /** Process fragments */\n  // Regex matching export const query = `...` or export const query = \"\" or export const query = ''\n  const fragmentsRegex =\n    /export\\s+const\\s+fragments\\s*=\\s*`([^`]+)`|export\\s+const\\s+fragments\\s*=\\s*[\"']([^\"']+)[\"']/g;\n  const fragmentsMatch = fileSource.match(fragmentsRegex);\n  const fragmentNames = [];\n  if (fragmentsMatch) {\n    const fragmentsBody = fragmentsMatch[0].replace(\n      fragmentsRegex,\n      (match, p1, p2) => p1 || p2\n    );\n    const fragmentsAst = parse(fragmentsBody);\n    fragmentsAst.definitions.forEach((fragment) => {\n      if (fragment.kind === 'FragmentDefinition') {\n        fragmentNames.push({\n          name: fragment.name.value,\n          type: fragment.typeCondition.name.value\n        });\n      } else {\n        throw new Error(\n          `Only fragments are allowed in 'export const fragments = \\`...\\`. Error in ${module}`\n        );\n      }\n    });\n    result.fragments.source = fragmentsBody;\n  } else {\n    result.fragments.source = '';\n  }\n\n  // Using regex to get all fragment consumption (e.g. ...fragmentName)\n  const fragmentConsumptions = (\n    result.query.source.match(/\\.\\.\\.([ ]+)?([a-zA-Z0-9_]+)/g) || []\n  ).concat(\n    result.fragments.source.match(/\\.\\.\\.([ ]+)?([a-zA-Z0-9_]+)/g) || []\n  );\n\n  // Deduplicate fragment consumptions to handle multiple usages of the same fragment\n  const uniqueFragmentNames = [\n    ...new Set(\n      fragmentConsumptions.map((consumption) =>\n        consumption.replace(/\\.\\.\\.([ ]+)?/, '')\n      )\n    )\n  ];\n\n  if (uniqueFragmentNames.length > 0) {\n    uniqueFragmentNames.forEach((fragmentName) => {\n      const fragment = fragmentNames.find((f) => f.name === fragmentName);\n      if (!fragment) {\n        throw new Error(\n          `Fragment '${fragmentName}' is not defined in ${module}`\n        );\n      } else {\n        result.fragments.pairs = result.fragments.pairs || [];\n        const alias = `${fragmentName}_${uniqid()}`;\n        const regex = new RegExp(`\\\\.\\\\.\\\\.([ ]+)?${fragmentName}\\\\b`, 'g');\n        // Replace in query source with alias\n        result.query.source = result.query.source.replace(regex, `...${alias}`);\n        // Replace in fragment source with alias\n        result.fragments.source = result.fragments.source.replace(\n          regex,\n          `...${alias}`\n        );\n        result.fragments.pairs.push({\n          name: fragmentName,\n          alias,\n          type: fragment.type\n        });\n      }\n    });\n  } else {\n    result.fragments.pairs = [];\n  }\n\n  /** Processing variables */\n  // Regex matching export const variables = `{ ... }`\n  const variablesRegex = /export\\s+const\\s+variables\\s*=\\s*`([^`]+)`/g;\n  const variablesMatch = fileSource.match(variablesRegex);\n  if (variablesMatch) {\n    let variablesBody = variablesMatch[0].replace(\n      variablesRegex,\n      (match, p1) => p1\n    );\n    variablesBody = variablesBody.replace(\n      /getContextValue\\(([^)]+)\\)/g,\n      (match, p1) => {\n        const base64 = Buffer.from(p1).toString('base64');\n        return `\"getContextValue_${base64}\"`;\n      }\n    );\n    variablesBody = variablesBody.replace(\n      /getWidgetSetting\\(([^)]*)\\)/g,\n      (match, p1) => {\n        const base64 = Buffer.from(p1).toString('base64');\n        return `\"getWidgetSetting_${base64}\"`;\n      }\n    );\n    try {\n      // Json parse the variables body\n      const variablesJson = JSON5.parse(variablesBody);\n      // Replace all variable in graphql query\n      Object.keys(variablesJson).forEach((variableName) => {\n        const variable = variables.find((v) => v.origin === variableName);\n        if (variable) {\n          variablesJson[variable.alias] = variablesJson[variableName];\n          delete variablesJson[variableName];\n        }\n      });\n      result.variables.source = JSON5.stringify(variablesJson);\n      result.variables.definitions = variables;\n    } catch (e) {\n      throw new Error(`Invalid variables in ${module}`);\n    }\n  } else {\n    result.variables.source = '{}';\n    result.variables.definitions = [];\n  }\n  return result;\n}\n"
  },
  {
    "path": "packages/evershop/src/lib/widget/tests/unit/widgetManager.test.js",
    "content": "import { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n\nconst getValidWidget = (type = 'TestWidgetType') => ({\n  name: `Widget ${type}`,\n  type: type,\n  description: `Description for ${type}`,\n  settingComponent: `${type}Settings.js`,\n  component: `${type}Component.js`,\n  enabled: true,\n  defaultSettings: { theme: 'light' }\n});\n\njest.unstable_mockModule('fs', () => ({\n  existsSync: jest.fn((path) => {\n    if (\n      path.includes('InvalidComponent.js') ||\n      path.includes('InvalidSetting.js')\n    ) {\n      return false; // Simulate unresolvable paths\n    }\n    return true; // Simulate valid paths for other components\n  }),\n  statSync: jest.fn(() => ({\n    isFile: () => true\n  }))\n}));\n\nconst realPath = await import('path');\njest.unstable_mockModule('path', () => ({\n  default: true,\n  ...realPath,\n  resolve: jest.fn((...args) => `/mocked/path/${args.join('/')}`)\n}));\n\ndescribe('Widget Manager Module', () => {\n  beforeEach(async () => {\n    jest.resetModules(); // Reset modules before each test to ensure fresh mocks\n  });\n\n  afterEach(() => {\n    jest.clearAllMocks(); // Clear mock call history after each test\n  });\n  // --- Test _isFrozen state enforcement ---\n  describe('Mutation after getAllWidgets()', () => {\n    it('should throw an error if either component or settingComponent is not a string', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      const invalidWidget = {\n        name: 'InvalidWidget',\n        type: 'InvalidType',\n        description: 'This widget has invalid components',\n        settingComponent: 123, // Invalid type\n        component: null, // Invalid type\n        enabled: true,\n        defaultSettings: { theme: 'dark' }\n      };\n      expect(() => widgetModule.registerWidget(invalidWidget)).toThrow(\n        'Invalid or unresolvable'\n      );\n    });\n\n    it('should throw an error if either component or settingComponent is unresolvable path', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      const invalidWidget = {\n        name: 'InvalidWidget',\n        type: 'InvalidType',\n        description: 'This widget has invalid components',\n        settingComponent: 'InvalidSetting.js', // Unresolvable path\n        component: 'InvalidComponent.js', // Unresolvable path\n        enabled: true,\n        defaultSettings: { theme: 'dark' }\n      };\n      expect(() => widgetModule.registerWidget(invalidWidget)).toThrow(\n        'Invalid or unresolvable'\n      );\n    });\n\n    it('should throw an error if registerWidget is called after getAllWidgets', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      widgetModule.getAllWidgets(); // This freezes the manager\n      const widget = getValidWidget('NewWidget');\n      expect(() => widgetModule.registerWidget(widget)).toThrow(\n        'Widget manager is in a read-only state. No further mutations are allowed. We suggest to register, update, or remove a widget from the bootstrap file.'\n      );\n    });\n\n    it('should throw an error if updateWidget is called after getAllWidgets', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      widgetModule.registerWidget(getValidWidget('ExistingWidget'));\n      widgetModule.getAllWidgets(); // This freezes the manager\n      expect(() =>\n        widgetModule.updateWidget('ExistingWidget', { description: 'Updated' })\n      ).toThrow(\n        'Widget manager is in a read-only state. No further mutations are allowed. We suggest to register, update, or remove a widget from the bootstrap file.'\n      );\n    });\n\n    it('should throw an error if removeWidget is called after getAllWidgets', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      widgetModule.registerWidget(getValidWidget('WidgetToRemove'));\n      widgetModule.getAllWidgets(); // This freezes the manager\n      expect(() => widgetModule.removeWidget('WidgetToRemove')).toThrow(\n        'Widget manager is in a read-only state. No further mutations are allowed. We suggest to register, update, or remove a widget from the bootstrap file.'\n      );\n    });\n\n    it('should allow getWidget after getAllWidgets', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      const widget = getValidWidget('ReadWidget');\n      widgetModule.registerWidget(widget);\n      widgetModule.getAllWidgets(); // This freezes the manager\n      expect(widgetModule.getWidget('ReadWidget')).toEqual(widget);\n    });\n\n    it('should allow hasWidget after getAllWidgets', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      const widget = getValidWidget('CheckWidget');\n      widgetModule.registerWidget(widget);\n      widgetModule.getAllWidgets(); // This freezes the manager\n      expect(widgetModule.hasWidget('CheckWidget')).toBe(true);\n    });\n\n    it('should allow to updateWidget', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      const widget = getValidWidget('UpdateWidget');\n      widgetModule.registerWidget(widget);\n      const newWidget = {\n        ...widget,\n        description: 'Updated Description',\n        component: 'UpdatedComponent.js'\n      };\n      widgetModule.updateWidget('UpdateWidget', newWidget);\n      expect(widgetModule.getWidget('UpdateWidget')).toEqual(newWidget);\n      expect(widgetModule.getWidget('UpdateWidget').component).toEqual(\n        'UpdatedComponent.js'\n      );\n      expect(widgetModule.getWidget('UpdateWidget').description).toEqual(\n        'Updated Description'\n      );\n    });\n\n    it('should throw error if trying to updateWidget with non-existing widget', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      expect(() =>\n        widgetModule.updateWidget('NonExistingWidget', { description: 'Test' })\n      ).toThrow('Widget not found');\n    });\n\n    it('should thrown an error if trying to update widget after calling getAllWidgets', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      widgetModule.registerWidget(getValidWidget('WidgetToUpdate'));\n      widgetModule.getAllWidgets(); // This freezes the manager\n      expect(() =>\n        widgetModule.updateWidget('WidgetToUpdate', { description: 'New Desc' })\n      ).toThrow(\n        'Widget manager is in a read-only state. No further mutations are allowed. We suggest to register, update, or remove a widget from the bootstrap file.'\n      );\n    });\n\n    it('should allow removing a widget', async () => {\n      const widgetModule = await import('../../widgetManager.js');\n      const widget = getValidWidget('WidgetToRemove');\n      widgetModule.registerWidget(widget);\n      widgetModule.removeWidget('WidgetToRemove');\n      expect(widgetModule.hasWidget('WidgetToRemove')).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/lib/widget/widgetManager.ts",
    "content": "import * as fs from 'fs';\nimport * as path from 'path';\nimport { Widget } from '../../types/widget.js';\nimport { warning } from '../log/logger.js';\nimport { generateComponentKey } from '../util/keyGenerator.js';\n\n/**\n * Checks if a given path is a valid and resolvable JavaScript file path.\n * A path is considered valid if it's a string, not empty, exists on the filesystem,\n * and has a .js extension.\n *\n * @param {string | undefined} filePath - The path to check. Can be undefined.\n * @returns {boolean} True if the path is a resolvable JavaScript file, false otherwise.\n */\nfunction isValidJsFilePath(filePath: string | undefined): boolean {\n  if (typeof filePath !== 'string' || filePath.trim() === '') {\n    return false;\n  }\n\n  const resolvedPath = path.resolve(filePath);\n  const fileExtension = path.extname(resolvedPath);\n\n  try {\n    if (!fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isFile()) {\n      return false;\n    }\n  } catch (e) {\n    return false;\n  }\n\n  return fileExtension === '.js';\n}\n\n/**\n * Checks if the base filename of a given path starts with an uppercase letter.\n * This is typically used for validating component names, following common conventions.\n *\n * @param {string | undefined} filePath - The path to check. Can be undefined.\n * @returns {boolean} True if the base filename starts with an uppercase letter, false otherwise.\n */\nfunction isComponentNameUppercase(filePath: string | undefined): boolean {\n  if (typeof filePath !== 'string' || filePath.trim() === '') {\n    return false;\n  }\n  const baseName = path.parse(filePath).name;\n  return (\n    baseName.length > 0 &&\n    baseName[0] === baseName[0].toUpperCase() &&\n    !!baseName[0].match(/[A-Z]/)\n  );\n}\n\n/**\n * Validates the type of a widget. Only characters and underscores are allowed, no spaces or special characters.\n * @param type - The type of the widget to validate.\n * @returns {boolean} True if the type is valid, false otherwise.\n */\nfunction isValidType(type: string | undefined): boolean {\n  return (\n    typeof type === 'string' &&\n    type.trim() !== '' &&\n    /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(type)\n  );\n}\n\nclass WidgetManager {\n  /**\n   * @private\n   * A private map to store registered widgets. The key is the widget's unique name,\n   * and the value is the widget object adhering to the Widget interface.\n   */\n  private widgets: Map<string, Widget> = new Map();\n\n  /**\n   * @private\n   * A flag indicating whether the widget manager has entered a read-only state.\n   * Once set to true (after `getAllWidgets` is called for the first time),\n   * no further mutations (add, remove, update) are allowed.\n   */\n  private _isFrozen: boolean = false;\n\n  /**\n   * Internal helper to check if mutations are allowed.\n   * Throws an error if the manager is in a frozen (read-only) state.\n   * @private\n   * @throws {Error} If a mutation attempt is made after the manager is frozen.\n   */\n  private _ensureMutable(): void {\n    if (this._isFrozen) {\n      throw new Error(\n        'Widget manager is in a read-only state. No further mutations are allowed. We suggest to register, update, or remove a widget from the bootstrap file.'\n      );\n    }\n  }\n\n  /**\n   * Registers a new widget with the manager.\n   * A widget must have a unique 'type' property.\n   * If a widget with the same type already exists, it will not be registered.\n   * Additionally, `settingComponent` and `component` paths must\n   * be resolvable paths to a JavaScript file, and their base filename must start\n   * with an uppercase letter.\n   *\n   * @param {Widget} widget - The widget object to register.\n   * @returns {boolean} True if the widget was successfully registered, false otherwise.\n   * @throws {Error} If called after the manager has entered a read-only state or on invalid widget data/paths.\n   */\n  public registerWidget(widget: Widget): boolean {\n    this._ensureMutable();\n\n    if (!widget || isValidType(widget.type) === false) {\n      throw new Error(\n        'Cannot register widget. Widget object must have a valid \"type\" property.'\n      );\n    }\n\n    const widgetType = widget.type;\n\n    if (this.widgets.has(widgetType)) {\n      warning(\n        `Widget with type \"${widgetType}\" is already registered. Skipping registration.`\n      );\n      return false;\n    }\n\n    if (!isValidJsFilePath(widget.settingComponent)) {\n      throw new Error(\n        `Cannot register widget \"${widgetType}\". Invalid or unresolvable settingComponent path: \"${widget.settingComponent}\". Please ensure it's a valid path to an existing JS file.`\n      );\n    }\n    if (!isComponentNameUppercase(widget.settingComponent)) {\n      throw new Error(\n        `Cannot register widget \"${widgetType}\". Setting component filename \"${\n          path.parse(widget.settingComponent).name\n        }\" must start with an uppercase letter.`\n      );\n    }\n\n    if (!isValidJsFilePath(widget.component)) {\n      throw new Error(\n        `Cannot register widget \"${widgetType}\". Invalid or unresolvable component path: \"${widget.component}\". Please ensure it's a valid path to an existing JS file.`\n      );\n    }\n    if (!isComponentNameUppercase(widget.component)) {\n      throw new Error(\n        `Cannot register widget \"${widgetType}\". Main component filename \"${\n          path.parse(widget.component).name\n        }\" must start with an uppercase letter.`\n      );\n    }\n\n    this.widgets.set(widgetType, widget);\n    return true;\n  }\n\n  /**\n   * Updates properties of an existing widget. This is useful for third-party extensions\n   * to modify or \"overwrite\" parts of a core widget, such as its `settingComponent` or `component`.\n   *\n   * @param {string} widgetType - The type of the widget to update.\n   * @param {Partial<Widget>} updates - An object containing the properties to update.\n   * @returns {boolean} True if the widget was successfully updated, false otherwise.\n   * @throws {Error} If called after the manager has entered a read-only state or on invalid update data/paths.\n   */\n  public updateWidget(widgetType: string, updates: Partial<Widget>): boolean {\n    this._ensureMutable();\n    if (\n      !updates ||\n      typeof updates !== 'object' ||\n      Object.keys(updates).length === 0\n    ) {\n      throw new Error(\n        `Cannot update widget \"${widgetType}\". No updates provided or updates object is invalid.`\n      );\n    }\n\n    const typeToUpdate = widgetType;\n    const existingWidget = this.widgets.get(typeToUpdate);\n\n    if (!existingWidget) {\n      throw new Error(\n        `Cannot update widget \"${typeToUpdate}\". Widget not found.`\n      );\n    }\n\n    if (updates.settingComponent !== undefined) {\n      if (!isValidJsFilePath(updates.settingComponent)) {\n        throw new Error(\n          `Cannot update widget \"${typeToUpdate}\". Invalid or unresolvable new settingComponent path: \"${updates.settingComponent}\". Please ensure it's a valid path to an existing JS file.`\n        );\n      } else if (!isComponentNameUppercase(updates.settingComponent)) {\n        throw new Error(\n          `Cannot update widget \"${typeToUpdate}\". New setting component filename \"${\n            path.parse(updates.settingComponent).name\n          }\" must start with an uppercase letter.`\n        );\n      } else {\n        existingWidget.settingComponent = updates.settingComponent;\n      }\n    }\n\n    if (updates.component !== undefined) {\n      if (!isValidJsFilePath(updates.component)) {\n        throw new Error(\n          `Error: Cannot update widget \"${typeToUpdate}\". Invalid or unresolvable new component path: \"${updates.component}\". Please ensure it's a valid path to an existing JS file.`\n        );\n      } else if (!isComponentNameUppercase(updates.component)) {\n        throw new Error(\n          `Error: Cannot update widget \"${typeToUpdate}\". New main component filename \"${\n            path.parse(updates.component).name\n          }\" must start with an uppercase letter.`\n        );\n      } else {\n        existingWidget.component = updates.component;\n      }\n    }\n\n    for (const key in updates) {\n      if (\n        Object.prototype.hasOwnProperty.call(updates, key) &&\n        key !== 'settingComponent' &&\n        key !== 'component'\n      ) {\n        (existingWidget as any)[key] = (updates as any)[key];\n      }\n    }\n\n    return true;\n  }\n\n  /**\n   * Removes a widget from the manager based on its unique type.\n   *\n   * @param {string} widgetType - The type of the widget to remove.\n   * @returns {boolean} True if the widget was successfully removed, false otherwise.\n   * @throws {Error} If called after the manager has entered a read-only state or on invalid widget type.\n   */\n  public removeWidget(widgetType: string): boolean {\n    this._ensureMutable();\n\n    const typeToRemove = widgetType;\n\n    if (this.widgets.has(typeToRemove)) {\n      this.widgets.delete(typeToRemove);\n      return true;\n    } else {\n      warning(`Widget with type \"${typeToRemove}\" not found. Cannot remove.`);\n      return false;\n    }\n  }\n\n  /**\n   * Retrieves a registered widget by its unique type.\n   *\n   * @param {string} widgetType - The type of the widget to retrieve.\n   * @returns {Widget | undefined} The widget object if found, otherwise undefined.\n   */\n  public getWidget(widgetType: string): Widget | undefined {\n    if (this.widgets.has(widgetType)) {\n      return this.widgets.get(widgetType);\n    } else {\n      warning(`Widget with type \"${widgetType}\" not found.`);\n      return undefined;\n    }\n  }\n\n  /**\n   * Retrieves all registered widgets.\n   * Returns a new array containing frozen (immutable) copies of the widget objects.\n   * This method also marks the WidgetManager as 'frozen', preventing any further\n   * calls to mutation methods (register, remove, update).\n   *\n   * @returns {Widget[]} An array containing all registered widget objects.\n   */\n  public getAllWidgets(): Widget[] {\n    this._isFrozen = true;\n\n    // Create a new array, and for each widget, create a frozen copy.\n    return Array.from(this.widgets.values()).map((widget) =>\n      Object.freeze({ ...widget })\n    );\n  }\n\n  /**\n   * Checks if a widget with the given type is registered.\n   *\n   * @param {string} widgetType - The type of the widget to check.\n   * @returns {boolean} True if the widget is registered, false otherwise.\n   */\n  public hasWidget(widgetType: string): boolean {\n    return this.widgets.has(widgetType);\n  }\n}\n\nconst widgetManager = new WidgetManager();\n\n/**\n * Retrieves all registered widgets. This function returns a new array containing\n * all widgets, each with its `settingComponentKey` and `componentKey` properties\n * generated using the `generateComponentKey` function.\n * Calling this function will also freeze the widget manager, preventing any further mutations (register, remove, update).\n * @returns {Widget[]} An array of all registered widgets.\n */\nexport function getAllWidgets(): Widget[] {\n  const allWidgets = widgetManager.getAllWidgets();\n  return allWidgets.map((widget) => {\n    return {\n      ...widget,\n      settingComponentKey: generateComponentKey(widget.settingComponent),\n      componentKey: generateComponentKey(widget.component)\n    };\n  });\n}\n\n/**\n * Retrieves all enabled widgets. An enabled widget is one that has its `enabled` property set to true.\n * This function returns a new array containing only the widgets that are enabled. Calling this function\n * will also freeze the widget manager, preventing any further mutations (register, remove, update).\n * @returns {Widget[]} An array of enabled widgets.\n */\nexport function getEnabledWidgets(): Widget[] {\n  const allWidgets = widgetManager.getAllWidgets();\n  return allWidgets\n    .filter((widget) => widget.enabled)\n    .map((widget) => {\n      return {\n        ...widget,\n        settingComponentKey: generateComponentKey(widget.settingComponent),\n        componentKey: generateComponentKey(widget.component)\n      };\n    });\n}\n\n/**\n * Registers a new widget. This function is intended to be called during the\n * bootstrap phase of the application, before the widget manager is frozen.\n * @param widget - The widget object to register.\n * @returns True if the widget was successfully registered, false otherwise.\n * @throws Error if the widget is invalid or if the manager is in a read-only state.\n */\nexport function registerWidget(widget: Widget): boolean {\n  return widgetManager.registerWidget(widget);\n}\n\n/**\n * Updates properties of an existing widget. This is useful for third-party extensions\n * to modify or \"overwrite\" parts of a core widget, such as its `settingComponent` or `component`.\n * @param widgetType - The type of the widget to update.\n * @param updates - An object containing the properties to update.\n * @returns True if the widget was successfully updated, false otherwise.\n */\nexport function updateWidget(\n  widgetType: string,\n  updates: Partial<Widget>\n): boolean {\n  return widgetManager.updateWidget(widgetType, updates);\n}\n\n/**\n * Removes a widget. This function supposed to be called from the bootstrap\n * phase of the application, before the widget manager is frozen.\n * @param widgetName - The name of the widget to remove.\n * @returns True if the widget was successfully removed, false otherwise.\n */\nexport function removeWidget(widgetName: string): boolean {\n  return widgetManager.removeWidget(widgetName);\n}\n/**\n * Retrieves a widget by its type.\n * @param widgetType - The type of the widget to retrieve.\n * @returns The widget if found, undefined otherwise.\n */\nexport function getWidget(widgetType: string): Widget | undefined {\n  return widgetManager.getWidget(widgetType);\n}\n\n/**\n * Checks if a widget with the given type is registered.\n * @param widgetType - The type of the widget to check.\n * @returns True if the widget is registered, false otherwise.\n */\nexport function hasWidget(widgetType: string): boolean {\n  return widgetManager.hasWidget(widgetType);\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/getUserToken/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/getUserToken/generateToken.ts",
    "content": "import {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport {\n  generateToken,\n  generateRefreshToken,\n  TOKEN_TYPES\n} from '../../../../lib/util/jwt.js';\nimport { CurrentUser, EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  try {\n    const message = 'Invalid email or password';\n    const { body } = request;\n    const { email, password } = body;\n    await request.loginUserWithEmail(email, password, (error) => {\n      if (error) {\n        response.status(INTERNAL_SERVER_ERROR);\n        response.json({\n          error: {\n            status: INTERNAL_SERVER_ERROR,\n            message\n          }\n        });\n        return;\n      }\n    });\n    response.status(OK);\n    const accessToken = generateToken(\n      { user: request.locals.user as CurrentUser },\n      TOKEN_TYPES.ADMIN\n    );\n    const refreshToken = generateRefreshToken(\n      { user: request.locals.user as CurrentUser },\n      TOKEN_TYPES.ADMIN\n    );\n    response.json({\n      data: {\n        accessToken,\n        refreshToken\n      }\n    });\n  } catch (error) {\n    response.status(INVALID_PAYLOAD).json({\n      error: {\n        message: error.message,\n        status: INVALID_PAYLOAD\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/getUserToken/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    },\n    \"password\": {\n      \"type\": \"string\"\n    },\n    \"full_name\": {\n      \"type\": \"string\"\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/getUserToken/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/user/tokens\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/global/[context]getCurrentUser.ts",
    "content": "import util from 'util';\nimport { select } from '@evershop/postgres-query-builder';\nimport sessionStorage from 'connect-pg-simple';\nimport session from 'express-session';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { setContextValue } from '../../../graphql/services/contextHelper.js';\nimport { getAdminSessionCookieName } from '../../services/getAdminSessionCookieName.js';\n\n/**\n * This is the session based authentication middleware.\n * We do not implement session middleware on API routes, instead we only load the session from the database and set the user in the context.\n * @param {*} request\n * @param {*} response\n * @param {*} next\n * @returns\n */\nexport default async (request: EvershopRequest, response, next) => {\n  // Check if the user is authenticated, if yes we assume previous authentication middleware has set the user in the context\n  let currentAdminUser = request.getCurrentUser();\n  if (!currentAdminUser) {\n    try {\n      // Get the sesionID cookies\n      const cookies = request.signedCookies;\n      const adminSessionCookieName = getAdminSessionCookieName();\n      // Check if the sessionID cookie is present\n      const sessionID = cookies[adminSessionCookieName];\n      if (sessionID) {\n        const storage = new (sessionStorage(session))({\n          pool\n        });\n        // Load the session using session storage\n        const getSession = util.promisify(storage.get).bind(storage);\n        const adminSessionData = await getSession(sessionID);\n        if (adminSessionData) {\n          // Set the user in the context\n          currentAdminUser = await select()\n            .from('admin_user')\n            .where('admin_user_id', '=', adminSessionData.userID)\n            .and('status', '=', 1)\n            .load(pool);\n\n          if (currentAdminUser) {\n            // Delete the password field if present (cast to any to avoid TS error)\n            if ('password' in currentAdminUser) {\n              delete (currentAdminUser as any).password;\n            }\n            request.locals.user = currentAdminUser;\n            setContextValue(request, 'user', currentAdminUser);\n          }\n        }\n      }\n    } catch (e) {\n      // Do nothing, the user is not logged in\n    }\n  }\n  next();\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/global/[context]jwtUserAuth[getCurrentUser].ts",
    "content": "import { UNAUTHORIZED } from '../../../../lib/util/httpStatus.js';\nimport {\n  decodeToken,\n  TOKEN_TYPES,\n  verifyToken\n} from '../../../../lib/util/jwt.js';\nimport { EvershopRequest } from '../../../../types/request.js';\n\nexport default (request: EvershopRequest, response, next) => {\n  try {\n    // Get token from Authorization header\n    const authHeader = request.headers.authorization;\n    if (!authHeader || !authHeader.startsWith('Bearer ')) {\n      return next();\n    }\n    const token = authHeader.substring(7);\n    const decodedWithoutVerification = decodeToken(token);\n\n    if (\n      !decodedWithoutVerification ||\n      decodedWithoutVerification.tokenType !== TOKEN_TYPES.ADMIN\n    ) {\n      // If token type is not admin, skip processing\n      return next();\n    }\n    // Verify token\n    const decoded = verifyToken(token, TOKEN_TYPES.ADMIN);\n\n    // Attach user info to request\n    request.locals = request.locals || {};\n    request.locals.user = decoded.user;\n\n    return next();\n  } catch (error) {\n    response.status(UNAUTHORIZED);\n    return response.json({\n      error: {\n        status: UNAUTHORIZED,\n        message: error.message || 'Invalid token'\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/global/[getCurrentUser]auth.ts",
    "content": "import { UNAUTHORIZED } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\n\n/**\n * This is the session based authentication middleware.\n * We do not implement session middleware on API routes, instead we only load the session from the database and set the user in the context.\n * @param {*} request\n * @param {*} response\n * @param {*} next\n * @returns\n */\nexport default async (request: EvershopRequest, response, next) => {\n  // Get the current route\n  const { currentRoute } = request;\n  const currentAdminUser = request.getCurrentUser();\n  // If the current route is public, continue to the next middleware\n  // Missing access property means private\n  if (currentRoute?.access === 'public') {\n    next();\n    return;\n  }\n\n  if (!currentAdminUser?.uuid) {\n    // Response with 401 status code\n    response.status(UNAUTHORIZED);\n    response.json({\n      error: {\n        status: UNAUTHORIZED,\n        message: 'Unauthorized'\n      }\n    });\n  } else {\n    // Get user roles\n    const userRoles = currentAdminUser.roles || '*';\n    if (userRoles === '*') {\n      next();\n    } else {\n      const roles = userRoles.split(',');\n      if (roles.includes(currentRoute.id)) {\n        next();\n      } else {\n        response.status(UNAUTHORIZED);\n        response.json({\n          error: {\n            status: UNAUTHORIZED,\n            message: 'Unauthorized'\n          }\n        });\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/global/[getCurrentUser]demoAccountBlocking[auth].ts",
    "content": "import { getEnv } from '../../../../lib/util/getEnv.js';\nimport { UNAUTHORIZED } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\n\nexport default (request: EvershopRequest, response, next) => {\n  const { currentRoute } = request;\n  if (\n    request.method === 'GET' ||\n    currentRoute?.id === 'adminGraphql' ||\n    currentRoute?.access === 'public'\n  ) {\n    next();\n  } else {\n    const user = request.getCurrentUser();\n    const currentUserEmail = user?.email;\n    const demoUserEmails = getEnv('DEMO_USER_EMAILS', '').split(',');\n\n    if (\n      user &&\n      demoUserEmails &&\n      demoUserEmails.includes(currentUserEmail || '')\n    ) {\n      response.status(UNAUTHORIZED).json({\n        error: {\n          status: UNAUTHORIZED,\n          message: 'The demo account is not allowed to make changes'\n        }\n      });\n    } else {\n      next();\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/refreshUserToken/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/refreshUserToken/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"refreshToken\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Refresh token must be a string\"\n      }\n    }\n  },\n  \"required\": [\"refreshToken\"],\n  \"errorMessage\": {\n    \"required\": {\n      \"refreshToken\": \"Refresh token is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/refreshUserToken/refreshToken.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport {\n  INVALID_PAYLOAD,\n  UNAUTHORIZED\n} from '../../../../lib/util/httpStatus.js';\nimport {\n  generateToken,\n  TOKEN_TYPES,\n  verifyRefreshToken\n} from '../../../../lib/util/jwt.js';\n\nexport default async (request, response, next) => {\n  const { refreshToken } = request.body;\n\n  if (!refreshToken) {\n    return response.status(INVALID_PAYLOAD).json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message: 'Refresh token is required'\n      }\n    });\n  }\n\n  try {\n    // Verify refresh token\n    const decoded = verifyRefreshToken(refreshToken, TOKEN_TYPES.ADMIN);\n    // Get fresh admin user data\n    const adminUser = await select()\n      .from('admin_user')\n      .where('admin_user_id', '=', decoded.user.admin_user_id)\n      .and('status', '=', 1)\n      .load(pool);\n\n    if (!adminUser) {\n      return response.status(UNAUTHORIZED).json({\n        error: {\n          status: UNAUTHORIZED,\n          message: 'Admin user not found or inactive'\n        }\n      });\n    }\n\n    // Generate new access token\n    const payload = decoded.user;\n    const newAccessToken = generateToken(\n      {\n        user: payload\n      },\n      TOKEN_TYPES.ADMIN\n    );\n    return response.json({\n      success: true,\n      data: {\n        accessToken: newAccessToken\n      }\n    });\n  } catch (error) {\n    return response.status(UNAUTHORIZED).json({\n      error: {\n        status: UNAUTHORIZED,\n        message: error.message || 'Invalid refresh token'\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/api/refreshUserToken/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/user/token/refresh\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/bootstrap.js",
    "content": "import { request } from 'express';\nimport { hookable } from '../../lib/util/hookable.js';\nimport { loginUserWithEmail } from './services/loginUserWithEmail.js';\nimport { logoutUser } from './services/logoutUser.js';\n\nexport default () => {\n  request.loginUserWithEmail = async function login(email, password, callback) {\n    await hookable(loginUserWithEmail.bind(this))(email, password);\n    if (this.session) {\n      this.session.save(callback);\n    }\n  };\n\n  request.logoutUser = function logout(callback) {\n    hookable(logoutUser.bind(this))();\n    if (this.session) {\n      this.session.save(callback);\n    }\n  };\n\n  request.isUserLoggedIn = function isUserLoggedIn() {\n    return !!this.session.userID;\n  };\n\n  request.getCurrentUser = function getCurrentUser() {\n    return this.locals.user;\n  };\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/graphql/types/AdminUser/AdminUser.admin.graphql",
    "content": "\"\"\"\nRetrieves a single admin user by ID\n\"\"\"\ntype AdminUser {\n  adminUserId: Int!\n  uuid: String!\n  status: Int!\n  email: String!\n  fullName: String!\n}\n\n\"\"\"\nRetrieves a collection of admin users\n\"\"\"\ntype AdminUserCollection {\n  items: [AdminUser]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\nextend type Query {\n  adminUser(id: Int): AdminUser\n  currentAdminUser: AdminUser\n  adminUsers(filters: [FilterInput]): AdminUserCollection\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/graphql/types/AdminUser/AdminUser.admin.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\n\nexport default {\n  Query: {\n    adminUser: async (root, { id }, { pool }) => {\n      const query = select().from('admin_user');\n      query.where('admin_user_id', '=', id);\n\n      const adminUser = await query.load(pool);\n      return adminUser ? camelCase(adminUser) : null;\n    },\n    currentAdminUser: (root, args, { user }) => (user ? camelCase(user) : null),\n    adminUsers: async (_, { filters = [] }, { pool }) => {\n      const query = select().from('admin_user');\n      const currentFilters = [];\n\n      // Attribute filters\n      filters.forEach((filter) => {\n        if (filter.key === 'full_name') {\n          query.andWhere('admin_user.full_name', 'LIKE', `%${filter.value}%`);\n          currentFilters.push({\n            key: 'full_name',\n            operation: 'eq',\n            value: filter.value\n          });\n        }\n        if (filter.key === 'status') {\n          query.andWhere('admin_user.status', '=', filter.value);\n          currentFilters.push({\n            key: 'status',\n            operation: 'eq',\n            value: filter.value\n          });\n        }\n      });\n\n      const sortBy = filters.find((f) => f.key === 'sortBy');\n      const sortOrder = filters.find(\n        (f) => f.key === 'sortOrder' && ['ASC', 'DESC'].includes(f.value)\n      ) || { value: 'ASC' };\n      if (sortBy && sortBy.value === 'full_name') {\n        query.orderBy('admin_user.full_name', sortOrder.value);\n        currentFilters.push({\n          key: 'sortBy',\n          operation: 'eq',\n          value: sortBy.value\n        });\n      } else {\n        query.orderBy('admin_user.admin_user_id', 'DESC');\n      }\n\n      if (sortOrder.key) {\n        currentFilters.push({\n          key: 'sortOrder',\n          operation: 'eq',\n          value: sortOrder.value\n        });\n      }\n      // Clone the main query for getting total right before doing the paging\n      const cloneQuery = query.clone();\n      cloneQuery.select('COUNT(admin_user.admin_user_id)', 'total');\n      cloneQuery.removeOrderBy();\n      // Paging\n      const page = filters.find((f) => f.key === 'page') || { value: 1 };\n      const limit = filters.find((f) => f.key === 'limit' && f.value > 0) || {\n        value: 20\n      }; // TODO: Get from the config\n      currentFilters.push({\n        key: 'page',\n        operation: 'eq',\n        value: page.value\n      });\n      currentFilters.push({\n        key: 'limit',\n        operation: 'eq',\n        value: limit.value\n      });\n      query.limit(\n        (page.value - 1) * parseInt(limit.value, 10),\n        parseInt(limit.value, 10)\n      );\n      return {\n        items: (await query.execute(pool)).map((row) => camelCase(row)),\n        total: (await cloneQuery.load(pool)).total,\n        currentFilters\n      };\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/migration/Version-1.0.0.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE IF NOT EXISTS \"admin_user\" (\n  \"admin_user_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"status\" boolean NOT NULL DEFAULT TRUE,\n  \"email\" varchar NOT NULL,\n  \"password\" varchar NOT NULL,\n  \"full_name\" varchar DEFAULT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"ADMIN_USER_EMAIL_UNIQUE\" UNIQUE (\"email\"),\n  CONSTRAINT \"ADMIN_USER_UUID_UNIQUE\" UNIQUE (\"uuid\")\n);`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"user_token_secret\" (\n  \"user_token_secret_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"sid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"user_id\" varchar NOT NULL,\n  \"secret\" varchar NOT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"USER_TOKEN_SID_UNIQUE\" UNIQUE (\"sid\"),\n  CONSTRAINT \"USER_TOKEN_SECRET_UNIQUE\" UNIQUE (\"secret\")\n);`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/migration/Version-1.0.1.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Remove user_token_secret table\n  await execute(connection, `DROP TABLE IF EXISTS user_token_secret;`);\n\n  // Create a session table following the `connect-pg-simple` package\n  await execute(\n    connection,\n    `CREATE TABLE IF NOT EXISTS session (\n      sid varchar NOT NULL COLLATE \"default\",\n      sess json NOT NULL,\n      expire timestamp(6) NOT NULL\n    )\n    WITH (OIDS=FALSE);\n    ALTER TABLE session ADD CONSTRAINT \"SESSION_PKEY\" PRIMARY KEY (\"sid\") NOT DEFERRABLE INITIALLY IMMEDIATE;`\n  );\n\n  await execute(\n    connection,\n    `CREATE INDEX \"IDX_SESSION_EXPIRE\" ON \"session\" (\"expire\");`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/adminLogin/LoginForm.scss",
    "content": "body.adminLogin {\n  .content-wrapper {\n    margin-top: 0;\n  }\n  .admin-navigation {\n    display: none;\n  }\n  .main-content {\n    margin-left: 0;\n  }\n\n  .main-content-inner {\n    display: flex;\n    justify-content: center;\n    justify-items: center;\n    align-items: center;\n  }\n  .admin-login-form {\n    width: 468px;\n    max-width: 80%;\n    background-color: white;\n    border-radius: 5px;\n    padding: 2.5rem;\n    -webkit-box-shadow: 6px 12px 60px rgb(0 0 0 / 20%);\n    box-shadow: 6px 12px 60px rgb(0 0 0 / 20%);\n\n    h1 {\n      font-size: 1.875rem;\n      margin-bottom: 0.625rem;\n      font-weight: 400;\n      text-align: center;\n    }\n    .form-submit-button {\n      button {\n        width: 100%;\n        padding: 0.75rem 1rem;\n      }\n    }\n\n    .form-field-container {\n      label {\n        color: #666;\n      }\n    }\n    input[type='email'],\n    input[type='password'] {\n      padding: 0.5rem 0.75rem;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/adminLogin/LoginForm.tsx",
    "content": "import React from 'react';\nimport './LoginForm.scss';\nimport Area from '@components/common/Area.js';\nimport { EmailField } from '@components/common/form/EmailField.js';\nimport { Form, useFormContext } from '@components/common/form/Form.js';\nimport { PasswordField } from '@components/common/form/PasswordField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { LockKeyhole, Mail } from 'lucide-react';\n\ninterface LoginFormProps {\n  authUrl: string;\n  dashboardUrl: string;\n}\n\nconst SubmitButton: React.FC = () => {\n  const {\n    formState: { isSubmitting }\n  } = useFormContext();\n  return (\n    <div className=\"form-submit-button flex border-t border-border mt-4 pt-4 justify-between\">\n      <Button type=\"submit\" size=\"lg\" isLoading={isSubmitting}>\n        SIGN IN\n      </Button>\n    </div>\n  );\n};\n\nexport default function LoginForm({ authUrl, dashboardUrl }: LoginFormProps) {\n  const [error, setError] = React.useState(null);\n\n  const onSuccess = (response) => {\n    if (!response.error) {\n      window.location.href = dashboardUrl;\n    } else {\n      setError(response.error.message);\n    }\n  };\n\n  return (\n    <div className=\"admin-login-form\">\n      <style>{`\n        .header {\n          display: none !important;\n        }\n      `}</style>\n      <div className=\"flex items-center justify-center mb-7\">\n        <svg\n          width=\"60\"\n          height=\"61\"\n          viewBox=\"0 0 251 276\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M62.2402 34.2864L0.329313 68.5728L0.131725 137.524L0 206.538L62.306 240.95C96.5546 259.858 124.81 275.363 125.139 275.363C125.468 275.363 142.527 266.035 163.142 254.69C183.691 243.282 211.748 227.841 225.448 220.277L250.278 206.538V191.789V176.978L248.829 177.735C247.973 178.176 219.915 193.617 186.457 212.147C152.933 230.677 125.205 245.677 124.81 245.614C124.349 245.488 102.219 233.387 75.5444 218.639L27.0037 191.853V137.65V83.4471L48.9359 71.346C60.9229 64.7282 82.9211 52.6271 97.7401 44.4337C112.493 36.2402 124.876 29.5594 125.139 29.5594C125.402 29.5594 142.593 38.9504 163.339 50.4212L223.801 83.447L233.337 78.0776L250.278 68.5728L223.801 54.1398C202.857 42.2908 125.6 -0.0629883 124.941 6.10352e-05C124.546 6.10352e-05 96.2912 15.4415 62.2402 34.2864Z\"\n            fill=\"#008060\"\n          />\n          <path\n            d=\"M188.367 102.796C154.514 121.515 126.325 137.019 125.732 137.146C125.073 137.335 108.542 128.511 87.0045 116.662L49.397 95.8632V110.8L49.4628 125.675L86.0166 145.843C106.105 156.936 123.229 166.264 124.085 166.579C125.402 167.02 134.623 162.167 187.445 132.986C221.43 114.141 249.488 98.5734 249.817 98.3213C250.08 98.0691 250.212 91.3253 250.146 83.321L249.949 68.7618L188.367 102.796Z\"\n            fill=\"#008060\"\n          />\n          <path\n            d=\"M243.362 126.557C239.74 128.511 211.814 143.953 181.254 160.844C150.694 177.735 125.468 191.537 125.139 191.537C124.81 191.537 107.751 182.21 87.1363 170.865L49.7263 150.192L49.5288 164.688C49.397 175.781 49.5946 179.373 50.1874 179.941C51.4388 181.012 124.349 221.16 125.139 221.16C125.798 221.16 248.763 153.406 249.817 152.524C250.08 152.272 250.212 145.528 250.146 137.461L249.949 122.902L243.362 126.557Z\"\n            fill=\"#008060\"\n          />\n        </svg>\n      </div>\n      {error && <div className=\"text-destructive py-2\">{error}</div>}\n      <Form\n        action={authUrl}\n        method=\"POST\"\n        id=\"adminLoginForm\"\n        onSuccess={onSuccess}\n        submitBtn={false}\n      >\n        <Area\n          id=\"adminLoginForm\"\n          className=\"space-y-3\"\n          coreComponents={[\n            {\n              component: {\n                default: (\n                  <EmailField\n                    prefixIcon={<Mail className=\"h-5 w-5\" />}\n                    label=\"Email\"\n                    name=\"email\"\n                    placeholder=\"Email\"\n                    required\n                    validation={{\n                      required: 'Email is required'\n                    }}\n                  />\n                )\n              },\n              sortOrder: 10\n            },\n            {\n              component: {\n                default: (\n                  <PasswordField\n                    prefixIcon={<LockKeyhole className=\"h-5 w-5\" />}\n                    label=\"Password\"\n                    name=\"password\"\n                    placeholder=\"Password\"\n                    required\n                    validation={{\n                      required: 'Password is required'\n                    }}\n                    showToggle\n                  />\n                )\n              },\n              sortOrder: 20\n            },\n            {\n              component: {\n                default: <SubmitButton />\n              },\n              sortOrder: 30\n            }\n          ]}\n        />\n      </Form>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    authUrl: url(routeId: \"adminLoginJson\")\n    dashboardUrl: url(routeId: \"dashboard\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/adminLogin/index.ts",
    "content": "import { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request, response, next) => {\n  // Check if the user is logged in\n  const user = request.getCurrentUser();\n  if (user) {\n    // Redirect to admin dashboard\n    response.redirect(buildUrl('dashboard'));\n  } else {\n    setPageMetaInfo(request, {\n      title: 'Admin Login',\n      description: 'Admin Login'\n    });\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/adminLogin/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/login\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/adminLoginJson/[bodyParser]logIn.js",
    "content": "import { translate } from '../../../../../lib/locale/translate/translate.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  try {\n    const message = translate('Invalid email or password');\n    const { body } = request;\n    const { email, password } = body;\n    await request.loginUserWithEmail(email, password, (error) => {\n      if (error) {\n        response.status(INTERNAL_SERVER_ERROR);\n        response.json({\n          error: {\n            status: INTERNAL_SERVER_ERROR,\n            message\n          }\n        });\n      } else {\n        response.status(OK);\n        response.$body = {\n          data: {\n            sid: request.sessionID\n          }\n        };\n        next();\n      }\n    });\n  } catch (error) {\n    response.status(INVALID_PAYLOAD).json({\n      error: {\n        message: error.message,\n        status: INVALID_PAYLOAD\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/adminLoginJson/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    },\n    \"password\": {\n      \"type\": [\"string\"]\n    }\n  },\n  \"required\": [\"email\", \"password\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"email\": \"Email or password is invalid\",\n      \"password\": \"Email or password is invalid\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/adminLoginJson/route.json",
    "content": "{\n  \"name\": \"Admin User Login\",\n  \"description\": \"Admin User Login\",\n  \"methods\": [\"POST\"],\n  \"path\": \"/user/login\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/adminLogoutJson/logout.js",
    "content": "import {\n  INTERNAL_SERVER_ERROR,\n  OK\n} from '../../../../../lib/util/httpStatus.js';\n\nexport default (request, response, next) => {\n  try {\n    request.logoutUser((error) => {\n      if (error) {\n        response.status(INTERNAL_SERVER_ERROR);\n        response.json({\n          error: {\n            status: INTERNAL_SERVER_ERROR,\n            message: error.message\n          }\n        });\n      } else {\n        response.status(OK);\n        response.$body = {\n          data: {}\n        };\n        next();\n      }\n    });\n  } catch (error) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: error.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/adminLogoutJson/route.json",
    "content": "{\n  \"methods\": [\"GET\", \"POST\"],\n  \"path\": \"/user/logout\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/all/AdminUser.jsx",
    "content": "import {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger\n} from '@components/common/ui/DropdownMenu.js';\nimport { LogOut } from 'lucide-react';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\nexport default function AdminUser({ adminUser, logoutUrl, loginPage }) {\n  const logout = async () => {\n    const response = await fetch(logoutUrl, {\n      method: 'GET',\n      headers: {\n        'Content-Type': 'application/json'\n      }\n    });\n    if (response.status === 200) {\n      window.location.href = loginPage;\n    } else {\n      toast.error('Logout failed');\n    }\n  };\n\n  if (!adminUser) {\n    return null;\n  }\n  const { fullName } = adminUser;\n  return (\n    <div className=\"admin-user flex grow justify-end items-center\">\n      <div className=\"flex justify-items-start gap-2 justify-center\">\n        <DropdownMenu>\n          <DropdownMenuTrigger>\n            <button\n              className=\"w-[2.188rem] h-[2.188rem] flex items-center justify-center rounded-full bg-primary/45 font-semibold border-2 border-primary cursor-pointer hover:bg-primary/60 transition-colors\"\n              onClick={(e) => e.preventDefault()}\n            >\n              {fullName[0]}\n            </button>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent align=\"end\" className=\"w-45\">\n            <DropdownMenuLabel className=\"text-base font-normal\">\n              Hello <span className=\"text-primary\">{fullName}!</span>\n            </DropdownMenuLabel>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem\n              className=\"text-destructive focus:bg-destructive/10 focus:text-destructive cursor-pointer\"\n              onClick={(e) => {\n                e.preventDefault();\n                logout();\n              }}\n            >\n              <div className=\"flex justify-start items-center gap-2\">\n                <LogOut className=\"w-4 h-4\" />\n                <span>Logout</span>\n              </div>\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </div>\n    </div>\n  );\n}\n\nAdminUser.propTypes = {\n  adminUser: PropTypes.shape({\n    email: PropTypes.string.isRequired,\n    fullName: PropTypes.string.isRequired\n  }),\n  loginPage: PropTypes.string.isRequired,\n  logoutUrl: PropTypes.string.isRequired\n};\n\nAdminUser.defaultProps = {\n  adminUser: null\n};\n\nexport const layout = {\n  areaId: 'header',\n  sortOrder: 50\n};\n\nexport const query = `\n  query Query {\n    adminUser: currentAdminUser {\n      adminUserId\n      fullName\n      email\n    },\n    logoutUrl: url(routeId: \"adminLogoutJson\"),\n    loginPage: url(routeId: \"adminLogin\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/pages/admin/all/[context]auth.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\n\nexport default async (request, response, next) => {\n  const { userID } = request.session;\n  // Load the user from the database\n  const user = await select()\n    .from('admin_user')\n    .where('admin_user_id', '=', userID)\n    .and('status', '=', 1)\n    .load(pool);\n\n  if (!user) {\n    // The user may not be logged in, or the account may be disabled\n    // Logout the user\n    request.logoutUser(() => {\n      // Check if current route is adminLogin\n      if (\n        request.currentRoute.id === 'adminLogin' ||\n        request.currentRoute.id === 'adminLoginJson'\n      ) {\n        next();\n      } else {\n        response.redirect(buildUrl('adminLogin'));\n      }\n    });\n  } else {\n    // Delete the password field\n    delete user.password;\n    request.locals.user = user;\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/services/getAdminSessionCookieName.ts",
    "content": "import { getConfig } from '../../../lib/util/getConfig.js';\n\nexport const getAdminSessionCookieName = (): string =>\n  getConfig('system.session.adminCookieName', 'asid');\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/services/getCookieSecret.ts",
    "content": "import { getConfig } from '../../../lib/util/getConfig.js';\n\nexport const getCookieSecret = (): string =>\n  getConfig('system.session.cookieSecret', 'keyboard cat');\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/services/getFrontStoreSessionCookieName.ts",
    "content": "import { getConfig } from '../../../lib/util/getConfig.js';\n\nexport const getFrontStoreSessionCookieName = (): string =>\n  getConfig('system.session.cookieName', 'sid');\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/services/getSessionConfig.ts",
    "content": "import sessionStorage from 'connect-pg-simple';\nimport session from 'express-session';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\n\nexport const getSessionConfig = (cookieSecret): session.SessionOptions => {\n  const sess = {\n    store: new (sessionStorage(session))({\n      pool\n    }),\n    secret: cookieSecret,\n    cookie: {\n      maxAge: 24 * 60 * 60 * 1000\n    },\n    resave: getConfig('system.session.resave', false),\n    saveUninitialized: getConfig('system.session.saveUninitialized', false)\n  };\n\n  return sess;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/services/loginUserWithEmail.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { comparePassword } from '../../../lib/util/passwordHelper.js';\n\n/**\n * This function will login the admin user with email and password. This function must be accessed from the request object (request.loginUserWithEmail(email, password, callback))\n * @param {string} email\n * @param {string} password\n */\nasync function loginUserWithEmail(\n  email: string,\n  password: string\n): Promise<void> {\n  // Escape the email to prevent SQL injection\n  const userEmail = email.replace(/%/g, '\\\\%');\n  const user = await select()\n    .from('admin_user')\n    .where('email', 'ILIKE', userEmail)\n    .and('status', '=', 1)\n    .load(pool);\n  const result = comparePassword(password, user ? user.password : '');\n  if (!user || !result) {\n    throw new Error('Invalid email or password');\n  }\n  if (this.session) {\n    this.session.userID = user.admin_user_id;\n  }\n  // Delete the password field\n  delete user.password;\n  // Save the user in the request\n  this.locals.user = user;\n}\n\nexport { loginUserWithEmail };\n"
  },
  {
    "path": "packages/evershop/src/modules/auth/services/logoutUser.ts",
    "content": "/**\n * Logout a current user. This function must be accessed from the request object (request.logoutUser(callback))\n */\nexport function logoutUser() {\n  this.session.userID = undefined;\n  this.locals.user = undefined;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/api/global/[apiResponse]apiErrorHandler.ts",
    "content": "import { error } from '../../../../lib/log/logger.js';\nimport { INTERNAL_SERVER_ERROR } from '../../../../lib/util/httpStatus.js';\nimport isDevelopmentMode from '../../../../lib/util/isDevelopmentMode.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  err,\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  if (isDevelopmentMode() || process.argv.includes('--debug')) {\n    error(err);\n  }\n  // Check if the header is already sent or not.\n  if (response.headersSent) {\n    // TODO: Write a log message or next(error)?.\n  } else {\n    let status = INTERNAL_SERVER_ERROR;\n    if (!response.statusCode || response.statusCode === 200) {\n      response.status(status);\n    } else {\n      status = response.statusCode;\n    }\n    response.json({\n      error: {\n        status,\n        message: err.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/api/global/[auth]apiResponse[apiErrorHandler].ts",
    "content": "import isErrorHandlerTriggered from '../../../../lib/middleware/isErrorHandlerTriggered.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  try {\n    /** If a rejected middleware called next(error) without throwing an error */\n    if (isErrorHandlerTriggered(response)) {\n      return;\n    } else {\n      response.json(response.$body || {});\n    }\n  } catch (error) {\n    if (!isErrorHandlerTriggered(response)) {\n      next(error);\n    } else {\n      // Do nothing here since the next(error) is already called\n      // when the error is thrown on each middleware\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/api/global/[auth]payloadValidate.ts",
    "content": "import { INVALID_PAYLOAD } from '../../../../lib/util/httpStatus.js';\nimport { getValueSync } from '../../../../lib/util/registry.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport { getAjv } from '../../services/getAjv.js';\nimport markSkipEscape from '../../services/markSkipEscape.js';\n\n// Initialize the ajv instance\nconst ajv = getAjv();\n// Define a custom keyword for html escape\najv.addKeyword({\n  keyword: 'skipEscape',\n  modifying: true,\n  compile(sch, parentSchema) {\n    return (data, t) => {\n      if (sch === true) {\n        // Mark the data as skip escape\n        markSkipEscape(t.rootData, t.instancePath);\n        return true;\n      } else {\n        return true;\n      }\n    };\n  }\n});\n\nexport default (request: EvershopRequest, response: EvershopResponse, next) => {\n  // Get the current route\n  const { currentRoute } = request;\n  // Get the payload schema\n  const payloadSchema = getValueSync(\n    'payloadSchema',\n    currentRoute.payloadSchema,\n    { route: currentRoute }\n  );\n  if (!payloadSchema) {\n    next();\n  } else {\n    const validate = ajv.compile(payloadSchema);\n    const valid = validate(request.body);\n    if (valid) {\n      next();\n    } else {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: validate.errors[0].message\n        }\n      });\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/api/global/[payloadValidate]escapeHtml.ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport escapePayload from '../../services/escapePayload.js';\n\nexport default (request: EvershopRequest, response: EvershopResponse, next) => {\n  if (request.method === 'GET') {\n    next();\n  } else {\n    escapePayload(request.body);\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/api/global/context.js",
    "content": "import { pool } from '../../../../lib/postgres/connection.js';\nimport { getBaseUrl } from '../../../../lib/util/getBaseUrl.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport {\n  hasContextValue,\n  setContextValue\n} from '../../../graphql/services/contextHelper.js';\n\nexport default (request, response) => {\n  response.context = {};\n  /** Some default context value */\n  if (!hasContextValue(request, 'pool')) {\n    setContextValue(request.app, 'pool', pool);\n  }\n  const homeUrl = getBaseUrl();\n  setContextValue(request.app, 'homeUrl', homeUrl);\n  setContextValue(request, 'currentUrl', `${homeUrl}${request.originalUrl}`);\n  setContextValue(request, 'baseUrl', request.baseUrl);\n  setContextValue(request, 'body', request.body);\n  setContextValue(request, 'cookies', request.cookies);\n  setContextValue(request, 'fresh', request.fresh);\n  setContextValue(request.app, 'hostname', request.hostname);\n  setContextValue(request, 'ip', request.ip);\n  setContextValue(request, 'ips', request.ips);\n  setContextValue(request, 'method', request.method);\n  setContextValue(request, 'originalUrl', request.originalUrl);\n  setContextValue(request, 'params', request.params);\n  setContextValue(request, 'path', request.path);\n  setContextValue(request, 'protocol', request.protocol);\n  setContextValue(request, 'query', request.query);\n  setContextValue(request, 'route', request.route);\n  setContextValue(request, 'secure', request.secure);\n  setContextValue(request, 'signedCookies', request.signedCookies);\n  setContextValue(request, 'stale', request.stale);\n  setContextValue(request, 'subdomains', request.subdomains);\n  setContextValue(request, 'xhr', request.xhr);\n  setContextValue(request, 'sid', request.sessionID);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/bootstrap.js",
    "content": "import { loadCsv } from '../../lib/locale/translate/translate.js';\nimport { merge } from '../../lib/util/merge.js';\nimport { addProcessor } from '../../lib/util/registry.js';\n\nexport default async () => {\n  await loadCsv();\n  addProcessor('configurationSchema', (schema) => {\n    merge(schema, {\n      properties: {\n        shop: {\n          type: 'object',\n          properties: {\n            homeUrl: {\n              type: 'string',\n              format: 'uri'\n            },\n            weightUnit: {\n              type: 'string'\n            },\n            currency: {\n              type: 'string'\n            },\n            language: {\n              type: 'string'\n            },\n            timezone: {\n              type: 'string'\n            }\n          }\n        },\n        system: {\n          type: 'object',\n          properties: {\n            extensions: {\n              type: 'array',\n              items: {\n                type: 'object',\n                properties: {\n                  name: {\n                    type: 'string'\n                  },\n                  resolve: {\n                    type: 'string'\n                  },\n                  enabled: {\n                    type: 'boolean'\n                  },\n                  priority: {\n                    type: 'number'\n                  }\n                },\n                required: ['name', 'enabled', 'resolve']\n              }\n            },\n            theme: {\n              type: 'string',\n              required: ['name']\n            },\n            session: {\n              type: 'object',\n              properties: {\n                cookieSecret: {\n                  type: 'string'\n                },\n                cookieName: {\n                  type: 'string'\n                },\n                maxAge: {\n                  type: 'number'\n                },\n                reSave: {\n                  type: 'boolean'\n                },\n                saveUninitialized: {\n                  type: 'boolean'\n                }\n              }\n            }\n          }\n        }\n      }\n    });\n    return schema;\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Country/Country.graphql",
    "content": "\"\"\"\nThe `Country` type represents a country.\n\"\"\"\ntype Country {\n  name: String!\n  code: String!\n  provinces: [Province]\n}\n\nextend type Query {\n  countries(countries: [String]): [Country]\n  allowedCountries: [Country]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Country/Country.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { countries } from '../../../../../lib/locale/countries.js';\nimport { provinces } from '../../../../../lib/locale/provinces.js';\nimport { pool } from '../../../../../lib/postgres/connection.js';\n\nexport default {\n  Query: {\n    countries: (_, argument) => {\n      const list = argument?.countries || [];\n      if (list.length === 0) {\n        return countries;\n      } else {\n        return countries.filter((c) => list.includes(c.code));\n      }\n    },\n    allowedCountries: async () => {\n      const allowedCountries = await select('country')\n        .from('shipping_zone')\n        .execute(pool);\n      return countries.filter((c) =>\n        allowedCountries.find((p) => p.country === c.code)\n      );\n    }\n  },\n  Country: {\n    name: (country) => {\n      if (country.name) {\n        return country.name;\n      } else {\n        const c = countries.find((p) => p.code === country);\n        return c.name;\n      }\n    },\n    code: (country) => {\n      if (country.code) {\n        return country.code;\n      } else {\n        return country;\n      }\n    },\n    provinces: (country) =>\n      provinces.filter((p) => p.countryCode === country.code)\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Currency/Currency.graphql",
    "content": "\"\"\"\nA currency\n\"\"\"\ntype Currency {\n  name: String!\n  code: String!\n}\n\nextend type Query {\n  currencies: [Currency]!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Currency/Currency.resolvers.js",
    "content": "import { currencies } from '../../../../../lib/locale/currencies.js';\n\nexport default {\n  Query: {\n    currencies: () => currencies\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/DateTime/DateTime.graphql",
    "content": "\"\"\"\nA DateTime is a string with a timezone.\n\"\"\"\ntype DateTime {\n  value: String\n  timezone: String\n  text(format: String): String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/DateTime/DateTime.resolvers.js",
    "content": "import { DateTime } from 'luxon';\nimport { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  DateTime: {\n    value: (dateTime) => dateTime,\n    timezone: async () => {\n      const timeZone = getConfig('shop.timezone', 'UTC');\n      return timeZone;\n    },\n    text: async (value, { format = 'yyyy-LL-dd' }) => {\n      if (!DateTime.fromJSDate(value).isValid) {\n        return null;\n      }\n      const timeZone = getConfig('shop.timezone', 'UTC');\n      const language = getConfig('shop.language', 'en');\n      const date = DateTime.fromJSDate(value, { zone: timeZone })\n        .setLocale(language)\n        .setZone(timeZone)\n        .toFormat(format);\n      return date;\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Province/Province.graphql",
    "content": "\"\"\"\nThe `Province` type represents a province/state.\n\"\"\"\ntype Province {\n  name: String!\n  code: String!\n  countryCode: String!\n}\n\nextend type Query {\n  provinces(countries: [String]): [Province]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Province/Province.resolvers.js",
    "content": "import { provinces } from '../../../../../lib/locale/provinces.js';\n\nexport default {\n  Query: {\n    provinces: (_, { countries = [] }) => {\n      if (countries.length === 0) {\n        return provinces;\n      } else {\n        return provinces.filter((p) => countries.includes(p.countryCode));\n      }\n    }\n  },\n  Province: {\n    name: (province) => {\n      if (province.name) {\n        return province.name;\n      } else {\n        const p = provinces.find((pr) => pr.code === province);\n        return p?.name || 'INVALID_PROVINCE';\n      }\n    },\n    countryCode: (province) => {\n      if (province.countryCode) {\n        return province.countryCode;\n      } else {\n        const p = provinces.find((pr) => pr.code === province);\n        return p?.countryCode || 'INVALID_PROVINCE';\n      }\n    },\n    code: (province) => {\n      if (province?.code) {\n        return province?.code;\n      } else {\n        return province || 'INVALID_PROVINCE';\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Route/Route.admin.graphql",
    "content": "\"\"\"\nRepresents a route in the system\n\"\"\"\ntype Route {\n  id: String!\n  isApi: Boolean!\n  isAdmin: Boolean!\n  name: String!\n  path: String!\n  method: [String!]!\n}\n\nextend type Query {\n  routes: [Route!]!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Route/Route.admin.resolvers.js",
    "content": "import { getRoutes } from '../../../../../lib/router/Router.js';\n\nexport default {\n  Query: {\n    routes: () => {\n      const routes = getRoutes();\n      return routes.filter((route) => route.name);\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Timezone/Timezone.graphql",
    "content": "\"\"\"\nA timezone\n\"\"\"\ntype Timezone {\n  name: String!\n  code: String!\n}\n\nextend type Query {\n  timezones: [Timezone]!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Timezone/Timezone.resolvers.js",
    "content": "import { timezones } from '../../../../../lib/locale/timezones.js';\n\nexport default {\n  Query: {\n    timezones: () => timezones\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Url/Url.graphql",
    "content": "\"\"\"\nA query parameter for a URL\n\"\"\"\ninput UrlParam {\n  key: String!\n  value: String!\n}\n\nextend type Query {\n  url(routeId: String!, params: [UrlParam]): String!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Url/Url.resolvers.js",
    "content": "import { buildUrl } from '../../../../../lib/router/buildUrl.js';\n\nexport default {\n  Query: {\n    url: (root, { routeId, params = [] }, { homeUrl }) => {\n      const queries = [];\n      params.forEach((param) => {\n        // Check if the key is a string number\n        if (param.key.match(/^[0-9]+$/)) {\n          queries.push(param.value);\n        } else {\n          queries[param.key] = param.value;\n        }\n      });\n      return `${homeUrl}${buildUrl(routeId, queries)}`;\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Version/Version.graphql",
    "content": "extend type Query {\n  version: String!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/graphql/types/Version/Version.resolvers.js",
    "content": "import json from '@evershop/evershop/package.json' with { type: 'json' };\nimport { error } from '../../../../../lib/log/logger.js';\n\nexport default {\n  Query: {\n    version: () => {\n      try {\n        return json.version;\n      } catch (e) {\n        error(e);\n        return 'unknown';\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/migration/Version-1.0.1.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE \"event\" (\n  \"event_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"name\" varchar NOT NULL,\n  \"data\" json,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"EVENT_UUID\" UNIQUE (\"uuid\")\n)`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/admin/all/FormCss.tsx",
    "content": "import React from 'react';\n\nexport default function FormCss() {\n  return null;\n}\n\nexport const layout = {\n  areaId: 'head',\n  sortOrder: 5\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/admin/all/GlobalCss.tsx",
    "content": "import React from 'react';\nimport './global.scss';\n\nexport default function GlobalCss() {\n  return null;\n}\n\nexport const layout = {\n  areaId: 'head',\n  sortOrder: 5\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/admin/all/Layout.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport React from 'react';\n\nexport default function AdminLayout() {\n  return (\n    <>\n      <div className=\"header\">\n        <Area id=\"header\" noOuter />\n      </div>\n      <div className=\"content-wrapper\">\n        <div className=\"admin-navigation\">\n          <Area id=\"adminNavigation\" noOuter />\n        </div>\n        <div className=\"main-content\">\n          <Area id=\"content\" className=\"main-content-inner\" />\n          <div className=\"footer flex justify-between\">\n            <Area id=\"footerLeft\" className=\"footer-left\" />\n            <Area id=\"footerRight\" className=\"footer-right\" />\n          </div>\n        </div>\n      </div>\n    </>\n  );\n}\n\nexport const layout = {\n  areaId: 'body',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/admin/all/Meta.tsx",
    "content": "import { Meta } from '@components/common/Meta.js';\nimport { Title } from '@components/common/Title.js';\nimport React from 'react';\n\ninterface SeoMetaProps {\n  pageInfo: {\n    title: string;\n    description: string;\n  };\n}\n\nexport default function SeoMeta({\n  pageInfo: { title, description }\n}: SeoMetaProps) {\n  return (\n    <>\n      <Title title={title} />\n      <Meta name=\"description\" content={description} />\n    </>\n  );\n}\n\nexport const layout = {\n  areaId: 'head',\n  sortOrder: 5\n};\n\nexport const query = `\n  query query {\n    pageInfo {\n      title\n      description\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/admin/all/TailwindCss.tsx",
    "content": "import React from 'react';\nimport './tailwind.css';\n\nexport default function TailwindCss() {\n  return null;\n}\n\nexport const layout = {\n  areaId: 'head',\n  sortOrder: 1\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/admin/all/[context]isAdmin[auth].js",
    "content": "export default (request, response) => {\n  request.isAdmin = true;\n  response.context.isAdmin = true;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/admin/all/form.scss",
    "content": "@reference './tailwind.css';\n\nbody.admin {\n  .required-indicator {\n    @apply text-destructive;\n  }\n\n  .form-field {\n    @apply mb-4;\n\n    label {\n      @apply flex items-center mb-2 font-medium text-gray-700;\n\n      .field-unit {\n        @apply ml-2 text-sm text-gray-500;\n      }\n    }\n\n    fieldset {\n      legend {\n        @apply flex items-center mb-2 font-medium text-gray-700;\n      }\n    }\n\n    input[type='text'],\n    input[type='email'],\n    input[type='password'],\n    input[type='number'],\n    input[type='search'],\n    input[type='tel'],\n    input[type='url'],\n    input[type='date'],\n    input[type='time'],\n    input[type='datetime-local'],\n    input[type='color'],\n    input[type='range'],\n    input[type='file'],\n    textarea,\n    select {\n      @apply block w-full px-3 py-2 text-base text-gray-900 placeholder-gray-300 border border-gray-300 rounded-md focus:outline-none focus:border-gray-300 sm:text-sm transition-colors;\n\n      &:focus {\n        box-shadow: 0 0 0 5px rgba(59, 130, 246, 0.1),\n          0 0 0 2px rgb(59, 130, 246);\n      }\n\n      &.error {\n        @apply border-red-500;\n\n        &:focus {\n          box-shadow: 0 0 0 5px rgba(239, 68, 68, 0.1),\n            0 0 0 2px rgb(239, 68, 68);\n        }\n      }\n    }\n\n    select {\n      @apply appearance-none bg-no-repeat bg-right pr-10;\n      background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e\");\n      background-position: right 0.75rem center;\n      background-size: 1.25em 1.25em;\n\n      &:focus {\n        background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%233b82f6' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e\");\n      }\n\n      & ~ .field-suffix {\n        margin-left: -1.85rem;\n      }\n    }\n\n    &.error {\n      select {\n        @apply border-red-500;\n        background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23ef4444' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e\");\n\n        &:focus {\n          box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1),\n            0 0 0 1px rgb(239, 68, 68);\n          background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23ef4444' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e\");\n        }\n      }\n\n      input[type='checkbox'],\n      input[type='radio'] {\n        @apply border-red-500;\n      }\n    }\n\n    input[type='range'] {\n      @apply py-1;\n    }\n\n    input[type='file'] {\n      @apply file:mr-3 file:py-2 file:px-3 file:rounded-md file:border-0 file:text-sm file:font-medium file:bg-gray-50 file:text-gray-700 hover:file:bg-gray-100;\n    }\n\n    input[type='color'] {\n      @apply h-10 w-16 cursor-pointer;\n    }\n\n    input[type='checkbox'],\n    input[type='radio'] {\n      @apply w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded;\n\n      &:focus {\n        @apply outline-none border-blue-500;\n      }\n    }\n\n    input[type='radio'] {\n      @apply rounded-full;\n    }\n\n    .checkbox-group,\n    .radio-group {\n      @apply space-y-2;\n\n      &.horizontal {\n        @apply flex flex-wrap items-center gap-4 space-y-0;\n      }\n\n      .checkbox-item,\n      .radio-item {\n        @apply flex items-center;\n\n        input {\n          @apply mr-2;\n        }\n\n        label {\n          @apply mb-0 font-normal cursor-pointer text-sm text-gray-700;\n\n          &.disabled {\n            @apply text-gray-400 cursor-not-allowed;\n          }\n        }\n      }\n    }\n\n    .field-error {\n      @apply text-red-600 text-sm mt-1;\n    }\n\n    input[readonly],\n    textarea[readonly],\n    select[readonly] {\n      @apply bg-gray-50 text-gray-600 cursor-not-allowed border-gray-200;\n\n      &:focus {\n        @apply ring-0 border-gray-200 outline-none;\n      }\n    }\n\n    &.readonly-field {\n      input,\n      textarea,\n      select {\n        @apply bg-gray-50 text-gray-600 cursor-not-allowed border-gray-200;\n\n        &:focus {\n          @apply ring-0 border-gray-200 outline-none;\n        }\n      }\n\n      label {\n        @apply text-gray-500;\n      }\n\n      .field-helper {\n        @apply text-gray-400;\n      }\n    }\n\n    input[disabled],\n    textarea[disabled],\n    select[disabled] {\n      @apply bg-gray-100 text-gray-400 cursor-not-allowed opacity-60;\n    }\n\n    .field-helper {\n      @apply text-gray-500 text-sm mt-1;\n    }\n\n    .field-tooltip {\n      @apply relative inline-block ml-1;\n\n      .tooltip-trigger {\n        @apply w-4 h-4 text-gray-400 cursor-help;\n      }\n\n      .tooltip-content {\n        @apply absolute z-50 px-3 py-2 text-sm text-white bg-gray-900 rounded-md shadow-lg opacity-0 invisible transition-all duration-200;\n        @apply bottom-full left-1/2 transform -translate-x-1/2 mb-2;\n        @apply min-w-max max-w-xs;\n\n        &::after {\n          content: '';\n          @apply absolute top-full left-1/2 transform -translate-x-1/2;\n          border: 4px solid transparent;\n          border-top-color: #111827;\n        }\n\n        &.show {\n          @apply opacity-100 visible;\n        }\n      }\n    }\n\n    .color-field-container {\n      @apply flex items-center gap-2;\n\n      input[type='text'] {\n        @apply flex-1;\n      }\n\n      input[type='color'] {\n        @apply flex-shrink-0;\n      }\n    }\n\n    .number-field-container {\n      @apply relative;\n\n      .number-unit {\n        @apply absolute right-3 top-1/2 transform -translate-y-1/2 text-sm text-gray-500 pointer-events-none;\n      }\n\n      input {\n        &.has-unit {\n          @apply pr-12;\n        }\n      }\n    }\n\n    .file-field-info {\n      @apply text-xs text-gray-500 mt-1;\n    }\n\n    .color-input-group {\n      @apply flex items-center space-x-2;\n\n      .color-picker {\n        @apply w-16 h-10 p-1 border rounded cursor-pointer;\n      }\n\n      .color-input {\n        @apply flex-1 px-3 py-2 border rounded-md;\n      }\n    }\n\n    .range-value {\n      @apply ml-2 text-sm text-gray-500;\n    }\n\n    .range-labels {\n      @apply flex justify-between text-xs text-gray-500 mt-1;\n    }\n\n    .file-size-hint {\n      @apply mt-1 text-sm text-gray-500;\n    }\n\n    .file-list {\n      @apply mt-2;\n\n      .file-list-label {\n        @apply text-sm text-gray-600;\n      }\n\n      .file-items {\n        @apply text-sm text-gray-500 list-disc list-inside;\n      }\n    }\n\n    .react-select {\n      &__control {\n        @apply border border-gray-300 rounded-md transition-colors;\n\n        &--is-focused {\n          @apply border-blue-500;\n          box-shadow: 0 0 0 1px rgb(59, 130, 246);\n        }\n      }\n\n      &__menu {\n        @apply bg-white border border-gray-300 rounded-md shadow-lg mt-1;\n      }\n\n      &__option {\n        @apply px-3 py-2 cursor-pointer;\n\n        &--is-focused {\n          @apply bg-blue-50;\n        }\n\n        &--is-selected {\n          @apply bg-blue-600 text-white;\n        }\n      }\n\n      &__multi-value {\n        @apply bg-blue-100 rounded;\n\n        &__label {\n          @apply text-blue-800;\n        }\n\n        &__remove {\n          @apply text-blue-600 hover:bg-red-500 hover:text-white rounded-r;\n        }\n      }\n    }\n\n    &.error .react-select {\n      &__control {\n        @apply border-red-500;\n\n        &--is-focused {\n          @apply border-red-500;\n          box-shadow: 0 0 0 2px rgb(239, 68, 68);\n        }\n      }\n    }\n\n    .react-select {\n      &__input {\n        input {\n          &:focus {\n            box-shadow: none !important;\n            outline: none !important;\n          }\n        }\n      }\n\n      &__control {\n        &:focus-within {\n          @apply shadow-none;\n          box-shadow: none !important;\n        }\n      }\n    }\n  }\n\n  .react-select__input input:focus,\n  .react-select__input input:focus-visible,\n  .react-select__input input {\n    box-shadow: none !important;\n    outline: none !important;\n  }\n\n  div[class*='react-select'] input:focus {\n    box-shadow: none !important;\n    outline: none !important;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/admin/all/global.scss",
    "content": "body {\n  font-size: 0.875rem;\n}\n\n.header {\n  height: 3.5rem;\n  width: 100%;\n  padding: 0 1rem;\n  box-sizing: border-box;\n  display: flex;\n  position: fixed;\n  top: 0;\n  left: 0;\n  background-color: #fff;\n  box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.15);\n  z-index: 1000;\n}\n.content-wrapper {\n  margin-top: 3.5rem;\n  min-height: 100vh;\n  .main-content {\n    overflow: hidden;\n    margin-left: 15rem;\n    .main-content-inner {\n      padding: 1.25rem 1.875rem;\n      min-height: 100vh;\n    }\n  }\n}\n.footer {\n  padding-left: 1.875rem;\n  margin-top: 1.25rem;\n  padding-bottom: 0.625rem;\n  border-top: 1px solid var(--divider);\n  padding-top: 0.625rem;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/admin/all/shadcn.css",
    "content": "@theme inline {\n  @keyframes accordion-down {\n    from {\n      height: 0;\n    }\n    to {\n      height: var(\n        --radix-accordion-content-height,\n        var(--accordion-panel-height, auto)\n      );\n    }\n  }\n\n  @keyframes accordion-up {\n    from {\n      height: var(\n        --radix-accordion-content-height,\n        var(--accordion-panel-height, auto)\n      );\n    }\n    to {\n      height: 0;\n    }\n  }\n}\n\n/* Custom variants */\n@custom-variant data-open {\n  &:where([data-state='open']),\n  &:where([data-open]:not([data-open='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-closed {\n  &:where([data-state='closed']),\n  &:where([data-closed]:not([data-closed='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-checked {\n  &:where([data-state='checked']),\n  &:where([data-checked]:not([data-checked='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-unchecked {\n  &:where([data-state='unchecked']),\n  &:where([data-unchecked]:not([data-unchecked='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-selected {\n  &:where([data-selected='true']) {\n    @slot;\n  }\n}\n\n@custom-variant data-disabled {\n  &:where([data-disabled='true']),\n  &:where([data-disabled]:not([data-disabled='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-active {\n  &:where([data-state='active']),\n  &:where([data-active]:not([data-active='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-horizontal {\n  &:where([data-orientation='horizontal']) {\n    @slot;\n  }\n}\n\n@custom-variant data-vertical {\n  &:where([data-orientation='vertical']) {\n    @slot;\n  }\n}\n\n@utility no-scrollbar {\n  -ms-overflow-style: none;\n  scrollbar-width: none;\n\n  &::-webkit-scrollbar {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/admin/all/tailwind.css",
    "content": "@import 'tailwindcss';\n@import 'tw-animate-css';\n@import './shadcn.css';\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --font-sans: var(--font-sans);\n  --font-mono: var(--font-geist-mono);\n  --color-sidebar-ring: var(--sidebar-ring);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar: var(--sidebar);\n  --color-chart-5: var(--chart-5);\n  --color-chart-4: var(--chart-4);\n  --color-chart-3: var(--chart-3);\n  --color-chart-2: var(--chart-2);\n  --color-chart-1: var(--chart-1);\n  --color-ring: var(--ring);\n  --color-input: var(--input);\n  --color-border: var(--border);\n  --color-divider: var(--divider);\n  --color-destructive: var(--destructive);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-accent: var(--accent);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-muted: var(--muted);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-secondary: var(--secondary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-primary: var(--primary);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-popover: var(--popover);\n  --color-card-foreground: var(--card-foreground);\n  --color-card: var(--card);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --radius-2xl: calc(var(--radius) + 8px);\n  --radius-3xl: calc(var(--radius) + 12px);\n  --radius-4xl: calc(var(--radius) + 16px);\n}\n\n:root {\n  --radius: 0.325rem;\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.145 0 0);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.145 0 0);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.145 0 0);\n  --primary: oklch(0.53 0.11 167.71);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.97 0 0);\n  --secondary-foreground: oklch(0.205 0 0);\n  --muted: oklch(0.97 0 0);\n  --muted-foreground: oklch(0.556 0 0);\n  --accent: oklch(0.97 0 0);\n  --accent-foreground: oklch(0.205 0 0);\n  --destructive: oklch(0.577 0.245 27.325);\n  --border: oklch(0.922 0 0);\n  --divider: oklch(0.922 0 0);\n  --input: oklch(0.922 0 0);\n  --ring: oklch(70.9% 0.00008 271.152);\n  --chart-1: oklch(0.646 0.222 41.116);\n  --chart-2: oklch(0.6 0.118 184.704);\n  --chart-3: oklch(0.398 0.07 227.392);\n  --chart-4: oklch(0.828 0.189 84.429);\n  --chart-5: oklch(0.769 0.188 70.08);\n  --sidebar: oklch(0.985 0 0);\n  --sidebar-foreground: oklch(0.145 0 0);\n  --sidebar-primary: oklch(0.205 0 0);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.97 0 0);\n  --sidebar-accent-foreground: oklch(0.205 0 0);\n  --sidebar-border: oklch(0.922 0 0);\n  --sidebar-ring: oklch(0.708 0 0);\n}\n\n.dark {\n  --background: oklch(0.15 0.01 180);\n  --foreground: oklch(0.95 0.008 180);\n  --card: oklch(0.18 0.012 180);\n  --card-foreground: oklch(0.95 0.008 180);\n  --popover: oklch(0.17 0.012 180);\n  --popover-foreground: oklch(0.95 0.008 180);\n  /* Primary: Brighter teal for dark mode */\n  --primary: oklch(0.62 0.14 172);\n  --primary-foreground: oklch(0.12 0.02 172);\n  /* Secondary: Darker teal variant */\n  --secondary: oklch(0.25 0.04 172);\n  --secondary-foreground: oklch(0.9 0.04 172);\n  /* Muted: Dark with teal tint */\n  --muted: oklch(0.22 0.015 172);\n  --muted-foreground: oklch(0.68 0.02 180);\n  /* Accent: Vibrant teal for dark backgrounds */\n  --accent: oklch(0.28 0.05 172);\n  --accent-foreground: oklch(0.9 0.04 172);\n  /* Destructive: Softer red for dark mode */\n  --destructive: oklch(0.62 0.24 27);\n  --border: oklch(0.26 0.02 172);\n  --input: oklch(0.24 0.02 172);\n  --ring: oklch(0.62 0.14 172);\n  /* Chart colors: Adjusted for dark mode visibility */\n  --chart-1: oklch(0.7 0.16 172);\n  --chart-2: oklch(0.65 0.18 200);\n  --chart-3: oklch(0.75 0.18 90);\n  --chart-4: oklch(0.7 0.15 320);\n  --chart-5: oklch(0.7 0.16 30);\n  /* Sidebar: Dark teal tinted background */\n  --sidebar: oklch(0.16 0.015 172);\n  --sidebar-foreground: oklch(0.92 0.01 172);\n  --sidebar-primary: oklch(0.62 0.14 172);\n  --sidebar-primary-foreground: oklch(0.12 0.02 172);\n  --sidebar-accent: oklch(0.24 0.03 172);\n  --sidebar-accent-foreground: oklch(0.9 0.04 172);\n  --sidebar-border: oklch(0.28 0.025 172);\n  --sidebar-ring: oklch(0.62 0.14 172);\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/Base.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { LoadingBar } from '@components/common/LoadingBar.js';\nimport {\n  CartProvider,\n  CartData\n} from '@components/frontStore/cart/CartContext.js';\nimport {\n  CustomerProvider,\n  Customer\n} from '@components/frontStore/customer/CustomerContext.js';\nimport { Footer } from '@components/frontStore/Footer.js';\nimport { Header } from '@components/frontStore/Header.js';\nimport React from 'react';\n\ninterface BaseProps {\n  myCart: CartData;\n  customer: Customer;\n  themeConfig: {\n    copyRight: string;\n  };\n  addMineCartItemApi: string;\n  loginApi: string;\n  logoutApi: string;\n  registerApi: string;\n}\nexport default function Base({\n  myCart,\n  customer,\n  themeConfig,\n  addMineCartItemApi,\n  loginApi,\n  logoutApi,\n  registerApi\n}: BaseProps) {\n  return (\n    <CustomerProvider\n      initialCustomer={customer}\n      loginAPI={loginApi}\n      logoutAPI={logoutApi}\n      registerAPI={registerApi}\n    >\n      <CartProvider\n        cart={myCart}\n        query={`${query}\\n${fragments}`}\n        addMineCartItemApi={addMineCartItemApi}\n      >\n        <LoadingBar />\n        <Header />\n        <main className=\"content\">\n          <Area id=\"content\" noOuter />\n        </main>\n        <Footer copyRight={themeConfig.copyRight} />\n      </CartProvider>\n    </CustomerProvider>\n  );\n}\n\nexport const layout = {\n  areaId: 'body',\n  sortOrder: 1\n};\n\nexport const query = `\n  query Query {\n    myCart {\n      ...ShoppingCart\n      addItemApi\n      addPaymentMethodApi\n      addShippingMethodApi\n      addContactInfoApi\n      addAddressApi\n      addNoteApi\n      checkoutApi\n      applyCouponApi\n      removeCouponApi\n      availableShippingMethods {\n        code\n        name\n        cost {\n          value\n          text\n        }\n      }\n      availablePaymentMethods {\n        code\n        name\n      }\n      items {\n        ...ShoppingCartItem\n        cartItemId\n        removeApi\n        updateQtyApi\n        errors\n      }\n    }\n    customer: currentCustomer {\n      customerId\n      uuid\n      email\n      fullName\n      groupId\n      createdAt {\n        value\n        text\n      }\n      addAddressApi\n      addresses {\n        addressId\n        uuid\n        fullName\n        telephone \n        address1\n        address2\n        city\n        province {\n          code\n          name\n        }\n        country {\n          code\n          name\n        }\n        postcode\n        isDefault\n        updateApi\n        deleteApi\n      }\n      orders {\n        ...ShoppingCart\n        orderId\n        status {\n          name\n          code\n          badge\n        }\n        orderNumber\n        shipmentStatus {\n          name\n          code\n          badge\n        }\n        paymentStatus {\n          name\n          code\n          badge\n        }\n        items {\n          ...ShoppingCartItem\n          orderItemId\n        }\n      }\n    }\n    themeConfig {\n      copyRight\n    }\n    addMineCartItemApi: url(routeId: \"addMineCartItem\")\n    loginApi: url(routeId: \"customerLoginJson\")\n    registerApi: url(routeId: \"createCustomer\")\n    logoutApi: url(routeId: \"customerLogoutJson\")\n  }\n`;\n\nexport const fragments = `\n  fragment ShoppingCart on ShoppingCart {\n    uuid\n    currency\n    customerId\n    customerGroupId\n    customerEmail\n    customerFullName\n    coupon\n    noShippingRequired\n    shippingMethod\n    shippingMethodName\n    paymentMethod\n    paymentMethodName\n    shippingNote\n    taxAmount {\n      value\n      text\n    }\n    totalTaxAmount {\n      value\n      text\n    }\n    discountAmount {\n      value\n      text\n    }\n    shippingFeeExclTax {\n      value\n      text\n    }\n    shippingFeeInclTax {\n      value\n      text\n    }\n    shippingTaxAmount {\n      value\n      text\n    }\n    subTotal {\n      value\n      text\n    }\n    subTotalInclTax {\n      value\n      text\n    }\n    subTotalWithDiscount {\n      value\n      text\n    }\n    subTotalWithDiscountInclTax {\n      value\n      text\n    }\n    totalQty\n    totalWeight {\n      value\n      unit\n    }\n    taxAmountBeforeDiscount {\n      value\n      text\n    }\n    grandTotal {\n      value\n      text\n    }\n    billingAddress {\n      fullName\n      telephone\n      address1\n      address2\n      city\n      province {\n        name\n        code\n      }\n      country {\n        name\n        code\n      }\n      postcode\n    }\n    shippingAddress {\n      fullName\n      telephone\n      address1\n      address2\n      city\n      province {\n        name\n        code\n      }\n      country {\n        name\n        code\n      }\n      postcode\n    }\n    createdAt {\n      value\n      text\n    }\n    updatedAt  {\n      value\n      text\n    }\n  }\n\n  fragment ShoppingCartItem on ShoppingCartItem {\n    uuid\n    productId\n    productSku\n    productName\n    thumbnail\n    productWeight {\n      value\n      unit\n    }\n    productPrice {\n      value\n      text\n    }\n    productPriceInclTax {\n      value\n      text\n    }\n    qty\n    finalPrice {\n      value\n      text\n    }\n    finalPriceInclTax {\n      value\n      text\n    }\n    taxPercent\n    taxAmount {\n      value\n      text\n    }\n    taxAmountBeforeDiscount {\n      value\n      text\n    }\n    discountAmount {\n      value\n      text\n    }\n    lineTotal {\n      value\n      text\n    }\n    subTotal {\n      value\n      text\n    }\n    lineTotalWithDiscount {\n      value\n      text\n    }\n    lineTotalWithDiscountInclTax {\n      value\n      text\n    }\n    lineTotalInclTax {\n      value\n      text\n    }\n    total {\n      value\n      text\n    }\n    variantGroupId\n    variantOptions {\n      attributeCode\n      attributeName\n      attributeId\n      optionId\n      optionText\n    }\n    productUrl\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/Breadcrumb.tsx",
    "content": "import {\n  Breadcrumb as BreadcrumbRoot,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator\n} from '@components/common/ui/Breadcrumb.js';\nimport React from 'react';\n\ninterface BreadcrumbProps {\n  pageInfo: {\n    breadcrumbs: Array<{\n      title: string;\n      url: string;\n    }>;\n  };\n}\n\nfunction Breadcrumb({ pageInfo: { breadcrumbs } }: BreadcrumbProps) {\n  return breadcrumbs.length ? (\n    <div className=\"page-width\">\n      <div className=\"py-5\">\n        <BreadcrumbRoot>\n          <BreadcrumbList>\n            {breadcrumbs.map((breadcrumb, index) => (\n              <React.Fragment key={index}>\n                <BreadcrumbItem>\n                  {index === breadcrumbs.length - 1 ? (\n                    <BreadcrumbPage>{breadcrumb.title}</BreadcrumbPage>\n                  ) : (\n                    <BreadcrumbLink href={breadcrumb.url}>\n                      {breadcrumb.title}\n                    </BreadcrumbLink>\n                  )}\n                </BreadcrumbItem>\n                {index < breadcrumbs.length - 1 && <BreadcrumbSeparator />}\n              </React.Fragment>\n            ))}\n          </BreadcrumbList>\n        </BreadcrumbRoot>\n      </div>\n    </div>\n  ) : null;\n}\n\nexport const query = `\n  query query {\n    pageInfo {\n      breadcrumbs {\n        title\n        url\n      }\n    }\n  }\n`;\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 0\n};\n\nexport default Breadcrumb;\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/GlobalCss.tsx",
    "content": "import React from 'react';\nimport './global.scss';\n\nexport default function GlobalCss() {\n  return null;\n}\n\nexport const layout = {\n  areaId: 'head',\n  sortOrder: 5\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/HeadTags.tsx",
    "content": "import { Og } from '@components/frontStore/Og.js';\nimport React, {\n  LinkHTMLAttributes,\n  MetaHTMLAttributes,\n  ScriptHTMLAttributes\n} from 'react';\n\ninterface HeadTagsProps {\n  pageInfo: {\n    title: string;\n    description: string;\n    keywords: string[];\n    canonicalUrl: string;\n    favicon: string;\n    ogInfo: {\n      locale: string;\n      title: string;\n      description: string;\n      image: string;\n      url: string;\n      type: 'website' | 'article' | 'product' | string;\n      siteName: string;\n      twitterCard: 'summary' | 'summary_large_image' | 'app' | 'player';\n      twitterSite: string;\n      twitterCreator: string;\n      twitterImage: string;\n    };\n  };\n  themeConfig: {\n    headTags: {\n      metas: Array<MetaHTMLAttributes<HTMLMetaElement>>;\n      links: Array<LinkHTMLAttributes<HTMLLinkElement>>;\n      scripts: Array<ScriptHTMLAttributes<HTMLScriptElement>>;\n      base?: {\n        href: string;\n        target: '_blank' | '_self' | '_parent' | '_top';\n      };\n    };\n  };\n}\nexport default function HeadTags({\n  pageInfo: { title, description, keywords, canonicalUrl, ogInfo, favicon },\n  themeConfig: {\n    headTags: { metas, links, scripts, base }\n  }\n}: HeadTagsProps) {\n  React.useEffect(() => {\n    const head = document.querySelector('head');\n    scripts.forEach((script) => {\n      const scriptElement = document.createElement('script');\n      Object.keys(script).forEach((key) => {\n        if (script[key]) {\n          scriptElement[key] = script[key];\n        }\n      });\n      head?.appendChild(scriptElement);\n    });\n  }, []);\n\n  return (\n    <>\n      <title>{title}</title>\n      <meta name=\"description\" content={description} />\n      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n      {metas.map((meta, index) => (\n        <meta key={index} {...meta} />\n      ))}\n      {links.map((link, index) => (\n        <link key={index} {...link} />\n      ))}\n      {scripts.map((script, index) => (\n        <script key={index} {...script} />\n      ))}\n      {favicon && <link rel=\"icon\" href={favicon} />}\n      {keywords && keywords.length > 0 && (\n        <meta name=\"keywords\" content={keywords.join(', ')} />\n      )}\n      {canonicalUrl && <link rel=\"canonical\" href={canonicalUrl} />}\n      {base && <base {...base} />}\n      <Og\n        type={ogInfo.type}\n        title={title}\n        description={description}\n        url={ogInfo.url}\n        siteName={ogInfo.siteName}\n        image={ogInfo.image}\n        locale={ogInfo.locale}\n        twitterCard={ogInfo.twitterCard}\n        twitterSite={ogInfo.twitterSite}\n        twitterCreator={ogInfo.twitterCreator}\n        twitterImage={ogInfo.twitterImage}\n      />\n    </>\n  );\n}\n\nexport const layout = {\n  areaId: 'head',\n  sortOrder: 5\n};\n\nexport const query = `\n  query query {\n    pageInfo {\n      title\n      description\n      keywords\n      canonicalUrl\n      favicon\n      ogInfo {\n        locale\n        title\n        description\n        image\n        url\n        type\n        siteName\n        twitterCard\n        twitterSite\n        twitterCreator\n        twitterImage\n      }\n    }\n    themeConfig {\n      headTags {\n        metas {\n          name\n          content\n          charSet\n          httpEquiv\n          property\n          itemProp\n          itemType\n          itemID\n          lang\n        }\n        links {\n          rel\n          href\n          sizes\n          type\n          hrefLang\n          media\n          title\n          as\n          crossOrigin\n          integrity\n          referrerPolicy\n        }\n        scripts {\n          src\n          type\n          async\n          defer\n          crossOrigin\n          integrity\n          noModule\n          nonce\n        }\n        base {\n          href\n          target\n        }\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/Logo.tsx",
    "content": "import React from 'react';\n\ninterface LogoProps {\n  themeConfig: {\n    logo: {\n      src?: string;\n      alt?: string;\n      width?: number;\n      height?: number;\n    };\n  };\n}\nexport default function Logo({\n  themeConfig: {\n    logo: { src, alt = 'Evershop', width = 128, height = 128 }\n  }\n}: LogoProps) {\n  return (\n    <div className=\"logo md:ml-0 flex justify-center items-center\">\n      {src && (\n        <a href=\"/\" className=\"logo-icon\">\n          <img src={src} alt={alt} width={width} height={height} />\n        </a>\n      )}\n      {!src && (\n        <a href=\"/\" className=\"logo-icon\">\n          <svg\n            width=\"128\"\n            height=\"146\"\n            viewBox=\"0 0 128 146\"\n            fill=\"none\"\n            className=\"w-10 h-10\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M32.388 18.0772L1.15175 36.1544L1.05206 72.5081L0.985596 108.895L32.4213 127.039C49.7009 137.008 63.9567 145.182 64.1228 145.182C64.289 145.182 72.8956 140.264 83.2966 134.283C93.6644 128.268 107.82 120.127 114.732 116.139L127.26 108.895V101.119V93.3102L126.529 93.7089C126.097 93.9415 111.941 102.083 95.06 111.853C78.1459 121.622 64.156 129.531 63.9567 129.498C63.724 129.431 52.5587 123.051 39.1005 115.275L14.6099 101.152V72.5746V43.9967L25.6756 37.6165C31.7234 34.1274 42.8223 27.7472 50.2991 23.4273C57.7426 19.1073 63.9899 15.585 64.1228 15.585C64.2557 15.585 72.9288 20.5362 83.3963 26.5841L113.902 43.9967L118.713 41.1657L127.26 36.1544L113.902 28.5447C103.334 22.2974 64.3554 -0.033191 64.0231 3.90721e-05C63.8237 3.90721e-05 49.568 8.14142 32.388 18.0772Z\"\n              fill=\"#1F1F1F\"\n            />\n            <path\n              d=\"M96.0237 54.1983C78.9434 64.0677 64.721 72.2423 64.4219 72.3088C64.0896 72.4084 55.7488 67.7562 44.8826 61.509L25.9082 50.543V58.4186L25.9414 66.2609L44.3841 76.8945C54.5193 82.743 63.1591 87.6611 63.5911 87.8272C64.2557 88.0598 68.9079 85.5011 95.5585 70.1156C112.705 60.1798 126.861 51.9719 127.027 51.839C127.16 51.7061 127.227 48.1505 127.194 43.9302L127.094 36.2541L96.0237 54.1983Z\"\n              fill=\"#1F1F1F\"\n            />\n            <path\n              d=\"M123.771 66.7261C121.943 67.7562 107.854 75.8976 92.4349 84.8033C77.0161 93.7089 64.289 100.986 64.1228 100.986C63.9567 100.986 55.3501 96.0683 44.9491 90.0869L26.0744 79.1874L25.9747 86.8303C25.9082 92.6788 26.0079 94.5729 26.307 94.872C26.9383 95.4369 63.7241 116.604 64.1228 116.604C64.4551 116.604 126.496 80.8821 127.027 80.4169C127.16 80.284 127.227 76.7284 127.194 72.4749L127.094 64.7987L123.771 66.7261Z\"\n              fill=\"#1F1F1F\"\n            />\n          </svg>\n        </a>\n      )}\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'headerMiddleCenter',\n  sortOrder: 10\n};\n\nexport const query = `\n  query query {\n    themeConfig {\n      logo {\n        src\n        alt\n        width\n        height\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/Notification.scss",
    "content": ".Toastify__toast-container {\n  z-index: 9999;\n  -webkit-transform: translate3d(0, 0, 9999px);\n  transform: translate3d(0, 0, 9999px);\n  position: fixed;\n  width: 380px;\n  background-color: transparent;\n}\n.Toastify__toast-container--top-left {\n  top: 1em;\n  left: 1em;\n}\n.Toastify__toast-container--top-center {\n  top: 1em;\n  left: 50%;\n  transform: translateX(-50%);\n}\n.Toastify__toast-container--top-right {\n  top: 1em;\n  right: 1em;\n}\n.Toastify__toast-container--bottom-left {\n  bottom: 1em;\n  left: 1em;\n}\n.Toastify__toast-container--bottom-center {\n  bottom: 1em;\n  left: 50%;\n  transform: translateX(-50%);\n}\n.Toastify__toast-container--bottom-right {\n  bottom: 1em;\n  right: 1em;\n}\n\n@media only screen and (max-width: 480px) {\n  .Toastify__toast-container {\n    width: 100vw;\n    padding: 0;\n    left: 0;\n    margin: 0;\n  }\n  .Toastify__toast-container--top-left,\n  .Toastify__toast-container--top-center,\n  .Toastify__toast-container--top-right {\n    top: 0;\n    transform: translateX(0);\n  }\n  .Toastify__toast-container--bottom-left,\n  .Toastify__toast-container--bottom-center,\n  .Toastify__toast-container--bottom-right {\n    bottom: 0;\n    transform: translateX(0);\n  }\n  .Toastify__toast-container--rtl {\n    right: 0;\n    left: initial;\n  }\n}\n.Toastify__toast {\n  position: relative;\n  min-height: 64px;\n  box-sizing: border-box;\n  margin-bottom: 0.625rem;\n  padding: 1rem;\n  border-radius: var(--radius);\n  display: -ms-flexbox;\n  display: flex;\n  -ms-flex-pack: justify;\n  justify-content: space-between;\n  max-height: 800px;\n  overflow: hidden;\n  font-family: sans-serif;\n  cursor: pointer;\n  direction: ltr;\n  background: var(--card);\n  color: var(--card-foreground);\n  border: 1px solid var(--border);\n  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n}\n.Toastify__toast--rtl {\n  direction: rtl;\n}\n.Toastify__toast--dark {\n  background: var(--card);\n  color: var(--card-foreground);\n  border-color: var(--border);\n}\n\n.Toastify__toast--info {\n  background: var(--card);\n  border-color: oklch(0.6 0.118 184.704);\n  color: var(--card-foreground);\n}\n.dark .Toastify__toast--info {\n  background: var(--card);\n  border-color: oklch(0.696 0.17 162.48);\n  color: var(--card-foreground);\n}\n.Toastify__toast--success {\n  background: var(--card);\n  border-color: var(--primary);\n  color: var(--card-foreground);\n}\n.dark .Toastify__toast--success {\n  background: var(--card);\n  border-color: var(--primary);\n  color: var(--card-foreground);\n}\n.Toastify__toast--warning {\n  background: var(--card);\n  border-color: oklch(0.828 0.189 84.429);\n  color: var(--card-foreground);\n}\n.dark .Toastify__toast--warning {\n  background: var(--card);\n  border-color: oklch(0.769 0.188 70.08);\n  color: var(--card-foreground);\n}\n.Toastify__toast--error {\n  background: var(--card);\n  border-color: var(--destructive);\n  color: var(--card-foreground);\n}\n.dark .Toastify__toast--error {\n  background: var(--card);\n  border-color: var(--destructive);\n  color: var(--card-foreground);\n}\n.Toastify__toast-body {\n  margin: auto 0;\n  -ms-flex: 1 1 auto;\n  flex: 1 1 auto;\n}\n\n@media only screen and (max-width: 480px) {\n  .Toastify__toast {\n    margin-bottom: 0;\n  }\n}\n.Toastify__close-button {\n  color: var(--muted-foreground);\n  background: transparent;\n  outline: none;\n  border: none;\n  padding: 0;\n  cursor: pointer;\n  opacity: 0.7;\n  transition: 0.3s ease;\n  -ms-flex-item-align: start;\n  align-self: flex-start;\n}\n.Toastify__close-button--default {\n  color: var(--muted-foreground);\n  opacity: 0.7;\n}\n.Toastify__close-button > svg {\n  fill: currentColor;\n  height: 16px;\n  width: 14px;\n}\n.Toastify__close-button:hover,\n.Toastify__close-button:focus {\n  opacity: 1;\n}\n\n@keyframes Toastify__trackProgress {\n  0% {\n    transform: scaleX(1);\n  }\n  100% {\n    transform: scaleX(0);\n  }\n}\n.Toastify__progress-bar {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 3px;\n  z-index: 9999;\n  opacity: 0.5;\n  background-color: currentColor;\n  transform-origin: left;\n}\n.Toastify__progress-bar--animated {\n  animation: Toastify__trackProgress linear 1 forwards;\n}\n.Toastify__progress-bar--controlled {\n  transition: transform 0.2s;\n}\n.Toastify__progress-bar--rtl {\n  right: 0;\n  left: initial;\n  transform-origin: right;\n}\n.Toastify__progress-bar--default {\n  background-color: var(--primary);\n}\n.Toastify__progress-bar--dark {\n  background-color: var(--primary);\n}\n@keyframes Toastify__bounceInRight {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  from {\n    opacity: 0;\n    transform: translate3d(3000px, 0, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(-25px, 0, 0);\n  }\n  75% {\n    transform: translate3d(10px, 0, 0);\n  }\n  90% {\n    transform: translate3d(-5px, 0, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n@keyframes Toastify__bounceOutRight {\n  20% {\n    opacity: 1;\n    transform: translate3d(-20px, 0, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(2000px, 0, 0);\n  }\n}\n@keyframes Toastify__bounceInLeft {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  0% {\n    opacity: 0;\n    transform: translate3d(-3000px, 0, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(25px, 0, 0);\n  }\n  75% {\n    transform: translate3d(-10px, 0, 0);\n  }\n  90% {\n    transform: translate3d(5px, 0, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n@keyframes Toastify__bounceOutLeft {\n  20% {\n    opacity: 1;\n    transform: translate3d(20px, 0, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(-2000px, 0, 0);\n  }\n}\n@keyframes Toastify__bounceInUp {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  from {\n    opacity: 0;\n    transform: translate3d(0, 3000px, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(0, -20px, 0);\n  }\n  75% {\n    transform: translate3d(0, 10px, 0);\n  }\n  90% {\n    transform: translate3d(0, -5px, 0);\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__bounceOutUp {\n  20% {\n    transform: translate3d(0, -10px, 0);\n  }\n  40%,\n  45% {\n    opacity: 1;\n    transform: translate3d(0, 20px, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(0, -2000px, 0);\n  }\n}\n@keyframes Toastify__bounceInDown {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  0% {\n    opacity: 0;\n    transform: translate3d(0, -3000px, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(0, 25px, 0);\n  }\n  75% {\n    transform: translate3d(0, -10px, 0);\n  }\n  90% {\n    transform: translate3d(0, 5px, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n@keyframes Toastify__bounceOutDown {\n  20% {\n    transform: translate3d(0, 10px, 0);\n  }\n  40%,\n  45% {\n    opacity: 1;\n    transform: translate3d(0, -20px, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(0, 2000px, 0);\n  }\n}\n.Toastify__bounce-enter--top-left,\n.Toastify__bounce-enter--bottom-left {\n  animation-name: Toastify__bounceInLeft;\n}\n.Toastify__bounce-enter--top-right,\n.Toastify__bounce-enter--bottom-right {\n  animation-name: Toastify__bounceInRight;\n}\n.Toastify__bounce-enter--top-center {\n  animation-name: Toastify__bounceInDown;\n}\n.Toastify__bounce-enter--bottom-center {\n  animation-name: Toastify__bounceInUp;\n}\n\n.Toastify__bounce-exit--top-left,\n.Toastify__bounce-exit--bottom-left {\n  animation-name: Toastify__bounceOutLeft;\n}\n.Toastify__bounce-exit--top-right,\n.Toastify__bounce-exit--bottom-right {\n  animation-name: Toastify__bounceOutRight;\n}\n.Toastify__bounce-exit--top-center {\n  animation-name: Toastify__bounceOutUp;\n}\n.Toastify__bounce-exit--bottom-center {\n  animation-name: Toastify__bounceOutDown;\n}\n\n@keyframes Toastify__zoomIn {\n  from {\n    opacity: 0;\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n  50% {\n    opacity: 1;\n  }\n}\n@keyframes Toastify__zoomOut {\n  from {\n    opacity: 1;\n  }\n  50% {\n    opacity: 0;\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n  to {\n    opacity: 0;\n  }\n}\n.Toastify__zoom-enter {\n  animation-name: Toastify__zoomIn;\n}\n\n.Toastify__zoom-exit {\n  animation-name: Toastify__zoomOut;\n}\n\n@keyframes Toastify__flipIn {\n  from {\n    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n    animation-timing-function: ease-in;\n    opacity: 0;\n  }\n  40% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n    animation-timing-function: ease-in;\n  }\n  60% {\n    transform: perspective(400px) rotate3d(1, 0, 0, 10deg);\n    opacity: 1;\n  }\n  80% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -5deg);\n  }\n  to {\n    transform: perspective(400px);\n  }\n}\n@keyframes Toastify__flipOut {\n  from {\n    transform: perspective(400px);\n  }\n  30% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n    opacity: 1;\n  }\n  to {\n    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n    opacity: 0;\n  }\n}\n.Toastify__flip-enter {\n  animation-name: Toastify__flipIn;\n}\n\n.Toastify__flip-exit {\n  animation-name: Toastify__flipOut;\n}\n\n@keyframes Toastify__slideInRight {\n  from {\n    transform: translate3d(110%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideInLeft {\n  from {\n    transform: translate3d(-110%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideInUp {\n  from {\n    transform: translate3d(0, 110%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideInDown {\n  from {\n    transform: translate3d(0, -110%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(110%, 0, 0);\n  }\n}\n@keyframes Toastify__slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-110%, 0, 0);\n  }\n}\n@keyframes Toastify__slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 500px, 0);\n  }\n}\n@keyframes Toastify__slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -500px, 0);\n  }\n}\n.Toastify__slide-enter--top-left,\n.Toastify__slide-enter--bottom-left {\n  animation-name: Toastify__slideInLeft;\n}\n.Toastify__slide-enter--top-right,\n.Toastify__slide-enter--bottom-right {\n  animation-name: Toastify__slideInRight;\n}\n.Toastify__slide-enter--top-center {\n  animation-name: Toastify__slideInDown;\n}\n.Toastify__slide-enter--bottom-center {\n  animation-name: Toastify__slideInUp;\n}\n\n.Toastify__slide-exit--top-left,\n.Toastify__slide-exit--bottom-left {\n  animation-name: Toastify__slideOutLeft;\n}\n.Toastify__slide-exit--top-right,\n.Toastify__slide-exit--bottom-right {\n  animation-name: Toastify__slideOutRight;\n}\n.Toastify__slide-exit--top-center {\n  animation-name: Toastify__slideOutUp;\n}\n.Toastify__slide-exit--bottom-center {\n  animation-name: Toastify__slideOutDown;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/Notification.tsx",
    "content": "import { useAppState } from '@components/common/context/app.js';\nimport { get } from '@evershop/evershop/lib/util/get';\nimport React from 'react';\nimport { toast, ToastContainer } from 'react-toastify';\nimport './Notification.scss';\n\nexport default function Notification() {\n  const notify = (type, message) => {\n    switch (type) {\n      case 'success':\n        toast.success(message);\n        break;\n      case 'error':\n        toast.error(message);\n        break;\n      case 'info':\n        toast.info(message);\n        break;\n      case 'warning':\n        toast.warning(message);\n        break;\n      default:\n        toast(message);\n    }\n  };\n  const context = useAppState();\n\n  React.useEffect(() => {\n    get(context, 'notifications', []).forEach((n) => notify(n.type, n.message));\n  }, []);\n\n  return (\n    <div>\n      <ToastContainer hideProgressBar autoClose={false} />\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'body',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/TailwindCss.tsx",
    "content": "import React from 'react';\nimport './tailwind.css';\n\nexport default function TailwindCss() {\n  return null;\n}\n\nexport const layout = {\n  areaId: 'head',\n  sortOrder: 1\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/global.scss",
    "content": "@layer base {\n  $font-size-sm: 0.875rem;\n  $font-size-md: 0.875rem;\n  $font-size-lg: 1rem;\n\n  $line-height-sm: 1.25;\n  $line-height-md: 1.5;\n  $line-height-lg: 2;\n\n  $font-size-base: $font-size-sm;\n\n  body {\n    font-size: 100%;\n    color: #3a3a3a;\n    input[type='email'],\n    input[type='password'],\n    input[type='tel'],\n    input[type='text'],\n    select,\n    textarea {\n      padding: 0.625rem 0.75rem;\n    }\n\n    main.content {\n      padding-bottom: 2rem;\n    }\n  }\n\n  .header {\n    padding-top: 1rem;\n    padding-bottom: 1rem;\n  }\n\n  @media (max-width: 640px) {\n    .slide__wrapper {\n      height: 30rem !important;\n    }\n  }\n\n  @media screen and (max-width: 767px) {\n    $font-size-base: $font-size-sm;\n    $h1-font-size: $font-size-base * 2.1;\n    $h2-font-size: $font-size-base * 1.9;\n    $h3-font-size: $font-size-base * 1.65;\n    $h4-font-size: $font-size-base * 1.45;\n    $h5-font-size: $font-size-base * 1;\n    $h6-font-size: $font-size-base;\n    body {\n      font-size: $font-size-base;\n      line-height: $line-height-sm;\n      .h1,\n      h1 {\n        font-size: $h1-font-size;\n      }\n      .h2,\n      h2 {\n        font-size: $h2-font-size;\n      }\n      .h3,\n      h3 {\n        font-size: $h3-font-size;\n      }\n      .h4,\n      h4 {\n        font-size: $h4-font-size;\n      }\n      .h5,\n      h5 {\n        font-size: $h5-font-size;\n      }\n      .h6,\n      h6 {\n        font-size: $h6-font-size;\n      }\n    }\n  }\n\n  @media (min-width: 768px) and (max-width: 1535px) {\n    $font-size-base: $font-size-md;\n    $h1-font-size: $font-size-base * 2.1;\n    $h2-font-size: $font-size-base * 1.9;\n    $h3-font-size: $font-size-base * 1.65;\n    $h4-font-size: $font-size-base * 1.45;\n    $h5-font-size: $font-size-base * 1.15;\n    $h6-font-size: $font-size-base;\n\n    body {\n      font-size: $font-size-base;\n      line-height: $line-height-md;\n      .h1,\n      h1 {\n        font-size: $h1-font-size;\n      }\n      .h2,\n      h2 {\n        font-size: $h2-font-size;\n      }\n      .h3,\n      h3 {\n        font-size: $h3-font-size;\n      }\n      .h4,\n      h4 {\n        font-size: $h4-font-size;\n      }\n      .h5,\n      h5 {\n        font-size: $h5-font-size;\n      }\n      .h6,\n      h6 {\n        font-size: $h6-font-size;\n      }\n    }\n  }\n\n  @media (min-width: 1536px) {\n    $font-size-base: $font-size-lg;\n    $h1-font-size: $font-size-base * 2.1;\n    $h2-font-size: $font-size-base * 1.9;\n    $h3-font-size: $font-size-base * 1.65;\n    $h4-font-size: $font-size-base * 1.45;\n    $h5-font-size: $font-size-base * 1.15;\n    $h6-font-size: $font-size-base;\n\n    body {\n      font-size: $font-size-base;\n      line-height: $line-height-lg;\n      .h1,\n      h1 {\n        font-size: $h1-font-size;\n      }\n      .h2,\n      h2 {\n        font-size: $h2-font-size;\n      }\n      .h3,\n      h3 {\n        font-size: $h3-font-size;\n      }\n      .h4,\n      h4 {\n        font-size: $h4-font-size;\n      }\n      .h5,\n      h5 {\n        font-size: $h5-font-size;\n      }\n      .h6,\n      h6 {\n        font-size: $h6-font-size;\n      }\n    }\n  }\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6 {\n    font-weight: 600;\n  }\n  .wrapper .content {\n    min-height: calc(100vh - 50px);\n  }\n\n  .container {\n    margin: 0 auto;\n  }\n  .page-width {\n    max-width: 1200px;\n    margin: 0 auto;\n    padding: 0 22px;\n    @media screen and (min-width: 1200px) {\n      padding: 0 55px;\n    }\n  }\n\n  table {\n    width: 100%;\n    border-collapse: collapse;\n    td,\n    th {\n      border: 1px solid var(--divider);\n      padding: 0.313rem;\n      text-align: left;\n    }\n    th {\n      vertical-align: top;\n    }\n    &.listing {\n      td,\n      th {\n        border-left: 0;\n        border-right: 0;\n        padding: 0.625rem;\n      }\n      th {\n        border: 0;\n        padding-top: 1.25rem;\n        padding-bottom: 1.25rem;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/shadcn.css",
    "content": "@theme inline {\n  @keyframes accordion-down {\n    from {\n      height: 0;\n    }\n    to {\n      height: var(\n        --radix-accordion-content-height,\n        var(--accordion-panel-height, auto)\n      );\n    }\n  }\n\n  @keyframes accordion-up {\n    from {\n      height: var(\n        --radix-accordion-content-height,\n        var(--accordion-panel-height, auto)\n      );\n    }\n    to {\n      height: 0;\n    }\n  }\n}\n\n/* Custom variants */\n@custom-variant data-open {\n  &:where([data-state='open']),\n  &:where([data-open]:not([data-open='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-closed {\n  &:where([data-state='closed']),\n  &:where([data-closed]:not([data-closed='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-checked {\n  &:where([data-state='checked']),\n  &:where([data-checked]:not([data-checked='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-unchecked {\n  &:where([data-state='unchecked']),\n  &:where([data-unchecked]:not([data-unchecked='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-selected {\n  &:where([data-selected='true']) {\n    @slot;\n  }\n}\n\n@custom-variant data-disabled {\n  &:where([data-disabled='true']),\n  &:where([data-disabled]:not([data-disabled='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-active {\n  &:where([data-state='active']),\n  &:where([data-active]:not([data-active='false'])) {\n    @slot;\n  }\n}\n\n@custom-variant data-horizontal {\n  &:where([data-orientation='horizontal']) {\n    @slot;\n  }\n}\n\n@custom-variant data-vertical {\n  &:where([data-orientation='vertical']) {\n    @slot;\n  }\n}\n\n@utility no-scrollbar {\n  -ms-overflow-style: none;\n  scrollbar-width: none;\n\n  &::-webkit-scrollbar {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/frontStore/all/tailwind.css",
    "content": "@import 'tailwindcss';\n@plugin \"@tailwindcss/typography\";\n@import 'tw-animate-css';\n@import './shadcn.css';\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --font-sans: var(--font-sans);\n  --font-mono: var(--font-geist-mono);\n  --color-sidebar-ring: var(--sidebar-ring);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar: var(--sidebar);\n  --color-chart-5: var(--chart-5);\n  --color-chart-4: var(--chart-4);\n  --color-chart-3: var(--chart-3);\n  --color-chart-2: var(--chart-2);\n  --color-chart-1: var(--chart-1);\n  --color-ring: var(--ring);\n  --color-input: var(--input);\n  --color-border: var(--border);\n  --color-divider: var(--divider);\n  --color-destructive: var(--destructive);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-accent: var(--accent);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-muted: var(--muted);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-secondary: var(--secondary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-primary: var(--primary);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-popover: var(--popover);\n  --color-card-foreground: var(--card-foreground);\n  --color-card: var(--card);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --radius-2xl: calc(var(--radius) + 8px);\n  --radius-3xl: calc(var(--radius) + 12px);\n  --radius-4xl: calc(var(--radius) + 16px);\n}\n\n:root {\n  --radius: 0.325rem;\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.145 0 0);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.145 0 0);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.145 0 0);\n  --primary: oklch(0.205 0 0);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.97 0 0);\n  --secondary-foreground: oklch(0.205 0 0);\n  --muted: oklch(0.97 0 0);\n  --muted-foreground: oklch(0.556 0 0);\n  --accent: oklch(0.97 0 0);\n  --accent-foreground: oklch(0.205 0 0);\n  --destructive: oklch(0.577 0.245 27.325);\n  --border: oklch(0.922 0 0);\n  --divider: oklch(0.922 0 0);\n  --input: oklch(0.922 0 0);\n  --ring: oklch(0.708 0 0);\n  --chart-1: oklch(0.646 0.222 41.116);\n  --chart-2: oklch(0.6 0.118 184.704);\n  --chart-3: oklch(0.398 0.07 227.392);\n  --chart-4: oklch(0.828 0.189 84.429);\n  --chart-5: oklch(0.769 0.188 70.08);\n  --sidebar: oklch(0.985 0 0);\n  --sidebar-foreground: oklch(0.145 0 0);\n  --sidebar-primary: oklch(0.205 0 0);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.97 0 0);\n  --sidebar-accent-foreground: oklch(0.205 0 0);\n  --sidebar-border: oklch(0.922 0 0);\n  --sidebar-ring: oklch(0.708 0 0);\n}\n\n.dark {\n  --background: oklch(0.145 0 0);\n  --foreground: oklch(0.985 0 0);\n  --card: oklch(0.205 0 0);\n  --card-foreground: oklch(0.985 0 0);\n  --popover: oklch(0.205 0 0);\n  --popover-foreground: oklch(0.985 0 0);\n  --primary: oklch(0.922 0 0);\n  --primary-foreground: oklch(0.205 0 0);\n  --secondary: oklch(0.269 0 0);\n  --secondary-foreground: oklch(0.985 0 0);\n  --muted: oklch(0.269 0 0);\n  --muted-foreground: oklch(0.708 0 0);\n  --accent: oklch(0.269 0 0);\n  --accent-foreground: oklch(0.985 0 0);\n  --destructive: oklch(0.704 0.191 22.216);\n  --border: oklch(1 0 0 / 10%);\n  --input: oklch(1 0 0 / 15%);\n  --ring: oklch(0.556 0 0);\n  --chart-1: oklch(0.488 0.243 264.376);\n  --chart-2: oklch(0.696 0.17 162.48);\n  --chart-3: oklch(0.769 0.188 70.08);\n  --chart-4: oklch(0.627 0.265 303.9);\n  --chart-5: oklch(0.645 0.246 16.439);\n  --sidebar: oklch(0.205 0 0);\n  --sidebar-foreground: oklch(0.985 0 0);\n  --sidebar-primary: oklch(0.488 0.243 264.376);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.269 0 0);\n  --sidebar-accent-foreground: oklch(0.985 0 0);\n  --sidebar-border: oklch(1 0 0 / 10%);\n  --sidebar-ring: oklch(0.556 0 0);\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/global/[auth]notFound[response].ts",
    "content": "import { translate } from '../../../../lib/locale/translate/translate.js';\nimport { getRoutes } from '../../../../lib/router/Router.js';\nimport { setPageMetaInfo } from '../../../cms/services/pageMetaInfo.js';\n\nexport default async (request, response, next) => {\n  if (response.statusCode !== 404) {\n    next();\n  } else {\n    const routes = getRoutes();\n    if (request.currentRoute?.isAdmin) {\n      request.currentRoute = routes.find((r) => r.id === 'adminNotFound');\n    } else {\n      request.currentRoute = routes.find((r) => r.id === 'notFound');\n    }\n    setPageMetaInfo(request, {\n      title: translate('Not found'),\n      description: translate('Page not found')\n    });\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/global/[response]errorHandler.js",
    "content": "import { encode } from 'html-entities';\nimport { error } from '../../../../lib/log/logger.js';\nimport { INTERNAL_SERVER_ERROR } from '../../../../lib/util/httpStatus.js';\nimport isDevelopmentMode from '../../../../lib/util/isDevelopmentMode.js';\n\nexport default async (err, request, response, next) => {\n  if (isDevelopmentMode() || process.argv.includes('--debug')) {\n    error(err);\n  }\n  // Set this flag to make sure this middleware only be executed 1 time\n  response.locals.errorHandlerTriggered = true;\n  // Check if the header is already sent or not.\n  if (response.headersSent) {\n    // TODO: Write a log message or next(error)?.\n  } else if (request.currentRoute.isApi === true) {\n    response.status(INTERNAL_SERVER_ERROR).json({\n      data: null,\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: err.message\n      }\n    });\n  } else {\n    response.status(500).send(encode(err.message));\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/global/context.js",
    "content": "import { pool } from '../../../../lib/postgres/connection.js';\nimport { getBaseUrl } from '../../../../lib/util/getBaseUrl.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport { setPageMetaInfo } from '../../../cms/services/pageMetaInfo.js';\nimport {\n  hasContextValue,\n  setContextValue\n} from '../../../graphql/services/contextHelper.js';\n\nexport default (request, response) => {\n  response.context = {}; // TODO: Fix this\n  /** Some default context value */\n  if (!hasContextValue(request, 'pool')) {\n    setContextValue(request.app, 'pool', pool);\n  }\n  setContextValue(request, 'pool', pool);\n  const homeUrl = getBaseUrl();\n  setContextValue(request.app, 'homeUrl', homeUrl);\n  setPageMetaInfo(request, {\n    baseUrl: homeUrl\n  });\n  setContextValue(request, 'currentUrl', `${homeUrl}${request.originalUrl}`);\n  setContextValue(request, 'baseUrl', request.baseUrl);\n  setContextValue(request, 'body', request.body);\n  setContextValue(request, 'cookies', request.cookies);\n  setContextValue(request, 'fresh', request.fresh);\n  setContextValue(request, 'hostname', request.hostname);\n  setContextValue(request, 'ip', request.ip);\n  setContextValue(request, 'ips', request.ips);\n  setContextValue(request, 'method', request.method);\n  setContextValue(request, 'originalUrl', request.originalUrl);\n  setContextValue(request, 'params', request.params);\n  setContextValue(request, 'path', request.path);\n  setContextValue(request, 'protocol', request.protocol);\n  setContextValue(request, 'query', request.query);\n  setContextValue(request, 'route', request.route);\n  setContextValue(request, 'secure', request.secure);\n  setContextValue(request, 'signedCookies', request.signedCookies);\n  setContextValue(request, 'stale', request.stale);\n  setContextValue(request, 'subdomains', request.subdomains);\n  setContextValue(request, 'xhr', request.xhr);\n  setContextValue(request, 'sid', request.sessionID);\n  setContextValue(request, 'currentRoute', {\n    id: request.currentRoute.id,\n    path: request.currentRoute.path,\n    params: request.params\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/pages/global/response[errorHandler].ts",
    "content": "import isErrorHandlerTriggered from '../../../../lib/middleware/isErrorHandlerTriggered.js';\nimport { render } from '../../../../lib/response/render.js';\nimport { get } from '../../../../lib/util/get.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport isDevelopmentMode from '../../../../lib/util/isDevelopmentMode.js';\nimport { getValueSync } from '../../../../lib/util/registry.js';\nimport {\n  getPageMetaInfo,\n  setPageMetaInfo\n} from '../../../../modules/cms/services/pageMetaInfo.js';\nimport { AppStateContextValue, Config } from '../../../../types/appContext.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { loadWidgetInstances } from '../../../cms/services/widget/loadWidgetInstances.js';\nimport { getContextValue } from '../../../graphql/services/contextHelper.js';\n\nexport default async (request: EvershopRequest, response, next) => {\n  try {\n    /** If a rejected middleware called next(error) without throwing an error */\n    if (isErrorHandlerTriggered(response)) {\n      return;\n    } else {\n      const route = request.currentRoute;\n\n      // Check if `$body` is empty or not. If yes, we consider the content is already generated by previous middlewares\n      if (response.$body && response.$body !== '') {\n        response.send(response.$body);\n      } else {\n        response.context.route = {\n          id: route.id,\n          path: route.path,\n          method: route.method,\n          isApi: route.isApi,\n          isAdmin: route.isAdmin\n        };\n        setPageMetaInfo(request, {\n          route: {\n            id: route.id,\n            isAdmin: route.isAdmin,\n            path: route.path,\n            url: getContextValue(request, 'currentUrl'),\n            params: Object.fromEntries(\n              Object.entries(request.params).map(([key, value]) => [\n                key,\n                Array.isArray(value) ? value[0] : value\n              ])\n            )\n          }\n        });\n        let widgetInstances;\n        // Check if we are in the test mode\n        if (process.env.NODE_ENV === 'test') {\n          widgetInstances = [];\n        } else {\n          widgetInstances = await loadWidgetInstances(request);\n        }\n        widgetInstances = widgetInstances.map((widget) => {\n          const newWidget = {\n            sortOrder: widget.sortOrder,\n            areaId: widget.areaId,\n            type: widget.type,\n            id: `e${widget.uuid.replace(/-/g, '')}`\n          };\n          if (route.isAdmin) {\n            newWidget.areaId = 'widget_setting_form';\n          }\n          return newWidget;\n        });\n        response.locals.widgets = widgetInstances;\n        if (\n          (isDevelopmentMode() &&\n            request.query &&\n            request.query.fashRefresh === 'true') ||\n          (request.query && request.query.ajax === 'true')\n        ) {\n          const pageMeta = getPageMetaInfo(request);\n          const appConfig = getValueSync<Config>(\n            'appConfig',\n            {\n              tax: {\n                priceIncludingTax: getConfig(\n                  'pricing.tax.price_including_tax',\n                  false\n                )\n              },\n              catalog: {\n                imageDimensions: {\n                  width: getConfig('catalog.product.image.width', 1200),\n                  height: getConfig('catalog.product.image.height', 1200)\n                }\n              },\n              pageMeta: pageMeta\n            },\n            { request, response },\n            (value) =>\n              value && typeof value === 'object' && !Array.isArray(value)\n          );\n          const config = Object.assign({}, appConfig, { pageMeta });\n\n          response.json({\n            success: true,\n            eContext: {\n              graphqlResponse: get<Record<string, any>>(\n                response,\n                'locals.graphqlResponse',\n                {}\n              ),\n              config: config,\n              propsMap: get(response, 'locals.propsMap', {}),\n              widgets: widgetInstances\n            } as AppStateContextValue\n          });\n        } else {\n          render(request, response);\n        }\n      }\n    }\n  } catch (error) {\n    if (!isErrorHandlerTriggered(response)) {\n      next(error);\n    } else {\n      // Do nothing here since the next(error) is already called\n      // when the error is thrown on each middleware\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/base/services/escapePayload.ts",
    "content": "import secret from '../../../modules/base/services/secret.js';\n\nfunction escapeHtmlTags(str) {\n  const regex = /<([a-zA-Z]+)(?:\\s[^>]*)?>|<\\/([a-zA-Z]+)>/g;\n  const replacements = {\n    '<': '&lt;',\n    '>': '&gt;'\n  };\n  return str.replace(regex, (match) =>\n    match.replace(/[<>&\"']/g, (char) => replacements[char] || char)\n  );\n}\n\nexport default function escapePayload(obj) {\n  if (obj === null || typeof obj !== 'object') {\n    return;\n  }\n  if (Array.isArray(obj)) {\n    obj.forEach((item) => escapePayload(item));\n    return;\n  }\n  // Fields registered on this object to skip HTML escaping\n  const skipFields = Array.isArray(obj[secret]) ? obj[secret] : [];\n  for (const prop in obj) {\n    // Skip the secret marker itself\n    if (prop === secret) continue;\n    // Skip fields explicitly marked to bypass escaping\n    if (skipFields.includes(prop)) continue;\n    if (typeof obj[prop] === 'string') {\n      obj[prop] = escapeHtmlTags(obj[prop]);\n    } else if (Array.isArray(obj[prop])) {\n      obj[prop].forEach((item) => escapePayload(item));\n    } else if (typeof obj[prop] === 'object' && obj[prop] !== null) {\n      escapePayload(obj[prop]);\n    }\n  }\n  delete obj[secret];\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/services/getAjv.js",
    "content": "import Ajv from 'ajv';\nimport ajvErrors from 'ajv-errors';\nimport addFormats from 'ajv-formats';\n\nexport function getAjv() {\n  // Initialize the ajv instance\n  const ajv = new Ajv({\n    strict: false,\n    useDefaults: 'empty',\n    allErrors: true\n  });\n\n  // Add the formats\n  addFormats(ajv);\n  ajv.addFormat('digit', /^[0-9]*$/);\n  ajvErrors(ajv);\n\n  return ajv;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/services/markSkipEscape.ts",
    "content": "import secret from './secret.js';\n\n/**\n * Marks a field on its parent object to skip HTML escaping.\n * Adds a special property keyed by `secret` to the parent object,\n * whose value is an array of field names that should not be escaped.\n */\nexport default function markSkipEscape(obj, path) {\n  const keys = path.split('/').filter((k) => k !== '');\n\n  if (keys.length === 0) {\n    return;\n  }\n\n  let result = obj;\n  const lastKeyIndex = keys.length - 1;\n\n  for (let i = 0; i < lastKeyIndex; i += 1) {\n    const key = keys[i];\n    if (result == null || typeof result !== 'object') {\n      return;\n    }\n    result = result[key];\n  }\n\n  if (result == null || typeof result !== 'object') {\n    return;\n  }\n\n  if (!Array.isArray(result[secret])) {\n    Object.defineProperty(result, secret, {\n      value: [],\n      enumerable: false,\n      configurable: true,\n      writable: true\n    });\n  }\n\n  const lastKey = keys[lastKeyIndex];\n  if (!result[secret].includes(lastKey)) {\n    result[secret].push(lastKey);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/base/services/notifications.js",
    "content": "import { hookable } from '../../../lib/util/hookable.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nfunction addNotificationMessage(request, message, type = 'info') {\n  const notification = {\n    message: getValueSync('notificationMessage', message),\n    type // Suppport 'success', 'error', 'info', 'warning'\n  };\n  const { session } = request;\n  session.notifications = session.notifications || [];\n  session.notifications.push(notification);\n}\n\nexport const getNotifications = (request) => {\n  const { session } = request;\n  const notifications = session.notifications || [];\n  session.notifications = [];\n  return notifications;\n};\n\nexport const addNotification = (request, message, type) =>\n  hookable(addNotificationMessage)(request, message, type);\n"
  },
  {
    "path": "packages/evershop/src/modules/base/services/secret.js",
    "content": "import { v4 as uuidv4 } from 'uuid';\n\nconst secret = uuidv4();\nexport default secret;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addProductToCategory/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addProductToCategory/addProducts.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport {\n  INVALID_PAYLOAD,\n  OK,\n  INTERNAL_SERVER_ERROR\n} from '../../../../lib/util/httpStatus.js';\nimport updateProduct from '../../services/product/updateProduct.js';\n\nexport default async (request, response, next) => {\n  const { category_id } = request.params;\n  const { product_id } = request.body;\n  try {\n    // Check if the category is exists\n    const category = await select()\n      .from('category')\n      .where('uuid', '=', category_id)\n      .load(pool);\n    if (!category) {\n      response.status(INVALID_PAYLOAD);\n      return response.json({\n        success: false,\n        message: 'Category does not exists'\n      });\n    }\n\n    // Check if the product is exists\n    const product = await select()\n      .from('product')\n      .where('uuid', '=', product_id)\n      .load(pool);\n    if (!product) {\n      response.status(INVALID_PAYLOAD);\n      return response.json({\n        success: false,\n        message: 'Product does not exists'\n      });\n    }\n    // Check if the product is already assigned to the category\n    const productCategory = await select()\n      .from('product')\n      .where('category_id', '=', category.category_id)\n      .and('product_id', '=', product.product_id)\n      .load(pool);\n    if (productCategory) {\n      response.status(OK);\n      return response.json({\n        success: true,\n        message: 'Product is assigned to the category'\n      });\n    }\n\n    await updateProduct(\n      product_id,\n      {\n        category_id: category.category_id\n      },\n      {\n        routeId: request.currentRoute.id\n      }\n    );\n    response.status(OK);\n    return response.json({\n      success: true,\n      data: {\n        product_id: product.product_id,\n        category_id: category.category_id\n      }\n    });\n  } catch (e) {\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    return response.json({\n      success: false,\n      message: e.message\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addProductToCategory/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"product_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"product_id\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addProductToCategory/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/categories/:category_id/products\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addProductToCollection/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addProductToCollection/addProducts.js",
    "content": "import {\n  commit,\n  insertOnUpdate,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { error } from '../../../../lib/log/logger.js';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const { collection_id } = request.params;\n  const { product_id } = request.body;\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    // Check if the collection is exists\n    const collection = await select()\n      .from('collection')\n      .where('uuid', '=', collection_id)\n      .load(connection);\n    if (!collection) {\n      response.status(INVALID_PAYLOAD);\n      return response.json({\n        success: false,\n        message: 'Collection does not exists'\n      });\n    }\n\n    // Check if the product is exists\n    const product = await select()\n      .from('product')\n      .where('uuid', '=', product_id)\n      .load(connection);\n    if (!product) {\n      response.status(INVALID_PAYLOAD);\n      return response.json({\n        success: false,\n        message: 'Product does not exists'\n      });\n    }\n\n    // If the product is belong to a variant group, get all the variants and assign them to the collection\n    if (product.variant_group_id) {\n      const variants = await select()\n        .from('product')\n        .where('variant_group_id', '=', product.variant_group_id)\n        .execute(connection);\n      // Insert all the variants to the collection\n      await Promise.all(\n        variants.map(async (variant) => {\n          await insertOnUpdate('product_collection', [\n            'collection_id',\n            'product_id'\n          ])\n            .given({\n              collection_id: collection.collection_id,\n              product_id: variant.product_id\n            })\n            .execute(connection);\n        })\n      );\n    } else {\n      // Assign the product to the collection\n      await insertOnUpdate('product_collection', [\n        'collection_id',\n        'product_id'\n      ])\n        .given({\n          collection_id: collection.collection_id,\n          product_id: product.product_id\n        })\n        .execute(connection);\n    }\n\n    await commit(connection);\n    response.status(OK);\n    return response.json({\n      success: true,\n      data: {\n        product_id: product.product_id,\n        collection_id: collection.collection_id\n      }\n    });\n  } catch (e) {\n    await rollback(connection);\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    return response.json({\n      success: false,\n      message: e.message\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addProductToCollection/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"product_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"product_id\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addProductToCollection/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/collections/:collection_id/products\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addVariantItem/[bodyParser]addItem.js",
    "content": "import {\n  select,\n  update,\n  startTransaction,\n  commit,\n  rollback\n} from '@evershop/postgres-query-builder';\nimport uniqid from 'uniqid';\nimport { pool, getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const { id: groupId } = request.params;\n\n  const { product_id } = request.body;\n  const connection = await getConnection(pool);\n  try {\n    await startTransaction(connection);\n    const group = await select()\n      .from('variant_group')\n      .where('uuid', '=', groupId)\n      .load(connection, false);\n\n    if (!group) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid variant group'\n        }\n      });\n      return;\n    }\n\n    const product = await select()\n      .from('product')\n      .where('uuid', '=', product_id)\n      .and('group_id', '=', group.attribute_group_id)\n      .load(connection, false);\n\n    if (!product) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message:\n            'The product is either not exist or using different attribute group'\n        }\n      });\n      return;\n    }\n\n    await update('product')\n      .given({\n        variant_group_id: group.variant_group_id\n      })\n      .where('uuid', '=', product_id)\n      .execute(connection, false);\n\n    const variantAttributeIds = Object.values({\n      attributeOne: group.attribute_one,\n      attributeTwo: group.attribute_two,\n      attributeThree: group.attribute_three,\n      attributeFour: group.attribute_four,\n      attributeFive: group.attribute_five\n    }).filter((a) => a !== null);\n\n    // Get product attribute values\n    const query = select().from('product_attribute_value_index');\n    query\n      .innerJoin('attribute')\n      .on(\n        'attribute.attribute_id',\n        '=',\n        'product_attribute_value_index.attribute_id'\n      );\n    query\n      .where(\n        'product_attribute_value_index.product_id',\n        '=',\n        product.product_id\n      )\n      .and(\n        'product_attribute_value_index.attribute_id',\n        'in',\n        variantAttributeIds\n      );\n\n    const attributes = await query.execute(connection, false);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: {\n        id: uniqid(),\n        attributes: attributes.map((a) => ({\n          attribute_id: a.attribute_id,\n          attribute_code: a.attribute_code,\n          option_id: a.option_id\n        })),\n        product\n      }\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addVariantItem/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addVariantItem/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"product_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"product_id\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/addVariantItem/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/variantGroups/:id/items\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createAttribute/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createAttribute/createAttribute[finish].js",
    "content": "import createProductAttribute from '../../services/attribute/createProductAttribute.js';\n\nexport default async (request, response) => {\n  const attribute = await createProductAttribute(request.body, {\n    routeId: request.currentRoute.id\n  });\n  return attribute;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createAttribute/finish[apiResponse].js",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const attribute = await getDelegate('createAttribute', request);\n  response.status(OK);\n  response.json({\n    data: {\n      ...attribute,\n      links: [\n        {\n          rel: 'attributeGrid',\n          href: buildUrl('attributeGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('attributeEdit', { id: attribute.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createAttribute/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"attribute_name\": {\n      \"type\": \"string\"\n    },\n    \"attribute_code\": {\n      \"type\": \"string\"\n    },\n    \"is_required\": {\n      \"type\": [\"integer\", \"string\"],\n      \"enum\": [0, 1, \"0\", \"1\"]\n    },\n    \"display_on_frontend\": {\n      \"type\": [\"integer\", \"string\"],\n      \"enum\": [0, 1, \"0\", \"1\"]\n    },\n    \"sort_order\": {\n      \"type\": [\"string\", \"integer\"],\n      \"pattern\": \"^[0-9]*$\"\n    },\n    \"is_filterable\": {\n      \"type\": [\"integer\", \"string\"],\n      \"enum\": [0, 1, \"0\", \"1\"]\n    },\n    \"groups\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": [\"string\", \"integer\"],\n        \"pattern\": \"^[0-9]*$\"\n      }\n    },\n    \"options\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"option_text\": {\n            \"type\": \"string\"\n          },\n          \"option_id\": {\n            \"type\": [\"string\", \"integer\"],\n            \"pattern\": \"^[1-9][0-9]*$\"\n          }\n        },\n        \"required\": [\"option_text\"]\n      }\n    }\n  },\n  \"required\": [\n    \"attribute_code\",\n    \"attribute_name\",\n    \"type\",\n    \"is_required\",\n    \"display_on_frontend\",\n    \"groups\"\n  ],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createAttribute/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/attributes\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createAttributeGroup/[bodyParser]saveGroup.js",
    "content": "import {\n  startTransaction,\n  insert,\n  commit,\n  rollback,\n  select\n} from '@evershop/postgres-query-builder';\nimport { getConnection, pool } from '../../../../lib/postgres/connection.js';\nimport { OK, INTERNAL_SERVER_ERROR } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  const data = request.body;\n  try {\n    await startTransaction(connection);\n    const result = await insert('attribute_group')\n      .given(data)\n      .execute(connection);\n    await commit(connection);\n\n    const group = await select()\n      .from('attribute_group')\n      .where('attribute_group_id', '=', result.insertId)\n      .load(pool);\n\n    response.status(OK);\n    response.json({\n      data: {\n        ...group\n      }\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createAttributeGroup/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createAttributeGroup/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"group_name\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"group_name\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createAttributeGroup/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/attributeGroups\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createCategory/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createCategory/createCategory[finish].ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport createCategory from '../../services/category/createCategory.js';\n\nexport default async (request: EvershopRequest, response: EvershopResponse) => {\n  const result = await createCategory(request.body, {\n    routeId: request.currentRoute.id\n  });\n  return result;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createCategory/finish[apiResponse].ts",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const category = await getDelegate<Record<string, any>>(\n    'createCategory',\n    request\n  );\n  response.status(OK);\n  response.json({\n    data: {\n      ...category,\n      links: [\n        {\n          rel: 'categoryGrid',\n          href: buildUrl('categoryGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'view',\n          href: buildUrl('categoryView', { uuid: category?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('categoryEdit', { id: category?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createCategory/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"description\": {\n      \"type\": \"array\",\n      \"skipEscape\": true,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Description block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Description block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Description block ID is required\",\n            \"size\": \"Description block size is required\",\n            \"columns\": \"Description block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Description must be an array\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createCategory/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/categories\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createCollection/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createCollection/createCollection[finish].ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport createCollection from '../../services/collection/createCollection.js';\n\nexport default async (request: EvershopRequest, response) => {\n  const collection = await createCollection(request.body, {\n    routeId: request.currentRoute.id\n  });\n  return collection;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createCollection/finish[apiResponse].ts",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const collection = await getDelegate<Record<string, any>>(\n    'createCollection',\n    request\n  );\n  response.status(OK);\n  response.json({\n    data: {\n      ...collection,\n      links: [\n        {\n          rel: 'collectionGrid',\n          href: buildUrl('collectionGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('collectionEdit', { id: collection?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createCollection/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Collection name must be a string\",\n        \"minLength\": \"Collection name is required and cannot be empty\"\n      }\n    },\n    \"description\": {\n      \"type\": \"array\",\n      \"skipEscape\": true,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Description block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Description block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Description block ID is required\",\n            \"size\": \"Description block size is required\",\n            \"columns\": \"Description block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Description must be an array\"\n      }\n    },\n    \"code\": {\n      \"type\": \"string\",\n      \"pattern\": \"^\\\\S+$\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Collection code must be a string\",\n        \"pattern\": \"Collection code cannot contain spaces\",\n        \"minLength\": \"Collection code is required and cannot be empty\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createCollection/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/collections\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createProduct/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createProduct/createProduct[finish].ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport createProduct from '../../services/product/createProduct.js';\n\nexport default async (request: EvershopRequest, response: EvershopResponse) => {\n  const result = await createProduct(request.body, {\n    routeId: request.currentRoute.id\n  });\n  return result;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createProduct/finish[apiResponse].ts",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const product = await getDelegate<Record<string, any>>(\n    'createProduct',\n    request\n  );\n  response.status(OK);\n  response.json({\n    data: {\n      ...product,\n      links: [\n        {\n          rel: 'productGrid',\n          href: buildUrl('productGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'view',\n          href: buildUrl('productView', { uuid: product?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('productEdit', { id: product?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createProduct/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"description\": {\n      \"type\": \"array\",\n      \"skipEscape\": true,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Description block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Description block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Description block ID is required\",\n            \"size\": \"Description block size is required\",\n            \"columns\": \"Description block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Description must be an array\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createProduct/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/products\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createVariantGroup/[bodyParser]saveGroup.js",
    "content": "import { insert, select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const { attribute_codes, attribute_group_id } = request.body;\n  try {\n    if (attribute_codes.length === 0) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'No attributes provided'\n        }\n      });\n      return;\n    }\n\n    if (attribute_codes.length > 5) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'We only support up to 5 attributes'\n        }\n      });\n      return;\n    }\n\n    const attributes = await select()\n      .from('attribute')\n      .where('attribute_code', 'in', attribute_codes)\n      .and('type', '=', 'select')\n      .execute(pool);\n\n    if (attributes.length !== attribute_codes.length) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Attribute must be of type select'\n        }\n      });\n      return;\n    }\n\n    const attributeGroupLinks = await select()\n      .from('attribute_group_link')\n      .where('group_id', '=', attribute_group_id)\n      .and(\n        'attribute_id',\n        'in',\n        attributes.map((a) => a.attribute_id)\n      )\n      .execute(pool);\n\n    if (attributeGroupLinks.length !== attributes.length) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Attribute must be assigned to the group'\n        }\n      });\n      return;\n    }\n\n    const data = {};\n    attributes.forEach((attribute, index) => {\n      let column;\n      switch (index) {\n        case 0:\n          column = 'attribute_one';\n          break;\n        case 1:\n          column = 'attribute_two';\n          break;\n        case 2:\n          column = 'attribute_three';\n          break;\n        case 3:\n          column = 'attribute_four';\n          break;\n        case 4:\n          column = 'attribute_five';\n          break;\n        default:\n          break;\n      }\n      data[column] = attribute.attribute_id;\n    });\n    data.attribute_group_id = attribute_group_id;\n    // Create a variant group\n    const result = await insert('variant_group').given(data).execute(pool);\n\n    const group = await select()\n      .from('variant_group')\n      .where('variant_group_id', '=', result.insertId)\n      .load(pool);\n\n    const promises = attributes.map(async (attribute) => {\n      const { attribute_id } = attribute;\n      const options = await select()\n        .from('attribute_option')\n        .where('attribute_id', '=', attribute_id)\n        .execute(pool);\n      return {\n        ...attribute,\n        options\n      };\n    });\n\n    const results = await Promise.all(promises);\n\n    group.attributes = results;\n    group.addItemApi = buildUrl('addVariantItem', { id: group.uuid });\n\n    response.status(OK);\n    response.json({\n      data: group\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createVariantGroup/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createVariantGroup/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"attribute_codes\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"attribute_group_id\": {\n      \"type\": [\"integer\", \"string\"],\n      \"pattern\": \"^[0-9]+$\"\n    }\n  },\n  \"required\": [\"attribute_codes\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/createVariantGroup/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/variantGroups\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/deleteAttribute/deleteAttribute.js",
    "content": "import { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport deleteProductAttribute from '../../services/attribute/deleteProductAttribute.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { id } = request.params;\n    const attribute = await deleteProductAttribute(id, {\n      routeId: request.currentRoute.id\n    });\n    response.status(OK);\n    response.json({\n      data: attribute\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/deleteAttribute/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/attributes/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/deleteAttributeGroup/deleteAttributeGroup.js",
    "content": "import { del, select } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  try {\n    const { id } = request.params;\n    const attributeGroup = await select()\n      .from('attribute_group')\n      .where('uuid', '=', id)\n      .load(connection);\n\n    if (!attributeGroup) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Attribute group not found'\n        }\n      });\n      return;\n    }\n\n    if (parseInt(attributeGroup.attribute_group_id, 10) === 1) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Can not delete the default attribute group'\n        }\n      });\n      return;\n    }\n\n    await del('attribute_group').where('uuid', '=', id).execute(connection);\n\n    response.status(OK);\n    response.json({\n      data: attributeGroup\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/deleteAttributeGroup/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/attributeGroups/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/deleteCategory/deleteCategory.js",
    "content": "import { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport deleteCategory from '../../services/category/deleteCategory.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { id } = request.params;\n    const category = await deleteCategory(id, {\n      routeId: request.currentRoute.id\n    });\n    response.status(OK);\n    response.json({\n      data: category\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/deleteCategory/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/categories/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/deleteCollection/deleteCollection.js",
    "content": "import { OK, INTERNAL_SERVER_ERROR } from '../../../../lib/util/httpStatus.js';\nimport deleteCollection from '../../services/collection/deleteCollection.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { id } = request.params;\n    const collection = await deleteCollection(id, {\n      routeId: request.currentRoute.id\n    });\n    response.status(OK);\n    response.json({\n      data: collection\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/deleteCollection/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/collections/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/deleteProduct/deleteProduct.js",
    "content": "import { OK, INTERNAL_SERVER_ERROR } from '../../../../lib/util/httpStatus.js';\nimport deleteProduct from '../../services/product/deleteProduct.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { id } = request.params;\n    const product = await deleteProduct(id, {\n      routeId: request.currentRoute.id\n    });\n    response.status(OK);\n    response.json({\n      data: product\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/deleteProduct/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/products/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/removeProductFromCategory/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/removeProductFromCategory/removeProducts.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport {\n  INVALID_PAYLOAD,\n  OK,\n  INTERNAL_SERVER_ERROR\n} from '../../../../lib/util/httpStatus.js';\nimport updateProduct from '../../services/product/updateProduct.js';\n\nexport default async (request, response, next) => {\n  const { category_id, product_id } = request.params;\n  try {\n    // Check if the category is exists\n    const category = await select()\n      .from('category')\n      .where('uuid', '=', category_id)\n      .load(pool);\n\n    if (!category) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        success: false,\n        message: 'Category does not exists'\n      });\n    }\n\n    // Check if the product is exists\n    const product = await select()\n      .from('product')\n      .where('uuid', '=', product_id)\n      .load(pool);\n\n    if (!product) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        success: false,\n        message: 'Product does not exists'\n      });\n    }\n\n    // Remove the product from the category\n    await updateProduct(product_id, { category_id: null });\n    response.status(OK);\n    response.json({\n      success: true,\n      data: {\n        product_id,\n        category_id\n      }\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      success: false,\n      message: e.message\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/removeProductFromCategory/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/categories/:category_id/products/:product_id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/removeProductFromCollection/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/removeProductFromCollection/removeProducts.js",
    "content": "import {\n  commit,\n  del,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const { collection_id, product_id } = request.params;\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    // Check if the collection is exists\n    const collection = await select()\n      .from('collection')\n      .where('uuid', '=', collection_id)\n      .load(connection);\n\n    if (!collection) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        success: false,\n        message: 'Collection does not exists'\n      });\n    }\n\n    // Check if the product is exists\n    const product = await select()\n      .from('product')\n      .where('uuid', '=', product_id)\n      .load(connection);\n\n    if (!product) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        success: false,\n        message: 'Product does not exists'\n      });\n    }\n\n    if (product.variant_group_id) {\n      const variants = await select()\n        .from('product')\n        .where('variant_group_id', '=', product.variant_group_id)\n        .execute(connection);\n\n      await Promise.all(\n        variants.map(async (variant) => {\n          await del('product_collection')\n            .where('collection_id', '=', collection.collection_id)\n            .and('product_id', '=', variant.product_id)\n            .execute(connection);\n        })\n      );\n    } else {\n      await del('product_collection')\n        .where('collection_id', '=', collection.collection_id)\n        .and('product_id', '=', product.product_id)\n        .execute(connection);\n    }\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      success: true,\n      data: {\n        product_id,\n        collection_id\n      }\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      success: false,\n      message: e.message\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/removeProductFromCollection/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/collections/:collection_id/products/:product_id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/unlinkVariant/[context]multerNone[auth].js",
    "content": "import multer from 'multer';\n\nconst upload = multer();\n\nexport default (request, response, next) => {\n  upload.none()(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/unlinkVariant/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/variants/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/unlinkVariant/unlinkVariants.js",
    "content": "import { update } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  try {\n    await update('product')\n      .given({ variant_group_id: null, visibility: null })\n      .where('product_id', '=', parseInt(`0${request.body.id}`, 10))\n      .execute(connection);\n    response.status(OK).json({\n      data: {}\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR).json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateAttribute/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateAttribute/finish[apiResponse].ts",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\nimport { AttributeRow } from '../../../../types/db/index.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const attribute = (await getDelegate(\n    'updateAttribute',\n    request\n  )) as AttributeRow;\n  response.status(OK);\n  response.json({\n    data: {\n      ...attribute,\n      links: [\n        {\n          rel: 'attributeGrid',\n          href: buildUrl('attributeGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('attributeEdit', { id: attribute.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateAttribute/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/attributes/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateAttribute/updateAttribute[finish].ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport updateProductAttribute from '../../services/attribute/updateProductAttribute.js';\n\nexport default async (request: EvershopRequest, response) => {\n  const result = await updateProductAttribute(\n    Array.isArray(request.params.id) ? request.params.id[0] : request.params.id,\n    request.body,\n    {}\n  );\n  return result;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateAttributeGroup/[bodyParser]saveGroup.js",
    "content": "import { update, select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const data = request.body;\n  try {\n    const group = await select()\n      .from('attribute_group')\n      .where('uuid', '=', request.params.id)\n      .load(pool);\n\n    if (!group) {\n      response.status(INVALID_PAYLOAD);\n      throw new Error('Invalid attribute group id');\n    }\n\n    await update('attribute_group')\n      .given(data)\n      .where('uuid', '=', request.params.id)\n      .execute(pool);\n\n    const updatedGroup = await select()\n      .from('attribute_group')\n      .where('uuid', '=', request.params.id)\n      .load(pool);\n\n    response.status(OK);\n    response.json({\n      data: {\n        ...updatedGroup\n      }\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateAttributeGroup/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateAttributeGroup/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"group_name\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"group_name\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateAttributeGroup/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/attributeGroups/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateCategory/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateCategory/finish[apiResponse].ts",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const category = await getDelegate<Record<string, any>>(\n    'updateCategory',\n    request\n  );\n  response.status(OK);\n  response.json({\n    data: {\n      ...category,\n      links: [\n        {\n          rel: 'categoryGrid',\n          href: buildUrl('categoryGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'view',\n          href: buildUrl('categoryView', { uuid: category?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('categoryEdit', { id: category?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateCategory/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"description\": {\n      \"type\": \"array\",\n      \"skipEscape\": true,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Description block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Description block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Description block ID is required\",\n            \"size\": \"Description block size is required\",\n            \"columns\": \"Description block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Description must be an array\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateCategory/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/categories/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateCategory/updateCategory[finish].ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport updateCategory from '../../services/category/updateCategory.js';\n\nexport default async (request: EvershopRequest, response: EvershopResponse) => {\n  const category = await updateCategory(\n    Array.isArray(request.params.id) ? request.params.id[0] : request.params.id,\n    request.body,\n    {\n      routeId: request.currentRoute.id\n    }\n  );\n  return category;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateCollection/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateCollection/finish[apiResponse].ts",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const collection = await getDelegate<Record<string, any>>(\n    'updateCollection',\n    request\n  );\n  response.status(OK);\n  response.json({\n    data: {\n      ...collection,\n      links: [\n        {\n          rel: 'collectionGrid',\n          href: buildUrl('collectionGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('collectionEdit', { id: collection?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateCollection/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Collection name must be a string\",\n        \"minLength\": \"Collection name is required and cannot be empty\"\n      }\n    },\n    \"description\": {\n      \"type\": \"array\",\n      \"skipEscape\": true,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Description block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Description block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Description block ID is required\",\n            \"size\": \"Description block size is required\",\n            \"columns\": \"Description block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Description must be an array\"\n      }\n    },\n    \"code\": {\n      \"type\": \"string\",\n      \"pattern\": \"^\\\\S+$\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Collection code must be a string\",\n        \"pattern\": \"Collection code cannot contain spaces\",\n        \"minLength\": \"Collection code is required and cannot be empty\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateCollection/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/collections/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateCollection/updateCollection[finish].ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport updateCollection from '../../services/collection/updateCollection.js';\n\nexport default async (request: EvershopRequest, response: EvershopResponse) => {\n  const collection = await updateCollection(\n    Array.isArray(request.params.id) ? request.params.id[0] : request.params.id,\n    request.body,\n    {\n      routeId: request.currentRoute.id\n    }\n  );\n  return collection;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateProduct/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateProduct/finish[apiResponse].ts",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const product = await getDelegate<Record<string, any>>(\n    'updateProduct',\n    request\n  );\n  response.status(OK);\n  response.json({\n    data: {\n      ...product,\n      links: [\n        {\n          rel: 'productGrid',\n          href: buildUrl('productGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'view',\n          href: buildUrl('productView', { uuid: product?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('productEdit', { id: product?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateProduct/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"description\": {\n      \"type\": \"array\",\n      \"skipEscape\": true,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Description block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Description block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Description block ID is required\",\n            \"size\": \"Description block size is required\",\n            \"columns\": \"Description block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Description must be an array\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateProduct/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/products/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/updateProduct/updateProduct[finish].ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport updateProduct from '../../services/product/updateProduct.js';\n\nexport default async (request: EvershopRequest, response: EvershopResponse) => {\n  const product = await updateProduct(\n    Array.isArray(request.params.id) ? request.params.id[0] : request.params.id,\n    request.body,\n    {\n      routeId: request.currentRoute.id\n    }\n  );\n  return product;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/variantSearch/loadVariants.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const query = select()\n    .select('product_id', 'variant_product_id')\n    .select('sku')\n    .select('name')\n    .select('status')\n    .select('price')\n    .select('qty')\n    .select('product.image')\n    .select('product_image.origin_image', 'gallery')\n    .from('product');\n\n  query\n    .leftJoin('product_image')\n    .on('product.product_id', '=', 'product_image.product_image_product_id');\n\n  query\n    .leftJoin('product_description')\n    .on(\n      'product.product_id',\n      '=',\n      'product_description.product_description_product_id'\n    );\n\n  // Only return item that not assigned to any group\n  query.where('variant_group_id', 'IS', null);\n\n  if (request.query.keyword) {\n    query\n      .andWhere('name', 'LIKE', `%${request.query.keyword}%`)\n      .or('sku', 'LIKE', `%${request.query.keyword}%`);\n  }\n\n  const results = await query.execute(pool);\n\n  const variants = [];\n  results.forEach((variant) => {\n    const index = variants.findIndex(\n      (v) => v.variant_product_id === variant.variant_product_id\n    );\n    if (index === -1) {\n      variants.push({\n        ...variant,\n        image: {\n          url: variant.origin_image\n        },\n        images: [\n          variant.image ? { url: variant.image, path: variant.image } : null,\n          variant.gallery\n            ? {\n                url: variant.gallery,\n                path: variant.gallery\n              }\n            : null\n        ].filter((i) => i !== null)\n      });\n    } else {\n      variants[index] = {\n        ...variants[index],\n        images: variants[index].images.concat({\n          url: variant.gallery,\n          path: variant.gallery\n        })\n      };\n    }\n  });\n\n  for (let i = 0; i < variants.length; i += 1) {\n    variants[i].attributes = JSON.parse(\n      JSON.stringify(\n        await select()\n          .from('product_attribute_value_index')\n          .where('product_id', '=', variants[i].variant_product_id)\n          .execute(pool)\n      )\n    );\n  }\n\n  response.status(OK).json({\n    data: { variants }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/api/variantSearch/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/variants\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/bootstrap.js",
    "content": "import path from 'path';\nimport config from 'config';\nimport { CONSTANTS } from '../../lib/helpers.js';\nimport { defaultPaginationFilters } from '../../lib/util/defaultPaginationFilters.js';\nimport { merge } from '../../lib/util/merge.js';\nimport { addProcessor } from '../../lib/util/registry.js';\nimport { registerWidget } from '../../lib/widget/widgetManager.js';\nimport { registerCartItemProductUrlField } from './services/registerCartItemProductUrlField.js';\nimport { registerCartItemVariantOptionsField } from './services/registerCartItemVariantOptionsField.js';\nimport registerDefaultAttributeCollectionFilters from './services/registerDefaultAttributeCollectionFilters.js';\nimport registerDefaultCategoryCollectionFilters from './services/registerDefaultCategoryCollectionFilters.js';\nimport registerDefaultCollectionCollectionFilters from './services/registerDefaultCollectionCollectionFilters.js';\nimport registerDefaultProductCollectionFilters from './services/registerDefaultProductCollectionFilters.js';\n\nexport default () => {\n  addProcessor('cartItemFields', registerCartItemProductUrlField, 0);\n  addProcessor('cartItemFields', registerCartItemVariantOptionsField, 0);\n  addProcessor('configurationSchema', (schema) => {\n    merge(schema, {\n      properties: {\n        catalog: {\n          type: 'object',\n          properties: {\n            product: {\n              type: 'object',\n              properties: {\n                image: {\n                  type: 'object',\n                  properties: {\n                    width: {\n                      type: 'integer'\n                    },\n                    height: {\n                      type: 'integer'\n                    }\n                  }\n                }\n              }\n            },\n            showOutOfStockProduct: {\n              type: 'boolean'\n            },\n            collectionPageSize: {\n              type: 'integer',\n              minimum: 1\n            }\n          }\n        },\n        pricing: {\n          type: 'object',\n          properties: {\n            rounding: {\n              type: 'string',\n              enum: ['round', 'floor', 'ceil']\n            },\n            precision: {\n              type: 'integer'\n            }\n          }\n        }\n      }\n    });\n    return schema;\n  });\n  const defaultCatalogConfig = {\n    product: {\n      image: {\n        width: 1200,\n        height: 1200\n      }\n    },\n    showOutOfStockProduct: false,\n    collectionPageSize: 20\n  };\n  config.util.setModuleDefaults('catalog', defaultCatalogConfig);\n\n  // Default pricing configuration\n  const defaultPricingConfig = {\n    rounding: 'round',\n    precision: 2\n  };\n  config.util.setModuleDefaults('pricing', defaultPricingConfig);\n\n  // Reigtering the default filters for product collection\n  addProcessor(\n    'productCollectionFilters',\n    registerDefaultProductCollectionFilters,\n    1\n  );\n  addProcessor(\n    'productCollectionFilters',\n    (filters) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n\n  // Reigtering the default filters for category collection\n  addProcessor(\n    'categoryCollectionFilters',\n    registerDefaultCategoryCollectionFilters,\n    1\n  );\n  addProcessor(\n    'categoryCollectionFilters',\n    (filters) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n\n  // Reigtering the default filters for collection collection\n  addProcessor(\n    'collectionCollectionFilters',\n    registerDefaultCollectionCollectionFilters,\n    1\n  );\n  addProcessor(\n    'collectionCollectionFilters',\n    (filters) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n\n  // Reigtering the default filters for attribute collection\n  addProcessor(\n    'attributeCollectionFilters',\n    registerDefaultAttributeCollectionFilters,\n    1\n  );\n  addProcessor(\n    'attributeCollectionFilters',\n    (filters) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n\n  // Reigtering the default filters for attribute group collection\n  addProcessor(\n    'attributeGroupCollectionFilters',\n    (filters) => [...filters, ...defaultPaginationFilters],\n    1\n  );\n\n  registerWidget({\n    type: 'collection_products',\n    name: 'Collection products',\n    description: 'A list of products from a collection',\n    settingComponent: path.resolve(\n      CONSTANTS.MODULESPATH,\n      'catalog/components/CollectionProductsSetting.js'\n    ),\n    component: path.resolve(\n      CONSTANTS.MODULESPATH,\n      'catalog/components/CollectionProducts.js'\n    ),\n    defaultSettings: {\n      collection: null,\n      count: 4,\n      countPerRow: 4\n    },\n    enabled: true\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/components/CollectionProducts.tsx",
    "content": "import { Editor } from '@components/common/Editor.js';\nimport { Row } from '@components/common/form/Editor.js';\nimport { ProductList } from '@components/frontStore/catalog/ProductList.js';\nimport React from 'react';\n\ninterface CollectionProductsProps {\n  collection: {\n    collectionId: number;\n    name: string;\n    description?: Row[];\n    products: {\n      items: Array<React.ComponentProps<typeof ProductList>['products'][0]>;\n    };\n  } | null;\n  collectionProductsWidget?: {\n    countPerRow?: number;\n  };\n}\nexport default function CollectionProducts({\n  collection,\n  collectionProductsWidget: { countPerRow } = {}\n}: CollectionProductsProps) {\n  if (!collection) {\n    return null;\n  }\n  return (\n    <div className=\"pt-7 collection__products__widget\">\n      <div className=\"page-width\">\n        <h3 className=\"text-center uppercase h5 tracking-widest\">\n          {collection?.name}\n        </h3>\n        <div className=\"flex justify-center\">\n          {collection?.description && <Editor rows={collection?.description} />}\n        </div>\n        <div className=\"mt-3\">\n          <ProductList\n            products={collection?.products?.items}\n            gridColumns={countPerRow}\n          />\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport const query = `\n  query Query($collection: String, $count: Int, $countPerRow: Int) {\n    collection (code: $collection) {\n      collectionId\n      name\n      description\n      products (filters: [{key: \"limit\", operation: eq, value: $count}]) {\n        items {\n          ...Product\n        }\n      }\n    }\n    collectionProductsWidget(collection: $collection, count: $count, countPerRow: $countPerRow) {\n      countPerRow\n    }\n  }\n`;\n\nexport const fragments = `\n  fragment Product on Product {\n    productId\n    name\n    sku\n    price {\n      regular {\n        value\n        text\n      }\n      special {\n        value\n        text\n      }\n    }\n    inventory {\n      isInStock\n    }\n    image {\n      alt\n      url\n    }\n    url\n  }\n`;\n\nexport const variables = `{\n  collection: getWidgetSetting(\"collection\"),\n  count: getWidgetSetting(\"count\"),\n  countPerRow: getWidgetSetting(\"countPerRow\", 4)\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/components/CollectionProductsSetting.tsx",
    "content": "import Spinner from '@components/admin/Spinner.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport { Item, ItemContent } from '@components/common/ui/Item.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport {\n  RadioGroup,\n  RadioGroupItem\n} from '@components/common/ui/RadioGroup.js';\nimport React from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport { useQuery } from 'urql';\n\nconst SearchQuery = `\n  query Query ($filters: [FilterInput!]) {\n    collections(filters: $filters) {\n      items {\n        collectionId\n        uuid\n        code\n        name\n      }\n      total\n    }\n  }\n`;\n\ninterface CollectionProductsSettingProps {\n  collectionProductsWidget: {\n    collection: string;\n    count: number;\n    countPerRow?: number;\n  };\n}\nfunction CollectionProductsSetting({\n  collectionProductsWidget: { collection, count, countPerRow }\n}: CollectionProductsSettingProps) {\n  const limit = 10;\n  const [inputValue, setInputValue] = React.useState<string | null>(null);\n  const [selectedCollection, setSelectedCollection] =\n    React.useState(collection);\n  const [page, setPage] = React.useState(1);\n  const { register, setValue } = useFormContext();\n  const [result, reexecuteQuery] = useQuery({\n    query: SearchQuery,\n    variables: {\n      filters: inputValue\n        ? [\n            { key: 'name', operation: 'like', value: inputValue },\n            { key: 'page', operation: 'eq', value: page.toString() },\n            { key: 'limit', operation: 'eq', value: limit.toString() }\n          ]\n        : [\n            { key: 'limit', operation: 'eq', value: limit.toString() },\n            { key: 'page', operation: 'eq', value: page.toString() }\n          ]\n    },\n    pause: true\n  });\n\n  React.useEffect(() => {\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  }, []);\n\n  React.useEffect(() => {\n    const timer = setTimeout(() => {\n      if (inputValue !== null) {\n        reexecuteQuery({ requestPolicy: 'network-only' });\n      }\n    }, 1500);\n\n    return () => clearTimeout(timer);\n  }, [inputValue]);\n\n  React.useEffect(() => {\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  }, [page]);\n\n  const { data, fetching, error } = result;\n\n  if (error) {\n    return (\n      <p className=\"text-destructive\">\n        There was an error fetching collections.\n        {error.message}\n      </p>\n    );\n  }\n\n  return (\n    <div>\n      <div>\n        <div className=\"mb-3\">\n          <Input\n            type=\"text\"\n            value={inputValue || ''}\n            placeholder=\"Search collections\"\n            onChange={(e) => setInputValue(e.target.value)}\n          />\n        </div>\n        {fetching && (\n          <Item variant={'outline'}>\n            <ItemContent>\n              <Spinner width={25} height={25} />\n            </ItemContent>\n          </Item>\n        )}\n        {!fetching && data && (\n          <div>\n            {data.collections.items.length === 0 && (\n              <div className=\"p-2 border border-divider rounded flex justify-center items-center\">\n                {inputValue ? (\n                  <p>\n                    No collections found for query &quot;{inputValue}&rdquo;\n                  </p>\n                ) : (\n                  <p>You have no collections to display</p>\n                )}\n              </div>\n            )}\n            <RadioGroup\n              defaultValue={selectedCollection}\n              onValueChange={(value) => {\n                setSelectedCollection(value as string);\n                setValue('settings[collection]', value, {\n                  shouldDirty: true\n                });\n              }}\n            >\n              <div className=\"divide-y mb-2\">\n                {data.collections.items.map((collection) => (\n                  <div\n                    key={collection.uuid}\n                    className=\"grid grid-cols-8 gap-5 py-3 border-divider items-center\"\n                  >\n                    <div className=\"col-span-6\">\n                      <Label>{collection.name}</Label>\n                    </div>\n                    <div className=\"col-span-2 flex items-center justify-end\">\n                      <RadioGroupItem value={collection.code} />\n                    </div>\n                  </div>\n                ))}\n              </div>\n              <InputField\n                type=\"hidden\"\n                name=\"settings[collection]\"\n                required\n                validation={{\n                  required: 'Please select a collection'\n                }}\n                defaultValue={selectedCollection}\n              />\n            </RadioGroup>\n          </div>\n        )}\n      </div>\n      <div className=\"mt-3 space-y-3\">\n        <NumberField\n          name=\"settings[count]\"\n          label=\"Total products\"\n          defaultValue={count}\n          required\n          validation={{ min: 1, required: 'Count is required' }}\n          min={1}\n          placeholder=\"Number of products\"\n        />\n        <div className=\"form-field\">\n          <NumberField\n            name=\"settings[countPerRow]\"\n            label=\"Products per row\"\n            min={1}\n            validation={{ min: 1, required: 'Count per row is required' }}\n            required\n            defaultValue={countPerRow}\n            placeholder=\"Number of products per row\"\n          />\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default CollectionProductsSetting;\n\nexport const query = `\n  query Query($collection: String, $count: Int, $countPerRow: Int) {\n    collectionProductsWidget(collection: $collection, count: $count, countPerRow: $countPerRow) {\n      collection\n      count\n      countPerRow\n    }\n  }\n`;\n\nexport const variables = `{\n  collection: getWidgetSetting(\"collection\"),\n  count: getWidgetSetting(\"count\"),\n  countPerRow: getWidgetSetting(\"countPerRow\")\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.graphql",
    "content": "\"\"\"\nRepresents a single attribute group\n\"\"\"\ntype AttributeGroup {\n  attributeGroupId: ID!\n  uuid: String!\n  groupName: String!\n  updateApi: String!\n  attributes: AttributeCollection\n}\n\nextend type Attribute {\n  groups: AttributeGroupCollection\n  editUrl: String!\n  updateApi: String!\n  deleteApi: String!\n}\n\n\"\"\"\nRepresents a collection of attributes\n\"\"\"\ntype AttributeCollection {\n  items: [Attribute]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\n\"\"\"\nRepresents a collection of attribute groups\n\"\"\"\ntype AttributeGroupCollection {\n  items: [AttributeGroup]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\nextend type Query {\n  attributes(filters: [FilterInput]): AttributeCollection\n  attributeGroups(filters: [FilterInput]): AttributeGroupCollection\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.resolvers.js",
    "content": "import { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { AttributeCollection } from '../../../../../modules/catalog/services/AttributeCollection.js';\nimport { AttributeGroupCollection } from '../../../../../modules/catalog/services/AttributeGroupCollection.js';\nimport { getAttributeGroupsBaseQuery } from '../../../../../modules/catalog/services/getAttributeGroupsBaseQuery.js';\nimport { getAttributesBaseQuery } from '../../../../../modules/catalog/services/getAttributesBaseQuery.js';\n\nexport default {\n  Query: {\n    attributes: async (_, { filters = [] }) => {\n      const query = getAttributesBaseQuery();\n      const root = new AttributeCollection(query);\n      await root.init(filters);\n      return root;\n    },\n    attributeGroups: async (_, { filters = [] }) => {\n      const query = getAttributeGroupsBaseQuery();\n      const root = new AttributeGroupCollection(query);\n      await root.init(filters);\n      return root;\n    }\n  },\n  AttributeGroup: {\n    attributes: async (group, { filters = [] }) => {\n      const query = getAttributesBaseQuery();\n      query\n        .innerJoin('attribute_group_link')\n        .on('attribute.attribute_id', '=', 'attribute_group_link.attribute_id');\n      query.where('attribute_group_link.group_id', '=', group.attributeGroupId);\n      const root = new AttributeCollection(query);\n      await root.init(filters);\n      return root;\n    },\n    updateApi: (group) => buildUrl('updateAttributeGroup', { id: group.uuid })\n  },\n\n  Attribute: {\n    groups: async (attribute, { filters = [] }) => {\n      const query = getAttributeGroupsBaseQuery();\n      query\n        .innerJoin('attribute_group_link')\n        .on(\n          'attribute_group.attribute_group_id',\n          '=',\n          'attribute_group_link.group_id'\n        );\n      query.where(\n        'attribute_group_link.attribute_id',\n        '=',\n        attribute.attributeId\n      );\n      const root = new AttributeGroupCollection(query);\n      await root.init(filters);\n      return root;\n    },\n    editUrl: ({ uuid }) => buildUrl('attributeEdit', { id: uuid }),\n    updateApi: (attribute) =>\n      buildUrl('updateAttribute', { id: attribute.uuid }),\n    deleteApi: (attribute) =>\n      buildUrl('deleteAttribute', { id: attribute.uuid })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.graphql",
    "content": "\"\"\"\nRepresents a single attribute option\n\"\"\"\ntype AttributeOption {\n  attributeOptionId: ID!\n  uuid: String!\n  optionText: String!\n}\n\n\"\"\"\nRepresents a single attribute\n\"\"\"\ntype Attribute {\n  attributeId: ID!\n  uuid: String!\n  attributeCode: String!\n  attributeName: String!\n  type: String!\n  isRequired: Int!\n  displayOnFrontend: Int!\n  sortOrder: Int!\n  isFilterable: Int!\n  options: [AttributeOption]\n}\n\nextend type Query {\n  attribute(id: Int): Attribute\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\n\nexport default {\n  Query: {\n    attribute: async (_, { id }, { pool }) => {\n      const attribute = await select()\n        .from('attribute')\n        .where('attribute_id', '=', id)\n        .load(pool);\n      if (!attribute) {\n        return null;\n      } else {\n        return camelCase(attribute);\n      }\n    }\n  },\n  Attribute: {\n    options: async (attribute, _, { pool }) => {\n      const results = await select()\n        .from('attribute_option')\n        .where('attribute_id', '=', attribute.attributeId)\n        .execute(pool);\n      return results.map((result) => camelCase(result));\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Category/Category.admin.graphql",
    "content": "extend type Category {\n  editUrl: String\n  updateApi: String!\n  deleteApi: String!\n  addProductUrl: String\n}\n\nextend type Product {\n  removeFromCategoryUrl: String\n}"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Category/Category.admin.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\n\nexport default {\n  Category: {\n    editUrl: (category) => buildUrl('categoryEdit', { id: category.uuid }),\n    updateApi: (category) => buildUrl('updateCategory', { id: category.uuid }),\n    deleteApi: (category) => buildUrl('deleteCategory', { id: category.uuid }),\n    addProductUrl: (category) =>\n      buildUrl('addProductToCategory', { category_id: category.uuid })\n  },\n  Product: {\n    removeFromCategoryUrl: async (product, _, { pool }) => {\n      if (!product.categoryId) {\n        return null;\n      } else {\n        const category = await select()\n          .from('category')\n          .where('category_id', '=', product.categoryId)\n          .load(pool);\n        return buildUrl('removeProductFromCategory', {\n          category_id: category.uuid,\n          product_id: product.uuid\n        });\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Category/Category.graphql",
    "content": "\"\"\"\nThe `Category` type represents a category object.\n\"\"\"\ntype Category {\n  categoryId: Int!\n  uuid: String!\n  name: String!\n  status: Int!\n  includeInNav: Int!\n  showProducts: Int!\n  description: JSON\n  urlKey: String\n  metaTitle: String\n  metaDescription: String\n  metaKeywords: String\n  image: CategoryImage\n  products(filters: [FilterInput]): ProductCollection\n  children: [Category]\n  parent: Category\n  path: [Category]\n  url: String\n  availableAttributes: [FilterAttribute]\n  priceRange: PriceRange\n  hasChildren: Boolean!\n}\n\n\"\"\"\nThe `CategoryImage` type represents a category image object.\n\"\"\"\ntype CategoryImage {\n  alt: String!\n  url: String!\n}\n\n\"\"\"\nThe `FilterInput` type represents a filter input object. Operations must be one of the following: eq, neq, gt, gteq, lt, lteq, like, nlike, in, nin.\n\"\"\"\nenum FilterOperation {\n  eq\n  neq\n  gt\n  gteq\n  lt\n  lteq\n  like\n  nlike\n  in\n  nin\n}\n\ninput FilterInput {\n  key: String!\n  operation: FilterOperation!\n  value: ID\n}\n\n\"\"\"\nThe `Filter` type represents a filter object.\n\"\"\"\ntype Filter {\n  key: String!\n  operation: String!\n  value: String!\n}\n\n\"\"\"\nThe `FilterOption` type represents a filter option object.\n\"\"\"\ntype FilterOption {\n  optionId: Int!\n  optionText: String!\n}\n\n\"\"\"\nThe `FilterAttribute` type represents a filter attribute object.\n\"\"\"\ntype FilterAttribute {\n  attributeName: String!\n  attributeCode: String!\n  attributeId: Int!\n  options: [FilterOption]\n}\n\n\"\"\"\nReturns a collection of categories.\n\"\"\"\ntype CategoryCollection {\n  items: [Category]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\ntype PriceRange {\n  min: Float!\n  minText: String!\n  max: Float!\n  maxText: String!\n}\n\nextend type Product {\n  category: Category\n}\n\nextend type Query {\n  categories(filters: [FilterInput]): CategoryCollection\n  category(id: Int): Category\n  currentCategory: Category\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Category/Category.resolvers.ts",
    "content": "import { execute, select } from '@evershop/postgres-query-builder';\nimport { v4 as uuidv4 } from 'uuid';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { toPrice } from '../../../../checkout/services/toPrice.js';\nimport { CategoryCollection } from '../../../services/CategoryCollection.js';\nimport { getCategoriesBaseQuery } from '../../../services/getCategoriesBaseQuery.js';\nimport { getFilterableAttributes } from '../../../services/getFilterableAttributes.js';\nimport { getProductsByCategoryBaseQuery } from '../../../services/getProductsByCategoryBaseQuery.js';\nimport { ProductCollection } from '../../../services/ProductCollection.js';\n\nexport default {\n  Query: {\n    category: async (_, { id }, { pool }) => {\n      const query = getCategoriesBaseQuery();\n      query.where('category.category_id', '=', id);\n      const result = await query.load(pool);\n      return result ? camelCase(result) : null;\n    },\n    currentCategory: async (_, args, { currentUrl, currentRoute, pool }) => {\n      if (currentRoute?.id !== 'categoryView') {\n        return null;\n      }\n      const { params } = currentRoute;\n      if (!params || !params.uuid) {\n        return null;\n      }\n      const query = getCategoriesBaseQuery();\n      query.where('uuid', '=', params.uuid);\n      const filtersFromUrl = buildFilterFromUrl(currentUrl);\n      const result = await query.load(pool);\n      return result\n        ? {\n            ...camelCase(result),\n            products: async (_, { filters = [] }) => {\n              const query = await getProductsByCategoryBaseQuery(\n                result.category_id,\n                true\n              );\n              const root = new ProductCollection(query);\n              // Can we merge 2 filters here and the filters take higher priority. Each is an array [{key, operation, value}]\n              const mergedFilters = [...filtersFromUrl, ...filters];\n              await root.init(mergedFilters, false);\n              return root;\n            }\n          }\n        : null;\n    },\n    categories: async (_, { filters = [] }, { user }) => {\n      const query = getCategoriesBaseQuery();\n      const root = new CategoryCollection(query);\n      await root.init(filters, !!user);\n      return root;\n    }\n  },\n  Category: {\n    products: async (category, { filters = [] }, { user }) => {\n      // This is a hack for mycategory\n      if (typeof category.products === 'function') {\n        return await category.products(category, { filters });\n      }\n      const query = await getProductsByCategoryBaseQuery(\n        category.categoryId,\n        !user\n      );\n      const root = new ProductCollection(query);\n      await root.init(filters, !!user);\n      return root;\n    },\n    availableAttributes: async (category) => {\n      const results = await getFilterableAttributes(category.categoryId);\n      return results;\n    },\n    priceRange: async (category, _, { pool }) => {\n      const query = await getProductsByCategoryBaseQuery(\n        category.categoryId,\n        true\n      );\n      query\n        .select('MIN(product.price)', 'min')\n        .select('MAX(product.price)', 'max');\n      const result = await query.load(pool);\n      return {\n        min: result.min || 0,\n        minText: toPrice(result.min || 0, true),\n        max: result.max || 0,\n        maxText: toPrice(result.max || 0, true)\n      };\n    },\n    url: async (category, _, { pool }) => {\n      // Get the url rewrite for this category\n      const urlRewrite = await select()\n        .from('url_rewrite')\n        .where('entity_uuid', '=', category.uuid)\n        .and('entity_type', '=', 'category')\n        .load(pool);\n      if (!urlRewrite) {\n        return buildUrl('categoryView', { uuid: category.uuid });\n      } else {\n        return urlRewrite.request_path;\n      }\n    },\n    image: (category) => {\n      const { image, name } = category;\n      if (!image) {\n        return null;\n      } else {\n        return {\n          alt: name,\n          url: image\n        };\n      }\n    },\n    hasChildren: async (category, _, { pool }) => {\n      const query = select().from('category');\n      query.where('category.parent_id', '=', category.categoryId);\n      const results = await query.execute(pool);\n      return results.length > 0;\n    },\n    children: async (category, _, { pool }) => {\n      const query = select().from('category');\n      query\n        .leftJoin('category_description', 'des')\n        .on(\n          'des.category_description_category_id',\n          '=',\n          'category.category_id'\n        );\n      query.where('category.parent_id', '=', category.categoryId);\n      const results = await query.execute(pool);\n      return results.map((row) => camelCase(row));\n    },\n    path: async (category, _, { pool }) => {\n      const query = await execute(\n        pool,\n        `WITH RECURSIVE category_path AS (\n          SELECT category_id, parent_id, 1 AS level\n          FROM category\n          WHERE category_id = ${category.categoryId}\n          UNION ALL\n          SELECT c.category_id, c.parent_id, cp.level + 1\n          FROM category c\n          INNER JOIN category_path cp ON cp.parent_id = c.category_id\n        )\n        SELECT category_id FROM category_path ORDER BY level DESC`\n      );\n      const categories = query.rows;\n      // Loop the categories and load the category description\n      return Promise.all(\n        categories.map(async (c) => {\n          const query = select().from('category');\n          query\n            .leftJoin('category_description', 'des')\n            .on(\n              'des.category_description_category_id',\n              '=',\n              'category.category_id'\n            );\n          query.where('category.category_id', '=', c.category_id);\n          return camelCase(await query.load(pool));\n        })\n      );\n    },\n    parent: async (category, _, { pool }) => {\n      if (!category.parentId) {\n        return null;\n      }\n      const query = select().from('category');\n      query\n        .leftJoin('category_description', 'des')\n        .on(\n          'des.category_description_category_id',\n          '=',\n          'category.category_id'\n        );\n      query.where('category.category_id', '=', category.parentId);\n      return camelCase(await query.load(pool));\n    },\n    description: ({ description }) => {\n      if (!description) {\n        return [];\n      }\n      try {\n        const json = JSON.parse(description);\n        return json;\n      } catch (e) {\n        // This is for backward compatibility. If the description is not a JSON string then it is a raw HTML block\n        const rowId = `r__${uuidv4()}`;\n        return [\n          {\n            size: 1,\n            id: rowId,\n            columns: [\n              {\n                id: 'c__c5d90067-c786-4324-8e24-8e30520ac3d7',\n                size: 1,\n                data: {\n                  time: 1723347125344,\n                  blocks: [\n                    {\n                      id: 'AU89ItzUa7',\n                      type: 'raw',\n                      data: {\n                        html: description\n                      }\n                    }\n                  ],\n                  version: '2.30.2'\n                }\n              }\n            ]\n          }\n        ];\n      }\n    }\n  },\n  Product: {\n    category: async (product, _, { pool }) => {\n      if (!product.categoryId) {\n        return null;\n      } else {\n        const categoryQuery = getCategoriesBaseQuery();\n        categoryQuery.where('category_id', '=', product.categoryId);\n        const category = await categoryQuery.load(pool);\n        return camelCase(category);\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.admin.graphql",
    "content": "extend type Collection {\n  editUrl: String\n  addProductUrl: String\n  updateApi: String!\n  deleteApi: String!\n}\n\nextend type Product {\n  removeFromCollectionUrl: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.admin.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\n\nexport default {\n  Collection: {\n    editUrl: (collection) =>\n      buildUrl('collectionEdit', { id: collection.uuid }),\n    addProductUrl: (collection) =>\n      buildUrl('addProductToCollection', { collection_id: collection.uuid }),\n    updateApi: (collection) =>\n      buildUrl('updateCollection', { id: collection.uuid }),\n    deleteApi: (collection) =>\n      buildUrl('deleteCollection', { id: collection.uuid })\n  },\n  Product: {\n    removeFromCollectionUrl: async (product, _, { pool }) => {\n      if (!product.collectionId) {\n        return null;\n      } else {\n        const collection = await select()\n          .from('collection')\n          .where('collection_id', '=', product.collectionId)\n          .load(pool);\n        return buildUrl('removeProductFromCollection', {\n          collection_id: collection.uuid,\n          product_id: product.uuid\n        });\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.graphql",
    "content": "\"\"\"\nThe `Collection` type represents a product collection.\n\"\"\"\ntype Collection {\n  collectionId: Int!\n  uuid: String!\n  name: String!\n  description: JSON\n  code: String!\n  products(filters: [FilterInput]): ProductCollection\n}\n\n\"\"\"\nReturns a collection of product collection.\n\"\"\"\ntype CollectionCollection {\n  items: [Collection]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\nextend type Product {\n  collections: [Collection],\n}\n\nextend type Query {\n  collections(filters: [FilterInput]): CollectionCollection\n  collection(code: String): Collection\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { v4 as uuidv4 } from 'uuid';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { CollectionCollection } from '../../../../../modules/catalog/services/CollectionCollection.js';\nimport { getCollectionsBaseQuery } from '../../../../../modules/catalog/services/getCollectionsBaseQuery.js';\nimport { getProductsByCollectionBaseQuery } from '../../../../../modules/catalog/services/getProductsByCollectionBaseQuery.js';\nimport { ProductCollection } from '../../../../../modules/catalog/services/ProductCollection.js';\n\nexport default {\n  Query: {\n    collection: async (_, { code }, { pool }) => {\n      const query = select().from('collection');\n      query.where('code', '=', code);\n      const result = await query.load(pool);\n      return result ? camelCase(result) : null;\n    },\n    collections: async (_, { filters = [] }) => {\n      const query = getCollectionsBaseQuery();\n      const root = new CollectionCollection(query);\n      await root.init(filters);\n      return root;\n    }\n  },\n  Collection: {\n    products: async (collection, { filters = [] }, { user }) => {\n      const query = getProductsByCollectionBaseQuery(collection.collectionId);\n      const root = new ProductCollection(query);\n      await root.init(filters, !!user);\n      return root;\n    },\n    description: ({ description }) => {\n      try {\n        return JSON.parse(description);\n      } catch (e) {\n        // This is for backward compatibility. If the description is not a JSON string then it is a raw HTML block\n        const rowId = `r__${uuidv4()}`;\n        return [\n          {\n            size: 1,\n            id: rowId,\n            columns: [\n              {\n                id: 'c__c5d90067-c786-4324-8e24-8e30520ac3d7',\n                size: 1,\n                data: {\n                  time: 1723347125344,\n                  blocks: [\n                    {\n                      id: 'AU89ItzUa7',\n                      type: 'raw',\n                      data: {\n                        html: description\n                      }\n                    }\n                  ],\n                  version: '2.30.2'\n                }\n              }\n            ]\n          }\n        ];\n      }\n    }\n  },\n  Product: {\n    collections: async (product, _, { pool }) => {\n      const query = getCollectionsBaseQuery();\n      query\n        .leftJoin('product_collection')\n        .on(\n          'collection.collection_id',\n          '=',\n          'product_collection.collection_id'\n        );\n      query.where('product_id', '=', product.productId);\n      return (await query.execute(pool)).map((row) => camelCase(row));\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/FeaturedProduct/FeaturedProduct.graphql",
    "content": "extend type Query {\n  featuredProducts(limit: Int): [Product]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/FeaturedProduct/FeaturedProduct.resolvers.js",
    "content": "import { node, select } from '@evershop/postgres-query-builder';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Query: {\n    featuredProducts: async (root, { limit = 4 }, { pool }) => {\n      const query = select('product.product_id')\n        .select('product.sku')\n        .select('product.price')\n        .select('product_description.product_description_id')\n        .select('product_description.name')\n        .select('product_description.url_key')\n        .select('product.image')\n        .select('SUM(cart_item.qty)', 'soldQty')\n        .from('product');\n      query\n        .leftJoin('product_description')\n        .on(\n          'product.product_id',\n          '=',\n          'product_description.product_description_product_id'\n        );\n      query\n        .innerJoin('product_inventory')\n        .on(\n          'product.product_id',\n          '=',\n          'product_inventory.product_inventory_product_id'\n        );\n      query\n        .leftJoin('cart_item')\n        .on('cart_item.product_id', '=', 'product.product_id');\n      query.where('product.status', '=', 1);\n      query.andWhere('product.visibility', '=', 1);\n      if (getConfig('catalog.showOutOfStockProduct', false) === false) {\n        query\n          .andWhere('product_inventory.manage_stock', '=', false)\n          .addNode(\n            node('OR')\n              .addLeaf('AND', 'product_inventory.qty', '>', 0)\n              .addLeaf('AND', 'product_inventory.stock_availability', '=', true)\n          );\n      }\n      query.groupBy(\n        'product.product_id',\n        'product_description.product_description_id'\n      );\n      query.orderBy('SUM(cart_item.qty)', 'desc');\n      query.limit(0, parseInt(limit, 10));\n      const products = await query.execute(pool);\n      return products.map((product) => camelCase(product));\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Attribute/ProductAttribute.graphql",
    "content": "\"\"\"\nThe ProductAttributeIndex object defines the attribute index for a product.\n\"\"\"\ntype ProductAttributeIndex {\n  attributeId: ID!\n  attributeName: String!\n  attributeCode: String!\n  optionId: Int\n  optionText: String\n}\n\nextend type Product {\n  attributeIndex: [ProductAttributeIndex]\n  attributes: [Attribute]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Attribute/ProductAttribute.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { camelCase } from '../../../../../../lib/util/camelCase.js';\n\nexport default {\n  Product: {\n    attributeIndex: async (product, _, { pool, user }) => {\n      const query = select().from('product_attribute_value_index');\n      query\n        .leftJoin('attribute')\n        .on(\n          'attribute.attribute_id',\n          '=',\n          'product_attribute_value_index.attribute_id'\n        );\n      query.where(\n        'product_attribute_value_index.product_id',\n        '=',\n        product.productId\n      );\n      if (!user) {\n        query.andWhere('attribute.display_on_frontend', '=', true);\n      }\n      query.orderBy('attribute.sort_order', 'ASC');\n      const attributes = await query.execute(pool);\n      return attributes.map((a) => camelCase(a));\n    },\n    attributes: async (product, _, { pool, user }) => {\n      const valueIndex = (\n        await select()\n          .from('product_attribute_value_index')\n          .where('product_id', '=', product.productId)\n          .execute(pool)\n      ).map((row) => row.attribute_id);\n      const attributes = await select()\n        .from('attribute')\n        .where('attribute_id', 'IN', valueIndex)\n        .and('display_on_frontend', 'IN', user ? [true] : [false, true])\n        .execute(pool);\n      return attributes.map((a) => camelCase(a));\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/CustomOption/CustomOption.graphql",
    "content": "\"\"\"\nRepresents a product option\n\"\"\"\ntype Option {\n  optionId: ID!\n  optionName: String!\n  optionType: String!\n  isRequired: Boolean!\n  values: [OptionValue]\n}\n\n\"\"\"\nRepresents a product option value\n\"\"\"\ntype OptionValue {\n  valueId: ID!\n  value: String!\n  extraPrice: Price!\n}\n\nextend type Product {\n  options: [Option]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/CustomOption/CustomOption.resolvers.js",
    "content": "export default {\n  Product: {\n    options: async () => [] // TODO: To be implemented\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Image/ProductImage.graphql",
    "content": "\"\"\"\nThe `Image` type represents a Product image.\n\"\"\"\ntype Image {\n  id: ID!\n  uuid: String!\n  alt: String\n  url: String\n  listing: String # @deprecated(reason: \"Use 'Image' component instead\")\n  single: String # @deprecated(reason: \"Use 'Image' component instead\")\n  thumb: String # @deprecated(reason: \"Use 'Image' component instead\")\n  origin: String # @deprecated(reason: \"Use 'Image' component instead\")\n}\n\nextend type Product {\n  image: Image\n  gallery: [Image]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Image/ProductImage.resolvers.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { v4 as uuidv4 } from 'uuid';\n\nexport default {\n  Product: {\n    image: async (product) => {\n      return product.originImage\n        ? {\n            alt: product.name,\n            url: product.originImage,\n            uuid: uuidv4(),\n            origin: product.originImage\n          }\n        : null;\n    },\n    gallery: async (product, _, { pool }) => {\n      const gallery = await select()\n        .from('product_image')\n        .where('product_image_product_id', '=', product.productId)\n        .and('is_main', '=', false)\n        .execute(pool);\n      return gallery.map((image) => ({\n        id: image.product_image_id,\n        alt: product.name,\n        url: image.origin_image,\n        uuid: uuidv4(),\n        origin: image.origin_image\n      }));\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Inventory/Inventory.admin.graphql",
    "content": "extend type Inventory {\n  qty: Int!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Inventory/Inventory.admin.resolvers.js",
    "content": "export default {\n  Inventory: {\n    qty: (inventory) => inventory.qty || 0\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Inventory/Inventory.graphql",
    "content": "\"\"\"\nThe `Inventory` type represents a product's inventory information.\n\"\"\"\ntype Inventory {\n  isInStock: Boolean!\n  stockAvailability: Int!\n  manageStock: Int!\n}\n\nextend type Product {\n  inventory: Inventory!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Inventory/Inventory.resolvers.js",
    "content": "export default {\n  Product: {\n    inventory: async (product) => ({\n      ...product,\n      qty: parseInt(product.qty, 10),\n      isInStock:\n        (parseInt(product.qty, 10) > 0 && product.stockAvailability === true) ||\n        product.manageStock === false\n    })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Price/ProductPrice.graphql",
    "content": "\"\"\"\nRepresents a price for a product.\n\"\"\"\ntype ProductPrice {\n  regular: Price!\n  special: Price!\n}\n\nextend type Product {\n  price: ProductPrice!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Price/ProductPrice.resolvers.js",
    "content": "export default {\n  Product: {\n    price: (product) => {\n      const price = parseFloat(product.price);\n      return {\n        regular: price,\n        special: price // TODO: implement special price\n      };\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Product.admin.graphql",
    "content": "extend type Product {\n  editUrl: String\n  updateApi: String!\n  deleteApi: String!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Product.admin.resolvers.ts",
    "content": "import { buildUrl } from '../../../../../lib/router/buildUrl.js';\n\nexport default {\n  Product: {\n    editUrl: (product) => buildUrl('productEdit', { id: product.uuid }),\n    updateApi: (product) => buildUrl('updateProduct', { id: product.uuid }),\n    deleteApi: (product) => buildUrl('deleteProduct', { id: product.uuid })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Product.graphql",
    "content": "\"\"\"\nRepresents a product.\n\"\"\"\ntype Product {\n  productId: Int!\n  uuid: String!\n  name: String!\n  status: Int!\n  sku: String!\n  weight: Weight!\n  noShippingRequired: Boolean\n  taxClass: Int\n  description: JSON\n  urlKey: String\n  metaTitle: String\n  metaDescription: String\n  metaKeywords: String\n  variantGroupId: ID\n  visibility: Int\n  groupId: ID\n  url: String\n}\n\n\"\"\"\nReturns a collection of products.\n\"\"\"\ntype ProductCollection {\n  items: [Product]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\n\"\"\"\nReturns a search.\n\"\"\"\ntype ProductSearch {\n  products: ProductCollection\n  keyword: String\n}\n\nextend type Query {\n  product(id: ID): Product\n  currentProduct: Product\n  products(filters: [FilterInput]): ProductCollection\n  productSearch: ProductSearch\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Product.resolvers.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport sanitizeHtml from 'sanitize-html';\nimport { v4 as uuidv4 } from 'uuid';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { getProductsBaseQuery } from '../../../services/getProductsBaseQuery.js';\nimport { ProductCollection } from '../../../services/ProductCollection.js';\n\nexport default {\n  Product: {\n    url: async (product, _, { pool }) => {\n      // Get the url rewrite for this product\n      const urlRewrite = await select()\n        .from('url_rewrite')\n        .where('entity_uuid', '=', product.uuid)\n        .and('entity_type', '=', 'product')\n        .load(pool);\n      if (!urlRewrite) {\n        return buildUrl('productView', { uuid: product.uuid });\n      } else {\n        return urlRewrite.request_path;\n      }\n    },\n    description: ({ description }) => {\n      if (!description) {\n        return [];\n      }\n      try {\n        const json = JSON.parse(description);\n        return json;\n      } catch (e) {\n        // This is for backward compatibility. If the description is not a JSON string then it is a raw HTML block\n        const rowId = `r__${uuidv4()}`;\n        return [\n          {\n            size: 1,\n            id: rowId,\n            columns: [\n              {\n                id: 'c__c5d90067-c786-4324-8e24-8e30520ac3d7',\n                size: 1,\n                data: {\n                  time: 1723347125344,\n                  blocks: [\n                    {\n                      id: 'AU89ItzUa7',\n                      type: 'raw',\n                      data: {\n                        html: description\n                      }\n                    }\n                  ],\n                  version: '2.30.2'\n                }\n              }\n            ]\n          }\n        ];\n      }\n    }\n  },\n  Query: {\n    product: async (_, { id }, { pool }) => {\n      const query = getProductsBaseQuery();\n      query.where('product.product_id', '=', id);\n      const result = await query.load(pool);\n      if (!result) {\n        return null;\n      } else {\n        return camelCase(result);\n      }\n    },\n    currentProduct: async (\n      _,\n      args,\n      { currentRoute, currentProductId, pool }\n    ) => {\n      if (currentRoute.id !== 'productView') {\n        return null;\n      }\n      const query = getProductsBaseQuery();\n      query.where('product.product_id', '=', currentProductId);\n      const product = await query.load(pool);\n      if (!product) {\n        return null;\n      } else {\n        return camelCase(product);\n      }\n    },\n    products: async (_, { filters = [] }, { user }) => {\n      const query = getProductsBaseQuery();\n      const root = new ProductCollection(query);\n      await root.init(filters, !!user);\n      return root;\n    },\n    productSearch: async (_, args, { currentUrl, currentRoute }) => {\n      if (currentRoute?.id !== 'catalogSearch') {\n        return null;\n      }\n      // Parse the keyword from the url\n      const url = new URL(currentUrl);\n      const keyword = url.searchParams.get('keyword') || undefined;\n      const query = getProductsBaseQuery();\n      const filtersFromUrl = buildFilterFromUrl(currentUrl);\n      const root = new ProductCollection(query);\n      await root.init(filtersFromUrl, false);\n      return {\n        products: root,\n        keyword: keyword\n      };\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Variant/Variant.graphql",
    "content": "\"\"\"\nRepresents a product variant attribute\n\"\"\"\ntype VariantAttribute {\n  attributeId: Int!\n  attributeCode: String!\n  attributeName: String!\n  options: [VariantAttributeOption]\n}\n\n\"\"\"\nRepresents a product variant attribute option\n\"\"\"\ntype VariantAttributeOption {\n  optionId: Int!\n  optionText: String!\n  productId: Int\n}\n\n\"\"\"\nRepresents a product variant attribute index\n\"\"\"\ntype VariantAttributeIndex {\n  attributeId: ID!\n  attributeCode: String!\n  optionId: Int\n  optionText: String\n}\n\n\"\"\"\nRepresents a product variant\n\"\"\"\ntype Variant {\n  id: String!\n  product: Product!\n  attributes: [VariantAttributeIndex]!\n  removeUrl: String!\n}\n\n\"\"\"\nRepresents a product variant group\n\"\"\"\ntype VariantGroup {\n  variantGroupId: Int!\n  variantAttributes: [VariantAttribute]!\n  items: [Variant]\n  addItemApi: String!\n}\n\nextend type Product {\n  variantGroup: VariantGroup\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Product/Variant/Variant.resolvers.js",
    "content": "import { select, node } from '@evershop/postgres-query-builder';\nimport uniqid from 'uniqid';\nimport { buildUrl } from '../../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../../lib/util/camelCase.js';\nimport { getConfig } from '../../../../../../lib/util/getConfig.js';\nimport { getProductsBaseQuery } from '../../../../services/getProductsBaseQuery.js';\n\nexport default {\n  Product: {\n    variantGroup: async (product, _, { pool, user }) => {\n      const { variantGroupId } = product;\n      if (!variantGroupId) {\n        return null;\n      } else {\n        const group = await select()\n          .from('variant_group')\n          .select('uuid')\n          .select('attribute_one')\n          .select('attribute_two')\n          .select('attribute_three')\n          .select('attribute_four')\n          .select('attribute_five')\n          .where('variant_group_id', '=', variantGroupId)\n          .load(pool);\n\n        const query = select();\n        query\n          .from('product')\n          .select('product.product_id')\n          .select('attribute.attribute_id')\n          .select('attribute.attribute_code')\n          .select('attribute.attribute_name')\n          .select('product_attribute_value_index.option_id')\n          .select('product_attribute_value_index.option_text');\n\n        query\n          .leftJoin('product_attribute_value_index')\n          .on(\n            'product.product_id',\n            '=',\n            'product_attribute_value_index.product_id'\n          );\n        query\n          .innerJoin('product_inventory')\n          .on(\n            'product.product_id',\n            '=',\n            'product_inventory.product_inventory_product_id'\n          );\n        query\n          .leftJoin('attribute')\n          .on(\n            'product_attribute_value_index.attribute_id',\n            '=',\n            'attribute.attribute_id'\n          );\n\n        if (!user && getConfig('catalog.showOutOfStockProduct') === false) {\n          query\n            .andWhere('product_inventory.manage_stock', '=', false)\n            .addNode(\n              node('OR')\n                .addLeaf('AND', 'product_inventory.qty', '>', 0)\n                .addLeaf(\n                  'AND',\n                  'product_inventory.stock_availability',\n                  '=',\n                  true\n                )\n            );\n        }\n\n        query.andWhere('variant_group_id', '=', variantGroupId);\n        query.andWhere(\n          'product_attribute_value_index.attribute_id',\n          'IN',\n          Object.values(group).filter((v) => Number.isInteger(v))\n        );\n        if (!user) {\n          query.andWhere('status', '=', 1);\n        }\n        query.orderBy('product_attribute_value_index.option_id', 'ASC');\n        const vs = await query.execute(pool);\n        const attributes = await select()\n          .from('attribute')\n          .where(\n            'attribute_id',\n            'IN',\n            Object.values(group).filter((v) => Number.isInteger(v))\n          )\n          .execute(pool);\n\n        return {\n          variantGroupId,\n          variantAttributes: attributes.map((a) => {\n            // We need to get all the options available from the variants list\n            const options = vs\n              .filter((v) => v.attribute_id === a.attribute_id)\n              .map((v) => ({\n                optionId: v.option_id,\n                optionText: v.option_text,\n                productId: v.product_id\n              }));\n            return {\n              attributeId: a.attribute_id,\n              attributeCode: a.attribute_code,\n              attributeName: a.attribute_name,\n              options\n            };\n          }),\n          items: () =>\n            vs\n              .reduce((acc, v) => {\n                const product = acc.find((p) => p.product_id === v.product_id);\n                if (!product) {\n                  acc.push({\n                    product_id: v.product_id,\n                    attributes: [\n                      {\n                        attributeId: v.attribute_id,\n                        attributeCode: v.attribute_code,\n                        optionId: v.option_id,\n                        optionText: v.option_text\n                      }\n                    ]\n                  });\n                } else {\n                  product.attributes.push({\n                    attributeId: v.attribute_id,\n                    attributeCode: v.attribute_code,\n                    optionId: v.option_id,\n                    optionText: v.option_text\n                  });\n                }\n                return acc;\n              }, [])\n              .map((p) => {\n                const productAttributes = p.attributes.map(\n                  (a) => a.attributeCode\n                );\n                const missingAttributes = attributes\n                  .filter((a) => !productAttributes.includes(a.attribute_code))\n                  .map((a) => ({\n                    attributeId: a.attribute_id,\n                    attributeCode: a.attribute_code,\n                    optionId: null,\n                    optionText: null\n                  }));\n                return {\n                  productId: p.product_id,\n                  id: `id-${uniqid()}`,\n                  attributes: [...p.attributes, ...missingAttributes].filter(\n                    (a) => a.attributeCode\n                  )\n                };\n              }),\n          addItemApi: buildUrl('addVariantItem', { id: group.uuid })\n        };\n      }\n    }\n  },\n  Variant: {\n    product: async ({ productId }, _, { pool }) => {\n      const query = getProductsBaseQuery();\n      query.where('product_id', '=', productId);\n      const result = await query.load(pool);\n      if (!result) {\n        return null;\n      } else {\n        return camelCase(result);\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Widget/CollectionProductsWidget/CollectionProductsWidget.graphql",
    "content": "\"\"\"\nReturn a collection products Widget\n\"\"\"\ntype CollectionProductsWidget {\n  collection: String\n  count: Int\n  countPerRow: Int\n}\n\nextend type Query {\n  collectionProductsWidget(\n    collection: String\n    count: Int\n    countPerRow: Int\n  ): CollectionProductsWidget\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/graphql/types/Widget/CollectionProductsWidget/CollectionProductsWidget.resolvers.js",
    "content": "export default {\n  Query: {\n    collectionProductsWidget: async (\n      root,\n      { collection, count, countPerRow }\n    ) => ({\n      collection,\n      count: count ? parseInt(count, 10) : 5,\n      countPerRow: countPerRow ? parseInt(countPerRow, 10) : 4\n    })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/migration/Version-1.0.0.js",
    "content": "import { execute, insert } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE \"attribute\" (\n  \"attribute_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"attribute_code\" varchar NOT NULL,\n  \"attribute_name\" varchar NOT NULL,\n  \"type\" varchar NOT NULL,\n  \"is_required\" boolean NOT NULL DEFAULT FALSE,\n  \"display_on_frontend\" boolean NOT NULL DEFAULT FALSE,\n  \"sort_order\" INT NOT NULL DEFAULT 0,\n  \"is_filterable\" boolean NOT NULL DEFAULT FALSE,\n  CONSTRAINT \"ATTRIBUTE_CODE_UNIQUE\" UNIQUE (\"attribute_code\"),\n  CONSTRAINT \"ATTRIBUTE_CODE_UUID_UNIQUE\" UNIQUE (\"uuid\")\n)`\n  );\n\n  const color = await insert('attribute')\n    .given({\n      attribute_code: 'color',\n      attribute_name: 'Color',\n      type: 'select',\n      is_required: 0,\n      display_on_frontend: 1,\n      is_filterable: 1\n    })\n    .execute(connection);\n  const size = await insert('attribute')\n    .given({\n      attribute_code: 'size',\n      attribute_name: 'Size',\n      type: 'select',\n      is_required: 0,\n      display_on_frontend: 1,\n      is_filterable: 1\n    })\n    .execute(connection);\n  await execute(\n    connection,\n    `CREATE TABLE \"attribute_option\" (\n  \"attribute_option_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"attribute_id\" INT NOT NULL,\n  \"attribute_code\" varchar NOT NULL,\n  \"option_text\" varchar NOT NULL,\n  CONSTRAINT \"ATTRIBUTE_OPTION_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_ATTRIBUTE_OPTION\" FOREIGN KEY (\"attribute_id\") REFERENCES \"attribute\" (\"attribute_id\") ON DELETE CASCADE\n)`\n  );\n\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ATTRIBUTE_OPTION\" ON \"attribute_option\" (\"attribute_id\")`\n  );\n\n  await insert('attribute_option')\n    .given({\n      attribute_id: color.insertId,\n      attribute_code: 'color',\n      option_text: 'White'\n    })\n    .execute(connection);\n\n  await insert('attribute_option')\n    .given({\n      attribute_id: color.insertId,\n      attribute_code: 'color',\n      option_text: 'Black'\n    })\n    .execute(connection);\n\n  await insert('attribute_option')\n    .given({\n      attribute_id: color.insertId,\n      attribute_code: 'color',\n      option_text: 'Yellow'\n    })\n    .execute(connection);\n\n  await insert('attribute_option')\n    .given({\n      attribute_id: size.insertId,\n      attribute_code: 'size',\n      option_text: 'XXL'\n    })\n    .execute(connection);\n\n  await insert('attribute_option')\n    .given({\n      attribute_id: size.insertId,\n      attribute_code: 'size',\n      option_text: 'XL'\n    })\n    .execute(connection);\n\n  await insert('attribute_option')\n    .given({\n      attribute_id: size.insertId,\n      attribute_code: 'size',\n      option_text: 'SM'\n    })\n    .execute(connection);\n\n  await execute(\n    connection,\n    `CREATE TABLE \"attribute_group\" (\n  \"attribute_group_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"group_name\" text NOT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"ATTRIBUTE_GROUP_UUID_UNIQUE\" UNIQUE (\"uuid\")\n)`\n  );\n\n  const defaultGroup = await insert('attribute_group')\n    .given({ group_name: 'Default' })\n    .execute(connection);\n\n  await execute(\n    connection,\n    `CREATE TABLE \"attribute_group_link\" (\n  \"attribute_group_link_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"attribute_id\" INT NOT NULL,\n  \"group_id\" INT NOT NULL,\n  CONSTRAINT \"ATTRIBUTE_GROUP_LINK_UNIQUE\" UNIQUE (\"attribute_id\",\"group_id\"),\n  CONSTRAINT \"FK_ATTRIBUTE_LINK\" FOREIGN KEY (\"attribute_id\") REFERENCES \"attribute\" (\"attribute_id\") ON DELETE CASCADE ON UPDATE CASCADE,\n  CONSTRAINT \"FK_GROUP_LINK\" FOREIGN KEY (\"group_id\") REFERENCES \"attribute_group\" (\"attribute_group_id\") ON DELETE CASCADE\n)`\n  );\n\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_GROUP_LINK\" ON \"attribute_group_link\" (\"group_id\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ATTRIBUTE_LINK\" ON \"attribute_group_link\" (\"attribute_id\")`\n  );\n\n  await insert('attribute_group_link')\n    .given({\n      group_id: defaultGroup.insertId,\n      attribute_id: color.insertId\n    })\n    .execute(connection);\n  await insert('attribute_group_link')\n    .given({\n      group_id: defaultGroup.insertId,\n      attribute_id: size.insertId\n    })\n    .execute(connection);\n\n  await execute(\n    connection,\n    `CREATE TABLE \"variant_group\" (\n  \"variant_group_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"attribute_group_id\" INT NOT NULL,\n  \"attribute_one\" INT DEFAULT NULL,\n  \"attribute_two\" INT DEFAULT NULL,\n  \"attribute_three\" INT DEFAULT NULL,\n  \"attribute_four\" INT DEFAULT NULL,\n  \"attribute_five\" INT DEFAULT NULL,\n  \"visibility\" boolean NOT NULL DEFAULT FALSE,\n  CONSTRAINT \"VARIANT_GROUP_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_ATTRIBUTE_GROUP_VARIANT\" FOREIGN KEY (\"attribute_group_id\") REFERENCES \"attribute_group\" (\"attribute_group_id\") ON DELETE CASCADE ON UPDATE NO ACTION,\n  CONSTRAINT \"FK_ATTRIBUTE_VARIANT_FIVE\" FOREIGN KEY (\"attribute_five\") REFERENCES \"attribute\" (\"attribute_id\") ON DELETE CASCADE ON UPDATE NO ACTION,\n  CONSTRAINT \"FK_ATTRIBUTE_VARIANT_FOUR\" FOREIGN KEY (\"attribute_four\") REFERENCES \"attribute\" (\"attribute_id\") ON DELETE CASCADE ON UPDATE NO ACTION,\n  CONSTRAINT \"FK_ATTRIBUTE_VARIANT_ONE\" FOREIGN KEY (\"attribute_one\") REFERENCES \"attribute\" (\"attribute_id\") ON DELETE CASCADE ON UPDATE NO ACTION,\n  CONSTRAINT \"FK_ATTRIBUTE_VARIANT_THREE\" FOREIGN KEY (\"attribute_three\") REFERENCES \"attribute\" (\"attribute_id\") ON DELETE CASCADE ON UPDATE NO ACTION,\n  CONSTRAINT \"FK_ATTRIBUTE_VARIANT_TWO\" FOREIGN KEY (\"attribute_two\") REFERENCES \"attribute\" (\"attribute_id\") ON DELETE CASCADE ON UPDATE NO ACTION\n)`\n  );\n\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ATTRIBUTE_VARIANT_ONE\" ON \"variant_group\" (\"attribute_one\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ATTRIBUTE_VARIANT_TWO\" ON \"variant_group\" (\"attribute_two\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ATTRIBUTE_VARIANT_THREE\" ON \"variant_group\" (\"attribute_three\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ATTRIBUTE_VARIANT_FOUR\" ON \"variant_group\" (\"attribute_four\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ATTRIBUTE_VARIANT_FIVE\" ON \"variant_group\" (\"attribute_five\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ATTRIBUTE_GROUP_VARIANT\" ON \"variant_group\" (\"attribute_group_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"product\" (\n  \"product_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"type\" varchar NOT NULL DEFAULT 'simple',\n  \"variant_group_id\" INT DEFAULT NULL,\n  \"visibility\" boolean NOT NULL DEFAULT TRUE,\n  \"group_id\" INT DEFAULT 1,\n  \"image\" varchar DEFAULT NULL,\n  \"sku\" varchar NOT NULL,\n  \"price\" decimal(12,4) NOT NULL,\n  \"qty\" INT NOT NULL,\n  \"weight\" decimal(12,4) DEFAULT NULL,\n  \"manage_stock\" boolean NOT NULL,\n  \"stock_availability\" boolean NOT NULL,\n  \"tax_class\" smallint DEFAULT NULL,\n  \"status\" boolean NOT NULL DEFAULT FALSE,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"PRODUCT_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"PRODUCT_SKU_UNIQUE\" UNIQUE (\"sku\"),\n  CONSTRAINT \"UNSIGNED_PRICE\" CHECK(price >= 0),\n  CONSTRAINT \"UNSIGNED_WEIGHT\" CHECK(weight >= 0),\n  CONSTRAINT \"FK_PRODUCT_ATTRIBUTE_GROUP\" FOREIGN KEY (\"group_id\") REFERENCES \"attribute_group\" (\"attribute_group_id\") ON DELETE SET NULL,\n  CONSTRAINT \"FK_PRODUCT_VARIANT_GROUP\" FOREIGN KEY (\"variant_group_id\") REFERENCES \"variant_group\" (\"variant_group_id\") ON DELETE SET NULL\n)`\n  );\n\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_PRODUCT_ATTRIBUTE_GROUP\" ON \"product\" (\"group_id\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_PRODUCT_VARIANT_GROUP\" ON \"product\" (\"variant_group_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"product_attribute_value_index\" (\n  \"product_attribute_value_index_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"product_id\" INT NOT NULL,\n  \"attribute_id\" INT NOT NULL,\n  \"option_id\" INT DEFAULT NULL,\n  \"option_text\" text DEFAULT NULL,\n  CONSTRAINT \"PRODUCT_ATTRIBUTE_VALUE_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"OPTION_VALUE_UNIQUE\" UNIQUE (\"product_id\",\"attribute_id\",\"option_id\"),\n  CONSTRAINT \"FK_ATTRIBUTE_OPTION_VALUE_LINK\" FOREIGN KEY (\"option_id\") REFERENCES \"attribute_option\" (\"attribute_option_id\") ON DELETE CASCADE,\n  CONSTRAINT \"FK_ATTRIBUTE_VALUE_LINK\" FOREIGN KEY (\"attribute_id\") REFERENCES \"attribute\" (\"attribute_id\") ON DELETE CASCADE,\n  CONSTRAINT \"FK_PRODUCT_ATTRIBUTE_LINK\" FOREIGN KEY (\"product_id\") REFERENCES \"product\" (\"product_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ATTRIBUTE_VALUE_LINK\" ON \"product_attribute_value_index\" (\"attribute_id\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ATTRIBUTE_OPTION_VALUE_LINK\" ON \"product_attribute_value_index\" (\"option_id\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_PRODUCT_ATTRIBUTE_LINK\" ON \"product_attribute_value_index\" (\"product_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"product_custom_option\" (\n  \"product_custom_option_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"product_custom_option_product_id\" INT NOT NULL,\n  \"option_name\" varchar NOT NULL,\n  \"option_type\" varchar NOT NULL,\n  \"is_required\" boolean NOT NULL DEFAULT FALSE,\n  \"sort_order\" INT DEFAULT NULL,\n  CONSTRAINT \"PRODUCT_CUSTOM_OPTION_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_PRODUCT_CUSTOM_OPTION\" FOREIGN KEY (\"product_custom_option_product_id\") REFERENCES \"product\" (\"product_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_PRODUCT_CUSTOM_OPTION\" ON \"product_custom_option\" (\"product_custom_option_product_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"product_custom_option_value\" (\n  \"product_custom_option_value_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"option_id\" INT NOT NULL,\n  \"extra_price\" decimal(12,4) DEFAULT NULL,\n  \"sort_order\" INT DEFAULT NULL,\n  \"value\" varchar NOT NULL,\n  CONSTRAINT \"PRODUCT_CUSTOM_OPTION_VALUE_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_CUSTOM_OPTION_VALUE\" FOREIGN KEY (\"option_id\") REFERENCES \"product_custom_option\" (\"product_custom_option_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_CUSTOM_OPTION_VALUE\" ON \"product_custom_option_value\" (\"option_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"product_description\" (\n  \"product_description_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"product_description_product_id\" INT NOT NULL,\n  \"name\" varchar NOT NULL,\n  \"description\" text DEFAULT NULL,\n  \"short_description\" text DEFAULT NULL,\n  \"url_key\" varchar NOT NULL,\n  \"meta_title\" text DEFAULT NULL,\n  \"meta_description\" text DEFAULT NULL,\n  \"meta_keywords\" text DEFAULT NULL,\n  CONSTRAINT \"PRODUCT_ID_UNIQUE\" UNIQUE (\"product_description_product_id\"),\n  CONSTRAINT \"PRODUCT_URL_KEY_UNIQUE\" UNIQUE (\"url_key\"),\n  CONSTRAINT \"FK_PRODUCT_DESCRIPTION\" FOREIGN KEY (\"product_description_product_id\") REFERENCES \"product\" (\"product_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_PRODUCT_DESCRIPTION\" ON \"product_description\" (\"product_description_product_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"product_image\" (\n  \"product_image_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"product_image_product_id\" INT NOT NULL,\n  \"image\" varchar NOT NULL,\n  CONSTRAINT \"PRODUCT_IMAGE_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_PRODUCT_IMAGE_LINK\" FOREIGN KEY (\"product_image_product_id\") REFERENCES \"product\" (\"product_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_PRODUCT_IMAGE_LINK\" ON \"product_image\" (\"product_image_product_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"category\" (\n  \"category_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"status\" boolean NOT NULL,\n  \"parent_id\" INT DEFAULT NULL,\n  \"include_in_nav\" boolean NOT NULL,\n  \"position\" smallint DEFAULT NULL,\n  \"show_products\" boolean DEFAULT TRUE,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n   CONSTRAINT \"CATEGORY_UUID_UNIQUE\" UNIQUE (\"uuid\")\n)`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"product_category\" (\n  \"product_category_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"category_id\" INT NOT NULL,\n  \"product_id\" INT NOT NULL,\n  CONSTRAINT \"PRODUCT_CATEGORY_UNIQUE\" UNIQUE (\"category_id\",\"product_id\"),\n  CONSTRAINT \"FK_CATEGORY_PRODUCT_LINK\" FOREIGN KEY (\"category_id\") REFERENCES \"category\" (\"category_id\") ON DELETE CASCADE,\n  CONSTRAINT \"FK_PRODUCT_CATEGORY_LINK\" FOREIGN KEY (\"product_id\") REFERENCES \"product\" (\"product_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_CATEGORY_PRODUCT_LINK\" ON \"product_category\" (\"category_id\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_PRODUCT_CATEGORY_LINK\" ON \"product_category\" (\"product_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"category_description\" (\n  \"category_description_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"category_description_category_id\" INT NOT NULL,\n  \"name\" varchar NOT NULL,\n  \"short_description\" text DEFAULT NULL,\n  \"description\" text DEFAULT NULL,\n  \"image\" varchar DEFAULT NULL,\n  \"meta_title\" text DEFAULT NULL,\n  \"meta_keywords\" text DEFAULT NULL,\n  \"meta_description\" text DEFAULT NULL,\n  \"url_key\" varchar NOT NULL,\n  CONSTRAINT \"CATEGORY_ID_UNIQUE\" UNIQUE (\"category_description_category_id\"),\n  CONSTRAINT \"CATEGORY_URL_KEY_UNIQUE\" UNIQUE (\"url_key\"),\n  CONSTRAINT \"FK_CATEGORY_DESCRIPTION\" FOREIGN KEY (\"category_description_category_id\") REFERENCES \"category\" (\"category_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_CATEGORY_DESCRIPTION\" ON \"category_description\" (\"category_description_category_id\")`\n  );\n\n  // Create 3 default categories, Kids, Men, Women\n  const kids = await insert('category')\n    .given({\n      status: 1,\n      include_in_nav: 1\n    })\n    .execute(connection);\n\n  await insert('category_description')\n    .given({\n      category_description_category_id: kids.insertId,\n      name: 'Kids',\n      url_key: 'kids',\n      meta_title: 'Kids',\n      meta_description: 'Kids',\n      meta_keywords: 'Kids',\n      description: 'Kids'\n    })\n    .execute(connection);\n\n  const women = await insert('category')\n    .given({\n      status: 1,\n      include_in_nav: 1\n    })\n    .execute(connection);\n\n  await insert('category_description')\n    .given({\n      category_description_category_id: women.insertId,\n      name: 'Women',\n      url_key: 'women',\n      meta_title: 'Women',\n      meta_description: 'Women',\n      meta_keywords: 'Women',\n      description: 'Women'\n    })\n    .execute(connection);\n\n  const men = await insert('category')\n    .given({\n      status: 1,\n      include_in_nav: 1\n    })\n    .execute(connection);\n\n  await insert('category_description')\n    .given({\n      category_description_category_id: men.insertId,\n      name: 'Men',\n      url_key: 'men',\n      meta_title: 'Men',\n      meta_description: 'Men',\n      meta_keywords: 'Men',\n      description: 'Men'\n    })\n    .execute(connection);\n\n  // COLLECTION\n  await execute(\n    connection,\n    `CREATE TABLE \"collection\" (\n  \"collection_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"name\" varchar NOT NULL,\n  \"description\" text DEFAULT NULL,\n  \"code\" varchar NOT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"COLLECTION_CODE_UNIQUE\" UNIQUE (\"code\"),\n  CONSTRAINT \"COLLECTION_UUID_UNIQUE\" UNIQUE (\"uuid\")\n)`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"product_collection\" (\n  \"product_collection_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"collection_id\" INT NOT NULL,\n  \"product_id\" INT NOT NULL,\n  CONSTRAINT \"PRODUCT_COLLECTION_UNIQUE\" UNIQUE (\"collection_id\",\"product_id\"),\n  CONSTRAINT \"FK_COLLECTION_PRODUCT_LINK\" FOREIGN KEY (\"collection_id\") REFERENCES \"collection\" (\"collection_id\") ON DELETE CASCADE,\n  CONSTRAINT \"FK_PRODUCT_COLLECTION_LINK\" FOREIGN KEY (\"product_id\") REFERENCES \"product\" (\"product_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_COLLECTION_PRODUCT_LINK\" ON \"product_collection\" (\"collection_id\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_PRODUCT_COLLECTION_LINK\" ON \"product_collection\" (\"product_id\")`\n  );\n\n  /* CREATE SOME TRIGGERS */\n  // Prevent deleting a default attribute group\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION prevent_delete_default_attribute_group()\n        RETURNS TRIGGER\n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        IF OLD.attribute_group_id = 1 THEN\n          RAISE EXCEPTION 'Cannot delete default attribute group';\n        END IF;\n        RETURN OLD;\n      END;\n      $$`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"PREVENT_DELETING_THE_DEFAULT_ATTRIBUTE_GROUP\"\n        BEFORE DELETE ON attribute_group\n        FOR EACH ROW\n        EXECUTE PROCEDURE prevent_delete_default_attribute_group();`\n  );\n\n  // Prevent changing product attribute group if product has variants\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION prevent_change_attribute_group()\n        RETURNS TRIGGER\n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        IF OLD.group_id != NEW.group_id AND OLD.variant_group_id IS NOT NULL THEN\n          RAISE EXCEPTION 'Cannot change attribute group of product with variants';\n        END IF;\n        RETURN NEW;\n      END;\n      $$`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"PREVENT_CHANGING_ATTRIBUTE_GROUP_OF_PRODUCT_WITH_VARIANTS\"\n        BEFORE UPDATE ON product\n        FOR EACH ROW\n        EXECUTE PROCEDURE prevent_change_attribute_group();`\n  );\n\n  //  Delete product attribute value and variant group when attribute is removed from group\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION remove_attribute_from_group()\n        RETURNS TRIGGER \n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        DELETE FROM product_attribute_value_index WHERE product_attribute_value_index.attribute_id = OLD.attribute_id AND product_attribute_value_index.product_id IN (SELECT product.product_id FROM product WHERE product.group_id = OLD.group_id);\n        DELETE FROM variant_group WHERE variant_group.attribute_group_id = OLD.group_id AND (variant_group.attribute_one = OLD.attribute_id OR variant_group.attribute_two = OLD.attribute_id OR variant_group.attribute_three = OLD.attribute_id OR variant_group.attribute_four = OLD.attribute_id OR variant_group.attribute_five = OLD.attribute_id);\n        RETURN OLD;\n      END;\n      $$;`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"TRIGGER_AFTER_REMOVE_ATTRIBUTE_FROM_GROUP\" AFTER DELETE ON \"attribute_group_link\"\n     FOR EACH ROW \n     EXECUTE PROCEDURE remove_attribute_from_group();\n    `\n  );\n\n  //  Update product attribute value option text when option is updated\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION update_product_attribute_option_value_text()\n        RETURNS TRIGGER \n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        UPDATE \"product_attribute_value_index\" SET \"option_text\" = NEW.option_text\n        WHERE \"product_attribute_value_index\".option_id = NEW.attribute_option_id AND \"product_attribute_value_index\".attribute_id = NEW.attribute_id;\n        RETURN NEW;\n      END;\n      $$;`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"TRIGGER_AFTER_ATTRIBUTE_OPTION_UPDATE\" AFTER UPDATE ON \"attribute_option\" FOR EACH ROW\n    EXECUTE PROCEDURE update_product_attribute_option_value_text();\n    `\n  );\n\n  //  Delete product attribute value index after option is deleted\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION delete_product_attribute_value_index()\n        RETURNS TRIGGER \n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        DELETE FROM \"product_attribute_value_index\" WHERE \"product_attribute_value_index\".option_id = OLD.attribute_option_id AND \"product_attribute_value_index\".\"attribute_id\" = OLD.attribute_id;\n        RETURN OLD;\n      END;\n      $$;`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"TRIGGER_AFTER_DELETE_ATTRIBUTE_OPTION\" AFTER DELETE ON \"attribute_option\" FOR EACH ROW\n    EXECUTE PROCEDURE delete_product_attribute_value_index();\n    `\n  );\n\n  // Update variant group visibility after new product is added\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION update_variant_group_visibility()\n        RETURNS TRIGGER \n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        UPDATE \"variant_group\" SET visibility = (SELECT bool_or(visibility) FROM \"product\" WHERE \"product\".\"variant_group_id\" = NEW.variant_group_id AND \"product\".\"status\" = TRUE) WHERE \"variant_group\".\"variant_group_id\" = NEW.variant_group_id;\n        RETURN NEW;\n      END;\n      $$;`\n  );\n  await execute(\n    connection,\n    `CREATE CONSTRAINT TRIGGER \"TRIGGER_AFTER_INSERT_PRODUCT\" AFTER INSERT ON \"product\" \n    DEFERRABLE INITIALLY IMMEDIATE\n    FOR EACH ROW\n    EXECUTE PROCEDURE update_variant_group_visibility();`\n  );\n\n  // Update product attribute index, variant group visibility and product visibility after product is updated\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION update_attribute_index_and_variant_group_visibility()\n        RETURNS TRIGGER \n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        DELETE FROM \"product_attribute_value_index\"\n        WHERE \"product_attribute_value_index\".\"product_id\" = NEW.product_id \n        AND \"product_attribute_value_index\".\"attribute_id\" NOT IN (SELECT \"attribute_group_link\".\"attribute_id\" FROM \"attribute_group_link\" WHERE \"attribute_group_link\".\"group_id\" = NEW.group_id);\n        UPDATE \"variant_group\" SET visibility = (SELECT bool_or(visibility) FROM \"product\" WHERE \"product\".\"variant_group_id\" = NEW.variant_group_id AND \"product\".\"status\" = TRUE GROUP BY \"product\".\"variant_group_id\") WHERE \"variant_group\".\"variant_group_id\" = NEW.variant_group_id;\n        RETURN NEW;\n      END;\n      $$;`\n  );\n  await execute(\n    connection,\n    `CREATE CONSTRAINT TRIGGER \"TRIGGER_PRODUCT_AFTER_UPDATE\" AFTER UPDATE ON \"product\"\n    DEFERRABLE INITIALLY DEFERRED\n    FOR EACH ROW\n    EXECUTE PROCEDURE update_attribute_index_and_variant_group_visibility();\n    `\n  );\n\n  // Delete variant group when attribute type is changed from select to something else\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION delete_variant_group_after_attribute_type_changed()\n        RETURNS TRIGGER \n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        IF (OLD.type = 'select' AND NEW.type <> 'select') THEN\n          DELETE FROM \"variant_group\" WHERE (\"variant_group\".attribute_one = OLD.attribute_id OR \"variant_group\".attribute_two = OLD.attribute_id OR \"variant_group\".attribute_three = OLD.attribute_id OR \"variant_group\".attribute_four = OLD.attribute_id OR \"variant_group\".attribute_five = OLD.attribute_id);\n        END IF;\n        RETURN NEW;\n      END\n      $$;`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"TRIGGER_AFTER_UPDATE_ATTRIBUTE\" AFTER UPDATE ON \"attribute\" FOR EACH ROW\n    EXECUTE PROCEDURE delete_variant_group_after_attribute_type_changed();\n    `\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/migration/Version-1.0.1.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `DROP TRIGGER IF EXISTS \"TRIGGER_PRODUCT_AFTER_UPDATE\" ON \"product\";`\n  );\n\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION update_attribute_index_and_variant_group_visibility()\n        RETURNS TRIGGER \n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        DELETE FROM \"product_attribute_value_index\"\n        WHERE \"product_attribute_value_index\".\"product_id\" = NEW.product_id \n        AND \"product_attribute_value_index\".\"attribute_id\" NOT IN (SELECT \"attribute_group_link\".\"attribute_id\" FROM \"attribute_group_link\" WHERE \"attribute_group_link\".\"group_id\" = NEW.group_id);\n        UPDATE \"variant_group\" SET visibility = COALESCE((SELECT bool_or(visibility) FROM \"product\" WHERE \"product\".\"variant_group_id\" = NEW.variant_group_id AND \"product\".\"status\" = TRUE GROUP BY \"product\".\"variant_group_id\"), FALSE) WHERE \"variant_group\".\"variant_group_id\" = NEW.variant_group_id;\n        RETURN NEW;\n      END;\n      $$;\n      `\n  );\n\n  await execute(\n    connection,\n    `CREATE CONSTRAINT TRIGGER \"TRIGGER_PRODUCT_AFTER_UPDATE\" AFTER UPDATE ON \"product\"\n    DEFERRABLE INITIALLY DEFERRED\n    FOR EACH ROW\n    EXECUTE PROCEDURE update_attribute_index_and_variant_group_visibility();\n    `\n  );\n\n  // Drop column uuid from product_attribute_value_index\n  await execute(\n    connection,\n    `ALTER TABLE product_attribute_value_index DROP COLUMN uuid;`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/migration/Version-1.0.2.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Add a category_id column to the product table\n  await execute(\n    connection,\n    `ALTER TABLE product ADD COLUMN category_id INT DEFAULT NULL;`\n  );\n\n  // Add a foreign key constraint to the category_id column\n  await execute(\n    connection,\n    `ALTER TABLE product ADD CONSTRAINT \"PRODUCT_CATEGORY_ID_CONSTRAINT\" FOREIGN KEY (\"category_id\") REFERENCES \"category\" (\"category_id\") ON DELETE SET NULL;`\n  );\n\n  // Get 1 category from the product_category table and update product table with according category_id\n  await execute(\n    connection,\n    `UPDATE product SET category_id = (SELECT category_id FROM product_category WHERE product_id = product.product_id LIMIT 1);`\n  );\n\n  // Delete the product_category table\n  await execute(connection, `DROP TABLE product_category;`);\n\n  // Create a function to build url_key from the name if the url_key is not provided. This function replace whitespace with dash and remove all special characters\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION build_url_key() RETURNS TRIGGER AS $$\n    DECLARE\n      url_key TEXT;\n    BEGIN\n      IF(NEW.url_key IS NULL) THEN\n        url_key = regexp_replace(NEW.name, '[^a-zA-Z0-9]+', '-', 'g');\n        url_key = regexp_replace(url_key, '^-|-$', '', 'g');\n        url_key = lower(url_key);\n        url_key = url_key || '-' || (SELECT floor(random() * 1000000)::text);\n        NEW.url_key = url_key;\n      ELSE\n        IF (NEW.url_key ~ '[/\\\\#]') THEN\n          RAISE EXCEPTION 'Invalid url_key: %', NEW.url_key;\n        END IF;\n      END IF;\n      RETURN NEW;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to build the url_key from the name if the url_key is not provided\n  await execute(\n    connection,\n    `CREATE TRIGGER \"BUILD_CATEGORY_URL_KEY_TRIGGER\"\n    BEFORE INSERT OR UPDATE ON category_description\n    FOR EACH ROW\n    EXECUTE PROCEDURE build_url_key();`\n  );\n\n  // Create a trigger to build the url_key from the name if the url_key is not provided\n  await execute(\n    connection,\n    `CREATE TRIGGER \"BUILD_PRODUCT_URL_KEY_TRIGGER\"\n    BEFORE INSERT OR UPDATE ON product_description\n    FOR EACH ROW\n    EXECUTE PROCEDURE build_url_key();`\n  );\n\n  // Create a url_rewrite table to store the url rewrite rules\n  await execute(\n    connection,\n    `CREATE TABLE url_rewrite (\n      \"url_rewrite_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n      \"language\" varchar NOT NULL DEFAULT 'en',\n      \"request_path\" varchar NOT NULL,\n      \"target_path\" varchar NOT NULL,\n      \"entity_uuid\" UUID DEFAULT NULL,\n      \"entity_type\" varchar DEFAULT NULL,\n      CONSTRAINT \"URL_REWRITE_PATH_UNIQUE\" UNIQUE (\"language\", \"entity_uuid\")\n    )`\n  );\n\n  // Create a function to add event to the event table after a category is created\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_category_created_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('category_created', row_to_json(NEW));\n      RETURN NEW;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a category is created\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_CATEGORY_CREATED_EVENT_TRIGGER\"\n    AFTER INSERT ON category\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_category_created_event();`\n  );\n\n  // Create a function to add event to the event table after a category is updated\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_category_updated_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('category_updated', row_to_json(NEW));\n      RETURN NEW;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a category is updated\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_CATEGORY_UPDATED_EVENT_TRIGGER\"\n    AFTER UPDATE ON category\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_category_updated_event();`\n  );\n\n  // Create a function to add event to the event table after a category is deleted\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_category_deleted_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('category_deleted', row_to_json(OLD));\n      RETURN OLD;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a category is deleted\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_CATEGORY_DELETED_EVENT_TRIGGER\"\n    AFTER DELETE ON category\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_category_deleted_event();`\n  );\n\n  // Create a function to add event to the event table after a product is created\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_product_created_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('product_created', row_to_json(NEW));\n      RETURN NEW;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a product is created\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_PRODUCT_CREATED_EVENT_TRIGGER\"\n    AFTER INSERT ON product\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_product_created_event();`\n  );\n\n  // Create a function to add event to the event table after a product is updated\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_product_updated_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('product_updated', row_to_json(NEW));\n      RETURN NEW;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a product is updated\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_PRODUCT_UPDATED_EVENT_TRIGGER\"\n    AFTER UPDATE ON product\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_product_updated_event();`\n  );\n\n  // Create a function to add event to the event table after a product is deleted\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_product_deleted_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('product_deleted', row_to_json(OLD));\n      RETURN OLD;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a product is deleted\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_PRODUCT_DELETED_EVENT_TRIGGER\"\n    AFTER DELETE ON product\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_product_deleted_event();`\n  );\n\n  // Create a product_inventory table to store the inventory of the product and move the inventory from the product table to this table\n  await execute(\n    connection,\n    `CREATE TABLE product_inventory (\n      \"product_inventory_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n      \"product_inventory_product_id\" INT NOT NULL,\n      \"qty\" INT NOT NULL DEFAULT 0,\n      \"manage_stock\" BOOLEAN NOT NULL DEFAULT false,\n      \"stock_availability\" BOOLEAN NOT NULL DEFAULT false,\n      CONSTRAINT \"PRODUCT_INVENTORY_PRODUCT_ID_CONSTANTSRAINT\" FOREIGN KEY (\"product_inventory_product_id\") REFERENCES \"product\" (\"product_id\") ON DELETE CASCADE,\n      CONSTRAINT \"PRODUCT_INVENTORY_PRODUCT_ID_UNIQUE\" UNIQUE (\"product_inventory_product_id\")\n    )`\n  );\n\n  // Copy the inventory from the product table to the product_inventory table\n  await execute(\n    connection,\n    `INSERT INTO product_inventory (product_inventory_product_id, qty, manage_stock, stock_availability)\n    SELECT product_id, qty, manage_stock, stock_availability FROM product;`\n  );\n\n  // Create a function to delete all sub categories of a category when the category is deleted\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION delete_sub_categories() RETURNS TRIGGER AS $$\n    DECLARE\n      sub_categories RECORD;\n    BEGIN\n      FOR sub_categories IN\n        WITH RECURSIVE sub_categories AS (\n          SELECT * FROM category WHERE parent_id = OLD.category_id\n          UNION\n          SELECT c.* FROM category c\n          INNER JOIN sub_categories sc ON c.parent_id = sc.category_id\n        ) SELECT * FROM sub_categories\n      LOOP\n        DELETE FROM category WHERE category_id = sub_categories.category_id;\n      END LOOP;\n      RETURN OLD;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to delete all sub categories of a category when the category is deleted, this trigger will be executed after the category is deleted, make sure to avoid infinite loop\n  await execute(\n    connection,\n    `CREATE TRIGGER \"DELETE_SUB_CATEGORIES_TRIGGER\"\n    AFTER DELETE ON category\n    FOR EACH ROW\n    EXECUTE PROCEDURE delete_sub_categories();`\n  );\n\n  // Load all categories and add a category_updated event to the event table\n  await execute(\n    connection,\n    `INSERT INTO event (name, data) SELECT 'category_updated', row_to_json(category) FROM category;`\n  );\n\n  // Load all products and add a product_updated event to the event table\n  await execute(\n    connection,\n    `INSERT INTO event (name, data) SELECT 'product_updated', row_to_json(product) FROM product;`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/migration/Version-1.0.3.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Remove the inventory from the product table\n  await execute(\n    connection,\n    `ALTER TABLE product DROP COLUMN qty, DROP COLUMN manage_stock, DROP COLUMN stock_availability;`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/migration/Version-1.0.4.js",
    "content": "import { execute, insert } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Create a function to add event to the event table after a order is created\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_product_inventory_updated_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('inventory_updated', json_build_object('old', row_to_json(OLD), 'new', row_to_json(NEW)));\n      RETURN NEW;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a order is created\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_INVENTORY_UPDATED_EVENT_TRIGGER\"\n    AFTER UPDATE ON \"product_inventory\"\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_product_inventory_updated_event();`\n  );\n\n  // Check if a default collection called \"Featured Products\" already exists\n  const featuredProductsExists = await execute(\n    connection,\n    `SELECT EXISTS (SELECT 1 FROM collection WHERE code = 'homepage');`\n  );\n\n  if (featuredProductsExists.rows[0].exists) {\n    return;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/migration/Version-1.0.5.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Create a GIN index for search on the product_description table using name and description column\n  await execute(\n    connection,\n    `CREATE INDEX \"PRODUCT_SEARCH_INDEX\" ON product_description USING GIN (to_tsvector('simple', name || ' ' || description));`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/migration/Version-1.0.6.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // rename the image column in the product_image table to origin_image\n  await execute(\n    connection,\n    `ALTER TABLE product_image RENAME COLUMN image TO origin_image`\n  );\n\n  // Add  columns 'thumb_image', 'listing_image', 'single_image', 'is_main' to the product_image table if not exist\n  await execute(\n    connection,\n    `ALTER TABLE product_image\n      ADD COLUMN IF NOT EXISTS thumb_image text,\n      ADD COLUMN IF NOT EXISTS listing_image text,\n      ADD COLUMN IF NOT EXISTS single_image text,\n      ADD COLUMN IF NOT EXISTS is_main boolean DEFAULT false`\n  );\n\n  // Drop the uuid column from the product_image table\n  await execute(connection, `ALTER TABLE product_image DROP COLUMN uuid`);\n\n  // Create a trigger to add an event to the event table when a product image is created\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION product_image_insert_trigger()\n      RETURNS TRIGGER AS $$\n      BEGIN\n        INSERT INTO event (name, data)\n        VALUES ('product_image_added', row_to_json(NEW));\n        RETURN NEW;\n      END;\n      $$ LANGUAGE plpgsql;`\n  );\n\n  await execute(\n    connection,\n    `CREATE TRIGGER \"PRODUCT_IMAGE_ADDED\"\n      AFTER INSERT ON product_image\n      FOR EACH ROW\n      EXECUTE PROCEDURE product_image_insert_trigger();`\n  );\n\n  // Update all the column origin_image in the product_image table to add the '/assets' prefix\n  await execute(\n    connection,\n    `UPDATE product_image SET origin_image = CONCAT('/assets', origin_image)`\n  );\n\n  // Add event to the event table for all the product images\n  await execute(\n    connection,\n    `INSERT INTO event (name, data)\n      SELECT 'product_image_added', row_to_json(product_image)\n      FROM product_image`\n  );\n\n  await execute(\n    connection,\n    `INSERT INTO product_image (product_image_product_id, origin_image, is_main)\n      SELECT product_id, CONCAT('/assets', image), true\n      FROM product\n      WHERE image IS NOT NULL`\n  );\n\n  // Drop the image column from product table\n  await execute(connection, `ALTER TABLE product DROP COLUMN image`);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/migration/Version-1.0.7.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // rename the image column in the product_image table to origin_image\n  await execute(\n    connection,\n    `ALTER TABLE category ADD COLUMN IF NOT EXISTS show_products boolean DEFAULT TRUE`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/migration/Version-1.0.8.ts",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `ALTER TABLE product ADD COLUMN IF NOT EXISTS no_shipping_required boolean DEFAULT FALSE`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/all/CatalogMenuGroup.jsx",
    "content": "import { NavigationItemGroup } from '@components/admin/NavigationItemGroup';\nimport { Box, Hash, Link, Tag } from 'lucide-react';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport default function CatalogMenuGroup({\n  productGrid,\n  categoryGrid,\n  attributeGrid,\n  collectionGrid\n}) {\n  return (\n    <NavigationItemGroup\n      id=\"catalogMenuGroup\"\n      name=\"Catalog\"\n      items={[\n        {\n          Icon: Box,\n          url: productGrid,\n          title: 'Products'\n        },\n        {\n          Icon: Link,\n          url: categoryGrid,\n          title: 'Categories'\n        },\n        {\n          Icon: Tag,\n          url: collectionGrid,\n          title: 'Collections'\n        },\n        {\n          Icon: Hash,\n          url: attributeGrid,\n          title: 'Attributes'\n        }\n      ]}\n    />\n  );\n}\n\nCatalogMenuGroup.propTypes = {\n  attributeGrid: PropTypes.string.isRequired,\n  categoryGrid: PropTypes.string.isRequired,\n  collectionGrid: PropTypes.string.isRequired,\n  productGrid: PropTypes.string.isRequired\n};\n\nexport const layout = {\n  areaId: 'adminMenu',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query {\n    productGrid: url(routeId:\"productGrid\")\n    categoryGrid: url(routeId:\"categoryGrid\")\n    attributeGrid: url(routeId:\"attributeGrid\")\n    collectionGrid: url(routeId:\"collectionGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/all/NewProductQuickLink.jsx",
    "content": "import { NavigationItem } from '@components/admin/NavigationItem.js';\nimport { BoxIcon } from 'lucide-react';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport default function NewProductQuickLink({ productNew }) {\n  return <NavigationItem Icon={BoxIcon} title=\"New Product\" url={productNew} />;\n}\n\nNewProductQuickLink.propTypes = {\n  productNew: PropTypes.string.isRequired\n};\n\nexport const layout = {\n  areaId: 'quickLinks',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query {\n    productNew: url(routeId:\"productNew\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeEdit/AttributeEditForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport React from 'react';\n\ninterface AttributeEditFormProps {\n  action: string;\n  gridUrl: string;\n}\nexport default function AttributeEditForm({\n  action,\n  gridUrl\n}: AttributeEditFormProps) {\n  return (\n    <Form\n      action={action}\n      method=\"PATCH\"\n      id=\"attributeEditForm\"\n      submitBtn={false}\n    >\n      <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"leftSide\" noOuter />\n        </div>\n        <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"rightSide\" noOuter />\n        </div>\n      </div>\n      <FormButtons formId=\"attributeEditForm\" cancelUrl={gridUrl} />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"updateAttribute\", params: [{key: \"id\", value: getContextValue(\"attributeUuid\")}]),\n    gridUrl: url(routeId: \"attributeGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeEdit/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (request, response, next) => {\n  try {\n    const query = select();\n    query.from('attribute');\n    query.andWhere('attribute.uuid', '=', request.params.id);\n    const attribute = await query.load(pool);\n\n    if (attribute === null) {\n      response.status(404);\n      next();\n    } else {\n      setContextValue(request, 'attributeId', attribute.attribute_id);\n      setContextValue(request, 'attributeUuid', attribute.uuid);\n      setPageMetaInfo(request, {\n        title: attribute.attribute_name,\n        description: attribute.attribute_name\n      });\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeEdit/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/attributes/edit/:id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeEdit+attributeNew/Avaibility.tsx",
    "content": "import { NumberField } from '@components/common/form/NumberField.js';\nimport { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface GeneralProps {\n  attribute?: {\n    displayOnFrontend?: number;\n    isFilterable?: number;\n    isRequired?: number;\n    sortOrder?: number;\n  };\n}\nexport default function General({ attribute }: GeneralProps) {\n  return (\n    <Card className=\"bg-popover\">\n      <CardHeader>\n        <CardTitle>Setting</CardTitle>\n        <CardDescription>Manage the setting of the attribute.</CardDescription>\n      </CardHeader>\n      <CardContent>\n        <RadioGroupField\n          name=\"is_required\"\n          label=\"Is Required?\"\n          options={[\n            { value: 0, label: 'No' },\n            { value: 1, label: 'Yes' }\n          ]}\n          required\n          validation={{\n            required: 'This field is required'\n          }}\n          defaultValue={attribute?.isRequired === 0 ? 0 : 1}\n        />\n      </CardContent>\n      <CardContent className=\"pt-6 border-t border-border\">\n        <RadioGroupField\n          name=\"is_filterable\"\n          label=\"Is Filterable?\"\n          options={[\n            { value: 0, label: 'No' },\n            { value: 1, label: 'Yes' }\n          ]}\n          required\n          validation={{\n            required: 'This field is required'\n          }}\n          defaultValue={attribute?.isFilterable === 1 ? 1 : 0}\n        />\n      </CardContent>\n      <CardContent className=\"pt-6 border-t border-border\">\n        <RadioGroupField\n          name=\"display_on_frontend\"\n          label=\"Display on Frontend?\"\n          options={[\n            { value: 0, label: 'No' },\n            { value: 1, label: 'Yes' }\n          ]}\n          required\n          validation={{\n            required: 'This field is required'\n          }}\n          defaultValue={attribute?.displayOnFrontend === 1 ? 1 : 0}\n        />\n      </CardContent>\n      <CardContent className=\"pt-6 border-t border-border\">\n        <NumberField\n          name=\"sort_order\"\n          label=\"Sort Order\"\n          placeholder=\"Sort order\"\n          required\n          validation={{\n            required: 'Sort order is required',\n            min: {\n              value: 0,\n              message: 'Sort order must be a positive number'\n            }\n          }}\n          defaultValue={attribute?.sortOrder}\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    attribute(id: getContextValue(\"attributeId\", null)) {\n      attributeId\n      isFilterable\n      isRequired\n      displayOnFrontend\n      sortOrder\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeEdit+attributeNew/General.scss",
    "content": "body.attributeEdit,\nbody.attributeNew {\n  .page-heading {\n    max-width: 62.5rem;\n  }\n  .main-content-inner {\n    max-width: 62.5rem;\n    margin: 1.875rem auto;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeEdit+attributeNew/General.tsx",
    "content": "import Spinner from '@components/admin/Spinner.jsx';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport {\n  InputGroupAddon,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { InputGroup } from '@components/common/ui/InputGroup.js';\nimport { PlusCircle } from 'lucide-react';\nimport React from 'react';\nimport { useFormContext, Controller, useFieldArray } from 'react-hook-form';\nimport Select from 'react-select';\nimport { useQuery } from 'urql';\nimport { v4 as uuidv4 } from 'uuid';\nimport { get } from '../../../../../lib/util/get.js';\nimport './General.scss';\n\nconst GroupsQuery = `\n  query Query {\n    attributeGroups {\n      items {\n        value: attributeGroupId\n        label: groupName\n      }\n    }\n  }\n`;\ninterface Group {\n  value: string;\n  label: string;\n  attributes: {\n    items: {\n      attributeId: string;\n      attributeName: string;\n      attributeCode: string;\n      type: string;\n      isRequired: number;\n      options: {\n        optionId: string;\n        optionText: string;\n      }[];\n    }[];\n  };\n}\n\nconst Groups: React.FC<{ groups: Group[]; createGroupApi: string }> = ({\n  groups,\n  createGroupApi\n}) => {\n  const [result, reexecuteQuery] = useQuery({\n    query: GroupsQuery\n  });\n  const { control } = useFormContext();\n  const newGroup = React.useRef<HTMLInputElement | null>(null);\n  const [createGroupError, setCreateGroupError] = React.useState<string | null>(\n    null\n  );\n  const { data, fetching, error } = result;\n\n  const createGroup = () => {\n    if (!newGroup.current?.value) {\n      setCreateGroupError('Group name is required');\n      return;\n    }\n    fetch(createGroupApi, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({ group_name: newGroup.current.value })\n    })\n      .then((response) => response.json())\n      .then((jsonData) => {\n        if (!jsonData.error) {\n          newGroup.current!.value = '';\n          setCreateGroupError(null);\n          reexecuteQuery({ requestPolicy: 'network-only' });\n        } else {\n          setCreateGroupError(jsonData.error.message);\n        }\n      });\n  };\n\n  if (fetching)\n    return (\n      <div>\n        <Spinner width={20} height={20} />\n      </div>\n    );\n  if (error) {\n    return <p className=\"text-destructive\">{error.message}</p>;\n  }\n\n  return (\n    <div>\n      <div className=\"mb-2\">Select groups the attribute belongs to</div>\n      <div className=\"grid gap-5 grid-cols-2\">\n        <div>\n          <Controller\n            name=\"groups\"\n            control={control}\n            defaultValue={groups.map((group) => group.value)}\n            render={({ field }) => (\n              <Select\n                {...field}\n                options={data.attributeGroups.items}\n                hideSelectedOptions\n                isMulti\n                onChange={(selectedOptions) => {\n                  field.onChange(\n                    selectedOptions.map((option) => option.value) || []\n                  );\n                }}\n                value={data.attributeGroups.items.filter((item) =>\n                  field.value.includes(item.value)\n                )}\n              />\n            )}\n          />\n        </div>\n        <div className=\"grid gap-5 grid-cols-1\">\n          <div>\n            <div className=\"flex gap-5\">\n              <InputGroup className=\"max-w-xs\">\n                <InputGroupInput\n                  type=\"text\"\n                  placeholder=\"Create a new group\"\n                  ref={newGroup}\n                />\n                <InputGroupAddon align=\"inline-end\">\n                  <a\n                    className=\"flex w-8 items-center justify-center text-primary\"\n                    href=\"#\"\n                    onClick={(e) => {\n                      e.preventDefault();\n                      createGroup();\n                    }}\n                  >\n                    <PlusCircle className=\"size-4\" />\n                  </a>\n                </InputGroupAddon>\n              </InputGroup>\n            </div>\n            {createGroupError && (\n              <p className=\"text-destructive text-xs mt-1\">\n                {createGroupError}\n              </p>\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst Options: React.FC<{\n  originOptions?: Array<{ optionId: string; optionText: string; uuid: string }>;\n}> = ({ originOptions = [] }) => {\n  const { control } = useFormContext();\n  const { fields, append, remove, replace } = useFieldArray({\n    name: 'options',\n    control\n  });\n\n  React.useEffect(() => {\n    replace(\n      originOptions.map((option) => ({\n        option_text: option.optionText,\n        option_id: option.optionId,\n        uuid: option.uuid\n      }))\n    );\n  }, []);\n\n  const addOption = (e: React.MouseEvent<HTMLButtonElement>) => {\n    e.preventDefault();\n    append({\n      option_text: '',\n      option_id: (\n        Math.floor(Math.random() * (9000000 - 1000000)) + 1000000\n      ).toString(),\n      uuid: uuidv4()\n    });\n  };\n\n  return (\n    <div className=\"attribute-edit-options\">\n      {fields.map((field, index) => {\n        return (\n          <div key={field.id} className=\"flex items-center mb-2 space-x-5\">\n            <div className=\"flex-1\">\n              <InputField\n                name={`options.${index}.option_text`}\n                placeholder=\"Option text\"\n                validation={{ required: 'Option text is required' }}\n              />\n              <InputField type=\"hidden\" name={`options.${index}.option_id`} />\n            </div>\n            <div className=\"self-center\">\n              <Button\n                type=\"button\"\n                onClick={(e) => {\n                  e.preventDefault();\n                  remove(index);\n                }}\n                variant={'destructive'}\n              >\n                Remove\n              </Button>\n            </div>\n          </div>\n        );\n      })}\n      <div className=\"mt-2\">\n        <Button type=\"button\" onClick={addOption} variant={'outline'}>\n          Add option\n        </Button>\n      </div>\n    </div>\n  );\n};\n\ninterface GeneralProps {\n  attribute?: {\n    type?: string;\n    attributeId?: string;\n    attributeName?: string;\n    attributeCode?: string;\n    options?: {\n      optionId: string;\n      uuid: string;\n      optionText: string;\n    }[];\n    groups?: {\n      items: {\n        value: string;\n        label: string;\n      }[];\n    };\n  };\n  createGroupApi: string;\n}\n\nexport default function General({ attribute, createGroupApi }: GeneralProps) {\n  const [type] = React.useState(attribute?.type || 'text');\n\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>General</CardTitle>\n        <CardDescription>\n          Manage the general information of the attribute.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"space-y-2\">\n          <InputField\n            name=\"attribute_name\"\n            label=\"Name\"\n            placeholder=\"Enter attribute name\"\n            required\n            defaultValue={attribute?.attributeName}\n            validation={{ required: 'Attribute name is required' }}\n          />\n\n          <InputField\n            name=\"attribute_code\"\n            label=\"Code\"\n            placeholder=\"Enter attribute code\"\n            required\n            defaultValue={attribute?.attributeCode}\n            validation={{ required: 'Attribute code is required' }}\n            helperText=\"Attribute code is used in API and must be unique\"\n          />\n\n          <div>\n            <div className=\"space-y-2\">\n              <RadioGroupField\n                name=\"type\"\n                options={[\n                  { label: 'Text', value: 'text' },\n                  { label: 'Select', value: 'select' },\n                  { label: 'Multiselect', value: 'multiselect' },\n                  { label: 'Textarea', value: 'textarea' }\n                ]}\n                label=\"Type\"\n                defaultValue={attribute?.type}\n                required\n                disabled={!!attribute?.attributeId}\n                validation={{ required: 'Type is required' }}\n              />\n            </div>\n          </div>\n        </div>\n      </CardContent>\n      {['select', 'multiselect'].includes(type) && (\n        <CardContent title=\"Attribute options\">\n          <Options originOptions={get(attribute, 'options', [])} />\n        </CardContent>\n      )}\n      <CardContent title=\"Attribute Group\">\n        <Groups\n          groups={get(attribute, 'groups.items', [])}\n          createGroupApi={createGroupApi}\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    attribute(id: getContextValue(\"attributeId\", null)) {\n      attributeId\n      attributeName\n      attributeCode\n      type\n      options {\n        optionId: attributeOptionId\n        uuid\n        optionText\n      }\n      groups {\n        items {\n          value: attributeGroupId\n          label: groupName\n        }\n      }\n    }\n    createGroupApi: url(routeId: \"createAttributeGroup\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeEdit+attributeNew/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface AttributeEditPageHeadingProps {\n  backUrl?: string;\n  attribute?: {\n    attributeName?: string;\n  };\n}\n\nexport default function AttributeEditPageHeading({\n  backUrl,\n  attribute\n}: AttributeEditPageHeadingProps) {\n  return (\n    <PageHeading\n      backUrl={backUrl}\n      heading={\n        attribute\n          ? `Editing ${attribute.attributeName}`\n          : 'Create a new attribute'\n      }\n    />\n  );\n}\n\nAttributeEditPageHeading.defaultProps = {\n  attribute: {}\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    attribute(id: getContextValue(\"attributeId\", null)) {\n      attributeName\n    }\n    backUrl: url(routeId: \"attributeGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeGrid/Grid.jsx",
    "content": "import { GridPagination } from '@components/admin/grid/GridPagination.js';\nimport { DummyColumnHeader } from '@components/admin/grid/header/Dummy';\nimport { SortableHeader } from '@components/admin/grid/header/Sortable';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { useAlertContext } from '@components/common/modal/Alert';\nimport { Badge } from '@components/common/ui/Badge.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport {\n  Card,\n  CardAction,\n  CardContent,\n  CardHeader\n} from '@components/common/ui/Card.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport {\n  Table,\n  TableHead,\n  TableHeader,\n  TableRow,\n  TableBody,\n  TableCell\n} from '@components/common/ui/Table.js';\nimport axios from 'axios';\nimport PropTypes from 'prop-types';\nimport React, { useState } from 'react';\nimport { toast } from 'react-toastify';\nimport { AttributeNameRow } from './rows/AttributeName.js';\nimport { GroupRow } from './rows/GroupRow.js';\n\nfunction Actions({ attributes = [], selectedIds = [] }) {\n  const { openAlert, closeAlert } = useAlertContext();\n  const [isLoading, setIsLoading] = useState(false);\n\n  const deleteAttributes = async () => {\n    setIsLoading(true);\n    try {\n      const promises = attributes\n        .filter((attribute) => selectedIds.includes(attribute.uuid))\n        .map((attribute) =>\n          axios.delete(attribute.deleteApi, {\n            validateStatus: () => true\n          })\n        );\n      const responses = await Promise.allSettled(promises);\n      setIsLoading(false);\n      responses.forEach((response) => {\n        // Get the axios response status code\n        const { status } = response.value;\n        if (status !== 200) {\n          throw new Error(response.value.data.error.message);\n        }\n      });\n      // Refresh the page\n      window.location.reload();\n    } catch (e) {\n      setIsLoading(false);\n      toast.error(e.message);\n    }\n  };\n\n  const actions = [\n    {\n      name: 'Delete',\n      onAction: () => {\n        openAlert({\n          heading: `Delete ${selectedIds.length} attributes`,\n          content: <div>Can&apos;t be undone</div>,\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Delete',\n            onAction: async () => {\n              await deleteAttributes();\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    }\n  ];\n\n  return (\n    <TableRow>\n      {selectedIds.length === 0 && null}\n      {selectedIds.length > 0 && (\n        <TableCell colSpan=\"100\">\n          <ButtonGroup>\n            {actions.map((action, i) => (\n              <Button\n                key={i}\n                variant={'outline'}\n                onClick={(e) => {\n                  e.preventDefault();\n                  action.onAction();\n                }}\n              >\n                {action.name}\n              </Button>\n            ))}\n          </ButtonGroup>\n        </TableCell>\n      )}\n    </TableRow>\n  );\n}\n\nActions.propTypes = {\n  selectedIds: PropTypes.arrayOf(PropTypes.string).isRequired,\n  attributes: PropTypes.arrayOf(\n    PropTypes.shape({\n      uuid: PropTypes.string.isRequired,\n      deleteApi: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport default function AttributeGrid({\n  attributes: { items: attributes, total, currentFilters = [] }\n}) {\n  const page = currentFilters.find((filter) => filter.key === 'page')\n    ? parseInt(currentFilters.find((filter) => filter.key === 'page').value, 10)\n    : 1;\n  const limit = currentFilters.find((filter) => filter.key === 'limit')\n    ? parseInt(\n        currentFilters.find((filter) => filter.key === 'limit').value,\n        10\n      )\n    : 20;\n  const [selectedRows, setSelectedRows] = useState([]);\n\n  return (\n    <Card>\n      <CardHeader className=\"flex justify-between\">\n        <Form submitBtn={false} id=\"attributeGridFilter\">\n          <InputField\n            name=\"name\"\n            placeholder=\"Search\"\n            defaultValue={currentFilters.find((f) => f.key === 'name')?.value}\n            onKeyPress={(e) => {\n              // If the user press enter, we should submit the form\n              if (e.key === 'Enter') {\n                const url = new URL(document.location);\n                const name = e.target?.value;\n                if (name) {\n                  url.searchParams.set('name[operation]', 'like');\n                  url.searchParams.set('name[value]', name);\n                } else {\n                  url.searchParams.delete('name[operation]');\n                  url.searchParams.delete('name[value]');\n                }\n                window.location.href = url;\n              }\n            }}\n          />\n        </Form>\n        <CardAction>\n          <Button\n            variant=\"link\"\n            onClick={() => {\n              // Just get the url and remove all query params\n              const url = new URL(document.location);\n              url.search = '';\n              window.location.href = url.href;\n            }}\n          >\n            Clear Filters\n          </Button>\n        </CardAction>\n      </CardHeader>\n      <CardContent>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>\n                <div className=\"form-field mb-0\">\n                  <Checkbox\n                    onCheckedChange={(checked) => {\n                      if (checked)\n                        setSelectedRows(attributes.map((a) => a.uuid));\n                      else setSelectedRows([]);\n                    }}\n                  />\n                </div>\n              </TableHead>\n              <Area\n                className=\"\"\n                id=\"attributeGridHeader\"\n                noOuter\n                coreComponents={[\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          name=\"name\"\n                          title=\"Attribute Name\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 10\n                  },\n                  {\n                    component: {\n                      default: () => <DummyColumnHeader title=\"Groups\" />\n                    },\n                    sortOrder: 15\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          name=\"type\"\n                          title=\"Type\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 20\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          name=\"is_required\"\n                          title=\"Is Required?\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 25\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          name=\"is_filterable\"\n                          title=\"Is Filterable?\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 30\n                  }\n                ]}\n              />\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            <Actions\n              attributes={attributes}\n              selectedIds={selectedRows}\n              setSelectedRows={setSelectedRows}\n            />\n            {attributes.map((a) => (\n              <TableRow key={a.attributeId}>\n                <TableCell>\n                  <div className=\"form-field mb-0\">\n                    <Checkbox\n                      checked={selectedRows.includes(a.uuid)}\n                      onCheckedChange={(checked) => {\n                        if (checked) {\n                          setSelectedRows(selectedRows.concat([a.uuid]));\n                        } else {\n                          setSelectedRows(\n                            selectedRows.filter((r) => r !== a.uuid)\n                          );\n                        }\n                      }}\n                    />\n                  </div>\n                </TableCell>\n                <Area\n                  className=\"\"\n                  id=\"attributeGridRow\"\n                  row={a}\n                  noOuter\n                  coreComponents={[\n                    {\n                      component: {\n                        default: () => (\n                          <AttributeNameRow\n                            id=\"name\"\n                            name={a.attributeName}\n                            url={a.editUrl}\n                          />\n                        )\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: () => <GroupRow groups={a.groups?.items} />\n                      },\n                      sortOrder: 15\n                    },\n                    {\n                      component: {\n                        default: ({ areaProps }) => (\n                          <TableCell>\n                            <Badge variant=\"outline\">\n                              {areaProps.row.type}\n                            </Badge>\n                          </TableCell>\n                        )\n                      },\n                      sortOrder: 20\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <TableCell>{a.isRequired ? 'Yes' : 'No'}</TableCell>\n                        )\n                      },\n                      sortOrder: 25\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <TableCell>{a.isFilterable ? 'Yes' : 'No'}</TableCell>\n                        )\n                      },\n                      sortOrder: 30\n                    }\n                  ]}\n                />\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {attributes.length === 0 && (\n          <div className=\"flex w-full justify-center mt-2\">\n            There is no attribute to display\n          </div>\n        )}\n        <GridPagination total={total} limit={limit} page={page} />\n      </CardContent>\n    </Card>\n  );\n}\n\nAttributeGrid.propTypes = {\n  attributes: PropTypes.shape({\n    items: PropTypes.arrayOf(\n      PropTypes.shape({\n        uuid: PropTypes.string.isRequired,\n        attributeId: PropTypes.string.isRequired,\n        attributeName: PropTypes.string.isRequired,\n        attributeCode: PropTypes.string.isRequired,\n        type: PropTypes.string.isRequired,\n        isRequired: PropTypes.number.isRequired,\n        isFilterable: PropTypes.number.isRequired,\n        editUrl: PropTypes.string.isRequired,\n        updateApi: PropTypes.string.isRequired,\n        deleteApi: PropTypes.string.isRequired\n      })\n    ).isRequired,\n    total: PropTypes.number.isRequired,\n    currentFilters: PropTypes.arrayOf(\n      PropTypes.shape({\n        key: PropTypes.string.isRequired,\n        operation: PropTypes.string.isRequired,\n        value: PropTypes.string.isRequired\n      })\n    ).isRequired\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query($filters: [FilterInput]) {\n    attributes (filters: $filters) {\n      items {\n        attributeId\n        uuid\n        attributeName\n        attributeCode\n        type\n        isRequired\n        isFilterable\n        editUrl\n        updateApi\n        deleteApi\n        groups {\n          items {\n            attributeGroupId\n            groupName\n            updateApi\n          }\n        }\n      }\n      total\n      currentFilters {\n        key\n        operation\n        value\n      }\n    }\n  }\n`;\n\nexport const variables = `\n{\n  filters: getContextValue('filtersFromUrl')\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeGrid/NewAttributeButton.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\n\ninterface NewAttributeButtonProps {\n  newAttributeUrl: string;\n}\nexport default function NewAttributeButton({\n  newAttributeUrl\n}: NewAttributeButtonProps) {\n  return (\n    <Button onClick={() => (window.location.href = newAttributeUrl)}>\n      New Attribute\n    </Button>\n  );\n}\n\nexport const layout = {\n  areaId: 'pageHeadingRight',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    newAttributeUrl: url(routeId: \"attributeNew\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeGrid/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface AttributGridPageHeadingProps {\n  backUrl?: string;\n}\nexport default function AttributGridPageHeading() {\n  return <PageHeading heading=\"Attributes\" />;\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeGrid/index.ts",
    "content": "import { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default (request: EvershopRequest, response) => {\n  setPageMetaInfo(request, {\n    title: 'Attributes',\n    description: 'Attributes'\n  });\n  setContextValue(\n    request,\n    'filtersFromUrl',\n    buildFilterFromUrl(request.originalUrl)\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeGrid/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/attributes\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeGrid/rows/AttributeName.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface AttributeNameRowProps {\n  url: string;\n  name: string;\n}\n\nexport function AttributeNameRow({ url, name }: AttributeNameRowProps) {\n  return (\n    <TableCell>\n      <div>\n        <a className=\"hover:underline font-semibold\" href={url}>\n          {name}\n        </a>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeGrid/rows/GroupRow.tsx",
    "content": "import { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { useAlertContext } from '@components/common/modal/Alert.js';\nimport { Badge } from '@components/common/ui/Badge.js';\nimport { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\nimport { toast } from 'react-toastify';\n\ninterface GroupRowProps {\n  groups: Array<{\n    attributeGroupId: string;\n    updateApi: string;\n    groupName: string;\n  }>;\n}\nexport function GroupRow({ groups }: GroupRowProps) {\n  const form = useForm();\n  const { openAlert, closeAlert, dispatchAlert } = useAlertContext();\n\n  const onEdit = (group) => {\n    openAlert({\n      heading: `Editing ${group.groupName}`,\n      content: (\n        <div>\n          <Form\n            form={form}\n            id=\"groupEdit\"\n            method=\"PATCH\"\n            action={group.updateApi}\n            submitBtn={false}\n            onSuccess={(response) => {\n              if (response.error) {\n                toast.error(response.error.message);\n              } else {\n                window.location.reload();\n              }\n            }}\n          >\n            <InputField\n              name=\"group_name\"\n              required\n              label=\"Group Name\"\n              placeholder=\"Enter group name\"\n              validation={{ required: 'Group name is required' }}\n              defaultValue={group.groupName}\n            />\n          </Form>\n        </div>\n      ),\n      primaryAction: {\n        title: 'Cancel',\n        onAction: closeAlert,\n        variant: 'critical'\n      },\n      secondaryAction: {\n        title: 'Save',\n        onAction: () => {\n          dispatchAlert({\n            type: 'update',\n            payload: {\n              secondaryAction: { isLoading: form.formState.isSubmitting }\n            }\n          });\n          (\n            document.getElementById('groupEdit') as HTMLFormElement\n          ).dispatchEvent(\n            new Event('submit', { cancelable: true, bubbles: true })\n          );\n        },\n        variant: 'secondary',\n        isLoading: false\n      }\n    });\n  };\n\n  return (\n    <TableCell>\n      {groups.map((group) => (\n        <div key={group.attributeGroupId}>\n          <Badge variant={'secondary'}>\n            <a\n              href=\"#\"\n              className=\"hover:underline\"\n              onClick={(e) => {\n                e.preventDefault();\n                onEdit(group);\n              }}\n            >\n              {group.groupName}\n            </a>\n          </Badge>\n        </div>\n      ))}\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeNew/AttributeNewForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface AttributeNewFormProps {\n  action: string;\n  gridUrl: string;\n}\nexport default function AttributeNewForm({\n  action,\n  gridUrl\n}: AttributeNewFormProps) {\n  return (\n    <Form\n      action={action}\n      method=\"POST\"\n      id=\"attributeNewForm\"\n      onSuccess={(response) => {\n        toast.success('Attribute created successfully!');\n        setTimeout(() => {\n          const editUrl = response.data.links.find(\n            (link) => link.rel === 'edit'\n          ).href;\n          window.location.href = editUrl;\n        }, 1500);\n      }}\n      submitBtn={false}\n    >\n      <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"leftSide\" noOuter />\n        </div>\n        <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"rightSide\" noOuter />\n        </div>\n      </div>\n      <FormButtons formId=\"attributeNewForm\" cancelUrl={gridUrl} />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"createAttribute\")\n    gridUrl: url(routeId: \"attributeGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeNew/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request, response) => {\n  setPageMetaInfo(request, {\n    title: 'Create a new attribute',\n    description: 'Create a new attribute'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/attributeNew/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/attributes/new\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit/CategoryEditForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport React from 'react';\n\ninterface CategoryEditFormProps {\n  action: string;\n  gridUrl: string;\n}\n\nexport default function CategoryEditForm({\n  action,\n  gridUrl\n}: CategoryEditFormProps) {\n  return (\n    <Form\n      action={action}\n      method=\"PATCH\"\n      id=\"categoryEditForm\"\n      submitBtn={false}\n    >\n      <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"leftSide\" noOuter />\n        </div>\n        <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"rightSide\" noOuter />\n        </div>\n      </div>\n      <FormButtons formId=\"categoryEditForm\" cancelUrl={gridUrl} />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"updateCategory\", params: [{key: \"id\", value: getContextValue(\"categoryUuid\")}]),\n    gridUrl: url(routeId: \"categoryGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit/Products.tsx",
    "content": "import { ProductListSkeleton } from '@components/admin/ProductListSkeleton.js';\nimport { ProductSelector } from '@components/admin/ProductSelector.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Card,\n  CardContent,\n  CardTitle,\n  CardDescription,\n  CardHeader,\n  CardAction\n} from '@components/common/ui/Card.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\nimport { useQuery } from 'urql';\n\nconst ProductsQuery = `\n  query Query ($id: Int, $filters: [FilterInput!]) {\n    category (id: $id) {\n      products (filters: $filters) {\n        items {\n          productId\n          uuid\n          name\n          sku\n          price {\n            regular {\n              text\n            }\n          }\n          image {\n            url\n          }\n          editUrl\n          removeFromCategoryUrl\n        }\n        total\n      }\n    }\n  }\n`;\n\ninterface ProductsProps {\n  category: {\n    categoryId: number;\n    addProductApi: string;\n  };\n}\n\nexport default function Products({\n  category: { categoryId, addProductApi }\n}: ProductsProps) {\n  const [keyword, setKeyword] = React.useState('');\n  const [loading, setLoading] = React.useState<boolean>(false);\n  const [page, setPage] = React.useState<number>(1);\n  const [removing, setRemoving] = React.useState<string[]>([]);\n\n  const addProductFunction = async (sku, uuid) => {\n    const response = await fetch(addProductApi, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({\n        product_id: uuid\n      }),\n      credentials: 'include'\n    });\n    const data = await response.json();\n    if (!data.success) {\n      toast.error(data.message);\n    } else {\n      reexecuteQuery({ requestPolicy: 'network-only' });\n    }\n  };\n  // Run query again when page changes\n  const [result, reexecuteQuery] = useQuery({\n    query: ProductsQuery,\n    variables: {\n      id: categoryId,\n      filters: !keyword\n        ? [\n            { key: 'page', operation: 'eq', value: page.toString() },\n            { key: 'limit', operation: 'eq', value: '10' }\n          ]\n        : [\n            { key: 'page', operation: 'eq', value: page.toString() },\n            { key: 'limit', operation: 'eq', value: '10' },\n            { key: 'keyword', operation: 'eq', value: keyword }\n          ]\n    },\n    pause: true\n  });\n\n  React.useEffect(() => {\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  }, []);\n\n  const removeProduct = async (api, uuid) => {\n    setRemoving([...removing, uuid]);\n    await fetch(api, {\n      method: 'DELETE',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      credentials: 'same-origin'\n    });\n    setPage(1);\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  };\n\n  React.useEffect(() => {\n    const timer = setTimeout(() => {\n      reexecuteQuery({ requestPolicy: 'network-only' });\n      setLoading(false);\n    }, 1500);\n\n    return () => clearTimeout(timer);\n  }, [keyword]);\n\n  React.useEffect(() => {\n    if (result.fetching) {\n      return;\n    }\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  }, [page]);\n\n  const { data, fetching, error } = result;\n\n  return (\n    <Dialog>\n      <Card>\n        <CardHeader>\n          <CardTitle>Products</CardTitle>\n          <CardDescription>\n            Manage the products assigned to this category.\n          </CardDescription>\n          <CardAction>\n            <DialogTrigger>\n              <Button variant=\"link\">Add Products</Button>\n            </DialogTrigger>\n          </CardAction>\n        </CardHeader>\n        {error && (\n          <CardContent>\n            <span className=\"text-destructive\">{error.message}</span>\n          </CardContent>\n        )}\n\n        <CardContent>\n          <div>\n            <div className=\"mb-5\">\n              <div className=\"form-field\">\n                <Input\n                  type=\"text\"\n                  value={keyword}\n                  placeholder=\"Search products\"\n                  onChange={(e) => {\n                    setLoading(true);\n                    setKeyword(e.target.value);\n                  }}\n                />\n              </div>\n            </div>\n            {data && !loading && (\n              <>\n                {data.category.products.items.length === 0 && (\n                  <div>No product to display.</div>\n                )}\n                <div className=\"flex justify-between\">\n                  <div>\n                    <i>{data.category.products.total} items</i>\n                  </div>\n                  <div>\n                    {data.category.products.total > 10 && (\n                      <div className=\"flex justify-between gap-2\">\n                        {page > 1 && (\n                          <a\n                            className=\"text-interactive\"\n                            href=\"#\"\n                            onClick={(e) => {\n                              e.preventDefault();\n                              setPage(page - 1);\n                            }}\n                          >\n                            Previous\n                          </a>\n                        )}\n                        {page < data.category.products.total / 10 && (\n                          <a\n                            className=\"text-interactive\"\n                            href=\"#\"\n                            onClick={(e) => {\n                              e.preventDefault();\n                              setPage(page + 1);\n                            }}\n                          >\n                            Next\n                          </a>\n                        )}\n                      </div>\n                    )}\n                  </div>\n                </div>\n                <div className=\"divide-y\">\n                  {data.category.products.items.map((p) => (\n                    <div\n                      key={p.uuid}\n                      className=\"flex justify-between py-2 border-divider items-center\"\n                    >\n                      <div className=\"flex justify-items-start gap-5\">\n                        <div className=\"grid-thumbnail text-border border border-divider p-2 rounded flex justify-center w-10 h-10\">\n                          {p.image?.url && (\n                            <img\n                              className=\"self-center\"\n                              src={p.image?.url}\n                              alt=\"\"\n                            />\n                          )}\n                          {!p.image?.url && (\n                            <svg\n                              className=\"self-center\"\n                              xmlns=\"http://www.w3.org/2000/svg\"\n                              width=\"2rem\"\n                              fill=\"none\"\n                              viewBox=\"0 0 24 24\"\n                              stroke=\"currentColor\"\n                            >\n                              <path\n                                strokeLinecap=\"round\"\n                                strokeLinejoin=\"round\"\n                                strokeWidth={2}\n                                d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n                              />\n                            </svg>\n                          )}\n                        </div>\n                        <div>\n                          <a\n                            href={p.editUrl || ''}\n                            className=\"font-semibold hover:underline\"\n                          >\n                            {p.name}\n                          </a>\n                        </div>\n                      </div>\n                      <div className=\"text-right\">\n                        <Button\n                          variant=\"destructive\"\n                          onClick={async () => {\n                            await removeProduct(\n                              p.removeFromCategoryUrl,\n                              p.uuid\n                            );\n                          }}\n                          isLoading={removing.includes(p.uuid)}\n                        >\n                          Remove\n                        </Button>\n                      </div>\n                    </div>\n                  ))}\n                </div>\n              </>\n            )}\n            {(fetching || loading) && <ProductListSkeleton />}\n          </div>\n        </CardContent>\n      </Card>\n      <DialogContent className=\"sm:max-w-[90vw] lg:max-w-200\">\n        <DialogHeader>\n          <DialogTitle>Add Products</DialogTitle>\n          <DialogDescription>\n            Add products to this category by selecting them from the list below.\n          </DialogDescription>\n          {data && (\n            <ProductSelector\n              onSelect={addProductFunction}\n              selectedProducts={data.category.products.items.map((p) => ({\n                sku: p.sku,\n                uuid: p.uuid,\n                productId: p.productId\n              }))}\n            />\n          )}\n        </DialogHeader>\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    category(id: getContextValue(\"categoryId\", null)) {\n      categoryId\n      addProductApi: addProductUrl\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (request, response, next) => {\n  try {\n    const query = select();\n    query.from('category');\n    query.andWhere('category.uuid', '=', request.params.id);\n    query\n      .leftJoin('category_description')\n      .on(\n        'category_description.category_description_category_id',\n        '=',\n        'category.category_id'\n      );\n\n    const category = await query.load(pool);\n\n    if (category === null) {\n      response.status(404);\n      next();\n    } else {\n      setContextValue(request, 'categoryId', category.category_id);\n      setContextValue(request, 'categoryUuid', category.uuid);\n      setPageMetaInfo(request, {\n        title: category.name,\n        description: category.meta_description || category.short_description\n      });\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/categories/edit/:id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit+categoryNew/General.scss",
    "content": "body.categoryEdit,\nbody.categoryNew {\n  .main-content-inner {\n    max-width: 62.5rem;\n    margin: 1.875rem auto;\n  }\n  .page-heading {\n    max-width: 62.5rem;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit+categoryNew/General.tsx",
    "content": "import {\n  CategoryTree,\n  CategoryTreeItem\n} from '@components/admin/CategoryTree.js';\nimport Area from '@components/common/Area.js';\nimport { Editor } from '@components/common/form/Editor.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\nimport './General.scss';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle\n} from '@components/common/ui/Dialog.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport { useFormContext } from 'react-hook-form';\n\nconst ParentCategory: React.FC<{\n  parent: CategoryTreeItem;\n}> = ({ parent }) => {\n  const { setValue } = useFormContext();\n  const [category, setCategory] = React.useState<CategoryTreeItem | null>(\n    parent || null\n  );\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n\n  const handleCategoryChange = (newCategory: CategoryTreeItem | null) => {\n    setCategory(newCategory);\n    setValue('parent_id', newCategory?.categoryId || '');\n  };\n\n  return (\n    <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n      <div className=\"my-3 space-y-3\">\n        <Label>Parent category</Label>\n        {category && (\n          <div className=\"border rounded border-border mb-2 p-2\">\n            {category.path.map((item, index) => (\n              <span key={item.name} className=\"text-gray-500\">\n                {item.name}\n                {index < category.path.length - 1 && ' > '}\n              </span>\n            ))}\n            <span className=\"text-interactive pl-5 hover:underline\">\n              <a\n                href=\"#\"\n                onClick={(e) => {\n                  e.preventDefault();\n                  setDialogOpen(true);\n                }}\n              >\n                Change\n              </a>\n            </span>\n            <span className=\"text-destructive pl-5 hover:underline\">\n              <a\n                href=\"#\"\n                onClick={(e) => {\n                  e.preventDefault();\n                  handleCategoryChange(null);\n                }}\n              >\n                Unlink\n              </a>\n            </span>\n          </div>\n        )}\n        {!category && (\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            onClick={(e) => {\n              e.preventDefault();\n              setDialogOpen(true);\n            }}\n          >\n            Select category\n          </Button>\n        )}\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>Select Parent Category</DialogTitle>\n          </DialogHeader>\n          <CategoryTree\n            selectedCategories={category ? [category] : []}\n            onSelect={(c) => {\n              handleCategoryChange(c);\n              setDialogOpen(false);\n            }}\n          />\n        </DialogContent>\n        <InputField\n          type=\"hidden\"\n          name=\"parent_id\"\n          defaultValue={category?.categoryId || ''}\n        />\n      </div>\n    </Dialog>\n  );\n};\n\ninterface GeneralProps {\n  category: {\n    name: string;\n    description: Array<{\n      id: string;\n      size: number;\n      columns: Array<{\n        id: string;\n        size: number;\n        data: Record<string, any>;\n      }>;\n    }>;\n    categoryId: number;\n    parent: {\n      categoryId: number;\n      name: string;\n      path: Array<{\n        name: string;\n      }>;\n    };\n  };\n}\n\nexport default function General({ category }: GeneralProps) {\n  const fields = [\n    {\n      component: {\n        default: (\n          <InputField\n            name=\"name\"\n            label=\"Category Name\"\n            placeholder=\"Enter Category Name\"\n            defaultValue={category?.name || ''}\n            required\n            validation={{\n              required: 'Category name is required'\n            }}\n          />\n        )\n      },\n      sortOrder: 10,\n      id: 'name'\n    },\n    {\n      component: { default: ParentCategory },\n      props: {\n        parent: category?.parent,\n        currentId: category?.categoryId\n      },\n      sortOrder: 15,\n      id: 'parent'\n    },\n    {\n      component: {\n        default: (\n          <Editor\n            name=\"description\"\n            label=\"Description\"\n            value={category?.description || []}\n          />\n        )\n      },\n      sortOrder: 30\n    }\n  ];\n\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>General</CardTitle>\n        <CardDescription>\n          Manage the general information of the category.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <Area id=\"categoryEditGeneral\" coreComponents={fields} />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    category(id: getContextValue(\"categoryId\", null)) {\n      categoryId\n      name\n      hasChildren\n      description\n      status\n      parent {\n        categoryId\n        hasChildren\n        name\n        path {\n          name\n        }\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit+categoryNew/Image.scss",
    "content": ".image-uploader {\n  border: 2px dashed var(--border);\n  padding: 1.25rem 1.875rem;\n  border-radius: 5px;\n  cursor: pointer;\n  position: relative;\n  .loading {\n    background-color: white;\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    z-index: 11;\n    svg {\n      width: 1.875rem;\n      height: 1.875rem;\n      fill: var(--primary);\n    }\n  }\n}\n.category-image {\n  height: 12.5rem;\n  border: 1px solid var(--divider);\n  text-align: center;\n  img {\n    display: inline;\n    max-width: 100%;\n    max-height: 100%;\n  }\n}\n\n.category__image__loading {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  svg {\n    width: 25px;\n    height: 25px;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit+categoryNew/Image.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport './Image.scss';\nimport { Image, ImageUploader } from '@components/admin/ImageUploader.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport { useFormContext } from 'react-hook-form';\n\ninterface ImageProps {\n  category?: {\n    image?: Image;\n  };\n}\n\nexport default function Image({ category }: ImageProps) {\n  const [image, setImage] = useState(category?.image);\n  const { setValue } = useFormContext();\n\n  useEffect(() => {\n    if (image) {\n      setValue('image', image.url);\n    } else {\n      setValue('image', '');\n    }\n  }, [image, setValue]);\n\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Category Image</CardTitle>\n        <CardDescription>Upload an image for the category.</CardDescription>\n      </CardHeader>\n      <CardContent>\n        <ImageUploader\n          onUpload={(images) => {\n            if (images.length > 0) {\n              setImage(images[0]);\n            }\n          }}\n          isMultiple={false}\n          allowDelete={true}\n          onDelete={() => {\n            setImage(undefined);\n          }}\n          currentImages={image ? [image] : []}\n          targetPath={`catalog/${\n            Math.floor(Math.random() * (9999 - 1000)) + 1000\n          }/${Math.floor(Math.random() * (9999 - 1000)) + 1000}`}\n        />\n        <InputField type=\"hidden\" value={image?.url} name=\"image\" />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    category(id: getContextValue(\"categoryId\", null)) {\n      image {\n        id: uuid\n        url\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit+categoryNew/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface CategoryEditPageHeadingProps {\n  backUrl: string;\n  category?: {\n    name?: string;\n  };\n}\n\nexport default function CategoryEditPageHeading({\n  backUrl,\n  category\n}: CategoryEditPageHeadingProps) {\n  return (\n    <PageHeading\n      backUrl={backUrl}\n      heading={category ? `Editing ${category.name}` : 'Create a new category'}\n    />\n  );\n}\n\nCategoryEditPageHeading.defaultProps = {\n  category: {}\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    category(id: getContextValue(\"categoryId\", null)) {\n      name\n    }\n    backUrl: url(routeId: \"categoryGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit+categoryNew/Seo.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { TextareaField } from '@components/common/form/TextareaField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface CategorySeoProps {\n  category?: {\n    urlKey?: string;\n    metaTitle?: string;\n    metaDescription?: string;\n  };\n}\nexport default function Seo({ category }: CategorySeoProps) {\n  const fields = [\n    {\n      component: {\n        default: (\n          <InputField\n            name=\"url_key\"\n            label=\"URL key\"\n            placeholder=\"Enter URL key\"\n            defaultValue={category?.urlKey || ''}\n            required\n            validation={{\n              required: 'URL key is required',\n              pattern: {\n                value: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,\n                message:\n                  'URL key must be lowercase and can only contain alphanumeric characters and hyphens'\n              }\n            }}\n          />\n        )\n      },\n      sortOrder: 0\n    },\n    {\n      component: {\n        default: (\n          <InputField\n            name=\"meta_title\"\n            label=\"Meta title\"\n            placeholder=\"Enter Meta title\"\n            defaultValue={category?.metaTitle || ''}\n            required\n            validation={{\n              required: 'Meta title is required'\n            }}\n          />\n        )\n      },\n      sortOrder: 10\n    },\n    {\n      component: {\n        default: (\n          <TextareaField\n            name=\"meta_description\"\n            label=\"Meta description\"\n            placeholder=\"Enter Meta description\"\n            defaultValue={category?.metaDescription || ''}\n            required\n            validation={{\n              required: 'Meta description is required'\n            }}\n          />\n        )\n      },\n      sortOrder: 30\n    }\n  ];\n\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Search engine optimize</CardTitle>\n        <CardDescription>\n          Manage the SEO settings of the category.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <Area\n          id=\"categoryEditSeo\"\n          coreComponents={fields}\n          className=\"space-y-2\"\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 60\n};\n\nexport const query = `\n  query Query {\n    category(id: getContextValue('categoryId', null)) {\n      urlKey\n      metaTitle\n      metaKeywords\n      metaDescription\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryEdit+categoryNew/Status.tsx",
    "content": "import { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\nexport interface CategoryStatusProps {\n  category?: {\n    status?: number;\n    includeInNav?: number;\n    showProducts?: number;\n  };\n}\n\nexport default function Status({ category }: CategoryStatusProps) {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Status</CardTitle>\n        <CardDescription>\n          Manage the status settings of the category.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <RadioGroupField\n          name=\"status\"\n          label=\"Status\"\n          options={[\n            { label: 'Disabled', value: 0 },\n            { label: 'Enabled', value: 1 }\n          ]}\n          defaultValue={category?.status === 0 ? 0 : 1}\n          validation={{\n            required: 'This field is required'\n          }}\n        />\n      </CardContent>\n      <CardContent className=\"pt-6 border-t border-border\">\n        <RadioGroupField\n          name=\"include_in_nav\"\n          label=\"Include in Store Menu?\"\n          options={[\n            { label: 'No', value: 0 },\n            { label: 'Yes', value: 1 }\n          ]}\n          defaultValue={category?.includeInNav === 0 ? 0 : 1}\n          validation={{\n            required: 'This field is required'\n          }}\n        />\n      </CardContent>\n      <CardContent className=\"pt-6 border-t border-border\">\n        <RadioGroupField\n          name=\"show_products\"\n          label=\"Show products?\"\n          options={[\n            { label: 'No', value: 0 },\n            { label: 'Yes', value: 1 }\n          ]}\n          defaultValue={category?.showProducts === 0 ? 0 : 1}\n          validation={{\n            required: 'This field is required'\n          }}\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    category(id: getContextValue(\"categoryId\", null)) {\n      status\n      includeInNav\n      showProducts\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryGrid/Grid.jsx",
    "content": "import { GridPagination } from '@components/admin/grid/GridPagination';\nimport { SortableHeader } from '@components/admin/grid/header/Sortable';\nimport { Status } from '@components/admin/Status.js';\nimport Area from '@components/common/Area';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { useAlertContext } from '@components/common/modal/Alert';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport { Card } from '@components/common/ui/Card';\nimport {\n  CardAction,\n  CardContent,\n  CardHeader\n} from '@components/common/ui/Card.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport axios from 'axios';\nimport PropTypes from 'prop-types';\nimport React, { useState } from 'react';\nimport { CategoryNameRow } from './rows/CategoryName.js';\n\nfunction Actions({ categories = [], selectedIds = [] }) {\n  const { openAlert, closeAlert } = useAlertContext();\n  const [isLoading, setIsLoading] = useState(false);\n\n  const deleteCategories = async () => {\n    setIsLoading(true);\n    const promises = categories\n      .filter((category) => selectedIds.includes(category.uuid))\n      .map((category) => axios.delete(category.deleteApi));\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const actions = [\n    {\n      name: 'Delete',\n      onAction: () => {\n        openAlert({\n          heading: `Delete ${selectedIds.length} categories`,\n          content: <div>Can&apos;t be undone</div>,\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Delete',\n            onAction: async () => {\n              await deleteCategories();\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    }\n  ];\n\n  return (\n    <TableRow>\n      {selectedIds.length === 0 && null}\n      {selectedIds.length > 0 && (\n        <TableCell colSpan=\"100\">\n          <ButtonGroup>\n            {actions.map((action, i) => (\n              <Button\n                key={i}\n                variant={'outline'}\n                onClick={(e) => {\n                  e.preventDefault();\n                  action.onAction();\n                }}\n              >\n                {action.name}\n              </Button>\n            ))}\n          </ButtonGroup>\n        </TableCell>\n      )}\n    </TableRow>\n  );\n}\n\nActions.propTypes = {\n  selectedIds: PropTypes.arrayOf(PropTypes.string).isRequired,\n  categories: PropTypes.arrayOf(\n    PropTypes.shape({\n      uuid: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport default function CategoryGrid({\n  categories: { items: categories, total, currentFilters = [] }\n}) {\n  const page = currentFilters.find((filter) => filter.key === 'page')\n    ? parseInt(currentFilters.find((filter) => filter.key === 'page').value, 10)\n    : 1;\n  const limit = currentFilters.find((filter) => filter.key === 'limit')\n    ? parseInt(\n        currentFilters.find((filter) => filter.key === 'limit').value,\n        10\n      )\n    : 20;\n  const [selectedRows, setSelectedRows] = useState([]);\n\n  return (\n    <Card>\n      <CardHeader className=\"flex justify-between\">\n        <Form submitBtn={false} id=\"categoryGridFilter\">\n          <InputField\n            name=\"name\"\n            placeholder=\"Search\"\n            defaultValue={currentFilters.find((f) => f.key === 'name')?.value}\n            onKeyPress={(e) => {\n              // If the user press enter, we should submit the form\n              if (e.key === 'Enter') {\n                const url = new URL(document.location);\n                const name = e.target?.value;\n                if (name) {\n                  url.searchParams.set('name[operation]', 'like');\n                  url.searchParams.set('name[value]', name);\n                } else {\n                  url.searchParams.delete('name[operation]');\n                  url.searchParams.delete('name[value]');\n                }\n                window.location.href = url;\n              }\n            }}\n          />\n        </Form>\n        <CardAction>\n          <Button\n            variant=\"link\"\n            onClick={() => {\n              // Just get the url and remove all query params\n              const url = new URL(document.location);\n              url.search = '';\n              window.location.href = url.href;\n            }}\n          >\n            Clear filters\n          </Button>\n        </CardAction>\n      </CardHeader>\n      <CardContent>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>\n                <div className=\"form-field mb-0\">\n                  <Checkbox\n                    onCheckedChange={(checked) => {\n                      if (checked) {\n                        setSelectedRows(categories.map((c) => c.uuid));\n                      } else {\n                        setSelectedRows([]);\n                      }\n                    }}\n                  />\n                </div>\n              </TableHead>\n              <Area\n                className=\"\"\n                id=\"categoryGridHeader\"\n                noOuter\n                coreComponents={[\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Category Name\"\n                          name=\"name\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 10\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          name=\"status\"\n                          title=\"Status\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 25\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          name=\"include_in_nav\"\n                          title=\"Include In Menu\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 30\n                  }\n                ]}\n              />\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            <Actions\n              categories={categories}\n              selectedIds={selectedRows}\n              setSelectedRows={setSelectedRows}\n            />\n            {categories.map((c) => (\n              <TableRow key={c.categoryId}>\n                <TableCell style={{ width: '2rem' }}>\n                  <div className=\"form-field mb-0\">\n                    <Checkbox\n                      checked={selectedRows.includes(c.uuid)}\n                      onCheckedChange={(checked) => {\n                        if (checked)\n                          setSelectedRows(selectedRows.concat([c.uuid]));\n                        else\n                          setSelectedRows(\n                            selectedRows.filter((r) => r !== c.uuid)\n                          );\n                      }}\n                    />\n                  </div>\n                </TableCell>\n                <Area\n                  className=\"\"\n                  id=\"categoryGridRow\"\n                  row={c}\n                  noOuter\n                  coreComponents={[\n                    {\n                      component: {\n                        default: () => (\n                          <CategoryNameRow id=\"name\" category={c} />\n                        )\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: ({ areaProps }) => (\n                          <Status status={parseInt(c.status, 10)} />\n                        )\n                      },\n                      sortOrder: 25\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <TableCell>{c.includeInNav ? 'Yes' : 'No'}</TableCell>\n                        )\n                      },\n                      sortOrder: 30\n                    }\n                  ]}\n                />\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {categories.length === 0 && (\n          <div className=\"flex w-full justify-center mt-2\">\n            There is no category to display\n          </div>\n        )}\n        <GridPagination total={total} limit={limit} page={page} />\n      </CardContent>\n    </Card>\n  );\n}\n\nCategoryGrid.propTypes = {\n  categories: PropTypes.shape({\n    items: PropTypes.arrayOf(\n      PropTypes.shape({\n        categoryId: PropTypes.number.isRequired,\n        uuid: PropTypes.string.isRequired,\n        name: PropTypes.string.isRequired,\n        status: PropTypes.number.isRequired,\n        includeInNav: PropTypes.number.isRequired,\n        editUrl: PropTypes.string.isRequired,\n        deleteApi: PropTypes.string.isRequired,\n        path: PropTypes.arrayOf(\n          PropTypes.shape({\n            name: PropTypes.string.isRequired\n          })\n        )\n      })\n    ).isRequired,\n    total: PropTypes.number.isRequired,\n    currentFilters: PropTypes.arrayOf(\n      PropTypes.shape({\n        key: PropTypes.string.isRequired,\n        operation: PropTypes.string.isRequired,\n        value: PropTypes.string.isRequired\n      })\n    )\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query($filters: [FilterInput]) {\n    categories (filters: $filters) {\n      items {\n        categoryId\n        uuid\n        name\n        status\n        includeInNav\n        editUrl\n        deleteApi\n        path {\n          name\n        }\n      }\n      total\n      currentFilters {\n        key\n        operation\n        value\n      }\n    }\n  }\n`;\n\nexport const variables = `\n{\n  filters: getContextValue('filtersFromUrl')\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryGrid/NewCategoryButton.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\n\ninterface NewCategoryButtonProps {\n  newCateoryUrl: string;\n}\nexport default function NewCategoryButton({\n  newCateoryUrl\n}: NewCategoryButtonProps) {\n  return (\n    <Button onClick={() => (window.location.href = newCateoryUrl)}>\n      New Category\n    </Button>\n  );\n}\n\nexport const layout = {\n  areaId: 'pageHeadingRight',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    newCateoryUrl: url(routeId: \"categoryNew\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryGrid/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport default function CategoryGridPageHeading() {\n  return <PageHeading heading=\"Categories\" />;\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryGrid/index.ts",
    "content": "import { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default (request: EvershopRequest, response) => {\n  setPageMetaInfo(request, {\n    title: 'Categories',\n    description: 'Categories'\n  });\n  setContextValue(\n    request,\n    'filtersFromUrl',\n    buildFilterFromUrl(request.originalUrl)\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryGrid/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/categories\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryGrid/rows/CategoryName.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface CategoryNameRowProps {\n  category: {\n    editUrl: string;\n    path: Array<{ name: string }>;\n  };\n}\n\nexport function CategoryNameRow({ category }: CategoryNameRowProps) {\n  return (\n    <TableCell>\n      <div>\n        <a className=\"hover:underline font-semibold\" href={category.editUrl}>\n          {category.path.map((p) => p.name).join(' / ')}\n        </a>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryNew/CategoryNewForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface CategoryNewFormProps {\n  action: string;\n  gridUrl: string;\n}\n\nexport default function CategoryNewForm({\n  action,\n  gridUrl\n}: CategoryNewFormProps) {\n  return (\n    <Form\n      action={action}\n      method=\"POST\"\n      id=\"categoryNewForm\"\n      onSuccess={(response) => {\n        toast.success('Category created successfully!');\n        setTimeout(() => {\n          const editUrl = response.data.links.find(\n            (link) => link.rel === 'edit'\n          ).href;\n          window.location.href = editUrl;\n        }, 1500);\n      }}\n      submitBtn={false}\n    >\n      <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"leftSide\" noOuter />\n        </div>\n        <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"rightSide\" noOuter />\n        </div>\n      </div>\n      <FormButtons formId=\"categoryNewForm\" cancelUrl={gridUrl} />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"createCategory\")\n    gridUrl: url(routeId: \"categoryGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryNew/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request, response) => {\n  setPageMetaInfo(request, {\n    title: 'Create a new category',\n    description: 'Create a new category'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/categoryNew/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/categories/new\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionEdit/CollectionEditForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport React from 'react';\n\ninterface CollectionEditFormProps {\n  action: string;\n  gridUrl: string;\n}\n\nexport default function CollectionEditForm({\n  action,\n  gridUrl\n}: CollectionEditFormProps) {\n  return (\n    <Form\n      action={action}\n      method=\"PATCH\"\n      id=\"collectionEditForm\"\n      submitBtn={false}\n    >\n      <div className=\"w-2/3\" style={{ margin: '0 auto' }}>\n        <div className=\"grid gap-5\">\n          <Area id=\"collectionFormInner\" noOuter />\n        </div>\n        <FormButtons formId=\"collectionEditForm\" cancelUrl={gridUrl} />\n      </div>\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"updateCollection\", params: [{key: \"id\", value: getContextValue(\"collectionUuid\")}]),\n    gridUrl: url(routeId: \"collectionGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionEdit/Products.tsx",
    "content": "import { ProductListSkeleton } from '@components/admin/ProductListSkeleton.js';\nimport { ProductSelector } from '@components/admin/ProductSelector.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Card,\n  CardAction,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\nimport { useQuery } from 'urql';\n\nconst ProductsQuery = `\n  query Query ($code: String!, $filters: [FilterInput!]) {\n    collection (code: $code) {\n      products (filters: $filters) {\n        items {\n          productId\n          uuid\n          name\n          sku\n          price {\n            regular {\n              text\n            }\n          }\n          image {\n            url\n          }\n          editUrl\n          removeFromCollectionUrl\n        }\n        total\n      }\n    }\n  }\n`;\n\ninterface ProductsProps {\n  collection: {\n    code: string;\n    addProductApi: string;\n  };\n}\n\nexport default function Products({\n  collection: { code, addProductApi }\n}: ProductsProps) {\n  const [keyword, setKeyword] = React.useState('');\n  const [loading, setLoading] = React.useState(false);\n  const [page, setPage] = React.useState(1);\n  const [removing, setRemoving] = React.useState<string[]>([]);\n\n  const addProductFunction = async (sku, uuid) => {\n    const response = await fetch(addProductApi, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({\n        product_id: uuid\n      }),\n      credentials: 'include'\n    });\n    const data = await response.json();\n    if (!data.success) {\n      toast.error(data.message);\n    } else {\n      reexecuteQuery({ requestPolicy: 'network-only' });\n    }\n  };\n\n  // Run query again when page changes\n  const [result, reexecuteQuery] = useQuery({\n    query: ProductsQuery,\n    variables: {\n      code,\n      filters: !keyword\n        ? [\n            { key: 'page', operation: 'eq', value: page.toString() },\n            { key: 'limit', operation: 'eq', value: '10' }\n          ]\n        : [\n            { key: 'page', operation: 'eq', value: page.toString() },\n            { key: 'limit', operation: 'eq', value: '10' },\n            { key: 'keyword', operation: 'eq', value: keyword }\n          ]\n    },\n    pause: true\n  });\n\n  React.useEffect(() => {\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  }, []);\n\n  const removeProduct = async (api, uuid) => {\n    setRemoving([...removing, uuid]);\n    await fetch(api, {\n      method: 'DELETE',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      credentials: 'same-origin'\n    });\n    setPage(1);\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  };\n\n  React.useEffect(() => {\n    const timer = setTimeout(() => {\n      reexecuteQuery({ requestPolicy: 'network-only' });\n      setLoading(false);\n    }, 1500);\n\n    return () => clearTimeout(timer);\n  }, [keyword]);\n\n  React.useEffect(() => {\n    if (result.fetching) {\n      return;\n    }\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  }, [page]);\n\n  const { data, fetching, error } = result;\n\n  return (\n    <Dialog>\n      <Card>\n        <CardHeader>\n          <CardTitle>Products</CardTitle>\n          <CardDescription>\n            Manage the products assigned to this collection.\n          </CardDescription>\n          <CardAction>\n            <DialogTrigger>\n              <Button variant=\"link\">Add Products</Button>\n            </DialogTrigger>\n          </CardAction>\n        </CardHeader>\n        {error && (\n          <CardContent>\n            <span className=\"text-destructive\">{error.message}</span>\n          </CardContent>\n        )}\n\n        <CardContent>\n          <div>\n            <div className=\"mb-5\">\n              <Input\n                type=\"text\"\n                value={keyword}\n                placeholder=\"Search products\"\n                onChange={(e) => {\n                  setLoading(true);\n                  setKeyword(e.target.value);\n                }}\n              />\n            </div>\n            {data && !loading && (\n              <>\n                {data.collection.products.items.length === 0 && (\n                  <div>No product to display.</div>\n                )}\n                <div className=\"flex justify-between\">\n                  <div>\n                    <i>{data.collection.products.total} items</i>\n                  </div>\n                  <div>\n                    {data.collection.products.total > 10 && (\n                      <div className=\"flex justify-between gap-2\">\n                        {page > 1 && (\n                          <a\n                            className=\"text-interactive\"\n                            href=\"#\"\n                            onClick={(e) => {\n                              e.preventDefault();\n                              setPage(page - 1);\n                            }}\n                          >\n                            Previous\n                          </a>\n                        )}\n                        {page < data.collection.products.total / 10 && (\n                          <a\n                            className=\"text-interactive\"\n                            href=\"#\"\n                            onClick={(e) => {\n                              e.preventDefault();\n                              setPage(page + 1);\n                            }}\n                          >\n                            Next\n                          </a>\n                        )}\n                      </div>\n                    )}\n                  </div>\n                </div>\n                <div className=\"divide-y\">\n                  {data.collection.products.items.map((p) => (\n                    <div\n                      key={p.uuid}\n                      className=\"flex justify-between py-2 border-divider items-center\"\n                    >\n                      <div className=\"flex justify-items-start gap-5\">\n                        <div className=\"grid-thumbnail text-border border border-divider p-2 rounded flex justify-center w-10 h-10\">\n                          {p.image?.url && (\n                            <img\n                              className=\"self-center\"\n                              src={p.image?.url}\n                              alt=\"\"\n                            />\n                          )}\n                          {!p.image?.url && (\n                            <svg\n                              className=\"self-center\"\n                              xmlns=\"http://www.w3.org/2000/svg\"\n                              width=\"2rem\"\n                              fill=\"none\"\n                              viewBox=\"0 0 24 24\"\n                              stroke=\"currentColor\"\n                            >\n                              <path\n                                strokeLinecap=\"round\"\n                                strokeLinejoin=\"round\"\n                                strokeWidth={2}\n                                d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n                              />\n                            </svg>\n                          )}\n                        </div>\n                        <div>\n                          <a\n                            href={p.editUrl || ''}\n                            className=\"font-semibold hover:underline\"\n                          >\n                            {p.name}\n                          </a>\n                        </div>\n                      </div>\n                      <div className=\"text-right\">\n                        <Button\n                          variant=\"destructive\"\n                          onClick={async () => {\n                            await removeProduct(\n                              p.removeFromCollectionUrl,\n                              p.uuid\n                            );\n                          }}\n                          isLoading={removing.includes(p.uuid)}\n                        >\n                          Remove\n                        </Button>\n                      </div>\n                    </div>\n                  ))}\n                </div>\n              </>\n            )}\n            {(fetching || loading) && <ProductListSkeleton />}\n          </div>\n        </CardContent>\n      </Card>\n      <DialogContent className=\"sm:max-w-[90vw] lg:max-w-200\">\n        <DialogHeader>\n          <DialogTitle>Add Products</DialogTitle>\n          <DialogDescription>\n            Select products to add to this collection.\n          </DialogDescription>\n        </DialogHeader>\n        {data && (\n          <ProductSelector\n            onSelect={addProductFunction}\n            selectedProducts={data.collection.products.items.map((p) => ({\n              sku: p.sku,\n              uuid: p.uuid,\n              productId: p.productId\n            }))}\n          />\n        )}\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nexport const layout = {\n  areaId: 'collectionFormInner',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query {\n    collection(code: getContextValue(\"collectionCode\", null)) {\n      collectionId\n      code\n      addProductApi: addProductUrl\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionEdit/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (request, response, next) => {\n  try {\n    const query = select();\n    query.from('collection');\n    query.andWhere('collection.uuid', '=', request.params.id);\n    const collection = await query.load(pool);\n    if (collection === null) {\n      response.status(404);\n      next();\n    } else {\n      setContextValue(request, 'collectionCode', collection.code);\n      setContextValue(request, 'collectionUuid', collection.uuid);\n      setPageMetaInfo(request, {\n        title: collection.name,\n        description: collection.description\n      });\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionEdit/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/collections/edit/:id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionEdit+collectionNew/General.scss",
    "content": "body.collectionEdit,\nbody.collectionNew {\n  .main-content-inner {\n    max-width: 62.5rem;\n    margin: 1.875rem auto;\n  }\n  .page-heading {\n    max-width: 62.5rem;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionEdit+collectionNew/General.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Editor, Row } from '@components/common/form/Editor.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport './General.scss';\nimport React from 'react';\n\ninterface GeneralProps {\n  collection?: {\n    collectionId?: string;\n    name?: string;\n    code?: string;\n    description?: Row[];\n  };\n}\n\nexport default function General({ collection }: GeneralProps) {\n  const fields = [\n    {\n      component: {\n        default: (\n          <InputField\n            name=\"name\"\n            label=\"Collection Name\"\n            placeholder=\"Enter Collection Name\"\n            defaultValue={collection?.name || ''}\n            required\n          />\n        )\n      },\n      sortOrder: 10,\n      id: 'name'\n    },\n    {\n      component: {\n        default: (\n          <InputField\n            name=\"code\"\n            label=\"Collection Code\"\n            defaultValue={collection?.code || ''}\n            required\n            validation={{\n              required: 'Collection code is required',\n              pattern: {\n                value: /^[a-zA-Z0-9_-]+$/,\n                message:\n                  'Collection code must be alphanumeric and can include underscores or dashes.'\n              }\n            }}\n            placeholder=\"Collection Code\"\n          />\n        )\n      },\n      sortOrder: 15,\n      id: 'code'\n    },\n    {\n      component: {\n        default: (\n          <Editor\n            name=\"description\"\n            label=\"Description\"\n            value={collection?.description || []}\n          />\n        )\n      },\n      sortOrder: 30\n    }\n  ];\n\n  return (\n    <Card title=\"General\">\n      <CardHeader>\n        <CardTitle>General Information</CardTitle>\n        <CardDescription>\n          Manage general information about the collection.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <Area\n          id=\"collectionEditGeneral\"\n          coreComponents={fields}\n          className=\"space-y-2\"\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'collectionFormInner',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    collection(code: getContextValue(\"collectionCode\", null)) {\n      collectionId\n      name\n      code\n      description\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionEdit+collectionNew/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface CollectionEditPageHeadingProps {\n  backUrl: string;\n  collection?: {\n    name?: string;\n  };\n}\n\nexport default function CollectionEditPageHeading({\n  backUrl,\n  collection = {}\n}: CollectionEditPageHeadingProps) {\n  return (\n    <div className=\"w-2/3\" style={{ margin: '0 auto' }}>\n      <PageHeading\n        backUrl={backUrl}\n        heading={\n          collection ? `Editing ${collection.name}` : 'Create a new collection'\n        }\n      />\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    collection(code: getContextValue(\"collectionCode\", null)) {\n      name\n    }\n    backUrl: url(routeId: \"collectionGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionGrid/Grid.jsx",
    "content": "import { GridPagination } from '@components/admin/grid/GridPagination';\nimport { DummyColumnHeader } from '@components/admin/grid/header/Dummy';\nimport { SortableHeader } from '@components/admin/grid/header/Sortable';\nimport Area from '@components/common/Area';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { useAlertContext } from '@components/common/modal/Alert';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport {\n  Card,\n  CardAction,\n  CardContent,\n  CardHeader\n} from '@components/common/ui/Card.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport {\n  Table,\n  TableCell,\n  TableRow,\n  TableHeader,\n  TableHead,\n  TableBody\n} from '@components/common/ui/Table.js';\nimport axios from 'axios';\nimport PropTypes from 'prop-types';\nimport React, { useState } from 'react';\nimport { CollectionNameRow } from './rows/CollectionNameRow.js';\n\nfunction Actions({ collections = [], selectedIds = [] }) {\n  const { openAlert, closeAlert } = useAlertContext();\n  const [isLoading, setIsLoading] = useState(false);\n\n  const deleteCategories = async () => {\n    setIsLoading(true);\n    const promises = collections\n      .filter((c) => selectedIds.includes(c.uuid))\n      .map((col) => axios.delete(col.deleteApi));\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const actions = [\n    {\n      name: 'Delete',\n      onAction: () => {\n        openAlert({\n          heading: `Delete ${selectedIds.length} collections`,\n          content: <div>Can&apos;t be undone</div>,\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Delete',\n            onAction: async () => {\n              await deleteCategories();\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    }\n  ];\n\n  return (\n    <TableRow>\n      {selectedIds.length === 0 && null}\n      {selectedIds.length > 0 && (\n        <TableCell colSpan=\"100\">\n          <ButtonGroup>\n            {actions.map((action, i) => (\n              <Button\n                key={i}\n                variant={'outline'}\n                onClick={(e) => {\n                  e.preventDefault();\n                  action.onAction();\n                }}\n              >\n                {action.name}\n              </Button>\n            ))}\n          </ButtonGroup>\n        </TableCell>\n      )}\n    </TableRow>\n  );\n}\n\nActions.propTypes = {\n  selectedIds: PropTypes.arrayOf(PropTypes.string).isRequired,\n  collections: PropTypes.arrayOf(\n    PropTypes.shape({\n      uuid: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport default function CollectionGrid({\n  collections: { items: collections, total, currentFilters = [] }\n}) {\n  const page = currentFilters.find((filter) => filter.key === 'page')\n    ? parseInt(currentFilters.find((filter) => filter.key === 'page').value, 10)\n    : 1;\n  const limit = currentFilters.find((filter) => filter.key === 'limit')\n    ? parseInt(\n        currentFilters.find((filter) => filter.key === 'limit').value,\n        10\n      )\n    : 20;\n  const [selectedRows, setSelectedRows] = useState([]);\n\n  return (\n    <div className=\"w-2/3\" style={{ margin: '0 auto' }}>\n      <Card>\n        <CardHeader className=\"flex justify-between\">\n          <Form submitBtn={false} id=\"collectionGridFilter\">\n            <InputField\n              name=\"name\"\n              placeholder=\"Search\"\n              defaultValue={currentFilters.find((f) => f.key === 'name')?.value}\n              onKeyPress={(e) => {\n                // If the user press enter, we should submit the form\n                if (e.key === 'Enter') {\n                  const url = new URL(document.location);\n                  const name = e.target?.value;\n                  if (name) {\n                    url.searchParams.set('name[operation]', 'like');\n                    url.searchParams.set('name[value]', name);\n                  } else {\n                    url.searchParams.delete('name[operation]');\n                    url.searchParams.delete('name[value]');\n                  }\n                  window.location.href = url;\n                }\n              }}\n            />\n          </Form>\n          <CardAction>\n            <Button\n              variant=\"link\"\n              onClick={() => {\n                // Just get the url and remove all query params\n                const url = new URL(document.location);\n                url.search = '';\n                window.location.href = url.href;\n              }}\n            >\n              Clear filters\n            </Button>\n          </CardAction>\n        </CardHeader>\n        <CardContent>\n          <Table>\n            <TableHeader>\n              <TableRow>\n                <TableHead>\n                  <div className=\"form-field mb-0\">\n                    <Checkbox\n                      onCheckedChange={(checked) => {\n                        if (checked) {\n                          setSelectedRows(collections.map((c) => c.uuid));\n                        } else {\n                          setSelectedRows([]);\n                        }\n                      }}\n                    />\n                  </div>\n                </TableHead>\n                <Area\n                  className=\"\"\n                  id=\"collectionGridHeader\"\n                  noOuter\n                  coreComponents={[\n                    {\n                      component: {\n                        default: () => (\n                          <DummyColumnHeader\n                            title=\"ID\"\n                            id=\"collectionId\"\n                            currentFilters={currentFilters}\n                          />\n                        )\n                      },\n                      sortOrder: 5\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <SortableHeader\n                            title=\"Collection Name\"\n                            name=\"name\"\n                            currentFilters={currentFilters}\n                          />\n                        )\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <SortableHeader\n                            title=\"Code\"\n                            name=\"code\"\n                            currentFilters={currentFilters}\n                          />\n                        )\n                      },\n                      sortOrder: 15\n                    }\n                  ]}\n                />\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              <Actions\n                collections={collections}\n                selectedIds={selectedRows}\n                setSelectedRows={setSelectedRows}\n              />\n              {collections.map((c) => (\n                <TableRow key={c.collectionId}>\n                  <TableCell style={{ width: '2rem' }}>\n                    <div className=\"form-field mb-0\">\n                      <Checkbox\n                        checked={selectedRows.includes(c.uuid)}\n                        onCheckedChange={(checked) => {\n                          if (checked)\n                            setSelectedRows(selectedRows.concat([c.uuid]));\n                          else\n                            setSelectedRows(\n                              selectedRows.filter((r) => r !== c.uuid)\n                            );\n                        }}\n                      />\n                    </div>\n                  </TableCell>\n                  <Area\n                    className=\"\"\n                    id=\"collectionGridRow\"\n                    row={c}\n                    noOuter\n                    coreComponents={[\n                      {\n                        component: {\n                          default: () => (\n                            <TableCell>{c.collectionId.toString()}</TableCell>\n                          )\n                        },\n                        sortOrder: 5\n                      },\n                      {\n                        component: {\n                          default: () => (\n                            <CollectionNameRow\n                              id=\"name\"\n                              name={c.name}\n                              url={c.editUrl}\n                            />\n                          )\n                        },\n                        sortOrder: 10\n                      },\n                      {\n                        component: {\n                          default: () => <TableCell>{c.code}</TableCell>\n                        },\n                        sortOrder: 15\n                      }\n                    ]}\n                  />\n                </TableRow>\n              ))}\n            </TableBody>\n          </Table>\n          {collections.length === 0 && (\n            <div className=\"flex w-full justify-center mt-2\">\n              There is no collections to display\n            </div>\n          )}\n          <GridPagination total={total} limit={limit} page={page} />\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n\nCollectionGrid.propTypes = {\n  collections: PropTypes.shape({\n    items: PropTypes.arrayOf(\n      PropTypes.shape({\n        collectionId: PropTypes.number.isRequired,\n        uuid: PropTypes.string.isRequired,\n        name: PropTypes.string.isRequired,\n        code: PropTypes.string.isRequired,\n        editUrl: PropTypes.string.isRequired,\n        deleteApi: PropTypes.string.isRequired\n      })\n    ).isRequired,\n    total: PropTypes.number.isRequired,\n    currentFilters: PropTypes.arrayOf(\n      PropTypes.shape({\n        key: PropTypes.string.isRequired,\n        operation: PropTypes.string.isRequired,\n        value: PropTypes.string.isRequired\n      })\n    )\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query($filters: [FilterInput]) {\n    collections (filters: $filters) {\n      items {\n        collectionId\n        uuid\n        name\n        code\n        editUrl\n        deleteApi\n      }\n      total\n      currentFilters {\n        key\n        operation\n        value\n      }\n    }\n  }\n`;\n\nexport const variables = `\n{\n  filters: getContextValue('filtersFromUrl')\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionGrid/NewCollectionButton.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\n\ninterface NewCollectionButtonProps {\n  newCollectionUrl: string;\n}\nexport default function NewCollectionButton({\n  newCollectionUrl\n}: NewCollectionButtonProps) {\n  return (\n    <Button onClick={() => (window.location.href = newCollectionUrl)}>\n      New Collection\n    </Button>\n  );\n}\n\nexport const layout = {\n  areaId: 'pageHeadingRight',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    newCollectionUrl: url(routeId: \"collectionNew\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionGrid/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport default function CollectionGridPageHeading() {\n  return (\n    <div className=\"w-2/3\" style={{ margin: '0 auto' }}>\n      <PageHeading heading=\"Collections\" />\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionGrid/index.ts",
    "content": "import { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default (request: EvershopRequest, response) => {\n  setPageMetaInfo(request, {\n    title: 'Collections',\n    description: 'Collections'\n  });\n  setContextValue(\n    request,\n    'filtersFromUrl',\n    buildFilterFromUrl(request.originalUrl)\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionGrid/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/collections\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionGrid/rows/CollectionNameRow.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface CollectionNameRowProps {\n  name: string;\n  url: string;\n}\n\nexport function CollectionNameRow({ name, url }: CollectionNameRowProps) {\n  return (\n    <TableCell>\n      <div>\n        <a className=\"hover:underline font-semibold\" href={url}>\n          {name}\n        </a>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionNew/CollectionNewForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface CollectionNewFormProps {\n  action: string;\n  gridUrl: string;\n}\n\nexport default function CollectionNewForm({\n  action,\n  gridUrl\n}: CollectionNewFormProps) {\n  return (\n    <Form\n      id=\"collectionNewForm\"\n      action={action}\n      submitBtn={false}\n      method=\"POST\"\n      onSuccess={(response) => {\n        toast.success('Collection created successfully!');\n        setTimeout(() => {\n          const editUrl = response.data.links.find(\n            (link) => link.rel === 'edit'\n          ).href;\n          window.location.href = editUrl;\n        }, 1500);\n      }}\n    >\n      <div className=\"w-2/3\" style={{ margin: '0 auto' }}>\n        <div className=\"grid gap-5\">\n          <Area id=\"collectionFormInner\" noOuter />\n        </div>\n        <FormButtons formId=\"collectionNewForm\" cancelUrl={gridUrl} />\n      </div>\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"createCollection\")\n    gridUrl: url(routeId: \"collectionGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionNew/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request, response) => {\n  setPageMetaInfo(request, {\n    title: 'Create a new collection',\n    description: 'Create a new collection'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/collectionNew/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/collections/new\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/Collection.tsx",
    "content": "import {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport { TagIcon } from 'lucide-react';\nimport React from 'react';\n\nexport interface Collection {\n  uuid: string;\n  name: string;\n  editUrl: string;\n}\nexport default function Collections({\n  product: { collections }\n}: {\n  product: {\n    collections: Collection[];\n  };\n}): React.ReactElement {\n  return (\n    <Card className=\"bg-popover\">\n      <CardHeader>\n        <CardTitle>Collections</CardTitle>\n        <CardDescription>\n          Manage the collections associated with this product.\n        </CardDescription>\n      </CardHeader>\n      <CardContent className=\"space-y-2\">\n        {collections.map((collection) => (\n          <div\n            className=\"flex justify-start gap-2 items-center align-middle\"\n            key={collection.uuid}\n          >\n            <TagIcon width={16} height={16} />\n            <a href={collection.editUrl} className=\"hover:underline\">\n              <span>{collection.name}</span>\n            </a>\n          </div>\n        ))}\n        {collections.length === 0 && (\n          <div className=\"text-gray-500\">No collections</div>\n        )}\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    product(id: getContextValue(\"productId\", null)) {\n      collections {\n        uuid\n        name\n        editUrl\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/ProductEditForm.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Form, useFormContext } from '@components/common/form/Form.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\nimport { SubmitHandler, useForm } from 'react-hook-form';\nimport { toast } from 'react-toastify';\n\nconst FormButton: React.FC<{\n  formId: string;\n  cancelUrl: string;\n}> = ({ cancelUrl, formId }) => {\n  const {\n    formState: { isSubmitting }\n  } = useFormContext();\n  return (\n    <div className=\"form-submit-button flex border-t border-border mt-4 pt-4 justify-between\">\n      <Button\n        size={'lg'}\n        variant=\"destructive\"\n        onClick={() => {\n          window.location.href = cancelUrl;\n        }}\n      >\n        Cancel\n      </Button>\n      <Button\n        onClick={() => {\n          (document.getElementById(formId) as HTMLFormElement).dispatchEvent(\n            new Event('submit', { cancelable: true, bubbles: true })\n          );\n        }}\n        isLoading={isSubmitting}\n      >\n        Save\n      </Button>\n    </div>\n  );\n};\n\nexport default function ProductEditForm({\n  action,\n  gridUrl\n}: {\n  action: string;\n  gridUrl: string;\n}) {\n  const form = useForm({\n    shouldUnregister: true\n  });\n  const submit: SubmitHandler<any> = async (data) => {\n    try {\n      const images = (data.images || []).map(\n        (image: { uuid: string; url: string }) => image.url\n      );\n      data.images = images;\n      const response = await fetch(action, {\n        method: 'PATCH',\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify({ ...data, action: undefined, method: undefined })\n      });\n      const result = await response.json();\n      if (result.error) {\n        toast.error(result.error.message);\n      } else {\n        toast.success('Product updated successfully');\n        form.setValue('product_id', result.data.uuid);\n      }\n    } catch (error) {\n      toast.error(error.message);\n    }\n  };\n  return (\n    <Form\n      form={form}\n      action={action}\n      method=\"PATCH\"\n      submitBtn={false}\n      id=\"productEditForm\"\n      onSubmit={submit}\n    >\n      <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"leftSide\" noOuter />\n        </div>\n        <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"rightSide\" noOuter />\n        </div>\n      </div>\n      <FormButton formId=\"productEditForm\" cancelUrl={gridUrl} />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"updateProduct\", params: [{key: \"id\", value: getContextValue(\"productUuid\")}]),\n    gridUrl: url(routeId: \"productGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/VariantGroup.tsx",
    "content": "import {\n  Card,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\nimport { New } from './variants/New.js';\nimport { Variants } from './variants/Variants.js';\n\nexport interface VariantGroup {\n  variantGroupId: number;\n  addItemApi: string;\n  attributes: Array<VariantAttribute>;\n}\n\nexport interface VariantProduct {\n  productId: number;\n  uuid: string;\n  variantGroup?: VariantGroup | null;\n}\n\nexport interface VariantAttribute {\n  attributeId: number;\n  attributeCode: string;\n  attributeName: string;\n  options: Array<{\n    optionId: number;\n    optionText: string;\n  }>;\n}\n\nexport interface VariantGroupProps {\n  product: VariantProduct;\n  createVariantGroupApi: string;\n  createProductApi: string;\n}\n\nconst VariantGroup: React.FC<VariantGroupProps> = ({\n  product,\n  createVariantGroupApi,\n  createProductApi\n}) => {\n  const [group, setGroup] = React.useState<VariantGroup | null>(\n    product?.variantGroup || null\n  );\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Variant Group</CardTitle>\n        <CardDescription>\n          Manage the variant group of the product.\n        </CardDescription>\n      </CardHeader>\n      {!group && (\n        <New\n          currentProductUuid={product.uuid}\n          createVariantGroupApi={createVariantGroupApi}\n          setGroup={setGroup}\n        />\n      )}\n      {group && (\n        <Variants\n          productId={product.productId}\n          productUuid={product.uuid}\n          variantGroup={group}\n          createProductApi={createProductApi}\n        />\n      )}\n    </Card>\n  );\n};\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 70\n};\n\nexport const query = `\nquery Query {\n  product(id: getContextValue('productId', null)) {\n    productId\n    uuid\n    variantGroup {\n      variantGroupId\n      attributes: variantAttributes {\n        attributeId\n        attributeCode\n        attributeName\n        options {\n          optionId\n          optionText\n        }\n      }\n      addItemApi\n    }\n  }\n  createVariantGroupApi: url(routeId: \"createVariantGroup\")\n  createProductApi: url(routeId: \"createProduct\")\n}\n`;\n\nexport default VariantGroup;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (request, response, next) => {\n  try {\n    const query = select();\n    query.from('product');\n    query.andWhere('product.uuid', '=', request.params.id);\n    query\n      .leftJoin('product_description')\n      .on(\n        'product_description.product_description_product_id',\n        '=',\n        'product.product_id'\n      );\n    const product = await query.load(pool);\n\n    if (product === null) {\n      response.status(404);\n      next();\n    } else {\n      setContextValue(request, 'productId', product.product_id);\n      setContextValue(request, 'productUuid', product.uuid);\n      setPageMetaInfo(request, {\n        title: product.meta_title || product.name,\n        description: product.meta_description || product.short_description\n      });\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/products/edit/:id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/variants/CreateVariant.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport React from 'react';\nimport { VariantGroup } from '../VariantGroup.js';\nimport { VariantModal } from './VariantModal.js';\n\nexport const CreateVariant: React.FC<{\n  variantGroup: VariantGroup;\n  createProductApi: string;\n  refresh: () => void;\n}> = ({ variantGroup, createProductApi, refresh }) => {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n\n  return (\n    <div className=\"mt-3\">\n      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n        <DialogTrigger>\n          <Button variant={'outline'}>Add Variant</Button>\n        </DialogTrigger>\n        <DialogContent className={'sm:max-w-212.5'}>\n          <DialogHeader>\n            <DialogTitle>New Variant</DialogTitle>\n            <DialogDescription>\n              Create a new variant for this product.\n            </DialogDescription>\n          </DialogHeader>\n          <VariantModal\n            refresh={refresh}\n            closeDialog={() => setDialogOpen(false)}\n            variantGroup={variantGroup}\n            createProductApi={createProductApi}\n          />\n        </DialogContent>\n      </Dialog>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/variants/CreateVariantGroup.tsx",
    "content": "import Spinner from '@components/admin/Spinner.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport React from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport { toast } from 'react-toastify';\nimport { useQuery } from 'urql';\nimport { VariantGroup } from '../VariantGroup.js';\n\nconst AttributesQuery = `\n  query Query($filters: [FilterInput]) {\n    attributes(filters: $filters) {\n      items {\n        attributeId\n        attributeCode\n        attributeName\n        options {\n          value: attributeOptionId\n          text: optionText\n        }\n      }\n    }\n  }\n`;\n\ninterface Attribute {\n  attributeId: number;\n  attributeCode: string;\n  attributeName: string;\n  options: Array<{\n    value: number;\n    text: string;\n  }>;\n}\nexport const CreateVariantGroup: React.FC<{\n  currentProductUuid: string;\n  createVariantGroupApi: string;\n  onCancel: () => void;\n  setGroup: (group: VariantGroup) => void;\n}> = ({ currentProductUuid, createVariantGroupApi, onCancel, setGroup }) => {\n  const [attributes, setAttributes] = React.useState<string[]>([]);\n  const { getValues } = useFormContext();\n  const groupId = getValues('group_id');\n\n  const onCreate = async (e) => {\n    e.preventDefault();\n    const response = await fetch(createVariantGroupApi, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({\n        attribute_codes: attributes.map((a) => a),\n        attribute_group_id: groupId\n      })\n    }).then((r) => r.json());\n\n    if (!response.error) {\n      // Call addItemApi to add the current product to the new variant group\n      const addItemApi = response.data.addItemApi;\n      const addItemResponse = await fetch(addItemApi, {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify({\n          product_id: currentProductUuid\n        })\n      }).then((r) => r.json());\n      if (addItemResponse.error) {\n        toast.error(addItemResponse.error.message);\n        return;\n      }\n      setGroup({\n        variantGroupId: response.data.variant_group_id,\n        addItemApi: response.data.addItemApi,\n        attributes: response.data.attributes.map((attribute) => ({\n          attributeCode: attribute.attribute_code,\n          uuid: attribute.uuid,\n          attributeName: attribute.attribute_name,\n          attributeId: attribute.attribute_id,\n          options: attribute.options.map((option) => ({\n            optionId: option.attribute_option_id,\n            optionText: option.option_text\n          }))\n        }))\n      });\n    } else {\n      toast.error(response.error.message);\n    }\n  };\n\n  const [result] = useQuery({\n    query: AttributesQuery,\n    variables: {\n      filters: [\n        { key: 'type', operation: 'eq', value: 'select' },\n        { key: 'group', operation: 'eq', value: groupId }\n      ]\n    },\n    pause: !groupId\n  });\n\n  const { data, fetching, error } = result as {\n    data: {\n      attributes: {\n        items: Array<Attribute>;\n      };\n    };\n    fetching: boolean;\n    error: Error | null;\n  };\n  if (fetching) {\n    return (\n      <div className=\"flex justify-center items-center\">\n        <Spinner width={30} height={30} />\n      </div>\n    );\n  }\n\n  if (error) {\n    return <p className=\"text-destructive\">{error.message}</p>;\n  }\n\n  return (\n    <div>\n      <div>\n        {(data?.attributes?.items || []).length > 0 && (\n          <div className=\"space-y-2\">\n            <div>\n              <span>Select the list of attribute</span>\n            </div>\n            {(data?.attributes?.items || []).map((a) => (\n              <label key={a.attributeCode} className=\"flex items-center gap-2\">\n                <Checkbox\n                  onCheckedChange={(checked) => {\n                    if (checked) {\n                      setAttributes(attributes.concat(a.attributeCode));\n                    } else {\n                      setAttributes(\n                        attributes.filter((attr) => a.attributeCode !== attr)\n                      );\n                    }\n                  }}\n                />\n                <span>{a.attributeName}</span>\n              </label>\n            ))}\n            <div className=\"mt-5 space-x-2\">\n              <Button\n                variant={'default'}\n                className={'hover:cursor-pointer'}\n                onClick={(e) => onCreate(e)}\n              >\n                Create\n              </Button>\n              <Button\n                variant=\"destructive\"\n                onClick={(e) => {\n                  e.preventDefault();\n                  onCancel();\n                }}\n              >\n                Cancel\n              </Button>\n            </div>\n          </div>\n        )}\n        {(data?.attributes?.items || []).length === 0 && (\n          <div className=\"alert alert-danger\" role=\"alert\">\n            There is no &quot;Select&quot; attribute available.\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/variants/EditVariant.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport { Cog } from 'lucide-react';\nimport React from 'react';\nimport { VariantGroup } from '../VariantGroup.js';\nimport { VariantModal } from './VariantModal.js';\nimport { VariantItem } from './Variants.js';\n\nexport const EditVariant: React.FC<{\n  variant: VariantItem;\n  refresh: () => void;\n  variantGroup: VariantGroup;\n}> = ({ variant, variantGroup, refresh }) => {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n\n  return (\n    <div>\n      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n        <DialogTrigger>\n          <Button variant={'link'} className={'hover:cursor-pointer'}>\n            <Cog className=\"w-5 h-5 text-primary\" />\n          </Button>\n        </DialogTrigger>\n        <DialogContent className={'sm:max-w-212.5'}>\n          <DialogHeader>\n            <DialogTitle>Edit Variant</DialogTitle>\n            <DialogDescription>\n              Update the variant details and attributes here.\n            </DialogDescription>\n          </DialogHeader>\n          <VariantModal\n            variant={variant}\n            variantGroup={variantGroup}\n            refresh={refresh}\n            closeDialog={() => setDialogOpen(false)}\n          />\n        </DialogContent>\n      </Dialog>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/variants/New.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport { Card, CardContent } from '@components/common/ui/Card.js';\nimport React from 'react';\nimport { CreateVariantGroup } from './CreateVariantGroup.js';\n\nexport const New: React.FC<{\n  currentProductUuid: string;\n  createVariantGroupApi: string;\n  setGroup: (group: any) => void;\n}> = ({ currentProductUuid, createVariantGroupApi, setGroup }) => {\n  const [action, setAction] = React.useState<'create' | undefined>();\n  return (\n    <>\n      <CardContent>\n        {action === undefined && (\n          <div>\n            <div className=\"justify-left text-left\">\n              <div className=\"space-y-2\">\n                <div>This product has some variants like color or size?</div>\n                <Button\n                  variant={'secondary'}\n                  onClick={(e) => {\n                    e.preventDefault();\n                    setAction('create');\n                  }}\n                >\n                  Create a variant group\n                </Button>\n              </div>\n            </div>\n          </div>\n        )}\n        {action === 'create' && (\n          <div>\n            <CreateVariantGroup\n              currentProductUuid={currentProductUuid}\n              setGroup={setGroup}\n              onCancel={() => setAction(undefined)}\n              createVariantGroupApi={createVariantGroupApi}\n            />\n          </div>\n        )}\n      </CardContent>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/variants/Skeleton.tsx",
    "content": "import {\n  Table,\n  TableBody,\n  TableCell,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface SkeletonProps {\n  rows?: number;\n  className?: string;\n}\n\nconst SkeletonRow: React.FC = () => (\n  <TableRow>\n    <TableCell>\n      <div className=\"w-7 h-7 bg-gray-200 rounded animate-pulse\" />\n    </TableCell>\n    <TableCell>\n      <div className=\"w-5 h-4 bg-gray-200 rounded animate-pulse\" />\n    </TableCell>\n    <TableCell>\n      <div className=\"w-7 h-4 bg-gray-200 rounded animate-pulse\" />\n    </TableCell>\n    <TableCell>\n      <div className=\"w-16 h-4 bg-gray-200 rounded animate-pulse\" />\n    </TableCell>\n    <TableCell>\n      <div className=\"w-10 h-4 bg-gray-200 rounded animate-pulse\" />\n    </TableCell>\n    <TableCell>\n      <div className=\"w-4 h-4 bg-gray-200 rounded animate-pulse\" />\n    </TableCell>\n    <TableCell>\n      <div className=\"w-10 h-4 bg-gray-200 rounded animate-pulse\" />\n    </TableCell>\n    <TableCell>\n      <div className=\"w-7 h-5 bg-gray-200 rounded animate-pulse\" />\n    </TableCell>\n  </TableRow>\n);\n\nexport const Skeleton: React.FC<SkeletonProps> = ({\n  rows = 5,\n  className = ''\n}) => {\n  return (\n    <div className={`w-full ${className}`}>\n      <Table>\n        <TableBody>\n          {Array.from({ length: rows }, (_, index) => (\n            <SkeletonRow key={index} />\n          ))}\n        </TableBody>\n      </Table>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/variants/Variant.tsx",
    "content": "import { ProductNoThumbnail } from '@components/common/ProductNoThumbnail.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { Item, ItemContent } from '@components/common/ui/Item.js';\nimport { TableCell, TableRow } from '@components/common/ui/Table.js';\nimport React from 'react';\nimport { VariantGroup } from '../VariantGroup.js';\nimport { EditVariant } from './EditVariant.js';\nimport { VariantItem } from './Variants.js';\n\nexport const Variant: React.FC<{\n  variant: VariantItem;\n  refresh: () => void;\n  variantGroup: VariantGroup;\n}> = ({ variant, refresh, variantGroup }) => {\n  return (\n    <TableRow>\n      <TableCell>\n        <Item variant={'outline'} size={'xs'}>\n          <ItemContent>\n            {variant.product?.image?.url ? (\n              <img\n                style={{ maxWidth: '50px', height: 'auto' }}\n                src={variant?.product?.image?.url}\n                alt=\"\"\n              />\n            ) : (\n              <ProductNoThumbnail className=\"size-12\" />\n            )}\n          </ItemContent>\n        </Item>\n      </TableCell>\n      {variantGroup.attributes.map((a) => {\n        const option = variant.attributes.find(\n          (attr) => attr.attributeCode === a.attributeCode\n        );\n        return (\n          <TableCell key={a.attributeId}>\n            <label>{option?.optionText || '--'}</label>\n          </TableCell>\n        );\n      })}\n      <TableCell>\n        <Button\n          variant={'link'}\n          className={'hover:cursor-pointer'}\n          onClick={(e) => {\n            e.preventDefault();\n            window.location.href = variant.product.editUrl;\n          }}\n        >\n          {variant.product?.sku}\n        </Button>\n      </TableCell>\n      <TableCell>{variant.product?.price?.regular?.text}</TableCell>\n      <TableCell>{variant.product?.inventory?.qty}</TableCell>\n      <TableCell>\n        {variant.product?.status === 1 ? (\n          <span className=\"text-primary font-medium\">Enabled</span>\n        ) : (\n          <span className=\"text-destructive font-medium\">Disabled</span>\n        )}\n      </TableCell>\n      <TableCell>\n        <EditVariant\n          variant={variant}\n          refresh={refresh}\n          variantGroup={variantGroup}\n        />\n      </TableCell>\n    </TableRow>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/variants/VariantModal.tsx",
    "content": "import { ImageUploader } from '@components/admin/ImageUploader.js';\nimport Spinner from '@components/admin/Spinner.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { ToggleField } from '@components/common/form/ToggleField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { DialogClose, DialogFooter } from '@components/common/ui/Dialog.js';\nimport React, { useEffect, useState } from 'react';\nimport { useFieldArray, useFormContext } from 'react-hook-form';\nimport { toast } from 'react-toastify';\nimport { useQuery } from 'urql';\nimport { AtLeastOne } from '../../../../../../types/atLeastOne.js';\nimport { VariantGroup } from '../VariantGroup.js';\nimport { VariantItem } from './Variants.js';\n\nconst AttributesQuery = `\n  query Query($filters: [FilterInput]) {\n    attributes(filters: $filters) {\n      items {\n        attributeId\n        attributeCode\n        attributeName\n        options {\n          value: attributeOptionId\n          label: optionText\n        }\n      }\n    }\n  }\n`;\n\nexport const VariantModal: React.FC<\n  AtLeastOne<{\n    variant: VariantItem;\n    createProductApi: string;\n  }> & {\n    variantGroup: VariantGroup;\n    refresh: () => void;\n    closeDialog: () => void;\n  }\n> = ({ variant, variantGroup, createProductApi, refresh, closeDialog }) => {\n  const [saving, setSaving] = useState(false);\n  const { watch, register, control } = useFormContext();\n  const { fields, append, remove, replace } = useFieldArray({\n    name: 'variant_images',\n    control\n  }) as ReturnType<typeof useFieldArray>;\n  const variantData = watch([\n    'url_key',\n    'variant_sku',\n    'variant_visibility',\n    'variant_qty',\n    'variant_status',\n    'variant_attributes',\n    'variant_images'\n  ]);\n  const { getValues, trigger } = useFormContext();\n  const addVariantItem = async (uuid, addVariantItemApi) => {\n    const response = await fetch(addVariantItemApi, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({\n        product_id: uuid\n      })\n    });\n    const responseJson = await response.json();\n    return responseJson;\n  };\n\n  const saveVariant = async () => {\n    setSaving(true);\n    const productFormData = getValues();\n    const formData = {\n      ...productFormData,\n      product_id: variant?.product.productId || '',\n      sku: variantData[1],\n      visibility: variantData[2],\n      qty: variantData[3],\n      status: variantData[4],\n      url_key: `${variantData[0]}-${variantData[1]}`,\n      images: (variantData[6] || []).map((image) => image.url),\n      attributes:\n        productFormData.attributes?.map((attr) => {\n          if (variantData[5]?.[attr.attribute_code]) {\n            return {\n              ...attr,\n              value: variantData[5][attr.attribute_code]\n            };\n          }\n          return attr;\n        }) || []\n    };\n    const response = await fetch(\n      variant ? variant.product.updateApi : (createProductApi as string),\n      {\n        method: variant ? 'PATCH' : 'POST',\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify(formData)\n      }\n    );\n    const responseJson = await response.json();\n    if (responseJson.error) {\n      toast.error(responseJson.error.message);\n    } else if (!variant) {\n      const addVariantResponse = await addVariantItem(\n        responseJson.data.uuid,\n        variantGroup.addItemApi\n      );\n      if (addVariantResponse.error) {\n        toast.error(addVariantResponse.error.message);\n      } else {\n        toast.success('Variant created successfully');\n        // Close the dialog\n        closeDialog();\n        // Refresh the page to reflect the changes\n        refresh();\n      }\n    } else {\n      toast.success('Variant updated successfully');\n      // Close the dialog\n      closeDialog();\n      // Refresh the page to reflect the changes\n      refresh();\n    }\n    setSaving(false);\n  };\n\n  useEffect(() => {\n    if (variant) {\n      const images = variant?.product.image\n        ? [variant.product.image].concat(variant.product.gallery || [])\n        : [];\n      replace(\n        images.map((image) => ({\n          uuid: image.uuid,\n          path: image.path,\n          url: image.url\n        }))\n      );\n    }\n  }, []);\n\n  const [result] = useQuery({\n    query: AttributesQuery,\n    variables: {\n      filters: [\n        {\n          key: 'code',\n          operation: 'in',\n          value: variantGroup.attributes.map((a) => a.attributeCode).join(',')\n        }\n      ]\n    }\n  });\n\n  const { data, fetching, error } = result;\n  if (fetching) {\n    return (\n      <div className=\"p-2 flex justify-center items-center\">\n        <Spinner width={30} height={30} />\n      </div>\n    );\n  }\n\n  if (error) {\n    return <p className=\"text-destructive\">{error.message}</p>;\n  }\n  return (\n    <>\n      <div className=\"grid grid-cols-2 gap-x-5\">\n        <div className=\"col-span-1\">\n          <ImageUploader\n            currentImages={\n              variant?.product.image\n                ? [variant.product.image].concat(variant.product.gallery || [])\n                : []\n            }\n            allowDelete={true}\n            allowSwap={true}\n            onDelete={(image) => {\n              const index = fields.findIndex(\n                (field) => field.uuid === image.uuid\n              );\n              if (index !== -1) {\n                remove(index);\n              }\n            }}\n            onUpload={(images) => {\n              const newImages = images.map((image) => ({\n                id: image.uuid,\n                path: image.path,\n                url: image.url\n              }));\n              append(newImages);\n            }}\n            onSortEnd={(oldIndex, newIndex) => {\n              const newImages = [...fields];\n              const [movedImage] = newImages.splice(oldIndex, 1);\n              newImages.splice(newIndex, 0, movedImage);\n              replace(newImages);\n            }}\n            targetPath={`catalog/${\n              Math.floor(Math.random() * (9999 - 1000)) + 1000\n            }/${Math.floor(Math.random() * (9999 - 1000)) + 1000}`}\n          />\n        </div>\n        <div className=\"col-span-1\">\n          <div className=\"grid grid-cols-2 gap-x-2 border-b border-border pb-4 mb-4\">\n            {data.attributes.items.map((a) => (\n              <div key={a.attributeCode} className=\"mt-2 col\">\n                <SelectField\n                  name={`variant_attributes.${a.attributeCode}`}\n                  label={a.attributeName}\n                  placeholder=\"Select an option\"\n                  required\n                  validation={{\n                    required: 'This field is required'\n                  }}\n                  defaultValue={\n                    variant?.attributes\n                      .find((attr) => attr.attributeCode === a.attributeCode)\n                      ?.optionId.toString() || ''\n                  }\n                  options={a.options}\n                />\n              </div>\n            ))}\n          </div>\n          <div className=\"grid grid-cols-3 gap-x-2 border-b border-border pb-4 mb-4\">\n            <div>\n              <InputField\n                name=\"variant_sku\"\n                label=\"Sku\"\n                placeholder=\"Enter SKU\"\n                required\n                validation={{\n                  required: 'SKU is required'\n                }}\n                defaultValue={variant?.product?.sku}\n              />\n            </div>\n            <div>\n              <NumberField\n                name=\"variant_qty\"\n                label=\"Quantity\"\n                required\n                placeholder=\"Enter quantity\"\n                validation={{\n                  required: 'Qty is required'\n                }}\n                allowDecimals={false}\n                defaultValue={variant?.product?.inventory?.qty || 0}\n              />\n            </div>\n          </div>\n          <div className=\"grid grid-cols-3 gap-x-2\">\n            <div>\n              <ToggleField\n                name=\"variant_status\"\n                label=\"Status\"\n                trueValue={true}\n                falseValue={false}\n                defaultValue={variant?.product.status === 1}\n              />\n            </div>\n            <div>\n              <ToggleField\n                name=\"variant_visibility\"\n                label=\"Visibility\"\n                trueValue={true}\n                falseValue={false}\n                defaultValue={variant?.product.visibility === 1}\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n      <DialogFooter>\n        <DialogClose>\n          <Button variant=\"outline\">Cancel</Button>\n        </DialogClose>\n        <Button\n          isLoading={saving}\n          onClick={async () => {\n            const isValid = await trigger();\n            if (isValid) {\n              await saveVariant();\n            }\n          }}\n        >\n          Save\n        </Button>\n      </DialogFooter>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit/variants/Variants.tsx",
    "content": "import { Image } from '@components/admin/ImageUploader.js';\nimport { CardContent } from '@components/common/ui/Card.js';\nimport {\n  Table,\n  TableBody,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport React from 'react';\nimport { useQuery } from 'urql';\nimport { VariantGroup } from '../VariantGroup.js';\nimport { CreateVariant } from './CreateVariant.js';\nimport { Skeleton } from './Skeleton.js';\nimport { Variant } from './Variant.js';\n\nexport const VariantQuery = `\nquery Query($productId: ID!) {\n  product(id: $productId) {\n    variantGroup {\n      items {\n        id\n        attributes {\n          attributeId\n          attributeCode\n          optionId\n          optionText\n        }\n        product {\n          productId\n          uuid\n          name\n          sku\n          qty\n          status\n          urlKey\n          visibility\n          price {\n            regular {\n              value\n              currency\n              text\n            }\n          }\n          inventory {\n            qty\n            isInStock\n            stockAvailability\n            manageStock\n          }\n          editUrl\n          updateApi\n          image {\n            uuid\n            url\n          }\n          gallery {\n            uuid\n            url\n          }\n        }\n      }\n    }\n  }\n}\n`;\n\nexport interface VariantsProps {\n  productId: number;\n  productUuid: string;\n  variantGroup: VariantGroup;\n  createProductApi: string;\n}\n\nexport interface VariantItem {\n  id: number;\n  attributes: Array<{\n    attributeId: number;\n    attributeCode: string;\n    optionId: number;\n    optionText: string;\n  }>;\n  product: {\n    productId: number;\n    uuid: string;\n    name: string;\n    sku: string;\n    qty: number;\n    status: number;\n    visibility: number;\n    price: {\n      regular: {\n        value: number;\n        currency: string;\n        text: string;\n      };\n    };\n    inventory: {\n      qty: number;\n      isInStock: boolean;\n      stockAvailability: string;\n      manageStock: boolean;\n    };\n    urlKey: string;\n    editUrl: string;\n    updateApi: string;\n    image: Image;\n    gallery: Array<Image>;\n  };\n}\n\nexport const Variants: React.FC<VariantsProps> = ({\n  productId,\n  variantGroup,\n  createProductApi\n}) => {\n  const [result, reexecuteQuery] = useQuery({\n    query: VariantQuery,\n    variables: {\n      productId\n    }\n  });\n\n  const refresh = () => {\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  };\n\n  const { data, fetching, error } = result as {\n    data: {\n      product: {\n        variantGroup: {\n          items: Array<VariantItem>;\n        };\n      };\n    };\n    fetching: boolean;\n    error: Error | null;\n  };\n  if (fetching) {\n    return (\n      <div className=\"p-2 flex justify-center items-center\">\n        <Skeleton />\n      </div>\n    );\n  }\n\n  if (error) {\n    return <p className=\"text-destructive\">{error.message}</p>;\n  }\n\n  return (\n    <CardContent>\n      <div className=\"variant-list overflow-x-scroll\">\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>Image</TableHead>\n              {variantGroup.attributes.map((attribute) => (\n                <TableHead key={attribute.attributeId}>\n                  {attribute.attributeName}\n                </TableHead>\n              ))}\n              <TableHead>Sku</TableHead>\n              <TableHead>Price</TableHead>\n              <TableHead>Stock</TableHead>\n              <TableHead>Status</TableHead>\n              <TableHead>Actions</TableHead>\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {(data.product.variantGroup?.items || [])\n              .filter((v) => v.product.productId !== productId)\n              .map((v) => (\n                <Variant\n                  key={v.id}\n                  variant={v}\n                  refresh={refresh}\n                  variantGroup={variantGroup}\n                />\n              ))}\n          </TableBody>\n        </Table>\n      </div>\n      <div className=\"self-center\">\n        <CreateVariant\n          variantGroup={variantGroup}\n          createProductApi={createProductApi}\n          refresh={refresh}\n        />\n      </div>\n    </CardContent>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/Attributes.tsx",
    "content": "import { InputField } from '@components/common/form/InputField.js';\nimport { ReactSelectField } from '@components/common/form/ReactSelectField.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { TextareaField } from '@components/common/form/TextareaField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport {\n  Item,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useEffect } from 'react';\nimport { useFieldArray, useFormContext } from 'react-hook-form';\n\ninterface Field {\n  attribute_id: string;\n  attribute_name: string;\n  attribute_code: string;\n  type: string;\n  is_required: number;\n}\n\ninterface Attribute extends Field {\n  options: {\n    value: string;\n    label: string;\n  }[];\n}\ninterface Group {\n  groupId: string;\n  groupName: string;\n  attributes: {\n    items: Attribute[];\n  };\n}\nconst getGroup = (groups: Group[] = [], groupId: string | undefined) =>\n  groups.find((group) => group.groupId === groupId) || groups[0];\n\nconst getAttributeOptions = (groups: Group[], attributeId: string) => {\n  const attribute = groups\n    .find((group) =>\n      group.attributes.items.find((attr) => attr.attribute_id === attributeId)\n    )\n    ?.attributes.items.find((attr) => attr.attribute_id === attributeId);\n  return attribute ? attribute.options : [];\n};\n\nconst getAttributeSelectedValues = (\n  attributeIndex,\n  attributeId,\n  attributeType\n) => {\n  switch (attributeType) {\n    case 'text':\n    case 'textarea':\n    case 'date':\n    case 'datetime':\n      return (\n        attributeIndex.find((idx) => idx.attributeId === attributeId)\n          ?.optionText || ''\n      );\n    case 'select':\n      return (\n        attributeIndex\n          .find((idx) => idx.attributeId === attributeId)\n          ?.optionId.toString() || ''\n      );\n    case 'multiselect':\n      return attributeIndex\n        .filter((idx) => idx.attributeId === attributeId)\n        .map((idx) => idx.optionId.toString());\n    default:\n      return '';\n  }\n};\n\ninterface AttributesProps {\n  product?: {\n    attributeIndex: {\n      attributeId: string;\n      optionId: number;\n      optionText: string;\n    }[];\n    groupId: string;\n    variantGroupId?: string;\n  };\n  groups: {\n    items: Group[];\n  };\n}\n\ninterface FormValues {\n  attributes: Field[];\n}\n\nexport default function Attributes({\n  product,\n  groups: { items }\n}: AttributesProps) {\n  const { unregister, watch } = useFormContext();\n  const { fields, remove, append } = useFieldArray<FormValues>({\n    name: 'attributes'\n  });\n  const attributeIndex = product?.attributeIndex || [];\n  const currentGroup = watch(\n    'group_id',\n    getGroup(items, product?.groupId)?.groupId || undefined\n  );\n  useEffect(() => {\n    if (currentGroup) {\n      // Unregister all existing attribute fields\n      fields.forEach((_, index) => {\n        unregister(`attributes.${index}`);\n      });\n\n      // Remove all existing fields\n      remove();\n\n      // Get new attributes for the selected group\n      const attributes = getGroup(items, currentGroup)?.attributes.items || [];\n      const newFields = attributes.map((attribute) => ({\n        attribute_code: attribute.attribute_code,\n        attribute_name: attribute.attribute_name,\n        type: attribute.type,\n        attribute_id: attribute.attribute_id,\n        value: getAttributeSelectedValues(\n          attributeIndex,\n          attribute.attribute_id,\n          attribute.type\n        ),\n        is_required: attribute.is_required\n      }));\n\n      // Append new fields\n      append(newFields);\n    }\n  }, [currentGroup, items, append, remove, unregister]);\n\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Attribute group</CardTitle>\n        <CardDescription>Manage the attributes.</CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div>\n          {product?.variantGroupId && (\n            <div className=\"flex flex-col\">\n              <InputField\n                type=\"hidden\"\n                defaultValue={product?.groupId}\n                name=\"group_id\"\n              />\n              <div>\n                <span className=\"font-semibold\">\n                  {getGroup(items, product?.groupId).groupName}\n                </span>\n                <p className=\"text-muted-foreground italic\">\n                  Can not change the attribute group of a product that is\n                  already in a variant group.\n                </p>\n              </div>\n            </div>\n          )}\n          {!product?.variantGroupId && (\n            <SelectField\n              name=\"group_id\"\n              label=\"Attribute group\"\n              options={items.map((group) => ({\n                value: group.groupId,\n                label: group.groupName\n              }))}\n              defaultValue={product?.groupId || currentGroup}\n              required\n            />\n          )}\n        </div>\n      </CardContent>\n      <CardContent>\n        <Table>\n          <TableBody>\n            {fields.map((attribute, index) => {\n              const validation =\n                attribute.is_required === 1\n                  ? {\n                      required: `${attribute.attribute_name} is required`\n                    }\n                  : {};\n              let Field: React.ReactNode = null;\n              switch (attribute.type) {\n                case 'text':\n                  Field = (\n                    <InputField\n                      name={`attributes.${index}.value`}\n                      required={attribute.is_required === 1}\n                      validation={validation}\n                    />\n                  );\n                  break;\n                case 'textarea':\n                  Field = (\n                    <TextareaField\n                      name={`attributes.${index}.value`}\n                      required={attribute.is_required === 1}\n                      validation={validation}\n                    />\n                  );\n                  break;\n                case 'select':\n                  Field = (\n                    <SelectField\n                      name={`attributes.${index}.value`}\n                      options={getAttributeOptions(\n                        items,\n                        attribute.attribute_id\n                      )}\n                      placeholder=\"Select an option\"\n                      validation={validation}\n                    />\n                  );\n                  break;\n                case 'multiselect':\n                  Field = (\n                    <ReactSelectField\n                      name={`attributes.${index}.value`}\n                      options={getAttributeOptions(\n                        items,\n                        attribute.attribute_id\n                      )}\n                      placeholder=\"Select options\"\n                      required={attribute.is_required === 1}\n                      validation={validation}\n                      isMulti\n                    />\n                  );\n                  break;\n                default:\n                  Field = (\n                    <InputField\n                      name={`attributes.${index}.value`}\n                      required={attribute.is_required === 1}\n                      validation={validation}\n                      placeholder={_('Enter value for ${attribute}', {\n                        attribute: attribute.attribute_name\n                      })}\n                    />\n                  );\n                  break;\n              }\n              return (\n                <TableRow key={attribute.id}>\n                  <TableCell>\n                    <span>{attribute.attribute_name}</span>\n                    {attribute.is_required === 1 && (\n                      <span className=\"text-destructive pl-1\">*</span>\n                    )}\n                  </TableCell>\n                  <TableCell>\n                    <InputField\n                      type=\"hidden\"\n                      value={attribute.attribute_code}\n                      name={`attributes.${index}.attribute_code`}\n                    />\n                    {Field}\n                  </TableCell>\n                </TableRow>\n              );\n            })}\n          </TableBody>\n        </Table>\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 30\n};\n\nexport const query = `\n  query Query ($filters: [FilterInput!]) {\n    product(id: getContextValue(\"productId\", null)) {\n      groupId\n      variantGroupId\n      attributeIndex {\n        attributeId\n        optionId\n        optionText\n      }\n    },\n    groups: attributeGroups(filters: $filters) {\n      items {\n        groupId: attributeGroupId\n        groupName\n        attributes {\n          items {\n            attribute_id: attributeId\n            attribute_name: attributeName\n            attribute_code: attributeCode\n            type\n            is_required: isRequired\n            options {\n              value: attributeOptionId\n              label: optionText\n            }\n          }\n        }\n      }\n    }\n  }\n`;\n\nexport const variables = `\n{\n  filters: [{ key: \"limit\", operation: 'eq', value: 1000 }]\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/General.scss",
    "content": "body.productEdit,\nbody.productNew {\n  .main-content-inner {\n    max-width: 62.5rem;\n    margin: 1.875rem auto;\n  }\n  .page-heading {\n    max-width: 62.5rem;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/General.tsx",
    "content": "import { CategorySelector } from '@components/admin/CategorySelector.js';\nimport Area from '@components/common/Area.js';\nimport { Editor } from '@components/common/form/Editor.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle\n} from '@components/common/ui/Dialog.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport { useQuery } from 'urql';\nimport './General.scss';\nimport { set, useFormContext } from 'react-hook-form';\n\nconst SKUAndPrice: React.FC<{\n  sku: string;\n  price: {\n    value: number | undefined;\n  };\n  setting: {\n    storeCurrency: string;\n    weightUnit: string;\n  };\n}> = ({ sku, price, setting }) => {\n  return (\n    <div className=\"grid grid-cols-2 gap-2\">\n      <InputField\n        name=\"sku\"\n        label=\"SKU\"\n        placeholder=\"Enter SKU\"\n        defaultValue={sku}\n        required\n        helperText={_('SKU must be unique')}\n      />\n      <NumberField\n        name=\"price\"\n        placeholder=\"Enter price\"\n        label={`Price`}\n        defaultValue={price?.value}\n        unit={setting.storeCurrency}\n        min={0}\n        required\n      />\n    </div>\n  );\n};\n\nconst CategoryQuery = `\n  query Query ($id: Int!) {\n    category(id: $id) {\n      categoryId\n      name\n      path {\n        name\n      }\n    }\n  }\n`;\n\nconst ProductCategory: React.FC<{\n  categoryId: number;\n  onChange: () => void;\n  onUnassign: () => void;\n}> = ({ categoryId, onChange, onUnassign }) => {\n  const { register } = useFormContext();\n  const [result] = useQuery({\n    query: CategoryQuery,\n    variables: {\n      id: categoryId\n    }\n  });\n  const { data, fetching, error } = result;\n  if (error) {\n    return (\n      <p className=\"text-destructive\">\n        There was an error fetching categories.\n        {error.message}\n      </p>\n    );\n  }\n  if (fetching) {\n    return <span>Loading...</span>;\n  }\n  return (\n    <div>\n      {data.category.path.map((item, index) => (\n        <span key={item.name} className=\"text-gray-500\">\n          {item.name}\n          {index < data.category.path.length - 1 && ' > '}\n        </span>\n      ))}\n      <span className=\"text-interactive pl-5\">\n        <a\n          href=\"#\"\n          onClick={(e) => {\n            e.preventDefault();\n            onChange();\n          }}\n        >\n          Change\n        </a>\n        <a\n          href=\"#\"\n          onClick={(e) => {\n            e.preventDefault();\n            onUnassign();\n          }}\n          className=\"text-destructive ml-5\"\n        >\n          Unassign\n        </a>\n      </span>\n      <input type=\"hidden\" {...register('category_id')} value={categoryId} />\n    </div>\n  );\n};\n\nconst CategorySelect: React.FC<{\n  product?:\n    | {\n        category?: {\n          categoryId: number;\n          name?: string;\n          path?: Array<{ name: string }>;\n        };\n      }\n    | undefined;\n}> = ({ product }) => {\n  const { setValue } = useFormContext();\n  const [category, setCategory] = React.useState(\n    product ? product.category : null\n  );\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const onSelect = (categoryId) => {\n    setCategory({ categoryId });\n    setValue('category_id', categoryId || '');\n    setDialogOpen(false);\n  };\n\n  return (\n    <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n      <div className=\"space-y-3\">\n        <Label>Category</Label>\n        {category && (\n          <div className=\"border rounded border-border p-2\">\n            <ProductCategory\n              categoryId={category.categoryId}\n              onChange={() => {\n                setDialogOpen(true);\n              }}\n              onUnassign={() => setCategory(null)}\n            />\n          </div>\n        )}\n        {!category && (\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            onClick={(e) => {\n              e.preventDefault();\n              setDialogOpen(true);\n            }}\n          >\n            Select category\n          </Button>\n        )}\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>Select Category</DialogTitle>\n          </DialogHeader>\n          <CategorySelector\n            onSelect={onSelect}\n            onUnSelect={() => {}}\n            selectedCategories={category ? [category] : []}\n          />\n        </DialogContent>\n      </div>\n    </Dialog>\n  );\n};\n\ninterface GeneralProps {\n  product?: {\n    description?: Array<{\n      id: string;\n      size: number;\n      columns: Array<{\n        id: string;\n        size: number;\n        data: object;\n      }>;\n    }>;\n    name: string;\n    price: {\n      regular: {\n        currency: string;\n        value: number;\n      };\n    };\n    productId: number;\n    uuid: string;\n    taxClass: number;\n    sku: string;\n    weight: {\n      unit: string;\n      value: number;\n    };\n    category?: {\n      categoryId: number;\n      name?: string;\n      path?: Array<{ name: string }>;\n    };\n  };\n  setting: {\n    storeCurrency: string;\n    weightUnit: string;\n  };\n  productTaxClasses: {\n    items: Array<{\n      value: number;\n      text: string;\n    }>;\n  };\n}\nexport default function General({\n  product,\n  setting,\n  productTaxClasses: { items: taxClasses }\n}: GeneralProps) {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>General Information</CardTitle>\n        <CardDescription>\n          Manage the general information of the product.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <Area\n          id=\"productEditGeneral\"\n          className=\"flex flex-col gap-2\"\n          coreComponents={[\n            {\n              component: {\n                default: (\n                  <InputField\n                    name=\"name\"\n                    placeholder=\"Enter product name\"\n                    label=\"Product Name\"\n                    defaultValue={product?.name}\n                    required\n                    helperText={_('Product name is required')}\n                  />\n                )\n              },\n              sortOrder: 10,\n              id: 'name'\n            },\n            {\n              component: {\n                default: (\n                  <SKUAndPrice\n                    sku={product?.sku || ''}\n                    price={\n                      product?.price.regular || {\n                        value: undefined\n                      }\n                    }\n                    setting={setting}\n                  />\n                )\n              },\n              sortOrder: 20,\n              id: 'SKUAndPrice'\n            },\n            {\n              component: {\n                default: <CategorySelect product={product} />\n              },\n              sortOrder: 22,\n              id: 'category'\n            },\n            {\n              component: {\n                default: (\n                  <SelectField\n                    name=\"tax_class\"\n                    label=\"Tax Class\"\n                    options={taxClasses.map((taxClass) => ({\n                      value: taxClass.value,\n                      label: taxClass.text\n                    }))}\n                    defaultValue={product?.taxClass || ''}\n                    required\n                    validation={{ required: true }}\n                  />\n                )\n              },\n              sortOrder: 25,\n              id: 'tax_class'\n            },\n            {\n              component: {\n                default: (\n                  <Editor\n                    name=\"description\"\n                    label=\"Description\"\n                    value={product?.description}\n                  />\n                )\n              },\n              sortOrder: 30,\n              id: 'description'\n            }\n          ]}\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    product(id: getContextValue(\"productId\", null)) {\n      productId\n      uuid\n      name\n      description\n      sku\n      taxClass\n      price {\n        regular {\n          value\n          currency\n        }\n      }\n      weight {\n        value\n        unit\n      }\n      category {\n        categoryId\n        path {\n          name\n        }\n      }\n    }\n    setting {\n      weightUnit\n      storeCurrency\n    }\n    productTaxClasses: taxClasses {\n      items {\n        value: taxClassId\n        text: name\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/Inventory.tsx",
    "content": "import { NumberField } from '@components/common/form/NumberField.js';\nimport { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface InventoryProps {\n  product:\n    | {\n        inventory: {\n          qty: number;\n          stockAvailability: number;\n          manageStock: number;\n        };\n      }\n    | undefined;\n}\nexport default function Inventory({ product }: InventoryProps) {\n  const inventory = product?.inventory || {\n    qty: undefined,\n    stockAvailability: undefined,\n    manageStock: undefined\n  };\n  return (\n    <Card className=\"bg-popover\">\n      <CardHeader>\n        <CardTitle>Inventory</CardTitle>\n        <CardDescription>\n          Manage the inventory settings of the product.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <RadioGroupField\n          name=\"manage_stock\"\n          label=\"Manage Stock\"\n          options={[\n            { value: 1, label: 'Yes' },\n            { value: 0, label: 'No' }\n          ]}\n          defaultValue={inventory.manageStock === 0 ? 0 : 1}\n          required\n        />\n      </CardContent>\n      <CardContent className=\"border-t border-t-border pt-6\">\n        <RadioGroupField\n          name=\"stock_availability\"\n          label=\"Stock Availability\"\n          options={[\n            { value: 1, label: 'In Stock' },\n            { value: 0, label: 'Out of Stock' }\n          ]}\n          defaultValue={inventory.stockAvailability === 0 ? 0 : 1}\n          required\n        />\n      </CardContent>\n      <CardContent className=\"border-t border-t-border pt-6\">\n        <NumberField\n          name=\"qty\"\n          defaultValue={inventory.qty}\n          placeholder=\"Quantity\"\n          label=\"Quantity\"\n          required\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    product(id: getContextValue(\"productId\", null)) {\n      inventory {\n        qty\n        stockAvailability\n        manageStock\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/Media.tsx",
    "content": "import { ImageUploader } from '@components/admin/ImageUploader.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React, { useEffect } from 'react';\nimport { useFieldArray, useFormContext } from 'react-hook-form';\n\ninterface MediaProps {\n  product?: {\n    image?: {\n      uuid: string;\n      path: string;\n      url: string;\n    };\n    gallery?: {\n      uuid: string;\n      path: string;\n      url: string;\n    }[];\n  };\n}\nexport default function Media({ product }: MediaProps) {\n  const control = useFormContext().control;\n  const { fields, append, remove, replace } = useFieldArray({\n    name: 'images',\n    control\n  }) as ReturnType<typeof useFieldArray>;\n  useEffect(() => {\n    const images = product?.image\n      ? [product.image].concat(product?.gallery || [])\n      : [];\n    replace(images);\n  }, []);\n  return (\n    <Card title=\"Media\">\n      <CardHeader>\n        <CardTitle>Media</CardTitle>\n        <CardDescription>\n          Manage product images and gallery. Drag and drop to reorder images.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <ImageUploader\n          currentImages={\n            product?.image ? [product.image].concat(product?.gallery || []) : []\n          }\n          allowDelete={true}\n          allowSwap={true}\n          onDelete={(image) => {\n            const index = fields.findIndex((img) => img.uuid === image.uuid);\n            if (index !== -1) {\n              remove(index);\n            }\n          }}\n          onUpload={(images) => {\n            append(images);\n          }}\n          onSortEnd={(oldIndex, newIndex) => {\n            const newImages = [...fields];\n            const [movedImage] = newImages.splice(oldIndex, 1);\n            newImages.splice(newIndex, 0, movedImage);\n            replace(newImages);\n          }}\n          targetPath={`catalog/${\n            Math.floor(Math.random() * (9999 - 1000)) + 1000\n          }/${Math.floor(Math.random() * (9999 - 1000)) + 1000}`}\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    product(id: getContextValue(\"productId\", null)) {\n      image {\n        uuid\n        path\n        url\n      }\n      gallery {\n        uuid\n        path\n        url\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface ProductEditPageHeadingProps {\n  backUrl: string;\n  product?: {\n    name?: string;\n  };\n}\n\nexport default function ProductEditPageHeading({\n  backUrl,\n  product\n}: ProductEditPageHeadingProps) {\n  return (\n    <PageHeading\n      backUrl={backUrl}\n      heading={product ? `Editing ${product.name}` : 'Create a new product'}\n    />\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    product(id: getContextValue(\"productId\", null)) {\n      name\n    }\n    backUrl: url(routeId: \"productGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/Seo.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { TextareaField } from '@components/common/form/TextareaField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface SEOProps {\n  product:\n    | {\n        urlKey: string;\n        metaTitle: string;\n        metaKeywords: string;\n        metaDescription: string;\n      }\n    | undefined;\n}\nexport default function SEO({ product }: SEOProps) {\n  const fields = [\n    {\n      component: {\n        default: (\n          <InputField\n            name=\"url_key\"\n            label=\"URL Key\"\n            placeholder=\"Enter URL Key\"\n            required\n            defaultValue={product?.urlKey}\n            validation={{\n              required: 'URL Key is required',\n              pattern: {\n                value: /^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$/,\n                message:\n                  'URL Key must be lowercase and can only contain letters, numbers, and hyphens'\n              }\n            }}\n          />\n        )\n      },\n      sortOrder: 0\n    },\n    {\n      component: {\n        default: (\n          <InputField\n            name=\"meta_title\"\n            label=\"Meta Title\"\n            placeholder=\"Enter Meta Title\"\n            required\n            defaultValue={product?.metaTitle}\n            validation={{\n              required: 'Meta Title is required'\n            }}\n          />\n        )\n      },\n      sortOrder: 10\n    },\n    {\n      component: {\n        default: (\n          <InputField\n            type=\"hidden\"\n            name=\"meta_keywords\"\n            defaultValue={product?.metaKeywords}\n          />\n        )\n      },\n      sortOrder: 20\n    },\n    {\n      component: {\n        default: (\n          <TextareaField\n            name=\"meta_description\"\n            label=\"Meta Description\"\n            placeholder=\"Enter Meta Description\"\n            defaultValue={product?.metaDescription || ''}\n          />\n        )\n      },\n      sortOrder: 30\n    }\n  ];\n\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>SEO</CardTitle>\n        <CardDescription>Manage the SEO settings.</CardDescription>\n      </CardHeader>\n      <CardContent>\n        <Area\n          id=\"productEditSeo\"\n          coreComponents={fields}\n          className=\"flex flex-col gap-2\"\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 60\n};\n\nexport const query = `\n  query Query {\n    product(id: getContextValue('productId', null)) {\n      urlKey\n      metaTitle\n      metaKeywords\n      metaDescription\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/Shipping.tsx",
    "content": "import { CheckboxField } from '@components/common/form/CheckboxField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\nimport { useFormContext, useWatch } from 'react-hook-form';\n\ninterface ShippingProps {\n  product:\n    | {\n        noShippingRequired: boolean;\n        weight: {\n          value: number;\n          unit: string;\n        };\n      }\n    | undefined;\n  setting: {\n    weightUnit: string;\n  };\n}\nexport default function Shipping({ product, setting }: ShippingProps) {\n  const shipping = product || {\n    noShippingRequired: undefined,\n    weight: undefined\n  };\n  const { control } = useFormContext();\n  const noShippingRequired = useWatch({\n    control,\n    name: 'no_shipping_required',\n    defaultValue:\n      (shipping.noShippingRequired !== null && shipping.noShippingRequired) ||\n      false\n  });\n\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Shipping</CardTitle>\n        <CardDescription>\n          Manage the shipping settings of the product.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <CheckboxField\n          name=\"no_shipping_required\"\n          label=\"No shipping required?\"\n          defaultValue={shipping.noShippingRequired === true}\n          helperText=\"Select this option if the product is a digital product or service that does not require shipping.\"\n          wrapperClassName=\"mb-0\"\n        />\n      </CardContent>\n      <CardContent>\n        {!noShippingRequired && (\n          <NumberField\n            name=\"weight\"\n            placeholder=\"Enter weight\"\n            label={`Weight`}\n            defaultValue={shipping.weight?.value}\n            unit={setting?.weightUnit}\n            required\n            validation={{\n              min: {\n                value: 0,\n                message: 'Weight must be a positive number'\n              }\n            }}\n            helperText={'Weight must be a positive number'}\n          />\n        )}\n        {noShippingRequired && (\n          <NumberField\n            name=\"weight_no_shipping\"\n            placeholder=\"Enter weight\"\n            label={`Weight`}\n            defaultValue={shipping.weight?.value}\n            unit={setting?.weightUnit}\n            disabled\n            helperText={'Weight must be a positive number'}\n          />\n        )}\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    product(id: getContextValue(\"productId\", null)) {\n      weight {\n        value\n        unit\n      }\n      noShippingRequired\n    }\n    setting {\n      weightUnit\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/Status.tsx",
    "content": "import { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport {\n  Card,\n  CardHeader,\n  CardDescription,\n  CardContent,\n  CardFooter,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface StatusProps {\n  product:\n    | {\n        status: number;\n        visibility: number;\n      }\n    | undefined;\n}\nexport default function Status({ product }: StatusProps) {\n  return (\n    <Card className=\"bg-popover\">\n      <CardHeader>\n        <CardTitle>Product Status</CardTitle>\n        <CardDescription>\n          Set the status and visibility of the product.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <RadioGroupField\n          name=\"status\"\n          label=\"Status\"\n          options={[\n            { value: 0, label: 'Disabled' },\n            { value: 1, label: 'Enabled' }\n          ]}\n          defaultValue={product?.status === 0 ? 0 : 1}\n          required\n          helperText=\"Disabled products will not be visible in the store and cannot be purchased.\"\n        />\n      </CardContent>\n      <CardContent className=\"border-t border-t-border pt-6\">\n        <RadioGroupField\n          name=\"visibility\"\n          label=\"Visibility\"\n          options={[\n            { value: 0, label: 'Not visible individually' },\n            { value: 1, label: 'Catalog, Search' }\n          ]}\n          defaultValue={product?.visibility === 0 ? 0 : 1}\n          required\n          helperText=\"Visibility determines where the product appears in the store. It does not affect the saleability of the product.\"\n        />\n      </CardContent>\n      <CardFooter></CardFooter>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    product(id: getContextValue(\"productId\", null)) {\n      status\n      visibility\n      category {\n        value: categoryId\n        label: name\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productGrid/Grid.jsx",
    "content": "import { GridPagination } from '@components/admin/grid/GridPagination.js';\nimport { DummyColumnHeader } from '@components/admin/grid/header/Dummy';\nimport { SortableHeader } from '@components/admin/grid/header/Sortable.js';\nimport { Thumbnail } from '@components/admin/grid/Thumbnail.js';\nimport { Status } from '@components/admin/Status.js';\nimport Area from '@components/common/Area';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { useAlertContext } from '@components/common/modal/Alert';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport {\n  Card,\n  CardAction,\n  CardContent,\n  CardHeader\n} from '@components/common/ui/Card.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectTrigger,\n  SelectValue\n} from '@components/common/ui/Select.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport axios from 'axios';\nimport { Check } from 'lucide-react';\nimport PropTypes from 'prop-types';\nimport React, { useState } from 'react';\nimport { ProductNameRow } from './rows/ProductName.js';\n\nfunction Actions({ products = [], selectedIds = [] }) {\n  const { openAlert, closeAlert } = useAlertContext();\n  const [isLoading, setIsLoading] = useState(false);\n\n  const updateProducts = async (status) => {\n    setIsLoading(true);\n    const promises = products\n      .filter((product) => selectedIds.includes(product.uuid))\n      .map((product) =>\n        axios.patch(product.updateApi, {\n          status\n        })\n      );\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const deleteProducts = async () => {\n    setIsLoading(true);\n    const promises = products\n      .filter((product) => selectedIds.includes(product.uuid))\n      .map((product) => axios.delete(product.deleteApi));\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const actions = [\n    {\n      name: 'Disable',\n      onAction: () => {\n        openAlert({\n          heading: `Disable ${selectedIds.length} products`,\n          content: 'Are you sure?',\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Disable',\n            onAction: async () => {\n              await updateProducts(0);\n            },\n            variant: 'default',\n            isLoading: false\n          }\n        });\n      }\n    },\n    {\n      name: 'Enable',\n      onAction: () => {\n        openAlert({\n          heading: `Enable ${selectedIds.length} products`,\n          content: 'Are you sure?',\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Enable',\n            onAction: async () => {\n              await updateProducts(1);\n            },\n            variant: 'default',\n            isLoading: false\n          }\n        });\n      }\n    },\n    {\n      name: 'Delete',\n      onAction: () => {\n        openAlert({\n          heading: `Delete ${selectedIds.length} products`,\n          content: <div>Can&apos;t be undone</div>,\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Delete',\n            onAction: async () => {\n              await deleteProducts();\n            },\n            variant: 'destructive',\n            isLoading\n          }\n        });\n      }\n    }\n  ];\n\n  return (\n    <TableRow>\n      {selectedIds.length === 0 && null}\n      {selectedIds.length > 0 && (\n        <TableCell style={{ borderTop: 0 }} colSpan=\"100\">\n          <ButtonGroup>\n            {actions.map((action, i) => (\n              <Button\n                key={i}\n                variant={'outline'}\n                onClick={(e) => {\n                  e.preventDefault();\n                  action.onAction();\n                }}\n              >\n                {action.name}\n              </Button>\n            ))}\n          </ButtonGroup>\n        </TableCell>\n      )}\n    </TableRow>\n  );\n}\n\nActions.propTypes = {\n  selectedIds: PropTypes.arrayOf(PropTypes.string).isRequired,\n  products: PropTypes.arrayOf(\n    PropTypes.shape({\n      uuid: PropTypes.string.isRequired,\n      updateApi: PropTypes.string.isRequired,\n      deleteApi: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport default function ProductGrid({\n  products: { items: products, total, currentFilters = [] }\n}) {\n  const page = currentFilters.find((filter) => filter.key === 'page')\n    ? parseInt(currentFilters.find((filter) => filter.key === 'page').value, 10)\n    : 1;\n\n  const limit = currentFilters.find((filter) => filter.key === 'limit')\n    ? parseInt(\n        currentFilters.find((filter) => filter.key === 'limit').value,\n        10\n      )\n    : 20;\n  const [selectedRows, setSelectedRows] = useState([]);\n\n  return (\n    <Card>\n      <CardHeader className=\"flex justify-between\">\n        <Form submitBtn={false} id=\"productGridFilter\">\n          <div className=\"flex gap-5 justify-center items-center\">\n            <Area\n              id=\"productGridFilter\"\n              noOuter\n              coreComponents={[\n                {\n                  component: {\n                    default: () => (\n                      <InputField\n                        name=\"keyword\"\n                        placeholder=\"Search\"\n                        defaultValue={\n                          currentFilters.find((f) => f.key === 'keyword')?.value\n                        }\n                        onKeyPress={(e) => {\n                          // If the user press enter, we should submit the form\n                          if (e.key === 'Enter') {\n                            const url = new URL(document.location);\n                            const keyword = e.target?.value;\n                            if (keyword) {\n                              url.searchParams.set('keyword', keyword);\n                            } else {\n                              url.searchParams.delete('keyword');\n                            }\n                            window.location.href = url;\n                          }\n                        }}\n                      />\n                    )\n                  },\n                  sortOrder: 5\n                },\n                {\n                  component: {\n                    default: () => (\n                      <Select\n                        value={\n                          currentFilters.find((f) => f.key === 'status')?.value\n                        }\n                        onValueChange={(value) => {\n                          const url = new URL(document.location);\n                          url.searchParams.set('status', value);\n                          window.location.href = url.href;\n                        }}\n                      >\n                        <SelectTrigger>\n                          <SelectValue>Status</SelectValue>\n                        </SelectTrigger>\n                        <SelectContent>\n                          <SelectGroup>\n                            <SelectLabel>Status</SelectLabel>\n                            <SelectItem value=\"1\">Enabled</SelectItem>\n                            <SelectItem value=\"0\">Disabled</SelectItem>\n                          </SelectGroup>\n                        </SelectContent>\n                      </Select>\n                    )\n                  },\n                  sortOrder: 10\n                },\n                {\n                  component: {\n                    default: () => (\n                      <Select\n                        value={\n                          currentFilters.find((f) => f.key === 'type')?.value\n                        }\n                        onValueChange={(value) => {\n                          const url = new URL(document.location);\n                          url.searchParams.set('type', value);\n                          window.location.href = url.href;\n                        }}\n                      >\n                        <SelectTrigger>\n                          <SelectValue>Product type</SelectValue>\n                        </SelectTrigger>\n                        <SelectContent>\n                          <SelectGroup>\n                            <SelectLabel>Product type</SelectLabel>\n                            <SelectItem value=\"simple\">Simple</SelectItem>\n                            <SelectItem value=\"configurable\">\n                              Configurable\n                            </SelectItem>\n                          </SelectGroup>\n                        </SelectContent>\n                      </Select>\n                    )\n                  },\n                  sortOrder: 15\n                }\n              ]}\n              currentFilters={currentFilters}\n            />\n          </div>\n        </Form>\n        <CardAction>\n          <Button\n            variant=\"link\"\n            className={'hover:cursor-pointer'}\n            onClick={() => {\n              const url = new URL(document.location);\n              url.search = '';\n              window.location.href = url.href;\n            }}\n          >\n            Clear Filters\n          </Button>\n        </CardAction>\n      </CardHeader>\n      <CardContent>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>\n                <Checkbox\n                  onCheckedChange={(checked) => {\n                    if (checked) {\n                      setSelectedRows(products.map((p) => p.uuid));\n                    } else {\n                      setSelectedRows([]);\n                    }\n                  }}\n                />\n              </TableHead>\n              <Area\n                id=\"productGridHeader\"\n                noOuter\n                coreComponents={[\n                  {\n                    component: {\n                      default: () => (\n                        <TableHead>\n                          <div className=\"table-header id-header\">\n                            <div className=\"font-medium uppercase text-xs\">\n                              <span>Thumbnail</span>\n                            </div>\n                          </div>\n                        </TableHead>\n                      )\n                    },\n                    sortOrder: 5\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Name\"\n                          name=\"name\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 10\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Price\"\n                          name=\"price\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 15\n                  },\n                  {\n                    component: {\n                      default: () => <DummyColumnHeader title=\"SKU\" />\n                    },\n                    sortOrder: 20\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Stock\"\n                          name=\"qty\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 25\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Status\"\n                          name=\"status\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 30\n                  }\n                ]}\n              />\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            <Actions\n              products={products}\n              selectedIds={selectedRows}\n              setSelectedRows={setSelectedRows}\n            />\n            {products.map((p) => (\n              <TableRow key={p.uuid}>\n                <TableCell>\n                  <div className=\"form-field mb-0\">\n                    <Checkbox\n                      checked={selectedRows.includes(p.uuid)}\n                      onCheckedChange={(checked) => {\n                        if (checked) {\n                          setSelectedRows(selectedRows.concat([p.uuid]));\n                        } else {\n                          setSelectedRows(\n                            selectedRows.filter((row) => row !== p.uuid)\n                          );\n                        }\n                      }}\n                    />\n                  </div>\n                </TableCell>\n                <Area\n                  id=\"productGridRow\"\n                  row={p}\n                  noOuter\n                  selectedRows={selectedRows}\n                  setSelectedRows={setSelectedRows}\n                  coreComponents={[\n                    {\n                      component: {\n                        default: () => (\n                          <Thumbnail src={p.image?.url} name={p.name} />\n                        )\n                      },\n                      sortOrder: 5\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <ProductNameRow\n                            id=\"name\"\n                            name={p.name}\n                            url={p.editUrl}\n                          />\n                        )\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <TableCell>{p.price?.regular.text}</TableCell>\n                        )\n                      },\n                      sortOrder: 15\n                    },\n                    {\n                      component: {\n                        default: () => <TableCell>{p.sku}</TableCell>\n                      },\n                      sortOrder: 20\n                    },\n                    {\n                      component: {\n                        default: () => <TableCell>{p.inventory?.qty}</TableCell>\n                      },\n                      sortOrder: 25\n                    },\n                    {\n                      component: {\n                        default: ({ areaProps }) => (\n                          <Status id=\"status\" status={parseInt(p.status, 10)} />\n                        )\n                      },\n                      sortOrder: 30\n                    }\n                  ]}\n                />\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {products.length === 0 && (\n          <div className=\"flex w-full justify-center mt-2\">\n            There is no product to display\n          </div>\n        )}\n        <GridPagination total={total} limit={limit} page={page} />\n      </CardContent>\n    </Card>\n  );\n}\n\nProductGrid.propTypes = {\n  products: PropTypes.shape({\n    items: PropTypes.arrayOf(\n      PropTypes.shape({\n        productId: PropTypes.number,\n        uuid: PropTypes.string,\n        name: PropTypes.string,\n        image: PropTypes.shape({\n          thumb: PropTypes.string\n        }),\n        sku: PropTypes.string,\n        status: PropTypes.number,\n        inventory: PropTypes.shape({\n          qty: PropTypes.number\n        }),\n        price: PropTypes.shape({\n          regular: PropTypes.shape({\n            value: PropTypes.number,\n            text: PropTypes.string\n          })\n        }),\n        editUrl: PropTypes.string,\n        updateApi: PropTypes.string,\n        deleteApi: PropTypes.string\n      })\n    ),\n    total: PropTypes.number,\n    currentFilters: PropTypes.arrayOf(\n      PropTypes.shape({\n        key: PropTypes.string,\n        operation: PropTypes.string,\n        value: PropTypes.string\n      })\n    )\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query($filters: [FilterInput]) {\n    products (filters: $filters) {\n      items {\n        productId\n        uuid\n        name\n        image {\n          url\n          alt\n        }\n        sku\n        status\n        inventory {\n          qty\n        }\n        price {\n          regular {\n            value\n            text\n          }\n        }\n        editUrl\n        updateApi\n        deleteApi\n      }\n      total\n      currentFilters {\n        key\n        operation\n        value\n      }\n    }\n    newProductUrl: url(routeId: \"productNew\")\n  }\n`;\n\nexport const variables = `\n{\n  filters: getContextValue('filtersFromUrl')\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productGrid/NewProductButton.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\n\ninterface NewProductButtonProps {\n  newProductUrl: string;\n}\nexport default function NewProductButton({\n  newProductUrl\n}: NewProductButtonProps) {\n  return (\n    <Button\n      onClick={() => (window.location.href = newProductUrl)}\n      title=\"New Product\"\n    >\n      New Product\n    </Button>\n  );\n}\n\nexport const layout = {\n  areaId: 'pageHeadingRight',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    newProductUrl: url(routeId: \"productNew\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productGrid/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface ProductGridPageHeadingProps {\n  backUrl: string;\n  product?: {\n    name?: string;\n  };\n}\n\nexport default function ProductEditPageHeading({\n  backUrl,\n  product\n}: ProductGridPageHeadingProps) {\n  return (\n    <PageHeading\n      backUrl={backUrl}\n      heading={product ? `Editing ${product.name}` : 'Create a new product'}\n    />\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productGrid/index.ts",
    "content": "import { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default (request, response) => {\n  setPageMetaInfo(request, {\n    title: 'Products',\n    description: 'Products'\n  });\n  setContextValue(\n    request,\n    'filtersFromUrl',\n    buildFilterFromUrl(request.originalUrl)\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productGrid/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/products\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productGrid/rows/ProductName.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\nexport interface ProductNameRowProps {\n  name: string;\n  url: string;\n}\nexport function ProductNameRow({ url, name }: ProductNameRowProps) {\n  return (\n    <TableCell>\n      <div>\n        <a className=\"hover:underline font-semibold\" href={url} title={name}>\n          {name.length > 50 ? `${name.substring(0, 50)}...` : name}\n        </a>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productNew/ProductNewForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport React from 'react';\nimport { SubmitHandler, useForm } from 'react-hook-form';\nimport { toast } from 'react-toastify';\n\nexport default function ProductNewForm({\n  action,\n  gridUrl\n}: {\n  action: string;\n  gridUrl: string;\n}) {\n  const form = useForm({\n    shouldUnregister: true\n  });\n  const submit: SubmitHandler<any> = async (data) => {\n    try {\n      const images = (data.images || []).map(\n        (image: { uuid: string; url: string }) => image.url\n      );\n      data.images = images;\n      const response = await fetch(action, {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify({ ...data, action: undefined, method: undefined })\n      });\n      const result = await response.json();\n      if (result.error) {\n        toast.error(result.error.message);\n      } else {\n        toast.success('Product created successfully');\n        const editUrl = result.data.links.find(\n          (link) => link.rel === 'edit'\n        ).href;\n        setTimeout(() => {\n          window.location.href = editUrl;\n        }, 1500);\n      }\n    } catch (error) {\n      toast.error(error.message);\n    }\n  };\n  return (\n    <Form\n      id=\"productNewForm\"\n      method=\"POST\"\n      action={action}\n      form={form}\n      onSubmit={submit}\n      submitBtn={false}\n    >\n      <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"leftSide\" noOuter />\n        </div>\n        <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"rightSide\" noOuter />\n        </div>\n      </div>\n      <FormButtons formId=\"productNewForm\" cancelUrl={gridUrl} />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"createProduct\"),\n    gridUrl: url(routeId: \"productGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productNew/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request, response) => {\n  setPageMetaInfo(request, {\n    title: 'Create a new product',\n    description: 'Create a new product'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/admin/productNew/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/products/new\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/frontStore/all/SearchBox.tsx",
    "content": "import { SearchBox as Search } from '@components/frontStore/catalog/SearchBox.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface SearchBoxProps {\n  searchPageUrl: string;\n}\nexport default function SearchBox({ searchPageUrl }: SearchBoxProps) {\n  return (\n    <Search searchPageUrl={searchPageUrl} enableAutocomplete maxResults={10} />\n  );\n}\n\nexport const layout = {\n  areaId: 'headerMiddleRight',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    searchPageUrl: url(routeId: \"catalogSearch\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/frontStore/catalogSearch/SearchPage.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport {\n  SearchPageData,\n  SearchProvider\n} from '@components/frontStore/catalog/SearchContext.js';\nimport { SearchInfo } from '@components/frontStore/catalog/SearchInfo.js';\nimport { SearchProducts } from '@components/frontStore/catalog/SearchProducts.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface SearchPageProps {\n  search: SearchPageData;\n}\n\nexport default function SearchPage({ search }: SearchPageProps) {\n  return (\n    <SearchProvider searchData={search}>\n      <Area id=\"searchPageTop\" className=\"search__page__top\" />\n      <div className=\"page-width grid grid-cols-1 \">\n        <SearchInfo />\n        <SearchProducts />\n      </div>\n      <Area id=\"searchPageBottom\" className=\"search__page__bottom\" />\n    </SearchProvider>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    search: productSearch {\n      keyword\n      products {\n        items {\n          ...Product\n        }\n        currentFilters {\n          key\n          operation\n          value\n        }\n        total\n      }\n    }\n}`;\n\nexport const fragments = `\n  fragment Product on Product {\n    productId\n    name\n    sku\n    price {\n      regular {\n        value\n        text\n      }\n      special {\n        value\n        text\n      }\n    }\n    inventory {\n      isInStock\n    }\n    image {\n      alt\n      url\n    }\n    url\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/frontStore/catalogSearch/index.ts",
    "content": "import { translate } from '../../../../../lib/locale/translate/translate.js';\nimport { get } from '../../../../../lib/util/get.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request: EvershopRequest, response, next) => {\n  // Get the keyword from the request query\n  const keyword = get(request, 'query.keyword');\n  if (!keyword) {\n    // Redirect to the home page if no keyword is not provided\n    response.redirect('/');\n  } else {\n    setPageMetaInfo(request, {\n      title: translate('Search results for: ${keyword}', { keyword }),\n      description: translate('Search results for: ${keyword}', { keyword })\n    });\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/frontStore/catalogSearch/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/search\",\n  \"name\": \"Catalog search page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/frontStore/categoryView/CategoryView.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport {\n  CategoryData,\n  CategoryProvider\n} from '@components/frontStore/catalog/CategoryContext.js';\nimport { CategoryInfo } from '@components/frontStore/catalog/CategoryInfo.js';\nimport { CategoryProducts } from '@components/frontStore/catalog/CategoryProducts.js';\nimport { CategoryProductsFilter } from '@components/frontStore/catalog/CategoryProductsFilter.js';\nimport { CategoryProductsPagination } from '@components/frontStore/catalog/CategoryProductsPagination.js';\nimport { ProductSorting } from '@components/frontStore/catalog/ProductSorting.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface CategoryViewProps {\n  category: CategoryData;\n}\n\nexport default function CategoryView({ category }: CategoryViewProps) {\n  return (\n    <CategoryProvider category={category}>\n      <Area id=\"categoryPageTop\" className=\"category__page__top\" />\n      <CategoryInfo />\n      <div className=\"page-width grid grid-cols-1 md:grid-cols-4 gap-5\">\n        <Area\n          id=\"categoryLeftColumn\"\n          className=\"md:col-span-1\"\n          coreComponents={[\n            {\n              component: { default: <CategoryProductsFilter /> },\n              sortOrder: 10,\n              id: 'productFilter'\n            }\n          ]}\n        />\n        <Area\n          id=\"categoryRightColumn\"\n          className=\"md:col-span-3\"\n          coreComponents={[\n            {\n              component: {\n                default: (\n                  <ProductSorting\n                    className=\"flex justify-start\"\n                    count={category.products.total}\n                  />\n                )\n              },\n              sortOrder: 10,\n              id: 'categoryProductsSorting'\n            },\n            {\n              component: { default: <CategoryProducts /> },\n              sortOrder: 20,\n              id: 'categoryProducts'\n            },\n            {\n              component: { default: <CategoryProductsPagination /> },\n              sortOrder: 30,\n              id: 'categoryProductsPagination'\n            }\n          ]}\n        />\n      </div>\n      <Area id=\"categoryPageBottom\" className=\"category__page__bottom\" />\n    </CategoryProvider>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    category: currentCategory {\n      showProducts\n      name\n      uuid\n      description\n      image {\n        alt\n        url\n      }\n      products {\n        items {\n          ...Product\n        }\n        currentFilters {\n          key\n          operation\n          value\n        }\n        total\n      }\n      availableAttributes {\n        attributeCode\n        attributeName\n        options {\n          optionId\n          optionText\n        }\n      }\n      priceRange {\n        min\n        max\n        minText\n        maxText\n      }\n      children {\n        categoryId,\n        name\n        uuid\n      }\n    }\n}`;\n\nexport const fragments = `\n  fragment Product on Product {\n    productId\n    name\n    sku\n    price {\n      regular {\n        value\n        text\n      }\n      special {\n        value\n        text\n      }\n    }\n    inventory {\n      isInStock\n    }\n    image {\n      alt\n      url\n    }\n    url\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/frontStore/categoryView/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { getBaseUrl } from '../../../../../lib/util/getBaseUrl.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (request, response, next) => {\n  try {\n    const query = select();\n    query\n      .from('category')\n      .leftJoin('category_description')\n      .on(\n        'category.category_id',\n        '=',\n        'category_description.category_description_category_id'\n      );\n\n    query.where('category.uuid', '=', request.params.uuid);\n    const category = await query.load(pool);\n    if (category === null) {\n      response.status(404);\n      next();\n    } else {\n      setContextValue(request, 'categoryId', category.category_id);\n      setPageMetaInfo(request, {\n        title: category.meta_title || category.name,\n        description: category.meta_description || category.short_description\n      });\n\n      if (category.image) {\n        const baseUrl = getBaseUrl();\n        setPageMetaInfo(request, {\n          ogInfo: {\n            image: `${baseUrl}/images?src=${category.image}&w=1200&q=80&h=675&f=png`\n          }\n        });\n      }\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/frontStore/categoryView/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/category/:uuid\",\n  \"name\": \"Category page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/frontStore/productView/ProductView.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Media } from '@components/frontStore/catalog/Media.js';\nimport {\n  ProductData,\n  ProductProvider\n} from '@components/frontStore/catalog/ProductContext.js';\nimport { ProductSingleAttributes } from '@components/frontStore/catalog/ProductSingleAttributes.js';\nimport { ProductSingleDescription } from '@components/frontStore/catalog/ProductSingleDescription.js';\nimport { ProductSingleForm } from '@components/frontStore/catalog/ProductSingleForm.js';\nimport { ProductSingleName } from '@components/frontStore/catalog/ProductSingleName.js';\nimport React from 'react';\n\nexport default function ProductView({ product }: ProductData) {\n  return (\n    <ProductProvider product={product}>\n      <div className=\"product__detail\">\n        <Area id=\"productPageTop\" className=\"product__page__top\" />\n        <div className=\"product__page__middle page-width\">\n          <div className=\"grid grid-cols-1 gap-7 md:grid-cols-2\">\n            <Area\n              id=\"productPageMiddleLeft\"\n              className=\"product__detail__left\"\n              coreComponents={[\n                {\n                  component: { default: <Media /> },\n                  sortOrder: 0,\n                  id: 'media'\n                }\n              ]}\n            />\n            <Area\n              id=\"productPageMiddleRight\"\n              className=\"product__detail__right\"\n              coreComponents={[\n                {\n                  component: { default: <ProductSingleName /> },\n                  sortOrder: 10,\n                  id: 'name'\n                },\n                {\n                  component: { default: <ProductSingleAttributes /> },\n                  sortOrder: 20,\n                  id: 'attributes'\n                },\n                {\n                  component: { default: <ProductSingleForm /> },\n                  sortOrder: 30,\n                  id: 'productForm'\n                }\n              ]}\n            />\n          </div>\n          <Area\n            id=\"productSingleDescription\"\n            coreComponents={[\n              {\n                component: { default: <ProductSingleDescription /> },\n                sortOrder: 10,\n                id: 'productSingleDescription'\n              }\n            ]}\n          />\n        </div>\n        <Area id=\"productPageBottom\" className=\"product__page__bottom\" />\n      </div>\n    </ProductProvider>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\nquery Query {\n    product: currentProduct {\n      name\n      description\n      sku\n      price {\n        regular {\n          value\n          text\n        }\n        special {\n          value\n          text\n        }\n      }\n      inventory {\n        isInStock\n      }\n      attributes: attributeIndex {\n        attributeName\n        attributeCode\n        optionText\n      }\n      image {\n        alt\n        url\n      }\n      gallery {\n        alt\n        url\n      }\n      variantGroup {\n        variantAttributes {\n          attributeId\n          attributeCode\n          attributeName\n          options {\n            optionId\n            optionText\n            productId\n          }\n        }\n        items {\n          attributes {\n            attributeCode\n            optionId\n          }\n        }\n      }\n    }\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/frontStore/productView/index.ts",
    "content": "import { node, select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { get } from '../../../../../lib/util/get.js';\nimport { getBaseUrl } from '../../../../../lib/util/getBaseUrl.js';\nimport { getConfig } from '../../../../../lib/util/getConfig.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (request, response, next) => {\n  let currentProduct;\n  try {\n    const query = select();\n    query\n      .from('product')\n      .leftJoin('product_description')\n      .on(\n        'product.product_id',\n        '=',\n        'product_description.product_description_product_id'\n      );\n    query\n      .innerJoin('product_inventory')\n      .on(\n        'product.product_id',\n        '=',\n        'product_inventory.product_inventory_product_id'\n      );\n    query.where('product.uuid', '=', request.params.uuid);\n    query.andWhere('status', '=', 1);\n    const product = await query.load(pool);\n\n    if (product === null) {\n      response.status(404);\n      next();\n    } else {\n      const queries = request.query;\n      if (\n        !get(product, 'variant_group_id') ||\n        Object.values(queries).length === 0\n      ) {\n        currentProduct = product;\n      } else {\n        const group = await select()\n          .from('variant_group')\n          .select('attribute_one')\n          .select('attribute_two')\n          .select('attribute_three')\n          .select('attribute_four')\n          .select('attribute_five')\n          .where('variant_group_id', '=', product.variant_group_id)\n          .load(pool);\n\n        const attributes = await select()\n          .from('attribute')\n          .where(\n            'attribute_id',\n            'IN',\n            Object.values(group).filter((v) => v != null)\n          )\n          .and('attribute_code', 'IN', Object.keys(queries))\n          .execute(pool);\n\n        if (attributes.length > 0) {\n          const vsQuery = select()\n            .from('product', 'p')\n            .select('p.product_id')\n            .select('COUNT(p.product_id)', 'count');\n\n          vsQuery\n            .innerJoin('product_inventory')\n            .on(\n              'p.product_id',\n              '=',\n              'product_inventory.product_inventory_product_id'\n            );\n          vsQuery\n            .innerJoin('product_attribute_value_index', 'a')\n            .on('p.product_id', '=', 'a.product_id');\n          vsQuery\n            .where('p.variant_group_id', '=', product.variant_group_id)\n            .and('p.status', '=', 1);\n\n          if (getConfig('catalog.showOutOfStockProduct') === false) {\n            vsQuery\n              .andWhere('product_inventory.manage_stock', '=', false)\n              .addNode(\n                node('OR')\n                  .addLeaf('AND', 'product_inventory.qty', '>', 0)\n                  .addLeaf(\n                    'AND',\n                    'product_inventory.stock_availability',\n                    '=',\n                    true\n                  )\n              );\n          }\n          vsQuery\n            .andWhere(\n              'a.attribute_id',\n              'IN',\n              attributes.map((a) => a.attribute_id)\n            )\n            .and(\n              'a.option_id',\n              'IN',\n              attributes.map((a) => queries[a.attribute_code])\n            );\n          vsQuery.groupBy('p.product_id');\n          vsQuery.having('COUNT(p.product_id)', '>=', attributes.length);\n          const variants = await vsQuery.execute(pool);\n\n          if (variants.length > 0) {\n            const variantQuery = select();\n            variantQuery\n              .from('product')\n              .leftJoin('product_description')\n              .on(\n                'product.product_id',\n                '=',\n                'product_description.product_description_product_id'\n              );\n            variantQuery\n              .innerJoin('product_inventory')\n              .on(\n                'product.product_id',\n                '=',\n                'product_inventory.product_inventory_product_id'\n              );\n            variantQuery.where('product_id', '=', variants[0].product_id);\n            const pv = await variantQuery.load(pool);\n            currentProduct = pv;\n          } else {\n            currentProduct = product;\n          }\n        } else {\n          currentProduct = product;\n        }\n      }\n      setContextValue(request, 'productId', currentProduct.product_id);\n      setContextValue(request, 'currentProductId', currentProduct.product_id);\n      setPageMetaInfo(request, {\n        title: currentProduct.meta_title || currentProduct.name,\n        description: currentProduct.meta_description || currentProduct.name\n      });\n      const productImage = await select()\n        .from('product_image')\n        .where('product_image_product_id', '=', currentProduct.product_id)\n        .and('is_main', '=', true)\n        .load(pool);\n      if (productImage) {\n        const baseUrl = getBaseUrl();\n        setPageMetaInfo(request, {\n          ogInfo: {\n            image: `${baseUrl}/images?src=${baseUrl}${productImage.origin_image}&w=1200&q=80&h=675&f=png`\n          }\n        });\n      }\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/pages/frontStore/productView/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/product/:uuid\",\n  \"name\": \"Product single page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/AttributeCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class AttributeCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n  }\n\n  async init(filters = []) {\n    const currentFilters = [];\n\n    // Apply the filters\n    const attributeCollectionFilters = await getValue(\n      'attributeCollectionFilters',\n      []\n    );\n\n    attributeCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(attribute.attribute_id)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/AttributeGroupCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValue, getValueSync } from '../../../lib/util/registry.js';\n\nexport class AttributeGroupCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n  }\n\n  async init(filters = []) {\n    const currentFilters = [];\n    const defaultFilters = [\n      {\n        key: 'name',\n        operation: ['eq', 'like', 'nlike'],\n        callback: (query, operation, value, currentFilters) => {\n          query.andWhere(\n            'attribute_group.group_name',\n            OPERATION_MAP[operation],\n            value\n          );\n          currentFilters.push({\n            key: 'name',\n            operation,\n            value\n          });\n        }\n      },\n      {\n        key: 'ob',\n        operation: ['eq'],\n        callback: (query, operation, value, currentFilters) => {\n          const attributeGroupsSortBy = getValueSync('attributeGroupsSortBy', {\n            name: (query) => query.orderBy('attribute_group.group_name')\n          });\n\n          if (attributeGroupsSortBy[value]) {\n            attributeGroupsSortBy[value](query, operation);\n            currentFilters.push({\n              key: 'ob',\n              operation,\n              value\n            });\n          }\n        }\n      }\n    ];\n    // Apply the filters\n    const attributeGroupCollectionFilters = await getValue(\n      'attributeGroupCollectionFilters',\n      defaultFilters\n    );\n\n    attributeGroupCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(attribute_group.attribute_group_id)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/CategoryCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class CategoryCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n    this.baseQuery.orderBy('category.category_id', 'DESC');\n  }\n\n  async init(filters = [], isAdmin = false) {\n    if (!isAdmin) {\n      this.baseQuery.andWhere('category.status', '=', 1);\n    }\n    const currentFilters = [];\n\n    // Apply the filters\n    const categoryCollectionFilters = await getValue(\n      'categoryCollectionFilters',\n      [],\n      {\n        isAdmin\n      }\n    );\n\n    categoryCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(category.category_id)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/CollectionCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class CollectionCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n    this.baseQuery.orderBy('collection.collection_id', 'DESC');\n  }\n\n  async init(filters = []) {\n    const currentFilters = [];\n\n    // Apply the filters\n    const collectionCollectionFilters = await getValue(\n      'collectionCollectionFilters',\n      []\n    );\n\n    collectionCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(collection.collection_id)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/ProductCollection.js",
    "content": "import { node, select, sql } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class ProductCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n    this.baseQuery.orderBy('product.product_id', 'DESC');\n  }\n\n  /**\n   *\n   * @param {{key: String, operation: String, value: String}[]} filters\n   * @param {boolean} isAdmin\n   */\n  async init(filters = [], isAdmin = false) {\n    // If the user is not admin, we need to filter out the out of stock products and the disabled products\n    if (!isAdmin) {\n      this.baseQuery.andWhere('product.status', '=', 1);\n      if (getConfig('catalog.showOutOfStockProduct', false) === false) {\n        this.baseQuery\n          .andWhere('product_inventory.manage_stock', '=', false)\n          .addNode(\n            node('OR')\n              .addLeaf('AND', 'product_inventory.qty', '>', 0)\n              .addLeaf('AND', 'product_inventory.stock_availability', '=', true)\n          );\n      }\n    }\n    const currentFilters = [];\n    // Attribute filter\n    const filterableAttributes = await select()\n      .from('attribute')\n      .where('type', '=', 'select')\n      .and('is_filterable', '=', 1)\n      .execute(pool);\n    // Apply the filters\n    const productCollectionFilters = await getValue(\n      'productCollectionFilters',\n      [],\n      {\n        isAdmin,\n        filterableAttributes\n      }\n    );\n    productCollectionFilters.forEach((filter) => {\n      const check =\n        filters &&\n        filters.find(\n          (f) => f.key === filter.key && filter.operation.includes(f.operation)\n        );\n      if (filter.key === '*' || check) {\n        filter.callback.apply({ isAdmin }, [\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        ]);\n      }\n    });\n\n    if (!isAdmin) {\n      // Visibility. For variant group\n      const copy = this.baseQuery.clone();\n      // Get all group that have at lease 1 item visibile\n      const visibleGroups = (\n        await select('variant_group_id')\n          .from('variant_group')\n          .where('visibility', '=', 't')\n          .execute(pool)\n      ).map((v) => v.variant_group_id);\n\n      if (visibleGroups) {\n        // Get all invisible variants from current query\n        copy\n          .select('bool_or(product.visibility)', 'sumv')\n          .select('max(product.product_id)', 'product_id')\n          .andWhere('product.variant_group_id', 'IN', visibleGroups);\n        copy.groupBy('product.variant_group_id');\n        copy.orderBy('product.variant_group_id', 'ASC');\n        copy.having('bool_or(product.visibility)', '=', 'f');\n        const invisibleIds = (await copy.execute(pool)).map(\n          (v) => v.product_id\n        );\n        if (invisibleIds.length > 0) {\n          const n = node('AND');\n          n.addLeaf('AND', 'product.product_id', 'IN', invisibleIds).addNode(\n            node('OR').addLeaf('OR', 'product.visibility', '=', 't')\n          );\n          this.baseQuery.getWhere().addNode(n);\n        } else {\n          this.baseQuery.andWhere('product.visibility', '=', 't');\n        }\n      } else {\n        this.baseQuery.andWhere('product.visibility', '=', 't');\n      }\n    } else {\n      const onePerVariantGroupQuery = this.baseQuery.clone();\n      onePerVariantGroupQuery.removeLimit();\n      onePerVariantGroupQuery.select(\n        sql(\n          'DISTINCT ON (COALESCE(product.variant_group_id, random())) product.product_id',\n          'product_id'\n        )\n      );\n      onePerVariantGroupQuery.removeOrderBy();\n      const onePerGroup = await onePerVariantGroupQuery.execute(pool);\n      this.baseQuery.andWhere(\n        'product.product_id',\n        'IN',\n        onePerGroup.map((v) => v.product_id)\n      );\n    }\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(product.product_id)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/attribute/attributeDataSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"attribute_name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Attribute name must be a string\",\n        \"minLength\": \"Attribute name is required and cannot be empty\"\n      }\n    },\n    \"attribute_code\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Attribute code must be a string\",\n        \"minLength\": \"Attribute code is required and cannot be empty\"\n      }\n    },\n    \"is_required\": {\n      \"type\": [\"integer\", \"string\"],\n      \"enum\": [0, 1, \"0\", \"1\"],\n      \"errorMessage\": {\n        \"type\": \"Is required must be a number or string\",\n        \"enum\": \"Is required must be either 0, 1, '0', or '1'\"\n      }\n    },\n    \"display_on_frontend\": {\n      \"type\": [\"integer\", \"string\"],\n      \"enum\": [0, 1, \"0\", \"1\"],\n      \"errorMessage\": {\n        \"type\": \"Display on frontend must be a number or string\",\n        \"enum\": \"Display on frontend must be either 0, 1, '0', or '1'\"\n      }\n    },\n    \"sort_order\": {\n      \"type\": [\"string\", \"integer\"],\n      \"pattern\": \"^[0-9]*$\",\n      \"errorMessage\": {\n        \"type\": \"Sort order must be a string or number\",\n        \"pattern\": \"Sort order must be a valid number (e.g., 0, 1, 10)\"\n      }\n    },\n    \"is_filterable\": {\n      \"type\": [\"integer\", \"string\"],\n      \"enum\": [0, 1, \"0\", \"1\"],\n      \"errorMessage\": {\n        \"type\": \"Is filterable must be a number or string\",\n        \"enum\": \"Is filterable must be either 0, 1, '0', or '1'\"\n      }\n    },\n    \"groups\": {\n      \"type\": \"array\",\n      \"minItems\": 1,\n      \"items\": [\n        {\n          \"type\": [\"string\", \"integer\"],\n          \"pattern\": \"^[1-9][0-9]*$\",\n          \"default\": 100000,\n          \"errorMessage\": {\n            \"type\": \"Group ID must be a string or number\",\n            \"pattern\": \"Group ID must be a valid positive number (e.g., 1, 10, 100)\"\n          }\n        }\n      ],\n      \"errorMessage\": {\n        \"type\": \"Groups must be an array\",\n        \"minItems\": \"At least one group is required\"\n      }\n    },\n    \"options\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"option_text\": {\n            \"type\": \"string\",\n            \"minLength\": 1,\n            \"errorMessage\": {\n              \"type\": \"Option text must be a string\",\n              \"minLength\": \"Option text is required and cannot be empty\"\n            }\n          },\n          \"option_id\": {\n            \"type\": [\"string\", \"integer\"],\n            \"pattern\": \"^[1-9][0-9]*$\",\n            \"errorMessage\": {\n              \"type\": \"Option ID must be a string or number\",\n              \"pattern\": \"Option ID must be a valid positive number (e.g., 1, 10, 100)\"\n            }\n          }\n        },\n        \"required\": [\"option_text\"],\n        \"errorMessage\": {\n          \"type\": \"Option must be an object\",\n          \"required\": {\n            \"option_text\": \"Option text is required\"\n          }\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Options must be an array\"\n      }\n    }\n  },\n  \"required\": [\n    \"attribute_code\",\n    \"attribute_name\",\n    \"type\",\n    \"is_required\",\n    \"display_on_frontend\",\n    \"groups\"\n  ],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"required\": {\n      \"attribute_code\": \"Attribute code is required\",\n      \"attribute_name\": \"Attribute name is required\",\n      \"type\": \"Attribute type is required\",\n      \"is_required\": \"Is required field is required\",\n      \"display_on_frontend\": \"Display on frontend field is required\",\n      \"groups\": \"At least one group must be assigned\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/attribute/createProductAttribute.ts",
    "content": "import {\n  commit,\n  insert,\n  insertOnUpdate,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport type { PoolClient } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport attributeDataSchema from './attributeDataSchema.json' with { type: 'json' };\n\nexport type AttributeData = {\n  attribute_code: string;\n  attribute_name: string;\n  type: string;\n  is_required: boolean;\n  display_on_frontend?: boolean;\n  groups: number[];\n  options?: { option_text: string, option_id: string | number }[];\n  [key: string]: unknown;\n};\n\nfunction validateAttributeDataBeforeInsert(data: AttributeData) {\n  const ajv = getAjv();\n  attributeDataSchema.required = [\n    'attribute_code',\n    'attribute_name',\n    'type',\n    'is_required',\n    'display_on_frontend',\n    'groups'\n  ];\n  const jsonSchema = getValueSync(\n    'createAttributeDataJsonSchema',\n    attributeDataSchema,\n    {}\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function insertAttributeGroups(attributeId: number, groups: number[], connection: PoolClient) {\n  // Ignore updating groups if it is not present in the data\n  if (groups.length === 0) {\n    return;\n  }\n\n  for (let index = 0; index < groups.length; index += 1) {\n    const group = await select()\n      .from('attribute_group')\n      .where('attribute_group_id', '=', groups[index])\n      .load(connection, false);\n    if (group) {\n      await insertOnUpdate('attribute_group_link', ['attribute_id', 'group_id'])\n        .given({ attribute_id: attributeId, group_id: groups[index] })\n        .execute(connection);\n    }\n  }\n}\n\nasync function insertAttributeOptions(\n  attributeId: number,\n  attributeType: string,\n  attributeCode: string,\n  options: { option_text: string }[],\n  connection: PoolClient\n) {\n  // Ignore updating options if it is not present in the data\n  if (\n    options.length === 0 ||\n    !['select', 'multiselect'].includes(attributeType)\n  ) {\n    return;\n  }\n\n  /* Adding new options */\n  await Promise.all(\n    options.map(async (option) => {\n      await insert('attribute_option')\n        .given({\n          option_text: option.option_text,\n          attribute_id: attributeId,\n          attribute_code: attributeCode\n        })\n        .execute(connection);\n    })\n  );\n}\n\nasync function insertAttributeData(data: AttributeData, connection: PoolClient) {\n  const result = await insert('attribute').given(data).execute(connection);\n  return result;\n}\n\n/**\n * Create attribute service. This service will create a attribute with all related data\n * @param {Object} data\n * @param {Object} context\n */\nasync function createAttribute(data: AttributeData, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const attributeData = await getValue('attributeDataBeforeCreate', data);\n    // Validate attribute data\n    validateAttributeDataBeforeInsert(attributeData);\n\n    // Insert attribute data\n    const attribute = await hookable(insertAttributeData, {\n      ...context,\n      connection\n    })(attributeData, connection);\n\n    // Save attribute groups\n    await hookable(insertAttributeGroups, {\n      ...context,\n      attribute,\n      connection\n    })(attribute.insertId, attributeData.groups || [], connection);\n\n    // Save attribute options\n    await hookable(insertAttributeOptions, {\n      ...context,\n      attribute,\n      connection\n    })(\n      attribute.insertId,\n      attribute.type,\n      attribute.attribute_code,\n      attributeData.options || [],\n      connection\n    );\n\n    await commit(connection);\n    return attribute;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Create attribute service. This service will create a attribute with all related data\n * @param {Object} data\n * @param {Object} context\n */\nexport default async (data: AttributeData, context: Record<string, any>) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const attribute = await hookable(createAttribute, context)(data, context);\n  return attribute;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/attribute/deleteProductAttribute.ts",
    "content": "import {\n  commit,\n  del,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport type { PoolClient } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\n\nasync function deleteAttributeData(uuid: string, connection: PoolClient) {\n  await del('attribute').where('uuid', '=', uuid).execute(connection);\n}\n/**\n * Delete attribute service. This service will delete an attribute with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nasync function deleteAttribute(uuid: string, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const attribute = await select()\n      .from('attribute')\n      .where('uuid', '=', uuid)\n      .load(connection);\n\n    if (!attribute) {\n      throw new Error('Invalid attribute id');\n    }\n\n    // Make sure the attribute is not being used in any variant group\n    const variantGroup = await select()\n      .from('variant_group')\n      .where('attribute_one', '=', attribute.attribute_id)\n      .or('attribute_two', '=', attribute.attribute_id)\n      .or('attribute_three', '=', attribute.attribute_id)\n      .or('attribute_four', '=', attribute.attribute_id)\n      .or('attribute_five', '=', attribute.attribute_id)\n      .load(connection);\n    if (variantGroup) {\n      throw new Error(\n        `The attribute \"${attribute.attribute_name}\" is being used in a variant group`\n      );\n    }\n    await hookable(deleteAttributeData, { ...context, connection, attribute })(\n      uuid,\n      connection\n    );\n    await commit(connection);\n    return attribute;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Delete attribute service. This service will delete an attribute with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nexport default async (uuid: string, context: Record<string, any>) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const attribute = await hookable(deleteAttribute, context)(uuid, context);\n  return attribute;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/attribute/updateProductAttribute.ts",
    "content": "import type { PoolClient } from '@evershop/postgres-query-builder';\nimport {\n  startTransaction,\n  commit,\n  rollback,\n  update,\n  select,\n  insertOnUpdate,\n  del,\n  insert\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport { getValueSync, getValue } from '../../../../lib/util/registry.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport attributeDataSchema from './attributeDataSchema.json' with { type: 'json' };\n\nexport type AttributeData = {\n  attribute_code?: string;\n  type?: string;\n  groups: number[];\n  options: { option_text: string, option_id: string | number }[];\n  [key: string]: unknown;\n};\n\nfunction validateAttributeDataBeforeInsert(data: AttributeData) {\n  const ajv = getAjv();\n  attributeDataSchema.required = [];\n  const jsonSchema = getValueSync(\n    'updateAttributeDataJsonSchema',\n    attributeDataSchema,\n    {}\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function updateAttributeOptions(\n  attributeId: number,\n  attributeType: string,\n  attributeCode: string,\n  options: { option_text: string, option_id: string | number }[],\n  connection: PoolClient\n) {\n  // Ignore updating options if it is not present in the data or if the attribute type is not select or multiselect\n  if (\n    !['select', 'multiselect'].includes(attributeType)\n  ) {\n    return;\n  }\n\n  const ids = options\n    .filter((o) => o !== undefined)\n    .map((o) => (typeof o.option_id === 'string' ? parseInt(o.option_id, 10) : o.option_id));\n  const oldOptions = await select()\n    .from('attribute_option')\n    .where('attribute_id', '=', attributeId)\n    .execute(connection, false);\n  await Promise.all(\n    oldOptions.map(async (oldOption) => {\n      if (!ids.includes(parseInt(oldOption.attribute_option_id, 10))) {\n        await del('attribute_option')\n          .where('attribute_option_id', '=', oldOption.attribute_option_id)\n          .execute(connection, false);\n      }\n    })\n  );\n  /* Adding new options */\n  // Looping options array and insert/update options. Use Promise.all to make sure all options are inserted/updated\n  await Promise.all(\n    options.map(async (option) => {\n      const exists = await select()\n        .from('attribute_option')\n        .where('attribute_option_id', '=', option.option_id)\n        .load(connection, false);\n\n      if (exists) {\n        await update('attribute_option')\n          .given({\n            option_text: option.option_text,\n            attribute_id: attributeId,\n            attribute_code: attributeCode\n          })\n          .where('attribute_option_id', '=', option.option_id)\n          .execute(connection, false);\n      } else {\n        await insert('attribute_option')\n          .given({\n            option_text: option.option_text,\n            attribute_id: attributeId,\n            attribute_code: attributeCode\n          })\n          .execute(connection, false);\n      }\n    })\n  );\n}\n\nasync function updateAttributeGroups(attributeId: number, groups: number[], connection: PoolClient) {\n  // Ignore updating groups if it is not present in the data\n  if (groups.length === 0) {\n    return;\n  }\n\n  // Get the current groups\n  const currentGroups = await select()\n    .from('attribute_group_link')\n    .where('attribute_id', '=', attributeId)\n    .execute(connection);\n\n  const shouldDelete: number[] = [];\n  currentGroups.forEach((g) => {\n    if (\n      !groups.find((group) => parseInt(group.toString(), 10) === parseInt(g.group_id.toString(), 10))\n    ) {\n      shouldDelete.push(g.group_id);\n    }\n  });\n\n  for (let index = 0; index < groups.length; index += 1) {\n    const group = await select()\n      .from('attribute_group')\n      .where('attribute_group_id', '=', groups[index])\n      .load(connection, false);\n    if (group) {\n      await insertOnUpdate('attribute_group_link', ['attribute_id', 'group_id'])\n        .given({ attribute_id: attributeId, group_id: groups[index] })\n        .execute(connection);\n    }\n  }\n\n  await del('attribute_group_link')\n    .where('group_id', 'IN', shouldDelete)\n    .and('attribute_id', '=', attributeId)\n    .execute(connection);\n}\n\nasync function updateAttributeData(uuid: string, data: AttributeData, connection: PoolClient) {\n  const attribute = await select()\n    .from('attribute')\n    .where('uuid', '=', uuid)\n    .load(connection);\n\n  if (!attribute) {\n    throw new Error('Requested attribute not found');\n  }\n  try {\n    const attribute = await update('attribute')\n      .given(data)\n      .where('uuid', '=', uuid)\n      .execute(connection);\n    return attribute;\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    } else {\n      return attribute;\n    }\n  }\n}\n\n/**\n * Update attribute service. This service will update a attribute with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nasync function updateAttribute(uuid: string, data: AttributeData, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const attributeData = await getValue('attributeDataBeforeUpdate', data);\n    // Delete the attribute_code and type from the data object, we do not allow to update these fields\n    delete attributeData.attribute_code;\n    delete attributeData.type;\n\n    // Validate attribute data\n    validateAttributeDataBeforeInsert(attributeData);\n\n    // Insert attribute data\n    const attribute = await hookable(updateAttributeData, {\n      ...context,\n      connection\n    })(uuid, attributeData, connection);\n\n    // Save attribute groups\n    await hookable(updateAttributeGroups, {\n      ...context,\n      connection,\n      attribute\n    })(attribute.attribute_id, attributeData.groups || [], connection);\n\n    // Save attribute options\n    await hookable(updateAttributeOptions, {\n      ...context,\n      connection,\n      attribute\n    })(\n      attribute.attribute_id,\n      attribute.type,\n      attribute.attribute_code,\n      attributeData.options || [],\n      connection\n    );\n\n    await commit(connection);\n    return attribute;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Update attribute service. This service will update a attribute with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nexport default async (uuid: string, data: AttributeData, context: Record<string, any>) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const attribute = await hookable(updateAttribute, context)(\n    uuid,\n    data,\n    context\n  );\n  return attribute;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/category/categoryDataSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Category name must be a string\",\n        \"minLength\": \"Category name is required and cannot be empty\"\n      }\n    },\n    \"description\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Description block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Description block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Description block ID is required\",\n            \"size\": \"Description block size is required\",\n            \"columns\": \"Description block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Description must be an array\"\n      }\n    },\n    \"image\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Image path must be a string\"\n      }\n    },\n    \"meta_title\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Meta title must be a string\",\n        \"minLength\": \"Meta title is required and cannot be empty\"\n      }\n    },\n    \"meta_description\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Meta description must be a string\",\n        \"minLength\": \"Meta description is required and cannot be empty\"\n      }\n    },\n    \"url_key\": {\n      \"type\": \"string\",\n      \"pattern\": \"^[a-z0-9]+(?:-[a-z0-9]+)*$\",\n      \"minLength\": 1,\n      \"maxLength\": 255,\n      \"errorMessage\": {\n        \"type\": \"URL key must be a string\",\n        \"pattern\": \"URL key must contain only lowercase letters, numbers, and hyphens (e.g., 'my-category-name')\",\n        \"minLength\": \"URL key cannot be empty\",\n        \"maxLength\": \"URL key cannot exceed 255 characters\"\n      }\n    },\n    \"status\": {\n      \"type\": [\"integer\", \"string\"],\n      \"enum\": [0, 1, \"0\", \"1\"],\n      \"errorMessage\": {\n        \"type\": \"Status must be a number or string\",\n        \"enum\": \"Status must be either 0, 1, '0', or '1'\"\n      }\n    },\n    \"include_in_nav\": {\n      \"type\": [\"integer\", \"string\"],\n      \"enum\": [0, 1, \"0\", \"1\"],\n      \"errorMessage\": {\n        \"type\": \"Include in navigation must be a number or string\",\n        \"enum\": \"Include in navigation must be either 0, 1, '0', or '1'\"\n      }\n    },\n    \"parent_id\": {\n      \"type\": [\"string\", \"number\", \"null\"],\n      \"pattern\": \"^[0-9]+$\",\n      \"default\": null,\n      \"errorMessage\": {\n        \"type\": \"Parent ID must be a string, number, or null\",\n        \"pattern\": \"Parent ID must be a valid numeric ID\"\n      }\n    },\n    \"position\": {\n      \"type\": [\"string\", \"integer\"],\n      \"pattern\": \"^[0-9]*$\",\n      \"errorMessage\": {\n        \"type\": \"Position must be a string or number\",\n        \"pattern\": \"Position must be a valid number (e.g., 0, 1, 10)\"\n      }\n    }\n  },\n  \"required\": [\n    \"name\",\n    \"description\",\n    \"status\",\n    \"meta_title\",\n    \"meta_description\"\n  ],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"required\": {\n      \"name\": \"Category name is required\",\n      \"description\": \"Category description is required\",\n      \"status\": \"Category status is required\",\n      \"meta_title\": \"Meta title is required\",\n      \"meta_description\": \"Meta description is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/category/createCategory.ts",
    "content": "import {\n  commit,\n  insert,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport type { PoolClient } from '@evershop/postgres-query-builder';\nimport { JSONSchemaType } from 'ajv';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { sanitizeRawHtml } from '../../../../lib/util/sanitizeHtml.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport categoryDataSchema from './categoryDataSchema.json' with { type: 'json' };\nimport { Row } from '@components/common/form/Editor.js';\n\nexport type CategoryData = {\n  name: string;\n  url_key: string;\n  description?: Row[];\n  [key: string]: unknown;\n};\n\nfunction validateCategoryDataBeforeInsert(data: CategoryData) {\n  const ajv = getAjv();\n  (categoryDataSchema as JSONSchemaType<any>).required = ['name', 'url_key'];\n  const jsonSchema = getValueSync(\n    'createCategoryDataJsonSchema',\n    categoryDataSchema,\n    {}\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function insertCategoryData(data: CategoryData & { parent_id?: number }, connection: PoolClient) {\n  const parentId = data.parent_id;\n  if (parentId) {\n    // Load the parent category\n    const parentCategory = await select()\n      .from('category')\n      .where('category_id', '=', parentId)\n      .load(connection);\n\n    if (!parentCategory) {\n      throw new Error('Parent category not found');\n    }\n  }\n\n  const category = await insert('category').given(data).execute(connection);\n  const description = await insert('category_description')\n    .given(data)\n    .prime('category_description_category_id', category.insertId)\n    .execute(connection);\n\n  return {\n    ...description,\n    ...category\n  };\n}\n\n/**\n * Create category service. This service will create a category with all related data\n * @param {Object} data\n * @param {Object} context\n */\nasync function createCategory(data: CategoryData, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const categoryData = await getValue('categoryDataBeforeCreate', data);\n    // Validate category data\n    validateCategoryDataBeforeInsert(categoryData);\n\n    // Sanitize raw HTML blocks in EditorJS content\n    if (categoryData.description) {\n      sanitizeRawHtml(categoryData.description);\n    }\n    // Insert category data\n    const category = await hookable(insertCategoryData, context)(\n      categoryData,\n      connection\n    );\n\n    await commit(connection);\n    return category;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Create category service. This service will create a category with all related data\n * @param {Object} data\n * @param {Object} context\n */\nexport default async (data: CategoryData, context: Record<string, any>) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const category = await hookable(createCategory, context)(data, context);\n  return category;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/category/deleteCategory.ts",
    "content": "import {\n  commit,\n  del,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport type { PoolClient } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\n\nasync function deleteCategoryData(uuid: string, connection: PoolClient) {\n  await del('category').where('uuid', '=', uuid).execute(connection);\n}\n/**\n * Delete category service. This service will delete a category with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nasync function deleteCategory(uuid: string, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const query = select().from('category');\n    query\n      .leftJoin('category_description')\n      .on(\n        'category_description.category_description_category_id',\n        '=',\n        'category.category_id'\n      );\n\n    const category = await query.where('uuid', '=', uuid).load(connection);\n\n    if (!category) {\n      throw new Error('Invalid category id');\n    }\n    await hookable(deleteCategoryData, { ...context, connection, category })(\n      uuid,\n      connection\n    );\n\n    await commit(connection);\n    return category;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Delete category service. This service will delete a category with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nexport default async (uuid: string, context: Record<string, any>) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const category = await hookable(deleteCategory, context)(uuid, context);\n  return category;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/category/updateCategory.ts",
    "content": "import {\n  commit,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport type { PoolClient } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { sanitizeRawHtml } from '../../../../lib/util/sanitizeHtml.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport categoryDataSchema from './categoryDataSchema.json' with { type: 'json' };\nimport { CategoryData } from './createCategory.js';\n\n\nfunction validateCategoryDataBeforeInsert(data: CategoryData) {\n  const ajv = getAjv();\n  categoryDataSchema.required = [];\n  const jsonSchema = getValueSync(\n    'updateCategoryDataJsonSchema',\n    categoryDataSchema,\n    {}\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function updateCategoryData(uuid: string, data: CategoryData, connection: PoolClient) {\n  const query = select().from('category');\n  query\n    .leftJoin('category_description')\n    .on(\n      'category_description.category_description_category_id',\n      '=',\n      'category.category_id'\n    );\n  const category = await query.where('uuid', '=', uuid).load(connection);\n  if (!category) {\n    throw new Error('Requested category not found');\n  }\n\n  try {\n    const newCategory = await update('category')\n      .given(data)\n      .where('uuid', '=', uuid)\n      .execute(connection);\n    Object.assign(category, newCategory);\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    }\n  }\n  try {\n    const description = await update('category_description')\n      .given(data)\n      .where('category_description_category_id', '=', category.category_id)\n      .execute(connection);\n    Object.assign(category, description);\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    }\n  }\n\n  return category;\n}\n\n/**\n * Update category service. This service will update a category with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nasync function updateCategory(uuid: string, data: CategoryData, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const categoryData = await getValue('categoryDataBeforeUpdate', data);\n    // Validate category data\n    validateCategoryDataBeforeInsert(categoryData);\n\n    if (categoryData.description) {\n      sanitizeRawHtml(categoryData.description);\n    }\n    // Insert category data\n    const category = await hookable(updateCategoryData, {\n      ...context,\n      connection\n    })(uuid, categoryData, connection);\n\n    await commit(connection);\n    return category;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Update category service. This service will update a category with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nexport default async (uuid: string, data: CategoryData, context: Record<string, any>) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const category = await hookable(updateCategory, context)(uuid, data, context);\n  return category;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/collection/createCollection.ts",
    "content": "import {\n  startTransaction,\n  commit,\n  rollback,\n  insert\n} from '@evershop/postgres-query-builder';\nimport type { PoolClient } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport { getValue } from '../../../../lib/util/registry.js';\nimport { Row, sanitizeRawHtml } from '../../../../lib/util/sanitizeHtml.js';\n\nexport type CollectionData = {\n  name: string;\n  code: string;\n  description?: Row[];\n  [key: string]: unknown;\n};\n\nasync function insertCollectionData(\n  data: CollectionData,\n  connection: PoolClient\n) {\n  const collection = await insert('collection').given(data).execute(connection);\n  return collection;\n}\n\n/**\n * Create collection service. This service will create a collection with all related data\n * @param {Object} data\n * @param {Object} context\n */\nasync function createCollection(\n  data: CollectionData,\n  context: Record<string, any>\n) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const hookContext = { connection, ...context };\n  try {\n    const collectionData = await getValue<CollectionData>(\n      'collectionDataBeforeCreate',\n      data,\n      {}\n    );\n    // Sanitize raw HTML blocks in EditorJS content\n    if (collectionData.description) {\n      sanitizeRawHtml(collectionData.description);\n    }\n\n    // Insert collection data\n    const collection = await hookable(insertCollectionData, hookContext)(\n      collectionData,\n      connection\n    );\n\n    await commit(connection);\n    return collection;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Create collection service. This service will create a collection with all related data\n * @param {Object} data\n * @param {Object} context\n */\nexport default async (data: CollectionData, context: Record<string, any>) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const collection = await hookable(createCollection)(data, context);\n  return collection;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/collection/deleteCollection.ts",
    "content": "import {\n  startTransaction,\n  commit,\n  rollback,\n  select,\n  del\n} from '@evershop/postgres-query-builder';\nimport type { PoolClient } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\n\nasync function deleteCollectionData(uuid: string, connection: PoolClient) {\n  await del('collection').where('uuid', '=', uuid).execute(connection);\n}\n/**\n * Delete collection service. This service will delete a collection with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nasync function deleteCollection(uuid: string, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const query = select().from('collection');\n    const collection = await query.where('uuid', '=', uuid).load(connection);\n\n    if (!collection) {\n      throw new Error('Invalid collection id');\n    }\n    await hookable(deleteCollectionData, {\n      ...context,\n      connection,\n      collection\n    })(uuid, connection);\n    await commit(connection);\n    return collection;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Delete collection service. This service will delete a collection with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nexport default async (uuid: string, context: Record<string, any>) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const collection = await hookable(deleteCollection, context)(uuid, context);\n  return collection;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/collection/updateCollection.ts",
    "content": "import {\n  commit,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport type { PoolClient } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport { getValue } from '../../../../lib/util/registry.js';\nimport { sanitizeRawHtml } from '../../../../lib/util/sanitizeHtml.js';\nimport { CollectionData } from './createCollection.js';\n\nasync function updateCollectionData(\n  uuid: string,\n  data: CollectionData,\n  connection: PoolClient\n) {\n  const collection = await select()\n    .from('collection')\n    .where('uuid', '=', uuid)\n    .load(connection);\n\n  if (!collection) {\n    throw new Error('Requested collection not found');\n  }\n\n  try {\n    const newCollection = await update('collection')\n      .given(data)\n      .where('uuid', '=', uuid)\n      .execute(connection);\n\n    return newCollection;\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    } else {\n      return collection;\n    }\n  }\n}\n\n/**\n * Update collection service. This service will update a collection with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nasync function updateCollection(\n  uuid: string,\n  data: CollectionData,\n  context: Record<string, any>\n) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const hookContext = { connection, ...context };\n  try {\n    const collectionData = await getValue('collectionDataBeforeUpdate', data);\n    // Sanitize raw HTML blocks in EditorJS content\n    if (collectionData.description) {\n      sanitizeRawHtml(collectionData.description);\n    }\n\n    // Update collection data\n    const collection = await hookable(updateCollectionData, hookContext)(\n      uuid,\n      collectionData,\n      connection\n    );\n    await commit(connection);\n    return collection;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Update collection service. This service will update a collection with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nexport default async (\n  uuid: string,\n  data: CollectionData,\n  context: Record<string, any>\n) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  // Merge hook context with context\n  const collection = await hookable(updateCollection, context)(\n    uuid,\n    data,\n    context\n  );\n  return collection;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/getAttributeGroupsBaseQuery.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\n\nexport const getAttributeGroupsBaseQuery = () =>\n  select().from('attribute_group');\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/getAttributesBaseQuery.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\n\nexport const getAttributesBaseQuery = () => select().from('attribute');\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/getCategoriesBaseQuery.ts",
    "content": "import { select, SelectQuery } from '@evershop/postgres-query-builder';\n\nexport const getCategoriesBaseQuery = (): SelectQuery => {\n  const query = select().from('category');\n  query\n    .leftJoin('category_description')\n    .on(\n      'category_description.category_description_category_id',\n      '=',\n      'category.category_id'\n    );\n\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/getCollectionsBaseQuery.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport type { SelectQuery } from '@evershop/postgres-query-builder';\n\nexport const getCollectionsBaseQuery = (): SelectQuery => {\n  const query = select().from('collection');\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/getFilterableAttributes.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getProductsByCategoryBaseQuery } from '../../../modules/catalog/services/getProductsByCategoryBaseQuery.js';\n\nexport const getFilterableAttributes = async (categoryId) => {\n  const productsQuery = await getProductsByCategoryBaseQuery(categoryId, true);\n  productsQuery.select('product.product_id');\n  // Get the list of productIds before applying pagination, sorting...etc\n  // Base on this list, we will find all attribute,\n  // category and price can be appeared in the filter table\n  const allIds = (await productsQuery.execute(pool)).map(\n    (row) => row.product_id\n  );\n\n  // Filterable attributes\n  const query = select('attribute.attribute_name', 'attribute_name')\n    .select('attribute.type', 'type')\n    .select('attribute.is_filterable', 'is_filterable')\n    .select('product_attribute_value_index.attribute_id', 'attribute_id')\n    .select('attribute.attribute_code', 'attribute_code')\n    .select('product_attribute_value_index.option_id', 'option_id')\n    .select('product_attribute_value_index.option_text', 'option_text')\n    .from('attribute');\n  query\n    .innerJoin('product_attribute_value_index')\n    .on(\n      'attribute.attribute_id',\n      '=',\n      'product_attribute_value_index.attribute_id'\n    );\n\n  query\n    .where('product_attribute_value_index.product_id', 'IN', allIds)\n    .and('type', '=', 'select')\n    .and('is_filterable', '=', 1);\n\n  const attributeData = await query.execute(pool);\n\n  const attributes = [];\n\n  for (let i = 0; i < attributeData.length; i++) {\n    const index = attributes.findIndex(\n      (a) => a.attributeCode === attributeData[i].attribute_code\n    );\n    if (index === -1) {\n      attributes.push({\n        attributeName: attributeData[i].attribute_name,\n        attributeId: attributeData[i].attribute_id,\n        attributeCode: attributeData[i].attribute_code,\n        options: [\n          {\n            optionId: attributeData[i].option_id,\n            optionText: attributeData[i].option_text\n          }\n        ]\n      });\n    } else {\n      const idx = attributes[index].options.findIndex(\n        (o) =>\n          parseInt(o.optionId, 10) === parseInt(attributeData[i].option_id, 10)\n      );\n      if (idx === -1) {\n        attributes[index].options = attributes[index].options.concat({\n          optionId: attributeData[i].option_id,\n          optionText: attributeData[i].option_text\n        });\n      } else {\n        attributes[index].options[idx].productCount++;\n      }\n    }\n  }\n\n  return attributes;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/getProductsBaseQuery.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport type { SelectQuery } from '@evershop/postgres-query-builder';\n\nexport const getProductsBaseQuery = (): SelectQuery => {\n  const query = select().from('product');\n  query\n    .leftJoin('product_description')\n    .on(\n      'product_description.product_description_product_id',\n      '=',\n      'product.product_id'\n    );\n  query\n    .innerJoin('product_inventory')\n    .on(\n      'product_inventory.product_inventory_product_id',\n      '=',\n      'product.product_id'\n    );\n\n  query\n    .leftJoin('product_image')\n    .on('product_image.product_image_product_id', '=', 'product.product_id')\n    .and('product_image.is_main', '=', true);\n\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/getProductsByCategoryBaseQuery.ts",
    "content": "import { execute, SelectQuery } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getProductsBaseQuery } from '../../../modules/catalog/services/getProductsBaseQuery.js';\n\nexport const getProductsByCategoryBaseQuery = async (\n  categoryId: number,\n  fromSubCategories = false\n): Promise<SelectQuery> => {\n  const query = getProductsBaseQuery();\n\n  if (!fromSubCategories) {\n    query.where('product.category_id', '=', categoryId);\n  } else {\n    // Get all the sub categories recursively\n    const subCategoriesQuery = await execute(\n      pool,\n      `WITH RECURSIVE sub_categories AS (\n        SELECT * FROM category WHERE category_id = ${categoryId}\n        UNION\n        SELECT c.* FROM category c\n        INNER JOIN sub_categories sc ON c.parent_id = sc.category_id\n      ) SELECT * FROM sub_categories`\n    );\n    const subCategories = subCategoriesQuery.rows;\n    const categoryIds = subCategories.map((category) => category.category_id);\n    query.where('product.category_id', 'IN', categoryIds);\n  }\n\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/getProductsByCollectionBaseQuery.ts",
    "content": "import { SelectQuery } from '@evershop/postgres-query-builder';\nimport { getProductsBaseQuery } from './getProductsBaseQuery.js';\n\nexport const getProductsByCollectionBaseQuery = (\n  collectionId: number\n): SelectQuery => {\n  const query = getProductsBaseQuery();\n  query\n    .leftJoin('product_collection')\n    .on('product_collection.product_id', '=', 'product.product_id')\n    .and('product_collection.collection_id', '=', collectionId);\n\n  query.andWhere('product_collection.collection_id', '=', collectionId);\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/index.ts",
    "content": "import createAttribute from './attribute/createProductAttribute.js';\nimport deleteAttribute from './attribute/deleteProductAttribute.js';\nimport updateAttribute from './attribute/updateProductAttribute.js';\nimport createCategory from './category/createCategory.js';\nimport deleteCategory from './category/deleteCategory.js';\nimport updateCategory from './category/updateCategory.js';\nimport createCollection from './collection/createCollection.js';\nimport deleteCollection from './collection/deleteCollection.js';\nimport updateCollection from './collection/updateCollection.js';\nimport { getCategoriesBaseQuery } from './getCategoriesBaseQuery.js';\nimport { getCollectionsBaseQuery } from './getCollectionsBaseQuery.js';\nimport { getProductsBaseQuery } from './getProductsBaseQuery.js';\nimport { getProductsByCategoryBaseQuery } from './getProductsByCategoryBaseQuery.js';\nimport { getProductsByCollectionBaseQuery } from './getProductsByCollectionBaseQuery.js';\nimport createProduct from './product/createProduct.js';\nimport deleteProduct from './product/deleteProduct.js';\nimport updateProduct from './product/updateProduct.js';\n\nexport {\n  createProduct,\n  updateProduct,\n  deleteProduct,\n  createCollection,\n  updateCollection,\n  deleteCollection,\n  createCategory,\n  updateCategory,\n  deleteCategory,\n  createAttribute,\n  updateAttribute,\n  deleteAttribute,\n  getCategoriesBaseQuery,\n  getCollectionsBaseQuery,\n  getProductsBaseQuery,\n  getProductsByCategoryBaseQuery,\n  getProductsByCollectionBaseQuery\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/product/createProduct.ts",
    "content": "import {\n  commit,\n  insert,\n  insertOnUpdate,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport type { PoolClient } from '@evershop/postgres-query-builder';\nimport { JSONSchemaType } from 'ajv';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { getBaseUrl } from '../../../../lib/util/getBaseUrl.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { sanitizeRawHtml } from '../../../../lib/util/sanitizeHtml.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport productDataSchema from './productDataSchema.json'  with { type: 'json' };\nimport { Row } from '@components/common/form/Editor.js';\n\nexport type ProductData = ProductInventoryData & {\n  name: string,\n  url_key?: string,\n  status: string,\n  sku: string,\n  price: number,\n  group_id: number,\n  visibility?: string,\n  attributes?: ProductAttributeData[],\n  images?: string[],\n  description?: Row[],\n  [key: string]: unknown;\n};\n\nexport type ProductInventoryData = {\n  qty: number,\n  manage_stock: boolean,\n  stock_availability: boolean,\n  [key: string]: unknown\n}\n\nexport type ProductAttributeData = {\n  attribute_code: string,\n  value: string,\n  [key: string]: unknown\n}\n\nfunction validateProductDataBeforeInsert(data: ProductData) {\n  const ajv = getAjv();\n  (productDataSchema as JSONSchemaType<any>).required = [\n    'name',\n    'url_key',\n    'status',\n    'sku',\n    'qty',\n    'price',\n    'group_id',\n    'visibility'\n  ];\n  const jsonSchema = getValueSync(\n    'createProductDataJsonSchema',\n    productDataSchema,\n    {}\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function insertProductInventory(inventoryData: ProductInventoryData, productId: number, connection: PoolClient) {\n  // Save the product inventory\n  await insert('product_inventory')\n    .given(inventoryData)\n    .prime('product_inventory_product_id', productId)\n    .execute(connection);\n}\n\nasync function insertProductAttributes(attributes: ProductAttributeData[], productId: number, connection: PoolClient) {\n  // Looping attributes array\n  for (let i = 0; i < attributes.length; i += 1) {\n    const attribute = attributes[i];\n    if (attribute.value) {\n      const attr = await select()\n        .from('attribute')\n        .where('attribute_code', '=', attribute.attribute_code)\n        .load(connection);\n\n      if (!attr) {\n        return;\n      }\n\n      if (attr.type === 'textarea' || attr.type === 'text') {\n        // Throw error if attribute value is not a string\n        if (typeof attribute.value !== 'string') {\n          throw new Error(`Attribute value must be a string for attribute ${attribute.attribute_code}`);\n        }\n        const flag = await select('attribute_id')\n          .from('product_attribute_value_index')\n          .where('product_id', '=', productId)\n          .and('attribute_id', '=', attr.attribute_id)\n          .load(connection);\n\n        if (flag) {\n          await update('product_attribute_value_index')\n            .given({ option_text: attribute.value.trim() })\n            .where('product_id', '=', productId)\n            .and('attribute_id', '=', attr.attribute_id)\n            .execute(connection);\n        } else {\n          await insert('product_attribute_value_index')\n            .prime('product_id', productId)\n            .prime('attribute_id', attr.attribute_id)\n            .prime('option_text', attribute.value.trim())\n            .execute(connection);\n        }\n      } else if (attr.type === 'multiselect') {\n        // Throw error if attribute value is not an array\n        if (!Array.isArray(attribute.value)) {\n          throw new Error(`Attribute value must be an array for attribute ${attribute.attribute_code}`);\n        }\n        await Promise.all(\n          attribute.value.map((optionId) =>\n            (async () => {\n              const option = await select()\n                .from('attribute_option')\n                .where(\n                  'attribute_option_id',\n                  '=',\n                  parseInt(optionId, 10)\n                )\n                .load(connection);\n\n              if (option === null) {\n                return;\n              }\n              await insertOnUpdate('product_attribute_value_index', [\n                'product_id',\n                'attribute_id',\n                'option_id'\n              ])\n                .prime('option_id', option.attribute_option_id)\n                .prime('product_id', productId)\n                .prime('attribute_id', attr.attribute_id)\n                .prime('option_text', option.option_text)\n                .execute(connection);\n            })()\n          )\n        );\n      } else if (attr.type === 'select') {\n        const option = await select()\n          .from('attribute_option')\n          .where('attribute_option_id', '=', parseInt(attribute.value, 10))\n          .load(connection);\n         \n        if (option === false) {\n          continue;\n        }\n        // Insert new option\n        await insertOnUpdate('product_attribute_value_index', [\n          'product_id',\n          'attribute_id',\n          'option_id'\n        ])\n          .prime('option_id', option.attribute_option_id)\n          .prime('product_id', productId)\n          .prime('attribute_id', attr.attribute_id)\n          .prime('option_text', option.option_text)\n          .execute(connection);\n      } else {\n        await insertOnUpdate('product_attribute_value_index', [\n          'product_id',\n          'attribute_id',\n          'option_id'\n        ])\n          .prime('option_text', attribute.value)\n          .execute(connection);\n      }\n    }\n  }\n}\n\nasync function insertProductImages(images: string[], productId: number, connection: PoolClient) {\n  const baseUrl = getBaseUrl()\n  await Promise.all(\n    images.map((f, index) =>\n      (async () => {\n        // Remove baseUrl from the image path if it exists\n        let imagePath = f;\n        if (imagePath.startsWith(baseUrl)) {\n          imagePath = imagePath.substring(baseUrl.length);\n        }\n        \n        await insert('product_image')\n          .given({ origin_image: imagePath, is_main: index === 0 })\n          .prime('product_image_product_id', productId)\n          .execute(connection);\n      })()\n    )\n  );\n}\n\n\nasync function insertProductData(data: ProductData, connection: PoolClient) {\n  // If no_shipping_required is true, set weight to 0\n  const productData = { ...data, weight: data.no_shipping_required ? 0 : data.weight };\n  const product = await insert('product').given(productData).execute(connection);\n  const description = await insert('product_description')\n    .given(productData)\n    .prime('product_description_product_id', product.product_id)\n    .execute(connection);\n\n  return {\n    ...description,\n    ...product\n  };\n}\n\n/**\n * Create product service. This service will create a product with all related data\n * @param {Object} data\n * @param {Object} context\n */\nasync function createProduct(data: ProductData, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const productData = await getValue('productDataBeforeCreate', data, {});\n\n    // Validate product data\n    validateProductDataBeforeInsert(productData);\n\n    // Sanitize the description\n    if (productData.description) {\n      sanitizeRawHtml(productData.description);\n    }\n    // Insert product data\n    const product = await hookable(insertProductData, {\n      connection,\n      ...context\n    })(productData, connection);\n\n    // Insert product inventory\n    await hookable(insertProductInventory, { ...context, connection, product })(\n      productData,\n      product.insertId,\n      connection\n    );\n    // Insert product attributes\n    await hookable(insertProductAttributes, {\n      ...context,\n      connection,\n      product\n    })(productData.attributes || [], product.insertId, connection);\n\n    // Insert product images\n    await hookable(insertProductImages, { ...context, connection, product })(\n      productData.images || [],\n      product.insertId,\n      connection\n    );\n\n    await commit(connection);\n    return product;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Create product service. This service will create a product with all related data\n * @param {Object} data\n * @param {Object} context\n */\nexport default async (data: ProductData, context: Record<string, any>) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const product = await hookable(createProduct, context)(data, context);\n  return product;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/product/deleteProduct.ts",
    "content": "import {\n  startTransaction,\n  commit,\n  rollback,\n  select,\n  del\n} from '@evershop/postgres-query-builder';\nimport type { PoolClient } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport { ProductData } from './createProduct.js';\n\nasync function deleteProductData(uuid: string, connection: PoolClient) {\n  await del('product').where('uuid', '=', uuid).execute(connection);\n}\n\n/**\n * Delete product service. This service will delete a product with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nasync function deleteProduct(uuid: string, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const query = select().from('product');\n    query\n      .leftJoin('product_description')\n      .on(\n        'product_description.product_description_product_id',\n        '=',\n        'product.product_id'\n      );\n\n    const product = await query.where('uuid', '=', uuid).load(connection);\n    if (!product) {\n      throw new Error('Invalid product id');\n    }\n    await hookable(deleteProductData, { ...context, connection, product })(\n      uuid,\n      connection\n    );\n    await commit(connection);\n    return product;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Delete product service. This service will delete a product with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nexport default async (\n  uuid: string,\n  context: Record<string, any>\n): Promise<ProductData> => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const hookContext = {\n      connection\n    };\n    // Make sure the context is either not provided or is an object\n    if (context && typeof context !== 'object') {\n      throw new Error('Context must be an object');\n    }\n    // Merge hook context with context\n    Object.assign(hookContext, context);\n    const product = await hookable(deleteProduct, hookContext)(\n      uuid,\n      connection\n    );\n    await commit(connection);\n    return product;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/product/productDataSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Product name must be a string\",\n        \"minLength\": \"Product name is required and cannot be empty\"\n      }\n    },\n    \"description\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Description block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Description block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Description block ID is required\",\n            \"size\": \"Description block size is required\",\n            \"columns\": \"Description block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Description must be an array\"\n      }\n    },\n    \"short_description\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Short description must be a string\"\n      }\n    },\n    \"url_key\": {\n      \"type\": \"string\",\n      \"pattern\": \"^[a-z0-9]+(?:-[a-z0-9]+)*$\",\n      \"minLength\": 1,\n      \"maxLength\": 255,\n      \"errorMessage\": {\n        \"type\": \"URL key must be a string\",\n        \"pattern\": \"URL key must contain only lowercase letters, numbers, and hyphens (e.g., 'my-product-name')\",\n        \"minLength\": \"URL key cannot be empty\",\n        \"maxLength\": \"URL key cannot exceed 255 characters\"\n      }\n    },\n    \"meta_title\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Meta title must be a string\"\n      }\n    },\n    \"meta_description\": {\n      \"type\": \"string\",\n      \"skipEscape\": true,\n      \"errorMessage\": {\n        \"type\": \"Meta description must be a string\"\n      }\n    },\n    \"status\": {\n      \"type\": [\"integer\", \"string\", \"boolean\"],\n      \"enum\": [0, 1, \"0\", \"1\", true, false],\n      \"errorMessage\": {\n        \"type\": \"Status must be a boolean, number, or string\",\n        \"enum\": \"Status must be either 0, 1, '0', '1', true, or false\"\n      }\n    },\n    \"sku\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"SKU must be a string\",\n        \"minLength\": \"SKU is required and cannot be empty\"\n      }\n    },\n    \"price\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n      \"errorMessage\": {\n        \"type\": \"Price must be a string or number\",\n        \"pattern\": \"Price must be a valid number with maximum 2 decimal places (e.g., 10.99)\"\n      }\n    },\n    \"weight\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^[0-9]+(\\\\.[0-9]{1,2})?$\",\n      \"errorMessage\": {\n        \"type\": \"Weight must be a string or number\",\n        \"pattern\": \"Weight must be a valid number with maximum 2 decimal places (e.g., 1.50)\"\n      }\n    },\n    \"qty\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^[0-9]+$\",\n      \"errorMessage\": {\n        \"type\": \"Quantity must be a string or number\",\n        \"pattern\": \"Quantity must be a whole number (e.g., 10, 100)\"\n      }\n    },\n    \"tax_class\": {\n      \"type\": [\"string\", \"number\", \"null\"],\n      \"pattern\": \"^[0-9]+$\",\n      \"default\": null,\n      \"errorMessage\": {\n        \"type\": \"Tax class must be a string, number, or null\",\n        \"pattern\": \"Tax class must be a valid numeric ID\"\n      }\n    },\n    \"manage_stock\": {\n      \"type\": [\"integer\", \"string\", \"boolean\"],\n      \"enum\": [0, 1, \"0\", \"1\", true, false],\n      \"errorMessage\": {\n        \"type\": \"Manage stock must be a boolean, number, or string\",\n        \"enum\": \"Manage stock must be either 0, 1, '0', '1', true, or false\"\n      }\n    },\n    \"stock_availability\": {\n      \"type\": [\"integer\", \"string\", \"boolean\"],\n      \"enum\": [0, 1, \"0\", \"1\", true, false],\n      \"errorMessage\": {\n        \"type\": \"Stock availability must be a boolean, number, or string\",\n        \"enum\": \"Stock availability must be either 0, 1, '0', '1', true, or false\"\n      }\n    },\n    \"group_id\": {\n      \"type\": [\"string\", \"integer\"],\n      \"pattern\": \"^[0-9]+$\",\n      \"errorMessage\": {\n        \"type\": \"Group ID must be a string or number\",\n        \"pattern\": \"Group ID must be a valid numeric ID\"\n      }\n    },\n    \"visibility\": {\n      \"type\": [\"integer\", \"string\", \"boolean\"],\n      \"enum\": [0, 1, \"0\", \"1\", true, false],\n      \"errorMessage\": {\n        \"type\": \"Visibility must be a boolean, number, or string\",\n        \"enum\": \"Visibility must be either 0, 1, '0', '1', true, or false\"\n      }\n    },\n    \"images\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\",\n        \"errorMessage\": {\n          \"type\": \"Image path must be a string\"\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Images must be an array\"\n      }\n    },\n    \"attributes\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"attribute_code\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Attribute code must be a string\"\n            }\n          },\n          \"value\": {\n            \"type\": [\"string\", \"array\"],\n            \"items\": {\n              \"type\": \"string\",\n              \"errorMessage\": {\n                \"type\": \"Attribute value item must be a string\"\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Attribute value must be a string or array\"\n            }\n          }\n        },\n        \"errorMessage\": {\n          \"type\": \"Attribute must be an object\"\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Attributes must be an array\"\n      }\n    },\n    \"category_id\": {\n      \"type\": [\"string\", \"number\", \"null\"],\n      \"pattern\": \"^[0-9]+$\",\n      \"default\": null,\n      \"errorMessage\": {\n        \"type\": \"Category ID must be a string, number, or null\",\n        \"pattern\": \"Category ID must be a valid numeric ID\"\n      }\n    },\n    \"options\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"option_name\": {\n            \"type\": \"string\",\n            \"minLength\": 1,\n            \"errorMessage\": {\n              \"type\": \"Option name must be a string\",\n              \"minLength\": \"Option name is required and cannot be empty\"\n            }\n          },\n          \"option_type\": {\n            \"type\": \"string\",\n            \"enum\": [\"select\", \"multiselect\"],\n            \"errorMessage\": {\n              \"type\": \"Option type must be a string\",\n              \"enum\": \"Option type must be either 'select' or 'multiselect'\"\n            }\n          },\n          \"is_required\": {\n            \"type\": [\"string\", \"integer\"],\n            \"enum\": [0, 1, \"0\", \"1\"],\n            \"default\": 0,\n            \"errorMessage\": {\n              \"type\": \"Is required must be a string or number\",\n              \"enum\": \"Is required must be either 0, 1, '0', or '1'\"\n            }\n          },\n          \"values\": {\n            \"type\": \"array\",\n            \"minItems\": 1,\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"value\": {\n                  \"type\": \"string\",\n                  \"minLength\": 1,\n                  \"errorMessage\": {\n                    \"type\": \"Option value must be a string\",\n                    \"minLength\": \"Option value is required and cannot be empty\"\n                  }\n                },\n                \"extra_price\": {\n                  \"type\": [\"string\", \"number\"],\n                  \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n                  \"errorMessage\": {\n                    \"type\": \"Extra price must be a string or number\",\n                    \"pattern\": \"Extra price must be a valid number with maximum 2 decimal places (e.g., 5.99)\"\n                  }\n                }\n              },\n              \"errorMessage\": {\n                \"type\": \"Option value item must be an object\"\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Option values must be an array\",\n              \"minItems\": \"At least one option value is required\"\n            }\n          }\n        },\n        \"required\": [\"option_name\", \"option_type\", \"values\"],\n        \"additionalProperties\": true,\n        \"errorMessage\": {\n          \"type\": \"Option must be an object\",\n          \"required\": {\n            \"option_name\": \"Option name is required\",\n            \"option_type\": \"Option type is required\",\n            \"values\": \"Option values are required\"\n          }\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Options must be an array\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/product/updateProduct.ts",
    "content": "import {\n  commit,\n  del,\n  insert,\n  insertOnUpdate,\n  PoolClient,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { JSONSchemaType } from 'ajv';\nimport { error } from '../../../../lib/log/logger.js';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { getBaseUrl } from '../../../../lib/util/getBaseUrl.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { sanitizeRawHtml } from '../../../../lib/util/sanitizeHtml.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport type { ProductAttributeData, ProductData, ProductInventoryData } from './createProduct.js';\nimport productDataSchema from './productDataSchema.json' with { type: 'json' };\n\nfunction validateProductDataBeforeUpdate(data: ProductData) {\n  const ajv = getAjv();\n  (productDataSchema as JSONSchemaType<any>).required = [];\n  const jsonSchema = getValueSync(\n    'updateProductDataJsonSchema',\n    productDataSchema,\n    {}\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function updateProductInventory(inventoryData: ProductInventoryData, productId: number, connection: PoolClient) {\n  // Save the product inventory\n  try {\n    // Update product inventory\n    await update('product_inventory')\n      .given(inventoryData)\n      .where('product_inventory_product_id', '=', productId)\n      .execute(connection);\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    }\n  }\n}\n\n/**\n * @param {number} productId\n * @param {[{attribute_code: string, value}]} attributes\n * @param {*} connection\n * @returns\n */\nasync function saveProductAttributes(productId: number, attributes: ProductAttributeData[], connection: PoolClient) {\n  for (let i = 0; i < attributes.length; i += 1) {\n    const attribute = attributes[i];\n    if (attribute.value) {\n      const attr = await select()\n        .from('attribute')\n        .where('attribute_code', '=', attribute.attribute_code)\n        .load(connection);\n\n      if (!attr) {\n        return;\n      }\n\n      if (attr.type === 'textarea' || attr.type === 'text') {\n        // Thrown error if value is not a string\n        if (typeof attribute.value !== 'string') {\n          throw new Error(`Attribute value must be a string for attribute ${attribute.attribute_code}`);\n        }\n        const flag = await select('attribute_id')\n          .from('product_attribute_value_index')\n          .where('product_id', '=', productId)\n          .and('attribute_id', '=', attr.attribute_id)\n          .load(connection);\n\n        if (flag) {\n          await update('product_attribute_value_index')\n            .given({ option_text: attribute.value.trim() })\n            .where('product_id', '=', productId)\n            .and('attribute_id', '=', attr.attribute_id)\n            .execute(connection);\n        } else {\n          await insert('product_attribute_value_index')\n            .prime('product_id', productId)\n            .prime('attribute_id', attr.attribute_id)\n            .prime('option_text', attribute.value.trim())\n            .execute(connection);\n        }\n      } else if (attr.type === 'multiselect') {\n        // Thrown error if value is not an array\n        if (!Array.isArray(attribute.value)) {\n          throw new Error(`Attribute value must be an array for attribute ${attribute.attribute_code}`);\n        }\n        await Promise.all(\n          attribute.value.map((optionId) =>\n            (async () => {\n              const option = await select()\n                .from('attribute_option')\n                .where(\n                  'attribute_option_id',\n                  '=',\n                  parseInt(optionId, 10)\n                )\n                .load(connection);\n              if (option === null) {\n                return;\n              }\n              await insertOnUpdate('product_attribute_value_index', [\n                'product_id',\n                'attribute_id',\n                'option_id'\n              ])\n                .prime('option_id', option.attribute_option_id)\n                .prime('product_id', productId)\n                .prime('attribute_id', attr.attribute_id)\n                .prime('option_text', option.option_text)\n                .execute(connection);\n            })()\n          )\n        );\n        // Delete old options that are not in the new value\n        await del('product_attribute_value_index')\n          .where('attribute_id', '=', attr.attribute_id)\n          .and('product_id', '=', productId)\n          .and('option_id', 'NOT IN', attribute.value.map((v) => parseInt(v, 10)))\n          .execute(connection);\n      } else if (attr.type === 'select') {\n        const option = await select()\n          .from('attribute_option')\n          .where('attribute_option_id', '=', parseInt(attribute.value, 10))\n          .load(connection);\n        if (option === false) {\n           \n          continue;\n        }\n        // Delete old option if any\n        await del('product_attribute_value_index')\n          .where('attribute_id', '=', attr.attribute_id)\n          .and('product_id', '=', productId)\n          .execute(connection);\n        // Insert new option\n        await insertOnUpdate('product_attribute_value_index', [\n          'product_id',\n          'attribute_id',\n          'option_id'\n        ])\n          .prime('option_id', option.attribute_option_id)\n          .prime('product_id', productId)\n          .prime('attribute_id', attr.attribute_id)\n          .prime('option_text', option.option_text)\n          .execute(connection);\n      } else {\n        await insertOnUpdate('product_attribute_value_index', [\n          'product_id',\n          'attribute_id',\n          'option_id'\n        ])\n          .prime('option_text', attribute.value)\n          .execute(connection);\n      }\n    }\n  }\n}\n\nasync function updateProductAttributes(\n  attributes,\n  productId,\n  variantGroupId,\n  connection\n) {\n  if (!variantGroupId) {\n    await saveProductAttributes(productId, attributes, connection);\n  } else {\n    const promises = [saveProductAttributes(productId, attributes, connection)];\n    const variantGroup = await select(\n      'attribute_one',\n      'attribute_two',\n      'attribute_three',\n      'attribute_four',\n      'attribute_five'\n    )\n      .from('variant_group')\n      .where('variant_group_id', '=', variantGroupId)\n      .load(connection);\n\n    // Get all the variant attributes\n    const variantAttributes = await select()\n      .from('attribute')\n      .where(\n        'attribute_id',\n        'IN',\n        Object.values(variantGroup).filter((v) => v !== null)\n      )\n      .execute(connection);\n\n    // Remove the attributes that are variant attributes\n    const filteredAttributes = attributes.filter((attr) =>\n      variantAttributes.every((v) => v.attribute_code !== attr.attribute_code)\n    );\n\n    const variants = await select()\n      .from('product')\n      .where('variant_group_id', '=', variantGroupId)\n      .and('product_id', '!=', productId)\n      .execute(connection);\n\n    for (let i = 0; i < variants.length; i += 1) {\n      promises.push(\n        saveProductAttributes(\n          variants[i].product_id,\n          filteredAttributes,\n          connection\n        )\n      );\n    }\n    await Promise.all(promises);\n  }\n}\n\nasync function updateProductImages(images, productId, connection) {\n  if (Array.isArray(images) && images.length === 0) {\n    // Delete all images\n    await del('product_image')\n      .where('product_image_product_id', '=', productId)\n      .execute(connection);\n  }\n  if (Array.isArray(images) && images.length > 0) {\n    const baseUrl = getBaseUrl();\n    try {\n      // Delete all images that not in the gallery anymore\n      await del('product_image')\n        .where('product_image_product_id', '=', productId)\n        .and('origin_image', 'NOT IN', images)\n        .execute(connection);\n\n      await Promise.all(\n        images.map((f, index) =>\n          (async () => {\n        // Remove baseUrl from the image path if it exists\n        const imagePath = f.startsWith(baseUrl) ? f.substring(baseUrl.length) : f;\n        \n        const image = await select()\n          .from('product_image')\n          .where('product_image_product_id', '=', productId)\n          .and('origin_image', '=', imagePath)\n          .load(connection);\n\n        if (!image) {\n          await insert('product_image')\n            .given({\n          product_image_product_id: productId,\n          origin_image: imagePath,\n          is_main: index === 0\n            })\n            .execute(connection);\n        } else {\n          await update('product_image')\n            .given({ is_main: index === 0 })\n            .where('product_image_product_id', '=', productId)\n            .and('origin_image', '=', imagePath)\n            .execute(connection);\n        }\n          })()\n        )\n      );\n    } catch (e) {\n      error(e);\n      throw e;\n    }\n  }\n}\n\nasync function updateProductData(uuid: string, data: ProductData, connection: PoolClient) {\n  // If no_shipping_required is true, set weight to 0\n  const productData = { ...data, weight: data.no_shipping_required ? 0 : data.weight };\n  const query = select().from('product');\n  query\n    .leftJoin('product_description')\n    .on(\n      'product_description.product_description_product_id',\n      '=',\n      'product.product_id'\n    );\n  const product = await query.where('uuid', '=', uuid).load(connection);\n  if (!product) {\n    throw new Error('Requested product not found');\n  }\n\n  let newProduct;\n  try {\n    newProduct = await update('product')\n      .given(productData)\n      .where('uuid', '=', uuid)\n      .execute(connection);\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    }\n  }\n\n  try {\n    const description = await update('product_description')\n      .given(data)\n      .where('product_description_product_id', '=', product.product_id)\n      .execute(connection);\n    Object.assign(product, description);\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    }\n  }\n\n  // Update product category and tax class to all products in same variant group\n  if (product.variant_group_id) {\n    const sharedData: Record<string, any> = {};\n    if (newProduct.tax_class !== product.tax_class) {\n      sharedData.tax_class = newProduct.tax_class;\n    }\n    if (newProduct.category_id !== product.category_id) {\n      sharedData.category_id = newProduct.category_id;\n    }\n    if (newProduct.no_shipping_required !== product.no_shipping_required) {\n      sharedData.no_shipping_required = newProduct.no_shipping_required;\n      sharedData.weight = newProduct.weight;\n    }\n    const sharedVariantData = getValueSync<Record<string, any>>(\n      'sharedVariantProductDataOnUpdate',\n      sharedData,\n      {newProduct, product}\n    );\n    if (Object.keys(sharedVariantData).length > 0) {\n      await update('product')\n        .given(sharedVariantData)\n        .where('variant_group_id', '=', product.variant_group_id)\n        .and('product_id', '<>', product.product_id)\n        .execute(connection);\n    }\n  }\n  Object.assign(product, newProduct);\n  return product;\n}\n\n/**\n * Update product service. This service will update a product with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nasync function updateProduct(uuid: string, data: ProductData, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const currentProduct = await select()\n      .from('product')\n      .where('uuid', '=', uuid)\n      .load(connection);\n\n    if (!currentProduct) {\n      throw new Error('Requested product does not exist');\n    }\n\n    const productData = await getValue('productDataBeforeUpdate', data);\n\n    // Validate product data\n    validateProductDataBeforeUpdate(productData);\n\n    // Sanitize the description\n    if (productData.description) {\n      sanitizeRawHtml(productData.description);\n    }\n    // Insert product data\n    const product = await hookable(updateProductData, {\n      ...context,\n      connection\n    })(uuid, productData, connection);\n\n    // Update product inventory\n    await hookable(updateProductInventory, { ...context, connection, product })(\n      productData,\n      product.product_id,\n      connection\n    );\n\n    // Update product attributes\n    await hookable(updateProductAttributes, {\n      ...context,\n      connection,\n      product\n    })(\n      productData.attributes || [],\n      product.product_id,\n      product.variant_group_id,\n      connection\n    );\n\n    // Insert product images\n    await hookable(updateProductImages, { ...context, connection, product })(\n      productData.images,\n      product.product_id,\n      connection\n    );\n\n    await commit(connection);\n    return product;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Update product service. This service will update a product with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nexport default async (uuid: string, data: ProductData, context: Record<string, any>) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const product = await hookable(updateProduct, context)(uuid, data, context);\n  return product;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/registerCartItemProductUrlField.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../lib/router/buildUrl.js';\n\nexport const registerCartItemProductUrlField = (fields) => {\n  const newFields = fields.concat([\n    {\n      key: 'productUrl',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          if (!this.getData('product_id')) {\n            return null;\n          }\n          const urlRewrite = await select()\n            .from('url_rewrite')\n            .where('entity_uuid', '=', product.uuid)\n            .and('entity_type', '=', 'product')\n            .load(pool);\n          if (!urlRewrite) {\n            return buildUrl('productView', {\n              uuid: product.uuid\n            });\n          } else {\n            return urlRewrite.request_path;\n          }\n        }\n      ],\n      dependencies: ['product_id']\n    }\n  ]);\n  return newFields;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/registerCartItemVariantOptionsField.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\n\nexport const registerCartItemVariantOptionsField = (fields) => {\n  const newFields = fields.concat([\n    {\n      key: 'variant_options',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          if (product.variant_group_id) {\n            const group = await select('attribute_one')\n              .select('attribute_two')\n              .select('attribute_three')\n              .select('attribute_four')\n              .select('attribute_five')\n              .from('variant_group')\n              .where('variant_group_id', '=', product.variant_group_id)\n              .load(pool);\n            if (!group) {\n              return null;\n            } else {\n              const query = select('a.attribute_code')\n                .select('a.attribute_name')\n                .select('a.attribute_id')\n                .select('o.option_id')\n                .select('o.option_text')\n                .from('attribute', 'a');\n              query\n                .innerJoin('product_attribute_value_index', 'o')\n                .on('a.attribute_id', '=', 'o.attribute_id');\n              query.where('o.product_id', '=', product.product_id).and(\n                'a.attribute_id',\n                'IN',\n                Object.values(group).filter((v) => v != null)\n              );\n\n              return JSON.stringify(await query.execute(pool));\n            }\n          } else {\n            return null;\n          }\n        }\n      ],\n      dependencies: ['variant_group_id']\n    }\n  ]);\n  return newFields;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/registerDefaultAttributeCollectionFilters.js",
    "content": "import { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport default async function registerDefaultAttributeCollectionFilters() {\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'name',\n      operation: ['like', 'nlike'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'attribute.attribute_name',\n          OPERATION_MAP[operation],\n          `%${value}%`\n        );\n        currentFilters.push({\n          key: 'name',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'code',\n      operation: ['eq', 'like', 'nlike', 'in'],\n      callback: (query, operation, value, currentFilters) => {\n        if (operation === 'in') {\n          query.andWhere(\n            'attribute.attribute_code',\n            OPERATION_MAP[operation],\n            value.split(',')\n          );\n        } else if (operation === 'eq') {\n          query.andWhere(\n            'attribute.attribute_code',\n            OPERATION_MAP[operation],\n            value\n          );\n        } else {\n          query.andWhere(\n            'attribute.attribute_code',\n            OPERATION_MAP[operation],\n            `%${value}%`\n          );\n        }\n        currentFilters.push({\n          key: 'code',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'group',\n      operation: ['in', 'eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query\n          .innerJoin('attribute_group_link')\n          .on(\n            'attribute.attribute_id',\n            '=',\n            'attribute_group_link.attribute_id'\n          );\n        query.andWhere(\n          'attribute_group_link.group_id',\n          OPERATION_MAP[operation],\n          value\n        );\n        currentFilters.push({\n          key: 'group',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'type',\n      operation: ['eq', 'neq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere('attribute.type', OPERATION_MAP[operation], value);\n        currentFilters.push({\n          key: 'type',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'is_required',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'attribute.is_required',\n          OPERATION_MAP[operation],\n          value\n        );\n        currentFilters.push({\n          key: 'is_required',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'is_filterable',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'attribute.is_filterable',\n          OPERATION_MAP[operation],\n          value\n        );\n        currentFilters.push({\n          key: 'is_filterable',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const attributeCollectionSortBy = getValueSync(\n          'attributeCollectionSortBy',\n          {\n            name: (query) => query.orderBy('attribute.name'),\n            type: (query) => query.orderBy('attribute.type'),\n            is_required: (query) => query.orderBy('attribute.is_required'),\n            is_filterable: (query) => query.orderBy('attribute.is_filterable')\n          }\n        );\n\n        if (attributeCollectionSortBy[value]) {\n          attributeCollectionSortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        }\n      }\n    }\n  ];\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/registerDefaultCategoryCollectionFilters.js",
    "content": "import { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport default async function registerDefaultCategoryCollectionFilters() {\n  const { isAdmin } = this;\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'name',\n      operation: ['like'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'category_description.name',\n          OPERATION_MAP[operation],\n          `%${value}%`\n        );\n        currentFilters.push({\n          key: 'name',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'status',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere('category.status', OPERATION_MAP[operation], value);\n        currentFilters.push({\n          key: 'status',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'include_in_nav',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'category.include_in_nav',\n          OPERATION_MAP[operation],\n          value\n        );\n        currentFilters.push({\n          key: 'include_in_nav',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'parent',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        if (value === null) {\n          query.andWhere('category.parent_id', 'IS NULL');\n        } else {\n          query.andWhere('category.parent_id', OPERATION_MAP[operation], value);\n        }\n        currentFilters.push({\n          key: 'parent',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const categorySortBy = getValueSync(\n          'categoryCollectionSortBy',\n          {\n            name: (query) => query.orderBy('category_description.name'),\n            include_in_nav: (query) => query.orderBy('category.include_in_nav'),\n            status: (query) => query.orderBy('category.status')\n          },\n          {\n            isAdmin\n          }\n        );\n\n        if (categorySortBy[value]) {\n          categorySortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        } else {\n          query.orderBy('category.category_id', 'DESC');\n        }\n      }\n    }\n  ];\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/registerDefaultCollectionCollectionFilters.js",
    "content": "import { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport default async function registerDefaultCollectionCollectionFilters() {\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'name',\n      operation: ['like'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'collection.name',\n          OPERATION_MAP[operation],\n          `%${value}%`\n        );\n        currentFilters.push({\n          key: 'name',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'code',\n      operation: ['like', 'eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'collection.code',\n          OPERATION_MAP[operation],\n          `%${value}%`\n        );\n        currentFilters.push({\n          key: 'code',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const collectionSortBy = getValueSync('collectionCollectionSortBy', {\n          name: (query) => query.orderBy('collection.name'),\n          code: (query) => query.orderBy('collection.code')\n        });\n\n        if (collectionSortBy[value]) {\n          collectionSortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        } else {\n          query.orderBy('collection.collection_id', 'DESC');\n        }\n      }\n    }\n  ];\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/services/registerDefaultProductCollectionFilters.js",
    "content": "import { value } from '@evershop/postgres-query-builder';\nimport uniqid from 'uniqid';\nimport { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport default async function registerDefaultProductCollectionFilters() {\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'keyword',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const where = query.getWhere();\n        const bindingKey = `keyword_${uniqid()}`;\n        where.addRaw(\n          'AND',\n          `to_tsvector('simple', product_description.name || ' ' || product_description.description) @@ websearch_to_tsquery('simple', :${bindingKey})`,\n          {\n            [bindingKey]: `%${value}%`\n          }\n        );\n        currentFilters.push({\n          key: 'keyword',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'min_price',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        // Check if the value is a positive number\n        if (!Number.isNaN(parseFloat(value)) && parseFloat(value) > 0) {\n          query.andWhere('product.price', '>=', parseFloat(value));\n          currentFilters.push({\n            key: 'min_price',\n            operation,\n            value\n          });\n        }\n      }\n    },\n    {\n      key: 'max_price',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        if (!Number.isNaN(parseFloat(value)) && parseFloat(value) > 0) {\n          query.andWhere('product.price', '<=', parseFloat(value));\n          currentFilters.push({\n            key: 'max_price',\n            operation,\n            value\n          });\n        }\n      }\n    },\n    {\n      key: 'name',\n      operation: ['like'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'product_description.name',\n          OPERATION_MAP[operation],\n          `%${value}%`\n        );\n        currentFilters.push({\n          key: 'name',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'qty',\n      operation: ['eq', 'gteq', 'lteq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'product_inventory.qty',\n          OPERATION_MAP[operation],\n          parseFloat(value) || 0\n        );\n        currentFilters.push({\n          key: 'qty',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'sku',\n      operation: ['like', 'in'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'product.sku',\n          OPERATION_MAP[operation],\n          value.split(',')\n        );\n        currentFilters.push({\n          key: 'sku',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'status',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere('product.status', OPERATION_MAP[operation], value);\n        currentFilters.push({\n          key: 'status',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'type',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        if (['simple', 'configurable'].includes(value)) {\n          switch (value) {\n            case 'simple':\n              query.andWhere('product.variant_group_id', 'IS NULL', null);\n              break;\n            case 'configurable':\n              query.andWhere('product.variant_group_id', 'IS NOT NULL', null);\n              break;\n            default:\n              break;\n          }\n          currentFilters.push({\n            key: 'type',\n            operation,\n            value\n          });\n        }\n      }\n    },\n    {\n      key: 'cat',\n      operation: ['eq', 'in', 'nin'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'product.category_id',\n          OPERATION_MAP[operation],\n          ['in', 'nin'].includes(operation) ? value.split(',') : value\n        );\n        currentFilters.push({\n          key: 'cat',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const productSortBy = getValueSync(\n          'productCollectionSortBy',\n          {\n            price: (query) => query.orderBy('product.price'),\n            name: (query) => query.orderBy('product_description.name'),\n            qty: (query) => query.orderBy('product_inventory.qty'),\n            status: (query) => query.orderBy('product.status')\n          },\n          {\n            isAdmin\n          }\n        );\n\n        if (productSortBy[value]) {\n          productSortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        } else {\n          query.orderBy('product.product_id', 'DESC');\n        }\n      }\n    }\n  ];\n\n  const { filterableAttributes } = this;\n  const { isAdmin } = this;\n  // Attribute filters\n  filterableAttributes.forEach((attribute) => {\n    defaultFilters.push({\n      key: attribute.attribute_code,\n      operation: ['in', 'eq'],\n      callback: (query, operation, val, currentFilters) => {\n        const alias = `attribute_${uniqid()}`;\n        // Split the value by comma and only get the positive integer\n        if (operation === 'in') {\n          const values = val\n            .split(',')\n            .map((v) => parseInt(v, 10))\n            .filter((v) => v > 0);\n          query\n            .innerJoin('product_attribute_value_index', alias)\n            .on(`${alias}.product_id`, '=', 'product.product_id')\n            .and(`${alias}.attribute_id`, '=', value(attribute.attribute_id))\n            .and(`${alias}.option_id`, 'IN', value(values));\n          currentFilters.push({\n            key: attribute.attribute_code,\n            operation,\n            value: val\n          });\n        } else if (operation === 'eq') {\n          const valueInt = parseInt(val, 10);\n          if (valueInt > 0) {\n            query\n              .innerJoin('product_attribute_value_index', alias)\n              .on(`${alias}.product_id`, '=', 'product.product_id')\n              .and(`${alias}.attribute_id`, '=', value(attribute.attribute_id))\n              .and(`${alias}.option_id`, '=', value(valueInt));\n            currentFilters.push({\n              key: attribute.attribute_code,\n              operation,\n              value: val\n            });\n          }\n        }\n      }\n    });\n  });\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/subscribers/category_created/buildUrlRewrite.ts",
    "content": "import {\n  execute,\n  insertOnUpdate,\n  select\n} from '@evershop/postgres-query-builder';\nimport { EventSubscriber } from '../../../../lib/event/subscriber.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\n\nconst buildUrlReWrite: EventSubscriber<'category_created'> = async (data) => {\n  const categoryId = data.category_id;\n  const categoryUuid = data.uuid;\n\n  // Load the category\n  const category = await select()\n    .from('category')\n    .where('category_id', '=', categoryId)\n    .load(pool);\n\n  if (!category) {\n    return;\n  }\n\n  // Load the parent categories\n  const query = await execute(\n    pool,\n    `WITH RECURSIVE parent_categories AS (\n      SELECT * FROM category WHERE category_id = ${categoryId}\n      UNION\n      SELECT c.* FROM category c\n      INNER JOIN parent_categories pc ON c.category_id = pc.parent_id\n    ) SELECT * FROM parent_categories`\n  );\n  const parentCategories = query.rows;\n\n  try {\n    // Build the url rewrite base on the category path, join the category_description table to get the url_key\n    let path = '';\n    for (let i = 0; i < parentCategories.length; i += 1) {\n      const cat = parentCategories[i];\n      const urlKey = await select('url_key')\n        .from('category_description')\n        .where('category_description_category_id', '=', cat.category_id)\n        .load(pool);\n      path = `/${urlKey.url_key}${path}`;\n    }\n    // Insert the url rewrite rule to the url_rewrite table\n    await insertOnUpdate('url_rewrite', ['entity_uuid', 'language'])\n      .given({\n        entity_type: 'category',\n        entity_uuid: categoryUuid,\n        request_path: path,\n        target_path: `/category/${categoryUuid}`\n      })\n      .execute(pool);\n  } catch (err) {\n    error(err);\n  }\n};\n\nexport default buildUrlReWrite;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/subscribers/category_deleted/deleteUrlRewrite.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { EventSubscriber } from '../../../../lib/event/subscriber.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\n\nconst deleteUrlReWrite: EventSubscriber<'category_deleted'> = async (data) => {\n  try {\n    const categoryUuid = data.uuid;\n    // Get the current url rewrite for this category\n    const urlRewrite = await select()\n      .from('url_rewrite')\n      .where('entity_uuid', '=', categoryUuid)\n      .and('entity_type', '=', 'category')\n      .load(pool);\n    // Delete all the url rewrite rule for this category\n    await pool.query(\n      `DELETE FROM url_rewrite WHERE entity_type = 'category' AND entity_uuid = $1`,\n      [categoryUuid]\n    );\n\n    if (!urlRewrite) {\n      return;\n    } else {\n      // Delete all the url rewrite rule for the sub categories and products\n      await pool.query(\n        `DELETE FROM url_rewrite WHERE request_path LIKE $1 AND entity_type IN ('category', 'product')`,\n        [`${urlRewrite.request_path}/%`]\n      );\n    }\n  } catch (err) {\n    error(err);\n  }\n};\n\nexport default deleteUrlReWrite;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/subscribers/category_updated/builUrlRewrite.ts",
    "content": "import {\n  execute,\n  insertOnUpdate,\n  select\n} from '@evershop/postgres-query-builder';\nimport { EventSubscriber } from '../../../../lib/event/subscriber.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\n\nconst buildUrlReWrite: EventSubscriber<'category_updated'> = async (data) => {\n  try {\n    const categoryUUid = data.uuid;\n    const categoryId = data.category_id;\n    // Load the category\n    const category = await select()\n      .from('category')\n      .where('category_id', '=', categoryId)\n      .load(pool);\n\n    if (!category) {\n      return;\n    }\n\n    // Load the parent categories\n    const parentCategoriesQuery = await execute(\n      pool,\n      `WITH RECURSIVE parent_categories AS (\n      SELECT * FROM category WHERE category_id = ${categoryId}\n      UNION\n      SELECT c.* FROM category c\n      INNER JOIN parent_categories pc ON c.category_id = pc.parent_id\n    ) SELECT * FROM parent_categories`\n    );\n    const parentCategories = parentCategoriesQuery.rows;\n    // Build the url rewrite base on the category path, join the category_description table to get the url_key\n    let path = '';\n    for (let i = 0; i < parentCategories.length; i += 1) {\n      const cat = parentCategories[i];\n      const urlKey = await select('url_key')\n        .from('category_description')\n        .where('category_description_category_id', '=', cat.category_id)\n        .load(pool);\n      path = `/${urlKey.url_key}${path}`;\n    }\n    // Save the current path\n    const currentPath = await select('request_path')\n      .from('url_rewrite')\n      .where('entity_uuid', '=', categoryUUid)\n      .and('entity_type', '=', 'category')\n      .load(pool);\n\n    // Insert the url rewrite rule to the url_rewrite table\n    await insertOnUpdate('url_rewrite', ['entity_uuid', 'language'])\n      .given({\n        entity_type: 'category',\n        entity_uuid: categoryUUid,\n        request_path: path,\n        target_path: `/category/${categoryUUid}`\n      })\n      .execute(pool);\n\n    // Replace the url rewrite rule for all the sub categories and products. Search for the url rewrite rule by entity_uuid and entity_type\n    if (currentPath) {\n      await pool.query(\n        `UPDATE url_rewrite SET request_path = REPLACE(request_path, $1, $2) WHERE entity_type IN ('category', 'product') AND entity_uuid != $3`,\n        [currentPath.request_path, path, categoryUUid]\n      );\n    }\n  } catch (err) {\n    error(err);\n  }\n};\n\nexport default buildUrlReWrite;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/subscribers/product_created/buildUrlRewrite.ts",
    "content": "import { insertOnUpdate, select } from '@evershop/postgres-query-builder';\nimport { EventSubscriber } from '../../../../lib/event/subscriber.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\n\nconst buildUrlRewrite: EventSubscriber<'product_created'> = async (data) => {\n  try {\n    const productId = data.product_id;\n    const productUuid = data.uuid;\n    const categoryId = data.category_id;\n    const productDescription = await select()\n      .from('product_description')\n      .where('product_description_product_id', '=', productId)\n      .load(pool);\n\n    if (!productDescription) {\n      return;\n    }\n    // Insert a new url rewrite for the product itself\n    await insertOnUpdate('url_rewrite', ['entity_uuid', 'language'])\n      .given({\n        entity_type: 'product',\n        entity_uuid: productUuid,\n        request_path: `/${productDescription.url_key}`,\n        target_path: `/product/${productUuid}`\n      })\n      .execute(pool);\n\n    // Load the category\n    const category = await select()\n      .from('category')\n      .where('category_id', '=', categoryId)\n      .load(pool);\n\n    if (!category) {\n      return;\n    }\n\n    // Get the url_rewrite for the category\n    const categoryUrlRewrite = await select()\n      .from('url_rewrite')\n      .where('entity_uuid', '=', category.uuid)\n      .and('entity_type', '=', 'category')\n      .load(pool);\n\n    if (!categoryUrlRewrite) {\n      // Wait for the category event to be fired and create the url rewrite for product\n      return;\n    } else {\n      await insertOnUpdate('url_rewrite', ['entity_uuid', 'language'])\n        .given({\n          entity_type: 'product',\n          entity_uuid: productUuid,\n          request_path: `${categoryUrlRewrite.request_path}/${productDescription.url_key}`,\n          target_path: `/product/${productUuid}`\n        })\n        .execute(pool);\n    }\n  } catch (e) {\n    error(e);\n  }\n};\n\nexport default buildUrlRewrite;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/subscribers/product_deleted/deleteUrlRewrite.ts",
    "content": "import { execute } from '@evershop/postgres-query-builder';\nimport { EventSubscriber } from '../../../../lib/event/subscriber.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\n\nconst buildUrlReWrite: EventSubscriber<'product_deleted'> = async (data) => {\n  const productUuid = data.uuid;\n\n  // Delete the url rewrite for the product\n  await execute(\n    pool,\n    `DELETE FROM url_rewrite WHERE entity_uuid = '${productUuid}' AND entity_type = 'product'`\n  );\n};\n\nexport default buildUrlReWrite;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/subscribers/product_updated/buildUrlRewrite.ts",
    "content": "import { insertOnUpdate, select } from '@evershop/postgres-query-builder';\nimport { EventSubscriber } from '../../../../lib/event/subscriber.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\n\nconst buildUrlReWrite: EventSubscriber<'product_updated'> = async (data) => {\n  const productId = data.product_id;\n  const productUuid = data.uuid;\n  const categoryId = data.category_id;\n  const productDescription = await select()\n    .from('product_description')\n    .where('product_description_product_id', '=', productId)\n    .load(pool);\n\n  if (!productDescription) {\n    return;\n  }\n\n  // Update the url rewrite for the product itself\n  await insertOnUpdate('url_rewrite', ['entity_uuid', 'language'])\n    .given({\n      entity_type: 'product',\n      entity_uuid: productUuid,\n      request_path: `/${productDescription.url_key}`,\n      target_path: `/product/${productUuid}`\n    })\n    .execute(pool);\n\n  // Load the category\n  const category = await select()\n    .from('category')\n    .where('category_id', '=', categoryId)\n    .load(pool);\n\n  if (!category) {\n    return;\n  }\n  // Get the url_rewrite for the category\n  const categoryUrlRewrite = await select()\n    .from('url_rewrite')\n    .where('entity_uuid', '=', category.uuid)\n    .and('entity_type', '=', 'category')\n    .load(pool);\n\n  if (!categoryUrlRewrite) {\n    // Wait for the category event to be fired and create the url rewrite for product\n  } else {\n    await insertOnUpdate('url_rewrite', ['entity_uuid', 'language'])\n      .given({\n        entity_type: 'product',\n        entity_uuid: productUuid,\n        request_path: `${categoryUrlRewrite.request_path}/${productDescription.url_key}`,\n        target_path: `/product/${productUuid}`\n      })\n      .execute(pool);\n  }\n};\n\nexport default buildUrlReWrite;\n"
  },
  {
    "path": "packages/evershop/src/modules/catalog/tests/intergration/productView.test.js",
    "content": "describe('Product page is rendered correctly', () => {\n  it('It should returns 404 when the product is not existed', () => {\n    cy.request({\n      url: '/product/123456789',\n      failOnStatusCode: false\n    }).should((response) => {\n      expect(response.status).to.eq(404);\n      expect(response.body).to.contains('Not found');\n    });\n  });\n\n  it('It should return 404 when the product is disabled', () => {\n    cy.request({\n      url: '/product/product-name',\n      failOnStatusCode: false\n    }).should((response) => {\n      expect(response.status).to.eq(404);\n      expect(response.body).to.contains('Not found');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartAddress/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartAddress/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"address\": {\n      \"type\": \"object\",\n      \"properties\": {},\n      \"additionalProperties\": true\n    },\n    \"type\": {\n      \"type\": \"string\",\n      \"enum\": [\"shipping\", \"billing\"]\n    }\n  },\n  \"required\": [\"address\", \"type\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"cart_id\": \"Cart id is required\",\n      \"address\": \"Address is required\",\n      \"type\": \"Address type is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartAddress/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/carts/:cart_id/addresses\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartAddress/saveAddress.ts",
    "content": "import {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { addBillingAddress } from '../../services/addBillingAddress.js';\nimport { addShippingAddress } from '../../services/addShippingAddress.js';\nimport { getCartByUUID } from '../../services/getCartByUUID.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { cart_id } = request.params;\n    const { address, type } = request.body;\n    // Check if cart exists\n    const cart = await getCartByUUID(cart_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD);\n      return response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid cart'\n        }\n      });\n    }\n    let addedAddress;\n    if (type === 'shipping') {\n      addedAddress = await addShippingAddress(cart.getData('uuid'), address, {\n        cart\n      });\n    } else {\n      addedAddress = await addBillingAddress(cart.getData('uuid'), address, {\n        cart\n      });\n    }\n\n    response.status(OK);\n    return response.json({\n      data: addedAddress\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    return response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartContactInfo/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartContactInfo/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    }\n  },\n  \"required\": [\"email\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"email\": \"Email is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartContactInfo/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/carts/:cart_id/contacts\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartContactInfo/saveContactInfo.js",
    "content": "import {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { getCartByUUID } from '../../services/getCartByUUID.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { cart_id } = request.params;\n    const { email } = request.body;\n    // Check if cart exists\n    const cart = await getCartByUUID(cart_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD).json({\n        error: {\n          message: 'Invalid cart',\n          status: INVALID_PAYLOAD\n        }\n      });\n    } else {\n      await cart.setData('customer_email', email);\n      await saveCart(cart);\n      response.status(OK);\n      response.$body = {\n        data: {\n          email: cart.getData('customer_email')\n        }\n      };\n      next();\n    }\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        message: e.message,\n        status: INTERNAL_SERVER_ERROR\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartItem/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartItem/addItemToCart.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport {\n  INVALID_PAYLOAD,\n  INTERNAL_SERVER_ERROR,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { setContextValue } from '../../../graphql/services/contextHelper.js';\nimport { getCartByUUID } from '../../services/getCartByUUID.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request, response, next) => {\n  try {\n    const cartId = request.params.cart_id;\n    const { sku, qty } = request.body;\n    const cart = await getCartByUUID(cartId); // Cart object\n\n    // If the cart is not found, respond with 400\n    if (!cart) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid cart id'\n        }\n      });\n      return;\n    }\n\n    // Load the product by sku\n    const product = await select()\n      .from('product')\n      .where('sku', '=', sku)\n      .and('status', '=', 1)\n      .load(pool);\n\n    if (!product) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Product not found'\n        }\n      });\n      return;\n    }\n\n    // If everything is fine, add the product to the cart\n    const item = await cart.addItem(product.product_id, parseInt(qty, 10));\n    await saveCart(cart);\n    response.status(OK);\n    response.$body = {\n      data: {\n        item: item.export(),\n        count: cart.getData('total_qty'),\n        cartId: cart.getData('uuid')\n      }\n    };\n    next();\n  } catch (error) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: error.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartItem/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"sku\": {\n      \"type\": \"string\"\n    },\n    \"qty\": {\n      \"type\": [\"string\", \"integer\"],\n      \"pattern\": \"^[1-9][0-9]*$\"\n    }\n  },\n  \"required\": [\"sku\", \"qty\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"sku\": \"Sku is required\",\n      \"qty\": \"Qty is invalid\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartItem/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/cart/:cart_id/items\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartPaymentMethod/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartPaymentMethod/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"method_code\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"method_code\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"cart_id\": \"Cart id is required\",\n      \"method_code\": \"Method code is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartPaymentMethod/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/carts/:cart_id/paymentMethods\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartPaymentMethod/savePaymentMethod.js",
    "content": "import {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { getCartByUUID } from '../../services/getCartByUUID.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { cart_id } = request.params;\n    const { method_code, method_name } = request.body;\n    // Check if cart exists\n    const cart = await getCartByUUID(cart_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD).json({\n        error: {\n          message: 'Invalid cart',\n          status: INVALID_PAYLOAD\n        }\n      });\n    } else {\n      // Save payment method\n      await cart.setData('payment_method', method_code);\n\n      // Save the cart\n      await saveCart(cart);\n      response.status(OK);\n      response.$body = {\n        data: {\n          method: {\n            code: method_code,\n            name: method_name\n          }\n        }\n      };\n      next();\n    }\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        message: e.message,\n        status: INTERNAL_SERVER_ERROR\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartShippingMethod/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartShippingMethod/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"method_code\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"method_code\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"method_code\": \"Method code is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartShippingMethod/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/carts/:cart_id/shippingMethods\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addCartShippingMethod/saveShippingMethod.js",
    "content": "import { translate } from '../../../../lib/locale/translate/translate.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { getCartByUUID } from '../../services/getCartByUUID.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { cart_id } = request.params;\n    const { method_code } = request.body;\n    // Check if cart exists\n    const cart = await getCartByUUID(cart_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD).json({\n        error: {\n          message: 'Invalid cart',\n          status: INVALID_PAYLOAD\n        }\n      });\n    } else {\n      // Save payment method\n      await cart.setData('shipping_method', method_code);\n\n      // Save the cart\n      await saveCart(cart);\n      response.status(OK);\n      response.$body = {\n        data: {\n          method: {\n            code: method_code\n          }\n        }\n      };\n      next();\n    }\n  } catch (e) {\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        message: translate('Failed to set shipping method'),\n        status: INTERNAL_SERVER_ERROR\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addMineCartItem/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addMineCartItem/addItemToCart.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { translate } from '../../../../lib/locale/translate/translate.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { setContextValue } from '../../../graphql/services/contextHelper.js';\nimport { createNewCart } from '../../services/createNewCart.js';\nimport { getMyCart } from '../../services/getMyCart.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request: EvershopRequest, response, next) => {\n  try {\n    const { sessionID, customer } = request.locals;\n    let myCart = await getMyCart(sessionID || '', customer?.customer_id);\n    if (!myCart) {\n      // Create a new cart\n      myCart = await createNewCart(sessionID || '', customer || {});\n    }\n    const { sku, qty } = request.body;\n\n    // Load the product by sku\n    const product = await select()\n      .from('product')\n      .where('sku', '=', sku)\n      .and('status', '=', 1)\n      .load(pool);\n\n    if (!product) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: translate('Product not found')\n        }\n      });\n      return;\n    }\n\n    // If everything is fine, add the product to the cart\n    const item = await myCart.addItem(product.product_id, parseInt(qty, 10), {\n      request\n    });\n    await saveCart(myCart);\n    response.status(OK);\n    response.$body = {\n      data: {\n        item: item.export(),\n        count: myCart.getData('total_qty'),\n        cartId: myCart.getData('uuid')\n      }\n    };\n    next();\n  } catch (e) {\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: (e as Error)?.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addMineCartItem/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"sku\": {\n      \"type\": \"string\"\n    },\n    \"qty\": {\n      \"type\": [\"string\", \"integer\"],\n      \"pattern\": \"^[1-9][0-9]*$\"\n    }\n  },\n  \"required\": [\"sku\", \"qty\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"sku\": \"Sku is required\",\n      \"qty\": \"Qty is invalid\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addMineCartItem/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/cart/mine/items\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addShippingNote/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addShippingNote/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"note\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"note\"],\n  \"additionalProperties\": false,\n  \"errorMessage\": {\n    \"properties\": {\n      \"note\": \"Note is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addShippingNote/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/carts/:cart_id/shippingNotes\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addShippingNote/saveShippingNote.js",
    "content": "import { translate } from '../../../../lib/locale/translate/translate.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { getCartByUUID } from '../../services/getCartByUUID.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { cart_id } = request.params;\n    const { note } = request.body;\n    // Check if cart exists\n    const cart = await getCartByUUID(cart_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD).json({\n        error: {\n          message: 'Invalid cart',\n          status: INVALID_PAYLOAD\n        }\n      });\n    } else {\n      // Save shipping note\n      await cart.setData('shipping_note', note);\n      // Save the cart\n      await saveCart(cart);\n      response.status(OK);\n      response.$body = {\n        data: {\n          note\n        }\n      };\n      next();\n    }\n  } catch (e) {\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        message: translate('Failed to set shipping note'),\n        status: INTERNAL_SERVER_ERROR\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addShippingZoneMethod/[context]borderParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addShippingZoneMethod/[validateMethod]addShippingZoneMethod.js",
    "content": "import {\n  rollback,\n  insert,\n  commit,\n  startTransaction,\n  select\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const { id } = request.params;\n  let {\n    cost,\n    calculate_api,\n    condition_type,\n    max,\n    min,\n    weight_based_cost,\n    price_based_cost\n  } = request.body;\n  const { method_id, is_enabled, calculation_type } = request.body;\n\n  if (calculation_type === 'api') {\n    cost = weight_based_cost = price_based_cost = null;\n  } else if (calculation_type === 'price_based_rate') {\n    calculate_api = cost = weight_based_cost = null;\n  } else if (calculation_type === 'weight_based_rate') {\n    calculate_api = cost = price_based_cost = null;\n  } else {\n    calculate_api = weight_based_cost = price_based_cost = null;\n  }\n  if (condition_type === 'none') {\n    condition_type = null;\n    min = max = null;\n  }\n\n  const connection = await getConnection();\n  await startTransaction(connection);\n\n  try {\n    const zone = await select()\n      .from('shipping_zone')\n      .where('uuid', '=', id)\n      .load(connection);\n\n    if (!zone) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid zone id'\n        }\n      });\n      return;\n    }\n\n    const method = await select()\n      .from('shipping_method')\n      .where('uuid', '=', method_id)\n      .load(connection);\n\n    if (!method) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid method id'\n        }\n      });\n      return;\n    }\n\n    const zoneMethod = await insert('shipping_zone_method')\n      .given({\n        zone_id: zone.shipping_zone_id,\n        method_id: method.shipping_method_id,\n        cost,\n        is_enabled,\n        calculate_api,\n        condition_type,\n        price_based_cost,\n        weight_based_cost,\n        max,\n        min\n      })\n      .execute(connection);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: zoneMethod\n    });\n  } catch (e) {\n    await rollback(connection);\n\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message.includes('METHOD_ZONE_UNIQUE')\n          ? 'This shipping method is already added to this zone'\n          : e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addShippingZoneMethod/payloadSchema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"method_id\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"description\": \"The shipping method ID\",\n      \"errorMessage\": {\n        \"type\": \"Shipping method ID must be a string\",\n        \"minLength\": \"Shipping method ID cannot be empty\"\n      }\n    },\n    \"cost\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n      \"description\": \"Flat rate shipping cost with up to 2 decimal places\",\n      \"errorMessage\": {\n        \"type\": \"Cost must be a number or numeric string\",\n        \"pattern\": \"Cost must be a valid number with up to 2 decimal places (e.g., 10.00)\"\n      }\n    },\n    \"is_enabled\": {\n      \"type\": [\"integer\", \"string\", \"boolean\"],\n      \"enum\": [0, 1, \"0\", \"1\", true, false],\n      \"description\": \"Whether the shipping method is enabled\",\n      \"errorMessage\": {\n        \"type\": \"Status must be a boolean, 0, 1, '0', or '1'\",\n        \"enum\": \"Status must be one of: 0, 1, '0', '1', true, or false\"\n      }\n    },\n    \"calculation_type\": {\n      \"type\": \"string\",\n      \"enum\": [\"flat_rate\", \"price_based_rate\", \"weight_based_rate\", \"api\"],\n      \"description\": \"The type of calculation to use for shipping cost\",\n      \"errorMessage\": {\n        \"type\": \"Calculation type must be a string\",\n        \"enum\": \"Calculation type must be one of: flat_rate, price_based_rate, weight_based_rate, or api\"\n      }\n    },\n    \"calculate_api\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"description\": \"API endpoint URL for calculating shipping cost\",\n      \"errorMessage\": {\n        \"type\": \"Calculate API must be a string\",\n        \"minLength\": \"Calculate API cannot be empty\"\n      }\n    },\n    \"condition_type\": {\n      \"type\": \"string\",\n      \"enum\": [\"weight\", \"price\", \"none\"],\n      \"description\": \"The type of condition to apply for this shipping method\",\n      \"errorMessage\": {\n        \"type\": \"Condition type must be a string\",\n        \"enum\": \"Condition type must be one of: weight, price, or none\"\n      }\n    },\n    \"min\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n      \"description\": \"Minimum order price or weight for condition\",\n      \"errorMessage\": {\n        \"type\": \"Minimum value must be a number or numeric string\",\n        \"pattern\": \"Minimum value must be a valid number with up to 2 decimal places\"\n      }\n    },\n    \"max\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n      \"description\": \"Maximum order price or weight for condition\",\n      \"errorMessage\": {\n        \"type\": \"Maximum value must be a number or numeric string\",\n        \"pattern\": \"Maximum value must be a valid number with up to 2 decimal places\"\n      }\n    },\n    \"weight_based_cost\": {\n      \"type\": \"array\",\n      \"minItems\": 1,\n      \"description\": \"Array of weight tiers for weight-based shipping calculation\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"min_weight\": {\n            \"type\": [\"string\", \"number\"],\n            \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n            \"description\": \"Minimum order weight for this tier\",\n            \"errorMessage\": {\n              \"type\": \"Minimum weight must be a number or numeric string\",\n              \"pattern\": \"Minimum weight must be a valid number with up to 2 decimal places\"\n            }\n          },\n          \"cost\": {\n            \"type\": [\"string\", \"number\"],\n            \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n            \"description\": \"Shipping cost for this weight tier\",\n            \"errorMessage\": {\n              \"type\": \"Cost must be a number or numeric string\",\n              \"pattern\": \"Cost must be a valid number with up to 2 decimal places\"\n            }\n          }\n        },\n        \"additionalProperties\": false,\n        \"required\": [\"min_weight\", \"cost\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"min_weight\": \"Minimum weight is required for each weight tier\",\n            \"cost\": \"Cost is required for each weight tier\"\n          }\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Weight based cost must be an array\",\n        \"minItems\": \"At least one weight tier is required for weight-based calculation\"\n      }\n    },\n    \"price_based_cost\": {\n      \"type\": \"array\",\n      \"minItems\": 1,\n      \"description\": \"Array of price tiers for price-based shipping calculation\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"min_price\": {\n            \"type\": [\"string\", \"number\"],\n            \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n            \"description\": \"Minimum order price for this tier\",\n            \"errorMessage\": {\n              \"type\": \"Minimum price must be a number or numeric string\",\n              \"pattern\": \"Minimum price must be a valid number with up to 2 decimal places\"\n            }\n          },\n          \"cost\": {\n            \"type\": [\"string\", \"number\"],\n            \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n            \"description\": \"Shipping cost for this price tier\",\n            \"errorMessage\": {\n              \"type\": \"Cost must be a number or numeric string\",\n              \"pattern\": \"Cost must be a valid number with up to 2 decimal places\"\n            }\n          }\n        },\n        \"additionalProperties\": false,\n        \"required\": [\"min_price\", \"cost\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"min_price\": \"Minimum price is required for each price tier\",\n            \"cost\": \"Cost is required for each price tier\"\n          }\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Price based cost must be an array\",\n        \"minItems\": \"At least one price tier is required for price-based calculation\"\n      }\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\"method_id\", \"condition_type\"],\n  \"errorMessage\": {\n    \"required\": {\n      \"method_id\": \"Shipping method ID is required\",\n      \"condition_type\": \"Condition type is required\"\n    }\n  },\n  \"allOf\": [\n    {\n      \"if\": {\n        \"properties\": {\n          \"calculation_type\": { \"const\": \"flat_rate\" }\n        }\n      },\n      \"then\": {\n        \"required\": [\"calculation_type\", \"cost\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"calculation_type\": \"Calculation type is required\",\n            \"cost\": \"Cost is required when calculation type is flat_rate\"\n          }\n        }\n      }\n    },\n    {\n      \"if\": {\n        \"properties\": {\n          \"calculation_type\": { \"const\": \"price_based_rate\" }\n        }\n      },\n      \"then\": {\n        \"required\": [\"calculation_type\", \"price_based_cost\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"calculation_type\": \"Calculation type is required\",\n            \"price_based_cost\": \"Price based cost tiers are required when calculation type is price_based_rate\"\n          }\n        }\n      }\n    },\n    {\n      \"if\": {\n        \"properties\": {\n          \"calculation_type\": { \"const\": \"weight_based_rate\" }\n        }\n      },\n      \"then\": {\n        \"required\": [\"calculation_type\", \"weight_based_cost\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"calculation_type\": \"Calculation type is required\",\n            \"weight_based_cost\": \"Weight based cost tiers are required when calculation type is weight_based_rate\"\n          }\n        }\n      }\n    },\n    {\n      \"if\": {\n        \"properties\": {\n          \"calculation_type\": { \"const\": \"api\" }\n        }\n      },\n      \"then\": {\n        \"required\": [\"calculation_type\", \"calculate_api\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"calculation_type\": \"Calculation type is required\",\n            \"calculate_api\": \"Calculate API URL is required when calculation type is api\"\n          }\n        }\n      }\n    },\n    {\n      \"if\": {\n        \"properties\": {\n          \"condition_type\": { \"enum\": [\"weight\", \"price\"] }\n        }\n      },\n      \"then\": {\n        \"required\": [\"min\", \"max\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"min\": \"Minimum value is required when using weight or price conditions\",\n            \"max\": \"Maximum value is required when using weight or price conditions\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addShippingZoneMethod/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/shippingZones/:id/methods\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/addShippingZoneMethod/validateMethod.js",
    "content": "import { error } from '../../../../lib/log/logger.js';\n\nexport default async (request, response, next) => {\n  const {\n    cost,\n    calculate_api,\n    weight_based_cost,\n    price_based_cost,\n    calculation_type\n  } = request.body;\n\n  try {\n    if (calculation_type === 'api') {\n      if (!calculate_api) {\n        throw new Error('API calculation type requires calculate_api');\n      }\n    } else if (calculation_type === 'price_based_rate') {\n      if (!price_based_cost || price_based_cost.length === 0) {\n        throw new Error('Require price based rates');\n      }\n    } else if (calculation_type === 'weight_based_rate') {\n      if (!weight_based_cost || weight_based_cost.length === 0) {\n        throw new Error('Require weight based rates');\n      }\n    } else if (!cost) {\n      throw new Error('Flat rate calculation type requires cost');\n    }\n    return next();\n  } catch (e) {\n    error(e);\n    return next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/cartCheckout/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/cartCheckout/checkout.ts",
    "content": "import { error } from '../../../../lib/log/logger.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport { checkout } from '../../services/checkout.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  try {\n    const { cart_id } = request.params;\n    const cartId = Array.isArray(cart_id) ? cart_id[0] : cart_id;\n    const checkoutData = {\n      ...request.body,\n      customer: {\n        email: request.body.customer.email\n      }\n    };\n    const customer = request.getCurrentCustomer();\n    if (customer) {\n      checkoutData.customer = {\n        id: customer.customer_id,\n        email: customer.email,\n        fullName: customer.full_name\n      };\n    }\n    const order = await checkout(cartId, checkoutData);\n    response.status(OK);\n    response.$body = {\n      data: {\n        ...order,\n        links: [\n          {\n            rel: 'edit',\n            href: buildUrl('orderEdit', { id: order.uuid }),\n            action: 'GET',\n            types: ['text/xml']\n          }\n        ]\n      }\n    };\n    next();\n  } catch (e) {\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        message: e.message,\n        status: INTERNAL_SERVER_ERROR\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/cartCheckout/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"cart_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"cart_id\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"cart_id\": \"Cart id is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/cartCheckout/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/carts/:cart_id/checkout\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createCart/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createCart/createNewCart.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { translate } from '../../../../lib/locale/translate/translate.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport {\n  INVALID_PAYLOAD,\n  INTERNAL_SERVER_ERROR,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { setContextValue } from '../../../graphql/services/contextHelper.js';\nimport { Cart } from '../../services/cart/Cart.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { items, customer_full_name, customer_email } = request.body;\n    const cartData = {\n      currency: getConfig('shop.currency', 'USD')\n    };\n    if (customer_full_name) {\n      cartData.customer_full_name = customer_full_name;\n    }\n    if (customer_email) {\n      cartData.customer_email = customer_email;\n    }\n    const cart = new Cart(cartData);\n    // Check if items is not an empty array\n    if (items.length === 0) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: translate('Requires at least one item to create a cart')\n        }\n      });\n      return;\n    }\n    // Maximum 100 items per cart\n    if (items.length > 100) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: translate('Too many items requested')\n        }\n      });\n      return;\n    }\n\n    // Loop through the items and group by sku\n    const groupedItems = items.reduce(\n      (acc, item) => {\n        if (acc.find((i) => i.sku === item.sku)) {\n          acc.find((i) => i.sku === item.sku).qty += item.qty;\n        } else {\n          acc.push(item);\n        }\n        return acc;\n      },\n      [{ sku: items[0].sku, qty: 0 }]\n    );\n    const products = await select()\n      .from('product')\n      .where(\n        'sku',\n        'IN',\n        groupedItems.map((item) => item.sku)\n      )\n      .and('status', '=', 1)\n      .execute(pool);\n    // Check if all products are available\n    if (products.length !== groupedItems.length) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: translate('Some products are not available')\n        }\n      });\n      return;\n    }\n    // Map the grouped items to include product_id\n    products.forEach((product) => {\n      const item = groupedItems.find((i) => i.sku === product.sku);\n      item.product_id = product.product_id;\n    });\n    // Loop through the grouped items and add them to the cart\n    const cartItems = await Promise.all(\n      groupedItems.map(async (item) => {\n        const cartItem = await cart.addItem(item.product_id, item.qty);\n        return cartItem;\n      })\n    );\n    await saveCart(cart);\n    // Set the new cart id to the context, so next middleware can use it\n    setContextValue(request, 'cartId', cart.getData('uuid'));\n    response.status(OK);\n    response.$body = {\n      data: {\n        items: cartItems.map((item) => item.export()),\n        count: cart.getItems().length,\n        cartId: cart.getData('uuid')\n      }\n    };\n    next();\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createCart/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"customer_full_name\": {\n      \"type\": \"string\"\n    },\n    \"customer_email\": {\n      \"type\": [\"string\"],\n      \"format\": \"email\",\n      \"errorMessage\": {\n        \"type\": \"Email is invalid\"\n      }\n    },\n    \"items\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"sku\": {\n            \"type\": \"string\"\n          },\n          \"qty\": {\n            \"type\": \"integer\"\n          }\n        },\n        \"required\": [\"sku\", \"qty\"],\n        \"additionalProperties\": true,\n        \"errorMessage\": {\n          \"properties\": {\n            \"sku\": \"Sku is required\",\n            \"qty\": \"Qty is invalid\"\n          }\n        }\n      }\n    }\n  },\n  \"required\": [\"items\"],\n  \"errorMessage\": {\n    \"required\": {\n      \"items\": \"Must provide at least one item\"\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createCart/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/carts\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createOrder/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createOrder/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"cart_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"cart_id\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"cart_id\": \"Cart id is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createOrder/placeOrder.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { getCartByUUID } from '../../services/getCartByUUID.js';\nimport { createOrder } from '../../services/orderCreator.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { cart_id } = request.body;\n    // Verify cart\n    const cart = await getCartByUUID(cart_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          message: 'Invalid cart',\n          status: INVALID_PAYLOAD\n        }\n      });\n      return;\n    } else if (cart.hasError()) {\n      const errors = cart.getErrors();\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          message: Object.values(errors)[0],\n          status: INVALID_PAYLOAD\n        }\n      });\n      return;\n    }\n\n    const { uuid: orderId } = await createOrder(cart);\n\n    // Load created order\n    const order = await select()\n      .from('order')\n      .where('uuid', '=', orderId)\n      .load(pool);\n\n    order.items = await select()\n      .from('order_item')\n      .where('order_item_order_id', '=', order.order_id)\n      .execute(pool);\n\n    order.shipping_address = await select()\n      .from('order_address')\n      .where('order_address_id', '=', order.shipping_address_id)\n      .load(pool);\n\n    order.billing_address = await select()\n      .from('order_address')\n      .where('order_address_id', '=', order.billing_address_id)\n      .load(pool);\n\n    response.status(OK);\n    response.$body = {\n      data: {\n        ...order,\n        links: [\n          {\n            rel: 'edit',\n            href: buildUrl('orderEdit', { id: order.uuid }),\n            action: 'GET',\n            types: ['text/xml']\n          }\n        ]\n      }\n    };\n    next();\n  } catch (e) {\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        message: e.message,\n        status: INTERNAL_SERVER_ERROR\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createOrder/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/orders\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createShippingMethod/[context]borderParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createShippingMethod/createShippingMethod.js",
    "content": "import {\n  rollback,\n  insert,\n  commit,\n  startTransaction,\n  select\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { name } = request.body;\n  try {\n    const method = await select()\n      .from('shipping_method')\n      .where('name', '=', name)\n      .load(connection);\n\n    if (method) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Method name already exists'\n        }\n      });\n      return;\n    }\n\n    const newMethod = await insert('shipping_method')\n      .given({\n        name\n      })\n      .execute(connection);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: newMethod\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createShippingMethod/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\"\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\"name\"]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createShippingMethod/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/shippingMethods\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createShippingZone/[context]borderParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createShippingZone/createShippingZone.js",
    "content": "import {\n  rollback,\n  insert,\n  commit,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { OK, INTERNAL_SERVER_ERROR } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { name, country, provinces = [] } = request.body;\n  try {\n    const zone = await insert('shipping_zone')\n      .given({\n        name,\n        country\n      })\n      .execute(connection);\n\n    const zoneId = zone.insertId;\n    const provincePromises = provinces\n      .filter((p) => !!p)\n      .map((province) =>\n        insert('shipping_zone_province')\n          .given({\n            zone_id: zoneId,\n            province\n          })\n          .execute(connection)\n      );\n    await Promise.all(provincePromises);\n    await commit(connection);\n\n    response.status(OK);\n    response.json({\n      data: zone\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createShippingZone/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\"\n    },\n    \"country\": {\n      \"type\": \"string\"\n    },\n    \"provinces\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\"name\", \"country\"]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/createShippingZone/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/shippingZones\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/deleteShippingZone/deleteShippingZone.js",
    "content": "import {\n  rollback,\n  commit,\n  startTransaction,\n  del,\n  select\n} from '@evershop/postgres-query-builder';\nimport { error } from '../../../../lib/log/logger.js';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const { id } = request.params;\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    // Load the shipping zone\n    const shippingZone = await select()\n      .from('shipping_zone')\n      .where('uuid', '=', id)\n      .load(connection);\n\n    if (!shippingZone) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid zone id'\n        }\n      });\n      return;\n    }\n    await del('shipping_zone').where('uuid', '=', id).execute(connection);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: shippingZone\n    });\n  } catch (e) {\n    error(e);\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/deleteShippingZone/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/shippingZones/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/deleteShippingZoneMethod/deleteShippingZoneMethod.js",
    "content": "import {\n  rollback,\n  commit,\n  startTransaction,\n  select,\n  del\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const { method_id, zone_id } = request.params;\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    // Load the shipping zone\n    const shippingZone = await select()\n      .from('shipping_zone')\n      .where('uuid', '=', zone_id)\n      .load(connection, false);\n\n    if (!shippingZone) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid zone id'\n        }\n      });\n      return;\n    }\n\n    const zoneMethodQuery = select().from('shipping_method');\n    zoneMethodQuery\n      .innerJoin('shipping_zone_method')\n      .on(\n        'shipping_method.shipping_method_id',\n        '=',\n        'shipping_zone_method.method_id'\n      );\n    zoneMethodQuery\n      .where('shipping_zone_method.zone_id', '=', shippingZone.shipping_zone_id)\n      .and('shipping_method.uuid', '=', method_id);\n\n    const zoneMethod = await zoneMethodQuery.load(connection, false);\n    if (!zoneMethod) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid method id'\n        }\n      });\n      return;\n    }\n\n    // Delete the shipping zone method\n    await del('shipping_zone_method')\n      .where('method_id', '=', zoneMethod.shipping_method_id)\n      .and('zone_id', '=', shippingZone.shipping_zone_id)\n      .execute(connection);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: zoneMethod\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/deleteShippingZoneMethod/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/shippingZones/:zone_id/methods/:method_id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/removeCartItem/removeItem.js",
    "content": "import {\n  INVALID_PAYLOAD,\n  INTERNAL_SERVER_ERROR,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { getCartByUUID } from '../../services/getCartByUUID.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { cart_id, item_id } = request.params;\n    const cart = await getCartByUUID(cart_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          message: 'Invalid cart',\n          status: INVALID_PAYLOAD\n        }\n      });\n      return;\n    }\n\n    const item = await cart.removeItem(item_id);\n    await saveCart(cart);\n    response.status(OK);\n    response.$body = {\n      data: {\n        item: item.export()\n      }\n    };\n    next();\n  } catch (error) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        message: error.message,\n        status: INTERNAL_SERVER_ERROR\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/removeCartItem/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/cart/:cart_id/items/:item_id\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/removeMineCartItem/removeItem.ts",
    "content": "import { translate } from '../../../../lib/locale/translate/translate.js';\nimport {\n  INVALID_PAYLOAD,\n  INTERNAL_SERVER_ERROR,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { getMyCart } from '../../services/getMyCart.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request: EvershopRequest, response, next) => {\n  try {\n    const { sessionID, customer } = request.locals;\n    const myCart = await getMyCart(sessionID || ' ', customer?.customer_id);\n    const { item_id } = Array.isArray(request.params.item_id)\n      ? { item_id: request.params.item_id[0] }\n      : { item_id: request.params.item_id };\n    if (!myCart) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          message: translate('Invalid cart'),\n          status: INVALID_PAYLOAD\n        }\n      });\n      return;\n    }\n\n    const item = await myCart.removeItem(item_id, {});\n    await saveCart(myCart);\n    response.status(OK);\n    response.$body = {\n      data: {\n        item: item.export()\n      }\n    };\n    next();\n  } catch (error) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        message: error.message,\n        status: INTERNAL_SERVER_ERROR\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/removeMineCartItem/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/cart/mine/items/:item_id\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateCartItemQty/[bodyParser]updateQty.js",
    "content": "import { translate } from '../../../../lib/locale/translate/translate.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { getCartByUUID } from '../../services/getCartByUUID.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { cart_id, item_id } = request.params;\n    const cart = await getCartByUUID(cart_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          message: translate('Invalid cart'),\n          status: INVALID_PAYLOAD\n        }\n      });\n      return;\n    }\n    const { action, qty } = request.body;\n    const item = await cart.updateItemQty(item_id, qty, action, { request });\n    await saveCart(cart);\n    response.status(OK);\n    response.$body = {\n      data: {\n        item: item.export(),\n        count: cart.getItems().length,\n        cartId: cart.getData('uuid')\n      }\n    };\n    next();\n  } catch (err) {\n    error(err);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: err.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateCartItemQty/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateCartItemQty/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"action\": {\n      \"type\": \"string\",\n      \"enum\": [\"increase\", \"decrease\"]\n    },\n    \"qty\": {\n      \"type\": [\"string\", \"integer\"],\n      \"pattern\": \"^[1-9][0-9]*$\"\n    }\n  },\n  \"required\": [\"action\", \"qty\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"action\": \"Action is required. It must be either 'increase' or 'decrease'\",\n      \"qty\": \"Qty is invalid\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateCartItemQty/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/cart/:cart_id/items/:item_id\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateMineCartItemQty/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateMineCartItemQty/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"action\": {\n      \"type\": \"string\",\n      \"enum\": [\"increase\", \"decrease\"]\n    },\n    \"qty\": {\n      \"type\": [\"string\", \"integer\"],\n      \"pattern\": \"^[1-9][0-9]*$\"\n    }\n  },\n  \"required\": [\"action\", \"qty\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"action\": \"Action is required. It must be either 'increase' or 'decrease'\",\n      \"qty\": \"Qty is invalid\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateMineCartItemQty/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/cart/mine/items/:item_id\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateMineCartItemQty/updateQty.ts",
    "content": "import { translate } from '../../../../lib/locale/translate/translate.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport {\n  INVALID_PAYLOAD,\n  INTERNAL_SERVER_ERROR,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { getMyCart } from '../../services/getMyCart.js';\nimport { saveCart } from '../../services/saveCart.js';\n\nexport default async (request: EvershopRequest, response, next) => {\n  try {\n    const { item_id } = request.params;\n    const { sessionID, customer } = request.locals;\n    const cart = await getMyCart(sessionID || '', customer?.customer_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          message: translate('Invalid cart'),\n          status: INVALID_PAYLOAD\n        }\n      });\n      return;\n    }\n    const { action, qty } = request.body;\n    const item = await cart.updateItemQty(item_id, qty, action);\n    await saveCart(cart);\n    response.status(OK);\n    response.$body = {\n      data: {\n        item: item.export(),\n        count: cart.getItems().length,\n        cartId: cart.getData('uuid')\n      }\n    };\n    next();\n  } catch (err) {\n    error(err);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: err.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingMethod/[context]borderParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingMethod/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\"\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\"name\"]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingMethod/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/shippingMethods/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingMethod/updateShippingMethod.js",
    "content": "import {\n  rollback,\n  commit,\n  startTransaction,\n  select,\n  update\n} from '@evershop/postgres-query-builder';\nimport { error } from '../../../../lib/log/logger.js';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { name } = request.body;\n  const { id } = request.params;\n  try {\n    const method = await select()\n      .from('shipping_method')\n      .where('uuid', '=', id)\n      .load(connection);\n\n    if (!method) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Requested method not found'\n        }\n      });\n      return;\n    }\n\n    const newMethod = await update('shipping_method')\n      .given({\n        name\n      })\n      .where('uuid', '=', id)\n      .execute(connection);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: newMethod\n    });\n  } catch (e) {\n    error(e);\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingZone/[context]borderParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingZone/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\"\n    },\n    \"country\": {\n      \"type\": \"string\"\n    },\n    \"provinces\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\"name\", \"country\"]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingZone/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/shippingZones/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingZone/updateShippingZone.js",
    "content": "import {\n  rollback,\n  commit,\n  startTransaction,\n  insertOnUpdate,\n  del,\n  select,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const { id } = request.params;\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { name, country, provinces } = request.body;\n  try {\n    // Load the shipping zone\n    const shippingZone = await select()\n      .from('shipping_zone')\n      .where('uuid', '=', id)\n      .load(connection);\n\n    if (!shippingZone) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid zone id'\n        }\n      });\n      return;\n    }\n    const zone = await update('shipping_zone')\n      .given({\n        name,\n        country\n      })\n      .where('uuid', '=', id)\n      .execute(connection);\n\n    const zoneId = zone.updatedId;\n    if (\n      !provinces ||\n      !provinces.length ||\n      (provinces.length === 1 && provinces[0] === '')\n    ) {\n      // Delete all provinces\n      await del('shipping_zone_province')\n        .where('zone_id', '=', zoneId)\n        .execute(connection);\n    } else {\n      const provincePromises = provinces.map((province) =>\n        insertOnUpdate('shipping_zone_province', ['province'])\n          .given({\n            zone_id: zoneId,\n            province\n          })\n          .where('zone_id', '=', zoneId)\n          .execute(connection)\n      );\n      await Promise.all(provincePromises);\n      // Delete all provinces that are not in the list\n      await del('shipping_zone_province')\n        .where('zone_id', '=', zoneId)\n        .and('province', 'NOT IN', provinces)\n        .execute(connection);\n    }\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: zone\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/[context]borderParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/[validateMethod]updateShippingZoneMethod.js",
    "content": "import {\n  rollback,\n  commit,\n  startTransaction,\n  select,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const { method_id, zone_id } = request.params;\n  const connection = await getConnection();\n  await startTransaction(connection);\n  let {\n    cost,\n    condition_type,\n    calculate_api,\n    price_based_cost,\n    weight_based_cost,\n    min,\n    max\n  } = request.body;\n  const { is_enabled, calculation_type } = request.body;\n  try {\n    // Load the shipping zone\n    const shippingZone = await select()\n      .from('shipping_zone')\n      .where('uuid', '=', zone_id)\n      .load(connection, false);\n\n    if (!shippingZone) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid zone id'\n        }\n      });\n      return;\n    }\n\n    const zoneMethodQuery = select().from('shipping_method');\n    zoneMethodQuery\n      .innerJoin('shipping_zone_method')\n      .on(\n        'shipping_method.shipping_method_id',\n        '=',\n        'shipping_zone_method.method_id'\n      );\n    zoneMethodQuery\n      .where('shipping_zone_method.zone_id', '=', shippingZone.shipping_zone_id)\n      .and('shipping_method.uuid', '=', method_id);\n\n    const zoneMethod = await zoneMethodQuery.load(connection, false);\n    if (!zoneMethod) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid method id'\n        }\n      });\n      return;\n    }\n\n    if (calculation_type === 'api') {\n      cost = weight_based_cost = price_based_cost = null;\n    } else if (calculation_type === 'price_based_rate') {\n      calculate_api = cost = weight_based_cost = null;\n    } else if (calculation_type === 'weight_based_rate') {\n      calculate_api = cost = price_based_cost = null;\n    } else {\n      calculate_api = weight_based_cost = price_based_cost = null;\n    }\n    if (condition_type === 'none') {\n      condition_type = null;\n      min = max = null;\n    }\n\n    const updateZoneMethod = await update('shipping_zone_method')\n      .given({\n        cost,\n        is_enabled,\n        calculate_api,\n        condition_type,\n        price_based_cost,\n        weight_based_cost,\n        max,\n        min\n      })\n      .where('method_id', '=', zoneMethod.shipping_method_id)\n      .and('zone_id', '=', shippingZone.shipping_zone_id)\n      .execute(connection, false);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: updateZoneMethod\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/payloadSchema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"method_id\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"description\": \"The shipping method ID\",\n      \"errorMessage\": \"Shipping method ID is required\"\n    },\n    \"cost\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n      \"description\": \"Flat rate shipping cost with up to 2 decimal places\",\n      \"errorMessage\": {\n        \"type\": \"Cost must be a number or numeric string\",\n        \"pattern\": \"Cost must be a valid number with up to 2 decimal places (e.g., 10.00)\"\n      }\n    },\n    \"is_enabled\": {\n      \"type\": [\"integer\", \"string\", \"boolean\"],\n      \"enum\": [0, 1, \"0\", \"1\", true, false],\n      \"description\": \"Whether the shipping method is enabled\",\n      \"errorMessage\": {\n        \"type\": \"Status must be a boolean, 0, 1, '0', or '1'\",\n        \"enum\": \"Status must be one of: 0, 1, '0', '1', true, or false\"\n      }\n    },\n    \"calculation_type\": {\n      \"type\": \"string\",\n      \"enum\": [\"flat_rate\", \"price_based_rate\", \"weight_based_rate\", \"api\"],\n      \"description\": \"The type of calculation to use for shipping cost\",\n      \"errorMessage\": {\n        \"type\": \"Calculation type must be a string\",\n        \"enum\": \"Calculation type must be one of: flat_rate, price_based_rate, weight_based_rate, or api\"\n      }\n    },\n    \"price_based_cost\": {\n      \"type\": \"array\",\n      \"minItems\": 1,\n      \"description\": \"Array of price tiers for price-based shipping calculation\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"min_price\": {\n            \"type\": [\"string\", \"number\"],\n            \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n            \"description\": \"Minimum order price for this tier\",\n            \"errorMessage\": {\n              \"type\": \"Minimum price must be a number or numeric string\",\n              \"pattern\": \"Minimum price must be a valid number with up to 2 decimal places\"\n            }\n          },\n          \"cost\": {\n            \"type\": [\"string\", \"number\"],\n            \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n            \"description\": \"Shipping cost for this price tier\",\n            \"errorMessage\": {\n              \"type\": \"Cost must be a number or numeric string\",\n              \"pattern\": \"Cost must be a valid number with up to 2 decimal places\"\n            }\n          }\n        },\n        \"additionalProperties\": false,\n        \"required\": [\"min_price\", \"cost\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"min_price\": \"Minimum price is required for each price tier\",\n            \"cost\": \"Cost is required for each price tier\"\n          }\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Price based cost must be an array\",\n        \"minItems\": \"At least one price tier is required for price-based calculation\"\n      }\n    },\n    \"weight_based_cost\": {\n      \"type\": \"array\",\n      \"minItems\": 1,\n      \"description\": \"Array of weight tiers for weight-based shipping calculation\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"min_weight\": {\n            \"type\": [\"string\", \"number\"],\n            \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n            \"description\": \"Minimum order weight for this tier\",\n            \"errorMessage\": {\n              \"type\": \"Minimum weight must be a number or numeric string\",\n              \"pattern\": \"Minimum weight must be a valid number with up to 2 decimal places\"\n            }\n          },\n          \"cost\": {\n            \"type\": [\"string\", \"number\"],\n            \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n            \"description\": \"Shipping cost for this weight tier\",\n            \"errorMessage\": {\n              \"type\": \"Cost must be a number or numeric string\",\n              \"pattern\": \"Cost must be a valid number with up to 2 decimal places\"\n            }\n          }\n        },\n        \"additionalProperties\": false,\n        \"required\": [\"min_weight\", \"cost\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"min_weight\": \"Minimum weight is required for each weight tier\",\n            \"cost\": \"Cost is required for each weight tier\"\n          }\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Weight based cost must be an array\",\n        \"minItems\": \"At least one weight tier is required for weight-based calculation\"\n      }\n    },\n    \"condition_type\": {\n      \"type\": \"string\",\n      \"enum\": [\"weight\", \"price\", \"none\"],\n      \"description\": \"The type of condition to apply for this shipping method\",\n      \"errorMessage\": {\n        \"type\": \"Condition type must be a string\",\n        \"enum\": \"Condition type must be one of: weight, price, or none\"\n      }\n    },\n    \"calculate_api\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"description\": \"API endpoint URL for calculating shipping cost\",\n      \"errorMessage\": {\n        \"type\": \"Calculate API must be a string\",\n        \"minLength\": \"Calculate API cannot be empty\"\n      }\n    },\n    \"min\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n      \"description\": \"Minimum order price or weight for condition\",\n      \"errorMessage\": {\n        \"type\": \"Minimum value must be a number or numeric string\",\n        \"pattern\": \"Minimum value must be a valid number with up to 2 decimal places\"\n      }\n    },\n    \"max\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n      \"description\": \"Maximum order price or weight for condition\",\n      \"errorMessage\": {\n        \"type\": \"Maximum value must be a number or numeric string\",\n        \"pattern\": \"Maximum value must be a valid number with up to 2 decimal places\"\n      }\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\"condition_type\", \"calculation_type\"],\n  \"errorMessage\": {\n    \"required\": {\n      \"condition_type\": \"Condition type is required\",\n      \"calculation_type\": \"Calculation type is required\"\n    }\n  },\n  \"allOf\": [\n    {\n      \"if\": {\n        \"properties\": {\n          \"calculation_type\": { \"const\": \"flat_rate\" }\n        }\n      },\n      \"then\": {\n        \"required\": [\"cost\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"cost\": \"Cost is required when calculation type is flat_rate\"\n          }\n        }\n      }\n    },\n    {\n      \"if\": {\n        \"properties\": {\n          \"calculation_type\": { \"const\": \"price_based_rate\" }\n        }\n      },\n      \"then\": {\n        \"required\": [\"price_based_cost\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"price_based_cost\": \"Price based cost tiers are required when calculation type is price_based_rate\"\n          }\n        }\n      }\n    },\n    {\n      \"if\": {\n        \"properties\": {\n          \"calculation_type\": { \"const\": \"weight_based_rate\" }\n        }\n      },\n      \"then\": {\n        \"required\": [\"weight_based_cost\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"weight_based_cost\": \"Weight based cost tiers are required when calculation type is weight_based_rate\"\n          }\n        }\n      }\n    },\n    {\n      \"if\": {\n        \"properties\": {\n          \"calculation_type\": { \"const\": \"api\" }\n        }\n      },\n      \"then\": {\n        \"required\": [\"calculate_api\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"calculate_api\": \"Calculate API URL is required when calculation type is api\"\n          }\n        }\n      }\n    },\n    {\n      \"if\": {\n        \"properties\": {\n          \"condition_type\": { \"enum\": [\"weight\", \"price\"] }\n        }\n      },\n      \"then\": {\n        \"required\": [\"min\", \"max\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"min\": \"Minimum value is required when using weight or price conditions\",\n            \"max\": \"Maximum value is required when using weight or price conditions\"\n          }\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/shippingZones/:zone_id/methods/:method_id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/validateMethod.js",
    "content": "import validate from '../addShippingZoneMethod/validateMethod.js';\n\nexport default async (request, response, next) =>\n  validate(request, response, next);\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/bootstrap.ts",
    "content": "import { error } from '../../lib/log/logger.js';\nimport { pool } from '../../lib/postgres/connection.js';\nimport { merge } from '../../lib/util/merge.js';\nimport { addFinalProcessor, addProcessor } from '../../lib/util/registry.js';\nimport { getProductsBaseQuery } from '../catalog/services/getProductsBaseQuery.js';\nimport { registerCartBaseFields } from '../checkout/services/cart/registerCartBaseFields.js';\nimport { registerCartItemBaseFields } from './services/cart/registerCartItemBaseFields.js';\nimport { sortFields } from './services/cart/sortFields.js';\n\nexport default () => {\n  addProcessor('cartFields', registerCartBaseFields, 0);\n\n  addProcessor('cartItemFields', registerCartItemBaseFields, 0);\n\n  addFinalProcessor('cartFields', (fields) => {\n    try {\n      const sortedFields = sortFields(fields);\n      return sortedFields;\n    } catch (e) {\n      error(e);\n      throw e;\n    }\n  });\n\n  addFinalProcessor('cartItemFields', (fields) => {\n    try {\n      const sortedFields = sortFields(fields);\n      return sortedFields;\n    } catch (e) {\n      error(e);\n      throw e;\n    }\n  });\n\n  addProcessor('cartItemProductLoaderFunction', () => async (id) => {\n    const productQuery = getProductsBaseQuery();\n    const product = await productQuery.where('product_id', '=', id).load(pool);\n    return product;\n  });\n\n  addProcessor('configurationSchema', (schema) => {\n    merge(schema, {\n      properties: {\n        checkout: {\n          type: 'object',\n          properties: {\n            showShippingNote: {\n              type: 'boolean'\n            }\n          }\n        }\n      }\n    });\n    return schema;\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/Cart/Cart.graphql",
    "content": "\"\"\"\nShopping Cart interface\n\"\"\"\ninterface ShoppingCart {\n  uuid: String!\n  currency: String!\n  customerId: Int\n  customerGroupId: Int\n  customerEmail: String\n  customerFullName: String\n  userIp: String\n  userId: String\n  coupon: String\n  noShippingRequired: Boolean!\n  shippingFeeExclTax: Price!\n  shippingFeeInclTax: Price!\n  shippingTaxAmount: Price!\n  discountAmount: Price!\n  subTotal: Price!\n  subTotalInclTax: Price!\n  subTotalWithDiscount: Price!\n  subTotalWithDiscountInclTax: Price!\n  totalQty: Int!\n  totalWeight: Weight!\n  taxAmount: Price!\n  taxAmountBeforeDiscount: Price!\n  totalTaxAmount: Price!\n  grandTotal: Price!\n  shippingMethod: String\n  shippingMethodName: String\n  shippingAddress: Address\n  paymentMethod: String\n  paymentMethodName: String\n  billingAddress: Address\n  shippingNote: String\n  createdAt: Date!\n  updatedAt: Date!\n}\n\n\"\"\"\nShopping Cart Item interface\n\"\"\"\ninterface ShoppingCartItem {\n  uuid: String!\n  productId: ID!\n  productSku: String!\n  productName: String\n  thumbnail: String\n  noShippingRequired: Boolean!\n  productWeight: Weight!\n  productPrice: Price!\n  productPriceInclTax: Price!\n  qty: Int!\n  finalPrice: Price!\n  finalPriceInclTax: Price!\n  taxPercent: Float!\n  taxAmount: Price!\n  taxAmountBeforeDiscount: Price!\n  discountAmount: Price!\n  lineTotal: Price!\n  subTotal: Price! # Deprecated, this field is equivalent to lineTotal\n  lineTotalWithDiscount: Price!\n  lineTotalWithDiscountInclTax: Price!\n  lineTotalInclTax: Price!\n  total: Price! # Deprecated, this field is equivalent to lineTotalInclTax\n  variantGroupId: Int\n  variantOptions: [VariantOption]\n  productCustomOptions: String\n  productUrl: String\n}\n\n\"\"\"\nAddress interface\n\"\"\"\ninterface Address {\n  fullName: String\n  postcode: String\n  telephone: String\n  country: Country\n  province: Province\n  city: String\n  address1: String\n  address2: String\n}\n\n\"\"\"\nRepresent a Cart Address\n\"\"\"\ntype CartAddress implements Address {\n  cartAddressId: Int!\n  uuid: String!\n  fullName: String\n  postcode: String\n  telephone: String\n  country: Country\n  province: Province\n  city: String\n  address1: String\n  address2: String\n}\n\ntype VariantOption {\n  attributeCode: String!\n  attributeName: String!\n  attributeId: Int!\n  optionId: Int!\n  optionText: String!\n}\n\"\"\"\nRepresent a Cart Item\n\"\"\"\ntype CartItem implements ShoppingCartItem {\n  cartItemId: ID\n  uuid: String!\n  cartId: ID!\n  removeApi: String!\n  updateQtyApi: String!\n  productId: ID!\n  productSku: String!\n  productName: String\n  thumbnail: String\n  noShippingRequired: Boolean!\n  productWeight: Weight!\n  productPrice: Price!\n  productPriceInclTax: Price!\n  qty: Int!\n  finalPrice: Price!\n  finalPriceInclTax: Price!\n  taxPercent: Float!\n  taxAmount: Price!\n  taxAmountBeforeDiscount: Price!\n  discountAmount: Price!\n  lineTotal: Price!\n  subTotal: Price! # Deprecated, this field is equivalent to lineTotal\n  lineTotalWithDiscount: Price!\n  lineTotalWithDiscountInclTax: Price!\n  lineTotalInclTax: Price!\n  total: Price! # Deprecated, this field is equivalent to lineTotalInclTax\n  variantGroupId: Int\n  variantOptions: [VariantOption]\n  productCustomOptions: String\n  productUrl: String\n  errors: [String!]\n}\n\n\"\"\"\nRepresent a Cart\n\"\"\"\ntype Cart implements ShoppingCart {\n  cartId: ID!\n  uuid: String!\n  items: [CartItem]\n  noShippingRequired: Boolean!\n  shippingAddress: CartAddress\n  billingAddress: CartAddress\n  currency: String!\n  customerId: Int\n  customerGroupId: Int\n  customerEmail: String\n  customerFullName: String\n  userIp: String\n  userId: String\n  status: Int!\n  coupon: String\n  shippingFeeExclTax: Price!\n  shippingFeeInclTax: Price!\n  shippingTaxAmount: Price!\n  discountAmount: Price!\n  subTotal: Price!\n  subTotalInclTax: Price!\n  subTotalWithDiscount: Price!\n  subTotalWithDiscountInclTax: Price!\n  totalQty: Int!\n  totalWeight: Weight!\n  taxAmount: Price!\n  taxAmountBeforeDiscount: Price!\n  totalTaxAmount: Price!\n  grandTotal: Price!\n  shippingMethod: String\n  shippingMethodName: String\n  paymentMethod: String\n  paymentMethodName: String\n  shippingNote: String\n  addItemApi: String!\n  addPaymentMethodApi: String!\n  addShippingMethodApi: String!\n  addContactInfoApi: String!\n  addAddressApi: String!\n  addNoteApi: String!\n  checkoutApi: String!\n  createdAt: Date!\n  updatedAt: Date!\n}\n\nextend type Query {\n  cart(id: String!): Cart\n  myCart: Cart\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/Cart/Cart.resolvers.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { debug } from '../../../../../lib/log/logger.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { getFrontStoreSessionCookieName } from '../../../../auth/services/getFrontStoreSessionCookieName.js';\nimport { getMyCart } from '../../../../checkout/services/getMyCart.js';\nimport { getCartByUUID } from '../../../services/getCartByUUID.js';\n\nexport default {\n  Query: {\n    cart: async (_, { id }) => {\n      try {\n        const cart = await getCartByUUID(id);\n        return camelCase(cart.exportData());\n      } catch (error) {\n        return null;\n      }\n    },\n    myCart: async (_, __, { signedCookies, customer }) => {\n      try {\n        const storeCookieName = getFrontStoreSessionCookieName();\n        // Check if the sessionID cookie is present\n        const sessionID = signedCookies[storeCookieName];\n        const cart = await getMyCart(sessionID, customer?.customer_id);\n        return cart ? camelCase(cart.exportData()) : null;\n      } catch (error) {\n        debug('Error in checkout resolver:');\n        debug(error);\n        return null;\n      }\n    }\n  },\n  Cart: {\n    items: async (cart) => {\n      const items = cart.items || [];\n      return items.map((item) => ({\n        ...camelCase(item),\n        removeApi: buildUrl('removeCartItem', {\n          item_id: item.uuid,\n          cart_id: cart.uuid\n        }),\n        updateQtyApi: buildUrl('updateCartItemQty', {\n          cart_id: cart.uuid,\n          item_id: item.uuid\n        })\n      }));\n    },\n    shippingAddress: async ({ shippingAddressId }, _, { pool }) => {\n      const address = await select()\n        .from('cart_address')\n        .where('cart_address_id', '=', shippingAddressId)\n        .load(pool);\n      return address ? camelCase(address) : null;\n    },\n    billingAddress: async ({ billingAddressId }, _, { pool }) => {\n      const address = await select()\n        .from('cart_address')\n        .where('cart_address_id', '=', billingAddressId)\n        .load(pool);\n      return address ? camelCase(address) : null;\n    },\n    addItemApi: (cart) => buildUrl('addCartItem', { cart_id: cart.uuid }),\n    addPaymentMethodApi: (cart) =>\n      buildUrl('addCartPaymentMethod', { cart_id: cart.uuid }),\n    addShippingMethodApi: (cart) =>\n      buildUrl('addCartShippingMethod', { cart_id: cart.uuid }),\n    addContactInfoApi: (cart) =>\n      buildUrl('addCartContactInfo', { cart_id: cart.uuid }),\n    addAddressApi: (cart) => buildUrl('addCartAddress', { cart_id: cart.uuid }),\n    addNoteApi: (cart) => buildUrl('addShippingNote', { cart_id: cart.uuid }),\n    checkoutApi: (cart) => buildUrl('cartCheckout', { cart_id: cart.uuid })\n  },\n  CartItem: {\n    thumbnail: ({ thumbnail }) => {\n      return thumbnail;\n    },\n    total: ({ lineTotalInclTax }) =>\n      // This field is deprecated, use lineTotalInclTax instead\n      lineTotalInclTax,\n    subTotal: ({ lineTotal }) =>\n      // This field is deprecated, use lineTotal instead\n      lineTotal,\n    variantOptions: ({ variantOptions }) => {\n      try {\n        return JSON.parse(variantOptions || '[]').map((option) => ({\n          ...camelCase(option),\n          attributeId: parseInt(option.attribute_id, 10),\n          optionId: parseInt(option.option_id, 10)\n        }));\n      } catch (error) {\n        return [];\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/CheckoutSetting/CheckoutSetting.graphql",
    "content": "extend type Setting {\n  showShippingNote: Boolean\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/CheckoutSetting/CheckoutSetting.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Setting: {\n    showShippingNote: () => getConfig('checkout.showShippingNote', true)\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/Date/Date.graphql",
    "content": "\"\"\"\nA date field.\n\"\"\"\ntype Date {\n  value: String\n  text: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/Date/Date.resolvers.js",
    "content": "import dayjs from 'dayjs';\n\nexport default {\n  Date: {\n    value: (raw) => raw,\n    text: (raw) => dayjs(raw).format('MMM D, YYYY')\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/PaymentMethod/AvailablePaymentMethod.graphql",
    "content": "\"\"\"\nRepresents an available payment method for a cart based on the payment details.\n\"\"\"\ntype AvailablePaymentMethod {\n  code: String!\n  name: String!\n}\n\nextend type Cart {\n  availablePaymentMethods: [AvailablePaymentMethod]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/PaymentMethod/AvailablePaymentMethod.resolvers.ts",
    "content": "import { getAvailablePaymentMethods } from '../../../services/index.js';\n\nexport default {\n  Cart: {\n    availablePaymentMethods: async () => {\n      const methods = await getAvailablePaymentMethods();\n      return methods;\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/Price/Price.graphql",
    "content": "\"\"\"\nRepresents a price value.\n\"\"\"\ntype Price {\n  value: Float!\n  currency(currency: String): String!\n  text(currency: String): String!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/Price/Price.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Price: {\n    value: (rawPrice) => parseFloat(rawPrice), // TODO: Format for decimal value?\n    currency: async (_, { currency }) => {\n      const curr = currency || getConfig('shop.currency', 'USD');\n      return curr;\n    },\n    text: async (rawPrice, { currency }) => {\n      const price = parseFloat(rawPrice); // TODO: Format for decimal value?\n      const curr = currency || getConfig('shop.currency', 'USD');\n      const language = getConfig('shop.language', 'en');\n      return new Intl.NumberFormat(language, {\n        style: 'currency',\n        currency: curr\n      }).format(price);\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/ShippingMethod/AvailableShippingMethod.graphql",
    "content": "\"\"\"\nRepresents an available shipping method for a cart based on the shipping address.\n\"\"\"\ntype AvailableShippingMethod {\n  id: String!\n  code: String!\n  name: String!\n  cost: Price!\n}\n\nextend type Cart {\n  availableShippingMethods(\n    country: String\n    province: String\n    postcode: String\n  ): [AvailableShippingMethod]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/ShippingMethod/AvailableShippingMethod.resolvers.ts",
    "content": "import { getAvailableShippingMethods } from '../../../services/getAvailableShippingMethods.js';\n\nexport default {\n  Cart: {\n    availableShippingMethods: async (\n      { uuid },\n      { country, province, postcode }\n    ) => {\n      const methods = await getAvailableShippingMethods(\n        uuid,\n        country,\n        province,\n        postcode\n      );\n      return methods;\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/ShippingMethod/ShippingMethod.admin.graphql",
    "content": "\"\"\"\nRepresents a shipping method.\n\"\"\"\ntype ShippingMethod {\n  shippingMethodId: Int!\n  uuid: String!\n  name: String!\n  updateApi: String!\n}\n\nextend type Query {\n  shippingMethods: [ShippingMethod]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/ShippingMethod/ShippingMethod.admin.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\n\nexport default {\n  Query: {\n    shippingMethods: async () => {\n      const shippingMethods = await select()\n        .from('shipping_method')\n        .orderBy('shipping_method_id', 'DESC')\n        .execute(pool);\n      return shippingMethods.map((row) => camelCase(row));\n    }\n  },\n  ShippingMethod: {\n    updateApi: ({ uuid }) => buildUrl('updateShippingMethod', { id: uuid })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/ShippingZone/ShippingZone.graphql",
    "content": "\"\"\"\nRepresents a price based cost\n\"\"\"\ntype PriceBasedCostItem {\n  minPrice: Price!\n  cost: Price!\n}\n\n\"\"\"\nRepresents a weight based cost\n\"\"\"\ntype WeightBasedCostItem {\n  minWeight: Price!\n  cost: Price!\n}\n\n\"\"\"\nRepresents a shipping method.\n\"\"\"\ntype ShippingMethodByZone {\n  methodId: Int!\n  zoneId: Int!\n  uuid: String!\n  name: String!\n  cost: Price\n  priceBasedCost: [PriceBasedCostItem]\n  weightBasedCost: [WeightBasedCostItem]\n  isEnabled: Boolean!\n  calculateApi: String\n  conditionType: String\n  max: Float\n  min: Float\n  updateApi: String!\n  deleteApi: String!\n}\n\n\"\"\"\nRepresents a shipping zone.\n\"\"\"\ntype ShippingZone {\n  shipping_zone_id: Int!\n  uuid: String!\n  name: String!\n  country: Country\n  provinces: [Province]\n  methods: [ShippingMethodByZone]\n  updateApi: String!\n  deleteApi: String!\n  addMethodApi: String!\n  removeMethodApi: String!\n}\n\nextend type Query {\n  shippingZones: [ShippingZone]\n  shippingZone(id: String!): ShippingZone\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/ShippingZone/ShippingZone.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { countries } from '../../../../../lib/locale/countries.js';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\n\nexport default {\n  Query: {\n    shippingZones: async () => {\n      const shippingZones = await select()\n        .from('shipping_zone')\n        .orderBy('shipping_zone_id', 'DESC')\n        .execute(pool);\n      // Parse the provinces field into an array\n      return shippingZones.map((row) => camelCase(row));\n    },\n    shippingZone: async (_, { id }) => {\n      const shippingZone = await select()\n        .from('shipping_zone')\n        .where('uuid', '=', id)\n        .load(pool);\n      return camelCase(shippingZone);\n    }\n  },\n  ShippingZone: {\n    methods: async (parent) => {\n      const query = select().from('shipping_zone_method');\n      query\n        .innerJoin('shipping_method')\n        .on(\n          'shipping_method.shipping_method_id',\n          '=',\n          'shipping_zone_method.method_id'\n        );\n\n      query.where('zone_id', '=', parent.shippingZoneId);\n      const methods = await query.execute(pool);\n      return methods.map((row) => camelCase(row));\n    },\n    country: ({ country }) => {\n      if (!country) {\n        return null;\n      } else {\n        const c = countries.find((p) => p.code === country);\n        if (c) {\n          return c;\n        } else {\n          return null;\n        }\n      }\n    },\n    provinces: async ({ shippingZoneId }) => {\n      const provinces = await select('province')\n        .from('shipping_zone_province')\n        .where('zone_id', '=', shippingZoneId)\n        .execute(pool);\n      return provinces.map((row) => row.province);\n    },\n    updateApi: ({ uuid }) => buildUrl('updateShippingZone', { id: uuid }),\n    deleteApi: ({ uuid }) => buildUrl('deleteShippingZone', { id: uuid }),\n    addMethodApi: ({ uuid }) => buildUrl('addShippingZoneMethod', { id: uuid }),\n    removeMethodApi: ({ uuid }) =>\n      buildUrl('removeShippingZoneMethod', { id: uuid })\n  },\n  ShippingMethodByZone: {\n    updateApi: async ({ uuid, zoneId }) => {\n      const zone = await select()\n        .from('shipping_zone')\n        .where('shipping_zone_id', '=', zoneId)\n        .load(pool);\n\n      return buildUrl('updateShippingZoneMethod', {\n        zone_id: zone.uuid,\n        method_id: uuid\n      });\n    },\n    deleteApi: async ({ uuid, zoneId }) => {\n      const zone = await select()\n        .from('shipping_zone')\n        .where('shipping_zone_id', '=', zoneId)\n        .load(pool);\n\n      return buildUrl('deleteShippingZoneMethod', {\n        zone_id: zone.uuid,\n        method_id: uuid\n      });\n    }\n  },\n  WeightBasedCostItem: {\n    minWeight: ({ min_weight }) => min_weight\n  },\n  PriceBasedCostItem: {\n    minPrice: ({ min_price }) => min_price\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/Weight/Weight.graphql",
    "content": "\"\"\"\nRepresents a weight value.\n\"\"\"\ntype Weight {\n  value: Float!\n  unit: String!\n  text: String!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/graphql/types/Weight/Weight.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Weight: {\n    value: (raw) => parseFloat(raw),\n    unit: () => {\n      const unit = getConfig('shop.weightUnit', 'kg');\n      return unit;\n    },\n    text: (raw) => {\n      const weight = parseFloat(raw);\n      const unit = getConfig('shop.weightUnit', 'kg');\n      // Localize the weight\n      return `${weight} ${unit}`;\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/migration/Version-1.0.0.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE \"cart\" (\n  \"cart_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"sid\" varchar DEFAULT NULL,\n  \"currency\" varchar NOT NULL,\n  \"customer_id\" INT DEFAULT NULL,\n  \"customer_group_id\" smallint DEFAULT NULL,\n  \"customer_email\" varchar DEFAULT NULL,\n  \"customer_full_name\" varchar DEFAULT NULL,\n  \"user_ip\" varchar DEFAULT NULL,\n  \"status\" boolean NOT NULL DEFAULT FALSE,\n  \"coupon\" varchar DEFAULT NULL,\n  \"shipping_fee_excl_tax\" decimal(12,4) DEFAULT NULL,\n  \"shipping_fee_incl_tax\" decimal(12,4) DEFAULT NULL,\n  \"discount_amount\" decimal(12,4) DEFAULT NULL,\n  \"sub_total\" decimal(12,4) NOT NULL,\n  \"sub_total_incl_tax\" decimal(12,4) NOT NULL,\n  \"sub_total_with_discount\" decimal(12,4) NOT NULL,\n  \"sub_total_with_discount_incl_tax\" decimal(12,4) NOT NULL,\n  \"total_qty\" INT NOT NULL,\n  \"total_weight\" decimal(12,4) DEFAULT NULL,\n  \"tax_amount\" decimal(12,4) NOT NULL,\n  \"tax_amount_before_discount\" decimal(12,4) NOT NULL,\n  \"shipping_tax_amount\" decimal(12,4) NOT NULL,\n  \"grand_total\" decimal(12,4) NOT NULL,\n  \"shipping_method\" varchar DEFAULT NULL,\n  \"shipping_method_name\" varchar DEFAULT NULL,\n  \"shipping_zone_id\" INT DEFAULT NULL,\n  \"shipping_address_id\" INT DEFAULT NULL,\n  \"payment_method\" varchar DEFAULT NULL,\n  \"payment_method_name\" varchar DEFAULT NULL,\n  \"billing_address_id\" INT DEFAULT NULL,\n  \"shipping_note\" text DEFAULT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"CART_UUID_UNIQUE\" UNIQUE (\"uuid\")\n)`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"cart_address\" (\n  \"cart_address_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"full_name\" varchar DEFAULT NULL,\n  \"postcode\" varchar DEFAULT NULL,\n  \"telephone\" varchar DEFAULT NULL,\n  \"country\" varchar DEFAULT NULL,\n  \"province\" varchar DEFAULT NULL,\n  \"city\" varchar DEFAULT NULL,\n  \"address_1\" varchar DEFAULT NULL,\n  \"address_2\" varchar DEFAULT NULL,\n  CONSTRAINT \"CART_ADDRESS_UUID_UNIQUE\" UNIQUE (\"uuid\")\n)\n`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"cart_item\" (\n  \"cart_item_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"cart_id\" INT NOT NULL,\n  \"product_id\" INT NOT NULL,\n  \"product_sku\" varchar NOT NULL,\n  \"product_name\" text NOT NULL,\n  \"thumbnail\" varchar DEFAULT NULL,\n  \"product_weight\" decimal(12,4) DEFAULT NULL,\n  \"product_price\" decimal(12,4) NOT NULL,\n  \"product_price_incl_tax\" decimal(12,4) NOT NULL,\n  \"qty\" INT NOT NULL,\n  \"final_price\" decimal(12,4) NOT NULL,\n  \"final_price_incl_tax\" decimal(12,4) NOT NULL,\n  \"tax_percent\" decimal(12,4) NOT NULL,\n  \"tax_amount\" decimal(12,4) NOT NULL,\n  \"tax_amount_before_discount\" decimal(12,4) NOT NULL,\n  \"discount_amount\" decimal(12,4) NOT NULL,\n  \"sub_total\" decimal(12,4) NOT NULL,\n  \"line_total_with_discount\" decimal(12,4) NOT NULL,\n  \"total\" decimal(12,4) NOT NULL,\n  \"line_total_with_discount_incl_tax\" decimal(12,4) NOT NULL,\n  \"variant_group_id\" INT DEFAULT NULL,\n  \"variant_options\" text DEFAULT NULL,\n  \"product_custom_options\" text DEFAULT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"CART_ITEM_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_CART_ITEM\" FOREIGN KEY (\"cart_id\") REFERENCES \"cart\" (\"cart_id\") ON DELETE CASCADE ON UPDATE CASCADE,\n  CONSTRAINT \"FK_CART_ITEM_PRODUCT\" FOREIGN KEY (\"product_id\") REFERENCES \"product\" (\"product_id\") ON DELETE CASCADE ON UPDATE NO ACTION\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_CART_ITEM\" ON \"cart_item\" (\"cart_id\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_CART_ITEM_PRODUCT\" ON \"cart_item\" (\"product_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"order\" (\n  \"order_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"integration_order_id\" varchar DEFAULT NULL,\n  \"sid\" varchar DEFAULT NULL,\n  \"order_number\" varchar NOT NULL,\n  \"status\" varchar NOT NULL DEFAULT NULL,\n  \"cart_id\" INT NOT NULL,\n  \"currency\" varchar NOT NULL,\n  \"customer_id\" INT DEFAULT NULL,\n  \"customer_email\" varchar DEFAULT NULL,\n  \"customer_full_name\" varchar DEFAULT NULL,\n  \"user_ip\" varchar DEFAULT NULL,\n  \"user_agent\" varchar DEFAULT NULL,\n  \"coupon\" varchar DEFAULT NULL,\n  \"shipping_fee_excl_tax\" decimal(12,4) DEFAULT NULL,\n  \"shipping_fee_incl_tax\" decimal(12,4) DEFAULT NULL,\n  \"discount_amount\" decimal(12,4) DEFAULT NULL,\n  \"sub_total\" decimal(12,4) NOT NULL,\n  \"sub_total_incl_tax\" decimal(12,4) NOT NULL,\n  \"sub_total_with_discount\" decimal(12,4) NOT NULL,\n  \"sub_total_with_discount_incl_tax\" decimal(12,4) NOT NULL,\n  \"total_qty\" INT NOT NULL,\n  \"total_weight\" decimal(12,4) DEFAULT NULL,\n  \"tax_amount\" decimal(12,4) NOT NULL,\n  \"tax_amount_before_discount\" decimal(12,4) NOT NULL,\n  \"shipping_tax_amount\" decimal(12,4) NOT NULL,\n  \"shipping_note\" text DEFAULT NULL,\n  \"grand_total\" decimal(12,4) NOT NULL,\n  \"shipping_method\" varchar DEFAULT NULL,\n  \"shipping_method_name\" varchar DEFAULT NULL,\n  \"shipping_address_id\" INT DEFAULT NULL,\n  \"payment_method\" varchar DEFAULT NULL,\n  \"payment_method_name\" varchar DEFAULT NULL,\n  \"billing_address_id\" INT DEFAULT NULL,\n  \"shipment_status\" varchar NOT NULL DEFAULT '0',\n  \"payment_status\" varchar NOT NULL DEFAULT '0',\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"ORDER_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"ORDER_NUMBER_UNIQUE\" UNIQUE (\"order_number\")\n)\n`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"order_activity\" (\n  \"order_activity_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"order_activity_order_id\" INT NOT NULL,\n  \"comment\" text NOT NULL,\n  \"customer_notified\" boolean NOT NULL DEFAULT FALSE,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"ORDER_ACTIVITY_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_ORDER_ACTIVITY\" FOREIGN KEY (\"order_activity_order_id\") REFERENCES \"order\" (\"order_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ORDER_ACTIVITY\" ON \"order_activity\" (\"order_activity_order_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"order_address\" (\n  \"order_address_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"full_name\" varchar DEFAULT NULL,\n  \"postcode\" varchar DEFAULT NULL,\n  \"telephone\" varchar DEFAULT NULL,\n  \"country\" varchar DEFAULT NULL,\n  \"province\" varchar DEFAULT NULL,\n  \"city\" varchar DEFAULT NULL,\n  \"address_1\" varchar DEFAULT NULL,\n  \"address_2\" varchar DEFAULT NULL,\n  CONSTRAINT \"ORDER_ADDRESS_UUID_UNIQUE\" UNIQUE (\"uuid\")\n)`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"order_item\" (\n  \"order_item_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"order_item_order_id\" INT NOT NULL,\n  \"product_id\" INT NOT NULL,\n  \"referer\" INT DEFAULT NULL,\n  \"product_sku\" varchar NOT NULL,\n  \"product_name\" text NOT NULL,\n  \"thumbnail\" varchar DEFAULT NULL,\n  \"product_weight\" decimal(12,4) DEFAULT NULL,\n  \"product_price\" decimal(12,4) NOT NULL,\n  \"product_price_incl_tax\" decimal(12,4) NOT NULL,\n  \"qty\" INT NOT NULL,\n  \"final_price\" decimal(12,4) NOT NULL,\n  \"final_price_incl_tax\" decimal(12,4) NOT NULL,\n  \"tax_percent\" decimal(12,4) NOT NULL,\n  \"tax_amount\" decimal(12,4) NOT NULL,\n  \"tax_amount_before_discount\" decimal(12,4) NOT NULL,\n  \"discount_amount\" decimal(12,4) NOT NULL,\n  \"sub_total\" decimal(12,4) NOT NULL,\n  \"line_total_with_discount\" decimal(12,4) NOT NULL,\n  \"total\" decimal(12,4) NOT NULL,\n  \"line_total_with_discount_incl_tax\" decimal(12,4) NOT NULL,\n  \"variant_group_id\" INT DEFAULT NULL,\n  \"variant_options\" text DEFAULT NULL,\n  \"product_custom_options\" text DEFAULT NULL,\n  \"requested_data\" text DEFAULT NULL,\n  CONSTRAINT \"ORDER_ITEM_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_ORDER\" FOREIGN KEY (\"order_item_order_id\") REFERENCES \"order\" (\"order_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ORDER\" ON \"order_item\" (\"order_item_order_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"payment_transaction\" (\n  \"payment_transaction_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"payment_transaction_order_id\" INT NOT NULL,\n  \"transaction_id\" varchar DEFAULT NULL,\n  \"transaction_type\" varchar NOT NULL,\n  \"amount\" decimal(12,4) NOT NULL,\n  \"parent_transaction_id\" varchar DEFAULT NULL,\n  \"payment_action\" varchar DEFAULT NULL,\n  \"additional_information\" text DEFAULT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"PAYMENT_TRANSACTION_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"UNQ_PAYMENT_TRANSACTION_ID_ORDER_ID\" UNIQUE (\"payment_transaction_order_id\",\"transaction_id\"),\n  CONSTRAINT \"FK_PAYMENT_TRANSACTION_ORDER\" FOREIGN KEY (\"payment_transaction_order_id\") REFERENCES \"order\" (\"order_id\") ON DELETE CASCADE ON UPDATE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_PAYMENT_TRANSACTION_ORDER\" ON \"payment_transaction\" (\"payment_transaction_order_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"shipment\" (\n  \"shipment_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"shipment_order_id\" INT NOT NULL,\n  \"carrier_name\" varchar DEFAULT NULL,\n  \"tracking_number\" varchar DEFAULT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"SHIPMENT_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_ORDER_SHIPMENT\" FOREIGN KEY (\"shipment_order_id\") REFERENCES \"order\" (\"order_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ORDER_SHIPMENT\" ON \"shipment\" (\"shipment_order_id\")`\n  );\n\n  // Reduce product stock when order is placed if product manage stock is true\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION reduce_product_stock_when_order_placed()\n        RETURNS TRIGGER \n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        UPDATE product SET qty = qty - NEW.qty WHERE product_id = NEW.product_id AND manage_stock = TRUE;\n        RETURN NEW;\n      END\n      $$;`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"TRIGGER_AFTER_INSERT_ORDER_ITEM\" AFTER INSERT ON \"order_item\" FOR EACH ROW\n    EXECUTE PROCEDURE reduce_product_stock_when_order_placed();\n    `\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/migration/Version-1.0.1.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE \"shipping_zone\" (\n  \"shipping_zone_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"name\" varchar NOT NULL,\n  \"country\" varchar NOT NULL,\n  CONSTRAINT \"SHIPPING_ZONE_UUID_UNIQUE\" UNIQUE (\"uuid\")\n)\n`\n  );\n\n  // Add foreign key from table cart (shipping_zone_id) to table shipping_zone (shipping_zone_id)\n  await execute(\n    connection,\n    `ALTER TABLE \"cart\" ADD CONSTRAINT \"FK_CART_SHIPPING_ZONE\" FOREIGN KEY (\"shipping_zone_id\") REFERENCES \"shipping_zone\" (\"shipping_zone_id\") ON DELETE SET NULL`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_CART_SHIPPING_ZONE\" ON \"cart\" (\"shipping_zone_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"shipping_zone_province\" (\n  \"shipping_zone_province_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"zone_id\" INT NOT NULL,\n  \"province\" varchar NOT NULL,\n  CONSTRAINT \"SHIPPING_ZONE_PROVINCE_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"SHIPPING_ZONE_PROVINCE_PROVINCE_UNIQUE\" UNIQUE (\"province\"),\n  CONSTRAINT \"FK_SHIPPING_ZONE_PROVINCE\" FOREIGN KEY (\"zone_id\") REFERENCES \"shipping_zone\" (\"shipping_zone_id\") ON DELETE CASCADE\n)\n`\n  );\n\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_SHIPPING_ZONE_PROVINCE\" ON \"shipping_zone_province\" (\"zone_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"shipping_method\" (\n  \"shipping_method_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"name\" varchar NOT NULL,\n  CONSTRAINT \"SHIPPING_METHOD_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"SHIPPING_METHOD_NAME_UNIQUE\" UNIQUE (\"name\")\n)\n`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"shipping_zone_method\" (\n  \"shipping_zone_method_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"method_id\" INT NOT NULL,\n  \"zone_id\" INT NOT NULL,\n  \"is_enabled\" boolean NOT NULL DEFAULT TRUE,\n  \"cost\" decimal(12,4) DEFAULT NULL,\n  \"calculate_api\" varchar DEFAULT NULL,\n  \"condition_type\" varchar DEFAULT NULL,\n  \"max\" decimal(12,4) DEFAULT NULL,\n  \"min\" decimal(12,4) DEFAULT NULL,\n  CONSTRAINT \"METHOD_ZONE_UNIQUE\" UNIQUE (\"zone_id\", \"method_id\"),\n  CONSTRAINT \"CONDITION_TYPE_MUST_BE_PRICE_OR_WEIGHT\" CHECK (condition_type IS NULL OR condition_type IN ('price', 'weight')),\n  CONSTRAINT \"CALCULATE API MUST BE PROVIDE IF COST IS NULL\" CHECK (cost IS NOT NULL OR calculate_api IS NOT NULL),\n  CONSTRAINT \"FK_ZONE_METHOD\" FOREIGN KEY (\"zone_id\") REFERENCES \"shipping_zone\" (\"shipping_zone_id\") ON DELETE CASCADE,\n  CONSTRAINT \"FK_METHOD_ZONE\" FOREIGN KEY (\"method_id\") REFERENCES \"shipping_method\" (\"shipping_method_id\") ON DELETE CASCADE\n)\n`\n  );\n\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_ZONE_METHOD\" ON \"shipping_zone_method\" (\"zone_id\")`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_METHOD_ZONE\" ON \"shipping_zone_method\" (\"method_id\")`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/migration/Version-1.0.2.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Reduce product stock when order is placed if product manage stock is true\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION reduce_product_stock_when_order_placed()\n        RETURNS TRIGGER \n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        UPDATE product_inventory SET qty = qty - NEW.qty WHERE product_inventory_product_id = NEW.product_id AND manage_stock = TRUE;\n        RETURN NEW;\n      END\n      $$;`\n  );\n\n  // Remove the trigger if it exists\n  await execute(\n    connection,\n    `DROP TRIGGER IF EXISTS \"TRIGGER_AFTER_INSERT_ORDER_ITEM\" ON \"order_item\"`\n  );\n\n  await execute(\n    connection,\n    `CREATE TRIGGER \"TRIGGER_AFTER_INSERT_ORDER_ITEM\" AFTER INSERT ON \"order_item\" FOR EACH ROW\n    EXECUTE PROCEDURE reduce_product_stock_when_order_placed();\n    `\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/migration/Version-1.0.3.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Create a function to add event to the event table after a order is created\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_order_created_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('order_created', row_to_json(NEW));\n      RETURN NEW;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a order is created\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_ORDER_CREATED_EVENT_TRIGGER\"\n    AFTER INSERT ON \"order\"\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_order_created_event();`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/migration/Version-1.0.4.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Add a column 'sub_total' to the order item table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"cart_item\" ADD COLUMN IF NOT EXISTS \"sub_total\" decimal(12,4)'\n  );\n\n  // For existing order items, calculate the sub_total = quantity * final_price\n  await execute(\n    connection,\n    'UPDATE \"cart_item\" SET sub_total = qty * final_price'\n  );\n\n  // Add a column 'sub_total' to the order item table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"order_item\" ADD COLUMN IF NOT EXISTS \"sub_total\" decimal(12,4)'\n  );\n\n  // For existing order items, calculate the sub_total = quantity * final_price\n  await execute(\n    connection,\n    'UPDATE \"order_item\" SET sub_total = qty * final_price'\n  );\n\n  // Add a column 'sub_total_incl_tax' to the cart table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"cart\" ADD COLUMN IF NOT EXISTS \"sub_total_incl_tax\" decimal(12,4)'\n  );\n\n  // For existing carts, calculate the sub_total_incl_tax = sub_total + tax_amount\n  await execute(\n    connection,\n    'UPDATE \"cart\" SET sub_total_incl_tax = sub_total + tax_amount'\n  );\n\n  // Add a column 'sub_total_incl_tax' to the order table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ADD COLUMN IF NOT EXISTS \"sub_total_incl_tax\" decimal(12,4)'\n  );\n\n  // For existing orders, calculate the sub_total_incl_tax = sub_total + tax_amount\n  await execute(\n    connection,\n    'UPDATE \"order\" SET sub_total_incl_tax = sub_total + tax_amount'\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/migration/Version-1.0.5.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Add a column 'price_based_cost' to the method table if it does not exist\n  await execute(\n    connection,\n    `ALTER TABLE \"shipping_zone_method\" ADD COLUMN \"price_based_cost\" jsonb`\n  );\n\n  // Add a column 'price_based_cost' to the method table if it does not exist\n  await execute(\n    connection,\n    `ALTER TABLE \"shipping_zone_method\" ADD COLUMN \"weight_based_cost\" jsonb`\n  );\n\n  // Delete the constraint 'CONDITION_TYPE_MUST_BE_PRICE_OR_WEIGHT' from the method table\n  await execute(\n    connection,\n    `ALTER TABLE \"shipping_zone_method\" DROP CONSTRAINT \"CONDITION_TYPE_MUST_BE_PRICE_OR_WEIGHT\"`\n  );\n\n  // Delete the constraint 'CONDITION_TYPE_MUST_BE_PRICE_OR_WEIGHT' from the method table\n  await execute(\n    connection,\n    `ALTER TABLE \"shipping_zone_method\" DROP CONSTRAINT \"CALCULATE API MUST BE PROVIDE IF COST IS NULL\"`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/migration/Version-1.0.6.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Rename sub_total column to line_total\n  await execute(\n    connection,\n    `ALTER TABLE \"cart_item\" RENAME COLUMN sub_total TO line_total`\n  );\n\n  await execute(\n    connection,\n    `ALTER TABLE \"order_item\" RENAME COLUMN sub_total TO line_total`\n  );\n\n  // Rename total column to line_total_incl_tax\n  await execute(\n    connection,\n    `ALTER TABLE \"cart_item\" RENAME COLUMN total TO line_total_incl_tax`\n  );\n\n  await execute(\n    connection,\n    `ALTER TABLE \"order_item\" RENAME COLUMN total TO line_total_incl_tax`\n  );\n\n  // Add a column 'tax_amount_before_discount' to the cart item table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"cart_item\" ADD COLUMN IF NOT EXISTS \"tax_amount_before_discount\" decimal(12,4)'\n  );\n\n  // Add a column 'tax_amount_before_discount' to the order item table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"order_item\" ADD COLUMN IF NOT EXISTS \"tax_amount_before_discount\" decimal(12,4)'\n  );\n\n  // Add a column 'tax_amount_before_discount' to the cart table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"cart\" ADD COLUMN IF NOT EXISTS \"tax_amount_before_discount\" decimal(12,4)'\n  );\n\n  // Add a column 'tax_amount_before_discount' to the order table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ADD COLUMN IF NOT EXISTS \"tax_amount_before_discount\" decimal(12,4)'\n  );\n\n  // Add a column 'line_total_with_discount' to the cart item table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"cart_item\" ADD COLUMN IF NOT EXISTS \"line_total_with_discount\" decimal(12,4)'\n  );\n\n  // Add a column 'line_total_with_discount_incl_tax' to the cart item table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"cart_item\" ADD COLUMN IF NOT EXISTS \"line_total_with_discount_incl_tax\" decimal(12,4)'\n  );\n\n  // Add a column 'line_total_with_discount' to the order item table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"order_item\" ADD COLUMN IF NOT EXISTS \"line_total_with_discount\" decimal(12,4)'\n  );\n\n  // Add a column 'line_total_with_discount_incl_tax' to the order item table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"order_item\" ADD COLUMN IF NOT EXISTS \"line_total_with_discount_incl_tax\" decimal(12,4)'\n  );\n\n  // Add a column 'sub_total_with_discount' to the cart table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"cart\" ADD COLUMN IF NOT EXISTS \"sub_total_with_discount\" decimal(12,4)'\n  );\n\n  // Add a column 'sub_total_with_discount' to the order table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ADD COLUMN IF NOT EXISTS \"sub_total_with_discount\" decimal(12,4)'\n  );\n\n  // Add a column 'sub_total_with_discount_incl_tax' to the cart table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"cart\" ADD COLUMN IF NOT EXISTS \"sub_total_with_discount_incl_tax\" decimal(12,4)'\n  );\n\n  // Add a column 'sub_total_with_discount_incl_tax' to the order table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ADD COLUMN IF NOT EXISTS \"sub_total_with_discount_incl_tax\" decimal(12,4)'\n  );\n\n  // Add a column 'shipping_tax_amount' to the cart table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"cart\" ADD COLUMN IF NOT EXISTS \"shipping_tax_amount\" decimal(12,4)'\n  );\n\n  // Add a column 'shipping_tax_amount' to the order table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ADD COLUMN IF NOT EXISTS \"shipping_tax_amount\" decimal(12,4)'\n  );\n\n  // Calculate the value for the new columns 'tax_amount_before_discount', 'line_total_with_discount', 'line_total_with_discount_incl_tax', 'sub_total'\n\n  await execute(\n    connection,\n    `UPDATE \"cart_item\" SET tax_amount_before_discount = ROUND(COALESCE(final_price, 0) * (COALESCE(tax_percent, 0) / 100), 2) * qty`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"order_item\" SET tax_amount_before_discount = ROUND(COALESCE(final_price, 0) * (COALESCE(tax_percent, 0) / 100), 2) * qty`\n  );\n\n  // Calculate the tax_amount_before_discount on cart and order by summing up the tax_amount_before_discount of all items\n  await execute(\n    connection,\n    `UPDATE \"cart\" SET tax_amount_before_discount = (SELECT SUM(tax_amount_before_discount) FROM cart_item WHERE cart_item.cart_id = cart.cart_id)`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"order\" SET tax_amount_before_discount = (SELECT SUM(tax_amount_before_discount) FROM \"order_item\" WHERE \"order_item\".order_item_order_id = \"order\".order_id)`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"cart_item\" SET line_total_with_discount = line_total`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"cart_item\" SET line_total = COALESCE(final_price, 0) * qty`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"order_item\" SET line_total_with_discount = line_total`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"order_item\" SET line_total = COALESCE(final_price, 0) * qty`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"cart_item\" SET line_total_with_discount_incl_tax = line_total_with_discount + tax_amount`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"order_item\" SET line_total_with_discount_incl_tax = line_total_with_discount + tax_amount`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"cart\" SET sub_total_with_discount = (SELECT SUM(line_total_with_discount) FROM cart_item WHERE cart_item.cart_id = cart.cart_id)`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"order\" SET sub_total_with_discount = (SELECT SUM(line_total_with_discount) FROM \"order_item\" WHERE \"order_item\".order_item_order_id = \"order\".order_id)`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"cart\" SET sub_total_with_discount_incl_tax = sub_total_with_discount + tax_amount`\n  );\n\n  await execute(\n    connection,\n    `UPDATE \"order\" SET sub_total_with_discount_incl_tax = sub_total_with_discount + tax_amount`\n  );\n\n  // Add a column 'total_tax_amount' to the cart table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"cart\" ADD COLUMN IF NOT EXISTS \"total_tax_amount\" decimal(12,4)'\n  );\n\n  // Update the value for the new column 'total_tax_amount' on the cart table get value from the `tax_amount` column\n  await execute(connection, `UPDATE \"cart\" SET total_tax_amount = tax_amount`);\n\n  // Add a column 'total_tax_amount' to the order table if it does not exist\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ADD COLUMN IF NOT EXISTS \"total_tax_amount\" decimal(12,4)'\n  );\n\n  // Update the value for the new column 'total_tax_amount' on the cart table get value from the `tax_amount` column\n  await execute(connection, `UPDATE \"order\" SET total_tax_amount = tax_amount`);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/migration/Version-1.0.7.ts",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `ALTER TABLE \"cart_item\" ADD COLUMN IF NOT EXISTS no_shipping_required boolean DEFAULT FALSE`\n  );\n\n  await execute(\n    connection,\n    `ALTER TABLE \"cart\" ADD COLUMN IF NOT EXISTS no_shipping_required boolean DEFAULT FALSE`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/all/ShippingSettingMenu.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { Settings } from 'lucide-react';\nimport React from 'react';\n\ninterface ShippingSettingMenuProps {\n  shippingSettingUrl: string;\n}\nexport default function ShippingSettingMenu({\n  shippingSettingUrl\n}: ShippingSettingMenuProps) {\n  const isActive =\n    typeof window !== 'undefined' &&\n    new URL(shippingSettingUrl, window.location.origin).pathname ===\n      window.location.pathname;\n\n  return (\n    <Item\n      variant={'outline'}\n      className={cn(\n        isActive && 'bg-primary/5 border-primary/20 dark:bg-primary/10'\n      )}\n      data-active={isActive ? 'true' : 'false'}\n    >\n      <ItemContent>\n        <ItemTitle>\n          <div>\n            <a\n              href={shippingSettingUrl}\n              className={cn(\n                'uppercase text-xs font-semibold',\n                isActive && 'text-primary'\n              )}\n            >\n              Shipping Setting\n            </a>\n          </div>\n        </ItemTitle>\n        <ItemDescription>\n          <div>Where you ship, shipping methods and delivery fee</div>\n        </ItemDescription>\n      </ItemContent>\n      <ItemActions>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          onClick={() => (window.location.href = shippingSettingUrl)}\n        >\n          <Settings className=\"h-4 w-4 mr-1\" />\n        </Button>\n      </ItemActions>\n    </Item>\n  );\n}\n\nexport const layout = {\n  areaId: 'settingPageMenu',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    shippingSettingUrl: url(routeId: \"shippingSetting\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/ShippingSetting.tsx",
    "content": "import { SettingMenu } from '@components/admin/SettingMenu.js';\nimport {\n  Card,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\nimport { Zones } from './shippingSetting/Zones.js';\n\nexport default function ShippingSetting({\n  createShippingZoneApi\n}: {\n  createShippingZoneApi: string;\n}) {\n  return (\n    <div className=\"main-content-inner\">\n      <div className=\"grid grid-cols-6 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2\">\n          <SettingMenu />\n        </div>\n        <div className=\"col-span-4\">\n          <Card>\n            <CardHeader>\n              <CardTitle>Shipping</CardTitle>\n              <CardDescription>\n                Choose where you ship and how much you charge for shipping.\n              </CardDescription>\n            </CardHeader>\n            <Zones createShippingZoneApi={createShippingZoneApi} />\n          </Card>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    createShippingZoneApi: url(routeId: \"createShippingZone\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request) => {\n  setPageMetaInfo(request, {\n    title: 'Shipping Setting',\n    description: 'Shipping Setting'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/setting/shipping\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/shippingSetting/Method.tsx",
    "content": "import {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle\n} from '@components/common/ui/Dialog.js';\nimport { Cog } from 'lucide-react';\nimport React from 'react';\nimport { toast } from 'react-toastify';\nimport { MethodForm } from './MethodForm.js';\n\nexport interface ShippingMethod {\n  methodId: string;\n  uuid: string;\n  name: string;\n  isEnabled: boolean;\n  cost?: {\n    text: string;\n    value: number;\n  };\n  priceBasedCost?: Array<{\n    minPrice: { value: number; text: string };\n    cost: { value: number; text: string };\n  }>;\n  weightBasedCost?: Array<{\n    minWeight: { value: number; text: string };\n    cost: { value: number; text: string };\n  }>;\n  calculateApi?: string;\n  conditionType?: string;\n  min?: number;\n  max?: number;\n  updateApi: string;\n  deleteApi: string;\n}\nexport interface MethodProps {\n  method: ShippingMethod;\n  reload: () => void;\n}\n\nfunction Method({ method, reload }: MethodProps) {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  return (\n    <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n      <>\n        <td className=\"border-none py-2\">{method.name}</td>\n        <td className=\"border-none py-2\">\n          {method.isEnabled ? (\n            <span className=\"text-green-700\">Enabled</span>\n          ) : (\n            <span className=\"text-destructive\">Disabled</span>\n          )}\n        </td>\n        <td className=\"border-none py-2\">\n          {method.cost?.text || (\n            <a\n              href=\"#\"\n              className=\"text-primary\"\n              onClick={(e) => {\n                e.preventDefault();\n                setDialogOpen(true);\n              }}\n            >\n              <Cog width={22} height={22} />\n            </a>\n          )}\n        </td>\n        <td className=\"border-none py-2\">\n          {method.conditionType\n            ? `${method.min || 0} <= ${method.conditionType} <= ${\n                method.max || '∞'\n              }`\n            : 'None'}\n        </td>\n        <td className=\"border-none py-2\">\n          <a\n            href=\"#\"\n            className=\"text-primary\"\n            onClick={(e) => {\n              e.preventDefault();\n              setDialogOpen(true);\n            }}\n          >\n            Edit\n          </a>\n          <a\n            href=\"#\"\n            className=\"text-destructive ml-5\"\n            onClick={async (e) => {\n              e.preventDefault();\n              try {\n                const response = await fetch(method.deleteApi, {\n                  method: 'DELETE',\n                  headers: {\n                    'Content-Type': 'application/json'\n                  },\n                  credentials: 'include'\n                });\n                if (response.ok) {\n                  reload();\n                  // Toast success\n                  toast.success('Method removed successfully');\n                } else {\n                  // Toast error\n                  toast.error('Failed to remove method');\n                }\n              } catch (error) {\n                // Toast error\n                toast.error('Failed to remove method');\n              }\n            }}\n          >\n            Delete\n          </a>\n        </td>\n      </>\n      <DialogContent>\n        <DialogHeader>\n          <DialogTitle>Edit Shipping Method</DialogTitle>\n        </DialogHeader>\n        <MethodForm\n          saveMethodApi={method.updateApi}\n          onSuccess={() => setDialogOpen(false)}\n          reload={reload}\n          method={method}\n        />\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nexport { Method };\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/shippingSetting/MethodForm.tsx",
    "content": "import Spinner from '@components/admin/Spinner.js';\nimport { Form, useFormContext } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport { ReactSelectCreatableField } from '@components/common/form/ReactSelectCreatableField.js';\nimport { ToggleField } from '@components/common/form/ToggleField.js';\nimport { UrlField } from '@components/common/form/UrlField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport { Item, ItemActions, ItemContent } from '@components/common/ui/Item.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport React, { useEffect } from 'react';\nimport { set, useForm } from 'react-hook-form';\nimport { toast } from 'react-toastify';\nimport { useQuery } from 'urql';\nimport { ShippingMethod } from './Method.js';\nimport { PriceBasedPrice } from './PriceBasedPrice.js';\nimport { WeightBasedPrice } from './WeightBasedPrice.js';\n\nconst MethodsQuery = `\n  query Methods {\n    shippingMethods {\n      value: uuid\n      label: name\n      updateApi\n    }\n    createShippingMethodApi: url(routeId: \"createShippingMethod\")\n  }\n`;\n\nexport interface ConditionProps {\n  method?: ShippingMethod;\n}\n\nfunction Condition({ method }: ConditionProps) {\n  const { watch, setValue } = useFormContext();\n  const type = watch('condition_type', method?.conditionType || 'price');\n\n  useEffect(() => {\n    setValue('condition_type', method?.conditionType || 'price');\n  }, [method]);\n\n  return (\n    <div className=\"pt-2 space-y-3\">\n      <Label>Conditions</Label>\n      <div>\n        <RadioGroupField\n          name=\"condition_type\"\n          options={[\n            { value: 'price', label: 'Based on order price' },\n            { value: 'weight', label: 'Based on order weight' }\n          ]}\n          defaultValue={type}\n        />\n      </div>\n      <div className=\"grid grid-cols-2 gap-5\">\n        <div>\n          <NumberField\n            name=\"min\"\n            label={\n              type === 'price' ? 'Minimum order price' : 'Minimum order weight'\n            }\n            placeholder={\n              type === 'price' ? 'Minimum order price' : 'Minimum order weight'\n            }\n            defaultValue={method?.min}\n            required\n            validation={{ required: 'Min is required' }}\n            helperText=\"This is the minimum order price or weight to apply this condition.\"\n          />\n        </div>\n        <div>\n          <NumberField\n            name=\"max\"\n            label={\n              type === 'price' ? 'Maximum order price' : 'Maximum order weight'\n            }\n            placeholder={\n              type === 'price' ? 'Maximum order price' : 'Maximum order weight'\n            }\n            defaultValue={method?.max}\n            validation={{ required: 'Max is required' }}\n            helperText=\"This is the maximum order price or weight to apply this condition.\"\n          />\n        </div>\n      </div>\n    </div>\n  );\n}\n\nconst getType = (method: ShippingMethod | null) => {\n  if (method?.calculateApi) {\n    return 'api';\n  }\n  if (method?.priceBasedCost) {\n    return 'price_based_rate';\n  }\n  if (method?.weightBasedCost) {\n    return 'weight_based_rate';\n  }\n  return 'flat_rate';\n};\n\nexport interface MethodFormProps {\n  saveMethodApi: string;\n  onSuccess: () => void;\n  reload: () => void;\n  method?: ShippingMethod;\n}\n\nconst CostSetting: React.FC<{\n  method: ShippingMethod | null;\n}> = ({ method }) => {\n  const { watch } = useFormContext();\n  const typeWatch = watch('calculation_type');\n  return (\n    <>\n      {typeWatch === 'flat_rate' && (\n        <NumberField\n          label=\"Flat rate cost\"\n          name=\"cost\"\n          placeholder=\"Shipping cost\"\n          required\n          validation={{ required: 'Shipping cost is required' }}\n          helperText=\"This is the flat rate cost for shipping.\"\n          defaultValue={method?.cost?.value}\n        />\n      )}\n      {typeWatch === 'price_based_rate' && (\n        <PriceBasedPrice lines={method?.priceBasedCost || []} />\n      )}\n      {typeWatch === 'weight_based_rate' && (\n        <WeightBasedPrice lines={method?.weightBasedCost || []} />\n      )}\n      {typeWatch === 'api' && (\n        <InputField\n          name=\"calculate_api\"\n          placeholder=\"Calculate API endpoint\"\n          required\n          validation={{ required: 'Calculate API is required' }}\n          defaultValue={method?.calculateApi || ''}\n          helperText=\"This is the ID of an internal api to calculate shipping cost.\"\n        />\n      )}\n    </>\n  );\n};\n\nfunction MethodForm({\n  saveMethodApi,\n  onSuccess,\n  reload,\n  method\n}: MethodFormProps) {\n  const form = useForm({\n    shouldUnregister: true\n  });\n  const [shippingMethod, setShippingMethod] = React.useState({\n    ...method,\n    updatingName: false\n  });\n  const [isLoading, setIsLoading] = React.useState(false);\n  const [hasCondition, setHasCondition] = React.useState(\n    !!method?.conditionType\n  );\n\n  const [result, reexecuteQuery] = useQuery({\n    query: MethodsQuery\n  });\n\n  const handleCreate = async (inputValue) => {\n    setIsLoading(true);\n    const response = await fetch(result.data.createShippingMethodApi, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      credentials: 'same-origin',\n      body: JSON.stringify({\n        name: inputValue\n      })\n    });\n    const data = await response.json();\n    if (response.ok) {\n      await reexecuteQuery({ requestPolicy: 'network-only' });\n      form.setValue('method_id', data.data.uuid);\n    } else {\n      toast.error(data.error.message);\n    }\n    setIsLoading(false);\n  };\n\n  if (result.fetching && !result.data) {\n    return (\n      <div className=\"flex justify-center p-2\">\n        <Spinner width={25} height={25} />\n      </div>\n    );\n  }\n\n  // Find the updateApi for the current method from the query results\n  const methodUpdateApi = method\n    ? result.data.shippingMethods.find((m) => m.value === method.uuid)\n        ?.updateApi\n    : null;\n\n  return (\n    <Form\n      id=\"shippingMethodForm\"\n      method={method ? 'PATCH' : 'POST'}\n      action={saveMethodApi}\n      submitBtn={false}\n      onError={(error) => {\n        toast.error(error);\n        setIsLoading(false);\n      }}\n      onSuccess={async (response) => {\n        setIsLoading(false);\n        if (!response.error) {\n          reload();\n          onSuccess && onSuccess();\n          toast.success('Shipping method saved successfully');\n        } else {\n          toast.error(response.error.message);\n        }\n      }}\n      form={form}\n    >\n      <div className=\"divide-y space-y-3\">\n        <div className=\"border-border py-3 space-y-3\">\n          {!method ? (\n            <ReactSelectCreatableField\n              name=\"method_id\"\n              label=\"Shipping Method\"\n              placeholder=\"Select or create shipping method\"\n              isClearable\n              isDisabled={isLoading}\n              isLoading={isLoading}\n              onCreateOption={handleCreate}\n              options={result.data.shippingMethods}\n              required\n              validation={{ required: 'Shipping method is required' }}\n            />\n          ) : (\n            <div className=\"space-y-3\">\n              <div className=\"flex gap-2 items-end\">\n                <div className=\"flex-1\">\n                  <InputField\n                    name=\"method_name\"\n                    label=\"Method name\"\n                    placeholder=\"Method name\"\n                    required\n                    defaultValue={method?.name || ''}\n                    disabled={!shippingMethod.updatingName}\n                    validation={{ required: 'Method name is required' }}\n                  />\n                </div>\n                <Button\n                  variant={shippingMethod.updatingName ? 'default' : 'outline'}\n                  onClick={async (e) => {\n                    e.preventDefault();\n                    if (shippingMethod.updatingName === true) {\n                      if (!methodUpdateApi) {\n                        toast.error('Update API not found');\n                        return;\n                      }\n                      setIsLoading(true);\n                      const methodName = form.getValues('method_name');\n                      const response = await fetch(methodUpdateApi, {\n                        method: 'PATCH',\n                        headers: {\n                          'Content-Type': 'application/json'\n                        },\n                        credentials: 'same-origin',\n                        body: JSON.stringify({\n                          name: methodName\n                        })\n                      });\n                      const data = await response.json();\n                      setIsLoading(false);\n                      if (response.ok) {\n                        setShippingMethod({\n                          ...shippingMethod,\n                          name: data.data.name,\n                          updatingName: false\n                        });\n                        toast.success('Method name updated successfully');\n                      } else {\n                        toast.error(data.error.message);\n                      }\n                    } else {\n                      setShippingMethod({\n                        ...shippingMethod,\n                        updatingName: true\n                      });\n                    }\n                  }}\n                  isLoading={isLoading}\n                >\n                  {shippingMethod.updatingName ? 'Save' : 'Edit name'}\n                </Button>\n              </div>\n            </div>\n          )}\n          <ToggleField\n            name=\"is_enabled\"\n            label=\"Status\"\n            trueLabel=\"Enable\"\n            falseLabel=\"Disable\"\n            defaultValue={method?.isEnabled || 0}\n          />\n          {method && (\n            <InputField\n              type=\"hidden\"\n              name=\"method_id\"\n              defaultValue={method?.uuid || ''}\n            />\n          )}\n        </div>\n        <div className=\"border-border py-3 space-y-3\">\n          <RadioGroupField\n            name=\"calculation_type\"\n            label=\"Calculation Type\"\n            options={[\n              { label: 'Flat rate', value: 'flat_rate' },\n              { label: 'Price based rate', value: 'price_based_rate' },\n              { label: 'Weight based rate', value: 'weight_based_rate' },\n              { label: 'API calculate', value: 'api' }\n            ]}\n            defaultValue={getType(method || null)}\n          />\n\n          <CostSetting method={method || null} />\n\n          {!hasCondition && (\n            <InputField\n              name=\"condition_type\"\n              type=\"hidden\"\n              defaultValue=\"none\"\n            />\n          )}\n          {hasCondition && <Condition method={method} />}\n          <Button\n            variant={hasCondition ? 'destructive' : 'outline'}\n            onClick={(e) => {\n              e.preventDefault();\n              setHasCondition(!hasCondition);\n            }}\n          >\n            {hasCondition ? 'Remove condition' : 'Add condition'}\n          </Button>\n        </div>\n        <div className=\"border-border\">\n          <div className=\"flex justify-end gap-2\">\n            <Button\n              title=\"Save\"\n              variant=\"default\"\n              onClick={async () => {\n                const result = await form.trigger();\n                if (!result) {\n                  return;\n                }\n                setIsLoading(true);\n                (\n                  document.getElementById(\n                    'shippingMethodForm'\n                  ) as HTMLFormElement\n                ).dispatchEvent(\n                  new Event('submit', {\n                    cancelable: true,\n                    bubbles: true\n                  })\n                );\n              }}\n              isLoading={isLoading}\n              disabled={shippingMethod.updatingName}\n            >\n              Save\n            </Button>\n          </div>\n        </div>\n      </div>\n    </Form>\n  );\n}\n\nexport { MethodForm };\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/shippingSetting/Methods.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport {\n  Table,\n  TableBody,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport React from 'react';\nimport { Method, ShippingMethod } from './Method.js';\nimport { MethodForm } from './MethodForm.js';\n\nexport interface MethodsProps {\n  methods: Array<ShippingMethod>;\n  reload: () => void;\n  addMethodApi: string;\n}\n\nexport function Methods({ reload, methods, addMethodApi }: MethodsProps) {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  return (\n    <div className=\"my-5 text-xs\">\n      <Table>\n        <TableHeader>\n          <TableRow className=\"text-xs\">\n            <TableHead className=\"border-none\">Method</TableHead>\n            <TableHead className=\"border-none\">Status</TableHead>\n            <TableHead className=\"border-none\">Cost</TableHead>\n            <TableHead className=\"border-none\">Condition</TableHead>\n            <TableHead className=\"border-none\">Action</TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {methods.map((method) => (\n            <TableRow\n              key={method.methodId}\n              className=\"border-divider py-5 text-xs\"\n            >\n              <Method method={method} reload={reload} />\n            </TableRow>\n          ))}\n        </TableBody>\n      </Table>\n      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n        <div className=\"mt-2\">\n          <DialogTrigger>\n            <Button\n              variant={'link'}\n              onClick={(e) => {\n                e.preventDefault();\n              }}\n            >\n              + Add Method\n            </Button>\n          </DialogTrigger>\n        </div>\n        <DialogContent>\n          <DialogTitle>Add Shipping Method</DialogTitle>\n          <MethodForm\n            saveMethodApi={addMethodApi}\n            onSuccess={() => {\n              setDialogOpen(false);\n            }}\n            reload={reload}\n          />\n        </DialogContent>\n      </Dialog>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/shippingSetting/PriceBasedPrice.tsx",
    "content": "import { NumberField } from '@components/common/form/NumberField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableFooter,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport React from 'react';\nimport { useFieldArray, useFormContext } from 'react-hook-form';\n\nexport interface PriceBasedPriceProps {\n  lines: Array<{\n    minPrice: { value: number };\n    cost: { value: number };\n  }>;\n}\n\nexport function PriceBasedPrice({ lines }: PriceBasedPriceProps) {\n  const { control } = useFormContext();\n  const { fields, append, remove } = useFieldArray({\n    control,\n    name: 'price_based_cost'\n  });\n\n  // Initialize the field array with existing lines if it's empty\n  React.useEffect(() => {\n    if (fields.length === 0 && lines.length > 0) {\n      lines.forEach((line) => {\n        append({\n          min_price: line.minPrice?.value || undefined,\n          cost: line.cost?.value || undefined\n        });\n      });\n    }\n  }, [lines, fields.length, append]);\n\n  // Ensure there's at least one row\n  React.useEffect(() => {\n    if (lines.length === 0) {\n      append({\n        min_price: undefined,\n        cost: undefined\n      });\n    }\n  }, [lines.length, append]);\n\n  return (\n    <div className=\"my-5\">\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead className=\"border-none\">Min Price</TableHead>\n            <TableHead className=\"border-none\">Shipping Cost</TableHead>\n            <TableHead className=\"border-none\">Action</TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {fields.map((field, index) => (\n            <TableRow key={field.id} className=\"border-border py-5\">\n              <TableCell>\n                <NumberField\n                  name={`price_based_cost.${index}.min_price`}\n                  placeholder=\"Min Price\"\n                  required\n                  validation={{ required: 'Min price is required' }}\n                />\n              </TableCell>\n              <TableCell>\n                <NumberField\n                  name={`price_based_cost.${index}.cost`}\n                  placeholder=\"Shipping Cost\"\n                  required\n                  validation={{ required: 'Shipping cost is required' }}\n                />\n              </TableCell>\n              <TableCell>\n                {fields.length > 1 && (\n                  <button\n                    type=\"button\"\n                    onClick={() => remove(index)}\n                    className=\"text-destructive\"\n                  >\n                    Delete\n                  </button>\n                )}\n              </TableCell>\n            </TableRow>\n          ))}\n        </TableBody>\n        <TableFooter className=\"border-border\">\n          <TableRow>\n            <TableCell colSpan={3} className=\"border-none\">\n              <Button\n                type=\"button\"\n                size={'sm'}\n                variant={'outline'}\n                onClick={() => {\n                  append({\n                    min_weight: undefined,\n                    cost: undefined\n                  });\n                }}\n              >\n                + Add Line\n              </Button>\n            </TableCell>\n          </TableRow>\n        </TableFooter>\n      </Table>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/shippingSetting/WeightBasedPrice.tsx",
    "content": "import { NumberField } from '@components/common/form/NumberField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Table,\n  TableRow,\n  TableBody,\n  TableHeader,\n  TableHead,\n  TableCell,\n  TableFooter\n} from '@components/common/ui/Table.js';\nimport React from 'react';\nimport { useFieldArray, useFormContext } from 'react-hook-form';\n\nexport interface WeightBasedPriceProps {\n  lines: Array<{\n    minWeight: { value: number };\n    cost: { value: number };\n  }>;\n}\n\nexport function WeightBasedPrice({ lines }: WeightBasedPriceProps) {\n  const { control } = useFormContext();\n  const { fields, append, remove } = useFieldArray({\n    control,\n    name: 'weight_based_cost'\n  });\n\n  // Initialize the field array with existing lines if it's empty\n  React.useEffect(() => {\n    if (fields.length === 0 && lines.length > 0) {\n      lines.forEach((line) => {\n        append({\n          min_weight: line.minWeight?.value,\n          cost: line.cost?.value\n        });\n      });\n    }\n  }, [lines, fields.length, append]);\n\n  // Ensure there's at least one row\n  React.useEffect(() => {\n    if (lines.length === 0) {\n      append({\n        min_weight: undefined,\n        cost: undefined\n      });\n    }\n  }, [lines.length, append]);\n\n  return (\n    <div className=\"my-5\">\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead className=\"border-none\">Min Weight</TableHead>\n            <TableHead className=\"border-none\">Shipping Cost</TableHead>\n            <TableHead className=\"border-none\">Action</TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {fields.map((field, index) => (\n            <TableRow key={field.id} className=\"border-divider py-5\">\n              <TableCell className=\"border-none\">\n                <NumberField\n                  name={`weight_based_cost.${index}.min_weight`}\n                  placeholder=\"Min Weight\"\n                  required\n                  validation={{ required: 'Min weight is required' }}\n                />\n              </TableCell>\n              <TableCell className=\"border-none\">\n                <NumberField\n                  name={`weight_based_cost.${index}.cost`}\n                  placeholder=\"Shipping Cost\"\n                  required\n                  validation={{ required: 'Shipping cost is required' }}\n                />\n              </TableCell>\n              <TableCell className=\"border-none\">\n                {fields.length > 1 && (\n                  <button\n                    type=\"button\"\n                    onClick={() => remove(index)}\n                    className=\"text-destructive\"\n                  >\n                    Delete\n                  </button>\n                )}\n              </TableCell>\n            </TableRow>\n          ))}\n        </TableBody>\n        <TableFooter className=\"border-border\">\n          <TableRow>\n            <TableCell colSpan={3} className=\"border-none\">\n              <Button\n                type=\"button\"\n                size={'sm'}\n                variant={'outline'}\n                onClick={() => {\n                  append({\n                    min_weight: undefined,\n                    cost: undefined\n                  });\n                }}\n              >\n                + Add Line\n              </Button>\n            </TableCell>\n          </TableRow>\n        </TableFooter>\n      </Table>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/shippingSetting/Zone.tsx",
    "content": "import { CardContent } from '@components/common/ui/Card.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTrigger,\n  DialogTitle\n} from '@components/common/ui/Dialog.js';\nimport axios from 'axios';\nimport { MapPin } from 'lucide-react';\nimport React from 'react';\nimport { toast } from 'react-toastify';\nimport { ShippingMethod } from './Method.js';\nimport { Methods } from './Methods.js';\nimport { ZoneForm } from './ZoneForm.js';\n\nexport interface ShippingZone {\n  name: string;\n  uuid: string;\n  country?: {\n    name: string;\n    code: string;\n  };\n  provinces: Array<{\n    name: string;\n    code: string;\n  }>;\n  methods: Array<ShippingMethod>;\n  addMethodApi: string;\n  deleteApi: string;\n  updateApi: string;\n}\n\nexport interface ZoneProps {\n  zone: ShippingZone;\n  reload: () => void;\n}\n\nfunction Zone({ zone, reload }: ZoneProps) {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  return (\n    <CardContent className=\"space-y-3 pt-3 border-t border-border\">\n      <div className=\"flex justify-between items-center gap-5\">\n        <div className=\"text-xs uppercase font-semibold\">{zone.name}</div>\n        <div className=\"flex justify-between gap-5\">\n          <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n            <DialogTrigger>Edit Zone</DialogTrigger>\n            <DialogContent>\n              <DialogHeader>\n                <DialogTitle>Edit Shipping Zone</DialogTitle>\n              </DialogHeader>\n              <ZoneForm\n                formMethod=\"PATCH\"\n                saveZoneApi={zone.updateApi}\n                onSuccess={() => setDialogOpen(false)}\n                reload={reload}\n                zone={zone}\n              />\n            </DialogContent>\n          </Dialog>\n          <a\n            className=\"text-destructive\"\n            href=\"#\"\n            onClick={async (e) => {\n              e.preventDefault();\n              try {\n                const response = await axios.delete(zone.deleteApi);\n                if (response.status === 200) {\n                  // Toast success\n                  toast.success('Zone removed successfully');\n                  // Delay for 2 seconds\n                  setTimeout(() => {\n                    // Reload page\n                    window.location.reload();\n                  }, 1500);\n                } else {\n                  // Toast error\n                  toast.error('Failed to remove zone');\n                }\n              } catch (error) {\n                // Toast error\n                toast.error('Failed to remove zone');\n              }\n            }}\n          >\n            Remove Zone\n          </a>\n        </div>\n      </div>\n      <div className=\"divide-y border rounded border-divider\">\n        <div className=\"flex justify-start items-center border-divider\">\n          <div className=\"p-5\">\n            <MapPin width={20} height={20} />\n          </div>\n          <div className=\"grow px-2\">\n            <div>\n              <b>{zone.country?.name || 'Worldwide'}</b>\n            </div>\n            <div>\n              {zone.provinces\n                .slice(0, 3)\n                .map((province) => province.name)\n                .join(', ')}\n              {zone.provinces.length > 3 && '...'}\n            </div>\n          </div>\n        </div>\n        <div className=\"flex justify-start items-center border-divider\">\n          <div className=\"grow px-2\">\n            <Methods\n              methods={zone.methods}\n              reload={reload}\n              addMethodApi={zone.addMethodApi}\n            />\n          </div>\n        </div>\n      </div>\n    </CardContent>\n  );\n}\n\nexport { Zone };\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/shippingSetting/ZoneForm.tsx",
    "content": "import Spinner from '@components/admin/Spinner.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { ReactSelectField } from '@components/common/form/ReactSelectField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useQuery } from 'urql';\nimport { ShippingZone } from './Zone.js';\n\nexport interface ZoneFormProps {\n  formMethod?: 'POST' | 'PATCH';\n  saveZoneApi: string;\n  onSuccess: () => void;\n  reload: () => void;\n  zone?: ShippingZone;\n}\n\nconst CountriesQuery = `\n  query Country {\n    countries {\n      value: code\n      label: name\n      provinces {\n        value: code\n        label: name\n      }\n    }\n  }\n`;\n\nfunction ZoneForm({\n  formMethod,\n  saveZoneApi,\n  onSuccess,\n  reload,\n  zone\n}: ZoneFormProps) {\n  const form = useForm();\n  const countryWatch = form.watch('country', zone?.country?.code);\n  const [{ data, fetching, error }] = useQuery({\n    query: CountriesQuery\n  });\n\n  // Reset provinces when country changes\n  React.useEffect(() => {\n    if (countryWatch !== zone?.country?.code) {\n      form.setValue('provinces', []);\n    }\n  }, [countryWatch, zone?.country?.code, form]);\n\n  if (fetching) return <Spinner width={20} height={20} />;\n  if (error) {\n    return <p className=\"text-destructive\">Error loading countries</p>;\n  }\n  return (\n    <Form\n      id=\"createShippingZone\"\n      method={formMethod || 'POST'}\n      action={saveZoneApi}\n      submitBtn={false}\n      onSuccess={async () => {\n        await reload();\n        onSuccess();\n      }}\n      form={form}\n    >\n      <div className=\"space-y-3\">\n        <InputField\n          name=\"name\"\n          label=\"Zone Name\"\n          aria-label=\"Zone Name\"\n          placeholder=\"Enter zone name\"\n          required\n          validation={{ required: 'Zone name is required' }}\n          defaultValue={zone?.name}\n        />\n        <ReactSelectField\n          name=\"country\"\n          label=\"Country\"\n          aria-label=\"Country\"\n          placeholder=\"Select country\"\n          required\n          validation={{ required: 'Country is required' }}\n          options={data.countries}\n          hideSelectedOptions={false}\n          isMulti={false}\n          defaultValue={zone?.country?.code}\n        />\n        <ReactSelectField\n          name=\"provinces\"\n          label=\"Provinces/States\"\n          aria-label=\"Provinces/States\"\n          placeholder=\"Select provinces/states\"\n          options={\n            data.countries.find((c) => c.value === countryWatch)?.provinces ||\n            []\n          }\n          hideSelectedOptions\n          isMulti\n          defaultValue={(zone?.provinces || []).map(\n            (province) => province.code\n          )}\n        />\n        <div className=\"flex justify-end gap-2\">\n          <Button\n            title=\"Save\"\n            variant=\"default\"\n            onClick={() => {\n              const form = document.getElementById(\n                'createShippingZone'\n              ) as HTMLFormElement | null;\n              if (form) {\n                form.dispatchEvent(\n                  new Event('submit', {\n                    cancelable: true,\n                    bubbles: true\n                  })\n                );\n              }\n            }}\n          >\n            Save\n          </Button>\n        </div>\n      </div>\n    </Form>\n  );\n}\n\nexport { ZoneForm };\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/admin/shippingSetting/shippingSetting/Zones.tsx",
    "content": "import Spinner from '@components/admin/Spinner.jsx';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport React from 'react';\nimport { useQuery } from 'urql';\nimport { Zone } from './Zone.js';\nimport { ZoneForm } from './ZoneForm.js';\n\nexport interface ShippingCountry {\n  label: string;\n  value: string;\n  provinces: Array<{\n    label: string;\n    value: string;\n  }>;\n}\n\nconst ZonesQuery = `\n  query Zones {\n    shippingZones {\n      uuid\n      name\n      country {\n        name\n        code\n      }\n      provinces {\n        name\n        code\n      }\n      methods {\n        methodId\n        uuid\n        name\n        cost {\n          text\n          value\n        }\n        priceBasedCost {\n          minPrice {\n            value\n            text\n          }\n          cost {\n            value\n            text\n          }\n        }\n        weightBasedCost {\n          minWeight {\n            value\n            text\n          }\n          cost {\n            value\n            text\n          }\n        }\n        isEnabled\n        conditionType\n        calculateApi\n        max\n        min\n        updateApi\n        deleteApi\n      }\n      updateApi\n      deleteApi\n      addMethodApi\n    }\n  }\n`;\n\nexport function Zones({\n  createShippingZoneApi\n}: {\n  createShippingZoneApi: string;\n}) {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const [{ data, fetching, error }, reexecuteQuery] = useQuery({\n    query: ZonesQuery,\n    requestPolicy: 'network-only'\n  });\n\n  if (fetching) return <Spinner width={'2rem'} height={'2rem'} />;\n  if (error) return <div className=\"text-destructive\">Error loading zones</div>;\n\n  if (!data || !data.shippingZones) return <div>No zones found</div>;\n  const reload = () => {\n    reexecuteQuery({ requestPolicy: 'network-only' });\n  };\n  return (\n    <>\n      {data.shippingZones.map((zone) => (\n        <Zone zone={zone} reload={reload} key={zone.uuid} />\n      ))}\n      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n        <div className=\"flex justify-end pr-5\">\n          <DialogTrigger>\n            <Button>Create New Zone</Button>\n          </DialogTrigger>\n        </div>\n        <DialogContent>\n          <ZoneForm\n            formMethod=\"POST\"\n            saveZoneApi={createShippingZoneApi}\n            onSuccess={() => {\n              setDialogOpen(false);\n            }}\n            reload={reload}\n          />\n        </DialogContent>\n      </Dialog>\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/all/MiniCartIcon.tsx",
    "content": "import { MiniCart } from '@components/frontStore/cart/MiniCart.js';\nimport React from 'react';\n\ninterface MiniCartIconProps {\n  cartUrl: string;\n}\nexport default function MiniCartIcon({ cartUrl }: MiniCartIconProps) {\n  return (\n    <MiniCart className=\"flex justify-center items-center\" cartUrl={cartUrl} />\n  );\n}\n\nexport const layout = {\n  areaId: 'headerMiddleRight',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query {\n    cartUrl: url(routeId: \"cart\"),\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/all/[auth]addCustomerToCart.ts",
    "content": "import { select, update } from '@evershop/postgres-query-builder';\nimport { error } from '../../../../../lib/log/logger.js';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\n\nexport default async (request: EvershopRequest, response, next) => {\n  try {\n    const { sessionID } = request;\n    const customer = request.getCurrentCustomer();\n    if (customer) {\n      // Check if there is any cart with the same sid\n      const cart = await select()\n        .from('cart')\n        .where('sid', '=', sessionID)\n        .and('status', '=', 1)\n        .load(pool);\n      if (cart) {\n        await update('cart')\n          .given({\n            customer_group_id: customer.group_id,\n            customer_id: customer.customer_id,\n            customer_full_name: customer.full_name,\n            customer_email: customer.email\n          })\n          .where('cart_id', '=', cart.cart_id)\n          .execute(pool);\n      }\n    }\n  } catch (e) {\n    error(e);\n  }\n  next();\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/cart/ShoppingCart.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { useCartState } from '@components/frontStore/cart/CartContext.js';\nimport { CartItems } from '@components/frontStore/cart/CartItems.js';\nimport { CartTotalSummary } from '@components/frontStore/cart/CartTotalSummary.js';\nimport { DefaultCartItemList } from '@components/frontStore/cart/DefaultCartItemList.js';\nimport { ShoppingCartEmpty } from '@components/frontStore/cart/ShoppingCartEmpty.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nconst Title: React.FC<{ title: string }> = ({ title }) => {\n  return (\n    <div className=\"mb-7 text-center shopping-cart-header\">\n      <h1 className=\"shopping-cart-title mb-2\">{title}</h1>\n      <a href=\"/\" className=\"underline\">\n        {_('Continue Shopping')}\n      </a>\n    </div>\n  );\n};\ninterface ShoppingCartProps {\n  checkoutUrl: string;\n}\nexport default function ShoppingCart({ checkoutUrl }: ShoppingCartProps) {\n  const { data: cart } = useCartState();\n  return (\n    <div className=\"cart page-width\">\n      {cart.items.length > 0 ? (\n        <>\n          <Title title={_('Shopping Cart')} />\n          <div className=\"grid gap-10 grid-cols-1 md:grid-cols-4\">\n            <div className=\"col-span-1 md:col-span-3\">\n              <CartItems>\n                {({ items, showPriceIncludingTax, loading, onRemoveItem }) => (\n                  <DefaultCartItemList\n                    items={items}\n                    showPriceIncludingTax={showPriceIncludingTax}\n                    loading={loading}\n                    onRemoveItem={onRemoveItem}\n                  />\n                )}\n              </CartItems>\n            </div>\n            <div className=\"col-span-1 md:col-span-1\">\n              <Area id=\"shoppingCartBeforeSummary\" noOuter />\n              <div className=\"grid grid-cols-1 gap-5 cart-summary\">\n                <h4>{_('Order summary')}</h4>\n                <CartTotalSummary />\n              </div>\n              <Area id=\"shoppingCartBeforeCheckoutButton\" noOuter />\n              <div className=\"shopping-cart-checkout-btn flex justify-between mt-5\">\n                <Button\n                  onClick={() => (window.location.href = checkoutUrl)}\n                  title={_('CHECKOUT')}\n                  variant=\"default\"\n                  size={'lg'}\n                  className={'w-full'}\n                >\n                  {_('CHECKOUT')}\n                </Button>\n              </div>\n              <Area id=\"shoppingCartAfterSummary\" noOuter />\n            </div>\n          </div>\n        </>\n      ) : (\n        <ShoppingCartEmpty />\n      )}\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    checkoutUrl: url(routeId: \"checkout\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/cart/index.ts",
    "content": "import { translate } from '../../../../../lib/locale/translate/translate.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request, response) => {\n  setPageMetaInfo(request, {\n    title: translate('Shopping cart'),\n    description: translate('Shopping cart')\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/cart/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/cart\",\n  \"name\": \"Cart page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkout/Checkout.scss",
    "content": ".checkout-steps {\n  form {\n    .form-submit-button {\n      padding-top: 0px;\n      border-top: 0;\n      display: flex;\n      justify-content: end;\n      button.button {\n        padding: 1.25rem;\n        font-weight: 400;\n      }\n    }\n    label {\n      color: var(--textSubdued);\n    }\n  }\n  .place-order-button {\n    button {\n      padding: 1.25rem;\n      font-weight: 400;\n    }\n  }\n}\n.wrapper {\n  .header {\n    margin-bottom: 0;\n  }\n  .footer {\n    margin-top: 0;\n  }\n}\n.checkout-steps {\n  padding-bottom: 1.25rem;\n}\n.separator {\n  padding: 0 5px;\n}\n.checkout-breadcrumb {\n  font-size: 12px;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkout/Checkout.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { CartItems } from '@components/frontStore/cart/CartItems.js';\nimport { CartSummaryItemsList } from '@components/frontStore/cart/CartSummaryItems.js';\nimport { CartTotalSummary } from '@components/frontStore/cart/CartTotalSummary.js';\nimport { CheckoutButton } from '@components/frontStore/checkout/CheckoutButton.js';\nimport { CheckoutProvider } from '@components/frontStore/checkout/CheckoutContext.js';\nimport { ContactInformation } from '@components/frontStore/checkout/ContactInformation.js';\nimport { Payment } from '@components/frontStore/checkout/Payment.js';\nimport { Shipment } from '@components/frontStore/checkout/Shipment.js';\nimport React from 'react';\nimport './Checkout.scss';\nimport { useForm } from 'react-hook-form';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\n\ninterface CheckoutPageProps {\n  placeOrderApi: string;\n  getPaymentMethodApi: string;\n  getShippingMethodApi: string;\n  checkoutSuccessUrl: string;\n}\n\nexport default function CheckoutPage({\n  placeOrderApi,\n  checkoutSuccessUrl\n}: CheckoutPageProps) {\n  const [disabled, setDisabled] = React.useState(false);\n  const form = useForm({\n    disabled: disabled,\n    mode: 'onBlur',\n    reValidateMode: 'onBlur',\n    defaultValues: {}\n  });\n\n  return (\n    <CheckoutProvider\n      form={form}\n      enableForm={() => setDisabled(false)}\n      disableForm={() => setDisabled(true)}\n      allowGuestCheckout={true}\n      placeOrderApi={placeOrderApi}\n      checkoutSuccessUrl={checkoutSuccessUrl}\n    >\n      <div className=\"page-width grid grid-cols-1 md:grid-cols-2 gap-7 pt-8 pb-8\">\n        <Form form={form} submitBtn={false}>\n          <Area id=\"checkoutFormBefore\" noOuter />\n          <div>\n            <ContactInformation />\n            <Shipment />\n            <Payment />\n            <CheckoutButton />\n          </div>\n          <Area id=\"checkoutFormAfter\" noOuter />\n        </Form>\n        <div>\n          <CartItems>\n            {({ items, loading, showPriceIncludingTax }) => (\n              <CartSummaryItemsList\n                items={items}\n                loading={loading}\n                showPriceIncludingTax={showPriceIncludingTax}\n              />\n            )}\n          </CartItems>\n          <CartTotalSummary />\n        </div>\n      </div>\n    </CheckoutProvider>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    placeOrderApi: url(routeId: \"createOrder\")\n    checkoutSuccessUrl: url(routeId: \"checkoutSuccess\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkout/index.ts",
    "content": "import { translate } from '../../../../../lib/locale/translate/translate.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { getMyCart } from '../../../services/getMyCart.js';\n\ndeclare module 'express-session' {\n  interface SessionData {\n    checkout: {\n      id?: string;\n      cartId?: string;\n      lastOrderId?: string;\n    };\n  }\n}\n\nexport default async (request: EvershopRequest, response, next) => {\n  const customer = request.getCurrentCustomer();\n  const cart = await getMyCart(request.sessionID || '', customer?.customer_id);\n  if (!cart) {\n    response.redirect(302, buildUrl('cart'));\n    return;\n  }\n  const items = cart.getItems();\n\n  if (items.length === 0 || cart.hasItemError()) {\n    response.redirect(302, buildUrl('cart'));\n  } else {\n    setPageMetaInfo(request, {\n      title: translate('Checkout'),\n      description: translate('Checkout')\n    });\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkout/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/checkout\",\n  \"name\": \"Checkout page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkoutSuccess/CheckoutSuccess.jsx",
    "content": "import Area from '@components/common/Area';\nimport React from 'react';\nimport './CheckoutSuccess.scss';\n\nexport default function CheckoutSuccessPage() {\n  return (\n    <div className=\"page-width grid grid-cols-1 md:grid-cols-2 gap-7\">\n      <Area id=\"checkoutSuccessPageLeft\" />\n      <Area id=\"checkoutSuccessPageRight\" />\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkoutSuccess/CheckoutSuccess.scss",
    "content": ".checkout-success-customer-info {\n  form {\n    .form-submit-button {\n      padding-top: 0px;\n      border-top: 0;\n      button.button {\n        padding: 1.25rem;\n        font-weight: 400;\n      }\n    }\n    label {\n      color: var(--textSubdued);\n    }\n  }\n  a.button {\n    padding: 1.25rem;\n    font-weight: 400;\n    letter-spacing: 0.031rem;\n  }\n}\n.wrapper {\n  .header {\n    margin-bottom: 0;\n  }\n  .footer {\n    margin-top: 0;\n  }\n}\n.checkout-success-customer-info {\n  padding-top: 3.125rem;\n  padding-bottom: 3.125rem;\n}\n\n.customer-info {\n  border: 1px solid var(--divider);\n  border-radius: 5px;\n  padding: 1.25rem;\n}\n.thank-you {\n  .check {\n    border: 2px solid var(--interactive);\n    border-radius: 100%;\n    padding: 0.313rem;\n  }\n}\n\n.checkoutSuccess .breadcrumb {\n  display: none;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkoutSuccess/CustomerInfo.tsx",
    "content": "import { AddressSummary } from '@components/common/customer/address/AddressSummary.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface CustomerInfoProps {\n  order: {\n    orderNumber: string;\n    customerFullName: string;\n    customerEmail: string;\n    paymentMethodName: string;\n    noShippingRequired: boolean;\n    shippingAddress: {\n      fullName: string;\n      postcode: string;\n      telephone: string;\n      country: {\n        name: string;\n        code: string;\n      };\n      province: {\n        name: string;\n        code: string;\n      };\n      city: string;\n      address1: string;\n      address2: string;\n    };\n    billingAddress: {\n      fullName: string;\n      postcode: string;\n      telephone: string;\n      country: {\n        name: string;\n        code: string;\n      };\n      province: {\n        name: string;\n        code: string;\n      };\n      city: string;\n      address1: string;\n      address2: string;\n    };\n  };\n}\n\nexport default function CustomerInfo({\n  order: {\n    orderNumber,\n    customerFullName,\n    customerEmail,\n    paymentMethodName,\n    noShippingRequired,\n    shippingAddress,\n    billingAddress\n  }\n}: CustomerInfoProps) {\n  return (\n    <div className=\"checkout-success-customer-info\">\n      <h3 className=\"thank-you flex justify-start space-x-5\">\n        <div className=\"check flex justify-center self-center text-interactive\">\n          <svg\n            style={{ width: '3rem', height: '3rem' }}\n            xmlns=\"http://www.w3.org/2000/svg\"\n            className=\"h-4 w-4\"\n            fill=\"none\"\n            viewBox=\"0 0 24 24\"\n            stroke=\"currentColor\"\n          >\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth={2}\n              d=\"M5 13l4 4L19 7\"\n            />\n          </svg>\n        </div>\n        <div className=\"self-center\">\n          <span style={{ fontSize: '1.6rem', fontWeight: '300' }}>\n            {_('Order #${orderNumber}', { orderNumber })}\n          </span>\n          <div>\n            {_('Thank you ${name}!', {\n              name: customerFullName || billingAddress?.fullName\n            })}\n          </div>\n        </div>\n      </h3>\n\n      <div className=\"customer-info mt-7 mb-5\">\n        <div className=\"grid grid-cols-2 gap-7\">\n          <div>\n            <div className=\"mb-2\">\n              <h3>{_('Contact information')}</h3>\n            </div>\n            <div className=\"text-textSubdued\">\n              {customerFullName || billingAddress?.fullName}\n            </div>\n            <div className=\"text-textSubdued\">{customerEmail}</div>\n          </div>\n          <div>\n            <div className=\"mb-2\">\n              <h3>{_('Shipping Address')}</h3>\n            </div>\n            <div className=\"text-textSubdued\">\n              {noShippingRequired ? (\n                _('No shipping required')\n              ) : (\n                <AddressSummary address={shippingAddress} />\n              )}\n            </div>\n          </div>\n          <div>\n            <div className=\"mb-2\">\n              <h3>{_('Payment Method')}</h3>\n            </div>\n            <div className=\"text-textSubdued\">{paymentMethodName}</div>\n          </div>\n          <div>\n            <div className=\"mb-2\">\n              <h3>{_('Billing Address')}</h3>\n            </div>\n            <div className=\"text-textSubdued\">\n              <AddressSummary address={billingAddress} />\n            </div>\n          </div>\n        </div>\n      </div>\n      <Button\n        variant={'default'}\n        size={'lg'}\n        onClick={() => (window.location.href = '/')}\n        title={_('CONTINUE SHOPPING')}\n      >\n        {_('CONTINUE SHOPPING')}\n      </Button>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'checkoutSuccessPageLeft',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    order (uuid: getContextValue('orderId')) {\n      orderNumber\n      customerFullName\n      customerEmail\n      paymentMethodName\n      noShippingRequired\n      shippingNote\n      shippingAddress {\n        fullName\n        postcode\n        telephone\n        country {\n          name\n          code\n        }\n        province {\n          name\n          code\n        }\n        city\n        address1\n        address2\n      }\n      billingAddress {\n        fullName\n        postcode\n        telephone\n        country {\n          name\n          code\n        }\n        province {\n          name\n          code\n        }\n        city\n        address1\n        address2\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkoutSuccess/ShippingNote.tsx",
    "content": "import React from 'react';\n\ninterface ShippingNoteProps {\n  setting: {\n    showShippingNote: boolean;\n  };\n  order: {\n    shippingNote: string;\n  };\n}\n\nexport default function ShippingNote({\n  setting: { showShippingNote },\n  order: { shippingNote }\n}: ShippingNoteProps) {\n  return showShippingNote ? (\n    <div className=\"shipping-note mt-5\">\n      <p className=\"italic\">{shippingNote}</p>\n    </div>\n  ) : null;\n}\n\nexport const layout = {\n  areaId: 'checkoutSuccessSummary',\n  sortOrder: 50\n};\n\nexport const query = `\n  query Query {\n    order (uuid: getContextValue('orderId')) {\n      shippingNote\n    }\n    setting {\n      showShippingNote\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkoutSuccess/Summary.scss",
    "content": ".checkout__summary {\n  padding-top: 3.125rem;\n  padding-bottom: 3.125rem;\n  border-left: 1px solid var(--divider);\n  position: relative;\n  padding-left: 1.25rem;\n  min-height: calc(100vh - 50px);\n  &::after {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    display: block;\n    width: 400%;\n    bottom: 0;\n    background-color: #fafafa;\n    z-index: -1;\n  }\n}\nbody {\n  overflow-x: hidden;\n}\n\n.summary__row {\n  display: grid;\n  grid-gap: 0.625rem;\n  grid-template-columns: 8.75rem auto;\n  > div {\n    display: grid;\n    grid-gap: 0.625rem;\n    grid-template-columns: 1fr auto;\n  }\n  &.grand-total {\n    border-top: 1px solid var(--divider);\n    .grand-total-value {\n      font-size: 1.25rem;\n      font-weight: bold;\n    }\n  }\n}\n.summary-wrapper {\n  display: grid;\n  grid-row-gap: 0.625rem;\n}\n.checkout-summary-block {\n  border-top: 1px solid var(--divider);\n  padding-top: 0.625rem;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkoutSuccess/Summary.tsx",
    "content": "import React from 'react';\nimport './Summary.scss';\nimport { OrderSummaryItems } from '@components/frontStore/checkout/OrderSummaryItems.js';\nimport { OrderTotalSummary } from '@components/frontStore/checkout/OrderTotalSummary.js';\nimport { Order } from '@components/frontStore/customer/CustomerContext.jsx';\nimport { useAppState } from '@components/common/context/app.js';\n\ninterface SummaryProps {\n  order: Order;\n}\n\nexport default function Summary({ order }: SummaryProps) {\n  const {\n    config: {\n      tax: { priceIncludingTax }\n    }\n  } = useAppState();\n  return (\n    <div className=\"checkout__summary h-full hidden md:block\">\n      <OrderSummaryItems items={order.items} />\n      <OrderTotalSummary\n        shippingCost={\n          priceIncludingTax\n            ? order.shippingFeeInclTax.text\n            : order.shippingFeeExclTax.text\n        }\n        subTotal={\n          priceIncludingTax ? order.subTotalInclTax.text : order.subTotal.text\n        }\n        total={order.grandTotal.text}\n        shippingMethod={order.shippingMethodName}\n        coupon={order.coupon || ''}\n        discountAmount={order.discountAmount.text}\n        taxAmount={order.totalTaxAmount.text}\n      />\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'checkoutSuccessPageRight',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    order (uuid: getContextValue('orderId')) {\n      orderNumber\n      discountAmount {\n        text\n      }\n      coupon\n      shippingMethodName\n      shippingFeeInclTax {\n        text\n      }\n      shippingFeeExclTax {\n        text\n      }\n      totalTaxAmount {\n        text\n      }\n      subTotal {\n        text\n      }\n      subTotalInclTax {\n        text\n      }\n      grandTotal {\n        text\n      }\n      items {\n        uuid\n        productName\n        thumbnail\n        productSku\n        qty\n        productUrl\n        productPrice {\n          text\n        }\n        productPriceInclTax {\n          text\n        }\n        variantOptions {\n          attributeCode\n          attributeName\n          attributeId\n          optionId\n          optionText\n        }\n        lineTotalInclTax {\n          text\n        }\n        lineTotal {\n          text\n        }\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkoutSuccess/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { translate } from '../../../../../lib/locale/translate/translate.js';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (request: EvershopRequest, response, next) => {\n  const { orderId } = request.params;\n  const order = await select()\n    .from('order')\n    .where('uuid', '=', orderId)\n    .and('sid', '=', request.sessionID || '')\n    .load(pool);\n  if (!order) {\n    response.redirect(302, buildUrl('homepage'));\n    return;\n  } else {\n    setPageMetaInfo(request, {\n      title: translate('Checkout success'),\n      description: translate('Checkout success')\n    });\n    setContextValue(request, 'orderId', order.uuid);\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/pages/frontStore/checkoutSuccess/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/checkout/success/:orderId?\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/addBillingAddress.ts",
    "content": "import {\n  insert,\n  select,\n  update,\n  getConnection,\n  startTransaction,\n  commit,\n  rollback,\n  PoolClient\n} from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { hookable } from '../../../lib/util/hookable.js';\nimport { Address } from '../../../types/customerAddress.js';\nimport { validateAddress } from '../../customer/services/customer/address/addressValidators.js';\n\ninterface BillingAddress extends Address {\n  /**\n   * The ID of the billing address\n   */\n  cart_address_id: number;\n}\n\n/**\n * Add billing address to cart service.\n * This service validates the address and saves the billing address to the cart.\n *\n * @param {string} cartUUID - The UUID of the cart to add the billing address to\n * @param {Address} addressData - The billing address data to be saved\n * @param {Record<string, unknown>} context - Additional context for hooks and extensions\n * @throws {Error} If cart does not exist or address validation fails\n * @returns {Promise<Address>} The newly created address object\n */\nasync function addBillingAddressService<\n  T extends Address = Address,\n  R = BillingAddress\n>(\n  cartUUID: string,\n  addressData: T,\n  context: Record<string, unknown> = {}\n): Promise<R> {\n  if (!cartUUID || typeof cartUUID !== 'string') {\n    throw new Error('Cart UUID is required');\n  }\n\n  if (!addressData || typeof addressData !== 'object') {\n    throw new Error('Address data is required');\n  }\n\n  if (typeof context !== 'object' || context === null) {\n    throw new Error('Context must be an object');\n  }\n\n  // Get database connection for transaction\n  const connection = await getConnection(pool);\n\n  try {\n    await startTransaction(connection);\n\n    // Verify cart exists and is active\n    const cart = await select()\n      .from('cart')\n      .where('uuid', '=', cartUUID)\n      .andWhere('status', '=', true)\n      .load(connection);\n\n    if (!cart) {\n      throw new Error('Cart not found or not active');\n    }\n\n    // Validate address\n    const validationResult = validateAddress(addressData);\n\n    if (!validationResult.valid) {\n      const errorMessage =\n        validationResult.errors?.[0] || 'Invalid address data';\n      throw new Error(errorMessage);\n    }\n\n    // Save address to database\n    const savedAddress = await hookable(saveBillingAddress, {\n      cartUUID,\n      addressData,\n      cart,\n      ...context\n    })(addressData, connection);\n\n    // Update cart with billing address\n    await hookable(updateCartWithAddress, {\n      cartUUID,\n      addressData,\n      cart,\n      savedAddress,\n      ...context\n    })(cart.cart_id, savedAddress.cart_address_id, connection);\n\n    await commit(connection);\n\n    return savedAddress as R;\n  } catch (error) {\n    await rollback(connection);\n    throw error;\n  }\n}\n\n/**\n * Save billing address to database\n */\nasync function saveBillingAddress(\n  addressData: Address,\n  connection: PoolClient\n) {\n  // Save address to database\n  const result = await insert('cart_address')\n    .given(addressData)\n    .execute(connection);\n\n  // Get the saved address\n  const savedAddress = await select()\n    .from('cart_address')\n    .where('cart_address_id', '=', result.insertId)\n    .load(connection);\n\n  return savedAddress;\n}\n\n/**\n * Update cart with billing address\n */\nasync function updateCartWithAddress(\n  cartId: number,\n  addressId: number,\n  connection: PoolClient\n) {\n  await update('cart')\n    .given({\n      billing_address_id: addressId\n    })\n    .where('cart_id', '=', cartId)\n    .execute(connection);\n}\n\n/**\n * Hookable wrapper for the addBillingAddress service.\n * This allows third-party extensions to hook into the billing address addition flow.\n */\nexport const addBillingAddress = async (\n  cartUUID: string,\n  addressData: Address,\n  context: Record<string, unknown> = {}\n) => {\n  const result = await hookable(addBillingAddressService, {\n    cartUUID,\n    addressData,\n    ...context\n  })(cartUUID, addressData, context);\n  return result;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/addCartItem.ts",
    "content": "import { hookable } from '../../../lib/util/hookable.js';\nimport { getValue } from '../../../lib/util/registry.js';\nimport { Cart, Item } from './cart/Cart.js';\n\nasync function addCartItem(\n  cart: Cart,\n  productID: number,\n  qty: number | string,\n  context: Record<string, unknown> = {}\n): Promise<Item> {\n  if (typeof context !== 'object' || context === null) {\n    throw new Error('Context must be an object');\n  }\n  const newItem = await cart.createItem(productID, parseInt(qty as string, 10));\n  context.cartData = cart.export();\n  context.itemData = newItem.export();\n  const item = await getValue('cartItemBeforeAdd', newItem, context);\n\n  if (item.hasError()) {\n    // Get the first error from the item.getErrors() object\n    throw new Error(Object.values(item.getErrors())[0]);\n  } else {\n    let items = cart.getItems();\n    let duplicateItem;\n    for (let i = 0; i < items.length; i += 1) {\n      if (items[i].getData('product_sku') === item.getData('product_sku')) {\n        await items[i].setData(\n          'qty',\n          item.getData('qty') + items[i].getData('qty')\n        );\n        if (items[i].hasError()) {\n          throw new Error(Object.values(items[i].getErrors())[0]);\n        }\n        duplicateItem = items[i];\n      }\n    }\n\n    if (!duplicateItem) {\n      items = items.concat(item);\n    }\n    await cart.setData('items', items, true);\n    return duplicateItem || item;\n  }\n}\n\n/**\n * Add item to cart service. This service will add an item to the cart.\n * @param {Cart} cart - The cart object to which the item will be added\n * @param {number} productID - The SKU of the product to be added\n * @param {number|string} qty - The quantity of the product to be added\n * @param {Record<string, unknown>} context - The context object containing additional data\n * @returns {Promise<Item>} The item that was added to the cart\n * @throws {Error} If the context is not an object or if there is an error\n */\nexport default async (\n  cart: Cart,\n  productID: number,\n  qty: number | string,\n  context: Record<string, unknown>\n): Promise<Item> => {\n  const item = await hookable(addCartItem, context)(\n    cart,\n    productID,\n    qty,\n    context\n  );\n  return item;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/addShippingAddress.ts",
    "content": "import {\n  insert,\n  select,\n  update,\n  getConnection,\n  startTransaction,\n  commit,\n  rollback,\n  PoolClient\n} from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { hookable } from '../../../lib/util/hookable.js';\nimport { Address } from '../../../types/customerAddress.js';\nimport { validateAddress } from '../../customer/services/customer/address/addressValidators.js';\n\ninterface ShippingAddress extends Address {\n  /**\n   * The ID of the shipping address\n   */\n  cart_address_id: number;\n}\n/**\n * Add shipping address to cart service.\n * This service validates the address, checks shipping zones, and saves the address to the cart.\n *\n * @param {string} cartUUID - The UUID of the cart to add the shipping address to\n * @param {Address} addressData - The shipping address data to be saved\n * @param {Record<string, unknown>} context - Additional context for hooks and extensions\n * @throws {Error} If cart does not exist, address validation fails, or shipping zone is not available\n * @returns {Promise<Address>} The newly created address object\n */\nasync function addShippingAddressService<\n  T extends Address = Address,\n  R = ShippingAddress\n>(\n  cartUUID: string,\n  addressData: T,\n  context: Record<string, unknown> = {}\n): Promise<R> {\n  if (!cartUUID || typeof cartUUID !== 'string') {\n    throw new Error('Cart UUID is required');\n  }\n\n  if (!addressData || typeof addressData !== 'object') {\n    throw new Error('Address data is required');\n  }\n\n  if (typeof context !== 'object' || context === null) {\n    throw new Error('Context must be an object');\n  }\n\n  // Get database connection for transaction\n  const connection = await getConnection(pool);\n\n  try {\n    await startTransaction(connection);\n\n    // Verify cart exists and is active\n    const cart = await select()\n      .from('cart')\n      .where('uuid', '=', cartUUID)\n      .andWhere('status', '=', true)\n      .load(connection);\n\n    if (!cart) {\n      throw new Error('Cart not found or not active');\n    }\n\n    // Validate address\n    const validationResult = validateAddress(addressData);\n\n    if (!validationResult.valid) {\n      const errorMessage =\n        validationResult.errors?.[0] || 'Invalid address data';\n      throw new Error(errorMessage);\n    }\n\n    // // Find shipping zone for the address\n    // const shippingZone = await hookable(findShippingZone, {\n    //   cartUUID,\n    //   addressData,\n    //   cart,\n    //   ...context\n    // })(addressData, connection);\n\n    // if (!shippingZone) {\n    //   throw new Error('We do not ship to this address');\n    // }\n\n    // Save address to database\n    const savedAddress = await hookable(saveShippingAddress, {\n      cartUUID,\n      addressData,\n      cart,\n      //shippingZone,\n      ...context\n    })(addressData, connection);\n\n    // Update cart with shipping zone and address\n    await hookable(updateCartWithAddress, {\n      cartUUID,\n      addressData,\n      cart,\n      //shippingZone,\n      savedAddress,\n      ...context\n    })(cart.cart_id, savedAddress.cart_address_id, connection);\n\n    await commit(connection);\n\n    return savedAddress;\n  } catch (error) {\n    await rollback(connection);\n    throw error;\n  }\n}\n\n/**\n * Find shipping zone for the given address\n */\nasync function findShippingZone(addressData: Address, connection: PoolClient) {\n  const shippingZoneQuery = select().from('shipping_zone');\n  shippingZoneQuery\n    .leftJoin('shipping_zone_province')\n    .on(\n      'shipping_zone_province.zone_id',\n      '=',\n      'shipping_zone.shipping_zone_id'\n    );\n  shippingZoneQuery.where('shipping_zone.country', '=', addressData.country);\n\n  const shippingZoneProvinces = await shippingZoneQuery.execute(connection);\n  const shippingZone = shippingZoneProvinces.find(\n    (zone) => zone.province === addressData.province || zone.province === null\n  );\n\n  return shippingZone;\n}\n\n/**\n * Save shipping address to database\n */\nasync function saveShippingAddress(\n  addressData: Address,\n  connection: PoolClient\n) {\n  // Save address to database\n  const result = await insert('cart_address')\n    .given(addressData)\n    .execute(connection);\n\n  // Get the saved address\n  const savedAddress = await select()\n    .from('cart_address')\n    .where('cart_address_id', '=', result.insertId)\n    .load(connection);\n\n  return savedAddress;\n}\n\n/**\n * Update cart with shipping zone and address\n */\nasync function updateCartWithAddress(\n  cartId: number,\n  addressId: number,\n  connection: PoolClient\n) {\n  await update('cart')\n    .given({\n      shipping_address_id: addressId\n    })\n    .where('cart_id', '=', cartId)\n    .execute(connection);\n}\n\n/**\n * Hookable wrapper for the addShippingAddress service.\n * This allows third-party extensions to hook into the shipping address addition flow.\n */\nexport const addShippingAddress = async (\n  cartUUID: string,\n  addressData: Address,\n  context: Record<string, unknown> = {}\n) => {\n  const result = await hookable(addShippingAddressService, {\n    cartUUID,\n    addressData,\n    ...context\n  })(cartUUID, addressData, context);\n  return result;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/cart/Cart.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { v4 as uuidv4 } from 'uuid';\nimport { translate } from '../../../../lib/locale/translate/translate.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getValue, getValueSync } from '../../../../lib/util/registry.js';\nimport addCartItem from '../../../../modules/checkout/services/addCartItem.js';\nimport { DataObject } from '../../../../modules/checkout/services/cart/DataObject.js';\nimport removeCartItem from '../../../../modules/checkout/services/removeCartItem.js';\nimport updateCartItemQty from '../../../../modules/checkout/services/updateCartItemQty.js';\n\nclass Item extends DataObject {\n  #cart;\n\n  #product;\n\n  constructor(cart, initialData = {}) {\n    super(getValueSync('cartItemFields', []), initialData);\n    this.#cart = cart;\n  }\n\n  async getProduct() {\n    if (this.#product) {\n      return this.#product;\n    }\n    const loaderFunction = getValueSync('cartItemProductLoaderFunction');\n    const product = await loaderFunction(this.getData('product_id'));\n    this.#product = product;\n    return product;\n  }\n\n  getId() {\n    return this.getData('uuid');\n  }\n\n  getCart() {\n    return this.#cart;\n  }\n}\n\nclass Cart extends DataObject {\n  constructor(initialData = {}) {\n    const fields = getValueSync('cartFields', []);\n    super(fields, initialData);\n  }\n\n  getId() {\n    return this.getData('uuid');\n  }\n\n  /**\n   * @returns {Array<Item>}\n   */\n  getItems() {\n    return this.getData('items') ?? [];\n  }\n\n  /**\n   * @param {string||int} productID\n   * @param {int} qty\n   * @param {object} context\n   * @returns {Item}\n   * @throws {Error}\n   */\n  async addItem(productID, qty, context) {\n    const addedItem = await addCartItem(this, productID, qty, context);\n    return addedItem;\n  }\n\n  /**\n   * @param {string} uuid\n   * @returns {Item}\n   * @throws {Error}\n   */\n  async removeItem(uuid, context) {\n    const removedItem = await removeCartItem(this, uuid, context);\n    return removedItem;\n  }\n\n  /**\n   * @param {string} sku\n   * @returns {Item}\n   * @throws {Error}\n   */\n  async removeItemBySku(sku, context) {\n    const items = this.getItems();\n    const item = items.find((i) => i.getData('product_sku') === sku);\n    if (item) {\n      const removedItem = await removeCartItem(\n        this,\n        item.getData('uuid'),\n        context\n      );\n      return removedItem;\n    }\n    throw new Error('Item not found');\n  }\n\n  async updateItemQty(uuid, qty, action, context) {\n    const updatedItem = await updateCartItemQty(\n      this,\n      uuid,\n      qty,\n      action,\n      context\n    );\n    return updatedItem;\n  }\n\n  async updateItemQtyBySku(sku, qty, action, context) {\n    const items = this.getItems();\n    const item = items.find((i) => i.getData('product_sku') === sku);\n    if (item) {\n      const updatedItem = await updateCartItemQty(\n        this,\n        item.getData('uuid'),\n        qty,\n        action,\n        context\n      );\n      return updatedItem;\n    }\n    throw new Error('Item not found');\n  }\n\n  async createItem(productId, qty) {\n    // Make sure the qty is a number, not NaN and greater than 0\n    if (typeof qty !== 'number' || Number.isNaN(qty) || qty <= 0) {\n      throw new Error(translate('Invalid quantity'));\n    }\n    const item = new Item(this, {\n      product_id: productId,\n      qty\n    });\n    await item.build();\n    if (item.hasError()) {\n      // Get the first error from the item.getErrors() object\n      throw new Error(Object.values(item.getErrors())[0]);\n    } else {\n      return item;\n    }\n  }\n\n  getItem(uuid) {\n    const items = this.getItems();\n    return items.find((item) => item.getData('uuid') === uuid);\n  }\n\n  hasItemError() {\n    const items = this.getItems();\n    let flag = false;\n    for (let i = 0; i < items.length; i += 1) {\n      if (items[i].hasError()) {\n        flag = true;\n        break;\n      }\n    }\n\n    return flag;\n  }\n\n  hasError() {\n    return super.hasError() || this.hasItemError();\n  }\n\n  exportData() {\n    const data = this.export();\n    data.errors = Object.values(this.getErrors());\n    const items = this.getItems();\n    data.items = items.map((item) => {\n      const itemData = item.export();\n      itemData.errors = Object.values(item.getErrors());\n      return itemData;\n    });\n    return data;\n  }\n}\n\nasync function createNewCart(initialData) {\n  const cart = new Cart(initialData);\n  await cart.build();\n  return cart;\n}\n\nasync function getCart(uuid) {\n  const cart = await select().from('cart').where('uuid', '=', uuid).load(pool);\n  if (!cart || cart.status !== true) {\n    throw new Error('Cart not found');\n  }\n  const cartObject = new Cart(cart);\n  // Get the cart items\n  const itemsQuery = await select().from('cart_item');\n  itemsQuery.where('cart_id', '=', cart.cart_id);\n  itemsQuery.orderBy('cart_item_id', 'DESC');\n  const items = await itemsQuery.execute(pool);\n  // Build the cart items\n  const cartItems = await Promise.all(\n    items.map(async (item) => {\n      const cartItem = new Item(cartObject, {\n        ...item\n      });\n      await cartItem.build();\n      return cartItem;\n    })\n  );\n\n  const finalItems = await getValue('cartInitialItems', cartItems, {\n    cart: cartObject,\n    unique: uuidv4() // To make sure the value will not be cached\n  });\n  await cartObject.setData('items', finalItems);\n  return cartObject;\n}\n\nexport { Cart, Item, createNewCart, getCart };\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/cart/DataObject.js",
    "content": "import isEqualWith from 'lodash.isequalwith';\nimport { error } from '../../../../lib/log/logger.js';\n\nexport class DataObject {\n  #fields;\n\n  #data = {};\n\n  #errors = {};\n\n  #triggeredField;\n\n  #requestedValue;\n\n  constructor(fields, initialData = {}) {\n    this.#fields = fields;\n    this.#data = initialData;\n    this.isBuilding = false;\n  }\n\n  // Build the field value. This function will be called when the field value is changed\n  // If error is thrown, all changes will be rollback\n  async build() {\n    const _this = this;\n\n    // Keep current values for rollback\n    const values = { ...this.#data };\n\n    try {\n      this.isBuilding = true;\n      this.#errors = {};\n\n      for (let i = 0; i < this.#fields.length; i += 1) {\n        const field = this.#fields[i];\n        let value =\n          field.key === this.#triggeredField\n            ? this.#requestedValue\n            : this.#data[field.key];\n        // Execute the list of resolvers\n        for (let j = 0; j < field.resolvers.length; j += 1) {\n          const resolver = field.resolvers[j];\n\n          value = await resolver.call(_this, value);\n        }\n        this.#data[field.key] = value;\n      }\n      this.isBuilding = false;\n    } catch (e) {\n      error(e);\n      this.isBuilding = false;\n      // Rollback the changes\n      this.#data = { ...values };\n      throw e;\n    }\n  }\n\n  getTriggeredField() {\n    return this.#triggeredField;\n  }\n\n  getRequestedValue() {\n    return this.#requestedValue;\n  }\n\n  getData(key) {\n    const field = this.#fields.find((f) => f.key === key);\n    if (field === undefined) {\n      throw new Error(`Field ${key} not existed`);\n    }\n\n    return this.#data[field.key];\n  }\n\n  setError(key, message) {\n    if (!message) {\n      delete this.#errors[key];\n    } else {\n      this.#errors[key] = message;\n    }\n  }\n\n  async setData(key, value, force = false) {\n    this.#triggeredField = key;\n    this.#requestedValue = value;\n    if (this.isBuilding === true) {\n      throw new Error('Can not set value when object is building');\n    }\n    const field = this.#fields.find((f) => f.key === key);\n    if (field === undefined) {\n      throw new Error(`Field ${key} not existed`);\n    }\n\n    if (isEqualWith(this.#data[key], value) && !force) {\n      return value;\n    }\n\n    // Run the full build\n    await this.build();\n    const result = this.#data[key];\n    if (!isEqualWith(result, value)) {\n      throw new Error(\n        `Field resolvers returned different value - ${key}, ${value}, ${result}`\n      );\n    } else {\n      return value;\n    }\n  }\n\n  hasError() {\n    return Object.keys(this.#errors).length > 0;\n  }\n\n  getErrors() {\n    return this.#errors;\n  }\n\n  export() {\n    const data = {};\n    this.#fields.forEach((f) => {\n      data[f.key] = structuredClone(this.#data[f.key]);\n    });\n    if (this.hasError()) {\n      data.errors = Object.values(this.#errors);\n    } else {\n      data.errors = {};\n    }\n    return data;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/cart/registerCartBaseFields.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport axios from 'axios';\nimport { v4 as uuidv4 } from 'uuid';\nimport { normalizePort } from '../../../../bin/lib/normalizePort.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport { validateAddress } from '../../../../modules/customer/services/index.js';\nimport { getSetting } from '../../../../modules/setting/services/setting.js';\nimport { calculateTaxAmount } from '../../../../modules/tax/services/calculateTaxAmount.js';\nimport { getTaxPercent } from '../../../../modules/tax/services/getTaxPercent.js';\nimport { getTaxRates } from '../../../../modules/tax/services/getTaxRates.js';\nimport { getAvailablePaymentMethods } from '../getAvailablePaymentMethods.js';\nimport { toPrice } from '../toPrice.js';\n\nexport function registerCartBaseFields(fields) {\n  const newFields = fields.concat([\n    {\n      key: 'cart_id',\n      resolvers: [\n        async function resolver() {\n          return this.getData('cart_id');\n        }\n      ]\n    },\n    {\n      key: 'uuid',\n      resolvers: [\n        function resolver() {\n          const uuid = this.getData('uuid');\n          const key = uuidv4();\n          // Replace all '-' with '' from key\n          return uuid || key.replace(/-/g, '');\n        }\n      ],\n      dependencies: ['cart_id']\n    },\n    {\n      key: 'currency',\n      resolvers: [\n        async function resolver() {\n          const currency = getConfig('shop.currency', 'USD');\n          return currency;\n        }\n      ]\n    },\n    {\n      key: 'created_at',\n      resolvers: [\n        async function resolver() {\n          const createdAt = this.getData('created_at');\n          return createdAt;\n        }\n      ],\n      dependencies: ['cart_id']\n    },\n    {\n      key: 'updated_at',\n      resolvers: [\n        async function resolver() {\n          const updatedAt = this.getData('updated_at');\n          return updatedAt;\n        }\n      ],\n      dependencies: ['cart_id']\n    },\n    {\n      key: 'user_ip',\n      resolvers: [\n        async function resolver(ip) {\n          return ip;\n        }\n      ]\n    },\n    {\n      key: 'sid',\n      resolvers: [\n        async function resolver(sid) {\n          return sid;\n        }\n      ]\n    },\n    {\n      key: 'status',\n      resolvers: [\n        async function resolver() {\n          return 1;\n        }\n      ]\n    },\n    {\n      key: 'total_qty',\n      resolvers: [\n        async function resolver() {\n          let count = 0;\n          const items = this.getItems();\n          items.forEach((i) => {\n            count += parseInt(i.getData('qty'), 10);\n          });\n          return count;\n        }\n      ],\n      dependencies: ['items']\n    },\n    {\n      key: 'total_weight',\n      resolvers: [\n        async function resolver() {\n          let weight = 0;\n          const items = this.getItems();\n          items.forEach((i) => {\n            weight += i.getData('product_weight') * i.getData('qty');\n          });\n          return weight;\n        }\n      ],\n      dependencies: ['items']\n    },\n    {\n      key: 'tax_amount',\n      resolvers: [\n        async function resolver() {\n          // Sum all tax amount from items\n          let taxAmount = 0;\n          const items = this.getItems();\n          items.forEach((i) => {\n            taxAmount += i.getData('tax_amount');\n          });\n\n          return toPrice(taxAmount);\n        }\n      ],\n      dependencies: ['items', 'shipping_tax_amount']\n    },\n    {\n      key: 'tax_amount_before_discount',\n      resolvers: [\n        async function resolver() {\n          // Sum all tax amount from items\n          let taxAmount = 0;\n          const items = this.getItems();\n          items.forEach((i) => {\n            taxAmount += i.getData('tax_amount_before_discount');\n          });\n          return taxAmount;\n        }\n      ],\n      dependencies: ['items']\n    },\n    {\n      key: 'sub_total',\n      resolvers: [\n        async function resolver() {\n          let total = 0;\n          const items = this.getItems();\n          items.forEach((i) => {\n            total += i.getData('line_total');\n          });\n          return toPrice(total);\n        }\n      ],\n      dependencies: ['items']\n    },\n    {\n      key: 'sub_total_incl_tax',\n      resolvers: [\n        async function resolver() {\n          let total = 0;\n          const items = this.getItems();\n          items.forEach((i) => {\n            total += i.getData('line_total_incl_tax');\n          });\n          return toPrice(total);\n        }\n      ],\n      dependencies: ['items']\n    },\n    {\n      key: 'no_shipping_required',\n      resolvers: [\n        async function resolver() {\n          const total = 0;\n          const items = this.getItems();\n          return items.every((i) => i.getData('no_shipping_required') === true);\n        }\n      ],\n      dependencies: ['items']\n    },\n    {\n      key: 'shipping_zone_id',\n      resolvers: [\n        async function resolver() {\n          if (this.getData('no_shipping_required')) {\n            return null;\n          }\n          const addressData = this.getData('shipping_address');\n          if (!addressData?.country) {\n            return null;\n          }\n          const shippingZoneQuery = select().from('shipping_zone');\n          shippingZoneQuery\n            .leftJoin('shipping_zone_province')\n            .on(\n              'shipping_zone_province.zone_id',\n              '=',\n              'shipping_zone.shipping_zone_id'\n            );\n          shippingZoneQuery.where(\n            'shipping_zone.country',\n            '=',\n            addressData.country\n          );\n\n          const shippingZoneProvinces = await shippingZoneQuery.execute(pool);\n          const shippingZone = shippingZoneProvinces.find(\n            (zone) =>\n              zone.province === addressData.province || zone.province === null\n          );\n          if (!shippingZone) {\n            this.setError('shipping_address', 'We do not ship to this address');\n            return null;\n          } else {\n            this.setError('shipping_address', undefined);\n            return shippingZone.shipping_zone_id;\n          }\n        }\n      ],\n      dependencies: ['shipping_address', 'no_shipping_required']\n    },\n    {\n      key: 'shipping_address_id',\n      resolvers: [\n        async function resolver(shippingAddressId) {\n          if (this.getData('no_shipping_required')) {\n            return null;\n          }\n          return shippingAddressId;\n        }\n      ],\n      dependencies: ['cart_id', 'no_shipping_required']\n    },\n    {\n      key: 'shipping_address',\n      resolvers: [\n        async function resolver(address) {\n          if (this.getData('no_shipping_required')) {\n            return null;\n          }\n          if (!this.getData('shipping_address_id')) {\n            if (validateAddress(address)) {\n              return address;\n            }\n            return undefined;\n          } else {\n            const shippingAddress = await select()\n              .from('cart_address')\n              .where(\n                'cart_address_id',\n                '=',\n                this.getData('shipping_address_id')\n              )\n              .load(pool);\n            return shippingAddress;\n          }\n        }\n      ],\n      dependencies: ['shipping_address_id', 'no_shipping_required']\n    },\n    {\n      key: 'shipping_method',\n      resolvers: [\n        async function resolver(shippingMethod) {\n          if (this.getData('no_shipping_required')) {\n            return null;\n          }\n          if (!shippingMethod) {\n            return null;\n          }\n          if (\n            !this.getData('shipping_address') ||\n            !this.getData('shipping_zone_id')\n          ) {\n            return null;\n          }\n          // By default, EverShop supports free shipping and flat rate shipping method\n          // Load shipping method from database\n          const shippingMethodQuery = select().from('shipping_method');\n          shippingMethodQuery\n            .innerJoin('shipping_zone_method')\n            .on(\n              'shipping_method.shipping_method_id',\n              '=',\n              'shipping_zone_method.method_id'\n            );\n          shippingMethodQuery\n            .where('uuid', '=', shippingMethod)\n            .and('is_enabled', '=', true)\n            .and(\n              'shipping_zone_method.zone_id',\n              '=',\n              this.getData('shipping_zone_id')\n            );\n          const method = await shippingMethodQuery.load(pool);\n          if (!method) {\n            return null;\n          } else {\n            // Validate shipping method using max weight and max price, min weight and min price\n            const { max, min } = method;\n            const total_weight = this.getData('total_weight');\n            const sub_total = this.getData('sub_total');\n            let flag = false;\n\n            if (method.condition_type === 'weight') {\n              if (\n                total_weight >= toPrice(min) &&\n                total_weight <= toPrice(max)\n              ) {\n                flag = true;\n              }\n            }\n            if (method.condition_type === 'price') {\n              if (sub_total >= toPrice(min) && sub_total <= toPrice(max)) {\n                flag = true;\n              }\n            }\n            if (method.condition_type === null) {\n              flag = true;\n            }\n            if (flag === false) {\n              this.setError('shipping_method', 'Shipping method is invalid');\n              return null;\n            } else {\n              return method.uuid;\n            }\n          }\n        }\n      ],\n      dependencies: [\n        'shipping_zone_id',\n        'shipping_address',\n        'sub_total',\n        'total_weight',\n        'total_qty',\n        'no_shipping_required'\n      ]\n    },\n    {\n      key: 'shipping_method_name',\n      resolvers: [\n        async function resolver() {\n          if (this.getData('no_shipping_required')) {\n            return null;\n          }\n          if (!this.getData('shipping_method')) {\n            return null;\n          } else {\n            const shippingMethod = await select()\n              .from('shipping_method')\n              .where('uuid', '=', this.getData('shipping_method'))\n              .load(pool);\n            return shippingMethod.name;\n          }\n        }\n      ],\n      dependencies: ['shipping_method', 'no_shipping_required']\n    },\n    {\n      key: 'shipping_fee_draft',\n      resolvers: [\n        async function resolver() {\n          if (this.getData('no_shipping_required')) {\n            return null;\n          }\n          if (!this.getData('shipping_method')) {\n            return 0;\n          } else {\n            // Check if the coupon is free shipping\n            const coupon = await select()\n              .from('coupon')\n              .where('coupon.coupon', '=', this.getData('coupon'))\n              .load(pool);\n            if (coupon && coupon.free_shipping) {\n              return 0;\n            }\n            const shippingMethodQuery = select().from('shipping_method');\n            shippingMethodQuery\n              .innerJoin('shipping_zone_method')\n              .on(\n                'shipping_method.shipping_method_id',\n                '=',\n                'shipping_zone_method.method_id'\n              );\n            shippingMethodQuery\n              .where('uuid', '=', this.getData('shipping_method'))\n              .and(\n                'shipping_zone_method.zone_id',\n                '=',\n                this.getData('shipping_zone_id')\n              );\n            const shippingMethod = await shippingMethodQuery.load(pool);\n            // Check if the method is flat rate\n            if (shippingMethod.cost !== null) {\n              return toPrice(shippingMethod.cost);\n            } else if (shippingMethod.calculate_api) {\n              // Call the API of the shipping method to calculate the shipping fee. This is an internal API\n              // use axios to call the API\n              // Ignore http status error\n              const port = normalizePort();\n              let api = `http://localhost:${port}`;\n              try {\n                api += buildUrl(shippingMethod.calculate_api, {\n                  cart_id: this.getData('uuid'),\n                  method_id: shippingMethod.uuid\n                });\n              } catch (e) {\n                throw new Error(\n                  `Your shipping calculate API ${shippingMethod.calculate_api} is invalid`\n                );\n              }\n              const response = await axios.get(api);\n              if (response.status < 400) {\n                return toPrice(response.data.data.cost);\n              } else {\n                this.setError('shipping_fee_excl_tax', response.data.message);\n                return 0;\n              }\n            } else if (shippingMethod.weight_based_cost) {\n              const totalWeight = this.getData('total_weight');\n              const weightBasedCost = shippingMethod.weight_based_cost\n                .map(({ min_weight, cost }) => ({\n                  min_weight: parseFloat(min_weight),\n                  cost: toPrice(cost)\n                }))\n                .sort((a, b) => a.min_weight - b.min_weight);\n\n              let cost = 0;\n              for (let i = 0; i < weightBasedCost.length; i += 1) {\n                if (totalWeight >= weightBasedCost[i].min_weight) {\n                  cost = weightBasedCost[i].cost;\n                }\n              }\n              return toPrice(cost);\n            } else if (shippingMethod.price_based_cost) {\n              const subTotal = this.getData('sub_total');\n              const priceBasedCost = shippingMethod.price_based_cost\n                .map(({ min_price, cost }) => ({\n                  min_price: toPrice(min_price),\n                  cost: toPrice(cost)\n                }))\n                .sort((a, b) => a.min_price - b.min_price);\n              let cost = 0;\n              for (let i = 0; i < priceBasedCost.length; i += 1) {\n                if (subTotal >= priceBasedCost[i].min_price) {\n                  cost = priceBasedCost[i].cost;\n                }\n              }\n              return toPrice(cost);\n            } else {\n              this.setError(\n                'shipping_fee_excl_tax',\n                'Could not calculate shipping fee'\n              );\n              return 0;\n            }\n          }\n        }\n      ],\n      dependencies: ['shipping_method', 'no_shipping_required']\n    },\n    {\n      key: 'shipping_fee_tax_percent',\n      resolvers: [\n        async function resolver() {\n          if (this.getData('no_shipping_required')) {\n            return null;\n          }\n          if (!this.getData('shipping_method')) {\n            return null;\n          }\n          let shippingTaxClass = await getSetting(\n            'defaultShippingTaxClassId',\n            ''\n          );\n\n          // -1: Protional allocation based on the items\n          // 0: Highest tax rate based on the items\n          if (shippingTaxClass === '') {\n            return 0;\n          } else {\n            shippingTaxClass = parseInt(shippingTaxClass, 10);\n            if (shippingTaxClass > 0) {\n              const taxClass = await select()\n                .from('tax_class')\n                .where('tax_class_id', '=', shippingTaxClass)\n                .load(pool);\n\n              if (!taxClass) {\n                return 0;\n              } else {\n                const shippingAddress = this.getData('shipping_address');\n                const percentage = getTaxPercent(\n                  await getTaxRates(\n                    shippingTaxClass,\n                    shippingAddress.country,\n                    shippingAddress.province,\n                    shippingAddress.postcode\n                  )\n                );\n                return percentage;\n              }\n            } else {\n              const items = this.getItems();\n              let percentage = 0;\n              if (shippingTaxClass === 0) {\n                // Highest tax rate\n                items.forEach((item) => {\n                  if (item.getData('tax_percent') > percentage) {\n                    percentage = item.getData('tax_percent');\n                  }\n                });\n              } else {\n                items.forEach((item) => {\n                  // Protional allocation\n                  const itemTotal =\n                    item.getData('final_price') * item.getData('qty');\n                  percentage +=\n                    (itemTotal / this.getData('sub_total')) *\n                    item.getData('tax_percent');\n                });\n              }\n              return percentage;\n            }\n          }\n        }\n      ],\n      dependencies: ['sub_total', 'shipping_method', 'no_shipping_required']\n    },\n    {\n      key: 'shipping_tax_amount',\n      resolvers: [\n        async function resolver() {\n          if (this.getData('no_shipping_required')) {\n            return 0;\n          }\n          const priceIncludingTax = getConfig(\n            'pricing.tax.price_including_tax',\n            false\n          );\n          if (this.getData('shipping_fee_draft') === 0) {\n            return 0;\n          }\n          const shippingFeeTax = calculateTaxAmount(\n            this.getData('shipping_fee_tax_percent'),\n            this.getData('shipping_fee_draft'),\n            1,\n            priceIncludingTax\n          );\n          return toPrice(shippingFeeTax);\n        }\n      ],\n      dependencies: [\n        'shipping_fee_draft',\n        'shipping_fee_tax_percent',\n        'no_shipping_required'\n      ]\n    },\n    {\n      key: 'shipping_fee_excl_tax',\n      resolvers: [\n        async function resolver() {\n          if (this.getData('no_shipping_required')) {\n            return 0;\n          }\n          const priceIncludingTax = getConfig(\n            'pricing.tax.price_including_tax',\n            false\n          );\n          if (this.getData('shipping_fee_draft') === 0) {\n            return 0;\n          }\n          if (priceIncludingTax === false) {\n            return this.getData('shipping_fee_draft');\n          } else {\n            const shippingFeeTax = calculateTaxAmount(\n              this.getData('shipping_fee_tax_percent'),\n              this.getData('shipping_fee_draft'),\n              1,\n              priceIncludingTax\n            );\n            return toPrice(this.getData('shipping_fee_draft') - shippingFeeTax);\n          }\n        }\n      ],\n      dependencies: [\n        'shipping_fee_tax_percent',\n        'shipping_fee_draft',\n        'no_shipping_required'\n      ]\n    },\n    {\n      key: 'shipping_fee_incl_tax',\n      resolvers: [\n        async function resolver() {\n          if (this.getData('no_shipping_required')) {\n            return 0;\n          }\n          const priceIncludingTax = getConfig(\n            'pricing.tax.price_including_tax',\n            false\n          );\n          if (this.getData('shipping_fee_draft') === 0) {\n            return 0;\n          }\n          if (priceIncludingTax === true) {\n            return this.getData('shipping_fee_draft');\n          } else {\n            return toPrice(\n              this.getData('shipping_fee_excl_tax') +\n                this.getData('shipping_tax_amount')\n            );\n          }\n        }\n      ],\n      dependencies: [\n        'shipping_fee_excl_tax',\n        'shipping_tax_amount',\n        'shipping_fee_draft',\n        'no_shipping_required'\n      ]\n    },\n    {\n      key: 'shipping_note',\n      resolvers: [\n        async function resolver(note) {\n          return note;\n        }\n      ]\n    },\n    {\n      key: 'total_tax_amount', // This field should contain the total tax amount of the cart including tax of items and shipping fee\n      resolvers: [\n        function resolver() {\n          return toPrice(\n            this.getData('tax_amount') + this.getData('shipping_tax_amount')\n          );\n        }\n      ],\n      dependencies: ['tax_amount', 'shipping_tax_amount']\n    },\n    {\n      key: 'billing_address_id',\n      resolvers: [\n        async function resolver(billingAddressId) {\n          return billingAddressId;\n        }\n      ],\n      dependencies: ['cart_id']\n    },\n    {\n      key: 'billing_address',\n      resolvers: [\n        async function resolver(address) {\n          if (!this.getData('billing_address_id')) {\n            if (validateAddress(address)) {\n              return address;\n            }\n            return undefined;\n          } else {\n            const billingAddress = await select()\n              .from('cart_address')\n              .where('cart_address_id', '=', this.getData('billing_address_id'))\n              .load(pool);\n            return billingAddress;\n          }\n        }\n      ],\n      dependencies: ['billing_address_id']\n    },\n    {\n      key: 'payment_method',\n      resolvers: [\n        async function resolver(paymentMethod) {\n          const methods = await getAvailablePaymentMethods();\n          if (\n            paymentMethod &&\n            methods.map((m) => m.code).includes(paymentMethod)\n          ) {\n            this.setError('payment_method', undefined);\n            return paymentMethod;\n          } else if (\n            paymentMethod &&\n            !methods.map((m) => m.code).includes(paymentMethod)\n          ) {\n            this.setError(\n              'payment_method',\n              `Payment method ${paymentMethod} is not available`\n            );\n            return null;\n          } else if (paymentMethod === null) {\n            this.setError('payment_method', 'Payment method is required');\n            return null;\n          }\n        }\n      ]\n    },\n    {\n      key: 'payment_method_name',\n      resolvers: [\n        async function resolver() {\n          const methods = await getAvailablePaymentMethods();\n          const method = methods.find(\n            (m) => m.code === this.getData('payment_method')\n          );\n          return method ? method.name : this.getData('payment_method');\n        }\n      ],\n      dependencies: ['payment_method']\n    },\n    {\n      key: 'items',\n      resolvers: [\n        async function resolver() {\n          const triggeredField = this.getTriggeredField();\n          const requestedValue = this.getRequestedValue();\n          const items = [];\n          if (triggeredField === 'items') {\n            requestedValue.forEach((item) => {\n              // If this is just new added item, add it to the list\n              if (!item.getId() && !item.hasError()) {\n                items.push(item);\n              } else {\n                items.push(item);\n              }\n            });\n            return items;\n          } else {\n            return this.getData('items');\n          }\n        }\n      ],\n      dependencies: ['cart_id', 'currency']\n    }\n  ]);\n  return newFields;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/cart/registerCartItemBaseFields.js",
    "content": "import { v4 as uuidv4 } from 'uuid';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport { calculateTaxAmount } from '../../../../modules/tax/services/calculateTaxAmount.js';\nimport { toPrice } from '../toPrice.js';\n\nexport function registerCartItemBaseFields(fields) {\n  const newFields = fields.concat([\n    {\n      key: 'cart_item_id',\n      resolvers: [\n        async function resolver() {\n          return this.getData('cart_item_id');\n        }\n      ]\n    },\n    {\n      key: 'uuid',\n      resolvers: [\n        async function resolver() {\n          return this.getData('uuid') ?? uuidv4();\n        }\n      ]\n    },\n    {\n      key: 'cart_id',\n      resolvers: [\n        async function resolver() {\n          const cart = this.getCart();\n          return cart.getData('cart_id');\n        }\n      ],\n      dependencies: ['cart_item_id']\n    },\n    {\n      key: 'product_id',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          if (product.status === false) {\n            this.setError('product_id', 'This product is not available');\n          }\n          return product.product_id;\n        }\n      ]\n    },\n    {\n      key: 'product_uuid',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          return product.uuid;\n        }\n      ]\n    },\n    {\n      key: 'product_sku',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          return product.sku;\n        }\n      ]\n    },\n    {\n      key: 'group_id',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          return parseInt(product.group_id, 10) ?? null;\n        }\n      ],\n      dependencies: ['product_id']\n    },\n    {\n      key: 'category_id',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          return product.category_id ? parseInt(product.category_id, 10) : null;\n        }\n      ],\n      dependencies: ['product_id']\n    },\n    {\n      key: 'product_name',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          return product.name ?? null;\n        }\n      ],\n      dependencies: ['product_id']\n    },\n    {\n      key: 'thumbnail',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          if (product.origin_image) {\n            return product.origin_image;\n          } else {\n            return null;\n          }\n        }\n      ],\n      dependencies: ['product_id']\n    },\n    {\n      key: 'product_weight',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          return parseFloat(product.weight) ?? null;\n        }\n      ],\n      dependencies: ['product_id']\n    },\n    {\n      key: 'no_shipping_required',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          return product.no_shipping_required ?? false;\n        }\n      ],\n      dependencies: ['product_id']\n    },\n    {\n      key: 'tax_class_id',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          return product.tax_class ?? null;\n        }\n      ],\n      dependencies: ['product_id']\n    },\n\n    {\n      key: 'product_price',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          const catalogPriceInclTax = getConfig(\n            'pricing.tax.price_including_tax',\n            false\n          );\n          if (catalogPriceInclTax) {\n            const taxAmount = calculateTaxAmount(\n              this.getData('tax_percent'),\n              product.price,\n              1,\n              true\n            );\n            return toPrice(product.price - taxAmount);\n          } else {\n            return toPrice(product.price);\n          }\n        }\n      ],\n      dependencies: ['product_id', 'tax_percent']\n    },\n    {\n      key: 'tax_amount_before_discount',\n      resolvers: [\n        async function resolver() {\n          const catalogPriceInclTax = getConfig(\n            'pricing.tax.price_including_tax',\n            false\n          );\n          if (catalogPriceInclTax) {\n            return calculateTaxAmount(\n              this.getData('tax_percent'),\n              this.getData('product_price_incl_tax'),\n              this.getData('qty'),\n              true\n            );\n          } else {\n            return calculateTaxAmount(\n              this.getData('tax_percent'),\n              this.getData('product_price'),\n              this.getData('qty')\n            );\n          }\n        }\n      ],\n      dependencies: [\n        'tax_percent',\n        'product_price',\n        'product_price_incl_tax',\n        'qty'\n      ]\n    },\n    {\n      key: 'tax_amount',\n      resolvers: [\n        async function resolver() {\n          const priceIncludingTax = getConfig(\n            'pricing.tax.price_including_tax',\n            false\n          );\n          const discountAmount = this.getData('discount_amount');\n          const discountAmountPerUnit = discountAmount / this.getData('qty');\n          const finalPricePerUnit = priceIncludingTax\n            ? this.getData('final_price_incl_tax') - discountAmountPerUnit\n            : this.getData('final_price') - discountAmountPerUnit;\n          return calculateTaxAmount(\n            this.getData('tax_percent'),\n            finalPricePerUnit,\n            this.getData('qty'),\n            priceIncludingTax\n          );\n        }\n      ],\n      dependencies: [\n        'discount_amount',\n        'tax_percent',\n        'final_price',\n        'final_price_incl_tax',\n        'qty'\n      ]\n    },\n    {\n      key: 'product_price_incl_tax',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          const catalogPriceInclTax = getConfig(\n            'pricing.tax.price_including_tax',\n            false\n          );\n\n          if (catalogPriceInclTax) {\n            return toPrice(product.price);\n          } else {\n            const taxAmount = calculateTaxAmount(\n              this.getData('tax_percent'),\n              this.getData('product_price'),\n              1\n            );\n            return toPrice(this.getData('product_price') + taxAmount);\n          }\n        }\n      ],\n      dependencies: ['product_price', 'tax_percent']\n    },\n    {\n      key: 'qty',\n      resolvers: [\n        async function resolver() {\n          const triggeredField = this.getTriggeredField();\n          const requestedValue = this.getRequestedValue();\n          const qty =\n            triggeredField === 'qty' ? requestedValue : this.getData('qty');\n          const product = await this.getProduct();\n          if (product.manage_stock === true && product.qty < 1) {\n            this.setError('qty', 'This item is out of stock');\n          } else if (product.manage_stock === true && product.qty < qty) {\n            this.setError('qty', 'We do not have enough stock');\n          }\n\n          return parseInt(qty, 10) ?? null;\n        }\n      ]\n    },\n    {\n      key: 'final_price',\n      resolvers: [\n        async function resolver() {\n          return this.getData('product_price'); // TODO This price should include the custom option price\n        }\n      ],\n      dependencies: ['product_price']\n    },\n    {\n      key: 'final_price_incl_tax',\n      resolvers: [\n        async function resolver() {\n          return this.getData('product_price_incl_tax');\n        }\n      ],\n      dependencies: ['product_price_incl_tax']\n    },\n    {\n      key: 'line_total',\n      resolvers: [\n        async function resolver() {\n          return this.getData('final_price') * this.getData('qty');\n        }\n      ],\n      dependencies: ['final_price', 'qty']\n    },\n    {\n      key: 'line_total_incl_tax',\n      resolvers: [\n        async function resolver() {\n          return this.getData('final_price_incl_tax') * this.getData('qty');\n        }\n      ],\n      dependencies: ['final_price_incl_tax', 'qty']\n    },\n    {\n      key: 'variant_group_id',\n      resolvers: [\n        async function resolver() {\n          const product = await this.getProduct();\n          return product.variant_group_id ?? null;\n        }\n      ],\n      dependencies: ['product_id']\n    },\n    {\n      key: 'removeUrl',\n      resolvers: [\n        async function resolver() {\n          if (this.getData('cart_item_id')) {\n            return buildUrl('removeMineCartItem', {\n              item_id: this.getData('uuid')\n            });\n          } else {\n            return undefined;\n          }\n        }\n      ],\n      dependencies: ['cart_item_id', 'uuid']\n    }\n  ]);\n  return newFields;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/cart/sortFields.js",
    "content": "import Topo from '@hapi/topo';\n\nexport function sortFields(fields) {\n  // Filter the fields.\n  const newFields = [];\n  fields.forEach((f) => {\n    const field = newFields.find((m) => m.key === f.key);\n    if (!field) {\n      newFields.push(f);\n    } else {\n      // Push the resolver and dependencies to the existed field\n      // Check if resolvers is an array\n      const resolvers = f.resolvers || [];\n      const dependencies = f.dependencies || [];\n      if (!Array.isArray(resolvers)) {\n        field.resolvers = [...field.resolvers, resolvers];\n      } else {\n        field.resolvers = [...field.resolvers, ...resolvers];\n      }\n      field.dependencies = field.dependencies\n        ? [...dependencies, ...field.dependencies]\n        : dependencies;\n    }\n  });\n  // // eslint-disable-next-line no-shadow\n  // const refinedFields = newFields.filter((f, index, currentArray) => {\n  //   if (!f.dependencies) {\n  //     return true;\n  //   }\n  //   const { dependencies } = f;\n  //   let flag = true;\n  //   // Field will be removed if it's dependency missing\n  //   dependencies.forEach((d) => {\n  //     if (flag === false || currentArray.findIndex((m) => m.key === d) === -1) {\n  //       flag = false;\n  //     }\n  //   });\n\n  //   return flag;\n  // });\n  const sorter = new Topo.Sorter();\n  newFields.forEach((f) => {\n    sorter.add(f.key, { before: [], after: f.dependencies, group: f.key });\n  });\n\n  const finalFields = sorter.nodes.map((key) => {\n    const index = newFields.findIndex((f) => f.key === key);\n    const f = newFields[index];\n    return { ...f };\n  });\n\n  return finalFields;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/checkout.ts",
    "content": "import { hookable } from '../../../lib/util/hookable.js';\nimport { CheckoutData } from '../../../types/checkoutData.js';\nimport { addBillingAddress } from './addBillingAddress.js';\nimport { addShippingAddress } from './addShippingAddress.js';\nimport { getCartByUUID } from './getCartByUUID.js';\nimport { createOrder } from './orderCreator.js';\nimport { saveCart } from './saveCart.js';\n\nasync function checkoutService(\n  cartId: string,\n  data: CheckoutData,\n  context: Record<string, unknown> = {}\n) {\n  // Validate if cart is exist (this time we use getCartByUUID function)\n  const cart = await getCartByUUID(cartId);\n\n  if (!cart) {\n    throw new Error('Cart not found');\n  }\n\n  // Add customer info\n  if (data.customer?.id) {\n    await cart.setData('customer_id', data.customer.id);\n  }\n  if (data.customer?.email) {\n    await cart.setData('customer_email', data.customer.email);\n  }\n  if (data.customer?.fullName) {\n    await cart.setData('customer_full_name', data.customer.fullName);\n  }\n\n  // Add Shipping Address\n  if (data.shippingAddress) {\n    const shippingAddress = await addShippingAddress(\n      cart.getData('uuid'),\n      data.shippingAddress,\n      context\n    );\n    await cart.setData('shipping_address_id', shippingAddress.cart_address_id);\n  }\n\n  // Add Billing Address\n  if (data.billingAddress) {\n    const billingAddress = await addBillingAddress(\n      cart.getData('uuid'),\n      data.billingAddress,\n      context\n    );\n    await cart.setData('billing_address_id', billingAddress.cart_address_id);\n  }\n\n  // Add Payment Method\n  if (data.paymentMethod) {\n    await cart.setData('payment_method', data.paymentMethod);\n  }\n\n  // Add Shipping Method (use cart.setData)\n  if (data.shippingMethod) {\n    await cart.setData('shipping_method', data.shippingMethod);\n  }\n\n  // Add Note (use cart.setData)\n  if (data.note) {\n    await cart.setData('note', data.note);\n  }\n  await saveCart(cart);\n  const order = await createOrder(cart);\n  return order;\n}\n\n/**\n * Hookable wrapper for the checkout service.\n * This allows third-party extensions to hook before or after the checkout process.\n */\nexport const checkout = async (\n  cartId: string,\n  data: CheckoutData,\n  context: Record<string, unknown> = {}\n) => {\n  const result = await hookable(checkoutService, {\n    cartId,\n    data,\n    ...context\n  })(cartId, data, context);\n  return result;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/createNewCart.ts",
    "content": "import { CustomerData } from '../../../modules/customer/services/customer/createCustomer.js';\nimport { Cart, createNewCart as create } from './cart/Cart.js';\n\nexport /**\n * Create a new cart for the customer\n * @param sid - The session ID\n * @param customer - The customer data\n * @returns The created cart\n */\nasync function createNewCart(\n  sid: string,\n  customer: CustomerData & { customer_id?: number } = {}\n): Promise<Cart> {\n  // Extract the customer info\n  const {\n    customer_id: customer_id,\n    email: customer_email,\n    group_id: customer_group_id,\n    full_name: customer_full_name\n  } = customer;\n  const cart = await create({\n    sid,\n    customer_id,\n    customer_email,\n    customer_group_id,\n    customer_full_name\n  });\n  return cart;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/getAvailablePaymentMethods.ts",
    "content": "import { addProcessor, getValue } from '../../../lib/util/registry.js';\n\nexport type PaymentMethodInfo = {\n  code: string;\n  name: string;\n};\n\nexport type PaymentMethodFactory = {\n  init: () => PaymentMethodInfo | Promise<PaymentMethodInfo>;\n  validator?: () => boolean | Promise<boolean>;\n};\n\n/**\n * This function retrieves the available payment methods from the registry.\n * @returns A promise that resolves to an array of payment methods.\n */\nexport async function getAvailablePaymentMethods(): Promise<\n  PaymentMethodInfo[]\n> {\n  // TODO: Support different payment methods per cart\n  const methods = await getValue(\n    'checkoutPaymentMethods',\n    [] as PaymentMethodFactory[],\n    {},\n    (methods: PaymentMethodFactory[]) => {\n      return (\n        Array.isArray(methods) &&\n        methods.every(\n          (method) =>\n            typeof method.init === 'function' &&\n            typeof method.validator === 'function'\n        )\n      );\n    }\n  );\n\n  const applicableMethods: PaymentMethodInfo[] = [];\n  for (const method of methods) {\n    const methodInfo = await method.init();\n    if (applicableMethods.some((m) => m.code === methodInfo.code)) {\n      throw new Error(`Duplicate payment method code: ${methodInfo.code}`);\n    }\n    if (!method.validator || (await method.validator())) {\n      applicableMethods.push(methodInfo);\n    }\n  }\n  return applicableMethods;\n}\n\n/**\n * Registers a new payment method.\n * @param factory - The factory object that contains the init and optional validator methods.\n * @throws Will throw an error if the factory does not have an init method.\n */\nexport function registerPaymentMethod(factory: PaymentMethodFactory): void {\n  addProcessor('checkoutPaymentMethods', (methods: PaymentMethodFactory[]) => {\n    return [...methods, factory];\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/getAvailableShippingMethods.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport axios from 'axios';\nimport { normalizePort } from '../../../bin/lib/normalizePort.js';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../lib/router/buildUrl.js';\nimport { toPrice } from './toPrice.js';\n\nexport const getAvailableShippingMethods = async (\n  cartId: string,\n  country?: string,\n  province?: string,\n  postcode?: string\n) => {\n  const cart = await select()\n    .from('cart')\n    .where('uuid', '=', cartId)\n    .and('status', '=', 't')\n    .load(pool);\n  if (!cart) {\n    throw new Error('Cart not found');\n  }\n\n  const shippingAddress = await select()\n    .from('cart_address')\n    .where('cart_address_id', '=', cart.shipping_address_id)\n    .load(pool);\n\n  if (!country && !shippingAddress?.country) {\n    return [];\n  }\n\n  const zoneQuery = select().from('shipping_zone');\n  zoneQuery\n    .leftJoin('shipping_zone_province')\n    .on(\n      'shipping_zone_province.zone_id',\n      '=',\n      'shipping_zone.shipping_zone_id'\n    );\n  if (province || shippingAddress?.province) {\n    zoneQuery\n      .where(\n        'shipping_zone_province.province',\n        '=',\n        province || shippingAddress?.province\n      )\n      .or('shipping_zone_province.province', 'IS NULL', null);\n  } else {\n    zoneQuery.where('shipping_zone_province.province', 'IS NULL', null);\n  }\n\n  zoneQuery.andWhere(\n    'shipping_zone.country',\n    '=',\n    country || shippingAddress?.country\n  );\n\n  const zone = await zoneQuery.load(pool);\n  if (!zone) {\n    return [];\n  }\n\n  const methodsQuery = select().from('shipping_method');\n  methodsQuery\n    .leftJoin('shipping_zone_method')\n    .on(\n      'shipping_zone_method.method_id',\n      '=',\n      'shipping_method.shipping_method_id'\n    );\n  methodsQuery\n    .where('shipping_zone_method.zone_id', '=', zone.shipping_zone_id)\n    .and('shipping_zone_method.is_enabled', '=', 't');\n  let methods = await methodsQuery.execute(pool);\n\n  methods = methods.filter((method) => {\n    if (!method.condition_type) {\n      return true;\n    }\n    if (method.condition_type === 'price') {\n      return (\n        toPrice(method.min) <= cart.sub_total &&\n        cart.sub_total <= toPrice(method.max)\n      );\n    } else if (method.condition_type === 'weight') {\n      return (\n        toPrice(method.min) <= cart.total_weight &&\n        cart.total_weight <= toPrice(method.max)\n      );\n    } else {\n      return false;\n    }\n  });\n\n  // Loop through the methods and calculate the cost\n  methods = await Promise.all(\n    methods.map(async (method) => {\n      if (method.calculate_api) {\n        // This API is internal. It must be public\n        const port = normalizePort();\n        let api = `http://localhost:${port}`;\n        try {\n          api += buildUrl(method.calculate_api, {\n            cart_id: cart.uuid,\n            method_id: method.uuid\n          });\n        } catch (e) {\n          throw new Error(\n            `Your shipping calculate API ${method.calculate_api} is invalid`\n          );\n        }\n        const jsonResponse = await axios.get(api);\n        // Detect if the API returns an error base on the http status\n        if (jsonResponse.status >= 400) {\n          throw new Error(\n            `Error calculating shipping cost for method ${method.name}`\n          );\n        }\n        return {\n          ...method,\n          cost: toPrice(jsonResponse.data.data.cost, false)\n        };\n      } else if (method.weight_based_cost) {\n        const totalWeight = cart.total_weight;\n        const weightBasedCost = method.weight_based_cost\n          .map(({ min_weight, cost }) => ({\n            min_weight: parseFloat(min_weight),\n            cost: toPrice(cost)\n          }))\n          .sort((a, b) => a.min_weight - b.min_weight);\n\n        let cost = 0;\n        for (let i = 0; i < weightBasedCost.length; i += 1) {\n          if (totalWeight >= weightBasedCost[i].min_weight) {\n            cost = weightBasedCost[i].cost;\n          }\n        }\n        return {\n          ...method,\n          cost: toPrice(cost.toString(), false)\n        };\n      } else if (method.price_based_cost) {\n        const subTotal = toPrice(cart.sub_total);\n        const priceBasedCost = method.price_based_cost\n          .map(({ min_price, cost }) => ({\n            min_price: toPrice(min_price),\n            cost: toPrice(cost)\n          }))\n          .sort((a, b) => a.min_price - b.min_price);\n        let cost = 0;\n        for (let i = 0; i < priceBasedCost.length; i += 1) {\n          if (subTotal >= priceBasedCost[i].min_price) {\n            cost = priceBasedCost[i].cost;\n          }\n        }\n        return {\n          ...method,\n          cost: toPrice(cost.toString(), false)\n        };\n      } else {\n        return {\n          ...method,\n          cost: toPrice(method.cost.toString(), false)\n        };\n      }\n    })\n  );\n\n  return methods.map((method) => ({\n    id: method.uuid,\n    code: method.uuid,\n    name: method.name,\n    cost: method.cost\n  }));\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/getCartByUUID.ts",
    "content": "import { Cart, getCart } from '../../../modules/checkout/services/cart/Cart.js';\n\n/**\n * This function returns a Cart object by ID.\n * @param {*} uuid - The UUID of the cart to retrieve\n * @throws {Error} If the cart does not exist or if there is an error during the transaction\n * @returns {Promise<Cart>}\n */\nexport const getCartByUUID = async (uuid: string): Promise<Cart> => {\n  const cart = await getCart(uuid);\n  return cart;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/getMyCart.ts",
    "content": "import { select, update } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { Cart, getCart } from './cart/Cart.js';\n\n/**\n * This function return a Cart object by the session ID.\n * @param {string} sid - The session ID\n * @param {number} customerId - The customer ID\n * @returns {Promise<Cart | null>} - The Cart object if found, otherwise null\n * @throws {Error} If there is an error during the query\n * @description This function retrieves the current cart associated with a session ID.\n */\nexport const getMyCart = async (\n  sid: string,\n  customerId?: number\n): Promise<Cart | null> => {\n  // Try to get the cart by the session id first\n  const cartBySid = await select()\n    .from('cart')\n    .where('status', '=', 1)\n    .andWhere('sid', '=', sid)\n    .load(pool);\n\n  if (cartBySid) {\n    const cart = await getCart(cartBySid.uuid);\n    return cart;\n  } else {\n    // Get the customer id from the session\n    if (customerId) {\n      // Check if any cart is associated with the customer id\n      const abandonedCart = await select()\n        .from('cart')\n        .where('customer_id', '=', customerId)\n        .andWhere('status', '=', 1)\n        .load(pool);\n\n      if (abandonedCart) {\n        // Update the cart with the session id\n        await update('cart')\n          .given({ sid: sid })\n          .where('uuid', '=', abandonedCart.uuid)\n          .execute(pool);\n        const cart = await getCart(abandonedCart.uuid);\n        return cart;\n      } else {\n        return null;\n      }\n    } else {\n      return null;\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/index.ts",
    "content": "export { Cart, Item } from './cart/Cart.js';\nexport * from './getMyCart.js';\nexport * from './createNewCart.js';\nexport * from './getCartByUUID.js';\nexport * from './getAvailablePaymentMethods.js';\nexport * from './saveCart.js';\nexport * from './toPrice.js';\nexport * from './orderCreator.js';\nexport * from './orderValidator.js';\nexport * from './addShippingAddress.js';\nexport { default as removeCartItem } from './removeCartItem.js';\nexport { default as updateCartItemQty } from './updateCartItemQty.js';\nexport { default as addCartItem } from './addCartItem.js';\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/orderCreator.ts",
    "content": "import {\n  commit,\n  getConnection,\n  insert,\n  PoolClient,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { v4 as uuidv4 } from 'uuid';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { hookable } from '../../../lib/util/hookable.js';\nimport type { OrderRow, InsertResultWithRow } from '../../../types/db/index.js';\nimport { PaymentStatus, ShipmentStatus } from '../../../types/order.js';\nimport addOrderActivityLog from '../../oms/services/addOrderActivityLog.js';\nimport { resolveOrderStatus } from '../../oms/services/updateOrderStatus.js';\nimport { Cart } from './cart/Cart.js';\nimport { validateBeforeCreateOrder } from './orderValidator.js';\n\n// ============================================================================\n// Hook Type Exports - Import these when registering hooks for type safety\n// ============================================================================\n\n/** Context available in saveOrder hooks via 'this' */\nexport interface SaveOrderContext {\n  cart: Cart;\n}\n\n/** Arguments passed to saveOrder function */\nexport type SaveOrderArgs = [cart: Cart, connection: PoolClient];\n\n/** Context available in saveOrderItems hooks via 'this' */\nexport interface SaveOrderItemsContext {\n  cart: Cart;\n}\n\n/** Arguments passed to saveOrderItems function */\nexport type SaveOrderItemsArgs = [\n  cart: Cart,\n  orderId: number,\n  connection: PoolClient\n];\n\n/** Context available in disableCart hooks via 'this' */\nexport interface DisableCartContext {\n  cart: Cart;\n}\n\n/** Arguments passed to disableCart function */\nexport type DisableCartArgs = [cartId: number, connection: PoolClient];\n\n/** Context available in createOrderFunc hooks via 'this' */\nexport interface CreateOrderContext {\n  cart: Cart;\n}\n\n/** Arguments passed to createOrderFunc function */\nexport type CreateOrderArgs = [cart: Cart];\n\n// ============================================================================\n// Order Result Type - Extends DB type with insert result\n// ============================================================================\n\nexport type CreateOrderResult = InsertResultWithRow<OrderRow>;\n\nasync function disableCart(cartId: number, connection: PoolClient) {\n  const cart = await update('cart')\n    .given({ status: false })\n    .where('cart_id', '=', cartId)\n    .execute(connection);\n  return cart;\n}\n\nasync function saveOrder<T = CreateOrderResult>(\n  cart: Cart,\n  connection: PoolClient\n): Promise<T> {\n  const shipmentStatusList = getConfig(\n    'oms.order.shipmentStatus',\n    {}\n  ) as Record<string, ShipmentStatus>;\n  const paymentStatusList = getConfig('oms.order.paymentStatus', {}) as Record<\n    string,\n    PaymentStatus\n  >;\n  let defaultShipmentStatus;\n  Object.keys(shipmentStatusList).forEach((key) => {\n    if (shipmentStatusList[key].isDefault) {\n      defaultShipmentStatus = key;\n    }\n  });\n\n  let defaultPaymentStatus;\n  Object.keys(paymentStatusList).forEach((key) => {\n    if (paymentStatusList[key].isDefault) {\n      defaultPaymentStatus = key;\n    }\n  });\n  let shipAddr;\n  if (cart.getData('shipping_address_id')) {\n    // Save the shipping address\n    const cartShippingAddress = await select()\n      .from('cart_address')\n      .where('cart_address_id', '=', cart.getData('shipping_address_id'))\n      .load(connection);\n    delete cartShippingAddress.uuid;\n    shipAddr = await insert('order_address')\n      .given(cartShippingAddress)\n      .execute(connection);\n  }\n\n  // Save the billing address\n  const cartBillingAddress = await select()\n    .from('cart_address')\n    .where('cart_address_id', '=', cart.getData('billing_address_id'))\n    .load(connection);\n  delete cartBillingAddress.uuid;\n  const billAddr = await insert('order_address')\n    .given(cartBillingAddress)\n    .execute(connection);\n\n  const previous = await select('order_id')\n    .from('order')\n    .orderBy('order_id', 'DESC')\n    .limit(0, 1)\n    .execute(connection);\n\n  const orderStatus = resolveOrderStatus(\n    defaultPaymentStatus,\n    defaultShipmentStatus\n  );\n\n  // Save order to DB\n  const order = await insert('order')\n    .given({\n      ...cart.exportData(),\n      uuid: uuidv4().replace(/-/g, ''),\n      order_number:\n        10000 + parseInt(previous[0] ? previous[0].order_id : 0, 10) + 1,\n      // FIXME: Must be structured\n      shipping_address_id: shipAddr ? shipAddr.insertId : null,\n      billing_address_id: billAddr.insertId,\n      status: orderStatus,\n      payment_status: defaultPaymentStatus,\n      shipment_status: defaultShipmentStatus\n    })\n    .execute(connection);\n  return order;\n}\n\nasync function saveOrderItems(\n  cart: Cart,\n  orderId: number,\n  connection: PoolClient\n) {\n  // Save order items\n  const items = cart.getItems();\n  const savedItems = await Promise.all(\n    items.map(async (item) => {\n      await insert('order_item')\n        .given({\n          ...item.export(),\n          uuid: uuidv4().replace(/-/g, ''),\n          order_item_order_id: orderId\n        })\n        .execute(connection);\n    })\n  );\n  return savedItems;\n}\n\nasync function createOrderFunc<T extends CreateOrderResult>(cart: Cart) {\n  // Start creating order\n  const connection = await getConnection(pool);\n  try {\n    await startTransaction(connection);\n\n    // Validate the cart\n    const validateResult = await validateBeforeCreateOrder(cart);\n    if (!validateResult.valid) {\n      throw new Error(\n        `Order validation failed: ${validateResult.errors.join('\\r\\n-- ')}`\n      );\n    }\n    // Save order to DB\n    const order = await hookable(saveOrder<T>, { cart })(cart, connection);\n\n    // Save order items\n    await hookable(saveOrderItems, { cart })(cart, order.insertId, connection);\n\n    // Save order activity\n    await addOrderActivityLog(\n      order.insertId,\n      `Order has been created`,\n      false,\n      connection\n    );\n\n    // Disable the cart\n    await hookable(disableCart, { cart })(cart.getData('cart_id'), connection);\n\n    await commit(connection);\n    return order;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Create a new order from the cart\n * @param cart\n * @returns {Promise<Object>} - The created order object\n * @throws {Error} - If the order creation fails due to validation errors or database issues\n */\nexport const createOrder = async <T extends CreateOrderResult>(\n  cart: Cart\n): Promise<T> => {\n  const order = await hookable(createOrderFunc<T>, {\n    cart\n  })(cart);\n  return order;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/orderValidator.ts",
    "content": "import { addProcessor, getValueSync } from '../../../lib/util/registry.js';\nimport { Validator, ValidatorManager } from '../../../lib/util/validator.js';\nimport { Cart } from './cart/Cart.js';\n\nconst initialValidators: Validator<Cart>[] = [\n  {\n    id: 'checkCartError',\n    /**\n     *\n     * @param {Cart} cart\n     * @returns {boolean}\n     */\n    func: (cart: Cart) => {\n      if (cart.hasError()) {\n        return false;\n      } else {\n        return true;\n      }\n    },\n    errorMessage: 'Cart has errors'\n  },\n  {\n    id: 'checkEmpty',\n    /**\n     *\n     * @param {Cart} cart\n     * @returns\n     */\n    func: (cart: Cart) => {\n      const items = cart.getItems();\n      if (items.length === 0) {\n        return false;\n      } else {\n        return true;\n      }\n    },\n    errorMessage: 'Cart is empty'\n  },\n  {\n    id: 'shippingAddress',\n    /**\n     *\n     * @param {Cart} cart\n     * @returns {boolean}\n     */\n    func: (cart: Cart) => {\n      if (cart.getData('no_shipping_required')) {\n        return true;\n      }\n      if (!cart.getData('shipping_address_id')) {\n        return false;\n      } else {\n        return true;\n      }\n    },\n    errorMessage: 'Shipping address is required'\n  },\n  {\n    id: 'shippingMethod',\n    /**\n     *\n     * @param {Cart} cart\n     * @returns {boolean}\n     */\n    func: (cart: Cart) => {\n      if (cart.getData('no_shipping_required')) {\n        return true;\n      }\n      if (!cart.getData('shipping_method')) {\n        return false;\n      } else {\n        return true;\n      }\n    },\n    errorMessage: 'Shipping method is required'\n  }\n];\n\nexport async function validateBeforeCreateOrder(\n  cart: Cart\n): Promise<{ valid: boolean; errors: string[] }> {\n  const validator = getValueSync<ValidatorManager<Cart>>(\n    'orderValidator',\n    () => new ValidatorManager(initialValidators),\n    {},\n    (value) => value instanceof ValidatorManager\n  );\n  return await validator.validate(cart);\n}\n\nexport function addOrderValidationRule(rule: Validator<Cart>): void {\n  addProcessor('orderValidator', (validatorManager) => {\n    if (validatorManager instanceof ValidatorManager) {\n      validatorManager.add(rule);\n      return validatorManager;\n    } else {\n      throw new Error('orderValidator must be an instance of ValidatorManager');\n    }\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/removeCartItem.ts",
    "content": "import { hookable } from '../../../lib/util/hookable.js';\nimport { Cart, Item } from './cart/Cart.js';\n\nasync function removeCartItem(cart: Cart, uuid: string) {\n  const items = cart.getItems();\n  const item = cart.getItem(uuid);\n  const newItems = items.filter((i) => i.getData('uuid') !== uuid);\n  if (item) {\n    await cart.setData('items', newItems);\n    return item;\n  } else {\n    throw new Error('Item not found');\n  }\n}\n\n/** Removes an item from the cart by its UUID.\n * @param {Cart} cart - The cart object.\n * @param {string} uuid - The UUID of the item to remove.\n * @returns {Promise<Item>} - The removed item.\n * @throws {Error} - If the item is not found in the cart.\n */\nexport default async (\n  cart: Cart,\n  uuid: string,\n  context: Record<string, unknown>\n): Promise<Item> => {\n  const removedItem = await hookable(removeCartItem, context)(cart, uuid);\n  return removedItem;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/saveCart.ts",
    "content": "import {\n  commit,\n  del,\n  insert,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../lib/postgres/connection.js';\nimport { Cart } from './cart/Cart.js';\n\n/**\n * @param {Cart} cart\n * @returns {Promise<Number|null>}\n * @throws {Error}\n * */\nexport const saveCart = async (cart: Cart) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const items = cart.getItems();\n    let cartId;\n    if (items.length === 0) {\n      // Delete cart if existed\n      if (cart.getData('cart_id')) {\n        await del('cart')\n          .where('cart_id', '=', cart.getData('cart_id'))\n          .execute(connection, false);\n      }\n      await commit(connection);\n      return null;\n    } else {\n      if (cart.getData('cart_id')) {\n        await update('cart')\n          .given(cart.exportData())\n          .where('cart_id', '=', cart.getData('cart_id'))\n          .execute(connection, false);\n        cartId = cart.getData('cart_id');\n      } else {\n        const c = await insert('cart')\n          .given(cart.exportData())\n          .execute(connection, false);\n        cartId = c.insertId;\n      }\n\n      // Get current items from database\n      const currentItems = await select()\n        .from('cart_item')\n        .where('cart_id', '=', cartId)\n        .execute(connection, false);\n\n      // Delete items that are not in cart\n      await Promise.all(\n        currentItems.map(async (i) => {\n          if (!cart.getItem(i.uuid)) {\n            await del('cart_item')\n              .where('cart_item_id', '=', i.cart_item_id)\n              .execute(connection, false);\n          }\n        })\n      );\n\n      await Promise.all(\n        items.map(async (item) => {\n          if (/^\\d+$/.test(item.getData('cart_item_id'))) {\n            await update('cart_item')\n              .given(item.export())\n              .where('cart_item_id', '=', item.getData('cart_item_id'))\n              .execute(connection, false);\n          } else {\n            await insert('cart_item')\n              .given({\n                ...item.export(),\n                cart_id: cart.getData('cart_id') || cartId\n              })\n              .execute(connection, false);\n          }\n        })\n      );\n\n      await commit(connection);\n      return cartId;\n    }\n  } catch (error) {\n    await rollback(connection);\n    throw error;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/toPrice.ts",
    "content": "import { getConfig } from '../../../lib/util/getConfig.js';\n\nexport type RoundType = 'up' | 'down' | 'round';\nexport function toPrice(value: string, forDisplay: boolean = false) {\n  let price = parseFloat(value || '0');\n  if (Number.isNaN(price)) {\n    throw new Error('Price is not a number');\n  }\n  const rounding = getConfig('pricing.rounding', 'round') as RoundType;\n  const precision = getConfig('pricing.precision', 2);\n  const precisionFix = 10 ** precision;\n  switch (rounding) {\n    case 'up':\n      price = Math.ceil(price * precisionFix) / precisionFix;\n      break;\n    case 'down':\n      price = Math.floor(price * precisionFix) / precisionFix;\n      break;\n    case 'round':\n      price = Math.round(price * precisionFix) / precisionFix;\n      break;\n    default:\n      price = Math.round(price * precisionFix) / precisionFix;\n      break;\n  }\n  if (!forDisplay) {\n    return price;\n  } else {\n    const currency = getConfig('shop.currency', 'USD');\n    const language = getConfig('shop.language', 'en');\n    return new Intl.NumberFormat(language, {\n      style: 'currency',\n      currency\n    }).format(price);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/services/updateCartItemQty.ts",
    "content": "import { hookable } from '../../../lib/util/hookable.js';\nimport { Cart, Item } from './cart/Cart.js';\n\nasync function updateCartItemQty(\n  cart: Cart,\n  uuid: string,\n  qty: string,\n  action: 'increase' | 'decrease',\n  context: Record<string, unknown> = {}\n) {\n  if (['increase', 'decrease'].indexOf(action) === -1) {\n    throw new Error('Invalid action');\n  }\n  const item = cart.getItem(uuid);\n  if (!item) {\n    throw new Error('Item not found');\n  }\n  if (typeof context !== 'object' || context === null) {\n    throw new Error('Context must be an object');\n  }\n  context.cartData = cart.export();\n  context.itemData = item.export();\n\n  if (action === 'increase') {\n    await item.setData('qty', item.getData('qty') + parseInt(qty, 10));\n  } else {\n    const currentQty = item.getData('qty');\n    const newQty = Math.max(currentQty - parseInt(qty, 10), 0);\n    if (newQty === 0) {\n      await cart.removeItem(uuid, context);\n    } else {\n      await item.setData('qty', newQty);\n    }\n  }\n  await cart.build();\n  return item;\n}\n\nexport default async (\n  cart: Cart,\n  uuid: string,\n  qty: string,\n  action: 'increase' | 'decrease',\n  context: Record<string, unknown> = {}\n): Promise<Item> => {\n  const updatedItem = await hookable(updateCartItemQty, context)(\n    cart,\n    uuid,\n    qty,\n    action,\n    context\n  );\n  return updatedItem;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/basicSetup.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = true;\nimport { addProcessor, addFinalProcessor } from '../../../lib/util/registry.js';\nimport { registerCartBaseFields } from '../services/cart/registerCartBaseFields.js';\nimport { registerCartItemBaseFields } from '../services/cart/registerCartItemBaseFields.js';\nimport { sortFields } from '../services/cart/sortFields.js';\nimport { products } from './products.js';\nimport { taxRates } from './taxRates.js';\nimport { registerCartPromotionFields } from '../../promotion/services/registerCartPromotionFields.js';\nimport { registerCartItemPromotionFields } from '../../promotion/services/registerCartItemPromotionFields.js';\nimport { coupons } from './coupons.js';\nimport { registerDefaultValidators } from '../../promotion/services/registerDefaultValidators.js';\nimport { registerDefaultCalculators } from '../../promotion/services/registerDefaultCalculators.js';\n\n// Default tax configuration\naddProcessor('cartFields', registerCartBaseFields, 0);\naddProcessor('cartFields', registerCartPromotionFields, 0);\naddProcessor('cartItemFields', registerCartItemBaseFields, 0);\naddProcessor('cartItemFields', registerCartItemPromotionFields, 0);\naddProcessor(\n  'cartItemFields',\n  (fields) => {\n    fields.push({\n      key: 'tax_percent',\n      resolvers: [\n        function resolver() {\n          const classId = this.getData('tax_class_id');\n          return taxRates[classId] || 0;\n        }\n      ],\n      dependencies: ['tax_class_id']\n    });\n    return fields;\n  },\n  0\n);\n\naddFinalProcessor('cartFields', (fields) => {\n  try {\n    const sortedFields = sortFields(fields);\n    return sortedFields;\n  } catch (e) {\n    error(e);\n    throw e;\n  }\n});\n\naddFinalProcessor('cartItemFields', (fields) => {\n  try {\n    const sortedFields = sortFields(fields);\n    return sortedFields;\n  } catch (e) {\n    error(e);\n    throw e;\n  }\n});\n\naddProcessor('cartItemProductLoaderFunction', () => {\n  return (id) => {\n    return products.find((product) => product.product_id === id);\n  };\n});\n\naddProcessor('couponLoaderFunction', () => {\n  return (code) => {\n    return coupons.find((coupon) => coupon.coupon === code);\n  };\n});\n\naddProcessor('couponValidatorFunctions', registerDefaultValidators);\naddProcessor('discountCalculatorFunctions', registerDefaultCalculators);\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/coupons.js",
    "content": "export const coupons = [\n  {\n    coupon_id: 1,\n    uuid: '511b3def-bfe0-4cff-91ce-667586939bdb',\n    status: true,\n    description: 'percentage_discount_to_entire_order',\n    discount_amount: 10,\n    free_shipping: false,\n    discount_type: 'percentage_discount_to_entire_order',\n    coupon: 'ten_percent_discount_to_entire_order',\n    used_time: 0,\n    target_products: [],\n    condition: { order_qty: '', order_total: '' },\n    user_condition: { email: '', groups: [''], purchased: '' },\n    buyx_gety: [\n      {\n        sku: 'AT46 8703 9964 4659 4364',\n        max_y: 2,\n        buy_qty: 3,\n        get_qty: 1,\n        discount: 70\n      },\n      {\n        sku: 'LU72 5610 V9CD PSQH XZTE',\n        max_y: 3,\n        buy_qty: 2,\n        get_qty: 1,\n        discount: 100\n      }\n    ],\n    max_uses_time_per_coupon: null,\n    max_uses_time_per_customer: null,\n    start_date: null,\n    end_date: null,\n    created_at: '2020-04-23 23:22:33+07',\n    updated_at: '2020-04-24 00:45:33+07'\n  },\n  {\n    coupon_id: 1,\n    uuid: '511b3sef-bfe0-4cff-91ce-667586939bdb',\n    status: true,\n    description: '100_fixed_discount_to_entire_order',\n    discount_amount: 100,\n    free_shipping: false,\n    discount_type: 'fixed_discount_to_entire_order',\n    coupon: '100_fixed_discount_to_entire_order',\n    used_time: 0,\n    target_products: [],\n    condition: { order_qty: '', order_total: '' },\n    user_condition: { email: '', groups: [''], purchased: '' },\n    buyx_gety: [],\n    max_uses_time_per_coupon: null,\n    max_uses_time_per_customer: null,\n    start_date: null,\n    end_date: null,\n    created_at: '2020-04-23 23:22:33+07',\n    updated_at: '2020-04-24 00:45:33+07'\n  },\n  {\n    coupon_id: 1,\n    uuid: '511b3sef-bfe0-4cff-91ce-667586939bdb',\n    status: true,\n    description: '500_fixed_discount_to_entire_order',\n    discount_amount: 500,\n    free_shipping: false,\n    discount_type: 'fixed_discount_to_entire_order',\n    coupon: '500_fixed_discount_to_entire_order',\n    used_time: 0,\n    target_products: [],\n    condition: { order_qty: '', order_total: '' },\n    user_condition: { email: '', groups: [''], purchased: '' },\n    buyx_gety: [],\n    max_uses_time_per_coupon: null,\n    max_uses_time_per_customer: null,\n    start_date: null,\n    end_date: null,\n    created_at: '2020-04-23 23:22:33+07',\n    updated_at: '2020-04-24 00:45:33+07'\n  }\n];\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/products.js",
    "content": "export const products = [\n  {\n    product_id: 1,\n    name: 'Product 1',\n    sku: 'SKU1',\n    price: 100,\n    status: true,\n    qty: 100,\n    tax_class: 1\n  },\n  {\n    product_id: 2,\n    name: 'Product 2',\n    sku: 'SKU2',\n    price: 200,\n    status: true,\n    qty: 100,\n    tax_class: 1\n  },\n  {\n    product_id: 3,\n    name: 'Product 3',\n    sku: 'SKU3',\n    price: 300,\n    status: true,\n    qty: 100,\n    tax_class: 1\n  },\n  {\n    product_id: 4,\n    name: 'No Tax',\n    sku: 'SKU4',\n    price: 300,\n    status: true,\n    qty: 100,\n    tax_class: null\n  },\n  {\n    product_id: 5,\n    name: 'Tax Exempt',\n    sku: 'SKU5',\n    price: 123.45,\n    status: true,\n    qty: 100,\n    tax_class: 3\n  },\n  {\n    product_id: 6,\n    name: 'Long decimal tax',\n    sku: 'SKU6',\n    price: 345.73,\n    status: true,\n    qty: 100,\n    tax_class: 4\n  },\n  {\n    product_id: 7,\n    name: 'Tax 10%',\n    sku: 'SKU7',\n    price: 819,\n    status: true,\n    qty: 100,\n    tax_class: 1\n  }\n];\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/taxRates.js",
    "content": "export const taxRates = [0, 10, 15, 7.25, 5.37];\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/addItemSideEffect.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\nimport { hookAfter, hookBefore } from '../../../../lib/util/hookable.js';\nimport { products } from '../products.js';\nimport { addProcessor } from '../../../../lib/util/registry.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\ndescribe('Test addCartItem side effects', () => {\n  it('Auto adding another item based on SKU', async () => {\n    hookAfter('addCartItem', async function addFreeItem(addedItem, cart) {\n      const productId = addedItem.getData('product_id');\n      if (productId === 1) {\n        await cart.addItem(2, 1, {});\n      }\n    });\n\n    const cart = new Cart({\n      status: 1\n    });\n\n    await cart.addItem(1, 1, {});\n\n    const items = cart.getItems();\n    expect(items.length).toEqual(2);\n    expect(items[0].getData('product_id')).toEqual(1);\n    expect(items[1].getData('product_id')).toEqual(2);\n  });\n\n  it('Prevent adding an item based on SKU', async () => {\n    hookBefore(\n      'addCartItem',\n      async function preventAddingItem(cart, productId, qty, context) {\n        const product = products.find((p) => p.product_id === productId);\n        if (product['sku'] === 'SKU1') {\n          throw new Error('This item is not saleable');\n        }\n      }\n    );\n\n    const cart = new Cart({\n      status: 1\n    });\n\n    try {\n      await cart.addItem(1, 1, {});\n    } catch (e) {\n      expect(e.message).toEqual('This item is not saleable');\n    }\n\n    const items = cart.getItems();\n    expect(items.length).toEqual(0);\n  });\n\n  it('Modify the item qty before adding it to the cart', async () => {\n    addProcessor('cartItemBeforeAdd', async function modifyQty(item) {\n      const qty = this.request.body.qty;\n      if (item.getData('qty') < qty) {\n        await item.setData('qty', qty);\n      }\n      return item;\n    });\n\n    const cart = new Cart({\n      status: 1\n    });\n\n    await cart.addItem(2, 1, { request: { body: { qty: 8 } } });\n    await cart.addItem(3, 9, { request: { body: { qty: 8 } } });\n\n    const items = cart.getItems();\n    expect(items.length).toEqual(2);\n    expect(items[0].getData('qty')).toEqual(8);\n    expect(items[1].getData('qty')).toEqual(9);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/discountAmount.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\ndescribe('Test discount amount calculation', () => {\n  it('Percentage discount to entire order', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const pricingConfig = config.get('pricing');\n    pricingConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('discount_amount')).toEqual(0);\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(cart.getData('discount_amount')).toEqual(10);\n    pricingConfig.tax.price_including_tax = true;\n    await item.build();\n    await cart.build();\n    expect(cart.getData('discount_amount')).toEqual(10);\n    pricingConfig.tax.price_including_tax = false;\n    await item.build();\n    const item2 = await cart.addItem(2, 2);\n    expect(cart.getData('discount_amount')).toEqual(50);\n    await cart.setData('coupon', '100_fixed_discount_to_entire_order');\n    expect(cart.getData('discount_amount')).toEqual(100);\n    pricingConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(cart.getData('discount_amount')).toEqual(50);\n    await cart.setData('coupon', '100_fixed_discount_to_entire_order');\n    expect(cart.getData('discount_amount')).toEqual(100);\n  });\n\n  it('It should apply maximum discount amount when discount amount is greater than the total price', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const pricingConfig = config.get('pricing');\n    pricingConfig.tax.price_including_tax = false;\n    await cart.setData('coupon', '500_fixed_discount_to_entire_order');\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('discount_amount')).toEqual(100);\n    const item2 = await cart.addItem(2, 1);\n    expect(cart.getData('discount_amount')).toEqual(300);\n    pricingConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.build();\n    expect(cart.getData('discount_amount')).toEqual(300);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/grandTotal.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\n// This test assumes there is no additional fee like shipping fee, etc.\ndescribe('Test grand total calculation', () => {\n  it('Grand total should equal to the sum of line total with discount including tax of all items when discount is not applied', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1); // Tax percent 10%\n    expect(cart.getData('grand_total')).toEqual(110);\n    const item2 = await cart.addItem(5, 1); // Tax percent 7.25%\n    expect(cart.getData('grand_total')).toEqual(242.4);\n    const item3 = await cart.addItem(2, 2); // Tax percent 10%\n    expect(cart.getData('grand_total')).toEqual(682.4);\n\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await item3.build();\n    await cart.build();\n    expect(cart.getData('grand_total')).toEqual(623.45);\n  });\n\n  it('Grand total should alway be equal to the sub total including tax when discount is not applied', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('grand_total')).toEqual(110);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(110);\n    const item2 = await cart.addItem(5, 1);\n    expect(cart.getData('grand_total')).toEqual(242.4);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(242.4);\n\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.build();\n    expect(cart.getData('grand_total')).toEqual(223.45);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(223.45);\n  });\n\n  it('Grand total should be calculated with discount amount deducted', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1); // Tax percent 10%\n    expect(cart.getData('grand_total')).toEqual(110);\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(cart.getData('grand_total')).toEqual(99);\n    const item2 = await cart.addItem(5, 1); // Tax percent 7.25%\n    // Tax rounding unit level, round to nearest with 2 decimal places\n    expect(cart.getData('grand_total')).toEqual(218.15);\n    const item3 = await cart.addItem(2, 2); // Tax percent 10%\n    expect(cart.getData('grand_total')).toEqual(614.15);\n\n    await cart.setData('coupon', '100_fixed_discount_to_entire_order');\n    expect(cart.getData('grand_total')).toEqual(572.94);\n\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await item3.build();\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(cart.getData('grand_total')).toEqual(561.1);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/lineTotal.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\ndescribe('Test line total calculation', () => {\n  it('Line total should equal to the product price multiplied by quantity when tax is not included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total')).toEqual(100);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('line_total')).toEqual(400);\n  });\n\n  it('Line total including tax should be calculated with tax amount added', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total_incl_tax')).toEqual(110);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('line_total_incl_tax')).toEqual(440);\n  });\n\n  it('Line total should be calculated with tax amount deducted when tax is included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = true;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total')).toEqual(90.91);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('line_total')).toEqual(363.64);\n  });\n\n  it('Line total including tax should be equal to product price when tax is included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = true;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total_incl_tax')).toEqual(100);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('line_total_incl_tax')).toEqual(400);\n  });\n\n  it('Line total should not be affected by discount amount', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total')).toEqual(100);\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(item.getData('line_total')).toEqual(100);\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    expect(item.getData('line_total')).toEqual(90.91);\n  });\n\n  it('Line total including tax should not be affected by discount amount', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total_incl_tax')).toEqual(110);\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(item.getData('line_total_incl_tax')).toEqual(110);\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    expect(item.getData('final_price_incl_tax')).toEqual(100);\n    expect(item.getData('line_total_incl_tax')).toEqual(100);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/lineTotalWithDiscount.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\ndescribe('Test line total with discount calculation', () => {\n  it('Line total with discount should equal to the product price multiplied by quantity when tax is not included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total_with_discount')).toEqual(100);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('line_total_with_discount')).toEqual(400);\n  });\n\n  it('Line total with discount including tax should be calculated with tax amount added', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total_with_discount_incl_tax')).toEqual(110);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('line_total_with_discount_incl_tax')).toEqual(440);\n  });\n\n  it('Line total with discount should be calculated with tax amount deducted when tax is included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = true;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total_with_discount')).toEqual(90.91);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('line_total_with_discount')).toEqual(363.64);\n  });\n\n  it('Line total with discount including tax should be equal to product price when tax is included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = true;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total_with_discount_incl_tax')).toEqual(100);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('line_total_with_discount_incl_tax')).toEqual(400);\n  });\n\n  it('Line total with discount should be affected by discount amount', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total_with_discount')).toEqual(100);\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(cart.getData('discount_amount')).toEqual(10);\n    expect(item.getData('line_total_with_discount')).toEqual(90);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('line_total_with_discount')).toEqual(360);\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.build();\n    expect(item.getData('line_total_with_discount')).toEqual(81.82);\n    expect(item2.getData('line_total_with_discount')).toEqual(327.28);\n  });\n\n  it('Line total with discount including tax should be affected by discount amount', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('line_total_with_discount_incl_tax')).toEqual(110);\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(cart.getData('discount_amount')).toEqual(10);\n    expect(item.getData('line_total_with_discount_incl_tax')).toEqual(99);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('line_total_with_discount_incl_tax')).toEqual(396);\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.build();\n    expect(item.getData('line_total_with_discount_incl_tax')).toEqual(90);\n    expect(item2.getData('line_total_with_discount_incl_tax')).toEqual(360);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/productPrice.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\ndescribe('Test product price calculation', () => {\n  it('Price should equal to the product price when tax is not included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('product_price')).toEqual(100);\n    expect(item.getData('final_price')).toEqual(100);\n    const noTax = await cart.addItem(4, 1);\n    expect(noTax.getData('product_price')).toEqual(300);\n    expect(noTax.getData('final_price')).toEqual(300);\n  });\n\n  it('Price including tax should be calculated with tax amount added', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('product_price_incl_tax')).toEqual(110);\n    expect(item.getData('final_price_incl_tax')).toEqual(110);\n    const noTax = await cart.addItem(4, 1);\n    expect(noTax.getData('product_price')).toEqual(300);\n    expect(noTax.getData('final_price')).toEqual(300);\n  });\n\n  it('Price should be calculated with tax amount deducted when tax is included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = true;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('product_price')).toEqual(90.91);\n    expect(item.getData('final_price')).toEqual(90.91);\n    const noTax = await cart.addItem(4, 1);\n    expect(noTax.getData('product_price')).toEqual(300);\n    expect(noTax.getData('final_price')).toEqual(300);\n  });\n\n  it('Price including tax should be equal to product price when tax is included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = true;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('product_price_incl_tax')).toEqual(100);\n    expect(item.getData('final_price_incl_tax')).toEqual(100);\n    const noTax = await cart.addItem(4, 1);\n    expect(noTax.getData('product_price')).toEqual(300);\n    expect(noTax.getData('final_price')).toEqual(300);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/removeItemSideEffect.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\nimport { hookAfter, hookBefore } from '../../../../lib/util/hookable.js';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\ndescribe('Test removeCartItem', () => {\n  it('It should remove an item from the cart', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    await cart.addItem(1, 1, {});\n    await cart.addItem(2, 1, {});\n    const items = cart.getItems();\n    expect(items.length).toEqual(2);\n    await cart.removeItem(items[0].getData('uuid'));\n    await cart.removeItemBySku('SKU2');\n    const newItems = cart.getItems();\n    expect(newItems.length).toEqual(0);\n  });\n\n  it('Auto remove another item based on SKU', async () => {\n    hookAfter(\n      'removeCartItem',\n      async function removeAnotherItem(removedItem, cart) {\n        const itemUUID = removedItem.getData('product_sku');\n        if (itemUUID === 'SKU1') {\n          await cart.removeItemBySku('SKU2');\n        }\n      }\n    );\n\n    const cart = new Cart({\n      status: 1\n    });\n\n    await cart.addItem(1, 1, {});\n    await cart.addItem(2, 1, {});\n\n    const items = cart.getItems();\n    expect(items.length).toEqual(2);\n    await cart.removeItemBySku('SKU1');\n    const newItems = cart.getItems();\n    expect(newItems.length).toEqual(0);\n  });\n\n  it('Prevent removing an item based on SKU', async () => {\n    hookBefore(\n      'removeCartItem',\n      async function preventRemovingItem(cart, uuid, context) {\n        const items = cart.getItems();\n        const item = items.find((i) => i.getData('sku') === 'SKU1');\n        if (item && item.getData('uuid') === uuid) {\n          throw new Error('This item is not removable');\n        }\n      }\n    );\n\n    const cart = new Cart({\n      status: 1\n    });\n\n    try {\n      await cart.addItem(1, 1, {});\n    } catch (e) {\n      expect(e.message).toEqual('This item is not removable');\n    }\n\n    const items = cart.getItems();\n    const check = items.find((i) => i.getData('product_sku') === 'SKU1');\n    expect(check).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/subTotal.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\nimport { jest, describe, it, expect, beforeAll, afterAll } from '@jest/globals';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\ndescribe('Test sub total calculation', () => {\n  it('Sub total should equal to the sum of line total of all items when tax is not included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('sub_total')).toEqual(100);\n    const item2 = await cart.addItem(5, 1);\n    expect(cart.getData('sub_total')).toEqual(223.45);\n  });\n\n  it('Sub total including tax should be calculated with tax amount added', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(110);\n    const item2 = await cart.addItem(5, 1);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(242.4);\n  });\n\n  it('Sub total should be calculated with tax amount deducted when tax is included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = true;\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('sub_total')).toEqual(90.91);\n    const item2 = await cart.addItem(5, 1);\n    expect(cart.getData('sub_total')).toEqual(206.01);\n  });\n\n  it('Sub total including tax should be equal to the sum of line total including tax of all items when tax is included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = true;\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(100);\n    const item2 = await cart.addItem(5, 1);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(223.45);\n  });\n\n  it('Sub total should not be affected by discount amount', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    await cart.addItem(1, 1);\n    expect(cart.getData('sub_total')).toEqual(100);\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(cart.getData('sub_total')).toEqual(100);\n  });\n\n  it('Sub total including tax should not be affected by discount amount', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    await cart.addItem(1, 1);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(110);\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(cart.getData('sub_total_incl_tax')).toEqual(110);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/subTotalWithDiscount.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\ndescribe('Test sub total with discount calculation', () => {\n  it('Sub total with discount should equal to the sub total when discount is not applied', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('sub_total')).toEqual(100);\n    expect(cart.getData('sub_total_with_discount')).toEqual(100);\n    const item2 = await cart.addItem(5, 1);\n    expect(cart.getData('sub_total')).toEqual(223.45);\n    expect(cart.getData('sub_total_with_discount')).toEqual(223.45);\n\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.build();\n    expect(cart.getData('sub_total')).toEqual(206.01);\n    expect(cart.getData('sub_total_with_discount')).toEqual(206.01);\n  });\n\n  it('Sub total with discount including tax should be equal to the sub total including tax when discount is not applied', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(110);\n    expect(cart.getData('sub_total_with_discount_incl_tax')).toEqual(110);\n    const item2 = await cart.addItem(5, 1);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(242.4);\n    expect(cart.getData('sub_total_with_discount_incl_tax')).toEqual(242.4);\n\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.build();\n    expect(cart.getData('sub_total_incl_tax')).toEqual(223.45);\n    expect(cart.getData('sub_total_with_discount_incl_tax')).toEqual(223.45);\n  });\n\n  it('Sub total with discount should be calculated with discount amount deducted', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('sub_total')).toEqual(100);\n    expect(cart.getData('sub_total_with_discount')).toEqual(90);\n    const item2 = await cart.addItem(5, 1);\n    expect(cart.getData('sub_total')).toEqual(223.45);\n    expect(cart.getData('sub_total_with_discount')).toEqual(201.1);\n\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.build();\n    expect(cart.getData('sub_total')).toEqual(206.01);\n    expect(cart.getData('sub_total_with_discount')).toEqual(185.41);\n  });\n\n  it('Sub total with discount including tax should be calculated with tax amount added', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(110);\n    expect(cart.getData('sub_total_with_discount_incl_tax')).toEqual(99);\n    const item2 = await cart.addItem(5, 1);\n    expect(cart.getData('sub_total_incl_tax')).toEqual(242.4);\n    expect(cart.getData('sub_total_with_discount_incl_tax')).toEqual(218.15);\n\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.build();\n    expect(cart.getData('sub_total_incl_tax')).toEqual(223.45);\n    expect(cart.getData('sub_total_with_discount_incl_tax')).toEqual(201.1);\n  });\n\n  it('Sub total with discount should be equal to the sum of line total with discount of all items when discount is not applied', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(cart.getData('sub_total_with_discount')).toEqual(100);\n    const item2 = await cart.addItem(5, 1);\n    expect(cart.getData('sub_total_with_discount')).toEqual(223.45);\n\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.build();\n    expect(cart.getData('sub_total_with_discount')).toEqual(206.01);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/taxAmount.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\ndescribe('Test tax amount calculation', () => {\n  it('Tax amount should be calculated correctly when tax is not included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('tax_amount')).toEqual(10);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('tax_amount')).toEqual(40);\n    const noTaxItem = await cart.addItem(4, 1);\n    expect(noTaxItem.getData('tax_amount')).toEqual(0);\n    expect(cart.getData('tax_amount')).toEqual(50);\n  });\n\n  it('Tax amount should be calculated correctly when tax is included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = true;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('tax_amount')).toEqual(9.09);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('tax_amount')).toEqual(36.36);\n    const noTaxItem = await cart.addItem(4, 1);\n    expect(noTaxItem.getData('tax_amount')).toEqual(0);\n    expect(cart.getData('tax_amount')).toEqual(45.45);\n  });\n\n  it('Tax amount should be calculated correctly when tax is included and discount is applied', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    priceConfig.tax.price_including_tax = false;\n    priceConfig.tax.rounding = 'round';\n    priceConfig.tax.round_level = 'unit';\n    priceConfig.tax.precision = 2;\n    const item = await cart.addItem(1, 1);\n    expect(item.getData('tax_amount')).toEqual(10);\n    await cart.setData('coupon', 'ten_percent_discount_to_entire_order');\n    expect(cart.getData('tax_amount')).toEqual(9);\n    const item2 = await cart.addItem(2, 2);\n    expect(item2.getData('tax_amount')).toEqual(36);\n    await cart.build();\n    expect(cart.getData('tax_amount')).toEqual(45);\n    priceConfig.tax.price_including_tax = true;\n    await item.build();\n    await item2.build();\n    await cart.build();\n    expect(item.getData('tax_amount')).toEqual(8.18);\n    expect(item2.getData('tax_amount')).toEqual(32.72);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/taxAmountRounding.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    rounding: 'round',\n    precision: 2,\n    round_level: 'unit',\n    price_including_tax: false\n  }\n});\n\ndescribe('Test tax amount calculation rounding', () => {\n  it('Tax amount should be calculated correctly when tax is not included', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    const priceConfig = config.get('pricing');\n    // Round\n    /// Level: unit\n    priceConfig.tax.precision = 2;\n    priceConfig.tax.round_level = 'unit';\n    priceConfig.tax.price_including_tax = false;\n    const item = await cart.addItem(5, 1);\n    expect(item.getData('tax_amount')).toEqual(8.95);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.9);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.85);\n    const item2 = await cart.addItem(1, 2);\n    expect(item2.getData('tax_amount')).toEqual(20);\n\n    priceConfig.tax.precision = 1;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(9);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(18);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(27);\n\n    priceConfig.tax.precision = 0;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(9);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(18);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(27);\n\n    /// Level: line\n    priceConfig.tax.precision = 2;\n    priceConfig.tax.round_level = 'line';\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.95);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.9);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.85);\n\n    priceConfig.tax.precision = 1;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(9);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.9);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.9);\n\n    priceConfig.tax.precision = 0;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(9);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(18);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(27);\n\n    /// Level: total\n    priceConfig.tax.round_level = 'total';\n    priceConfig.tax.precision = 2;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.950125);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.90025);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.850375);\n\n    priceConfig.tax.precision = 1;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.950125);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.90025);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.850375);\n\n    priceConfig.tax.precision = 0;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.950125);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.90025);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.850375);\n\n    // Ceil\n    /// Level: unit\n    priceConfig.tax.rounding = 'up';\n    priceConfig.tax.round_level = 'unit';\n    priceConfig.tax.precision = 2;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.96);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.92);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.88);\n\n    priceConfig.tax.precision = 1;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(9);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(18);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(27);\n\n    priceConfig.tax.precision = 0;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(9);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(18);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(27);\n\n    /// Level: line\n    priceConfig.tax.precision = 2;\n    priceConfig.tax.round_level = 'line';\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.96);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.91);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.86);\n\n    priceConfig.tax.precision = 1;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(9);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(18);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.9);\n\n    priceConfig.tax.precision = 0;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(9);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(18);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(27);\n\n    /// Level: total\n    priceConfig.tax.round_level = 'total';\n    priceConfig.tax.precision = 2;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.950125);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.90025);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.850375);\n\n    priceConfig.tax.precision = 1;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.950125);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.90025);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.850375);\n\n    priceConfig.tax.precision = 0;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.950125);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.90025);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.850375);\n\n    // Floor\n    /// Level: unit\n    priceConfig.tax.rounding = 'down';\n    priceConfig.tax.round_level = 'unit';\n    priceConfig.tax.precision = 2;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.95);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.9);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.85);\n\n    priceConfig.tax.precision = 1;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.9);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.8);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.7);\n\n    priceConfig.tax.precision = 0;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(16);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(24);\n\n    /// Level: line\n    priceConfig.tax.precision = 2;\n    priceConfig.tax.round_level = 'line';\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.95);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.9);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.85);\n\n    priceConfig.tax.precision = 1;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.9);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.9);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.8);\n\n    priceConfig.tax.precision = 0;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26);\n\n    /// Level: total\n    priceConfig.tax.round_level = 'total';\n    priceConfig.tax.precision = 2;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.950125);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.90025);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.850375);\n\n    priceConfig.tax.precision = 1;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.950125);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.90025);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.850375);\n\n    priceConfig.tax.precision = 0;\n    await item.setData('qty', 1);\n    expect(item.getData('tax_amount')).toEqual(8.950125);\n    await item.setData('qty', 2);\n    expect(item.getData('tax_amount')).toEqual(17.90025);\n    await item.setData('qty', 3);\n    expect(item.getData('tax_amount')).toEqual(26.850375);\n  });\n\n  // it('Tax amount should be calculated correctly when tax is included', async () => {\n  //   const cart = new Cart({\n  //     status: 1\n  //   });\n  //   const priceConfig = config.get('pricing');\n  //   priceConfig.tax.price_including_tax = true;\n  //   const item = await cart.addItem(5, 1);\n  //   expect(item.getData('tax_amount')).toEqual(8.35);\n  //   priceConfig.tax.precision = 1;\n  //   await item.build();\n  //   expect(item.getData('tax_amount')).toEqual(8.3);\n  // });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/checkout/tests/unit/updateCartItemQtySideEffect.test.js",
    "content": "process.env.ALLOW_CONFIG_MUTATIONS = 'true';\nimport config from 'config';\nimport '../basicSetup.js';\nimport { Cart } from '../../services/cart/Cart.js';\nimport { hookAfter, hookBefore } from '../../../../lib/util/hookable.js';\n// Default tax configuration\nconfig.util.setModuleDefaults('pricing', {\n  tax: {\n    price_including_tax: false\n  }\n});\n\ndescribe('Test updateCartItemQty', () => {\n  it('It should update the item qty from the cart', async () => {\n    const cart = new Cart({\n      status: 1\n    });\n    await cart.addItem(5, 1, {});\n    await cart.addItem(6, 1, {});\n    const items = cart.getItems();\n    expect(items.length).toEqual(2);\n    await cart.updateItemQty(items[0].getData('uuid'), 2, 'increase', {});\n    await cart.updateItemQtyBySku('SKU6', 1, 'decrease', {});\n    const newItems = cart.getItems();\n    expect(newItems[0].getData('qty')).toEqual(3);\n    expect(newItems.length).toEqual(1);\n  });\n\n  it('Auto add another item based on SKU', async () => {\n    hookAfter(\n      'updateCartItemQty',\n      async function addAnotherItem(updatedItem, cart, uuid, qty, action) {\n        if (action === 'increase' && updatedItem.getData('product_id') === 2) {\n          await cart.addItem(4, 1, {});\n        }\n      }\n    );\n\n    const cart = new Cart({\n      status: 1\n    });\n\n    await cart.addItem(2, 2, {});\n    await cart.addItem(3, 1, {});\n    const items = cart.getItems();\n    expect(items.length).toEqual(2);\n    await cart.updateItemQty(items[0].getData('uuid'), 2, 'increase', {});\n    const newItems = cart.getItems();\n    expect(newItems.length).toEqual(3);\n    await cart.updateItemQtyBySku('SKU2', 1, 'decrease', {});\n    const anotherItems = cart.getItems();\n    expect(anotherItems.length).toEqual(3);\n  });\n\n  it('Prevent update an item qty based on SKU', async () => {\n    hookBefore(\n      'updateCartItemQty',\n      async function preventUpdatingItemQty(cart, uuid) {\n        const items = cart.getItems();\n        const item = items.find((i) => i.getData('product_sku') === 'SKU2');\n        if (item && item.getData('uuid') === uuid) {\n          throw new Error('This item is not updateable');\n        }\n      }\n    );\n\n    const cart = new Cart({\n      status: 1\n    });\n\n    try {\n      await cart.addItem(2, 1, {});\n      await cart.updateItemQtyBySku('SKU2', 2, 'increase', {});\n    } catch (e) {\n      expect(e.message).toEqual('This item is not updateable');\n    }\n\n    const items = cart.getItems();\n    const check = items.find((i) => i.getData('product_sku') === 'SKU2');\n    expect(check.getData('qty')).toEqual(1);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/createCmsPage/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/createCmsPage/createPage[finish].ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport createPage from '../../services/page/createPage.js';\n\nexport default async (request: EvershopRequest, response: EvershopResponse) => {\n  const data = request.body;\n  const result = await createPage(data, {\n    routeId: request.currentRoute.id\n  });\n\n  return result;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/createCmsPage/finish[apiResponse].ts",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const page = await getDelegate<Record<string, any>>('createPage', request);\n  response.status(OK);\n  response.json({\n    data: {\n      ...page,\n      links: [\n        {\n          rel: 'cmsPageGrid',\n          href: buildUrl('cmsPageGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('cmsPageEdit', { id: page?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'view',\n          href: buildUrl('cmsPageView', { url_key: page?.url_key }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/createCmsPage/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"content\": {\n      \"type\": \"array\",\n      \"skipEscape\": true,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Content block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Content block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Content block ID is required\",\n            \"size\": \"Content block size is required\",\n            \"columns\": \"Content block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Content must be an array\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/createCmsPage/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/pages\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/createWidget/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/createWidget/createWidget[finish].js",
    "content": "import createWidget from '../../services/widget/createWidget.js';\n\nexport default async (request, response) => {\n  const data = request.body;\n  if (!data.route || (data.route.length === 1 && data.route[0] === '')) {\n    data.route = [];\n  }\n  if (!data.area || (data.area.length === 1 && data.area[0] === '')) {\n    data.area = [];\n  }\n  const result = await createWidget(data, {\n    routeId: request.currentRoute.id\n  });\n\n  return result;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/createWidget/finish[apiResponse].js",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const widget = await getDelegate('createWidget', request);\n  response.status(OK);\n  response.json({\n    data: {\n      ...widget,\n      links: [\n        {\n          rel: 'widgetGrid',\n          href: buildUrl('widgetGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('widgetEdit', { id: widget.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/createWidget/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"area\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"route\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/createWidget/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/widgets\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/deleteCmsPage/deleteCmsPage.js",
    "content": "import { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport deletePage from '../../services/page/deletePage.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { id } = request.params;\n    const page = await deletePage(id, {\n      routeId: request.currentRoute.id\n    });\n    response.status(OK);\n    response.json({\n      data: page\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/deleteCmsPage/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/pages/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/deleteWidget/deleteWidget.js",
    "content": "import { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport deleteWidget from '../../services/widget/deleteWidget.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { id } = request.params;\n    const widget = await deleteWidget(id, {\n      routeId: request.currentRoute.id\n    });\n    response.status(OK);\n    response.json({\n      data: widget\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/deleteWidget/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/widgets/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/fileBrowser/[context]validatePath[auth].js",
    "content": "import { CONSTANTS } from '../../../../lib/helpers.js';\nimport { INVALID_PAYLOAD } from '../../../../lib/util/httpStatus.js';\nimport { validatePath } from '../../services/validatePath.js';\n\nexport default (request, response, next) => {\n  const path = request.params[0] || '';\n  // Validate the path to avoid Relative Path Traversal attack\n  if (validatePath(CONSTANTS.MEDIAPATH, path) === false) {\n    response.status(INVALID_PAYLOAD).json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message: 'Invalid path'\n      }\n    });\n  } else {\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/fileBrowser/browFiles.js",
    "content": "import { OK } from '../../../../lib/util/httpStatus.js';\nimport { browFiles } from '../../services/browFiles.js';\n\nexport default async (request, response, next) => {\n  const path = request.params[0] || '';\n  const results = await browFiles(path);\n  response.status(OK);\n  response.json({\n    data: results\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/fileBrowser/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/files/*\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/fileDelete/[context]validatePath[auth].js",
    "content": "import { CONSTANTS } from '../../../../lib/helpers.js';\nimport { INVALID_PAYLOAD } from '../../../../lib/util/httpStatus.js';\nimport { validatePath } from '../../services/validatePath.js';\n\nexport default (request, response, next) => {\n  const path = request.params[0] || '';\n  // Validate the path to avoid Relative Path Traversal attack\n  if (validatePath(CONSTANTS.MEDIAPATH, path) === false) {\n    response.status(INVALID_PAYLOAD).json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message: 'Invalid path'\n      }\n    });\n  } else {\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/fileDelete/deleteFile.js",
    "content": "import { OK } from '../../../../lib/util/httpStatus.js';\nimport { deleteFile } from '../../services/deleteFile.js';\n\nexport default async (request, response, next) => {\n  const path = request.params[0] || '';\n  await deleteFile(path);\n  response.status(OK).json({\n    data: {\n      path\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/fileDelete/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/files/*\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/fileUpload/[auth,validatePath]multerFile.js",
    "content": "import { INVALID_PAYLOAD } from '../../../../lib/util/httpStatus.js';\nimport { getMulter } from '../../services/getMulter.js';\n\nconst upload = getMulter();\n\nexport default (request, response, next) => {\n  const path = request.params[0] || '';\n  if (path && !/^[a-zA-Z0-9_\\/-]+$/i.test(path)) {\n    response.status(INVALID_PAYLOAD).json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message:\n          'Invalid path. Path must be alphanumeric and can include underscores and hyphens only.'\n      }\n    });\n  } else {\n    upload.array('images', 20)(request, response, next);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/fileUpload/[multerFile]upload.js",
    "content": "import { INVALID_PAYLOAD, OK } from '../../../../lib/util/httpStatus.js';\nimport { uploadFile } from '../../services/uploadFile.js';\n\nexport default async (request, response, next) => {\n  if (!request.files || request.files.length === 0) {\n    response.status(INVALID_PAYLOAD).json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message: 'No image was provided'\n      }\n    });\n  } else {\n    const files = await uploadFile(request.files, request.params[0] || '');\n    response.status(OK).json({\n      data: {\n        files\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/fileUpload/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/files/*\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/fileUpload/validatePath.js",
    "content": "import { CONSTANTS } from '../../../../lib/helpers.js';\nimport { INVALID_PAYLOAD } from '../../../../lib/util/httpStatus.js';\nimport { validatePath } from '../../services/validatePath.js';\n\nexport default (request, response, next) => {\n  const path = request.params[0] || '';\n  // Validate the path to avoid Relative Path Traversal attack\n  if (validatePath(CONSTANTS.MEDIAPATH, path) === false) {\n    response.status(INVALID_PAYLOAD).json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message: 'Invalid path'\n      }\n    });\n  } else {\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/folderCreate/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/folderCreate/[context]validatePath[auth].js",
    "content": "import { CONSTANTS } from '../../../../lib/helpers.js';\nimport { INVALID_PAYLOAD } from '../../../../lib/util/httpStatus.js';\nimport { validatePath } from '../../services/validatePath.js';\n\nexport default (request, response, next) => {\n  const { path } = request.body || '';\n  // Validate the path to avoid Relative Path Traversal attack\n  if (\n    validatePath(CONSTANTS.MEDIAPATH, path) === false ||\n    !/^[a-zA-Z0-9_\\/-]+$/i.test(path)\n  ) {\n    response.status(INVALID_PAYLOAD).json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message:\n          'Invalid path. Path must be alphanumeric and can include underscores and hyphens only.'\n      }\n    });\n  } else {\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/folderCreate/createFolder.js",
    "content": "import { basename } from 'path';\nimport { OK } from '../../../../lib/util/httpStatus.js';\nimport { createFolder } from '../../services/createFolder.js';\n\nexport default async (request, response, next) => {\n  const { path } = request.body || '';\n  await createFolder(path);\n  response.status(OK).json({\n    data: {\n      path,\n      name: basename(path)\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/folderCreate/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"path\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"path\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/folderCreate/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/folders\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/imageUpload/[auth,validatePath]multerFile.js",
    "content": "import multerFileMiddleware from '../fileUpload/[auth,validatePath]multerFile.js';\n\nexport default multerFileMiddleware;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/imageUpload/[multerFile]upload.js",
    "content": "import uploadMiddleware from '../fileUpload/[multerFile]upload.js';\n\nexport default uploadMiddleware;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/imageUpload/[multerFile]verifyImages[upload].js",
    "content": "import { INVALID_PAYLOAD } from '../../../../lib/util/httpStatus.js';\n\nexport default (request, response, next) => {\n  if (!request.files || request.files.length === 0) {\n    next();\n  } else {\n    // Make sure the files are images\n    const test = request.files.find(\n      (file) => !file.mimetype.startsWith('image')\n    );\n    if (test) {\n      response.status(INVALID_PAYLOAD).json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Only images are allowed'\n        }\n      });\n    } else {\n      next();\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/imageUpload/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/images/*\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/imageUpload/validatePath.js",
    "content": "import validatePathMiddelware from '../fileUpload/validatePath.js';\n\nexport default validatePathMiddelware;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/updateCmsPage/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/updateCmsPage/finish[apiResponse].ts",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const page = await getDelegate<Record<string, any>>('updatePage', request);\n  response.status(OK);\n  response.json({\n    data: {\n      ...page,\n      links: [\n        {\n          rel: 'cmsPageGrid',\n          href: buildUrl('cmsPageGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('cmsPageEdit', { id: page?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'view',\n          href: buildUrl('cmsPageView', { url_key: page?.url_key }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/updateCmsPage/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"content\": {\n      \"type\": \"array\",\n      \"skipEscape\": true,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Content block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Content block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Content block ID is required\",\n            \"size\": \"Content block size is required\",\n            \"columns\": \"Content block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Content must be an array\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/updateCmsPage/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/pages/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/updateCmsPage/updatePage[finish].ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport updatePage from '../../services/page/updatePage.js';\n\nexport default async (request: EvershopRequest, response) => {\n  const data = request.body;\n  const page = await updatePage(request.params.id, data, {\n    routeId: request.currentRoute.id\n  });\n\n  return page;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/updateWidget/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/updateWidget/finish[apiResponse].ts",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const widget = await getDelegate<{ uuid: string }>('updateWidget', request);\n  response.status(OK);\n  response.json({\n    data: {\n      ...widget,\n      links: [\n        {\n          rel: 'widgetGrid',\n          href: buildUrl('widgetGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('widgetEdit', { id: widget?.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/updateWidget/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"area\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"route\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/updateWidget/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/widgets/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/api/updateWidget/updateWidget[finish].ts",
    "content": "import updateWidget from '../../services/widget/updateWidget.js';\n\nexport default async (request, response) => {\n  const data = request.body;\n\n  const widget = await updateWidget(request.params.id, data, {\n    routeId: request.currentRoute.id\n  });\n\n  return widget;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/bootstrap.ts",
    "content": "import path from 'path';\nimport { JSONSchemaType } from 'ajv';\nimport config from 'config';\nimport { CONSTANTS } from '../../lib/helpers.js';\nimport { defaultPaginationFilters } from '../../lib/util/defaultPaginationFilters.js';\nimport { merge } from '../../lib/util/merge.js';\nimport { addProcessor } from '../../lib/util/registry.js';\nimport { registerWidget } from '../../lib/widget/widgetManager.js';\nimport { registerDefaultPageCollectionFilters } from '../../modules/cms/services/registerDefaultPageCollectionFilters.js';\nimport { registerDefaultWidgetCollectionFilters } from '../../modules/cms/services/registerDefaultWidgetCollectionFilters.js';\nimport { Route } from '../../types/route.js';\n\nexport default () => {\n  addProcessor('configurationSchema', (schema) => {\n    merge(schema, {\n      properties: {\n        themeConfig: {\n          type: 'object',\n          properties: {\n            logo: {\n              type: 'object',\n              properties: {\n                alt: {\n                  type: 'string'\n                },\n                src: {\n                  type: 'string',\n                  format: 'uri-reference'\n                },\n                width: {\n                  type: 'integer'\n                },\n                height: {\n                  type: 'integer'\n                }\n              }\n            },\n            headTags: {\n              type: 'object',\n              properties: {\n                links: {\n                  type: 'array',\n                  items: {\n                    type: 'object',\n                    properties: {\n                      rel: {\n                        type: 'string'\n                      },\n                      href: {\n                        type: 'string',\n                        format: 'uri-reference'\n                      }\n                    },\n                    required: ['rel', 'href']\n                  }\n                },\n                metas: {\n                  type: 'array',\n                  items: {\n                    type: 'object',\n                    properties: {\n                      name: {\n                        type: 'string'\n                      },\n                      content: {\n                        type: 'string'\n                      }\n                    },\n                    required: ['name', 'content']\n                  }\n                },\n                scripts: {\n                  type: 'array',\n                  items: {\n                    type: 'object',\n                    properties: {\n                      src: {\n                        type: 'string',\n                        format: 'uri-reference'\n                      },\n                      type: {\n                        type: 'string'\n                      },\n                      async: {\n                        type: 'boolean'\n                      },\n                      defer: {\n                        type: 'boolean'\n                      },\n                      crossorigin: {\n                        type: 'string'\n                      },\n                      integrity: {\n                        type: 'string'\n                      },\n                      noModule: {\n                        type: 'string'\n                      },\n                      nonce: {\n                        type: 'string'\n                      }\n                    },\n                    required: ['src']\n                  }\n                },\n                bases: {\n                  type: 'array',\n                  items: {\n                    type: 'object',\n                    properties: {\n                      href: {\n                        type: 'string',\n                        format: 'uri-reference'\n                      }\n                    },\n                    required: ['href']\n                  }\n                }\n              }\n            }\n          }\n        },\n        system: {\n          type: 'object',\n          properties: {\n            file_storage: {\n              type: 'string',\n              enum: ['local']\n            }\n          }\n        }\n      }\n    });\n    return schema;\n  });\n\n  const defaultThemeConfig = {\n    logo: {\n      alt: undefined,\n      src: undefined,\n      width: undefined,\n      height: undefined\n    },\n    headTags: {\n      links: [],\n      metas: [],\n      scripts: [],\n      bases: []\n    },\n    copyRight: `© 2022 Evershop. All Rights Reserved.`\n  };\n  config.util.setModuleDefaults('themeConfig', defaultThemeConfig);\n\n  // Set the default file storage to local\n  config.util.setModuleDefaults('system', {\n    file_storage: 'local'\n  });\n\n  registerWidget({\n    type: 'text_block',\n    settingComponent: path.resolve(\n      CONSTANTS.MODULESPATH,\n      'cms/components/TextBlockSetting.js'\n    ),\n    component: path.resolve(\n      CONSTANTS.MODULESPATH,\n      'cms/components/TextBlock.js'\n    ),\n    name: 'Text block',\n    description: 'Add rich text content',\n    defaultSettings: {\n      className: 'page-width'\n    },\n    enabled: true\n  });\n\n  registerWidget({\n    type: 'basic_menu',\n    settingComponent: path.resolve(\n      CONSTANTS.MODULESPATH,\n      'cms/components/BasicMenuSetting.js'\n    ),\n    component: path.resolve(\n      CONSTANTS.MODULESPATH,\n      'cms/components/BasicMenu.js'\n    ),\n    name: 'Menu',\n    description: 'Navigation links',\n    defaultSettings: {},\n    enabled: true\n  });\n\n  registerWidget({\n    type: 'banner',\n    settingComponent: path.resolve(\n      CONSTANTS.MODULESPATH,\n      'cms/components/BannerSetting.js'\n    ),\n    component: path.resolve(CONSTANTS.MODULESPATH, 'cms/components/Banner.js'),\n    defaultSettings: {},\n    name: 'Banner',\n    description: 'Image with call-to-action',\n    enabled: true\n  });\n\n  registerWidget({\n    type: 'simple_slider',\n    settingComponent: path.resolve(\n      CONSTANTS.MODULESPATH,\n      'cms/components/SlideshowSetting.js'\n    ),\n    component: path.resolve(\n      CONSTANTS.MODULESPATH,\n      'cms/components/Slideshow.js'\n    ),\n    defaultSettings: {},\n    name: 'Simple Slideshow',\n    description: 'Rotating image carousel',\n    enabled: true\n  });\n\n  // Reigtering the default filters for cms page collection\n  addProcessor(\n    'cmsPageCollectionFilters',\n    registerDefaultPageCollectionFilters,\n    1\n  );\n  addProcessor<Array<any>>(\n    'cmsPageCollectionFilters',\n    (filters) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n\n  // Reigtering the default filters for widget collection\n  addProcessor<Array<any>>(\n    'widgetCollectionFilters',\n    registerDefaultWidgetCollectionFilters,\n    1\n  );\n  addProcessor<Array<any>>(\n    'widgetCollectionFilters',\n    (filters) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n\n  addProcessor('payloadSchema', function (schema: JSONSchemaType<any>) {\n    const ctx = this as { route: Route };\n    const route = ctx.route;\n    if (route.id === 'createWidget' || route.id === 'updateWidget') {\n      schema.properties.settings = {\n        properties: {\n          text: {\n            type: 'string',\n            skipEscape: true\n          }\n        }\n      };\n    }\n    return schema;\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/components/Banner.tsx",
    "content": "import { Image } from '@components/common/Image.js';\nimport React from 'react';\n\ninterface BannerProps {\n  bannerWidget: {\n    src: string;\n    alignment: string;\n    width: string;\n    height: string;\n    alt: string;\n  };\n}\n\nexport default function Banner({\n  bannerWidget: { src, alignment, width, height, alt }\n}: BannerProps) {\n  // Parse tailwind classes for alignment\n  const alignmentClass =\n    alignment === 'left'\n      ? 'justify-start'\n      : alignment === 'center'\n      ? 'justify-center'\n      : 'justify-end';\n\n  return (\n    <div className={`banner-widget w-full flex ${alignmentClass}`}>\n      <Image\n        src={src}\n        width={parseInt(width, 10)}\n        height={parseInt(height, 10)}\n        className={alignmentClass}\n        alt={alt}\n        priority={true}\n      />\n    </div>\n  );\n}\n\nexport const query = `\n  query Query($src: String, $alignment: String, $width: Float, $height: Float, $alt: String) {\n    bannerWidget(src: $src, alignment: $alignment, width: $width, height: $height, alt: $alt) {\n      src\n      alignment\n      width\n      height\n      alt\n    }\n  }\n`;\n\nexport const variables = `{\n  src: getWidgetSetting(\"src\"),\n  alignment: getWidgetSetting(\"alignment\"),\n  width: getWidgetSetting(\"width\"),\n  height: getWidgetSetting(\"height\"),\n  alt: getWidgetSetting(\"alt\")\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/components/BannerSetting.tsx",
    "content": "/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/img-redundant-alt */\nimport { FileBrowser } from '@components/admin/FileBrowser.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\nimport { useFormContext } from 'react-hook-form';\n\ntype AlignmentType = 'left' | 'center' | 'right';\n\ninterface BannerSettingProps {\n  bannerWidget: {\n    src: string;\n    alignment: AlignmentType;\n    width: number;\n    height: number;\n    alt: string;\n    link?: string;\n  };\n}\n\nexport default function BannerSetting({\n  bannerWidget: { src, alignment = 'left', width, height, alt, link }\n}: BannerSettingProps) {\n  const { setValue, watch } = useFormContext();\n  const image = watch('settings.src', src);\n  const currentAlignment = watch('settings.alignment', alignment);\n  const [openFileBrowser, setOpenFileBrowser] = React.useState(false);\n  const [imageDimensions, setImageDimensions] = React.useState({\n    width: width || 0,\n    height: height || 0\n  });\n\n  // Function to get image dimensions\n  const getImageDimensions = (imageUrl: string) => {\n    if (!imageUrl) return;\n\n    const img = new Image();\n    img.onload = () => {\n      const newWidth = img.naturalWidth;\n      const newHeight = img.naturalHeight;\n      setImageDimensions({ width: newWidth, height: newHeight });\n\n      // Update form values\n      setValue('settings.width', newWidth);\n      setValue('settings.height', newHeight);\n    };\n    img.src = imageUrl;\n  };\n\n  // Get dimensions when image changes\n  React.useEffect(() => {\n    if (image) {\n      getImageDimensions(image);\n    }\n  }, [image]);\n  return (\n    <div className={`banner-widget space-y-3`}>\n      <div className=\"max-h-96\">\n        {openFileBrowser && (\n          <FileBrowser\n            isMultiple={false}\n            onInsert={(file) => {\n              setValue('settings.src', file);\n              setOpenFileBrowser(false);\n            }}\n            close={() => setOpenFileBrowser(false)}\n          />\n        )}\n      </div>\n      <div className=\"w-full h-80 border border-gray-300 bg-gray-200 relative overflow-hidden\">\n        {/* Add a semi-transparent grid background to better visualize alignment */}\n        <div className=\"absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+CiAgPHJlY3Qgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIiBmaWxsPSIjZjFmMWYxIj48L3JlY3Q+CiAgPHBhdGggZD0iTTAgMGgyMHYyMEgwVjB6IiBmaWxsPSJub25lIiBzdHJva2U9IiNlNWU1ZTUiIHN0cm9rZS13aWR0aD0iMSI+PC9wYXRoPgo8L3N2Zz4=')]\"></div>\n\n        {/* Apply alignment to a container that only takes necessary width */}\n        <div\n          className={`flex h-full w-full ${\n            currentAlignment === 'center'\n              ? 'justify-center'\n              : currentAlignment === 'right'\n              ? 'justify-end'\n              : 'justify-start'\n          } p-4`}\n        >\n          {image && (\n            <div\n              className={`relative h-full flex items-center w-full ${\n                currentAlignment === 'center'\n                  ? 'justify-center'\n                  : currentAlignment === 'right'\n                  ? 'justify-end'\n                  : 'justify-start'\n              }`}\n            >\n              <img\n                src={image}\n                className=\"h-auto max-h-full object-contain shadow-md rounded\"\n                style={{\n                  maxWidth: '60%' // Consistent size for all alignments\n                }}\n                onLoad={(e) => {\n                  // This is a backup in case the useEffect doesn't trigger\n                  const img = e.target as HTMLImageElement;\n                  if (img.naturalWidth > 0 && img.naturalHeight > 0) {\n                    if (\n                      imageDimensions.width !== img.naturalWidth ||\n                      imageDimensions.height !== img.naturalHeight\n                    ) {\n                      setImageDimensions({\n                        width: img.naturalWidth,\n                        height: img.naturalHeight\n                      });\n                      setValue('settings.width', img.naturalWidth);\n                      setValue('settings.height', img.naturalHeight);\n                    }\n                  }\n                }}\n                alt=\"Banner Image\"\n              />\n            </div>\n          )}\n        </div>\n\n        <Button\n          variant=\"outline\"\n          onClick={(e) => {\n            e.preventDefault();\n            setOpenFileBrowser(true);\n          }}\n          className=\"absolute bottom-2 right-2 z-10\"\n        >\n          Select Image\n        </Button>\n      </div>\n\n      <InputField\n        type=\"hidden\"\n        name=\"settings.src\"\n        defaultValue={image || ''}\n      />\n\n      <div className=\"mb-4\">\n        <div className=\"mb-2\">\n          <label>Alignment</label>\n        </div>\n        <div className=\"grid grid-cols-3 gap-2\">\n          <div\n            onClick={() => {\n              setValue('settings.alignment', 'left');\n            }}\n            className={`border p-3 flex justify-center items-center cursor-pointer ${\n              currentAlignment === 'left'\n                ? 'border-blue-500 bg-blue-100'\n                : 'border-gray-300'\n            }`}\n          >\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"24\"\n              height=\"24\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            >\n              <line x1=\"5\" y1=\"6\" x2=\"19\" y2=\"6\"></line>\n              <line x1=\"5\" y1=\"12\" x2=\"12\" y2=\"12\"></line>\n              <line x1=\"5\" y1=\"18\" x2=\"16\" y2=\"18\"></line>\n            </svg>\n          </div>\n          <div\n            onClick={() => {\n              setValue('settings.alignment', 'center');\n            }}\n            className={`border p-3 flex justify-center items-center cursor-pointer ${\n              currentAlignment === 'center'\n                ? 'border-blue-500 bg-blue-100'\n                : 'border-gray-300'\n            }`}\n          >\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"24\"\n              height=\"24\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            >\n              <line x1=\"5\" y1=\"6\" x2=\"19\" y2=\"6\"></line>\n              <line x1=\"8\" y1=\"12\" x2=\"16\" y2=\"12\"></line>\n              <line x1=\"6\" y1=\"18\" x2=\"18\" y2=\"18\"></line>\n            </svg>\n          </div>\n          <div\n            onClick={() => {\n              setValue('settings.alignment', 'right');\n            }}\n            className={`border p-3 flex justify-center items-center cursor-pointer ${\n              currentAlignment === 'right'\n                ? 'border-blue-500 bg-blue-100'\n                : 'border-gray-300'\n            }`}\n          >\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"24\"\n              height=\"24\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            >\n              <line x1=\"5\" y1=\"6\" x2=\"19\" y2=\"6\"></line>\n              <line x1=\"12\" y1=\"12\" x2=\"19\" y2=\"12\"></line>\n              <line x1=\"8\" y1=\"18\" x2=\"19\" y2=\"18\"></line>\n            </svg>\n          </div>\n        </div>\n        <InputField\n          type=\"hidden\"\n          name=\"settings.alignment\"\n          defaultValue={alignment}\n        />\n      </div>\n\n      {/* Hidden width and height fields - automatically populated */}\n      <InputField\n        type=\"hidden\"\n        name=\"settings.width\"\n        defaultValue={width || imageDimensions.width}\n      />\n      <InputField\n        type=\"hidden\"\n        name=\"settings.height\"\n        defaultValue={height || imageDimensions.height}\n      />\n\n      {/* Display image dimensions as information */}\n      <div className=\"mb-4\">\n        <div className=\"text-sm text-gray-500\">\n          Image dimensions: {imageDimensions.width} × {imageDimensions.height}{' '}\n          pixels\n        </div>\n      </div>\n      <InputField\n        type=\"text\"\n        label=\"Alt Text\"\n        placeholder='e.g., \"Promotional Banner\"'\n        name=\"settings.alt\"\n        defaultValue={alt}\n      />\n      <InputField\n        type=\"text\"\n        placeholder=\"e.g., https://example.com\"\n        label=\"Banner Link\"\n        name=\"settings.link\"\n        defaultValue={link || ''}\n      />\n    </div>\n  );\n}\n\nexport const query = `\n  query Query($src: String, $alignment: String, $width: Float, $height: Float, $alt: String) {\n    bannerWidget(src: $src, alignment: $alignment, width: $width, height: $height, alt: $alt) {\n      src\n      alignment\n      width\n      height\n      alt\n    }\n  }\n`;\n\nexport const variables = `{\n  src: getWidgetSetting(\"src\"),\n  alignment: getWidgetSetting(\"alignment\"),\n  width: getWidgetSetting(\"width\"),\n  height: getWidgetSetting(\"height\"),\n  alt: getWidgetSetting(\"alt\")\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/components/BasicMenu.tsx",
    "content": "import { useIsMobile } from '@components/common/ui/hooks/useIsMobile.js';\nimport {\n  NavigationMenu,\n  NavigationMenuContent,\n  NavigationMenuItem,\n  NavigationMenuLink,\n  NavigationMenuList,\n  NavigationMenuTrigger\n} from '@components/common/ui/NavigationMenu.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport React from 'react';\n\ninterface BasicMenuProps {\n  basicMenuWidget: {\n    menus: {\n      id: string;\n      name: string;\n      url: string;\n      type: string;\n      uuid: string;\n      children: {\n        name: string;\n        url: string;\n        type: string;\n        uuid: string;\n      }[];\n    }[];\n    isMain: boolean;\n    className: string;\n  };\n}\n\nexport default function BasicMenu({\n  basicMenuWidget: { menus, isMain, className }\n}: BasicMenuProps) {\n  const [isOpen, setIsOpen] = React.useState(!isMain);\n  const isMobile = useIsMobile();\n  const [currentPath, setCurrentPath] = React.useState('');\n\n  React.useEffect(() => {\n    setCurrentPath(window.location.pathname);\n  }, []);\n\n  const isActive = (url: string) => {\n    if (url === '/') {\n      return currentPath === '/';\n    }\n    return currentPath.startsWith(url);\n  };\n\n  const toggleMenu = () => {\n    setIsOpen(!isOpen);\n  };\n\n  return (\n    <div className={className}>\n      <div className=\"flex justify-start gap-4 items-center\">\n        <nav className=\"p-2 relative md:flex md:justify-center w-full\">\n          <div className=\"flex justify-between items-center w-full\">\n            {isMain && isMobile && (\n              <div>\n                <a\n                  href=\"#\"\n                  onClick={(e) => {\n                    e.preventDefault();\n                    toggleMenu();\n                  }}\n                  className=\"text-black focus:outline-none\"\n                >\n                  <svg\n                    className=\"w-6 h-6\"\n                    fill=\"none\"\n                    stroke=\"currentColor\"\n                    viewBox=\"0 0 24 24\"\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                  >\n                    <path\n                      strokeLinecap=\"round\"\n                      strokeLinejoin=\"round\"\n                      strokeWidth=\"2\"\n                      d=\"M4 6h16M4 12h16m-7 6h7\"\n                    />\n                  </svg>\n                </a>\n              </div>\n            )}\n            <div\n              className={cn(\n                isMain\n                  ? 'md:flex absolute md:relative -left-10 md:left-0 top-full md:top-auto mt-2 md:mt-0 w-screen md:w-auto p-2 md:p-0 min-w-62.5 bg-white md:bg-transparent z-30'\n                  : 'flex relative -left-10 md:left-0 w-screen md:w-auto p-2 md:p-0 min-w-62.5 bg-white md:bg-transparent',\n                isOpen ? 'block' : 'hidden',\n                'md:flex'\n              )}\n            >\n              <NavigationMenu className=\"w-full max-w-full\">\n                <NavigationMenuList className=\"flex-col md:flex-row items-start md:items-center w-full md:w-auto\">\n                  {menus.map((item) => (\n                    <NavigationMenuItem\n                      key={item.uuid}\n                      className=\"w-full md:w-auto\"\n                    >\n                      {item.children.length > 0 && !isMobile ? (\n                        <>\n                          <NavigationMenuTrigger className=\"w-full md:w-auto justify-start md:justify-center bg-transparent hover:bg-transparent focus:bg-transparent data-open:bg-transparent data-open:hover:bg-transparent data-open:focus:bg-transparent data-popup-open:bg-transparent data-popup-open:hover:bg-transparent hover:font-semibold hover:text-primary\">\n                            {item.name}\n                          </NavigationMenuTrigger>\n                          <NavigationMenuContent>\n                            <ul className=\"flex flex-col min-w-50 p-2\">\n                              {item.children.map((subItem) => (\n                                <li key={subItem.uuid}>\n                                  <NavigationMenuLink\n                                    href={subItem.url}\n                                    className=\"w-full\"\n                                  >\n                                    {subItem.name}\n                                  </NavigationMenuLink>\n                                </li>\n                              ))}\n                            </ul>\n                          </NavigationMenuContent>\n                        </>\n                      ) : (\n                        <NavigationMenuLink\n                          href={item.url}\n                          className=\"w-full md:w-auto px-4 py-2 hover:text-primary data-[active=true]:bg-transparent data-[active=true]:hover:bg-transparent transition-colors data-[active=true]:text-primary data-[active=true]:font-semibold hover:bg-transparent focus:bg-transparent hover:underline text-xl md:text-base\"\n                          data-active={isActive(item.url)}\n                        >\n                          {item.name}\n                        </NavigationMenuLink>\n                      )}\n                    </NavigationMenuItem>\n                  ))}\n                </NavigationMenuList>\n              </NavigationMenu>\n            </div>\n          </div>\n        </nav>\n      </div>\n    </div>\n  );\n}\n\nexport const query = `\n  query Query($settings: JSON) {\n    basicMenuWidget(settings: $settings) {\n      menus {\n        id\n        name\n        url\n        type\n        uuid\n        children {\n          name\n          url\n          type\n          uuid\n        }\n      }\n      isMain\n      className\n    }\n  }\n`;\n\nexport const variables = `{\n  settings: getWidgetSetting()\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/components/BasicMenuSetting.scss",
    "content": ".menu__container > * {\n  margin-top: 10px;\n}\n\n.item {\n  padding: 10px;\n  border: 1px dashed var(--border);\n  margin-top: 10px;\n  background-color: #fff;\n}\n\n.menu__container .draggable-source--is-dragging {\n  background: var(--divider);\n  color: var(--divider);\n}\n\n.menu__container .draggable-source--is-dragging > * {\n  visibility: hidden;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/components/BasicMenuSetting.tsx",
    "content": "import Spinner from '@components/admin/Spinner.js';\nimport { CheckboxField } from '@components/common/form/CheckboxField.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTrigger,\n  DialogTitle\n} from '@components/common/ui/Dialog.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport { Item, ItemContent } from '@components/common/ui/Item.js';\nimport {\n  DndContext,\n  closestCenter,\n  KeyboardSensor,\n  PointerSensor,\n  useSensor,\n  useSensors\n} from '@dnd-kit/core';\nimport {\n  arrayMove,\n  SortableContext,\n  sortableKeyboardCoordinates,\n  verticalListSortingStrategy\n} from '@dnd-kit/sortable';\nimport { useSortable } from '@dnd-kit/sortable';\nimport { CSS } from '@dnd-kit/utilities';\nimport React, { useEffect } from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport CreatableSelect from 'react-select/creatable';\nimport uniqid from 'uniqid';\nimport { useQuery } from 'urql';\nimport './BasicMenuSetting.scss';\n\nconst menuQuery = `\n  query Query ($filters: [FilterInput]) {\n    categories (filters: $filters) {\n      items {\n        value: uuid,\n        label: name\n        path {\n          name\n        }\n      }\n    }\n    cmsPages (filters: $filters) {\n      items {\n        value: uuid,\n        label: name\n      }\n    }\n  }\n`;\n\ninterface MenuItem {\n  id: string;\n  name: string;\n  url: string;\n  type: string;\n  uuid: string;\n  children: MenuItem[];\n}\n\ninterface SortableMenuItemProps {\n  item: MenuItem;\n  updateItem: (item: MenuItem) => void;\n  deleteItem: (item: MenuItem) => void;\n  isChild?: boolean;\n}\n\nconst SortableMenuItem: React.FC<SortableMenuItemProps> = ({\n  item,\n  updateItem,\n  deleteItem,\n  isChild = false\n}) => {\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    transform,\n    transition,\n    isDragging\n  } = useSortable({ id: item.id });\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n\n  const style = {\n    transform: CSS.Transform.toString(transform),\n    transition,\n    opacity: isDragging ? 0.5 : 1\n  };\n\n  const [itemInEdit, setItemInEdit] = React.useState(item);\n\n  const addChildren = (i) => {\n    updateItem({\n      ...item,\n      children: [...item.children, i]\n    });\n  };\n\n  const updateItemFunc = (i) => {\n    if (i.id === item.id) {\n      updateItem(i);\n    } else {\n      addChildren(i);\n    }\n    setDialogOpen(false);\n  };\n\n  return (\n    <div\n      ref={setNodeRef}\n      style={style}\n      className=\"flex justify-between py-2 px-2 bg-white border border-border rounded mb-2\"\n    >\n      <div className=\"flex justify-start gap-3 items-center\">\n        <button\n          type=\"button\"\n          className=\"cursor-move p-1\"\n          {...attributes}\n          {...listeners}\n        >\n          <svg\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            fill=\"#949494\"\n            width={20}\n            height={20}\n          >\n            <g>\n              <path fill=\"none\" d=\"M0 0h24v24H0z\" />\n              <path\n                fillRule=\"nonzero\"\n                d=\"M14 6h2v2h5a1 1 0 0 1 1 1v7.5L16 13l.036 8.062 2.223-2.15L20.041 22H9a1 1 0 0 1-1-1v-5H6v-2h2V9a1 1 0 0 1 1-1h5V6zm8 11.338V21a1 1 0 0 1-.048.307l-1.96-3.394L22 17.338zM4 14v2H2v-2h2zm0-4v2H2v-2h2zm0-4v2H2V6h2zm0-4v2H2V2h2zm4 0v2H6V2h2zm4 0v2h-2V2h2zm4 0v2h-2V2h2z\"\n              />\n            </g>\n          </svg>\n        </button>\n        <div>{item.name}</div>\n      </div>\n      <div className=\"flex justify-end gap-3 items-center\">\n        <Button\n          variant={'outline'}\n          onClick={() => {\n            setItemInEdit(item);\n            setDialogOpen(true);\n          }}\n          size={'sm'}\n        >\n          Edit\n        </Button>\n        {!isChild && (\n          <Button\n            variant={'outline'}\n            onClick={() => {\n              setItemInEdit({\n                id: uniqid(),\n                name: '',\n                url: '',\n                type: 'category',\n                uuid: '',\n                children: []\n              });\n              setDialogOpen(true);\n            }}\n            size={'sm'}\n          >\n            Add child\n          </Button>\n        )}\n        <Button variant={'destructive'} onClick={() => deleteItem(item)}>\n          Delete\n        </Button>\n      </div>\n      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>{`Edit Menu Item: ${itemInEdit.name}`}</DialogTitle>\n          </DialogHeader>\n          <MenuSettingPopup item={itemInEdit} updateItem={updateItemFunc} />\n        </DialogContent>\n      </Dialog>\n    </div>\n  );\n};\n\nconst MenuSettingPopup: React.FC<{\n  item: MenuItem;\n  updateItem: (item: MenuItem) => void;\n}> = ({ item, updateItem }) => {\n  const [currentItem, setCurrentItem] = React.useState(item);\n  const [err, setErr] = React.useState<string | null>(null);\n\n  const [result] = useQuery({\n    query: menuQuery,\n    variables: {\n      filters: []\n    }\n  });\n  const { data, fetching, error } = result;\n\n  if (fetching) {\n    return (\n      <Item variant={'outline'}>\n        <ItemContent>\n          <Spinner width={25} height={25} />\n        </ItemContent>\n      </Item>\n    );\n  }\n  if (error) {\n    return (\n      <Item variant={'outline'}>\n        <ItemContent>\n          <div className=\"text-destructive\">{error.message}</div>\n        </ItemContent>\n      </Item>\n    );\n  }\n\n  const groupOptions = [\n    {\n      label: 'Categories',\n      options: data.categories.items.map((i) => ({\n        ...i,\n        label: i.path.map((p) => p.name).join(' > ')\n      }))\n    },\n    {\n      label: 'CMS Pages',\n      options: data.cmsPages.items\n    },\n    {\n      label: 'Custom',\n      options:\n        currentItem.type === 'custom'\n          ? [\n              {\n                value: currentItem.uuid,\n                label: currentItem.uuid\n              }\n            ]\n          : []\n    }\n  ];\n\n  const handleCreate = (inputValue) => {\n    setCurrentItem({\n      ...item,\n      uuid: inputValue,\n      name: inputValue,\n      url: inputValue,\n      type: 'custom'\n    });\n  };\n\n  return (\n    <div className=\"grid grid-flow-row gap-5\">\n      <div>\n        <Input\n          id=\"menuName\"\n          type=\"text\"\n          value={currentItem.name}\n          placeholder=\"Menu name\"\n          onChange={(e) =>\n            setCurrentItem({\n              ...currentItem,\n              name: e.target.value\n            })\n          }\n          className=\"w-full \"\n        />\n      </div>\n      <div>\n        <CreatableSelect\n          isClearable\n          onChange={(newValue: {\n            value: string;\n            label: string;\n            __typename?: string;\n          }) => {\n            setCurrentItem({\n              ...currentItem,\n              uuid: newValue?.value || '',\n              name: newValue?.label || '',\n              type: newValue?.__typename === 'Category' ? 'category' : 'page'\n            });\n          }}\n          onCreateOption={handleCreate}\n          options={groupOptions}\n          value={{\n            value: currentItem.uuid,\n            label:\n              currentItem.type === 'custom'\n                ? currentItem.uuid\n                : [...groupOptions[0].options, ...groupOptions[1].options].find(\n                    (option) => option.value === currentItem.uuid\n                  )?.label || ''\n          }}\n        />\n      </div>\n      {err && <div className=\"text-destructive\">{err}</div>}\n      <div className=\"flex justify-end\">\n        <Button\n          onClick={() => {\n            if (currentItem.uuid === '') {\n              setErr('Please select a menu item');\n              return;\n            }\n            if (currentItem.name === '') {\n              setErr('Please enter a name');\n              return;\n            }\n            updateItem(currentItem);\n          }}\n        >\n          Save\n        </Button>\n      </div>\n    </div>\n  );\n};\n\ninterface BasicMenuSettingProps {\n  basicMenuWidget: {\n    menus: MenuItem[];\n    isMain: boolean;\n    className: string;\n  };\n}\n\nexport default function BasicMenuSetting({\n  basicMenuWidget: { menus, isMain, className }\n}: BasicMenuSettingProps) {\n  const { register, setValue } = useFormContext();\n  const [items, setItems] = React.useState(menus);\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n\n  const sensors = useSensors(\n    useSensor(PointerSensor),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates\n    })\n  );\n\n  const handleDragEnd = (event) => {\n    const { active, over } = event;\n\n    if (active.id !== over.id) {\n      setItems((items) => {\n        const oldIndex = items.findIndex((item) => item.id === active.id);\n        const newIndex = items.findIndex((item) => item.id === over.id);\n        return arrayMove(items, oldIndex, newIndex);\n      });\n    }\n  };\n\n  const handleChildDragEnd = (event, parentId) => {\n    const { active, over } = event;\n    if (active.id !== over.id) {\n      setItems((items) => {\n        return items.map((item) => {\n          if (item.id === parentId) {\n            const oldIndex = item.children.findIndex(\n              (child) => child.id === active.id\n            );\n            const newIndex = item.children.findIndex(\n              (child) => child.id === over.id\n            );\n\n            return {\n              ...item,\n              children: arrayMove(item.children, oldIndex, newIndex)\n            };\n          }\n          return item;\n        });\n      });\n    }\n  };\n\n  const updateItem = (item) => {\n    setItems((prevItems) => {\n      const newItems = prevItems.map((prevItem) => {\n        if (prevItem.id === item.id) {\n          return item;\n        } else if (prevItem.children.length > 0) {\n          return {\n            ...prevItem,\n            children: prevItem.children.map((child) => {\n              if (child.id === item.id) {\n                return item;\n              }\n              return child;\n            })\n          };\n        }\n        return prevItem;\n      });\n      return newItems;\n    });\n  };\n\n  const deleteItem = (item) => {\n    setItems((prevItems) => {\n      const newItems = prevItems.filter((prevItem) => {\n        if (prevItem.id === item.id) {\n          return false;\n        } else if (prevItem.children.length > 0) {\n          prevItem.children = prevItem.children.filter(\n            (child) => child.id !== item.id\n          );\n        }\n        return true;\n      });\n      return newItems;\n    });\n  };\n\n  useEffect(() => {\n    setValue('settings.menus', items);\n  }, [items]);\n\n  return (\n    <>\n      <DndContext\n        sensors={sensors}\n        collisionDetection={closestCenter}\n        onDragEnd={handleDragEnd}\n      >\n        <SortableContext\n          items={items.map((item) => item.id)}\n          strategy={verticalListSortingStrategy}\n        >\n          <div className=\"space-y-2\">\n            {items.map((menu) => (\n              <div key={menu.id}>\n                <SortableMenuItem\n                  item={menu}\n                  updateItem={updateItem}\n                  deleteItem={deleteItem}\n                />\n                {menu.children && menu.children.length > 0 && (\n                  <div className=\"ml-5 mt-2\">\n                    <DndContext\n                      sensors={sensors}\n                      collisionDetection={closestCenter}\n                      onDragEnd={(event) => handleChildDragEnd(event, menu.id)}\n                    >\n                      <SortableContext\n                        items={menu.children.map((child) => child.id)}\n                        strategy={verticalListSortingStrategy}\n                      >\n                        <div className=\"space-y-1\">\n                          {menu.children.map((child) => (\n                            <SortableMenuItem\n                              key={child.id}\n                              item={child}\n                              updateItem={updateItem}\n                              deleteItem={deleteItem}\n                              isChild={true}\n                            />\n                          ))}\n                        </div>\n                      </SortableContext>\n                    </DndContext>\n                  </div>\n                )}\n              </div>\n            ))}\n          </div>\n        </SortableContext>\n      </DndContext>\n\n      <input\n        type=\"hidden\"\n        {...register('settings.menus')}\n        value={JSON.stringify(items)}\n      />\n      <div className=\"space-y-3\">\n        <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n          <DialogTrigger>\n            <Button variant={'outline'} size={'sm'}>\n              Add Menu Item\n            </Button>\n          </DialogTrigger>\n\n          <DialogContent>\n            <DialogHeader>\n              <DialogTitle>Add Menu Item</DialogTitle>\n            </DialogHeader>\n            <MenuSettingPopup\n              item={{\n                id: uniqid(),\n                name: '',\n                url: '',\n                type: 'category',\n                uuid: '',\n                children: []\n              }}\n              updateItem={(item) => {\n                setItems((prevItems) => [...prevItems, item]);\n                setDialogOpen(false);\n              }}\n            />\n          </DialogContent>\n        </Dialog>\n\n        <div>\n          <CheckboxField\n            label=\"Is Main Menu?\"\n            name=\"settings.isMain\"\n            defaultValue={isMain}\n          />\n        </div>\n        <div>\n          <InputField\n            label=\"Custom CSS classes\"\n            name=\"settings.className\"\n            defaultValue={className}\n            helperText=\"Custom CSS classes for the menu\"\n          />\n        </div>\n      </div>\n    </>\n  );\n}\n\nexport const query = `\n  query Query($settings: JSON) {\n    basicMenuWidget(settings: $settings) {\n      menus {\n        id\n        name\n        url\n        type\n        uuid\n        children {\n          id\n          name\n          url\n          type\n          uuid\n        }\n      }\n      isMain\n      className\n    }\n  }\n`;\n\nexport const variables = `{\n  settings: getWidgetSetting()\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/components/Slideshow.tsx",
    "content": "import { Image } from '@components/common/Image.js';\nimport React from 'react';\nimport Slider from 'react-slick';\nimport 'slick-carousel/slick/slick.css';\n\nfunction PrevArrow(props: any) {\n  const { onClick } = props;\n  return (\n    <button\n      className=\"absolute bottom-[20px] right-[70px] z-20 flex h-10 w-10 items-center justify-center rounded-full bg-black/50 text-white transition-all hover:bg-black/70 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-white/50 md:bottom-[20px] md:right-[70px] md:h-10 md:w-10\"\n      onClick={onClick}\n      aria-label=\"Previous slide\"\n      type=\"button\"\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 24 24\"\n        width=\"24\"\n        height=\"24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        className=\"h-6 w-6 md:h-6 md:w-6\"\n      >\n        <polyline points=\"15 18 9 12 15 6\"></polyline>\n      </svg>\n    </button>\n  );\n}\n\nfunction NextArrow(props: any) {\n  const { onClick } = props;\n  return (\n    <button\n      className=\"absolute bottom-[20px] right-5 z-20 flex h-10 w-10 items-center justify-center rounded-full bg-black/50 text-white transition-all hover:bg-black/70 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-white/50 md:bottom-[20px] md:right-5 md:h-10 md:w-10\"\n      onClick={onClick}\n      aria-label=\"Next slide\"\n      type=\"button\"\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 24 24\"\n        width=\"24\"\n        height=\"24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        className=\"h-6 w-6 md:h-6 md:w-6\"\n      >\n        <polyline points=\"9 18 15 12 9 6\"></polyline>\n      </svg>\n    </button>\n  );\n}\n\nfunction CustomDot(props: any) {\n  const { onClick, active, className } = props;\n  const isActive = active || (className && className.includes('active'));\n\n  return (\n    <button\n      onClick={onClick}\n      className={`mx-1 my-0 h-3 w-3 rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-white/50 md:h-3 md:w-3 ${\n        isActive\n          ? '!bg-black scale-125 shadow-md'\n          : '!bg-black/70 !hover:bg-black/90'\n      }`}\n      aria-label=\"Go to slide\"\n      type=\"button\"\n    />\n  );\n}\n\nconst SliderComponent = Slider as any;\n\ninterface SlideData {\n  id: string;\n  image: string;\n  width?: number; // Image natural width (automatically detected)\n  height?: number; // Image natural height (automatically detected)\n  headline?: string;\n  subText?: string;\n  buttonText?: string;\n  buttonLink?: string;\n  buttonColor?: string;\n}\n\ninterface SlideshowProps {\n  slideshowWidget: {\n    slides: SlideData[];\n    autoplay?: boolean;\n    autoplaySpeed?: number;\n    arrows?: boolean;\n    dots?: boolean;\n  };\n}\n\nexport default function Slideshow({\n  slideshowWidget: {\n    slides = [],\n    autoplay = true,\n    autoplaySpeed = 3000,\n    arrows = true,\n    dots = true\n  }\n}: SlideshowProps) {\n  const settings = {\n    dots: false,\n    infinite: true,\n    speed: 500,\n    slidesToShow: 1,\n    slidesToScroll: 1,\n    autoplay: Boolean(autoplay),\n    autoplaySpeed: Number(autoplaySpeed) || 3000,\n    arrows: Boolean(arrows),\n    fade: false,\n    pauseOnHover: true,\n    adaptiveHeight: true,\n    nextArrow: arrows ? <NextArrow /> : undefined,\n    prevArrow: arrows ? <PrevArrow /> : undefined,\n    customPaging: function (i: number) {\n      return <CustomDot active={false} />;\n    },\n    appendDots: (dots: React.ReactNode) => (\n      <div className=\"w-full flex justify-center items-center\">\n        <div className=\"pr-[120px] md:pr-[120px]\">{dots}</div>\n      </div>\n    ),\n    dotsClass: 'slick-dots custom-dots-container'\n  };\n\n  if (!slides || slides.length === 0) {\n    return null;\n  }\n\n  const containerClasses = ['slideshow-widget', 'relative', 'w-full'].join(' ');\n\n  const containerStyle: React.CSSProperties = {\n    height: 'auto',\n    maxWidth: '100%'\n  };\n\n  const sliderStyle: React.CSSProperties = {\n    height: 'auto' // Adaptive height for slider\n  };\n\n  return (\n    <div className={containerClasses} style={containerStyle}>\n      <SliderComponent {...settings} style={sliderStyle}>\n        {slides.map((slide) => (\n          <div\n            key={slide.id}\n            className=\"relative lg:h-auto slide__wrapper !block\"\n            style={{ display: 'block' }}\n          >\n            <div className=\"relative w-full h-full\">\n              <Image\n                src={slide.image}\n                alt={slide.headline || 'Slideshow image'}\n                width={slide.width || 1920} // Use individual slide width if available\n                height={slide.height || 0} // Use individual slide height if available\n                style={{\n                  objectFit: 'cover',\n                  width: '100%',\n                  height: '100%',\n                  objectPosition: 'center'\n                }}\n                sizes=\"100vw\"\n                priority={true}\n              />\n\n              <div className=\"absolute inset-0 flex flex-col items-center justify-center text-center p-4 md:p-8\">\n                {(slide.headline ||\n                  slide.subText ||\n                  (slide.buttonText && slide.buttonLink)) && (\n                  <div className=\"p-4 md:p-8 rounded-lg max-w-3xl\">\n                    {slide.headline && (\n                      <h2 className=\"text-white text-2xl md:text-4xl lg:text-5xl font-bold mb-2 md:mb-4 drop-shadow-lg\">\n                        {slide.headline}\n                      </h2>\n                    )}\n\n                    {slide.subText && (\n                      <p className=\"text-white text-sm md:text-base lg:text-lg mb-4 md:mb-8 max-w-2xl mx-auto drop-shadow-md\">\n                        {slide.subText}\n                      </p>\n                    )}\n\n                    {slide.buttonText && slide.buttonLink && (\n                      <a\n                        href={slide.buttonLink}\n                        className=\"inline-block px-6 py-3 rounded-lg text-white font-medium transition-all hover:opacity-90 hover:scale-105\"\n                        style={{\n                          backgroundColor: slide.buttonColor || '#3B82F6'\n                        }}\n                      >\n                        {slide.buttonText}\n                      </a>\n                    )}\n                  </div>\n                )}\n              </div>\n            </div>\n          </div>\n        ))}\n      </SliderComponent>\n    </div>\n  );\n}\n\nexport const query = `\n  query Query($slides: [SlideInput], $autoplay: Boolean, $autoplaySpeed: Int, $arrows: Boolean, $dots: Boolean) {\n    slideshowWidget(\n      slides: $slides,\n      autoplay: $autoplay,\n      autoplaySpeed: $autoplaySpeed,\n      arrows: $arrows,\n      dots: $dots\n    ) {\n      slides {\n        id\n        image\n        width\n        height\n        headline\n        subText\n        buttonText\n        buttonLink\n        buttonColor\n      }\n      autoplay\n      autoplaySpeed\n      arrows\n      dots\n    }\n  }\n`;\n\nexport const fragments = `\n  fragment SlideData on Slide {\n    id\n    image\n    width\n    height\n    headline\n    subText\n    buttonText\n    buttonLink\n    buttonColor\n  }\n`;\n\nexport const variables = `{\n  slides: getWidgetSetting(\"slides\"),\n  autoplay: getWidgetSetting(\"autoplay\"),\n  autoplaySpeed: getWidgetSetting(\"autoplaySpeed\"),\n  arrows: getWidgetSetting(\"arrows\"),\n  dots: getWidgetSetting(\"dots\")\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/components/SlideshowSetting.tsx",
    "content": "/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */\nimport { FileBrowser } from '@components/admin/FileBrowser.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport { Input } from '@components/common/ui/Input.js';\nimport { Item, ItemContent, ItemTitle } from '@components/common/ui/Item.js';\nimport { Label } from '@components/common/ui/Label.js';\nimport React, { useEffect } from 'react';\nimport { useFieldArray, useFormContext } from 'react-hook-form';\nimport { v4 as uuidv4 } from 'uuid';\n\ninterface SlideData {\n  id: string;\n  image: string;\n  width?: number; // Image natural width (automatically detected)\n  height?: number; // Image natural height (automatically detected)\n  headline: string;\n  subText: string;\n  buttonText: string;\n  buttonLink: string;\n  buttonColor: string;\n}\n\ninterface SlideshowSettingProps {\n  slideshowWidget?: {\n    slides?: SlideData[];\n    autoplay?: boolean;\n    autoplaySpeed?: number;\n    arrows?: boolean;\n    dots?: boolean;\n    fullWidth?: boolean;\n    widthValue?: number;\n    heightValue?: number;\n    heightType?: 'auto' | 'fixed' | 'full';\n  };\n}\n\nexport default function SlideshowSetting({\n  slideshowWidget\n}: SlideshowSettingProps) {\n  const {\n    slides = [],\n    autoplay = true,\n    autoplaySpeed = 3000,\n    arrows = true,\n    dots = true,\n    fullWidth = true,\n    widthValue = 1920,\n    heightValue = 800,\n    heightType = 'auto'\n  } = slideshowWidget || {};\n\n  const { control, setValue, watch } = useFormContext();\n  const { fields, append, remove, move } = useFieldArray({\n    control,\n    name: 'settings.slides'\n  });\n\n  const currentSlides = watch('settings.slides', slides);\n  const currentAutoplay = watch('settings.autoplay', autoplay);\n  const currentAutoplaySpeed = watch('settings.autoplaySpeed', autoplaySpeed);\n  const currentArrows = watch('settings.arrows', arrows);\n  const currentDots = watch('settings.dots', dots);\n  const currentFullWidth = watch('settings.fullWidth', fullWidth);\n\n  useEffect(() => {\n    // Initialize slides with existing data\n    setValue('settings.slides', currentSlides?.length ? currentSlides : []);\n\n    // Initialize the autoplay settings\n    const handleAutoplay =\n      currentAutoplay === undefined || currentAutoplay === null\n        ? autoplay\n        : Boolean(currentAutoplay);\n    setValue('settings.autoplay', handleAutoplay);\n\n    // Initialize the autoplay speed\n    const speed = Number(currentAutoplaySpeed) || Number(autoplaySpeed) || 3000;\n    setValue('settings.autoplaySpeed', speed);\n\n    // Initialize the arrows setting\n    const handleArrows =\n      currentArrows === undefined || currentArrows === null\n        ? arrows\n        : Boolean(currentArrows);\n    setValue('settings.arrows', handleArrows);\n\n    // Initialize the dots setting\n    const handleDots =\n      currentDots === undefined || currentDots === null\n        ? dots\n        : Boolean(currentDots);\n    setValue('settings.dots', handleDots);\n\n    // Initialize the fullWidth setting\n    const handleFullWidth =\n      currentFullWidth === undefined || currentFullWidth === null\n        ? fullWidth\n        : Boolean(currentFullWidth);\n    setValue('settings.fullWidth', handleFullWidth);\n\n    // Always use adaptive height for the slideshow\n    setValue('settings.heightType', 'auto');\n\n    // Process all slides to detect image dimensions if they don't have them yet\n    if (currentSlides?.length) {\n      currentSlides.forEach((slide, index) => {\n        if (slide.image && (!slide.width || !slide.height)) {\n          getImageDimensions(slide.image, index);\n        }\n      });\n    }\n  }, []);\n\n  const [activeSlideIndex, setActiveSlideIndex] = React.useState<number | null>(\n    null\n  );\n  const [openFileBrowser, setOpenFileBrowser] = React.useState(false);\n\n  // Function to get image dimensions\n  const getImageDimensions = (imageUrl: string, slideIndex: number) => {\n    if (!imageUrl) return;\n\n    const img = new Image();\n    img.onload = () => {\n      const width = img.naturalWidth;\n      const height = img.naturalHeight;\n\n      // Update the current slides with the new dimensions\n      const newSlides = [...currentSlides];\n      newSlides[slideIndex] = {\n        ...newSlides[slideIndex],\n        width,\n        height\n      };\n      setValue('settings.slides', newSlides);\n    };\n    img.src = imageUrl;\n  };\n\n  const handleImageSelect = (image: string) => {\n    if (activeSlideIndex !== null) {\n      setValue(`settings.slides.${activeSlideIndex}.image`, image);\n\n      // Detect image dimensions when a new image is selected\n      getImageDimensions(image, activeSlideIndex);\n      setOpenFileBrowser(false);\n    }\n  };\n\n  const addSlide = () => {\n    const newSlide: SlideData = {\n      id: uuidv4(),\n      image: '',\n      width: 0, // Will be automatically set when image is selected\n      height: 0, // Will be automatically set when image is selected\n      headline: '',\n      subText: '',\n      buttonText: '',\n      buttonLink: '',\n      buttonColor: '#3B82F6' // Default blue color\n    };\n    append(newSlide);\n\n    setTimeout(() => {\n      setActiveSlideIndex(fields.length);\n    }, 50);\n  };\n\n  const moveUp = (index: number) => {\n    if (index > 0) {\n      move(index, index - 1);\n      setActiveSlideIndex(index - 1);\n    }\n  };\n\n  const moveDown = (index: number) => {\n    if (index < fields.length - 1) {\n      move(index, index + 1);\n      setActiveSlideIndex(index + 1);\n    }\n  };\n\n  return (\n    <div className=\"slideshow-widget\">\n      {openFileBrowser && (\n        <div className=\"max-h-96\">\n          <FileBrowser\n            isMultiple={false}\n            onInsert={handleImageSelect}\n            close={() => setOpenFileBrowser(false)}\n          />\n        </div>\n      )}\n\n      <Item variant={'outline'}>\n        <ItemContent>\n          <ItemTitle>Slideshow Settings</ItemTitle>\n          <div className=\"space-y-2 mt-3\">\n            <div className=\"col-span-2 md:col-span-1 space-y-2\">\n              <div className=\"flex items-center mb-4\">\n                <Checkbox\n                  id=\"arrows\"\n                  checked={Boolean(currentArrows)}\n                  onCheckedChange={(checked) => {\n                    setValue('settings.arrows', checked);\n                  }}\n                  className=\"mr-2 h-4 w-4\"\n                />\n                <Label htmlFor=\"arrows\">Show Navigation Arrows</Label>\n              </div>\n              <div className=\"flex justify-start items-center\">\n                <Checkbox\n                  id=\"autoplay\"\n                  checked={Boolean(currentAutoplay)}\n                  onCheckedChange={(checked) => {\n                    setValue('settings.autoplay', checked);\n                  }}\n                  className=\"mr-2 h-4 w-4\"\n                />\n                <Label htmlFor=\"autoplay\" className=\"text-sm\">\n                  Enable Autoplay\n                </Label>\n              </div>\n\n              {Boolean(currentAutoplay) && (\n                <InputField\n                  type=\"number\"\n                  label=\"Autoplay Speed (ms)\"\n                  name=\"settings.autoplaySpeed\"\n                  defaultValue={Number(autoplaySpeed) || 3000}\n                  placeholder=\"e.g., 3000 for 3 seconds\"\n                  validation={{\n                    min: { value: 1000, message: 'Minimum speed is 1000ms' }\n                  }}\n                />\n              )}\n            </div>\n\n            <div className=\"col-span-2 md:col-span-1\">\n              {/* <div className=\"flex items-center\">\n              <input\n                type=\"checkbox\"\n                id=\"dots\"\n                checked={Boolean(currentDots)}\n                onChange={(e) => {\n                  const isChecked = Boolean(e.target.checked);\n                  setValue('settings.dots', isChecked);\n                }}\n                className=\"mr-2 h-4 w-4\"\n              />\n              <label htmlFor=\"dots\" className=\"text-sm\">\n                Show Navigation Dots\n              </label>\n            </div> */}\n            </div>\n          </div>\n        </ItemContent>\n      </Item>\n      <div className=\"mt-4\">\n        <div className=\"flex justify-between items-center mb-2\">\n          <h2 className=\"text-lg font-medium\">Slides</h2>\n          <Button onClick={addSlide} variant={'outline'}>\n            Add New Slide\n          </Button>\n        </div>\n\n        {fields.length > 0 ? (\n          <div className=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 mb-4\">\n            {fields.map((slide, index) => (\n              <div\n                key={slide.id}\n                onClick={() => setActiveSlideIndex(index)}\n                className={`relative border border-border rounded overflow-hidden cursor-pointer ${\n                  activeSlideIndex === index ? 'ring-2 ring-blue-500' : ''\n                }`}\n              >\n                <div className=\"aspect-[16/9] bg-gray-100 flex items-center justify-center\">\n                  {currentSlides[index]?.image ? (\n                    <img\n                      src={currentSlides[index].image}\n                      alt={`Slide ${index + 1}`}\n                      className=\"w-full h-full object-cover\"\n                    />\n                  ) : (\n                    <div className=\"text-gray-400\">No Image</div>\n                  )}\n                </div>\n                <div className=\"p-2 bg-white border-t border-border\">\n                  <p className=\"text-sm font-medium truncate\">\n                    {currentSlides[index]?.headline || `Slide ${index + 1}`}\n                  </p>\n                  <div className=\"flex mt-2\">\n                    <Button\n                      variant={'outline'}\n                      size={'sm'}\n                      onClick={(e) => {\n                        e.stopPropagation();\n                        moveUp(index);\n                      }}\n                      disabled={index === 0}\n                      className={`mr-1 p-1`}\n                    >\n                      <svg\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                        width=\"16\"\n                        height=\"16\"\n                        viewBox=\"0 0 24 24\"\n                        fill=\"none\"\n                        stroke=\"currentColor\"\n                        strokeWidth=\"2\"\n                        strokeLinecap=\"round\"\n                        strokeLinejoin=\"round\"\n                      >\n                        <path d=\"M18 15l-6-6-6 6\" />\n                      </svg>\n                    </Button>\n                    <Button\n                      type=\"button\"\n                      size={'sm'}\n                      onClick={(e) => {\n                        e.stopPropagation();\n                        moveDown(index);\n                      }}\n                      disabled={index === fields.length - 1}\n                      className={`mr-1 p-1`}\n                    >\n                      <svg\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                        width=\"16\"\n                        height=\"16\"\n                        viewBox=\"0 0 24 24\"\n                        fill=\"none\"\n                        stroke=\"currentColor\"\n                        strokeWidth=\"2\"\n                        strokeLinecap=\"round\"\n                        strokeLinejoin=\"round\"\n                      >\n                        <path d=\"M6 9l6 6 6-6\" />\n                      </svg>\n                    </Button>\n                    <Button\n                      variant=\"destructive\"\n                      size={'sm'}\n                      onClick={(e) => {\n                        e.stopPropagation();\n                        remove(index);\n                        if (activeSlideIndex === index) {\n                          setActiveSlideIndex(null);\n                        } else if (\n                          activeSlideIndex !== null &&\n                          activeSlideIndex > index\n                        ) {\n                          setActiveSlideIndex(activeSlideIndex - 1);\n                        }\n                      }}\n                    >\n                      <svg\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                        width=\"16\"\n                        height=\"16\"\n                        viewBox=\"0 0 24 24\"\n                        fill=\"none\"\n                        stroke=\"currentColor\"\n                        strokeWidth=\"2\"\n                        strokeLinecap=\"round\"\n                        strokeLinejoin=\"round\"\n                      >\n                        <path d=\"M3 6h18\"></path>\n                        <path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"></path>\n                        <path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"></path>\n                      </svg>\n                    </Button>\n                  </div>\n                </div>\n              </div>\n            ))}\n          </div>\n        ) : (\n          <div className=\"bg-gray-50 border border-dashed border-gray-300 rounded-lg p-8 text-center mb-4\">\n            <p className=\"text-gray-500 mb-4\">No slides have been added yet.</p>\n            <Button variant=\"outline\" onClick={addSlide}>\n              Add Your First Slide\n            </Button>\n          </div>\n        )}\n      </div>\n\n      {activeSlideIndex !== null && fields[activeSlideIndex] && (\n        <div className=\"bg-white p-4 rounded border border-border\">\n          <h3 className=\"text-sm font-normal mb-4\">\n            Edit Slide {activeSlideIndex + 1}\n          </h3>\n          <div className=\"mb-2 border border-border rounded overflow-hidden\">\n            <div className=\"aspect-[16/9] bg-gray-100 relative\">\n              {currentSlides[activeSlideIndex]?.image ? (\n                <div className=\"relative w-full h-full\">\n                  <img\n                    src={currentSlides[activeSlideIndex].image}\n                    alt={`Slide ${activeSlideIndex + 1}`}\n                    className=\"w-full h-full object-cover\"\n                    onLoad={(e) => {\n                      // Additional dimensions detection when the preview image loads\n                      const img = e.target as HTMLImageElement;\n                      if (img.naturalWidth > 0 && img.naturalHeight > 0) {\n                        if (\n                          !currentSlides[activeSlideIndex]?.width ||\n                          !currentSlides[activeSlideIndex]?.height\n                        ) {\n                          const newSlides = [...currentSlides];\n                          newSlides[activeSlideIndex] = {\n                            ...newSlides[activeSlideIndex],\n                            width: img.naturalWidth,\n                            height: img.naturalHeight\n                          };\n                          setValue('settings.slides', newSlides);\n                        }\n                      }\n                    }}\n                  />\n                  <div className=\"absolute inset-0 flex flex-col items-center justify-center p-4 text-center\">\n                    {currentSlides[activeSlideIndex]?.headline && (\n                      <h3 className=\"text-white text-xl md:text-2xl font-bold mb-2\">\n                        {currentSlides[activeSlideIndex].headline}\n                      </h3>\n                    )}\n                    {currentSlides[activeSlideIndex]?.subText && (\n                      <p className=\"text-white mb-4\">\n                        {currentSlides[activeSlideIndex].subText}\n                      </p>\n                    )}\n                    {currentSlides[activeSlideIndex]?.buttonText && (\n                      <button\n                        type=\"button\"\n                        className=\"px-4 py-2 rounded\"\n                        style={{\n                          backgroundColor:\n                            currentSlides[activeSlideIndex].buttonColor ||\n                            '#3B82F6'\n                        }}\n                      >\n                        {currentSlides[activeSlideIndex].buttonText}\n                      </button>\n                    )}\n                  </div>\n                </div>\n              ) : (\n                <div className=\"w-full h-full flex items-center justify-center\">\n                  <Button\n                    variant=\"outline\"\n                    onClick={() => setOpenFileBrowser(true)}\n                  >\n                    Select Image\n                  </Button>\n                </div>\n              )}\n\n              {currentSlides[activeSlideIndex]?.image && (\n                <Button\n                  variant=\"outline\"\n                  onClick={() => setOpenFileBrowser(true)}\n                  className=\"absolute bottom-2 right-2\"\n                >\n                  Change Image\n                </Button>\n              )}\n            </div>\n          </div>\n          <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n            <input\n              type=\"hidden\"\n              name={`settings.slides.${activeSlideIndex}.image`}\n              value={\n                (currentSlides && currentSlides[activeSlideIndex]?.image) || ''\n              }\n            />\n\n            <input\n              type=\"hidden\"\n              name={`settings.slides.${activeSlideIndex}.id`}\n              value={\n                (currentSlides && currentSlides[activeSlideIndex]?.id) ||\n                uuidv4()\n              }\n            />\n\n            {/* Hidden fields for image dimensions */}\n            <input\n              type=\"hidden\"\n              name={`settings.slides.${activeSlideIndex}.width`}\n              value={currentSlides[activeSlideIndex]?.width || 0}\n            />\n\n            <input\n              type=\"hidden\"\n              name={`settings.slides.${activeSlideIndex}.height`}\n              value={currentSlides[activeSlideIndex]?.height || 0}\n            />\n\n            {/* Display image dimensions if available */}\n            {currentSlides[activeSlideIndex]?.image && (\n              <div className=\"md:col-span-2 mb-2\">\n                <div className=\"text-sm text-gray-500\">\n                  {currentSlides[activeSlideIndex]?.width &&\n                  currentSlides[activeSlideIndex]?.height ? (\n                    <p>\n                      Image dimensions: {currentSlides[activeSlideIndex].width}{' '}\n                      × {currentSlides[activeSlideIndex].height} pixels\n                    </p>\n                  ) : (\n                    <p>Detecting image dimensions...</p>\n                  )}\n                </div>\n              </div>\n            )}\n\n            <div className=\"md:col-span-2\">\n              <label className=\"block mb-1 text-sm\">Headline</label>\n              <input\n                type=\"text\"\n                className=\"w-full p-2 border border-gray-300 rounded\"\n                name={`settings.slides.${activeSlideIndex}.headline`}\n                value={currentSlides[activeSlideIndex]?.headline || ''}\n                onChange={(e) => {\n                  const newSlides = [...currentSlides];\n                  newSlides[activeSlideIndex] = {\n                    ...newSlides[activeSlideIndex],\n                    headline: e.target.value\n                  };\n                  setValue('settings.slides', newSlides);\n                }}\n                placeholder=\"e.g., New Collection Available\"\n              />\n            </div>\n\n            <div className=\"md:col-span-2\">\n              <label className=\"block mb-1 text-sm\">Sub Text</label>\n              <textarea\n                className=\"w-full p-2 border border-gray-300 rounded\"\n                name={`settings.slides.${activeSlideIndex}.subText`}\n                value={currentSlides[activeSlideIndex]?.subText || ''}\n                onChange={(e) => {\n                  const newSlides = [...currentSlides];\n                  newSlides[activeSlideIndex] = {\n                    ...newSlides[activeSlideIndex],\n                    subText: e.target.value\n                  };\n                  setValue('settings.slides', newSlides);\n                }}\n                placeholder=\"e.g., Check out our latest products with special discounts\"\n                rows={3}\n              ></textarea>\n            </div>\n\n            <div>\n              <label className=\"block mb-1 text-sm\">Button Text</label>\n              <input\n                type=\"text\"\n                className=\"w-full p-2 border border-gray-300 rounded\"\n                name={`settings.slides.${activeSlideIndex}.buttonText`}\n                value={currentSlides[activeSlideIndex]?.buttonText || ''}\n                onChange={(e) => {\n                  const newSlides = [...currentSlides];\n                  newSlides[activeSlideIndex] = {\n                    ...newSlides[activeSlideIndex],\n                    buttonText: e.target.value\n                  };\n                  setValue('settings.slides', newSlides);\n                }}\n                placeholder=\"e.g., Shop Now\"\n              />\n            </div>\n\n            <div>\n              <label className=\"block mb-1 text-sm\">Button Link</label>\n              <input\n                type=\"text\"\n                className=\"w-full p-2 border border-gray-300 rounded\"\n                name={`settings.slides.${activeSlideIndex}.buttonLink`}\n                value={currentSlides[activeSlideIndex]?.buttonLink || ''}\n                onChange={(e) => {\n                  const newSlides = [...currentSlides];\n                  newSlides[activeSlideIndex] = {\n                    ...newSlides[activeSlideIndex],\n                    buttonLink: e.target.value\n                  };\n                  setValue('settings.slides', newSlides);\n                }}\n                placeholder=\"e.g., /category/new-arrivals\"\n              />\n            </div>\n\n            <div>\n              <label className=\"block mb-1 text-sm\">Button Color</label>\n              <div className=\"flex items-center\">\n                <input\n                  type=\"color\"\n                  value={\n                    currentSlides[activeSlideIndex]?.buttonColor || '#3B82F6'\n                  }\n                  onChange={(e) => {\n                    const newSlides = [...currentSlides];\n                    newSlides[activeSlideIndex] = {\n                      ...newSlides[activeSlideIndex],\n                      buttonColor: e.target.value\n                    };\n                    setValue('settings.slides', newSlides);\n                  }}\n                  className=\"w-10 h-10 rounded border-border mr-2 cursor-pointer\"\n                />\n                <Input\n                  type=\"text\"\n                  value={\n                    currentSlides[activeSlideIndex]?.buttonColor || '#3B82F6'\n                  }\n                  onChange={(e) => {\n                    const newSlides = [...currentSlides];\n                    newSlides[activeSlideIndex] = {\n                      ...newSlides[activeSlideIndex],\n                      buttonColor: e.target.value\n                    };\n                    setValue('settings.slides', newSlides);\n                  }}\n                  placeholder=\"#3B82F6\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport const query = `\n  query Query($slides: [SlideInput], $autoplay: Boolean, $autoplaySpeed: Int, $arrows: Boolean, $dots: Boolean) {\n    slideshowWidget(\n      slides: $slides, \n      autoplay: $autoplay, \n      autoplaySpeed: $autoplaySpeed, \n      arrows: $arrows, \n      dots: $dots,\n    ) {\n      slides {\n        id\n        image\n        width\n        height\n        headline\n        subText\n        buttonText\n        buttonLink\n        buttonColor\n      }\n      autoplay\n      autoplaySpeed\n      arrows\n      dots\n    }\n  }\n`;\n\nexport const fragments = `\n  fragment SlideData on Slide {\n    id\n    image\n    width\n    height\n    headline\n    subText\n    buttonText\n    buttonLink\n    buttonColor\n  }\n`;\n\nexport const variables = `{\n  slides: getWidgetSetting(\"slides\"),\n  autoplay: getWidgetSetting(\"autoplay\"),\n  autoplaySpeed: getWidgetSetting(\"autoplaySpeed\"),\n  arrows: getWidgetSetting(\"arrows\"),\n  dots: getWidgetSetting(\"dots\"),\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/components/TextBlock.tsx",
    "content": "import { Editor } from '@components/common/Editor.js';\nimport { Row } from '@components/common/form/Editor.js';\nimport React from 'react';\n\ninterface TextBlockProps {\n  textWidget: {\n    text: Row[];\n    className: string;\n  };\n}\nexport default function TextBlock({\n  textWidget: { text, className }\n}: TextBlockProps) {\n  return (\n    <div className={`text-block-widget ${className}`}>\n      <Editor rows={text} />\n    </div>\n  );\n}\n\nexport const query = `\n  query Query($text: String, $className: String) {\n    textWidget(text: $text, className: $className) {\n      ...TextBlockWidget\n    }\n  }\n`;\n\nexport const fragments = `\n  fragment TextBlockWidget on TextBlockWidget {\n    text\n    className\n  }\n`;\n\nexport const variables = `{\n  text: getWidgetSetting(\"text\"),\n  className: getWidgetSetting(\"className\")\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/components/TextBlockSetting.tsx",
    "content": "import { Editor, Row } from '@components/common/form/Editor.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport React from 'react';\nimport { useFormContext } from 'react-hook-form';\n\ninterface TextBlockSettingProps {\n  textWidget: {\n    text: Row[];\n    className: string;\n  };\n}\nexport default function TextBlockSetting({\n  textWidget: { text, className }\n}: TextBlockSettingProps) {\n  const { register, watch, setValue } = useFormContext();\n\n  const editorValue = watch('temp_editor_text');\n\n  React.useEffect(() => {\n    if (editorValue) {\n      setValue('settings.text', JSON.stringify(editorValue));\n    }\n  }, [editorValue, setValue]);\n\n  return (\n    <div className=\"space-y-3\">\n      <InputField\n        label=\"Custom CSS classes\"\n        name=\"settings.className\"\n        defaultValue={className}\n        helperText=\"Custom CSS classes for the text block\"\n      />\n      <input\n        type=\"hidden\"\n        {...register('settings.text')}\n        defaultValue={typeof text === 'string' ? text : JSON.stringify(text)}\n      />\n      <Editor\n        name=\"temp_editor_text\"\n        label=\"Content\"\n        value={typeof text === 'string' ? JSON.parse(text) : text}\n      />\n    </div>\n  );\n}\n\nexport const query = `\n  query Query($text: String, $className: String) {\n    textWidget(text: $text, className: $className) {\n      text\n      className\n    }\n  }\n`;\n\nexport const variables = `{\n  text: getWidgetSetting(\"text\"),\n  className: getWidgetSetting(\"className\")\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/graphql/types/CmsPage/CmsPage.graphql",
    "content": "\"\"\"\nLookup CMS page by ID\n\"\"\"\ntype CmsPage {\n  cmsPageId: Int\n  uuid: String!\n  status: Int!\n  urlKey: String!\n  name: String!\n  content: JSON!\n  metaTitle: String\n  metaKeywords: String\n  metaDescription: String\n  url: String!\n  editUrl: String!\n  updateApi: String!\n  deleteApi: String!\n}\n\n\"\"\"\nReturn a collection of CMS pages\n\"\"\"\ntype CmsPageCollection {\n  items: [CmsPage]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\nextend type Query {\n  cmsPage(id: Int): CmsPage\n  currentCmsPage: CmsPage\n  cmsPages(filters: [FilterInput]): CmsPageCollection\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/graphql/types/CmsPage/CmsPage.resolvers.ts",
    "content": "import { v4 as uuidv4 } from 'uuid';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { CMSPageCollection } from '../../../services/CMSPageCollection.js';\nimport { getCmsPagesBaseQuery } from '../../../services/getCmsPagesBaseQuery.js';\n\nexport default {\n  Query: {\n    cmsPage: async (root, { id }, { pool }) => {\n      const query = getCmsPagesBaseQuery();\n      query.where('cms_page_id', '=', id);\n      const page = await query.load(pool);\n      return page ? camelCase(page) : null;\n    },\n    cmsPages: async (_, { filters = [] }, { user }) => {\n      const query = getCmsPagesBaseQuery();\n      const root = new CMSPageCollection(query);\n      await root.init(filters, !!user);\n      return root;\n    },\n    currentCmsPage: async (_, args, { currentRoute, pool }) => {\n      if (currentRoute?.id !== 'cmsPageView') {\n        return null;\n      }\n      const { params } = currentRoute;\n      if (!params || !params.url_key) {\n        return null;\n      }\n      const query = getCmsPagesBaseQuery();\n      query.where('cms_page_description.url_key', '=', params.url_key);\n      const page = await query.load(pool);\n      return page ? camelCase(page) : null;\n    }\n  },\n  CmsPage: {\n    url: ({ urlKey }) => buildUrl('cmsPageView', { url_key: urlKey }),\n    editUrl: ({ uuid }) => buildUrl('cmsPageEdit', { id: uuid }),\n    updateApi: (page) => buildUrl('updateCmsPage', { id: page.uuid }),\n    deleteApi: (page) => buildUrl('deleteCmsPage', { id: page.uuid }),\n    content: ({ content }) => {\n      try {\n        const json = JSON.parse(content);\n        return json;\n      } catch (e) {\n        // This is for backward compatibility. If the content is not a JSON string then it is a raw HTML block\n        const rowId = `r__${uuidv4()}`;\n        return [\n          {\n            size: 1,\n            id: rowId,\n            columns: [\n              {\n                id: 'c__c5d90067-c786-4324-8e24-8e30520ac3d7',\n                size: 1,\n                data: {\n                  time: 1723347125344,\n                  blocks: [\n                    {\n                      id: 'AU89ItzUa7',\n                      type: 'raw',\n                      data: {\n                        html: content\n                      }\n                    }\n                  ],\n                  version: '2.30.2'\n                }\n              }\n            ]\n          }\n        ];\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/graphql/types/Menu/Menu.graphql",
    "content": "\"\"\"\nRepresents a menu item\n\"\"\"\ntype MenuItem {\n  name: String!\n  url: String!\n  children: [MenuItem]\n}\n\n\"\"\"\nRepresents a menu\n\"\"\"\ntype Menu {\n  items: [MenuItem]\n}\n\nextend type Query {\n  menu: Menu\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/graphql/types/Menu/Menu.resolvers.js",
    "content": "import { select, value } from '@evershop/postgres-query-builder';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\n\nexport default {\n  Query: {\n    menu: async (root, _, { pool }) => {\n      const query = select('name')\n        .select('uuid')\n        .select('request_path')\n        .select('url_key')\n        .from('category', 'cat');\n      query\n        .leftJoin('category_description', 'des')\n        .on('cat.category_id', '=', 'des.category_description_category_id');\n      query\n        .leftJoin('url_rewrite', 'url')\n        .on('url.entity_uuid', '=', 'cat.uuid')\n        .and('url.entity_type', '=', value('category'));\n      query\n        .where('cat.status', '=', 1)\n        .and('cat.include_in_nav', '=', 1)\n        .and('des.url_key', 'IS NOT NULL', null)\n        .and('des.url_key', '!=', '');\n\n      const items = (await query.execute(pool)).map((i) => ({\n        name: i.name,\n        url: i.request_path || buildUrl('categoryView', { uuid: i.uuid })\n      }));\n\n      return { items };\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/graphql/types/PageInfo/PageInfo.graphql",
    "content": "\"\"\"\nRepresents a breadcrumb information.\n\"\"\"\ntype Breadcrumb {\n  url: String!\n  title: String!\n}\n\ntype OgInfo {\n  title: String\n  description: String\n  image: String\n  url: String\n  siteName: String\n  type: String\n  twitterImage: String\n  twitterCard: String\n  twitterSite: String\n  twitterCreator: String\n}\n\n\"\"\"\nRepresents a page information.\n\"\"\"\ntype PageInfo {\n  url: String!\n  title: String!\n  description: String!\n  locale: String\n  keywords: [String!]\n  ogInfo: OgInfo\n  breadcrumbs: [Breadcrumb!]\n  favicon: String\n  canonicalUrl: String\n}\n\nextend type Query {\n  pageInfo: PageInfo\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/graphql/types/PageInfo/PageInfo.resolvers.ts",
    "content": "import { access } from 'fs/promises';\nimport path from 'path';\nimport { select } from '@evershop/postgres-query-builder';\nimport { CONSTANTS } from '../../../../../lib/helpers.js';\nimport { translate } from '../../../../../lib/locale/translate/translate.js';\nimport { get } from '../../../../../lib/util/get.js';\nimport { getBaseUrl } from '../../../../../lib/util/getBaseUrl.js';\nimport { getConfig } from '../../../../../lib/util/getConfig.js';\nimport { getValueSync } from '../../../../../lib/util/registry.js';\nimport { OgInfo } from '../../../../../types/pageMeta.js';\nimport { getSetting } from '../../../../setting/services/setting.js';\n\nexport default {\n  Query: {\n    pageInfo: async (root, args, context) => ({\n      url: get(context, 'currentUrl'),\n      title: get(\n        context,\n        'pageInfo.title',\n        await getSetting('storeName', 'Evershop')\n      ),\n      description: get(context, 'pageInfo.description', ''),\n      keywords: get(context, 'pageInfo.keywords', []),\n      canonicalUrl: get(\n        context,\n        'pageInfo.canonicalUrl',\n        get(context, 'currentUrl')\n      ),\n      favicon: async () => {\n        // Check if a file named favicon.ico exists in the public folder\n        try {\n          await access(path.resolve(CONSTANTS.PUBLICPATH, 'favicon.ico'));\n          return getBaseUrl() + '/assets/favicon.ico';\n        } catch (error) {\n          return null;\n        }\n      }\n    })\n  },\n  PageInfo: {\n    breadcrumbs: async (root, args, context) => {\n      // Check if the current page is home page\n      if (context.originalUrl === '/') {\n        return [];\n      }\n      // Get the current path\n      const path = context.originalUrl\n        .split('?')[0]\n        .replace(/^\\/|\\/$/g, '')\n        .replace(/\\./g, '');\n\n      // Check if the path is existed in the url_rewrite table\n      const rewriteRule = await select()\n        .from('url_rewrite')\n        .where('request_path', '=', `/${path}`)\n        .load(context.pool);\n      if (!rewriteRule) {\n        return [\n          {\n            title: translate('Home'),\n            url: '/'\n          },\n          {\n            title: get(context, 'pageInfo.title', ''),\n            url: get(context, 'currentUrl')\n          }\n        ];\n      } else {\n        // Split the target path and remove the last element\n        const paths = rewriteRule.request_path.split('/');\n        paths.pop();\n        // Each element is represented for a category (url_key)\n        // Build the breadrumbs\n        const breadcrumbs = [\n          {\n            title: translate('Home'),\n            url: '/'\n          }\n        ];\n        for (let i = 0; i < paths.length; i += 1) {\n          if (paths[i] === '') {\n            continue;\n          }\n          const urlKey = paths[i];\n          const categoryQuery = select().from('category');\n          categoryQuery\n            .leftJoin('category_description')\n            .on(\n              'category_description.category_description_category_id',\n              '=',\n              'category.category_id'\n            );\n          categoryQuery.where('category_description.url_key', '=', urlKey);\n          const category = await categoryQuery.load(context.pool);\n          if (category) {\n            breadcrumbs.push({\n              title: category.name,\n              url: `${paths.slice(0, i + 1).join('/')}`\n            });\n          } else {\n            continue;\n          }\n        }\n\n        breadcrumbs.push({\n          title: get(context, 'pageInfo.title', ''),\n          url: get(context, 'currentUrl')\n        });\n\n        return breadcrumbs;\n      }\n    },\n    ogInfo: async (root, args, context): Promise<OgInfo> => {\n      let logo = getConfig('themeConfig.logo.src');\n      const baseUrl = getBaseUrl();\n      // Check if logo is a full URL\n      // If logo is not set, use default /images/logo.png\n      if (logo && !logo.startsWith('http')) {\n        // If logo is a relative path, convert to absolute URL\n        logo = `${baseUrl}${logo}`;\n      }\n      const image = get(\n        context,\n        'pageInfo.ogInfo.image',\n        logo ? `${baseUrl}/images?src=${logo}&w=1200&q=80&h=675&f=png` : ''\n      );\n\n      return getValueSync<OgInfo>(\n        'ogInfo',\n        {\n          title: get(context, 'pageInfo.ogTitle', root.title),\n          description: get(\n            context,\n            'pageInfo.ogInfo.description',\n            root.description\n          ),\n          image: get(\n            context,\n            'pageInfo.ogInfo.image',\n            image ? image : root.image\n          ),\n          url: get(context, 'pageInfo.ogInfo.url', root.url),\n          siteName: get(context, 'pageInfo.ogInfo.siteName', root.siteName),\n          type: get(context, 'pageInfo.ogInfo.type', 'website'),\n          locale: get(context, 'pageInfo.ogInfo.locale', root.locale),\n          twitterCard: get(context, 'pageInfo.ogInfo.twitterCard', 'summary'),\n          twitterSite: get(\n            context,\n            'pageInfo.ogInfo.twitterSite',\n            await getSetting('storeName', 'Evershop')\n          ),\n          twitterCreator: get(\n            context,\n            'pageInfo.ogInfo.twitterCreator',\n            await getSetting('storeName', 'Evershop')\n          ),\n          twitterImage: get(context, 'pageInfo.ogInfo.twitterImage', image)\n        },\n        context\n      );\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/graphql/types/ThemeConfig/ThemeConfig.graphql",
    "content": "\"\"\"\nRepresents a link html tag.\n\"\"\"\ntype Link {\n  href: String!\n  text: String!\n  title: String\n  rel: String\n  target: String\n  type: String\n  media: String\n  hrefLang: String\n  sizes: String\n  as: String\n  crossOrigin: String\n  referrerPolicy: String\n  integrity: String\n}\n\n\"\"\"\nRepresents a meta html tag.\n\"\"\"\ntype Meta {\n  name: String\n  content: String\n  charSet: String\n  property: String\n  itemProp: String\n  itemType: String\n  itemID: String\n  httpEquiv: String\n  lang: String\n}\n\n\"\"\"\nRepresents a script html tag.\n\"\"\"\ntype Script {\n  src: String\n  type: String\n  async: Boolean\n  defer: Boolean\n  crossOrigin: String\n  integrity: String\n  noModule: String\n  nonce: String\n}\n\n\"\"\"\nRepresents a base html tag.\n\"\"\"\ntype Base {\n  href: String\n  target: String\n}\n\n\"\"\"\nRepresents a logo.\n\"\"\"\ntype Logo {\n  src: String\n  alt: String\n  width: String\n  height: String\n}\n\n\"\"\"\nRepresents a nav head tag.\n\"\"\"\ntype HeadTag {\n  links: [Link]\n  metas: [Meta]\n  scripts: [Script]\n  base: Base\n}\n\n\"\"\"\nRepresents a base theme config.\n\"\"\"\ntype ThemeConfig {\n  logo: Logo\n  headTags: HeadTag\n  copyRight: String\n}\n\nextend type Query {\n  themeConfig: ThemeConfig\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/graphql/types/ThemeConfig/ThemeConfig.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Query: {\n    themeConfig: () => getConfig('themeConfig')\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/graphql/types/Widget/Widget.graphql",
    "content": "\"\"\"\nLookup Widget by ID\n\"\"\"\ntype Widget {\n  widgetId: Int\n  uuid: String!\n  name: String!\n  type: String!\n  area: [String!]\n  route: [String!]\n  sortOrder: Int!\n  status: Int!\n  settings: JSON!\n  editUrl: String!\n  updateApi: String!\n  deleteApi: String!\n}\n\n\"\"\"\nWidget type\n\"\"\"\ntype WidgetType {\n  code: String!\n  name: String!\n  description: String!\n  createWidgetUrl: String!\n  settingComponent: String!\n  component: String!\n  defaultSetting: JSON\n}\n\n\"\"\"\nReturn a collection of Widget\n\"\"\"\ntype WidgetCollection {\n  items: [Widget]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\n\"\"\"\nReturn a text Widget\n\"\"\"\ntype TextBlockWidget {\n  text: JSON\n  className: String\n}\n\n\"\"\"\nReturn a menu input\n\"\"\"\ninput BasicMenuInput {\n  id: ID\n  name: String!\n  label: String\n  url: String\n  children: [BasicMenuInput]\n  type: String!\n  uuid: String\n}\n\n\"\"\"\nReturn a menu item\n\"\"\"\ntype Menu {\n  id: ID!\n  name: String!\n  url: String\n  children: [Menu]\n  type: String!\n  uuid: String\n}\n\n\"\"\"\nReturn a menu Widget\n\"\"\"\ntype BasicMenuWidget {\n  menus: [Menu]\n  isMain: Boolean\n  className: String\n}\n\n\"\"\"\nReturn a banner Widget\n\"\"\"\ntype BasicBannerWidget {\n  src: String\n  alignment: String\n  width: Float\n  height: Float\n  alt: String\n}\n\n\"\"\"\nInput for a slide in a slideshow\n\"\"\"\ninput SlideInput {\n  id: String!\n  image: String!\n  headline: String\n  subText: String\n  buttonText: String\n  buttonLink: String\n  buttonColor: String\n  width: Float\n  height: Float\n}\n\n\"\"\"\nA slide in a slideshow\n\"\"\"\ntype Slide {\n  id: String!\n  image: String!\n  headline: String\n  subText: String\n  buttonText: String\n  buttonLink: String\n  buttonColor: String\n  width: Float\n  height: Float\n}\n\n\"\"\"\nReturn a slideshow Widget\n\"\"\"\ntype SlideshowWidget {\n  slides: [Slide!]\n  autoplay: Boolean\n  autoplaySpeed: Int\n  arrows: Boolean\n  dots: Boolean\n}\n\nextend type Query {\n  widget(id: Int): Widget\n  widgets(filters: [FilterInput]): WidgetCollection\n  widgetTypes: [WidgetType]\n  widgetType(code: String!): WidgetType\n  textWidget(text: String, className: String): TextBlockWidget\n  basicMenuWidget(settings: JSON): BasicMenuWidget\n  bannerWidget(\n    src: String\n    alignment: String\n    width: String\n    height: String\n    alt: String\n  ): BasicBannerWidget\n  slideshowWidget(\n    slides: [SlideInput]\n    autoplay: Boolean\n    autoplaySpeed: Int\n    arrows: Boolean\n    dots: Boolean\n  ): SlideshowWidget\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/graphql/types/Widget/Widget.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport sanitizeHtml from 'sanitize-html';\nimport uniqid from 'uniqid';\nimport { error } from '../../../../../lib/log/logger.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { getEnabledWidgets } from '../../../../../lib/widget/widgetManager.js';\nimport { getCmsPagesBaseQuery } from '../../../services/getCmsPagesBaseQuery.js';\nimport { getWidgetsBaseQuery } from '../../../services/getWidgetsBaseQuery.js';\nimport { WidgetCollection } from '../../../services/WidgetCollection.js';\n\nexport default {\n  Query: {\n    widget: async (root, { id }, { pool }) => {\n      const query = getWidgetsBaseQuery();\n      query.where('widget_id', '=', id);\n      const widget = await query.load(pool);\n      return widget ? camelCase(widget) : null;\n    },\n    widgets: async (_, { filters = [] }, { user }) => {\n      const query = getWidgetsBaseQuery();\n      const root = new WidgetCollection(query);\n      await root.init(filters, !!user);\n      return root;\n    },\n    widgetTypes: () => {\n      const types = getEnabledWidgets();\n      return types.map((row) => ({\n        code: row.type,\n        name: row.name,\n        description: row.description,\n        settingComponent: row.settingComponent,\n        component: row.component,\n        defaultSettings: row.defaultSettings,\n        createWidgetUrl: buildUrl('widgetNew', { type: row.type })\n      }));\n    },\n    widgetType: (_, { code }) => {\n      const types = getEnabledWidgets();\n      const type = types.find((row) => row.type === code);\n      return type\n        ? {\n            code: type.type,\n            name: type.name,\n            description: type.description,\n            settingComponent: type.settingComponent,\n            component: type.component,\n            defaultSettings: type.defaultSettings,\n            createWidgetUrl: buildUrl('widgetNew', { type: type.type })\n          }\n        : null;\n    },\n    textWidget(_, { text, className }) {\n      try {\n        if (!text) {\n          return { text: [], className };\n        }\n        const json = JSON.parse(text);\n        return {\n          text: json,\n          className\n        };\n      } catch (e) {\n        error(e);\n        return { text: [], className };\n      }\n    },\n    bannerWidget(_, { src, alignment, width, height, alt }) {\n      return { src, alignment, width, height, alt };\n    },\n    slideshowWidget(\n      _,\n      {\n        slides,\n        autoplay,\n        autoplaySpeed,\n        arrows,\n        dots,\n        fullWidth,\n        widthValue,\n        heightValue,\n        heightType\n      }\n    ) {\n      return {\n        slides: slides || [],\n        autoplay: autoplay !== undefined ? autoplay : true,\n        autoplaySpeed: autoplaySpeed || 3000,\n        arrows: arrows !== undefined ? arrows : true,\n        dots: dots !== undefined ? dots : true,\n        fullWidth: fullWidth !== undefined ? fullWidth : true,\n        widthValue: widthValue || 1920,\n        heightValue: heightValue || 800,\n        heightType: heightType || 'auto'\n      };\n    },\n    basicMenuWidget: async (_, { settings }, { pool }) => {\n      const categories = [];\n      const pages = [];\n      const menus = settings?.menus || undefined;\n      const isMain = [1, '1', 'true', true].includes(settings?.isMain) || false;\n      if (!menus) {\n        return { menus: [] };\n      }\n\n      for (const menu of menus) {\n        if (menu.type === 'category') {\n          categories.push(menu.uuid);\n        }\n        if (menu.type === 'page') {\n          pages.push(menu.uuid);\n        }\n        menu.children.forEach((child) => {\n          if (child.type === 'category') {\n            categories.push(child.uuid);\n          }\n          if (child.type === 'page') {\n            pages.push(child.uuid);\n          }\n        });\n      }\n      let urls = [];\n      if (categories.length > 0) {\n        const rewrites = await select()\n          .from('url_rewrite')\n          .where('entity_uuid', 'IN', categories)\n          .execute(pool);\n        urls = urls.concat(\n          rewrites.map((r) => ({\n            uuid: r.entity_uuid,\n            url: r.request_path\n          }))\n        );\n      }\n      if (pages.length > 0) {\n        const query = getCmsPagesBaseQuery();\n        query.where('uuid', 'IN', pages);\n        const cmsPages = await query.execute(pool);\n        urls = urls.concat(\n          cmsPages.map((p) => ({\n            uuid: p.uuid,\n            url: buildUrl('cmsPageView', { url_key: p.url_key })\n          }))\n        );\n      }\n      const items = menus.map((menu) => {\n        const url = urls.find((u) => u.uuid === menu.uuid);\n        return {\n          ...menu,\n          id: uniqid(),\n\n          url: url ? url.url : menu.type === 'custom' ? menu.url : null,\n          children: menu.children.map((child) => {\n            const url = urls.find((u) => u.uuid === child.uuid);\n            return {\n              ...child,\n              id: uniqid(),\n\n              url: url ? url.url : child.type === 'custom' ? child.url : null\n            };\n          })\n        };\n      });\n      return { menus: items, isMain, className: settings?.className };\n    }\n  },\n  Widget: {\n    editUrl: ({ uuid }) => buildUrl('widgetEdit', { id: uuid }),\n    updateApi: (widget) => buildUrl('updateWidget', { id: widget.uuid }),\n    deleteApi: (widget) => buildUrl('deleteWidget', { id: widget.uuid })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/migration/Version-1.0.0.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE \"cms_page\" (\n  \"cms_page_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"layout\" varchar NOT NULL,\n  \"status\" boolean DEFAULT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"CMS_PAGE_UUID\" UNIQUE (\"uuid\")\n)`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"cms_page_description\" (\n  \"cms_page_description_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"cms_page_description_cms_page_id\" INT,\n  \"url_key\" varchar NOT NULL,\n  \"name\" varchar NOT NULL,\n  \"content\" text DEFAULT NULL,\n  \"meta_title\" varchar DEFAULT NULL,\n  \"meta_keywords\" varchar DEFAULT NULL,\n  \"meta_description\" text DEFAULT NULL,\n  CONSTRAINT \"PAGE_ID_UNIQUE\" UNIQUE (\"cms_page_description_cms_page_id\"),\n  CONSTRAINT \"URL_KEY_UNIQUE\" UNIQUE (\"url_key\"),\n  CONSTRAINT \"FK_CMS_PAGE_DESCRIPTION\" FOREIGN KEY (\"cms_page_description_cms_page_id\") REFERENCES \"cms_page\" (\"cms_page_id\") ON DELETE CASCADE ON UPDATE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_CMS_PAGE_DESCRIPTION\" ON \"cms_page_description\" (\"cms_page_description_cms_page_id\")`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/migration/Version-1.1.0.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE \"widget\" (\n    \"widget_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n    \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n    \"name\" varchar NOT NULL,\n    \"type\" varchar NOT NULL,\n    \"route\" jsonb NOT NULL default '[]'::jsonb,\n    \"area\" jsonb NOT NULL default '[]'::jsonb,\n    \"sort_order\" INT NOT NULL DEFAULT 1,\n    \"settings\" jsonb NOT NULL default '{}'::jsonb,\n    \"status\" boolean DEFAULT NULL,\n    \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n    \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n    CONSTRAINT \"WIDGET_UUID\" UNIQUE (\"uuid\")\n)`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/migration/Version-1.1.1.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Drop the layout column in the cms_page table\n  await execute(connection, `ALTER TABLE cms_page DROP COLUMN layout`);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/adminNotFound/Meta.tsx",
    "content": "import { Meta } from '@components/common/Meta.js';\nimport { Title } from '@components/common/Title.js';\nimport React from 'react';\n\nexport default function SeoMeta() {\n  return (\n    <>\n      <Title title=\"Page Not Found\" />\n      <Meta name=\"description\" content=\"Page Not Found\" />\n    </>\n  );\n}\n\nexport const layout = {\n  areaId: 'head',\n  sortOrder: 1\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/adminNotFound/NotFound.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\n\nfunction Name() {\n  return (\n    <h1 className=\"page-name text-center mt-6 mb-4\">404 Page Not Found</h1>\n  );\n}\n\ninterface ContentProps {\n  dashboardUrl: string;\n}\nfunction Content({ dashboardUrl }: ContentProps) {\n  return (\n    <div className=\"page-content\">\n      <div className=\"text-center\">The page you requested does not exist.</div>\n      <div className=\"mt-5 text-center\">\n        <Button\n          title=\"Back To Dashboard\"\n          onClick={() => (window.location.href = dashboardUrl)}\n          variant={'default'}\n        >\n          Back To Dashboard\n        </Button>\n      </div>\n    </div>\n  );\n}\n\nexport default function NotFound({ dashboardUrl }: ContentProps) {\n  return (\n    <div className=\"page-width mt-6\">\n      <div className=\"pt-4\">\n        <Area\n          id=\"notfound-page\"\n          coreComponents={[\n            {\n              component: { default: Name },\n              props: {},\n              sortOrder: 10,\n              id: 'notfound-page-title'\n            },\n            {\n              component: { default: Content },\n              props: { dashboardUrl },\n              sortOrder: 20,\n              id: 'notfound-page-content'\n            }\n          ]}\n        />\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    dashboardUrl: url(routeId: \"dashboard\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/adminNotFound/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/notfound\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/adminStaticAsset/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/assets/*\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/adminStaticAsset/staticAssets.ts",
    "content": "import staticMiddleware from '../../../../../lib/middlewares/static.js';\n\nexport default async (request, response, next) => {\n  await staticMiddleware(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/CmsMenuGroup.jsx",
    "content": "import { NavigationItemGroup } from '@components/admin/NavigationItemGroup';\nimport { Book, Puzzle } from 'lucide-react';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport default function CmsMenuGroup({ cmsPageGrid, widgetGrid }) {\n  return (\n    <NavigationItemGroup\n      id=\"cmsMenuGroup\"\n      name=\"CMS\"\n      items={[\n        {\n          Icon: Book,\n          url: cmsPageGrid,\n          title: 'Pages'\n        },\n        {\n          Icon: Puzzle,\n          url: widgetGrid,\n          title: 'Widgets'\n        }\n      ]}\n    />\n  );\n}\n\nCmsMenuGroup.propTypes = {\n  cmsPageGrid: PropTypes.string.isRequired,\n  widgetGrid: PropTypes.string.isRequired\n};\n\nexport const layout = {\n  areaId: 'adminMenu',\n  sortOrder: 60\n};\n\nexport const query = `\n  query Query {\n    cmsPageGrid: url(routeId:\"cmsPageGrid\")\n    widgetGrid: url(routeId:\"widgetGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/CopyRight.tsx",
    "content": "import React from 'react';\n\ninterface CopyRightProps {\n  themeConfig: {\n    copyRight: string;\n  };\n}\nexport default function CopyRight({\n  themeConfig: { copyRight }\n}: CopyRightProps) {\n  return (\n    <div className=\"copyright\">\n      <span>{copyRight}</span>\n    </div>\n  );\n}\n\nCopyRight.defaultProps = {\n  themeConfig: {\n    copyRight: '© 2025 Evershop. All Rights Reserved.'\n  }\n};\n\nexport const layout = {\n  areaId: 'footerLeft',\n  sortOrder: 10\n};\n\nexport const query = `\n  query query {\n    themeConfig {\n      copyRight\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/Logo.tsx",
    "content": "import React from 'react';\n\ninterface LogoProps {\n  dashboardUrl: string;\n}\nexport default function Logo({ dashboardUrl }: LogoProps) {\n  return (\n    <div className=\"logo w-9 h-auto flex items-center\">\n      <a href={dashboardUrl} className=\"flex items-end\">\n        <svg\n          width=\"256\"\n          height=\"282\"\n          viewBox=\"0 0 256 282\"\n          fill=\"none\"\n          className=\"w-8 h-auto\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M63.6632 35.0703L0.336842 70.1406L0.134737 140.668L0 211.26L63.7305 246.459C98.7621 265.799 127.663 281.658 128 281.658C128.337 281.658 145.785 272.117 166.872 260.513C187.891 248.844 216.589 233.05 230.602 225.314L256 211.26V196.174V181.024L254.518 181.798C253.642 182.249 224.943 198.044 190.72 216.997C156.429 235.951 128.067 251.294 127.663 251.229C127.192 251.101 104.556 238.723 77.2716 223.637L27.6211 196.239V140.797V85.3549L50.0547 72.9771C62.3158 66.2081 84.8168 53.8303 99.9747 45.4496C115.065 37.0688 127.731 30.2353 128 30.2353C128.269 30.2353 145.853 39.8409 167.074 51.574L228.918 85.3549L238.672 79.8626L256 70.1406L228.918 55.3775C207.495 43.2577 128.472 -0.0643921 127.798 9.15527e-05C127.394 9.15527e-05 98.4926 15.7946 63.6632 35.0703Z\"\n            fill=\"#008060\"\n          />\n          <path\n            d=\"M192.674 105.146C158.046 124.293 129.213 140.152 128.606 140.281C127.933 140.475 111.023 131.449 88.9937 119.329L50.5263 98.055V113.334L50.5937 128.548L87.9832 149.178C108.531 160.524 126.046 170.065 126.922 170.387C128.269 170.839 137.701 165.875 191.731 136.026C226.493 116.751 255.192 100.827 255.528 100.569C255.798 100.311 255.933 93.4133 255.865 85.226L255.663 70.334L192.674 105.146Z\"\n            fill=\"#008060\"\n          />\n          <path\n            d=\"M248.926 129.451C245.221 131.449 216.657 147.244 185.398 164.521C154.139 181.798 128.337 195.917 128 195.917C127.663 195.917 110.215 186.375 89.1284 174.771L50.8632 153.626L50.6611 168.453C50.5263 179.8 50.7284 183.474 51.3347 184.055C52.6147 185.15 127.192 226.216 128 226.216C128.674 226.216 254.451 156.914 255.528 156.011C255.798 155.753 255.933 148.855 255.865 140.603L255.663 125.712L248.926 129.451Z\"\n            fill=\"#008060\"\n          />\n        </svg>\n      </a>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'header',\n  sortOrder: 10\n};\n\nexport const query = `\n  query query {\n    dashboardUrl: url(routeId:\"dashboard\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/Navigation.jsx",
    "content": "import Area from '@components/common/Area';\nimport React from 'react';\nimport './Navigation.scss';\n\nexport default function AdminNavigation() {\n  return (\n    <div className=\"admin-nav-container\">\n      <div className=\"admin-nav\">\n        <ul className=\"list-unstyled\">\n          <Area id=\"adminMenu\" noOuter />\n        </ul>\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'adminNavigation',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/Navigation.scss",
    "content": ".admin-navigation {\n  width: 15rem;\n  float: left;\n  height: 100vh;\n  border-right: 1px solid var(--divider);\n  position: fixed;\n  left: 0;\n  top: 3.5rem;\n  padding-right: 4px;\n  padding-bottom: 9.375rem;\n  overflow-y: scroll;\n}\n.admin-navigation .top-bar {\n  display: flex;\n  justify-content: space-between;\n  position: absolute;\n  top: 20px;\n  left: 20px;\n  right: 20px;\n}\n.admin-navigation .top-bar .logo {\n  width: 70px;\n}\n.admin-navigation .top-bar .logo img {\n  max-width: 100%;\n}\n.admin-navigation .menu-toggle {\n  color: #fff;\n}\n.admin-nav li span,\n.admin-navigation li a {\n  line-height: 35px;\n  font-weight: 600;\n}\n.admin-nav .root-label {\n  text-transform: uppercase;\n  font-weight: bold;\n  font-size: 11px;\n}\n.nav-item {\n  margin-top: 2px;\n}\n.nav-item > a,\n.nav-item .root-label {\n  padding-left: 0.625rem;\n  margin-left: 0.625rem;\n}\n.nav-item > a:hover {\n  background-color: #edeeef;\n  border-radius: 4px;\n}\n.nav-item.active::before {\n  content: '';\n  width: 3.5px;\n  display: block;\n  position: absolute;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  border-radius: 0 4px 4px 0;\n  background-color: var(--primary);\n}\n.nav-item.active {\n  position: relative;\n  a {\n    color: var(--primary);\n    background-color: #edeeef;\n    border-radius: 4px;\n  }\n}\n.admin-nav > ul > li {\n  margin-top: 0.625rem;\n}\n.admin-navigation::-webkit-scrollbar {\n  width: 5px;\n  height: 8px;\n  background-color: #edeeef;\n}\n\n.admin-navigation::-webkit-scrollbar-thumb {\n  background-color: #058c8c;\n  outline: 1px solid #058c8c;\n  border-radius: 2px;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/Notification.jsx",
    "content": "import { useAppState } from '@components/common/context/app';\nimport React from 'react';\nimport { toast, ToastContainer } from 'react-toastify';\nimport { get } from '../../../../../lib/util/get.js';\nimport './Notification.scss';\n\nexport default function Notification() {\n  const notify = (type, message) => {\n    switch (type) {\n      case 'success':\n        toast.success(message);\n        break;\n      case 'error':\n        toast.error(message);\n        break;\n      case 'info':\n        toast.info(message);\n        break;\n      case 'warning':\n        toast.warning(message);\n        break;\n      default:\n        toast(message);\n    }\n  };\n  const context = useAppState();\n\n  React.useEffect(() => {\n    get(context, 'notifications', []).forEach((n) => notify(n.type, n.message));\n  }, []);\n\n  return (\n    <div>\n      <ToastContainer hideProgressBar autoClose={false} />\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'body',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/Notification.scss",
    "content": ".Toastify__toast-container {\n  z-index: 9999;\n  -webkit-transform: translate3d(0, 0, 9999px);\n  transform: translate3d(0, 0, 9999px);\n  position: fixed;\n  width: 380px;\n  background-color: transparent;\n}\n.Toastify__toast-container--top-left {\n  top: 1em;\n  left: 1em;\n}\n.Toastify__toast-container--top-center {\n  top: 1em;\n  left: 50%;\n  transform: translateX(-50%);\n}\n.Toastify__toast-container--top-right {\n  top: 1em;\n  right: 1em;\n}\n.Toastify__toast-container--bottom-left {\n  bottom: 1em;\n  left: 1em;\n}\n.Toastify__toast-container--bottom-center {\n  bottom: 1em;\n  left: 50%;\n  transform: translateX(-50%);\n}\n.Toastify__toast-container--bottom-right {\n  bottom: 1em;\n  right: 1em;\n}\n\n@media only screen and (max-width: 480px) {\n  .Toastify__toast-container {\n    width: 100vw;\n    padding: 0;\n    left: 0;\n    margin: 0;\n  }\n  .Toastify__toast-container--top-left,\n  .Toastify__toast-container--top-center,\n  .Toastify__toast-container--top-right {\n    top: 0;\n    transform: translateX(0);\n  }\n  .Toastify__toast-container--bottom-left,\n  .Toastify__toast-container--bottom-center,\n  .Toastify__toast-container--bottom-right {\n    bottom: 0;\n    transform: translateX(0);\n  }\n  .Toastify__toast-container--rtl {\n    right: 0;\n    left: initial;\n  }\n}\n.Toastify__toast {\n  position: relative;\n  min-height: 64px;\n  box-sizing: border-box;\n  margin-bottom: 0.625rem;\n  padding: 1rem;\n  border-radius: var(--radius);\n  display: -ms-flexbox;\n  display: flex;\n  -ms-flex-pack: justify;\n  justify-content: space-between;\n  max-height: 800px;\n  overflow: hidden;\n  font-family: sans-serif;\n  cursor: pointer;\n  direction: ltr;\n  background: var(--card);\n  color: var(--card-foreground);\n  border: 1px solid var(--border);\n  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n}\n.Toastify__toast--rtl {\n  direction: rtl;\n}\n.Toastify__toast--dark {\n  background: var(--card);\n  color: var(--card-foreground);\n  border-color: var(--border);\n}\n\n.Toastify__toast--info {\n  background: var(--card);\n  border-color: oklch(0.6 0.118 184.704);\n  color: var(--card-foreground);\n}\n.dark .Toastify__toast--info {\n  background: var(--card);\n  border-color: oklch(0.696 0.17 162.48);\n  color: var(--card-foreground);\n}\n.Toastify__toast--success {\n  background: var(--card);\n  border-color: var(--primary);\n  color: var(--card-foreground);\n}\n.dark .Toastify__toast--success {\n  background: var(--card);\n  border-color: var(--primary);\n  color: var(--card-foreground);\n}\n.Toastify__toast--warning {\n  background: var(--card);\n  border-color: oklch(0.828 0.189 84.429);\n  color: var(--card-foreground);\n}\n.dark .Toastify__toast--warning {\n  background: var(--card);\n  border-color: oklch(0.769 0.188 70.08);\n  color: var(--card-foreground);\n}\n.Toastify__toast--error {\n  background: var(--card);\n  border-color: var(--destructive);\n  color: var(--card-foreground);\n}\n.dark .Toastify__toast--error {\n  background: var(--card);\n  border-color: var(--destructive);\n  color: var(--card-foreground);\n}\n.Toastify__toast-body {\n  margin: auto 0;\n  -ms-flex: 1 1 auto;\n  flex: 1 1 auto;\n}\n\n@media only screen and (max-width: 480px) {\n  .Toastify__toast {\n    margin-bottom: 0;\n  }\n}\n.Toastify__close-button {\n  color: var(--muted-foreground);\n  background: transparent;\n  outline: none;\n  border: none;\n  padding: 0;\n  cursor: pointer;\n  opacity: 0.7;\n  transition: 0.3s ease;\n  -ms-flex-item-align: start;\n  align-self: flex-start;\n}\n.Toastify__close-button--default {\n  color: var(--muted-foreground);\n  opacity: 0.7;\n}\n.Toastify__close-button > svg {\n  fill: currentColor;\n  height: 16px;\n  width: 14px;\n}\n.Toastify__close-button:hover,\n.Toastify__close-button:focus {\n  opacity: 1;\n}\n\n@keyframes Toastify__trackProgress {\n  0% {\n    transform: scaleX(1);\n  }\n  100% {\n    transform: scaleX(0);\n  }\n}\n.Toastify__progress-bar {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 3px;\n  z-index: 9999;\n  opacity: 0.5;\n  background-color: currentColor;\n  transform-origin: left;\n}\n.Toastify__progress-bar--animated {\n  animation: Toastify__trackProgress linear 1 forwards;\n}\n.Toastify__progress-bar--controlled {\n  transition: transform 0.2s;\n}\n.Toastify__progress-bar--rtl {\n  right: 0;\n  left: initial;\n  transform-origin: right;\n}\n.Toastify__progress-bar--default {\n  background-color: var(--primary);\n}\n.Toastify__progress-bar--dark {\n  background-color: var(--primary);\n}\n@keyframes Toastify__bounceInRight {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  from {\n    opacity: 0;\n    transform: translate3d(3000px, 0, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(-25px, 0, 0);\n  }\n  75% {\n    transform: translate3d(10px, 0, 0);\n  }\n  90% {\n    transform: translate3d(-5px, 0, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n@keyframes Toastify__bounceOutRight {\n  20% {\n    opacity: 1;\n    transform: translate3d(-20px, 0, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(2000px, 0, 0);\n  }\n}\n@keyframes Toastify__bounceInLeft {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  0% {\n    opacity: 0;\n    transform: translate3d(-3000px, 0, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(25px, 0, 0);\n  }\n  75% {\n    transform: translate3d(-10px, 0, 0);\n  }\n  90% {\n    transform: translate3d(5px, 0, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n@keyframes Toastify__bounceOutLeft {\n  20% {\n    opacity: 1;\n    transform: translate3d(20px, 0, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(-2000px, 0, 0);\n  }\n}\n@keyframes Toastify__bounceInUp {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  from {\n    opacity: 0;\n    transform: translate3d(0, 3000px, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(0, -20px, 0);\n  }\n  75% {\n    transform: translate3d(0, 10px, 0);\n  }\n  90% {\n    transform: translate3d(0, -5px, 0);\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__bounceOutUp {\n  20% {\n    transform: translate3d(0, -10px, 0);\n  }\n  40%,\n  45% {\n    opacity: 1;\n    transform: translate3d(0, 20px, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(0, -2000px, 0);\n  }\n}\n@keyframes Toastify__bounceInDown {\n  from,\n  60%,\n  75%,\n  90%,\n  to {\n    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);\n  }\n  0% {\n    opacity: 0;\n    transform: translate3d(0, -3000px, 0);\n  }\n  60% {\n    opacity: 1;\n    transform: translate3d(0, 25px, 0);\n  }\n  75% {\n    transform: translate3d(0, -10px, 0);\n  }\n  90% {\n    transform: translate3d(0, 5px, 0);\n  }\n  to {\n    transform: none;\n  }\n}\n@keyframes Toastify__bounceOutDown {\n  20% {\n    transform: translate3d(0, 10px, 0);\n  }\n  40%,\n  45% {\n    opacity: 1;\n    transform: translate3d(0, -20px, 0);\n  }\n  to {\n    opacity: 0;\n    transform: translate3d(0, 2000px, 0);\n  }\n}\n.Toastify__bounce-enter--top-left,\n.Toastify__bounce-enter--bottom-left {\n  animation-name: Toastify__bounceInLeft;\n}\n.Toastify__bounce-enter--top-right,\n.Toastify__bounce-enter--bottom-right {\n  animation-name: Toastify__bounceInRight;\n}\n.Toastify__bounce-enter--top-center {\n  animation-name: Toastify__bounceInDown;\n}\n.Toastify__bounce-enter--bottom-center {\n  animation-name: Toastify__bounceInUp;\n}\n\n.Toastify__bounce-exit--top-left,\n.Toastify__bounce-exit--bottom-left {\n  animation-name: Toastify__bounceOutLeft;\n}\n.Toastify__bounce-exit--top-right,\n.Toastify__bounce-exit--bottom-right {\n  animation-name: Toastify__bounceOutRight;\n}\n.Toastify__bounce-exit--top-center {\n  animation-name: Toastify__bounceOutUp;\n}\n.Toastify__bounce-exit--bottom-center {\n  animation-name: Toastify__bounceOutDown;\n}\n\n@keyframes Toastify__zoomIn {\n  from {\n    opacity: 0;\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n  50% {\n    opacity: 1;\n  }\n}\n@keyframes Toastify__zoomOut {\n  from {\n    opacity: 1;\n  }\n  50% {\n    opacity: 0;\n    transform: scale3d(0.3, 0.3, 0.3);\n  }\n  to {\n    opacity: 0;\n  }\n}\n.Toastify__zoom-enter {\n  animation-name: Toastify__zoomIn;\n}\n\n.Toastify__zoom-exit {\n  animation-name: Toastify__zoomOut;\n}\n\n@keyframes Toastify__flipIn {\n  from {\n    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n    animation-timing-function: ease-in;\n    opacity: 0;\n  }\n  40% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n    animation-timing-function: ease-in;\n  }\n  60% {\n    transform: perspective(400px) rotate3d(1, 0, 0, 10deg);\n    opacity: 1;\n  }\n  80% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -5deg);\n  }\n  to {\n    transform: perspective(400px);\n  }\n}\n@keyframes Toastify__flipOut {\n  from {\n    transform: perspective(400px);\n  }\n  30% {\n    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);\n    opacity: 1;\n  }\n  to {\n    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);\n    opacity: 0;\n  }\n}\n.Toastify__flip-enter {\n  animation-name: Toastify__flipIn;\n}\n\n.Toastify__flip-exit {\n  animation-name: Toastify__flipOut;\n}\n\n@keyframes Toastify__slideInRight {\n  from {\n    transform: translate3d(110%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideInLeft {\n  from {\n    transform: translate3d(-110%, 0, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideInUp {\n  from {\n    transform: translate3d(0, 110%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideInDown {\n  from {\n    transform: translate3d(0, -110%, 0);\n    visibility: visible;\n  }\n  to {\n    transform: translate3d(0, 0, 0);\n  }\n}\n@keyframes Toastify__slideOutRight {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(110%, 0, 0);\n  }\n}\n@keyframes Toastify__slideOutLeft {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(-110%, 0, 0);\n  }\n}\n@keyframes Toastify__slideOutDown {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, 500px, 0);\n  }\n}\n@keyframes Toastify__slideOutUp {\n  from {\n    transform: translate3d(0, 0, 0);\n  }\n  to {\n    visibility: hidden;\n    transform: translate3d(0, -500px, 0);\n  }\n}\n.Toastify__slide-enter--top-left,\n.Toastify__slide-enter--bottom-left {\n  animation-name: Toastify__slideInLeft;\n}\n.Toastify__slide-enter--top-right,\n.Toastify__slide-enter--bottom-right {\n  animation-name: Toastify__slideInRight;\n}\n.Toastify__slide-enter--top-center {\n  animation-name: Toastify__slideInDown;\n}\n.Toastify__slide-enter--bottom-center {\n  animation-name: Toastify__slideInUp;\n}\n\n.Toastify__slide-exit--top-left,\n.Toastify__slide-exit--bottom-left {\n  animation-name: Toastify__slideOutLeft;\n}\n.Toastify__slide-exit--top-right,\n.Toastify__slide-exit--bottom-right {\n  animation-name: Toastify__slideOutRight;\n}\n.Toastify__slide-exit--top-center {\n  animation-name: Toastify__slideOutUp;\n}\n.Toastify__slide-exit--bottom-center {\n  animation-name: Toastify__slideOutDown;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/QuickLinks.jsx",
    "content": "import { NavigationItemGroup } from '@components/admin/NavigationItemGroup';\nimport { HomeIcon } from 'lucide-react';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport default function QuickLinks({ dashboard }) {\n  return (\n    <NavigationItemGroup\n      id=\"quickLinks\"\n      name=\"Quick links\"\n      items={[\n        {\n          Icon: HomeIcon,\n          url: dashboard,\n          title: 'Dashboard'\n        }\n      ]}\n    />\n  );\n}\n\nQuickLinks.propTypes = {\n  dashboard: PropTypes.string.isRequired\n};\n\nexport const layout = {\n  areaId: 'adminMenu',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    dashboard: url(routeId: \"dashboard\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/SearchBox.tsx",
    "content": "import Spinner from '@components/admin/Spinner.js';\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput\n} from '@components/common/ui/InputGroup.js';\nimport { Search } from 'lucide-react';\nimport React, { useRef, useState } from 'react';\nimport { useQuery } from 'urql';\nimport { NoResult } from './search/NoResult.js';\nimport { Results } from './search/Results.js';\n\nconst useClickOutside = (ref, callback) => {\n  const handleClick = (e) => {\n    if (ref.current && !ref.current.contains(e.target)) {\n      callback();\n    }\n  };\n  React.useEffect(() => {\n    document.addEventListener('click', handleClick);\n    return () => {\n      document.removeEventListener('click', handleClick);\n    };\n  });\n};\n\nconst SearchQuery = `\n  query Query ($filters: [FilterInput]) {\n    customers(filters: $filters) {\n      items {\n        customerId\n        uuid\n        fullName\n        email\n        url: editUrl\n      }\n    }\n    products(filters: $filters) {\n      items {\n        productId\n        uuid\n        sku\n        name\n        url: editUrl\n      }\n    }\n    orders(filters: $filters) {\n      items {\n        orderId\n        uuid\n        orderNumber\n        url: editUrl\n      }\n    }\n  }\n`;\n\ninterface SearchBoxProps {\n  resourceLinks: {\n    url: string;\n    name: string;\n  }[];\n}\n\nexport default function SearchBox({ resourceLinks }: SearchBoxProps) {\n  const [keyword, setKeyword] = React.useState('');\n  const [showResult, setShowResult] = useState(false);\n  const [loading, setLoading] = useState(false);\n  const InputRef = useRef<HTMLInputElement>(null);\n\n  const clickRef = React.useRef<HTMLDivElement>(null);\n  const onClickOutside = () => {\n    if (InputRef.current !== document.activeElement) {\n      setShowResult(false);\n    }\n  };\n  useClickOutside(clickRef, onClickOutside);\n\n  const [result, reexecuteQuery] = useQuery({\n    query: SearchQuery,\n    variables: {\n      filters: keyword\n        ? [{ key: 'keyword', operation: 'eq', value: keyword }]\n        : []\n    },\n    pause: true\n  });\n  const { data, fetching } = result;\n\n  React.useEffect(() => {\n    setLoading(true);\n    if (keyword) {\n      setShowResult(true);\n    } else {\n      setShowResult(false);\n    }\n    const timer = setTimeout(() => {\n      if (keyword) {\n        reexecuteQuery({ requestPolicy: 'network-only' });\n        setLoading(false);\n      }\n    }, 1500);\n\n    return () => clearTimeout(timer);\n  }, [keyword]);\n\n  return (\n    <div className=\"relative self-center ml-[14.563rem] w-[34.375rem]\">\n      <InputGroup className=\"bg-[#f1f2f3] rounded-[3px] border-[#f1f2f3]\">\n        <InputGroupAddon>\n          <Search />\n        </InputGroupAddon>\n        <InputGroupInput\n          type=\"text\"\n          placeholder=\"Search\"\n          ref={InputRef}\n          onChange={(e) => setKeyword(e.target.value)}\n        />\n      </InputGroup>\n      {showResult && (\n        <div\n          className=\"absolute top-[calc(100%+1rem)] left-0 bg-white rounded-[5px] w-full py-5 px-2.5 border border-border shadow-lg z-50 max-h-[30rem] overflow-y-auto\"\n          ref={clickRef}\n        >\n          {(loading || fetching) && (\n            <div className=\"p-2 flex justify-center items-center\">\n              <Spinner width={25} height={25} />\n            </div>\n          )}\n          {!keyword && (\n            <div className=\"text-center\">\n              <span>Search for products, order and other resources</span>\n            </div>\n          )}\n          {data?.products.items.length === 0 &&\n            data?.customers.items.length === 0 &&\n            data?.orders.items.length === 0 &&\n            keyword &&\n            !loading && (\n              <NoResult keyword={keyword} resourseLinks={resourceLinks} />\n            )}\n          {data &&\n            !loading &&\n            !fetching &&\n            (data?.products.items.length > 0 ||\n              data?.customers.items.length > 0 ||\n              data?.orders.items.length > 0) && (\n              <Results keyword={keyword} results={data} />\n            )}\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'header',\n  sortOrder: 20\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/Survey.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport { MessageSquare } from 'lucide-react';\nimport React from 'react';\n\nexport default function Survey() {\n  const surveyUrl = 'https://evershop.io/admin-survey';\n\n  const handleSurveyClick = () => {\n    window.open(surveyUrl, '_blank', 'noopener,noreferrer');\n  };\n\n  return (\n    <div className=\"fixed bottom-6 right-6 z-50\">\n      <Button\n        onClick={handleSurveyClick}\n        size=\"default\"\n        className=\"shadow-lg hover:shadow-xl transition-shadow gap-2\"\n        title=\"Take our survey\"\n      >\n        <MessageSquare className=\"size-4\" />\n        Give Feedback\n      </Button>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 999\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/Version.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\n\n\nexport default function Version({ version }) {\n  return (\n    <div className=\"version\">\n      <span>Version {version}</span>\n    </div>\n  );\n}\n\nVersion.propTypes = {\n  version: PropTypes.string.isRequired\n};\n\nexport const layout = {\n  areaId: 'footerLeft',\n  sortOrder: 20\n};\n\nexport const query = `\n  query query {\n    version\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/search/NoResult.tsx",
    "content": "import React from 'react';\n\ninterface NoResultProps {\n  keyword: string;\n  resourseLinks?: {\n    url: string;\n    name: string;\n  }[];\n}\n\nexport function NoResult({ keyword = '', resourseLinks = [] }: NoResultProps) {\n  return (\n    <div className=\"items-center text-center\">\n      <h3 className=\"text-xl font-semibold text-muted-foreground\">\n        No results for &quot;\n        {keyword}\n        &quot;\n      </h3>\n      <div className=\"grid grid-cols-2 mt-2\">\n        {resourseLinks.map((link, index) => (\n          <div\n            key={index}\n            className=\"flex space-x-2 justify-center items-center\"\n          >\n            <a href={link.url} className=\"text-divider hover:underline\">\n              {link.name}\n            </a>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/all/search/Results.jsx",
    "content": "import {\n  Tabs,\n  TabsList,\n  TabsTrigger,\n  TabsContent\n} from '@components/common/ui/Tabs.js';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport function Results({ keyword, results = {} }) {\n  const { customers = [], products = [], orders = [] } = results;\n\n  // Determine which tabs have data\n  const availableTabs = [];\n  if (products.items.length > 0) availableTabs.push('products');\n  if (customers.items.length > 0) availableTabs.push('customers');\n  if (orders.items.length > 0) availableTabs.push('orders');\n\n  // Default to first available tab\n  const defaultTab = availableTabs[0] || 'products';\n\n  return (\n    <div className=\"space-y-3\">\n      <h3 className=\"text-xl font-semibold\">\n        Results for &quot;\n        {keyword}\n        &quot;\n      </h3>\n      <Tabs defaultValue={defaultTab}>\n        <TabsList variant=\"line\">\n          {products.items.length > 0 && (\n            <TabsTrigger value=\"products\">\n              Products ({products.items.length})\n            </TabsTrigger>\n          )}\n          {customers.items.length > 0 && (\n            <TabsTrigger value=\"customers\">\n              Customers ({customers.items.length})\n            </TabsTrigger>\n          )}\n          {orders.items.length > 0 && (\n            <TabsTrigger value=\"orders\">\n              Orders ({orders.items.length})\n            </TabsTrigger>\n          )}\n        </TabsList>\n\n        {products.items.length > 0 && (\n          <TabsContent value=\"products\" className=\"max-h-60 overflow-y-auto\">\n            <div className=\"flex flex-col space-y-1\">\n              {products.items.map((product, index) => (\n                <a\n                  href={product.url}\n                  key={index}\n                  className=\"rounded py-2 px-2 hover:bg-muted block\"\n                >\n                  <div className=\"font-bold\">{product.name}</div>\n                  <div className=\"text-sm text-muted-foreground\">\n                    #{product.sku}\n                  </div>\n                </a>\n              ))}\n            </div>\n          </TabsContent>\n        )}\n\n        {customers.items.length > 0 && (\n          <TabsContent value=\"customers\" className=\"max-h-60 overflow-y-auto\">\n            <div className=\"flex flex-col space-y-1\">\n              {customers.items.map((customer, index) => (\n                <a\n                  href={customer.url}\n                  key={index}\n                  className=\"rounded py-2 px-2 hover:bg-muted block\"\n                >\n                  <div className=\"font-bold\">{customer.fullName}</div>\n                  <div className=\"text-sm text-muted-foreground\">\n                    {customer.email}\n                  </div>\n                </a>\n              ))}\n            </div>\n          </TabsContent>\n        )}\n\n        {orders.items.length > 0 && (\n          <TabsContent value=\"orders\" className=\"max-h-60 overflow-y-auto\">\n            <div className=\"flex flex-col space-y-1\">\n              {orders.items.map((order, index) => (\n                <a\n                  href={order.url}\n                  key={index}\n                  className=\"rounded py-2 px-2 hover:bg-muted block\"\n                >\n                  <div className=\"font-bold\">#{order.orderNumber}</div>\n                  <div className=\"text-sm text-muted-foreground\">\n                    {order.email}\n                  </div>\n                </a>\n              ))}\n            </div>\n          </TabsContent>\n        )}\n      </Tabs>\n    </div>\n  );\n}\n\nResults.propTypes = {\n  keyword: PropTypes.string,\n  results: PropTypes.arrayOf(\n    PropTypes.shape({\n      items: PropTypes.arrayOf(\n        PropTypes.shape({\n          url: PropTypes.string,\n          name: PropTypes.string,\n          description: PropTypes.string\n        })\n      )\n    })\n  )\n};\n\nResults.defaultProps = {\n  keyword: undefined,\n  results: []\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageEdit/PageEditForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport React from 'react';\n\ninterface CmsPageEditFormProps {\n  action: string;\n  gridUrl: string;\n}\nexport default function CmsPageEditForm({\n  action,\n  gridUrl\n}: CmsPageEditFormProps) {\n  return (\n    <Form method=\"PATCH\" action={action} id=\"cmsPageEditForm\" submitBtn={false}>\n      <div className=\"grid gap-5 grid-cols-1 w-2/3 mx-auto\">\n        <Area id=\"wideScreen\" noOuter />\n      </div>\n      <FormButtons formId=\"cmsPageEditForm\" cancelUrl={gridUrl} />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"updateCmsPage\", params: [{key: \"id\", value: getContextValue(\"cmsPageUuid\")}]),\n    gridUrl: url(routeId: \"cmsPageGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageEdit/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (request, response: EvershopResponse, next) => {\n  try {\n    const query = select();\n    query.from('cms_page');\n    query.andWhere('cms_page.uuid', '=', request.params.id);\n    query\n      .leftJoin('cms_page_description')\n      .on(\n        'cms_page_description.cms_page_description_cms_page_id',\n        '=',\n        'cms_page.cms_page_id'\n      );\n\n    const cmsPage = await query.load(pool);\n\n    if (cmsPage === null) {\n      response.status(404);\n      next();\n    } else {\n      setContextValue(request, 'cmsPageId', cmsPage.cms_page_id);\n      setContextValue(request, 'cmsPageUuid', cmsPage.uuid);\n      setPageMetaInfo(request, {\n        title: cmsPage.name,\n        description: cmsPage.name\n      });\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageEdit/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/pages/edit/:id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageEdit+cmsPageNew/General.tsx",
    "content": "import { Editor, Row } from '@components/common/form/Editor.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface CmsPageGeneralProps {\n  page?: {\n    cmsPageId?: string;\n    name?: string;\n    status?: number;\n    sortOrder?: number;\n    content?: Row[];\n  };\n}\n\nexport default function General({ page }: CmsPageGeneralProps) {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>General Information</CardTitle>\n        <CardDescription>\n          Provide the basic information for the CMS page.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"space-y-3\">\n          <div>\n            <InputField\n              id=\"cms_page_name\"\n              name=\"name\"\n              label=\"Page Name\"\n              placeholder=\"Enter page name\"\n              defaultValue={page?.name}\n              required\n              validation={{ required: 'Page name is required' }}\n              helperText=\"This is the name of the CMS page that will be displayed in the admin panel.\"\n            />\n          </div>\n          <div className=\"space-y-2\">\n            <RadioGroupField\n              name=\"status\"\n              label=\"Status\"\n              options={[\n                { value: 1, label: 'Enabled' },\n                { value: 0, label: 'Disabled' }\n              ]}\n              defaultValue={page?.status}\n              required\n              helperText=\"Enable this page to make it visible on the frontend.\"\n            />\n          </div>\n          <div>\n            <label htmlFor=\"content\" className=\"block mb-2 font-medium\">\n              Content\n            </label>\n            <Editor name=\"content\" value={page?.content || []} />\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'wideScreen',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    page: cmsPage(id: getContextValue(\"cmsPageId\", null)) {\n      cmsPageId\n      name\n      status\n      sortOrder\n      content\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageEdit+cmsPageNew/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface CmsGridPageHeadingProps {\n  backUrl: string;\n  page?: {\n    name?: string;\n  };\n}\n\nexport default function CmsGridPageHeading({\n  backUrl,\n  page\n}: CmsGridPageHeadingProps) {\n  return (\n    <div className=\"w-2/3 mx-auto\">\n      <PageHeading\n        backUrl={backUrl}\n        heading={page ? `Editing ${page.name}` : 'Create a new page'}\n      />\n    </div>\n  );\n}\n\nCmsGridPageHeading.defaultProps = {\n  page: null\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    page: cmsPage(id: getContextValue(\"cmsPageId\", null)) {\n      name\n    }\n    backUrl: url(routeId: \"cmsPageGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageEdit+cmsPageNew/Seo.tsx",
    "content": "import { InputField } from '@components/common/form/InputField.js';\nimport { TextareaField } from '@components/common/form/TextareaField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface CmsPageSeoProps {\n  page?: {\n    urlKey?: string;\n    metaTitle?: string;\n    metaKeywords?: string;\n    metaDescription?: string;\n  };\n}\n\nexport default function Seo({ page }: CmsPageSeoProps) {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>SEO Information</CardTitle>\n        <CardDescription>\n          Provide the SEO details for the CMS page.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"space-y-3\">\n          <InputField\n            id=\"urlKey\"\n            name=\"url_key\"\n            label=\"URL Key\"\n            placeholder=\"Enter URL key\"\n            defaultValue={page?.urlKey}\n            required\n            validation={{ required: 'URL key is required' }}\n            helperText=\"This is the URL path for the CMS page.\"\n          />\n\n          <InputField\n            id=\"metaTitle\"\n            name=\"meta_title\"\n            label=\"Meta Title\"\n            placeholder=\"Enter meta title\"\n            defaultValue={page?.metaTitle}\n            required\n            validation={{ required: 'Meta title is required' }}\n            helperText=\"This is the meta title for the CMS page.\"\n          />\n\n          <TextareaField\n            name=\"meta_description\"\n            label=\"Meta Description\"\n            placeholder=\"Enter meta description\"\n            defaultValue={page?.metaDescription}\n          />\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'wideScreen',\n  sortOrder: 30\n};\n\nexport const query = `\n  query Query {\n    page: cmsPage(id: getContextValue('cmsPageId', null)) {\n      urlKey\n      metaTitle\n      metaKeywords\n      metaDescription\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/Grid.jsx",
    "content": "import { GridPagination } from '@components/admin/grid/GridPagination';\nimport { SortableHeader } from '@components/admin/grid/header/Sortable';\nimport { Status } from '@components/admin/Status.js';\nimport Area from '@components/common/Area';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { useAlertContext } from '@components/common/modal/Alert';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport { Card } from '@components/common/ui/Card';\nimport {\n  CardAction,\n  CardContent,\n  CardHeader\n} from '@components/common/ui/Card.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport axios from 'axios';\nimport PropTypes from 'prop-types';\nimport React, { useState } from 'react';\nimport { PageName } from './rows/PageName.js';\n\nfunction Actions({ pages = [], selectedIds = [] }) {\n  const { openAlert, closeAlert } = useAlertContext();\n  const [isLoading, setIsLoading] = useState(false);\n\n  const updatePages = async (status) => {\n    setIsLoading(true);\n    const promises = pages\n      .filter((page) => selectedIds.includes(page.uuid))\n      .map((page) =>\n        axios.patch(page.updateApi, {\n          status\n        })\n      );\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const deletePages = async () => {\n    setIsLoading(true);\n    const promises = pages\n      .filter((page) => selectedIds.includes(page.uuid))\n      .map((page) => axios.delete(page.deleteApi));\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const actions = [\n    {\n      name: 'Disable',\n      onAction: () => {\n        openAlert({\n          heading: `Disable ${selectedIds.length} pages`,\n          content: 'Are you sure?',\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Disable',\n            onAction: async () => {\n              await updatePages(0);\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    },\n    {\n      name: 'Enable',\n      onAction: () => {\n        openAlert({\n          heading: `Enable ${selectedIds.length} pages`,\n          content: 'Are you sure?',\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Enable',\n            onAction: async () => {\n              await updatePages(1);\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    },\n    {\n      name: 'Delete',\n      onAction: () => {\n        openAlert({\n          heading: `Delete ${selectedIds.length} pages`,\n          content: <div>Can&apos;t be undone</div>,\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Delete',\n            onAction: async () => {\n              await deletePages();\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    }\n  ];\n\n  return (\n    <TableRow>\n      {selectedIds.length === 0 && null}\n      {selectedIds.length > 0 && (\n        <TableCell colSpan=\"100\">\n          <ButtonGroup>\n            {actions.map((action, i) => (\n              <Button\n                key={i}\n                variant={'outline'}\n                onClick={(e) => {\n                  e.preventDefault();\n                  action.onAction();\n                }}\n              >\n                {action.name}\n              </Button>\n            ))}\n          </ButtonGroup>\n        </TableCell>\n      )}\n    </TableRow>\n  );\n}\n\nActions.propTypes = {\n  selectedIds: PropTypes.arrayOf(PropTypes.string).isRequired,\n  pages: PropTypes.arrayOf(\n    PropTypes.shape({\n      uuid: PropTypes.string.isRequired,\n      updateApi: PropTypes.string.isRequired,\n      deleteApi: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport default function CMSPageGrid({\n  cmsPages: { items: pages, total, currentFilters = [] }\n}) {\n  const page = currentFilters.find((filter) => filter.key === 'page')\n    ? parseInt(currentFilters.find((filter) => filter.key === 'page').value, 10)\n    : 1;\n  const limit = currentFilters.find((filter) => filter.key === 'limit')\n    ? parseInt(\n        currentFilters.find((filter) => filter.key === 'limit').value,\n        10\n      )\n    : 20;\n\n  const [selectedRows, setSelectedRows] = useState([]);\n\n  return (\n    <Card>\n      <CardHeader className=\"flex justify-between\">\n        <Form submitBtn={false} id=\"pageGridFilter\">\n          <Area\n            id=\"cmsPageGridFilter\"\n            noOuter\n            coreComponents={[\n              {\n                component: {\n                  default: () => (\n                    <InputField\n                      name=\"name\"\n                      placeholder=\"Search\"\n                      defaultValue={\n                        currentFilters.find((f) => f.key === 'name')?.value\n                      }\n                      onKeyPress={(e) => {\n                        // If the user press enter, we should submit the form\n                        if (e.key === 'Enter') {\n                          const url = new URL(document.location);\n                          const name = e.target?.value;\n                          if (name) {\n                            url.searchParams.set('name[operation]', 'like');\n                            url.searchParams.set('name[value]', name);\n                          } else {\n                            url.searchParams.delete('name[operation]');\n                            url.searchParams.delete('name[value]');\n                          }\n                          window.location.href = url;\n                        }\n                      }}\n                    />\n                  )\n                },\n                sortOrder: 10\n              }\n            ]}\n          />\n        </Form>\n        <CardAction>\n          <Button\n            variant={'link'}\n            onClick={() => {\n              // Just get the url and remove all query params\n              const url = new URL(document.location);\n              url.search = '';\n              window.location.href = url.href;\n            }}\n          >\n            Clear filter\n          </Button>\n        </CardAction>\n      </CardHeader>\n      <CardContent>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableCell>\n                <div className=\"form-field mb-0\">\n                  <Checkbox\n                    onCheckedChange={(checked) => {\n                      if (checked) {\n                        setSelectedRows(pages.map((p) => p.uuid));\n                      } else {\n                        setSelectedRows([]);\n                      }\n                    }}\n                  />\n                </div>\n              </TableCell>\n              <Area\n                className=\"\"\n                id=\"pageGridHeader\"\n                noOuter\n                coreComponents={[\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Name\"\n                          name=\"name\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 10\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Status\"\n                          name=\"status\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 20\n                  }\n                ]}\n              />\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            <Actions\n              pages={pages}\n              selectedIds={selectedRows}\n              setSelectedRows={setSelectedRows}\n            />\n            {pages.map((p, i) => (\n              <TableRow key={i}>\n                <TableCell style={{ width: '2rem' }}>\n                  <div className=\"form-field mb-0\">\n                    <Checkbox\n                      checked={selectedRows.includes(p.uuid)}\n                      onCheckedChange={(checked) => {\n                        if (checked) {\n                          setSelectedRows(selectedRows.concat([p.uuid]));\n                        } else {\n                          setSelectedRows(\n                            selectedRows.filter((row) => row !== p.uuid)\n                          );\n                        }\n                      }}\n                    />\n                  </div>\n                </TableCell>\n                <Area\n                  className=\"\"\n                  id=\"pageGridRow\"\n                  row={p}\n                  noOuter\n                  coreComponents={[\n                    {\n                      component: {\n                        default: () => (\n                          <PageName url={p.editUrl} name={p.name} />\n                        )\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: ({ areaProps }) => (\n                          <Status status={parseInt(p.status, 10)} />\n                        )\n                      },\n                      sortOrder: 20\n                    }\n                  ]}\n                />\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {pages.length === 0 && (\n          <div className=\"flex w-full justify-center mt-2\">\n            There is no page to display\n          </div>\n        )}\n        <GridPagination total={total} limit={limit} page={page} />\n      </CardContent>\n    </Card>\n  );\n}\n\nCMSPageGrid.propTypes = {\n  cmsPages: PropTypes.shape({\n    items: PropTypes.arrayOf(\n      PropTypes.shape({\n        uuid: PropTypes.string.isRequired,\n        updateApi: PropTypes.string.isRequired,\n        deleteApi: PropTypes.string.isRequired\n      })\n    ).isRequired,\n    total: PropTypes.number.isRequired,\n    currentFilters: PropTypes.arrayOf(\n      PropTypes.shape({\n        key: PropTypes.string.isRequired,\n        operation: PropTypes.string.isRequired,\n        value: PropTypes.string.isRequired\n      })\n    )\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query($filters: [FilterInput]) {\n    cmsPages (filters: $filters) {\n      items {\n        cmsPageId\n        uuid\n        name\n        status\n        content\n        editUrl\n        updateApi\n        deleteApi\n      }\n      total\n      currentFilters {\n        key\n        operation\n        value\n      }\n    }\n  }\n`;\n\nexport const variables = `\n{\n  filters: getContextValue('filtersFromUrl')\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/NewPageButton.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\n\ninterface NewPageButtonProps {\n  newPageUrl: string;\n}\n\nexport default function NewPageButton({ newPageUrl }: NewPageButtonProps) {\n  return (\n    <Button onClick={() => (window.location.href = newPageUrl)}>\n      New Page\n    </Button>\n  );\n}\n\nexport const layout = {\n  areaId: 'pageHeadingRight',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    newPageUrl: url(routeId: \"cmsPageNew\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport default function CmsPageHeading() {\n  return <PageHeading heading=\"Cms Pages\" />;\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/index.ts",
    "content": "import { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default (request: EvershopRequest, response) => {\n  setPageMetaInfo(request, {\n    title: 'Cms pages',\n    description: 'Cms pages'\n  });\n  setContextValue(\n    request,\n    'filtersFromUrl',\n    buildFilterFromUrl(request.originalUrl)\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/pages\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/rows/PageName.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface PageNameProps {\n  url: string;\n  name: string;\n}\n\nexport function PageName({ url, name }: PageNameProps) {\n  return (\n    <TableCell>\n      <div>\n        <a className=\"hover:underline font-semibold\" href={url}>\n          {name}\n        </a>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageNew/PageNewForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface CmsPageNewFormProps {\n  action: string;\n  gridUrl: string;\n}\nexport default function CmsPageNewForm({\n  action,\n  gridUrl\n}: CmsPageNewFormProps) {\n  return (\n    <Form\n      action={action}\n      method=\"POST\"\n      onSuccess={(response) => {\n        toast.success('Page created successfully!');\n        setTimeout(() => {\n          const editUrl = response.data.links.find(\n            (link) => link.rel === 'edit'\n          ).href;\n          window.location.href = editUrl;\n        }, 1500);\n      }}\n      id=\"cmsPageNewForm\"\n      submitBtn={false}\n    >\n      <div className=\"grid gap-5 grid-cols-1 w-2/3 mx-auto\">\n        <Area id=\"wideScreen\" noOuter />\n      </div>\n      <FormButtons formId=\"cmsPageNewForm\" cancelUrl={gridUrl} />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"createCmsPage\")\n    gridUrl: url(routeId: \"cmsPageGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageNew/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request, response) => {\n  setPageMetaInfo(request, {\n    title: 'Create a new cms page',\n    description: 'Create a new cms page'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/cmsPageNew/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/pages/new\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/dashboard/Layout.jsx",
    "content": "import Area from '@components/common/Area';\nimport React from 'react';\nimport './Layout.scss';\n\nexport default function DashboardLayout() {\n  return (\n    <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n      <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n        <Area id=\"leftSide\" noOuter />\n      </div>\n      <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n        <Area id=\"rightSide\" noOuter />\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/dashboard/Layout.scss",
    "content": "body.dashboard {\n  .main-content-inner {\n    max-width: 62.5rem;\n    margin: 1.875rem auto;\n  }\n  .page-heading {\n    max-width: 62.5rem;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/dashboard/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport default function DashboardPageHeading() {\n  return <PageHeading heading=\"Dashboard\" />;\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 5\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/dashboard/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request, response) => {\n  setPageMetaInfo(request, {\n    title: 'Dashboard',\n    description: 'dashboard'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/dashboard/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetEdit/WidgetEditForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport React from 'react';\n\ninterface WidgetEditFormProps {\n  action: string;\n  gridUrl: string;\n  type: {\n    code: string;\n    description: string;\n    settingComponent: string;\n    defaultSetting: any;\n  };\n}\n\nexport default function WidgetEditForm({\n  action,\n  gridUrl,\n  type\n}: WidgetEditFormProps) {\n  return (\n    <Form action={action} method=\"PATCH\" id=\"widgetEditForm\" submitBtn={false}>\n      <InputField type=\"hidden\" name=\"type\" defaultValue={type.code} />\n      <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"leftSide\" type={type} noOuter />\n        </div>\n        <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"rightSide\" type={type} noOuter />\n        </div>\n      </div>\n      <FormButtons formId=\"widgetEditForm\" cancelUrl={gridUrl} />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"updateWidget\", params: [{key: \"id\", value: getContextValue(\"widgetUuid\")}]),\n    gridUrl: url(routeId: \"widgetGrid\")\n    type: widgetType(code: getContextValue('type')) {\n      code\n      description\n      settingComponent\n      defaultSetting\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetEdit/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { getEnabledWidgets } from '../../../../../lib/widget/widgetManager.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\nimport { setPageMetaInfo } from '../../../services/pageMetaInfo.js';\n\nexport default async (request, response: EvershopResponse, next) => {\n  try {\n    const query = select();\n    query.from('widget');\n    query.andWhere('widget.uuid', '=', request.params.id);\n    const widget = await query.load(pool);\n    const enabledWidgets = getEnabledWidgets();\n    if (\n      widget === null ||\n      !enabledWidgets.find((row) => row.type === widget.type)\n    ) {\n      response.status(404);\n      next();\n    } else {\n      setContextValue(request, 'type', widget.type);\n      setContextValue(request, 'widgetId', widget.widget_id);\n      setContextValue(request, 'widgetUuid', widget.uuid);\n      setPageMetaInfo(request, {\n        title: widget.name,\n        description: widget.name\n      });\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetEdit/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/widgets/edit/:id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetEdit+widgetNew/General.tsx",
    "content": "import { InputField } from '@components/common/form/InputField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport { Card, CardContent, CardTitle } from '@components/common/ui/Card.js';\nimport React from 'react';\nimport { useFormContext, Controller, Control } from 'react-hook-form';\nimport Select from 'react-select';\nimport CreatableSelect from 'react-select/creatable';\n\nconst components = {\n  DropdownIndicator: null\n};\n\nconst createOption = (label) => ({\n  label,\n  value: label\n});\n\nconst AreaInput: React.FC<{\n  values: {\n    label: string;\n    value: string;\n  }[];\n  control: Control<any>;\n}> = ({ values, control }) => {\n  const [inputValue, setInputValue] = React.useState('');\n\n  const handleKeyDown = (event, onChange, value) => {\n    if (!inputValue) return;\n    switch (event.key) {\n      case 'Enter':\n      case 'Tab':\n        const newOption = createOption(inputValue);\n        onChange([...value, newOption]);\n        setInputValue('');\n        event.preventDefault();\n        break;\n      default:\n        break;\n    }\n  };\n\n  return (\n    <Controller\n      name=\"area\"\n      control={control}\n      defaultValue={values.map((v) => v.value)}\n      render={({ field }) => (\n        <CreatableSelect\n          components={components}\n          inputValue={inputValue}\n          isClearable\n          isMulti\n          menuIsOpen={false}\n          onChange={(newValue) => {\n            const stringArray = newValue\n              ? newValue.map((option) => option.value)\n              : [];\n            field.onChange(stringArray);\n          }}\n          onInputChange={(newValue) => setInputValue(newValue)}\n          onKeyDown={(event) =>\n            handleKeyDown(\n              event,\n              (newOptions) => {\n                const stringArray = newOptions.map((option) => option.value);\n                field.onChange(stringArray);\n              },\n              field.value\n                ? field.value.map((val) =>\n                    typeof val === 'string' ? createOption(val) : val\n                  )\n                : []\n            )\n          }\n          placeholder=\"Type area and press enter...\"\n          value={\n            field.value\n              ? field.value.map((val) =>\n                  typeof val === 'string' ? createOption(val) : val\n                )\n              : []\n          }\n        />\n      )}\n    />\n  );\n};\n\ninterface GeneralProps {\n  widget?: {\n    name?: string;\n    status?: number;\n    sortOrder?: number;\n    area?: string[];\n    route?: string[];\n  };\n  routes: Array<{\n    value: string;\n    label: string;\n    isApi: boolean;\n    isAdmin: boolean;\n    method: string[];\n  }>;\n}\n\nexport default function General({ widget, routes }: GeneralProps) {\n  const { register, control } = useFormContext();\n  const allRoutes = [\n    {\n      value: 'all',\n      label: 'All',\n      isAdmin: false,\n      isApi: false,\n      method: ['GET']\n    },\n    ...routes\n  ];\n\n  return (\n    <Card>\n      <CardContent>\n        <InputField\n          name=\"name\"\n          defaultValue={widget?.name}\n          label=\"Name\"\n          required\n          validation={{ required: 'Name is required' }}\n          placeholder=\"Name\"\n        />\n      </CardContent>\n      <CardContent className=\"pt-3 border-t border-border\">\n        <RadioGroupField\n          name=\"status\"\n          label=\"Status\"\n          defaultValue={widget?.status}\n          required\n          validation={{ required: 'Status is required' }}\n          options={[\n            { value: 0, label: 'Disabled' },\n            { value: 1, label: 'Enabled' }\n          ]}\n        />\n      </CardContent>\n      <CardContent className=\"pt-3 border-t border-border\">\n        <div\n          role=\"group\"\n          data-slot=\"field\"\n          data-orientation=\"vertical\"\n          className=\"data-[invalid=true]:text-destructive gap-3 group/field flex w-full flex-col [&amp;&gt;*]:w-full [&amp;&gt;.sr-only]:w-auto\"\n        >\n          <label\n            data-slot=\"field-label\"\n            className=\"text-sm font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed has-data-checked:bg-primary/5 has-data-checked:border-primary dark:has-data-checked:bg-primary/10 gap-1 group-data-[disabled=true]/field:opacity-50 has-[&gt;[data-slot=field]]:rounded-md has-[&gt;[data-slot=field]]:border [&amp;&gt;*]:data-[slot=field]:p-3 group/field-label peer/field-label flex w-fit leading-snug has-[&gt;[data-slot=field]]:w-full has-[&gt;[data-slot=field]]:flex-col\"\n          >\n            Areas\n          </label>\n          <AreaInput\n            control={control}\n            values={\n              widget?.area?.length\n                ? widget.area.map((a) => ({ value: a, label: a }))\n                : []\n            }\n          />\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-3 border-t border-border\">\n        <div\n          role=\"group\"\n          data-slot=\"field\"\n          data-orientation=\"vertical\"\n          className=\"data-[invalid=true]:text-destructive gap-3 group/field flex w-full flex-col [&amp;&gt;*]:w-full [&amp;&gt;.sr-only]:w-auto\"\n        >\n          <label\n            data-slot=\"field-label\"\n            className=\"text-sm font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed has-data-checked:bg-primary/5 has-data-checked:border-primary dark:has-data-checked:bg-primary/10 gap-1 group-data-[disabled=true]/field:opacity-50 has-[&gt;[data-slot=field]]:rounded-md has-[&gt;[data-slot=field]]:border [&amp;&gt;*]:data-[slot=field]:p-3 group/field-label peer/field-label flex w-fit leading-snug has-[&gt;[data-slot=field]]:w-full has-[&gt;[data-slot=field]]:flex-col\"\n          >\n            Pages\n          </label>\n          <Controller\n            name=\"route\"\n            control={control}\n            defaultValue={\n              widget?.route\n                ? widget.route // Keep as string array\n                : []\n            }\n            render={({ field }) => (\n              <Select\n                options={allRoutes.filter(\n                  (r) =>\n                    r.isApi === false &&\n                    r.isAdmin === false &&\n                    r.method.includes('GET') &&\n                    r.method.length === 1\n                )}\n                hideSelectedOptions\n                isMulti\n                aria-label=\"Select pages\"\n                onChange={(selectedOptions) => {\n                  const stringArray = selectedOptions\n                    ? selectedOptions.map((option) => option.value)\n                    : [];\n                  field.onChange(stringArray);\n                }}\n                value={allRoutes.filter((r) => field.value?.includes(r.value))}\n                className=\"page-select relative z-50\"\n              />\n            )}\n          />\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-3 border-t border-border\">\n        <NumberField\n          name=\"sort_order\"\n          label=\"Sort Order\"\n          defaultValue={widget?.sortOrder}\n          placeholder=\"Sort Order\"\n          validation={{\n            required: 'Sort order is required',\n            min: {\n              value: 0,\n              message: 'Sort order must be a positive number'\n            }\n          }}\n          required\n          helperText=\"The order in which this widget will be displayed. Lower numbers appear first.\"\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    widget(id: getContextValue(\"widgetId\", null)) {\n      name\n      status\n      sortOrder\n      area\n      route\n    }\n    routes {\n      value: id\n      label: name\n      isApi\n      isAdmin\n      method\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetEdit+widgetNew/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface WidgetEditPageHeadingProps {\n  backUrl: string;\n  widget?: {\n    name: string;\n  };\n}\nexport default function WidgetEditPageHeading({\n  backUrl,\n  widget\n}: WidgetEditPageHeadingProps) {\n  return (\n    <PageHeading\n      backUrl={backUrl}\n      heading={widget ? `Editing widget ${widget.name}` : 'Create a new widget'}\n    />\n  );\n}\n\nWidgetEditPageHeading.defaultProps = {\n  widget: null\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    page: widget(id: getContextValue(\"widgetId\", null)) {\n      name\n    }\n    backUrl: url(routeId: \"widgetGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetEdit+widgetNew/Setting.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface SettingProps {\n  type: {\n    code: string;\n    name: string;\n  };\n}\n\nexport default function Setting({ type }: SettingProps) {\n  const areaId = `widget_setting_form`;\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Widget Settings</CardTitle>\n        <CardDescription>\n          Configure the settings for the {type.name} widget.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <Area id={areaId} noOurter />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 30\n};\n\nexport const query = `\n  query Query {\n    type: widgetType(code: getContextValue('type', null)) {\n      code\n      name\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetGrid/Grid.jsx",
    "content": "import { GridPagination } from '@components/admin/grid/GridPagination';\nimport { SortableHeader } from '@components/admin/grid/header/Sortable';\nimport { Status } from '@components/admin/Status.js';\nimport Area from '@components/common/Area';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { useAlertContext } from '@components/common/modal/Alert';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport {\n  Card,\n  CardAction,\n  CardContent,\n  CardHeader\n} from '@components/common/ui/Card.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport axios from 'axios';\nimport PropTypes from 'prop-types';\nimport React, { useState } from 'react';\nimport { Name } from './rows/Name.js';\nimport { WidgetTypeRow } from './rows/WidgetTypeRow.js';\n\nfunction Actions({ widgets = [], selectedIds = [] }) {\n  const { openAlert, closeAlert } = useAlertContext();\n  const [isLoading, setIsLoading] = useState(false);\n\n  const updatePages = async (status) => {\n    setIsLoading(true);\n    const promises = widgets\n      .filter((widget) => selectedIds.includes(widget.uuid))\n      .map((widget) =>\n        axios.patch(widget.updateApi, {\n          status\n        })\n      );\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const deletePages = async () => {\n    setIsLoading(true);\n    const promises = widgets\n      .filter((widget) => selectedIds.includes(widget.uuid))\n      .map((widget) => axios.delete(widget.deleteApi));\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const actions = [\n    {\n      name: 'Disable',\n      onAction: () => {\n        openAlert({\n          heading: `Disable ${selectedIds.length} widgets`,\n          content: 'Are you sure?',\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Disable',\n            onAction: async () => {\n              await updatePages(0);\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    },\n    {\n      name: 'Enable',\n      onAction: () => {\n        openAlert({\n          heading: `Enable ${selectedIds.length} widgets`,\n          content: 'Are you sure?',\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Enable',\n            onAction: async () => {\n              await updatePages(1);\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    },\n    {\n      name: 'Delete',\n      onAction: () => {\n        openAlert({\n          heading: `Delete ${selectedIds.length} widgets`,\n          content: <div>Can&apos;t be undone</div>,\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Delete',\n            onAction: async () => {\n              await deletePages();\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    }\n  ];\n\n  return (\n    <TableRow>\n      {selectedIds.length === 0 && null}\n      {selectedIds.length > 0 && (\n        <TableCell colSpan=\"100\">\n          <ButtonGroup>\n            {actions.map((action, i) => (\n              <Button\n                key={i}\n                variant={'outline'}\n                onClick={(e) => {\n                  e.preventDefault();\n                  action.onAction();\n                }}\n              >\n                {action.name}\n              </Button>\n            ))}\n          </ButtonGroup>\n        </TableCell>\n      )}\n    </TableRow>\n  );\n}\n\nActions.propTypes = {\n  selectedIds: PropTypes.arrayOf(PropTypes.string).isRequired,\n  widgets: PropTypes.arrayOf(\n    PropTypes.shape({\n      uuid: PropTypes.string.isRequired,\n      updateApi: PropTypes.string.isRequired,\n      deleteApi: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport default function WidgetGrid({\n  widgets: { items, total, currentFilters = [] },\n  widgetTypes\n}) {\n  const page = currentFilters.find((filter) => filter.key === 'page')\n    ? parseInt(currentFilters.find((filter) => filter.key === 'page').value, 10)\n    : 1;\n  const limit = currentFilters.find((filter) => filter.key === 'limit')\n    ? parseInt(\n        currentFilters.find((filter) => filter.key === 'limit').value,\n        10\n      )\n    : 20;\n\n  const [selectedRows, setSelectedRows] = useState([]);\n\n  return (\n    <Card>\n      <CardHeader className=\"flex justify-between\">\n        <Form submitBtn={false} id=\"widgetGridFilter\">\n          <Area\n            id=\"widgetGridFilter\"\n            noOuter\n            coreComponents={[\n              {\n                component: {\n                  default: () => (\n                    <InputField\n                      name=\"name\"\n                      placeholder=\"Search\"\n                      defaultValue={\n                        currentFilters.find((f) => f.key === 'name')?.value\n                      }\n                      onKeyPress={(e) => {\n                        // If the user press enter, we should submit the form\n                        if (e.key === 'Enter') {\n                          const url = new URL(document.location);\n                          const name = e.target?.value;\n                          if (name) {\n                            url.searchParams.set('name[operation]', 'like');\n                            url.searchParams.set('name[value]', name);\n                          } else {\n                            url.searchParams.delete('name[operation]');\n                            url.searchParams.delete('name[value]');\n                          }\n                          window.location.href = url;\n                        }\n                      }}\n                    />\n                  )\n                },\n                sortOrder: 10\n              }\n            ]}\n          />\n        </Form>\n        <CardAction>\n          <Button\n            variant=\"link\"\n            onClick={() => {\n              // Just get the url and remove all query params\n              const url = new URL(document.location);\n              url.search = '';\n              window.location.href = url.href;\n            }}\n          >\n            Clear Filters\n          </Button>\n        </CardAction>\n      </CardHeader>\n      <CardContent>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>\n                <div className=\"form-field mb-0\">\n                  <Checkbox\n                    onCheckedChange={(checked) => {\n                      if (checked) {\n                        setSelectedRows(items.map((p) => p.uuid));\n                      } else {\n                        setSelectedRows([]);\n                      }\n                    }}\n                  />\n                </div>\n              </TableHead>\n              <Area\n                className=\"\"\n                id=\"widgetGridHeader\"\n                noOuter\n                coreComponents={[\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Name\"\n                          name=\"name\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 10\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Type\"\n                          name=\"type\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 15\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Status\"\n                          name=\"status\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 20\n                  }\n                ]}\n              />\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            <Actions\n              widgets={items}\n              selectedIds={selectedRows}\n              setSelectedRows={setSelectedRows}\n            />\n            {items.map((w, i) => (\n              <TableRow key={i}>\n                <TableCell style={{ width: '2rem' }}>\n                  <div className=\"form-field mb-0\">\n                    <Checkbox\n                      checked={selectedRows.includes(w.uuid)}\n                      onCheckedChange={(checked) => {\n                        if (checked) {\n                          setSelectedRows(selectedRows.concat([w.uuid]));\n                        } else {\n                          setSelectedRows(\n                            selectedRows.filter((row) => row !== w.uuid)\n                          );\n                        }\n                      }}\n                    />\n                  </div>\n                </TableCell>\n                <Area\n                  className=\"\"\n                  id=\"widgetGridRow\"\n                  row={w}\n                  noOuter\n                  coreComponents={[\n                    {\n                      component: {\n                        default: () => <Name url={w.editUrl} name={w.name} />\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <WidgetTypeRow code={w.type} types={widgetTypes} />\n                        )\n                      },\n                      sortOrder: 15\n                    },\n                    {\n                      component: {\n                        default: ({ areaProps }) => (\n                          <Status status={parseInt(w.status, 10)} />\n                        )\n                      },\n                      sortOrder: 20\n                    }\n                  ]}\n                />\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {items.length === 0 && (\n          <div className=\"flex w-full justify-center mt-2\">\n            There is no widget to display\n          </div>\n        )}\n        <GridPagination total={total} limit={limit} page={page} />\n      </CardContent>\n    </Card>\n  );\n}\n\nWidgetGrid.propTypes = {\n  widgets: PropTypes.shape({\n    items: PropTypes.arrayOf(\n      PropTypes.shape({\n        uuid: PropTypes.string.isRequired,\n        name: PropTypes.string.isRequired,\n        editUrl: PropTypes.string.isRequired,\n        updateApi: PropTypes.string.isRequired,\n        deleteApi: PropTypes.string.isRequired\n      })\n    ).isRequired,\n    total: PropTypes.number.isRequired,\n    currentFilters: PropTypes.arrayOf(\n      PropTypes.shape({\n        key: PropTypes.string.isRequired,\n        operation: PropTypes.string.isRequired,\n        value: PropTypes.string.isRequired\n      })\n    )\n  }).isRequired,\n  widgetTypes: PropTypes.arrayOf(\n    PropTypes.shape({\n      code: PropTypes.string.isRequired,\n      name: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query($filters: [FilterInput]) {\n    widgets (filters: $filters) {\n      items {\n        widgetId\n        uuid\n        name\n        area\n        route\n        type\n        status\n        editUrl\n        updateApi\n        deleteApi\n      }\n      total\n      currentFilters {\n        key\n        operation\n        value\n      }\n    }\n    widgetTypes {\n      code\n      name\n    }\n  }\n`;\n\nexport const variables = `\n{\n  filters: getContextValue('filtersFromUrl')\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetGrid/Heading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport default function WidgetGridHeading() {\n  return <PageHeading heading=\"Widgets\" />;\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetGrid/NewWidgetButton.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport React from 'react';\n\ninterface WidgetType {\n  code: string;\n  name: string;\n  description: string;\n  createWidgetUrl: string;\n}\n\nconst WidgetTypes: React.FC<{\n  types: Array<WidgetType>;\n}> = ({ types }) => {\n  return (\n    <div className=\"space-y-2\">\n      {types.map((type, index) => (\n        <Item key={index} variant=\"outline\">\n          <ItemContent>\n            <ItemTitle> {type.name}</ItemTitle>\n            <ItemDescription>{type.description}</ItemDescription>\n          </ItemContent>\n          <ItemActions>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={(e) => {\n                window.location.href = type.createWidgetUrl;\n              }}\n            >\n              Choose\n            </Button>\n          </ItemActions>\n        </Item>\n      ))}\n    </div>\n  );\n};\n\ninterface NewWidgetButtonProps {\n  widgetTypes: Array<WidgetType>;\n}\n\nexport default function NewWidgetButton({ widgetTypes }: NewWidgetButtonProps) {\n  return (\n    <Dialog>\n      <DialogTrigger>\n        <Button>New Widget</Button>\n      </DialogTrigger>\n      <DialogContent>\n        <DialogHeader>\n          <DialogTitle>New Widget</DialogTitle>\n        </DialogHeader>\n        <WidgetTypes types={widgetTypes} />\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nexport const layout = {\n  areaId: 'pageHeadingRight',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    widgetTypes {\n      code\n      name\n      description\n      createWidgetUrl\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetGrid/index.ts",
    "content": "import { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\nimport { setPageMetaInfo } from '../../../services/pageMetaInfo.js';\n\nexport default (request: EvershopRequest, response) => {\n  setPageMetaInfo(request, {\n    title: 'Widgets',\n    description: 'Widgets'\n  });\n  setContextValue(\n    request,\n    'filtersFromUrl',\n    buildFilterFromUrl(request.originalUrl)\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetGrid/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/widgets\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetGrid/rows/Name.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface NameProps {\n  url: string;\n  name: string;\n}\n\nexport function Name({ url, name }: NameProps) {\n  return (\n    <TableCell>\n      <div>\n        <a className=\"hover:underline font-semibold\" href={url}>\n          {name}\n        </a>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetGrid/rows/WidgetTypeRow.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface WidgetTypeRowProps {\n  code: string;\n  types: Array<{\n    code: string;\n    name: string;\n  }>;\n}\n\nexport function WidgetTypeRow({ code, types }: WidgetTypeRowProps) {\n  const type = types.find((t) => t.code === code);\n  if (!type) {\n    return (\n      <TableCell>\n        <div>Unknown</div>\n      </TableCell>\n    );\n  } else {\n    return (\n      <TableCell>\n        <div>{type.name}</div>\n      </TableCell>\n    );\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetNew/WidgetNewForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface WidgetNewFormProps {\n  action: string;\n  gridUrl: string;\n  type: {\n    code: string;\n    description: string;\n    settingComponent: string;\n    defaultSetting: any;\n  };\n}\n\nexport default function WidgetNewForm({\n  action,\n  gridUrl,\n  type\n}: WidgetNewFormProps) {\n  return (\n    <Form\n      action={action}\n      method=\"POST\"\n      onSuccess={(response) => {\n        toast.success('Widget created successfully!');\n        setTimeout(() => {\n          const editUrl = response.data.links.find(\n            (link) => link.rel === 'edit'\n          ).href;\n          window.location.href = editUrl;\n        }, 1500);\n      }}\n      id=\"widgetNewForm\"\n      submitBtn={false}\n    >\n      <InputField type=\"hidden\" name=\"type\" defaultValue={type.code} />\n      <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"leftSide\" type={type} noOuter />\n        </div>\n        <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n          <Area id=\"rightSide\" type={type} noOuter />\n        </div>\n      </div>\n      <FormButtons formId=\"widgetNewForm\" cancelUrl={gridUrl} />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"createWidget\")\n    gridUrl: url(routeId: \"widgetGrid\")\n    type: widgetType(code: getContextValue('type')) {\n      code\n      description\n      settingComponent\n      defaultSetting\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetNew/index.ts",
    "content": "import { setPageMetaInfo } from '../../../services/pageMetaInfo.js';\n\nexport default (request, response) => {\n  setPageMetaInfo(request, {\n    title: 'Create a new widget',\n    description: 'Create a new widget'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetNew/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/widgets/new/:type\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/admin/widgetNew/typeValidate.js",
    "content": "import { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { getEnabledWidgets } from '../../../../../lib/widget/widgetManager.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default (request, response, next) => {\n  const { type } = request.params;\n  const enabledWidgets = getEnabledWidgets();\n\n  if (!enabledWidgets.find((widget) => widget.type === type)) {\n    // Redirect to the widget grid if the widget type is not found\n    response.redirect(buildUrl('widgetGrid'));\n  } else {\n    setContextValue(request, 'type', type);\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/cmsPageView/CmsPageView.tsx",
    "content": "import { Editor } from '@components/common/Editor.js';\nimport { Row } from '@components/common/form/Editor.js';\nimport React from 'react';\n\ninterface CmsPage {\n  name: string;\n  content: Row[];\n}\n\ninterface CmsPageViewProps {\n  page: CmsPage;\n}\nexport default function CmsPageView({ page }: CmsPageViewProps) {\n  return (\n    <div className=\"page-width\">\n      <div className=\"prose prose-base max-w-none\">\n        <h1 className=\"cms__page__heading text-center text-3xl\">{page.name}</h1>\n        <Editor rows={page.content} />\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 1\n};\n\nexport const query = `\n  query Query {\n    page: currentCmsPage {\n      name\n      content\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/cmsPageView/index.ts",
    "content": "import { pool } from '../../../../../lib/postgres/connection.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\nimport { getCmsPagesBaseQuery } from '../../../services/getCmsPagesBaseQuery.js';\n\nexport default async (request, response: EvershopResponse, next) => {\n  try {\n    const query = getCmsPagesBaseQuery();\n    query\n      .where('cms_page_description.url_key', '=', request.params.url_key)\n      .and('cms_page.status', '=', 1);\n    const page = await query.load(pool);\n    if (page === null) {\n      response.status(404);\n      next();\n    } else {\n      setContextValue(request, 'pageId', page.cms_page_id);\n      setPageMetaInfo(request, {\n        title: page.meta_title || page.name,\n        description: page.meta_description || page.meta_title\n      });\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/cmsPageView/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/page/:url_key\",\n  \"name\": \"Static Page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/homepage/meta.ts",
    "content": "import { getSetting } from '../../../../setting/services/setting.js';\nimport { setPageMetaInfo } from '../../../services/pageMetaInfo.js';\n\nexport default async (request, response, next) => {\n  setPageMetaInfo(request, {\n    title: await getSetting('storeName', 'EverShop'),\n    description: await getSetting(\n      'storeDescription',\n      'The best eCommerce platform'\n    )\n  });\n  next();\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/homepage/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/\",\n  \"name\": \"Home Page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/images/images.ts",
    "content": "import { EvershopRequest } from '../../../../../types/request.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { imageProcessor } from '../../../services/imageProcessor.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const src = typeof request.query.src === 'string' ? request.query.src : '';\n  const width = parseInt(request.query.w as string, 10);\n  const height = request.query.h\n    ? parseInt(request.query.h as string, 10)\n    : undefined;\n  // Default quality to 75 if not provided or invalid\n  const quality = parseInt(request.query.q as string, 10) || 75;\n  const format = ['jpeg', 'png', 'webp', 'avif'].includes(\n    request.query.f as string\n  )\n    ? (request.query.f as 'jpeg' | 'png' | 'webp' | 'avif')\n    : 'webp';\n\n  if (\n    !src ||\n    !width ||\n    isNaN(width) ||\n    (height !== undefined && isNaN(height))\n  ) {\n    return response.status(400).send('Invalid parameters');\n  }\n\n  try {\n    const result = await imageProcessor(src, width, quality, format, height);\n    response.setHeader('Content-Type', result.metadata.contentType);\n    response.setHeader('Cache-Control', 'public, max-age=31536000, immutable');\n    // Send only the buffer data, not the entire result object\n    response.send(result.buffer);\n  } catch (error) {\n    response.status(404).send('Not Found');\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/images/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/images\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/notFound/Meta.tsx",
    "content": "import { Meta } from '@components/common/Meta.js';\nimport { Title } from '@components/common/Title.js';\nimport React from 'react';\n\nexport default function SeoMeta() {\n  return (\n    <>\n      <Title title=\"Page Not Found\" />\n      <Meta name=\"description\" content=\"Page Not Found\" />\n    </>\n  );\n}\n\nexport const layout = {\n  areaId: 'head',\n  sortOrder: 1\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/notFound/NotFound.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nfunction Name() {\n  return (\n    <h1 className=\"page-name text-center mt-6 mb-4\">\n      {_('404 Page Not Found')}\n    </h1>\n  );\n}\n\ninterface ContentProps {\n  continueShoppingUrl: string;\n}\n\nfunction Content({ continueShoppingUrl }: ContentProps) {\n  return (\n    <div className=\"page-content\">\n      <div className=\"text-center\">\n        {_('The page you requested does not exist.')}\n      </div>\n      <div className=\"mt-5 text-center\">\n        <Button\n          title={_('Continue shopping')}\n          onClick={() => (window.location.href = continueShoppingUrl)}\n          variant=\"default\"\n        >\n          {_('Continue shopping')}\n        </Button>\n      </div>\n    </div>\n  );\n}\ninterface NotFoundProps {\n  continueShoppingUrl: string;\n}\nexport default function NotFound({ continueShoppingUrl }: NotFoundProps) {\n  return (\n    <div className=\"page-width mt-6\">\n      <div className=\"pt-4\">\n        <Area\n          id=\"notfound-page\"\n          coreComponents={[\n            {\n              component: { default: Name },\n              props: {},\n              sortOrder: 10,\n              id: 'notfound-page-title'\n            },\n            {\n              component: { default: Content },\n              props: { continueShoppingUrl },\n              sortOrder: 20,\n              id: 'notfound-page-content'\n            }\n          ]}\n        />\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    continueShoppingUrl: url(routeId: \"homepage\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/notFound/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/notfound\",\n  \"name\": \"Not Found\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/staticAsset/[context]staticAssets[auth].ts",
    "content": "import staticMiddleware from '../../../../../lib/middlewares/static.js';\n\nexport default async (request, response, next) => {\n  await staticMiddleware(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/pages/frontStore/staticAsset/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/assets/*\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/CMSPageCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class CMSPageCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n  }\n\n  async init(filters = [], isAdmin = false) {\n    if (!isAdmin) {\n      this.baseQuery.andWhere('cms_page.status', '=', 't');\n    }\n    const currentFilters = [];\n\n    // Apply the filters\n    const cmsPageCollectionFilters = await getValue(\n      'cmsPageCollectionFilters',\n      []\n    );\n\n    cmsPageCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(cms_page.cms_page_id)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/CustomMemoryStorage.js",
    "content": "import concat from 'concat-stream';\n\nfunction CustomMemoryStorage(opts) {\n  this.getFilename = opts.filename;\n}\n\nCustomMemoryStorage.prototype._handleFile = function _handleFile(\n  req,\n  file,\n  cb\n) {\n  const filename = this.getFilename(file.originalname);\n  file.stream.pipe(\n    concat({ encoding: 'buffer' }, (data) => {\n      cb(null, {\n        buffer: data,\n        size: data.length,\n        filename\n      });\n    })\n  );\n};\n\nCustomMemoryStorage.prototype._removeFile = function _removeFile(\n  req,\n  file,\n  cb\n) {\n  delete file.buffer;\n  cb(null);\n};\n\nexport default function (opts) {\n  return new CustomMemoryStorage(opts);\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/WidgetCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class WidgetCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n  }\n\n  async init(filters = [], isAdmin = false) {\n    if (!isAdmin) {\n      this.baseQuery.andWhere('widget.status', '=', 't');\n    }\n    const currentFilters = [];\n\n    // Apply the filters\n    const widgetCollectionFilters = await getValue(\n      'widgetCollectionFilters',\n      []\n    );\n\n    widgetCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(widget.widget_id)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/browFiles.ts",
    "content": "import { existsSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { buildUrl } from '../../../lib/router/buildUrl.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport interface FileBrowser {\n  name: string;\n  url: string;\n}\n\n/**\n * @param {String} path the file path\n */\nexport const browFiles = async (\n  path: string\n): Promise<{ files: FileBrowser[]; folders: string[] }> => {\n  /**\n   * @type {Object} uploader\n   * @property {Function} list\n   */\n  const fileBrowser = getValueSync(\n    'fileBrowser',\n    localFileBrowser,\n    {\n      config: getConfig('system.file_storage')\n    },\n    (value) =>\n      // The value must be an object with an delete method\n      value && typeof value.list === 'function'\n  );\n\n  const results = await fileBrowser.list(path);\n  return results;\n};\n\nconst localFileBrowser = {\n  list: async (\n    path: string\n  ): Promise<{ files: FileBrowser[]; folders: string[] }> => {\n    const targetPath = join(CONSTANTS.MEDIAPATH, path);\n    if (!existsSync(targetPath)) {\n      throw new Error('Requested path does not exist');\n    } else {\n      return {\n        folders: readdirSync(targetPath, {\n          withFileTypes: true\n        })\n          .filter((dirent) => dirent.isDirectory())\n          .map((dirent) => dirent.name),\n        files: readdirSync(targetPath, {\n          withFileTypes: true\n        })\n          .filter((dirent) => dirent.isFile())\n          .map((f) => ({\n            url: buildUrl('staticAsset', [`${path}/${f.name}`]),\n            name: f.name\n          }))\n      };\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/createFolder.ts",
    "content": "import { existsSync } from 'fs';\nimport fs from 'fs/promises';\nimport path from 'path';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\n/**\n * Create a folder at the specified destination path.\n * @param {String} destinationPath the destination path\n */\nexport const createFolder = async (\n  destinationPath: string\n): Promise<string> => {\n  /**\n   * @type {Object} uploader\n   * @property {Function} create\n   */\n  const folderCreator = getValueSync(\n    'folderCreator',\n    localFolderCreator,\n    {\n      config: getConfig('system.file_storage')\n    },\n    (value) =>\n      // The value must be an object with an create method\n      value && typeof value.create === 'function'\n  );\n\n  const results = await folderCreator.create(destinationPath);\n  return results;\n};\n\nconst localFolderCreator = {\n  create: async (destinationPath: string): Promise<string> => {\n    const mediaPath = CONSTANTS.MEDIAPATH;\n    const destination = path.join(mediaPath, destinationPath);\n    // Check if the folder already exists\n    if (!existsSync(destination)) {\n      // Create the destination folder if it does not exist\n      await fs.mkdir(destination, { recursive: true });\n    }\n    return destinationPath;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/deleteFile.ts",
    "content": "import { existsSync, lstatSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\n/**\n * Delete a file at the specified path.\n * @param {String} path the file path\n */\nexport const deleteFile = async (path: string): Promise<void> => {\n  /**\n   * @type {Object} uploader\n   * @property {Function} upload\n   */\n  const fileDeleter = getValueSync(\n    'fileDeleter',\n    localFileDeleter,\n    {\n      config: getConfig('system.file_storage')\n    },\n    (value) =>\n      // The value must be an object with an delete method\n      value && typeof value.delete === 'function'\n  );\n\n  await fileDeleter.delete(path);\n};\n\nconst localFileDeleter = {\n  delete: async (path: string): Promise<void> => {\n    const mediaPath = CONSTANTS.MEDIAPATH;\n    const destination = join(mediaPath, path);\n    if (!existsSync(destination)) {\n      throw new Error('Requested path does not exist');\n    } else if (lstatSync(destination).isDirectory()) {\n      throw new Error('Requested path is not a file');\n    } else {\n      unlinkSync(destination);\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/generateFileName.js",
    "content": "import path from 'path';\n\nexport const generateFileName = (originalname) => {\n  const extension = path.extname(originalname);\n  const name = path.basename(originalname, extension);\n  // Replace special characters and white spaces with a -\n  const fileName = name.replace(/[^a-z0-9]/gi, '-').toLowerCase();\n  return `${fileName}${extension}`;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/getCmsPagesBaseQuery.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\n\nexport const getCmsPagesBaseQuery = () => {\n  const query = select().from('cms_page');\n  query\n    .leftJoin('cms_page_description')\n    .on(\n      'cms_page.cms_page_id',\n      '=',\n      'cms_page_description.cms_page_description_cms_page_id'\n    );\n\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/getMulter.js",
    "content": "import multer from 'multer';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport customMemoryStorage from './CustomMemoryStorage.js';\nimport { generateFileName } from './generateFileName.js';\n\nfunction fileFilter(request, file, cb) {\n  const allowedMimeTypes = getConfig('system.upload_allowed_mime_types', [\n    'image/jpeg',\n    'image/png',\n    'image/gif',\n    'image/webp',\n    'image/avif',\n    'image/apng'\n  ]);\n  // Only accept images\n  if (!allowedMimeTypes.includes(file.mimetype)) {\n    return cb(new Error('The file you are trying to upload is not allowed'));\n  } else {\n    return cb(null, true);\n  }\n}\n\nexport const getMulter = () => {\n  const memoryStorage = customMemoryStorage({ filename: generateFileName });\n  return multer({ storage: memoryStorage, fileFilter });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/getWidgetsBaseQuery.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\n\nexport const getWidgetsBaseQuery = () => {\n  const query = select().from('widget');\n\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/imageProcessor.ts",
    "content": "import fs from 'fs/promises';\nimport path from 'path';\nimport sharp from 'sharp';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { debug } from '../../../lib/log/logger.js';\n\nconst hasTransparency = async (imageBuffer: Buffer): Promise<boolean> => {\n  try {\n    const metadata = await sharp(imageBuffer).metadata();\n    return (\n      metadata.channels === 4 ||\n      metadata.format === 'png' ||\n      metadata.format === 'svg'\n    );\n  } catch {\n    return false;\n  }\n};\n\nconst isSvg = async (imageBuffer: Buffer): Promise<boolean> => {\n  try {\n    const metadata = await sharp(imageBuffer).metadata();\n    return metadata.format === 'svg';\n  } catch {\n    return false;\n  }\n};\n\n// We'll handle SVG detection and transparency checks directly in the imageProcessor function\n// Generate cache key using encoded src (works for both internal and external)\nconst generateCacheKey = (\n  src: string,\n  width: number,\n  quality: number,\n  format: string,\n  height?: number,\n  allowUpscale: boolean = false\n) => {\n  // Use base64 encoding to safely handle both internal paths and external URLs\n  const encodedSrc = Buffer.from(src).toString('base64url'); // URL-safe base64\n  const heightPart = height ? `-${height}h` : '';\n  const upscalePart = allowUpscale ? '-up' : '';\n  return `${encodedSrc}-${width}w${heightPart}${upscalePart}-${quality}q.${format}`;\n};\n\ninterface ProcessedImage {\n  buffer: Buffer;\n  path: string;\n  metadata: {\n    format: string;\n    width: number;\n    height: number;\n    contentType: string;\n  };\n}\n\nexport const imageProcessor = async (\n  src: string,\n  width: number,\n  quality: number,\n  format: 'jpeg' | 'png' | 'webp' | 'avif' = 'webp',\n  height?: number,\n  allowUpscale: boolean = false\n): Promise<ProcessedImage> => {\n  if (\n    !src ||\n    !width ||\n    isNaN(width) ||\n    (height !== undefined && isNaN(height))\n  ) {\n    throw new Error('Missing or invalid \"src\", \"w\", or \"h\" parameter');\n  }\n\n  // Enhanced security validation for src parameter\n  const isExternalUrl = src.startsWith('http://') || src.startsWith('https://');\n\n  if (!isExternalUrl) {\n    // Special case: Handle assets path format like \"/assets/media/image.png\" or \"/assets/image.png\"\n    if (src.startsWith('/assets/')) {\n      // Extract the filename after '/assets/'\n      const assetsPattern = /\\/assets\\/(.+)$|^assets\\/(.+)$/;\n      const matches = src.match(assetsPattern);\n\n      if (matches) {\n        const assetPath = matches[1] || matches[2]; // Use the matched group\n\n        // Try multiple possible locations in order of priority\n        const possiblePaths = [\n          `media/${assetPath}`,\n          `public/${assetPath}`\n          // For themes, we need to check each theme's public directory\n          // This is more complex and would require listing themes\n        ];\n\n        let fileExists = false;\n        for (const possiblePath of possiblePaths) {\n          try {\n            // Check if the file exists in this location\n            await fs.access(path.join(CONSTANTS.ROOTPATH, possiblePath));\n            // If we get here, the file exists in this location\n            src = possiblePath;\n            debug(`[imageProcessor] Found asset at: ${src}`);\n            fileExists = true;\n            break;\n          } catch {\n            // File doesn't exist in this location, continue to next one\n            continue;\n          }\n        }\n\n        // If file wasn't found in any of the standard locations,\n        // default to media directory and let the normal error handling take over\n        if (!fileExists) {\n          src = `media/${assetPath}`;\n          debug(`[imageProcessor] Defaulting to media path: ${src}`);\n        }\n      }\n    }\n\n    // Now continue with regular path validation\n    // Remove leading slash if present for consistent handling\n    const normalized = path.normalize(src);\n    const cleanPath = normalized.startsWith('/')\n      ? normalized.substring(1)\n      : normalized;\n\n    // Normalize to forward slashes for consistent checking across platforms\n    const normalizedPath = cleanPath.replace(/\\\\/g, '/');\n\n    // Prevent directory traversal attacks\n    if (normalizedPath.includes('..') || normalizedPath.includes('\\0')) {\n      throw new Error('Invalid characters in image path');\n    }\n\n    // Only allow specific directories from project root\n    const allowedPaths = ['media/', 'public/', 'themes/'];\n\n    const isAllowedPath = allowedPaths.some((allowedPath) => {\n      if (allowedPath === 'themes/') {\n        // Special handling for themes: must be themes/<themename>/public/\n        const themePattern = /^themes\\/[^\\/]+\\/public\\//;\n        return themePattern.test(normalizedPath);\n      }\n      return normalizedPath.startsWith(allowedPath);\n    });\n\n    if (!isAllowedPath) {\n      throw new Error(\n        'Image source must be from media/, public/, or themes/<themename>/public/ directories at project root'\n      );\n    }\n\n    // Update src to use normalized path (keep it relative to project root)\n    src = normalizedPath;\n  }\n\n  // Use the specified format directly\n  const CACHE_DIR = path.resolve(CONSTANTS.BUILDPATH, '../images');\n\n  // Ensure cache directory exists\n  try {\n    await fs.access(CACHE_DIR);\n  } catch {\n    await fs.mkdir(CACHE_DIR, { recursive: true });\n  }\n  try {\n    let sourceImageBuffer: Buffer;\n    let cachedImageKey = generateCacheKey(\n      src,\n      width,\n      quality,\n      format,\n      height,\n      allowUpscale\n    );\n\n    if (isExternalUrl) {\n      // Handle external URL\n      const cachedImagePath = path.join(CACHE_DIR, cachedImageKey);\n\n      // Check cache first for external images\n      try {\n        await fs.access(cachedImagePath);\n        // Read the buffer and metadata for the cached file\n        const buffer = await fs.readFile(cachedImagePath);\n        const metadata = await sharp(buffer).metadata();\n\n        return {\n          buffer,\n          path: cachedImagePath,\n          metadata: {\n            format: metadata.format || format,\n            width: metadata.width || width,\n            height: metadata.height || height || 0,\n            contentType: `image/${metadata.format || format}`\n          }\n        };\n      } catch {\n        // Not in cache, fetch from external URL\n      }\n\n      // Fetch image from external URL\n      try {\n        const response = await fetch(src);\n        if (!response.ok) {\n          throw new Error(\n            `Failed to fetch image: ${response.status} ${response.statusText}`\n          );\n        }\n        const arrayBuffer = await response.arrayBuffer();\n        sourceImageBuffer = Buffer.from(arrayBuffer);\n      } catch (fetchError) {\n        throw new Error(\n          `Failed to fetch external image: ${fetchError.message}`\n        );\n      }\n    } else {\n      // Handle local file\n      const cachedImagePath = path.join(CACHE_DIR, cachedImageKey);\n      const sourcePath = path.join(CONSTANTS.ROOTPATH, src);\n      try {\n        await fs.access(cachedImagePath);\n        // Read the buffer and metadata for the cached file\n        const buffer = await fs.readFile(cachedImagePath);\n        const metadata = await sharp(buffer).metadata();\n\n        return {\n          buffer,\n          path: cachedImagePath,\n          metadata: {\n            format: metadata.format || format,\n            width: metadata.width || width,\n            height: metadata.height || height || 0,\n            contentType: `image/${metadata.format || format}`\n          }\n        };\n      } catch {\n        // Not in cache, process the source image\n      }\n\n      try {\n        await fs.access(sourcePath);\n      } catch {\n        throw new Error(`Image ${sourcePath} not found`);\n      }\n\n      // Read the image file\n      sourceImageBuffer = await fs.readFile(sourcePath);\n    }\n\n    // Use the specified format directly\n    // For SVG images, we might want to consider using PNG for better quality\n    let finalFormat = format;\n\n    // Check if the image is SVG and adjust format if needed\n    if ((await isSvg(sourceImageBuffer)) && format === 'jpeg') {\n      // SVGs with transparency should use PNG or WebP instead of JPEG\n      finalFormat = 'png';\n    }\n\n    // Update cache key with the final format\n    cachedImageKey = generateCacheKey(\n      src,\n      width,\n      quality,\n      finalFormat,\n      height,\n      allowUpscale\n    );\n\n    // Process image with Sharp (common for both internal and external)\n    let sharpInstance;\n\n    // Set up resize options\n    const resizeOptions: sharp.ResizeOptions = {\n      width: width,\n      withoutEnlargement: !allowUpscale, // Don't upscale unless explicitly allowed\n      background: { r: 0, g: 0, b: 0, alpha: 0 } // Transparent background for padding\n    };\n\n    // Add height if specified\n    if (height !== undefined) {\n      resizeOptions.height = height;\n      resizeOptions.fit = 'contain'; // Use contain to preserve aspect ratio\n    }\n\n    // Check if the image is SVG for special handling\n    if (await isSvg(sourceImageBuffer)) {\n      // Configure sharp for optimal SVG rendering\n      sharpInstance = sharp(sourceImageBuffer, {\n        // Higher density for SVG to raster conversion for better quality\n        density: 300,\n        // Don't limit input pixels for SVG to preserve vector quality\n        limitInputPixels: false\n      }).resize(resizeOptions);\n    } else {\n      // Standard handling for raster images\n      sharpInstance = sharp(sourceImageBuffer).resize(resizeOptions);\n    }\n\n    // Apply format-specific optimizations\n    let optimizedImageBuffer: Buffer;\n\n    switch (finalFormat) {\n      case 'avif':\n        optimizedImageBuffer = await sharpInstance\n          .avif({\n            quality,\n            effort: 4, // Good balance between speed and compression\n            chromaSubsampling: '4:2:0' // Better compression for photos\n          })\n          .toBuffer();\n        break;\n\n      case 'webp':\n        optimizedImageBuffer = await sharpInstance\n          .webp({\n            quality,\n            effort: 4, // Balanced encoding effort\n            nearLossless: quality > 90, // Use near-lossless for high quality\n            smartSubsample: true // Better compression\n          })\n          .toBuffer();\n        break;\n\n      case 'jpeg':\n        optimizedImageBuffer = await sharpInstance\n          .jpeg({\n            quality,\n            progressive: true, // Progressive JPEG for better perceived loading\n            mozjpeg: true // Use mozjpeg encoder for better compression\n          })\n          .toBuffer();\n        break;\n\n      case 'png':\n        optimizedImageBuffer = await sharpInstance\n          .png({\n            quality,\n            compressionLevel: 9, // Maximum compression\n            adaptiveFiltering: true, // Better compression for photos\n            palette: quality < 90 // Use palette for lower quality requirements\n          })\n          .toBuffer();\n        break;\n\n      default:\n        // Fallback to WebP\n        optimizedImageBuffer = await sharpInstance.webp({ quality }).toBuffer();\n    }\n\n    // Save to cache\n    const finalCachedImagePath = path.join(CACHE_DIR, cachedImageKey);\n    await fs.writeFile(finalCachedImagePath, optimizedImageBuffer);\n\n    // Get metadata from the processed image\n    const metadata = await sharp(optimizedImageBuffer).metadata();\n\n    return {\n      buffer: optimizedImageBuffer,\n      path: finalCachedImagePath,\n      metadata: {\n        format: metadata.format || finalFormat,\n        width: metadata.width || width,\n        height: metadata.height || height || 0,\n        contentType: `image/${metadata.format || finalFormat}`\n      }\n    };\n  } catch (error) {\n    debug(error);\n    throw new Error('Image not found or processing failed');\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/index.ts",
    "content": "export * from './browFiles.js';\nexport * from './createFolder.js';\nexport * from './deleteFile.js';\nexport * from './uploadFile.js';\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/page/createPage.ts",
    "content": "import {\n  commit,\n  insert,\n  rollback,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { sanitizeRawHtml } from '../../../../lib/util/sanitizeHtml.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport pageDataSchema from './pageDataSchema.json' with { type: 'json' };\n\nfunction validatePageDataBeforeInsert(data) {\n  const ajv = getAjv();\n  pageDataSchema.required = [\n    'status',\n    'name',\n    'url_key',\n    'content',\n    'meta_title'\n  ];\n  const jsonSchema = getValueSync('createPageDataJsonSchema', pageDataSchema, {});\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function insertPageData(data, connection) {\n  const page = await insert('cms_page').given(data).execute(connection);\n  const description = await insert('cms_page_description')\n    .given(data)\n    .prime('cms_page_description_cms_page_id', page.insertId)\n    .execute(connection);\n\n  return {\n    ...description,\n    ...page\n  };\n}\n\n/**\n * Create page service. This service will create a page with all related data\n * @param {Object} data\n * @param {Object} context\n */\nasync function createPage(data, context) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const pageData = await getValue('pageDataBeforeCreate', data);\n    // Validate page data\n    validatePageDataBeforeInsert(pageData);\n// Sanitize raw HTML blocks in EditorJS content\n    if (pageData.content) {\n      sanitizeRawHtml(pageData.content);\n    }\n    // Insert page data\n    const page = await hookable(insertPageData, { ...context, connection })(\n      pageData,\n      connection\n    );\n\n    await commit(connection);\n    return page;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\nexport default async (data, context) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const page = await hookable(createPage, context)(data, context);\n  return page;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/page/deletePage.ts",
    "content": "import {\n  commit,\n  del,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\n\nasync function deletePageData(uuid, connection) {\n  await del('cms_page').where('uuid', '=', uuid).execute(connection);\n}\n/**\n * Delete page service. This service will delete a page with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nasync function deletePage(uuid, context) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const query = select().from('cms_page');\n    query\n      .leftJoin('cms_page_description')\n      .on(\n        'cms_page_description.cms_page_description_cms_page_id',\n        '=',\n        'cms_page.cms_page_id'\n      );\n\n    const page = await query.where('uuid', '=', uuid).load(connection);\n    if (!page) {\n      throw new Error('Invalid page id');\n    }\n    await hookable(deletePageData, { ...context, page, connection })(\n      uuid,\n      connection\n    );\n    await commit(connection);\n    return page;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\nexport default async (uuid, context) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const page = await hookable(deletePage, context)(uuid, context);\n  return page;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/page/pageDataSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"status\": {\n      \"type\": [\"string\", \"integer\"],\n      \"enum\": [\"0\", \"1\", 0, 1],\n      \"errorMessage\": {\n        \"type\": \"Status must be a string or number\",\n        \"enum\": \"Status must be either 0, 1, '0', or '1'\"\n      }\n    },\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Page name must be a string\",\n        \"minLength\": \"Page name is required and cannot be empty\"\n      }\n    },\n    \"content\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Content block ID must be a string\"\n            }\n          },\n          \"size\": {\n            \"type\": \"number\",\n            \"errorMessage\": {\n              \"type\": \"Content block size must be a number\"\n            }\n          },\n          \"columns\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"id\": {\n                  \"type\": \"string\",\n                  \"errorMessage\": {\n                    \"type\": \"Column ID must be a string\"\n                  }\n                },\n                \"size\": {\n                  \"type\": \"number\",\n                  \"errorMessage\": {\n                    \"type\": \"Column size must be a number\"\n                  }\n                },\n                \"data\": {\n                  \"type\": \"object\",\n                  \"errorMessage\": {\n                    \"type\": \"Column data must be an object\"\n                  }\n                }\n              },\n              \"required\": [\"id\", \"size\", \"data\"],\n              \"errorMessage\": {\n                \"required\": {\n                  \"id\": \"Column ID is required\",\n                  \"size\": \"Column size is required\",\n                  \"data\": \"Column data is required\"\n                }\n              }\n            },\n            \"errorMessage\": {\n              \"type\": \"Columns must be an array\"\n            }\n          }\n        },\n        \"required\": [\"id\", \"size\", \"columns\"],\n        \"errorMessage\": {\n          \"required\": {\n            \"id\": \"Content block ID is required\",\n            \"size\": \"Content block size is required\",\n            \"columns\": \"Content block columns are required\"\n          }\n        }\n      },\n      \"default\": [],\n      \"errorMessage\": {\n        \"type\": \"Content must be an array\"\n      }\n    },\n    \"url_key\": {\n      \"type\": \"string\",\n      \"pattern\": \"^[a-z0-9]+(?:-[a-z0-9]+)*$\",\n      \"minLength\": 1,\n      \"maxLength\": 255,\n      \"errorMessage\": {\n        \"type\": \"URL key must be a string\",\n        \"pattern\": \"URL key must contain only lowercase letters, numbers, and hyphens (e.g., 'my-product-name')\",\n        \"minLength\": \"URL key cannot be empty\",\n        \"maxLength\": \"URL key cannot exceed 255 characters\"\n      }\n    },\n    \"meta_title\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Meta title must be a string\"\n      }\n    },\n    \"meta_description\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Meta description must be a string\"\n      }\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\n    \"status\",\n    \"name\",\n    \"content\",\n    \"url_key\",\n    \"meta_title\",\n    \"meta_description\"\n  ]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/page/updatePage.ts",
    "content": "import {\n  commit,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { sanitizeRawHtml } from '../../../../lib/util/sanitizeHtml.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport pageDataSchema from './pageDataSchema.json' with { type: 'json' };\n\nfunction validatePageDataBeforeInsert(data) {\n  const ajv = getAjv();\n  pageDataSchema.required = ['status'];\n  const jsonSchema = getValueSync('updatePageDataJsonSchema', pageDataSchema, {});\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function updatePageData(uuid, data, connection) {\n  const query = select().from('cms_page');\n  query\n    .leftJoin('cms_page_description')\n    .on(\n      'cms_page_description.cms_page_description_cms_page_id',\n      '=',\n      'cms_page.cms_page_id'\n    );\n  const page = await query.where('uuid', '=', uuid).load(connection);\n\n  if (!page) {\n    throw new Error('Requested page not found');\n  }\n  const newPage = await update('cms_page')\n    .given(data)\n    .where('uuid', '=', uuid)\n    .execute(connection);\n\n  Object.assign(page, newPage);\n  let description = {};\n  try {\n    description = await update('cms_page_description')\n      .given(data)\n      .where('cms_page_description_cms_page_id', '=', page.cms_page_id)\n      .execute(connection);\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    }\n  }\n\n  return {\n    ...page,\n    ...description\n  };\n}\n\n/**\n * Update page service. This service will update a page with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nasync function updatePage(uuid, data, context) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const pageData = await getValue('pageDataBeforeUpdate', data);\n    // Validate page data\n    validatePageDataBeforeInsert(pageData);\n    // Sanitize raw HTML blocks in EditorJS content\n    if (pageData.content) {\n      sanitizeRawHtml(pageData.content);\n    }\n\n    // Insert page data\n    const page = await hookable(updatePageData, { ...context, connection })(\n      uuid,\n      pageData,\n      connection\n    );\n\n    await commit(connection);\n    return page;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\nexport default async (uuid, data, context) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const page = await hookable(updatePage, context)(uuid, data, context);\n  return page;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/pageMetaInfo.ts",
    "content": "import { PageMetaInfo } from '../../../types/pageMeta.js';\nimport { EvershopRequest } from '../../../types/request.js';\nimport {\n  getContextValue,\n  setContextValue\n} from '../../graphql/services/contextHelper.js';\n\nexport function setPageMetaInfo(\n  request: EvershopRequest,\n  info: Partial<PageMetaInfo>\n) {\n  const current = getContextValue(request, 'pageInfo', {});\n  const newInfo = { ...current, ...info };\n  setContextValue(request, 'pageInfo', newInfo);\n}\n\nexport function getPageMetaInfo(request: EvershopRequest): PageMetaInfo {\n  return getContextValue(request, 'pageInfo', {}) as PageMetaInfo;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/registerDefaultPageCollectionFilters.js",
    "content": "import { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport async function registerDefaultPageCollectionFilters() {\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'name',\n      operation: ['eq', 'like'],\n      callback: (query, operation, value, currentFilters) => {\n        if (operation === 'eq') {\n          query.andWhere('cms_page_description.name', '=', value);\n        } else {\n          query.andWhere('cms_page_description.name', 'ilike', `%${value}%`);\n        }\n        currentFilters.push({\n          key: 'name',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'status',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere('cms_page.status', OPERATION_MAP[operation], value);\n        currentFilters.push({\n          key: 'status',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const cmsPageCollectionSortBy = getValueSync(\n          'cmsPageCollectionSortBy',\n          {\n            name: (query) => query.orderBy('cms_page_description.name'),\n            status: (query) => query.orderBy('cms_page.status')\n          }\n        );\n\n        if (cmsPageCollectionSortBy[value]) {\n          cmsPageCollectionSortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        }\n      }\n    }\n  ];\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/registerDefaultWidgetCollectionFilters.js",
    "content": "import { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport async function registerDefaultWidgetCollectionFilters() {\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'name',\n      operation: ['eq', 'like'],\n      callback: (query, operation, value, currentFilters) => {\n        if (operation === 'eq') {\n          query.andWhere('widget.name', '=', value);\n        } else {\n          query.andWhere('widget.name', 'ilike', `%${value}%`);\n        }\n        currentFilters.push({\n          key: 'name',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'status',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere('widget.status', OPERATION_MAP[operation], value);\n        currentFilters.push({\n          key: 'status',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const widgetCollectionSortBy = getValueSync('widgetCollectionSortBy', {\n          name: (query) => query.orderBy('widget.name'),\n          type: (query) => query.orderBy('widget.type'),\n          area: (query) => query.orderBy('widget.area'),\n          route: (query) => query.orderBy('widget.route'),\n          status: (query) => query.orderBy('widget.status')\n        });\n\n        if (widgetCollectionSortBy[value]) {\n          widgetCollectionSortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        }\n      }\n    }\n  ];\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/tailwind.admin.config.ts",
    "content": "/**\n * @deprecated. We are using tailwind 4 now. This module will be removed in future versions.\n */\nexport default {\n  theme: {\n    extend: {\n      colors: {\n        primary: '#008060',\n        secondary: '#111213',\n        surface: '#111213',\n        onSurface: '#111213',\n        interactive: '#2c6ecb',\n        critical: '#d72c0d',\n        warning: '#FFC453',\n        highlight: '#5BCDDA',\n        success: '#008060',\n        decorative: '#FFC96B',\n        border: '#8c9196',\n        background: '#f6f6f7fc',\n        icon: '#5c5f62',\n        divider: '#e1e3e5',\n        textSubdued: '#6d7175'\n      },\n      boxShadow: {\n        DEFAULT: '0 0 0 1px rgba(63,63,68,.05),0 1px 3px 0 rgba(63,63,68,.15)'\n      }\n    }\n  },\n  variants: {\n    extend: {\n      borderWidth: ['first', 'last'],\n      margin: ['first', 'last'],\n      padding: ['first', 'last']\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/tailwind.frontStore.config.ts",
    "content": "import plugin from '@tailwindcss/typography';\n\n/**\n * @deprecated. We are using tailwind 4 now. This module will be removed in future versions.\n */\nexport default {\n  theme: {\n    extend: {\n      colors: {\n        white: '#ffffff',\n        primary: '#008060',\n        secondary: '#111213',\n        surface: '#111213',\n        onSurface: '#111213',\n        interactive: '#2c6ecb',\n        critical: '#fa4545',\n        warning: '#FFC453',\n        highlight: '#5BCDDA',\n        success: '#008060',\n        decorative: '#FFC96B',\n        border: '#8c9196',\n        icon: '#5c5f62',\n        divider: '#e1e3e5',\n        textSubdued: '#737373',\n        button: '#008060'\n      },\n      boxShadow: {\n        DEFAULT: '0 0 0 1px rgba(63,63,68,.05),0 1px 3px 0 rgba(63,63,68,.15)'\n      }\n    }\n  },\n  variants: {\n    extend: {\n      borderWidth: ['first', 'last'],\n      margin: ['first', 'last'],\n      padding: ['first', 'last']\n    }\n  },\n  plugins: [plugin]\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/tests/unit/imageProcessor.test.js",
    "content": "/**\n * @jest-environment node\n */\nimport path from 'path';\nimport { promises as fs } from 'fs';\nimport sharp from 'sharp';\nimport { fileURLToPath } from 'url';\nimport {\n  jest,\n  describe,\n  it,\n  test,\n  expect,\n  beforeAll,\n  afterAll,\n  beforeEach,\n  afterEach\n} from '@jest/globals';\n\n// Setup helper method to check if file exists\nasync function fileExists(filePath) {\n  try {\n    await fs.access(filePath);\n    return true;\n  } catch (error) {\n    return false;\n  }\n}\n\n// Get __dirname equivalent in ESM\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Use the 'root' directory as our ROOTPATH for testing\nconst ROOTPATH = path.join(__dirname, 'root');\n\n// Create the buildpath in the same root directory so that our imageProcessor can find it\nconst BUILDPATH = path.join(ROOTPATH, 'images');\n\n// Media directory in our test root\nconst MEDIA_DIR = path.join(ROOTPATH, 'media');\n\n// Define TEMP_DIR to point to media directory as well (ensuring backward compatibility)\nconst TEMP_DIR = MEDIA_DIR;\n\n// Mock the helpers module before importing imageProcessor\nawait jest.unstable_mockModule('../../../../../lib/helpers.js', () => ({\n  CONSTANTS: {\n    ROOTPATH: ROOTPATH,\n    BUILDPATH: BUILDPATH,\n    MEDIAPATH: path.join(ROOTPATH, 'media'),\n    PUBLICPATH: path.join(ROOTPATH, 'public'),\n    THEMEPATH: path.join(ROOTPATH, 'themes')\n  }\n}));\n\n// Import imageProcessor only after mocking helpers\nconst { imageProcessor } = await import('../../imageProcessor.js');\n\n// Define test images to be created in the test root media directory\nconst testImages = {\n  // These are relative paths from ROOTPATH\n  png: 'media/test-image.png',\n  jpg: 'media/test-image.jpg',\n  svg: 'media/test-image.svg',\n  transparentSvg: 'media/transparent-svg.svg'\n};\n\ndescribe('imageProcessor', () => {\n  // Set up and tear down test environment\n  beforeAll(async () => {\n    // Test images have been created using create-test-images.cjs\n    // and are committed to the repository, so we don't need to create them here.\n\n    // Just make sure the cache directory exists for processed images\n    try {\n      await fs.mkdir(path.join(ROOTPATH, 'media/cache'), { recursive: true });\n    } catch (error) {\n      // Directory might already exist\n    }\n\n    // Create the BUILDPATH (images) directory for processed images\n    try {\n      await fs.mkdir(BUILDPATH, { recursive: true });\n    } catch (error) {\n      // Directory might already exist\n    }\n\n    // Also create the dist version of the BUILDPATH\n    const BUILDPATH = path.join(ROOTPATH, 'images');\n    try {\n      await fs.mkdir(BUILDPATH, { recursive: true });\n    } catch (error) {\n      // Directory might already exist\n    }\n  });\n\n  afterAll(async () => {\n    // No cleanup needed as the test images are permanent\n  }); // Delete any cached images before each test\n  beforeEach(async () => {\n    try {\n      // Clean media/cache directory\n      const cacheDir = path.join(ROOTPATH, 'media', 'cache');\n      const files = await fs.readdir(cacheDir);\n      for (const file of files) {\n        try {\n          await fs.unlink(path.join(cacheDir, file));\n        } catch (error) {\n          // Ignore errors for individual files\n        }\n      }\n    } catch (error) {\n      // Ignore errors if directory doesn't exist\n    }\n  });\n\n  // Basic functionality tests\n  describe('Basic functionality', () => {\n    test('Should process a PNG image successfully', async () => {\n      const result = await imageProcessor(testImages.png, 200, 80, 'png');\n      expect(result).toBeTruthy();\n      expect(typeof result).toBe('object');\n\n      // Check the returned structure\n      expect(result.buffer).toBeInstanceOf(Buffer);\n      expect(result.path).toContain('.png');\n      expect(result.metadata).toBeDefined();\n      expect(result.metadata.format).toBe('png');\n      expect(result.metadata.contentType).toBe('image/png');\n\n      // Check path properties\n      expect(result.path).toContain(ROOTPATH.replace(process.cwd(), ''));\n      expect(result.path).toContain('images');\n\n      // Check the image metadata\n      expect(result.metadata.width).toBeLessThanOrEqual(200); // Might be less if original is smaller\n    });\n\n    test('Should process a JPG image successfully', async () => {\n      const result = await imageProcessor(testImages.jpg, 300, 75, 'jpeg');\n      expect(result).toBeTruthy();\n      expect(typeof result).toBe('object');\n\n      // Check the returned structure\n      expect(result.buffer).toBeInstanceOf(Buffer);\n      expect(result.path).toContain('.jpeg');\n      expect(result.metadata).toBeDefined();\n      expect(result.metadata.format).toBe('jpeg');\n      expect(result.metadata.contentType).toBe('image/jpeg');\n\n      // Check path properties\n      expect(result.path).toContain(ROOTPATH.replace(process.cwd(), ''));\n      expect(result.path).toContain('images');\n\n      // Check the image metadata\n      expect(result.metadata.width).toBeLessThanOrEqual(300); // Might be less if original is smaller\n    });\n\n    test('Should process an SVG image successfully', async () => {\n      const result = await imageProcessor(testImages.svg, 150, 90, 'png');\n      expect(result).toBeTruthy();\n      expect(typeof result).toBe('object');\n\n      // Check the returned structure\n      expect(result.buffer).toBeInstanceOf(Buffer);\n      expect(result.path).toContain('.png'); // SVGs convert to PNG\n      expect(result.metadata).toBeDefined();\n      expect(result.metadata.format).toBe('png');\n      expect(result.metadata.contentType).toBe('image/png');\n\n      // Check path properties\n      expect(result.path).toContain(ROOTPATH.replace(process.cwd(), ''));\n      expect(result.path).toContain('images');\n    });\n\n    test('Should process transparent SVG with special handling', async () => {\n      // For SVGs, we should automatically use PNG as output format\n      const result = await imageProcessor(\n        testImages.transparentSvg,\n        200,\n        90,\n        'jpeg'\n      );\n\n      // Check the returned structure\n      expect(result).toBeTruthy();\n      expect(typeof result).toBe('object');\n      expect(result.buffer).toBeInstanceOf(Buffer);\n\n      // Ensure we got PNG even though we requested JPEG\n      expect(result.path).toContain('.png');\n      expect(result.metadata.format).toBe('png');\n      expect(result.metadata.contentType).toBe('image/png');\n\n      // Check path properties\n      expect(result.path).toContain(ROOTPATH.replace(process.cwd(), ''));\n      expect(result.path).toContain('images');\n    });\n  });\n});\n\n// Parameter validation tests\ndescribe('Parameter validation', () => {\n  test('Should throw error with missing src parameter', async () => {\n    await expect(imageProcessor(null, 200, 80)).rejects.toThrow(\n      'Missing or invalid \"src\", \"w\", or \"h\" parameter'\n    );\n  });\n\n  test('Should throw error with missing width parameter', async () => {\n    await expect(imageProcessor(testImages.jpg, null, 80)).rejects.toThrow(\n      'Missing or invalid \"src\", \"w\", or \"h\" parameter'\n    );\n  });\n\n  test('Should throw error with NaN width parameter', async () => {\n    await expect(imageProcessor(testImages.jpg, NaN, 80)).rejects.toThrow(\n      'Missing or invalid \"src\", \"w\", or \"h\" parameter'\n    );\n  });\n\n  test('Should throw error with NaN height parameter', async () => {\n    await expect(\n      imageProcessor(testImages.jpg, 200, 80, 'webp', NaN)\n    ).rejects.toThrow('Missing or invalid \"src\", \"w\", or \"h\" parameter');\n  });\n});\n\n// Path safety validation tests\ndescribe('Path safety validation', () => {\n  test('Should throw error with path traversal attempt', async () => {\n    await expect(\n      imageProcessor('../../../config/config.json', 200, 80)\n    ).rejects.toThrow('Invalid characters in image path');\n  });\n\n  test('Should throw error with disallowed path', async () => {\n    await expect(imageProcessor('config/config.json', 200, 80)).rejects.toThrow(\n      /Image source must be from media\\/, public\\//\n    );\n  });\n\n  test('Should normalize paths correctly', async () => {\n    // Create a file with double slashes to test normalization\n    const testPath = 'media//double//slash//test.jpg';\n    const normalizedPath = 'media/double/slash/test.jpg';\n\n    // Process should succeed with normalized path\n    const result = await imageProcessor(testPath, 12, 12);\n    expect(result).toBeTruthy();\n\n    // Clean up\n    await fs.unlink(path.join(ROOTPATH, normalizedPath));\n\n    // Try to remove directories (may fail if not empty, which is fine)\n    try {\n      await fs.rmdir(dirPath);\n    } catch (error) {\n      // Ignore errors\n    }\n  });\n\n  // Test for assets path handling with dynamic path resolution\n  test('Should locate asset in media directory first', async () => {\n    // Create a test image in the media directory\n    const mediaTestImagePath = 'media/asset-test.png';\n    const mediaFullPath = path.join(ROOTPATH, mediaTestImagePath);\n\n    // Ensure the test image exists in media\n    try {\n      await fs.access(mediaFullPath);\n    } catch {\n      // Create a test image if it doesn't exist\n      await sharp({\n        create: {\n          width: 100,\n          height: 100,\n          channels: 4,\n          background: { r: 255, g: 100, b: 100, alpha: 1 }\n        }\n      })\n        .png()\n        .toFile(mediaFullPath);\n    }\n\n    // Use the path with leading slash\n    const assetsPath = '/assets/asset-test.png';\n    const result = await imageProcessor(assetsPath, 75, 80, 'webp');\n\n    expect(result).toBeTruthy();\n    expect(result.metadata.width).toBe(75);\n    expect(result.metadata.format).toBe('webp');\n  });\n\n  // Test fallback to public directory when file doesn't exist in media\n  test('Should fallback to public directory when asset not in media', async () => {\n    // Create a test image only in the public directory\n    const publicTestImagePath = 'public/public-only-asset.png';\n    const publicFullPath = path.join(ROOTPATH, publicTestImagePath);\n\n    // Ensure the test image exists in public but not in media\n    try {\n      // Try to remove from media if it exists\n      await fs\n        .unlink(path.join(ROOTPATH, 'media/public-only-asset.png'))\n        .catch(() => {});\n\n      // Ensure it exists in public\n      try {\n        await fs.access(publicFullPath);\n      } catch {\n        // Create a test image if it doesn't exist\n        await sharp({\n          create: {\n            width: 100,\n            height: 100,\n            channels: 4,\n            background: { r: 100, g: 255, b: 100, alpha: 1 }\n          }\n        })\n          .png()\n          .toFile(publicFullPath);\n      }\n    } catch (error) {\n      console.error('Test setup failed:', error);\n    }\n\n    // Use the assets path format\n    const assetsPath = '/assets/public-only-asset.png';\n    const result = await imageProcessor(assetsPath, 80, 80, 'webp');\n\n    expect(result).toBeTruthy();\n    expect(result.metadata.width).toBe(80);\n    expect(result.metadata.format).toBe('webp');\n  });\n\n  // Test default behavior when asset not found in any directory\n  test('Should default to media path and throw appropriate error when asset not found', async () => {\n    // Use a non-existent asset path\n    const nonExistentPath = '/assets/non-existent-asset-' + Date.now() + '.png';\n\n    // Should try media path by default and throw the standard error\n    await expect(\n      imageProcessor(nonExistentPath, 80, 80, 'webp')\n    ).rejects.toThrow(/not found/);\n  });\n\n  // Test for regular assets path with leading slash handling\n  test('Should convert /assets path to public path', async () => {\n    // Create a test file in public directory\n    const testImagePath = 'public/assets-test.png';\n    const fullPath = path.join(ROOTPATH, testImagePath);\n\n    // Ensure the test image exists\n    try {\n      await fs.access(fullPath);\n    } catch {\n      // Create a simple test image if it doesn't exist\n      await sharp({\n        create: {\n          width: 100,\n          height: 100,\n          channels: 4,\n          background: { r: 100, g: 255, b: 100, alpha: 1 }\n        }\n      })\n        .png()\n        .toFile(fullPath);\n    }\n\n    // Use the path with leading slash - this matches the implementation\n    const assetsPath = '/assets/assets-test.png';\n    const result = await imageProcessor(assetsPath, 80, 80, 'webp');\n\n    expect(result).toBeTruthy();\n    expect(result.metadata.width).toBe(80);\n    expect(result.metadata.format).toBe('webp');\n  });\n\n  // Test for assets path with leading slash handling\n  test('Should convert /assets path to public path', async () => {\n    // Use the path with leading slash - this matches the implementation\n    const assetsPath = '/assets/assets-test.png';\n    const result = await imageProcessor(assetsPath, 80, 80, 'webp');\n\n    expect(result).toBeTruthy();\n    expect(result.metadata.width).toBe(80);\n    expect(result.metadata.format).toBe('webp');\n  });\n});\n\n// Format handling tests\ndescribe('Format handling', () => {\n  test('Should use explicitly requested format', async () => {\n    const result = await imageProcessor(testImages.png, 200, 80, 'webp');\n    console.log('Result path:', result);\n    expect(result.path.endsWith('.webp')).toBeTruthy();\n\n    // Verify the file was created with correct format\n    const metadata = await sharp(result.path).metadata();\n    expect(metadata.format).toBe('webp');\n  });\n\n  test('Should use webp as default format when none specified', async () => {\n    const result = await imageProcessor(testImages.png, 200, 80);\n    expect(result.path.endsWith('.webp')).toBeTruthy();\n\n    // Verify the file was created with correct format\n    const metadata = await sharp(result.path).metadata();\n    expect(metadata.format).toBe('webp');\n  });\n\n  test('Should handle SVG images with JPEG format correctly', async () => {\n    // Request jpeg for an SVG image, should use PNG instead\n    const result = await imageProcessor(testImages.svg, 200, 80, 'jpeg');\n    expect(result).toBeTruthy();\n    expect(result.path.endsWith('.png')).toBeTruthy(); // Should switch to PNG for SVG\n\n    // Verify the file was created with PNG format\n    const metadata = await sharp(result.path).metadata();\n    expect(metadata.format).toBe('png');\n  });\n});\n\n// Height and upscale option tests\ndescribe('Height and upscale options', () => {\n  test('Should handle both width and height correctly', async () => {\n    // Create a test image with specific dimensions for this test\n    const testImagePath = path.join(ROOTPATH, 'media/width-height-test.png');\n    await sharp({\n      create: {\n        width: 500,\n        height: 400,\n        channels: 4,\n        background: { r: 255, g: 0, b: 0, alpha: 0.5 }\n      }\n    })\n      .png()\n      .toFile(testImagePath);\n\n    // Process with both width and height\n    const relativeTestPath = path.relative(ROOTPATH, testImagePath);\n    const result = await imageProcessor(relativeTestPath, 300, 80, 'png', 200);\n\n    // Verify the result\n    expect(result).toBeTruthy();\n    const metadata = await sharp(result.path).metadata();\n\n    // Width and height should respect the contain fit\n    expect(metadata.width).toBe(300);\n    expect(metadata.height).toBe(200);\n\n    // Clean up\n    await fs.unlink(testImagePath);\n  });\n\n  test('Should respect allowUpscale=false by default', async () => {\n    // Create a small test image\n    const testImagePath = path.join(ROOTPATH, 'media/no-upscale-test.png');\n    await sharp({\n      create: {\n        width: 100,\n        height: 80,\n        channels: 4,\n        background: { r: 0, g: 255, b: 0, alpha: 1 }\n      }\n    })\n      .png()\n      .toFile(testImagePath);\n\n    // Try to upscale without allowing it (default behavior)\n    const relativeTestPath = path.relative(ROOTPATH, testImagePath);\n    const result = await imageProcessor(relativeTestPath, 2000, 80, 'png');\n\n    // Verify the result - image should not be upscaled\n    const metadata = await sharp(result.path).metadata();\n    expect(metadata.width).toBe(100); // Original width, not upscaled\n\n    // Clean up\n    await fs.unlink(testImagePath);\n  });\n\n  test('Should allow upscaling when allowUpscale=true', async () => {\n    // Create a small test image\n    const testImagePath = path.join(ROOTPATH, 'media/upscale-test.png');\n    await sharp({\n      create: {\n        width: 100,\n        height: 80,\n        channels: 4,\n        background: { r: 0, g: 0, b: 255, alpha: 1 }\n      }\n    })\n      .png()\n      .toFile(testImagePath);\n\n    // Process with upscale allowed\n    const relativeTestPath = path.relative(ROOTPATH, testImagePath);\n    const result = await imageProcessor(\n      relativeTestPath,\n      200,\n      80,\n      'png',\n      undefined,\n      true\n    );\n\n    // Verify the result - image should be upscaled\n    const metadata = await sharp(result.path).metadata();\n    expect(metadata.width).toBe(200); // Should be upscaled to requested width\n\n    // Clean up\n    await fs.unlink(testImagePath);\n  });\n\n  test('Should use contain fit when both width and height are specified', async () => {\n    // Create a rectangular test image (wider than tall)\n    const testImagePath = path.join(ROOTPATH, 'media/contain-test.png');\n    await sharp({\n      create: {\n        width: 400,\n        height: 200,\n        channels: 4,\n        background: { r: 255, g: 255, b: 0, alpha: 1 }\n      }\n    })\n      .png()\n      .toFile(testImagePath);\n\n    // Process with both dimensions specified - should use contain fit\n    const relativeTestPath = path.relative(ROOTPATH, testImagePath);\n    const result = await imageProcessor(relativeTestPath, 300, 80, 'png', 300);\n\n    // Verify the result\n    const metadata = await sharp(result.path).metadata();\n\n    // For a 400x200 image resized to fit in 300x300 with contain:\n    // It should be scaled proportionally to fit within the bounds\n    expect(metadata.width).toBe(300);\n    expect(metadata.height).toBe(300); // We respect the requested height, fill the gap with transparency\n\n    // No cleanup for test image as it's in the permanent media directory\n    // await fs.unlink(testImagePath);\n  });\n});\n\n// Cache key generation tests\ndescribe('Cache key generation', () => {\n  // Create a test image to use for all cache key tests\n  const testImagePath = path.join(ROOTPATH, 'media/cache-key-test.jpg');\n\n  beforeAll(async () => {\n    // Create a test image\n    await sharp({\n      create: {\n        width: 300,\n        height: 200,\n        channels: 3,\n        background: { r: 100, g: 100, b: 100 }\n      }\n    })\n      .jpeg()\n      .toFile(testImagePath);\n  });\n\n  afterAll(async () => {\n    // No cleanup as we're using the permanent media directory\n    // try {\n    //   await fs.unlink(testImagePath);\n    // } catch (error) {\n    //   // Ignore if already deleted\n    // }\n  });\n\n  test('Should generate different cache keys for different dimensions', async () => {\n    const relativeTestPath = path.relative(ROOTPATH, testImagePath);\n\n    const result1 = await imageProcessor(relativeTestPath, 200, 80, 'jpeg');\n    const result2 = await imageProcessor(relativeTestPath, 300, 80, 'jpeg');\n\n    expect(result1).not.toBe(result2);\n\n    // Verify both files exist\n    const existsResult1 = await fileExists(result1.path);\n    const existsResult2 = await fileExists(result2.path);\n    expect(existsResult1).toBe(true);\n    expect(existsResult2).toBe(true);\n\n    // Verify the dimensions are different\n    const metadata1 = await sharp(result1.path).metadata();\n    const metadata2 = await sharp(result2.path).metadata();\n    expect(metadata1.width).toBe(200);\n    expect(metadata2.width).toBe(300);\n  });\n\n  test('Should generate different cache keys with/without height', async () => {\n    const relativeTestPath = path.relative(ROOTPATH, testImagePath);\n\n    const result1 = await imageProcessor(relativeTestPath, 200, 80, 'jpeg');\n    const result2 = await imageProcessor(\n      relativeTestPath,\n      200,\n      80,\n      'jpeg',\n      150\n    );\n\n    expect(result1).not.toBe(result2);\n\n    // Extract the filenames to check the cache key format\n    const filename1 = path.basename(result1.path);\n    const filename2 = path.basename(result2.path);\n    expect(filename2).toContain('h');\n\n    // Verify the height parameter was applied\n    const metadata2 = await sharp(result2.path).metadata();\n    expect(metadata2.height).toBe(150);\n  });\n\n  test('Should generate different cache keys with/without allowUpscale', async () => {\n    const relativeTestPath = path.relative(ROOTPATH, testImagePath);\n\n    // Create a small test image to verify upscaling behavior\n    const smallTestPath = path.join(ROOTPATH, 'media/small-test.jpg');\n    await sharp({\n      create: {\n        width: 50,\n        height: 50,\n        channels: 3,\n        background: { r: 0, g: 0, b: 0 }\n      }\n    })\n      .jpeg()\n      .toFile(smallTestPath);\n\n    const smallRelativePath = path.relative(ROOTPATH, smallTestPath);\n\n    // Without upscaling (default)\n    const result1 = await imageProcessor(\n      smallRelativePath,\n      200,\n      80,\n      'jpeg',\n      undefined,\n      false\n    );\n    // With upscaling\n    const result2 = await imageProcessor(\n      smallRelativePath,\n      200,\n      80,\n      'jpeg',\n      undefined,\n      true\n    );\n\n    expect(result1).not.toBe(result2);\n\n    // Verify the cache keys include upscale flag\n    const filename2 = path.basename(result2.path);\n    expect(filename2).toContain('up');\n\n    // Verify upscaling was applied\n    const metadata1 = await sharp(result1.path).metadata();\n    const metadata2 = await sharp(result2.path).metadata();\n    expect(metadata1.width).toBe(50); // Not upscaled\n    expect(metadata2.width).toBe(200); // Upscaled\n\n    // No cleanup for small test image as it's in the permanent media directory\n    // await fs.unlink(smallTestPath);\n  });\n\n  test('Should include appropriate parameters in the cache key', async () => {\n    const relativeTestPath = path.relative(ROOTPATH, testImagePath);\n\n    const result = await imageProcessor(\n      relativeTestPath,\n      400,\n      90,\n      'webp',\n      300,\n      true\n    );\n\n    // Extract the filename to check cache key format\n    const filename = path.basename(result.path);\n    expect(filename).toContain('w');\n    expect(filename).toContain('h');\n    expect(filename).toContain('up');\n    expect(filename).toContain('q');\n    expect(filename).toContain('webp');\n\n    // Verify all parameters were applied\n    const metadata = await sharp(result.path).metadata();\n    expect(metadata.width).toBe(400);\n    expect(metadata.height).toBe(300);\n    expect(metadata.format).toBe('webp');\n  });\n\n  test('Should handle external URLs in cache key', async () => {\n    // Mock fetch for external URLs\n    global.fetch = jest.fn().mockResolvedValue({\n      ok: true,\n      arrayBuffer: () => sharp(testImagePath).toBuffer()\n    });\n\n    const url = 'https://example.com/image.jpg';\n    const result = await imageProcessor(url, 200, 85, 'jpeg');\n    const encodedSrc = Buffer.from(url).toString('base64url');\n    // The URL should be encoded in the result\n    expect(result).toBeTruthy();\n    expect(result.path).toContain(encodedSrc);\n\n    // Verify the file exists and has correct dimensions\n    const fileExistsResult = await fileExists(result.path);\n    expect(fileExistsResult).toBe(true);\n\n    // Clean up the global mock\n    delete global.fetch;\n  });\n});\n\n// Error handling tests\ndescribe('Error handling', () => {\n  test('Should handle non-existent local file gracefully', async () => {\n    await expect(\n      imageProcessor('media/nonexistent.jpg', 200, 80)\n    ).rejects.toThrow('Image not found');\n  });\n\n  test('Should handle external URL fetch errors gracefully', async () => {\n    global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));\n\n    await expect(\n      imageProcessor('https://example.com/image.jpg', 200, 80)\n    ).rejects.toThrow('Image not found or processing failed');\n\n    delete global.fetch;\n  });\n\n  test('Should handle unsuccessful HTTP response', async () => {\n    global.fetch = jest.fn().mockResolvedValue({\n      ok: false,\n      status: 404,\n      statusText: 'Not Found'\n    });\n\n    await expect(\n      imageProcessor('https://example.com/image.jpg', 200, 80)\n    ).rejects.toThrow('Image not found or processing failed');\n\n    delete global.fetch;\n  });\n\n  test('Should handle invalid URL format', async () => {\n    // This string is not a valid URL but doesn't contain path traversal\n    // Should be processed as a local file (which will fail with not found)\n    await expect(imageProcessor('not-a-valid-url', 200, 80)).rejects.toThrow();\n  });\n\n  test('Should reject paths outside allowed directories', async () => {\n    await expect(\n      imageProcessor('node_modules/package/file.jpg', 200, 80)\n    ).rejects.toThrow('Image source must be from');\n  });\n\n  test('Should handle corrupt image gracefully', async () => {\n    // Create a corrupt image file (not a valid image format)\n    const corruptFilePath = path.join(ROOTPATH, 'media/corrupt.jpg');\n    await fs.writeFile(corruptFilePath, 'This is not an image file');\n\n    const relativeCorruptPath = path.relative(ROOTPATH, corruptFilePath);\n\n    // Should fail to process the corrupt image\n    await expect(\n      imageProcessor(relativeCorruptPath, 200, 80)\n    ).rejects.toThrow();\n\n    // Clean up\n    await fs.unlink(corruptFilePath);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/tests/unit/root/images/.gitkeep",
    "content": ""
  },
  {
    "path": "packages/evershop/src/modules/cms/services/tests/unit/root/images/junk.txt",
    "content": ""
  },
  {
    "path": "packages/evershop/src/modules/cms/services/tests/unit/root/media/cache/.gitkeep",
    "content": ""
  },
  {
    "path": "packages/evershop/src/modules/cms/services/tests/unit/root/media/cache/junk.txt",
    "content": ""
  },
  {
    "path": "packages/evershop/src/modules/cms/services/tests/unit/root/media/images/.gitkeep",
    "content": ""
  },
  {
    "path": "packages/evershop/src/modules/cms/services/tests/unit/root/media/images/junk.txt",
    "content": ""
  },
  {
    "path": "packages/evershop/src/modules/cms/services/tests/unit/validatePath.test.js",
    "content": "import { CONSTANTS } from '../../../../../lib/helpers.js';\nimport { validatePath } from '../../validatePath.js';\n\ndescribe('Test validatePath', () => {\n  it('It should return true for valid path', async () => {\n    const gootPath = '/good/path';\n    expect(validatePath(CONSTANTS.MEDIAPATH, gootPath)).toEqual(true);\n    const goodPathAgain = 'goot/path/';\n    expect(validatePath(CONSTANTS.MEDIAPATH, goodPathAgain)).toEqual(true);\n    const oneMoreGood = '';\n    expect(validatePath(CONSTANTS.MEDIAPATH, oneMoreGood)).toEqual(true);\n    const oneMoreGood2 = 'good/.../path';\n    expect(validatePath(CONSTANTS.MEDIAPATH, oneMoreGood2)).toEqual(true);\n  });\n\n  it('It should return false for invalid path', async () => {\n    const badPath = '../bad/path';\n    expect(validatePath(CONSTANTS.MEDIAPATH, badPath)).toEqual(false);\n    const badPathAgain = '../../bad/path/';\n    expect(validatePath(CONSTANTS.MEDIAPATH, badPathAgain)).toEqual(false);\n    const oneMoreBad = '/bad/../path/';\n    expect(validatePath(CONSTANTS.MEDIAPATH, oneMoreBad)).toEqual(false);\n\n    const badPath2 = 'bad//path';\n    expect(validatePath(CONSTANTS.MEDIAPATH, badPath2)).toEqual(false);\n    const badPathAgain2 = './bad/path/';\n    expect(validatePath(CONSTANTS.MEDIAPATH, badPathAgain2)).toEqual(false);\n    const oneMoreBad2 = '/bad/./path/';\n    expect(validatePath(CONSTANTS.MEDIAPATH, oneMoreBad2)).toEqual(false);\n  });\n});\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/uploadFile.ts",
    "content": "import fs from 'fs/promises';\nimport path from 'path';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { buildUrl } from '../../../lib/router/buildUrl.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\nimport { FileBrowser } from './browFiles.js';\n\nexport interface UploadedFile extends FileBrowser {\n  mimetype: string;\n  size: number;\n  url: string;\n}\n\n/**\n * Upload files to the specified destination path.\n * @param {Array} files an array of files in the format of {name: String, data: Buffer}\n * @param {String} destinationPath the destination path\n */\nexport const uploadFile = async (\n  files: Express.Multer.File[],\n  destinationPath: string\n): Promise<UploadedFile[]> => {\n  /**\n   * @type {Object} uploader\n   * @property {Function} upload\n   */\n  const fileUploader = getValueSync(\n    'fileUploader',\n    localUploader,\n    {\n      config: getConfig('system.file_storage')\n    },\n    (value) =>\n      // The value must be an object with an upload method\n      value && typeof value.upload === 'function'\n  );\n\n  const results = await fileUploader.upload(files, destinationPath);\n  return results;\n};\n\nconst localUploader = {\n  upload: async (\n    files: Express.Multer.File[],\n    destinationPath: string\n  ): Promise<UploadedFile[]> => {\n    // Assumming the we are using MemoryStorage for multer. Now we need to write the files to disk.\n    // The files argument is an array of files from multer.\n    const mediaPath = CONSTANTS.MEDIAPATH;\n    const destination = path.join(mediaPath, destinationPath);\n    // Create the destination folder if it does not exist\n    await fs.mkdir(destination, { recursive: true });\n    // Save the files to disk asynchrously\n    const results = await Promise.all(\n      files.map((file) =>\n        fs\n          .writeFile(path.join(destination, file.filename), file.buffer)\n          .then(() => ({\n            name: file.filename,\n            mimetype: file.mimetype,\n            size: file.size,\n            url: buildUrl('staticAsset', [\n              path\n                .join(destinationPath, file.filename)\n                .split('\\\\')\n                .join('/')\n                .replace(/^\\//, '')\n            ])\n          }))\n      )\n    );\n    return results;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/validatePath.js",
    "content": "import path from 'path';\n\nexport function validatePath(root, requestedPath) {\n  if (requestedPath === '') {\n    return true;\n  }\n  const normalizedPath = path.normalize(requestedPath);\n  const joinedPath = path.join(root, normalizedPath);\n\n  const cleanPath = requestedPath.replace(/[\\/\\\\]/g, path.sep);\n  if (joinedPath.startsWith(root) && cleanPath === normalizedPath) {\n    return true;\n  }\n\n  return false;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/widget/createWidget.js",
    "content": "import {\n  commit,\n  insert,\n  rollback,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport widgetDataSchema from './widgetDataSchema.json' with { type: 'json' };\n\nfunction validateWidgetDataBeforeInsert(data) {\n  const ajv = getAjv();\n  widgetDataSchema.required = ['status', 'name', 'sort_order'];\n  const jsonSchema = getValueSync(\n    'createWidgetDataJsonSchema',\n    widgetDataSchema\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function insertWidgetData(data, connection) {\n  const widget = await insert('widget').given(data).execute(connection);\n  return widget;\n}\n\n/**\n * Create widget service. This service will create a widget with all related data\n * @param {Object} data\n * @param {Object} context\n */\nasync function createWidget(data, context) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const widgetData = await getValue('widgetDataBeforeCreate', data);\n    // Validate widget data\n    validateWidgetDataBeforeInsert(widgetData);\n\n    // Insert widget data\n    const widget = await hookable(insertWidgetData, { ...context, connection })(\n      widgetData,\n      connection\n    );\n\n    await commit(connection);\n    return widget;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\nexport default async (data, context) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const widget = await hookable(createWidget, context)(data, context);\n  return widget;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/widget/deleteWidget.js",
    "content": "import {\n  commit,\n  del,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\n\nasync function deleteWidgetData(uuid, connection) {\n  await del('widget').where('uuid', '=', uuid).execute(connection);\n}\n/**\n * Delete widget service. This service will delete a widget with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nasync function deleteWidget(uuid, context) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const query = select().from('widget');\n    const widget = await query.where('uuid', '=', uuid).load(connection);\n    if (!widget) {\n      throw new Error('Invalid widget id');\n    }\n    await hookable(deleteWidgetData, { ...context, widget, connection })(\n      uuid,\n      connection\n    );\n    await commit(connection);\n    return widget;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\nexport default async (uuid, context) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const widget = await hookable(deleteWidget, context)(uuid, context);\n  return widget;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/widget/loadWidgetInstances.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { v4 as uuidv4 } from 'uuid';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getEnabledWidgets } from '../../../../lib/widget/widgetManager.js';\n\nconst newUUID = uuidv4();\nexport async function loadWidgetInstances(request) {\n  const route = request.currentRoute;\n  if (route.isAdmin && !['widgetEdit', 'widgetNew'].includes(route.id)) {\n    return [];\n  }\n  const enabledWidgets = getEnabledWidgets();\n  const query = select().from('widget');\n  if (!route.isAdmin) {\n    query.where('status', '=', 't');\n  } else if (route.id === 'widgetEdit') {\n    const uuid = request.params.id;\n    query.where('uuid', '=', uuid);\n  } else {\n    const { type } = request.params;\n    return enabledWidgets\n      .map((widget) => ({\n        type: widget.type,\n        areaId: 'widget_setting_form',\n        uuid: newUUID,\n        sortOrder: 0,\n        settings: widget.defaultSettings || {}\n      }))\n      .filter((widget) => widget.type === type);\n  }\n\n  const node = query.andWhere(\n    'type',\n    'in',\n    enabledWidgets.map((widget) => widget.type)\n  );\n\n  if (!route.isAdmin) {\n    node.addRaw('AND', `(route @> '[\"all\"]' OR route @> '[\"${route.id}\"]')`);\n  }\n  query.orderBy('sort_order', 'asc');\n  const widgetInstances = await query.execute(pool);\n  return widgetInstances.map((widgetInstance) => ({\n    type: widgetInstance.type,\n    uuid: widgetInstance.uuid,\n    areaId: widgetInstance.area,\n    settings: widgetInstance.settings,\n    sortOrder: widgetInstance.sort_order\n  }));\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/widget/updateWidget.js",
    "content": "import {\n  commit,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport widgetDataSchema from './widgetDataSchema.json' with { type: 'json' };\n\nfunction validateWidgetDataBeforeInsert(data) {\n  const ajv = getAjv();\n  widgetDataSchema.required = ['status'];\n  const jsonSchema = getValueSync(\n    'updateWidgetDataJsonSchema',\n    widgetDataSchema\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function updateWidgetData(uuid, data, connection) {\n  const query = select().from('widget');\n  const widget = await query.where('uuid', '=', uuid).load(connection);\n\n  if (!widget) {\n    throw new Error('Requested widget not found');\n  }\n  const newWidget = await update('widget')\n    .given(data)\n    .where('uuid', '=', uuid)\n    .execute(connection);\n\n  return newWidget;\n}\n\n/**\n * Update widget service. This service will update a widget with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nasync function updateWidget(uuid, data, context) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const widgetData = await getValue('widgetDataBeforeUpdate', data);\n    // Validate widget data\n    validateWidgetDataBeforeInsert(widgetData);\n\n    // Insert widget data\n    const widget = await hookable(updateWidgetData, { ...context, connection })(\n      uuid,\n      widgetData,\n      connection\n    );\n\n    await commit(connection);\n    return widget;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\nexport default async (uuid, data, context) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const widget = await hookable(updateWidget, context)(uuid, data, context);\n  return widget;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cms/services/widget/widgetDataSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Widget name must be a string\",\n        \"minLength\": \"Widget name is required and cannot be empty\"\n      }\n    },\n    \"status\": {\n      \"type\": [\"string\", \"integer\"],\n      \"enum\": [\"0\", \"1\", 0, 1],\n      \"errorMessage\": {\n        \"type\": \"Status must be a string or number\",\n        \"enum\": \"Status must be either 0, 1, '0', or '1'\"\n      }\n    },\n    \"area\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\",\n        \"errorMessage\": {\n          \"type\": \"Area item must be a string\"\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Area must be an array\"\n      }\n    },\n    \"route\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\",\n        \"errorMessage\": {\n          \"type\": \"Route item must be a string\"\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Route must be an array\"\n      }\n    },\n    \"settings\": {\n      \"type\": \"object\",\n      \"additionalProperties\": true,\n      \"errorMessage\": {\n        \"type\": \"Settings must be an object\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cod/api/codCapturePayment/[bodyParser]capture.js",
    "content": "import { insert, select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { INVALID_PAYLOAD, OK } from '../../../../lib/util/httpStatus.js';\nimport { updatePaymentStatus } from '../../../oms/services/updatePaymentStatus.js';\n\nexport default async (request, response, next) => {\n  const { order_id } = request.body;\n\n  // Validate the order;\n  const order = await select()\n    .from('order')\n    .where('uuid', '=', order_id)\n    .and('payment_method', '=', 'cod')\n    .and('payment_status', '=', 'pending')\n    .load(pool);\n\n  if (!order) {\n    response.status(INVALID_PAYLOAD);\n    response.json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message:\n          'Requested order does not exist or is not in pending payment status'\n      }\n    });\n  } else {\n    // Update order payment status\n    await updatePaymentStatus(order.order_id, 'paid', pool);\n\n    // Add transaction data to database\n    await insert('payment_transaction')\n      .given({\n        payment_transaction_order_id: order.order_id,\n        amount: order.grand_total,\n        currency: order.currency,\n        payment_action: 'capture',\n        transaction_type: 'offline'\n      })\n      .execute(pool);\n\n    // Save order activities\n    await insert('order_activity')\n      .given({\n        order_activity_order_id: order.order_id,\n        comment: 'Customer paid using cash.',\n        customer_notified: 0 // TODO: check config of SendGrid\n      })\n      .execute(pool);\n\n    response.status(OK);\n    response.json({\n      data: {}\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cod/api/codCapturePayment/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cod/api/codCapturePayment/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"order_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"order_id\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cod/api/codCapturePayment/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/cod/captures\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cod/bootstrap.ts",
    "content": "import { emit } from '../../lib/event/emitter.js';\nimport { getConfig } from '../../lib/util/getConfig.js';\nimport { hookAfter } from '../../lib/util/hookable.js';\nimport { getSetting } from '../../modules/setting/services/setting.js';\nimport { registerPaymentMethod } from '../checkout/services/getAvailablePaymentMethods.js';\nimport {\n  CreateOrderResult,\n  SaveOrderContext\n} from '../checkout/services/orderCreator.js';\n\nexport default async () => {\n  registerPaymentMethod({\n    init: async () => ({\n      code: 'cod',\n      name: await getSetting('codDisplayName', 'Cash on Delivery')\n    }),\n    validator: async () => {\n      const codConfig = getConfig('system.cod', {}) as { status?: number };\n      let codStatus;\n      if (codConfig.status) {\n        codStatus = codConfig.status;\n      } else {\n        codStatus = await getSetting('codPaymentStatus', 0);\n      }\n      if (parseInt(codStatus, 10) === 1) {\n        return true;\n      } else {\n        return false;\n      }\n    }\n  });\n\n  hookAfter<SaveOrderContext, CreateOrderResult>(\n    'createOrderFunc',\n    async function EmitOrderPlacedEvent(order) {\n      if (order.payment_method === 'cod') {\n        await emit('order_placed', order);\n      }\n    }\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cod/graphql/types/CODSetting/CODSetting.graphql",
    "content": "extend type Setting {\n  codPaymentStatus: Int\n  codDisplayName: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/cod/graphql/types/CODSetting/CODSetting.resolvers.js",
    "content": "export default {\n  Setting: {\n    codPaymentStatus: (setting) => {\n      const codPaymentStatus = setting.find(\n        (s) => s.name === 'codPaymentStatus'\n      );\n      if (codPaymentStatus) {\n        return parseInt(codPaymentStatus.value, 10);\n      } else {\n        return 0;\n      }\n    },\n    codDisplayName: (setting) => {\n      const codDisplayName = setting.find((s) => s.name === 'codDisplayName');\n      if (codDisplayName) {\n        return codDisplayName.value;\n      } else {\n        return 'Cash On Delivery';\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/cod/pages/admin/orderEdit/CaptureButton.jsx",
    "content": "import RenderIfTrue from '@components/common/RenderIfTrue.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { CardContent } from '@components/common/ui/Card.js';\nimport axios from 'axios';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\nexport default function CaptureButton({\n  captureAPI,\n  order: { paymentStatus, uuid, paymentMethod }\n}) {\n  const [isLoading, setIsLoading] = React.useState(false);\n\n  const onAction = async () => {\n    setIsLoading(true);\n    // Use Axios to call the capture API\n    const response = await axios.post(\n      captureAPI,\n      { order_id: uuid },\n      { validateStatus: false }\n    );\n    if (!response.data.error) {\n      // Reload the page\n      window.location.reload();\n    } else {\n      toast.error(response.data.error.message);\n    }\n    setIsLoading(false);\n  };\n\n  return (\n    <RenderIfTrue\n      condition={paymentStatus.code === 'pending' && paymentMethod === 'cod'}\n    >\n      <CardContent>\n        <div className=\"flex justify-end\">\n          <Button onClick={onAction} isLoading={isLoading}>\n            Capture Payment\n          </Button>\n        </div>\n      </CardContent>\n    </RenderIfTrue>\n  );\n}\n\nCaptureButton.propTypes = {\n  captureAPI: PropTypes.string.isRequired,\n  order: PropTypes.shape({\n    paymentStatus: PropTypes.shape({\n      code: PropTypes.string.isRequired\n    }).isRequired,\n    uuid: PropTypes.string.isRequired,\n    paymentMethod: PropTypes.string.isRequired\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'orderPaymentActions',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    captureAPI: url(routeId: \"codCapturePayment\")\n    order(uuid: getContextValue(\"orderId\")) {\n      uuid\n      paymentStatus {\n        code\n      }\n      paymentMethod\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cod/pages/admin/paymentSetting/CODSetting.tsx",
    "content": "import { InputField } from '@components/common/form/InputField.js';\nimport { ToggleField } from '@components/common/form/ToggleField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface CODPaymentProps {\n  setting: {\n    codPaymentStatus: true | false | 0 | 1;\n    codDisplayName: string;\n  };\n}\nexport default function CODPayment({\n  setting: { codPaymentStatus, codDisplayName }\n}: CODPaymentProps) {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Cash On Delivery Payment</CardTitle>\n        <CardDescription>\n          Configure your Cash On Delivery payment gateway settings\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Enable?</h4>\n          </div>\n          <div className=\"col-span-2 flex justify-start\">\n            <ToggleField\n              name=\"codPaymentStatus\"\n              defaultValue={codPaymentStatus}\n              trueValue={1}\n              falseValue={0}\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Dislay Name</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <InputField\n              name=\"codDisplayName\"\n              placeholder=\"Display Name\"\n              defaultValue={codDisplayName}\n            />\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'paymentSetting',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query {\n    setting {\n      codPaymentStatus\n      codDisplayName\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/cod/pages/frontStore/checkout/CashOnDelivery.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  useCheckout,\n  useCheckoutDispatch\n} from '@components/frontStore/checkout/CheckoutContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React, { useEffect } from 'react';\nimport { toast } from 'react-toastify';\n\ninterface CashOnDeliveryMethodProps {\n  setting: {\n    codDisplayName: string;\n  };\n}\n\ninterface CODLogoProps {\n  width?: number;\n  height?: number;\n}\n\nfunction CODLogo({ width = 100, height = 30 }: CODLogoProps) {\n  return (\n    <img\n      width={width}\n      height={height}\n      src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJ4AAAAvCAYAAAASE3OrAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABKuSURBVHgB7V19cFXXcd9z75PEhywJECCDwcYhbieGQNJJXQMztpsP17Q2ePpHIR0ct3UGYtczbhKTJuBxM2PaAnY9jZ0GyHSmIbahfziBTOIY7EySGUP8n42Fk7EN4kNSJCEZJPEkJL2Pk909u+ee+yTIx8Qe3eQt83jvne/7zu/u7tn9XTAwgZy6bm0T5Ev3GrC3gLHLwZrroCpV+R3lur7vm8qyXGXBqdl33mvypScBbBMXWANVqcrvW1LAO9N855PW2oegKlV5jyXSDww6qIKuKu+PMPDIvFZBV5X3U9iBOz37zlNg7XWQZTF4Kda6K7KVdROUXXGsK7S/3Dy/UR9tJN//SOSlXMttlWURabssgo62jc49vH28j9adg8aBzqT3mL5LH9B3+czjCSZ0bN/WTHDIojKtqwThuNXKGq28QzBmOE44jwnnMfCHBNWcsbAGsiICLNqAmiXXQ27hbCgNDMHwkVa3b6yIjNsn2aWaGxeBaZzOHS9hOx2G30UDmYbpON4iHreM440cb/P9c431UIdz0TxjVA4ONFRfi30iHHsUy8uDQ7wwGpMhEii3+Jq5EONa6WuxvQfGzp7j6kjWUnsjjTMNxznF81upi3BdtTh3eSDPc+jKjVt8prVmDkx5eVZCJvQ716/7OMzc9lneFJUCbmbPlj0w9MNXIcbvkQCwZuFcmPfj//bt2tf8KwwfbcU2BtvQNZehaeMaaNr86XHjncW2xfZzUH/HTTD3qX9BcL8BnWu/zGCKGNwGZu/9CtQsmMvlBOooMKGEi3jhHGj+2kMwZeXS1HVc2H0A+nY8C6XBYe4z97H7YCq26d99EM5t3cNrL2O7Bpx7Ds5NY/PcODkDUkBnflsXYhJJlJngMP7K9es/Ds1PPcQgySMQ3t13GIaPn+TNv2bvI1Cz4kYYs2Uo4G6U8VWL30OZsvomGLUlGMPaIr4YxI85EPft+i507/g2jJ7t4fEIbDRWUc0kvgq42UV6Yd+SLftxqYzmLFA5OJNfxg4tB/6dQUfa8sK+l6D/haPcfsbGtTj+53EtZe6j2KGbYAqCbQzXSOU6A7UoSFsavySmOstuYgRZEHahLDQ9vJ6/9u3+LryzZjO0/fPj8OYtn4NeBCDJLNxQBpUA5qp1n+Dy87jpJDPXfRJGsJ7AR/VkXkkGXvgZtG/dBWe374XWWzfB4CvH3GbzRrvtp79HBRC08aVA1RAQC1Qn5WXRzDkE8BgC+a3b7se17oS3NzwKrbds4j4Nq29GUC5h8NlgrPkIyGLjVC4vytxU667LAV9bm2wYqgklG8Ajs3nj9byRJD1oqgrgwEWvTtRUbQ8+Duf2H2bAlLBNvGAOmy+Sjh17oYh+Uoz+2tQVS532oBeWkdRju0YEqUV/bWRgEI6v+QL8/K4vMsBU61jrgKhaqlCh8cZ43jLPXxZtSjL4w6Nw6WwXFEkL4mvozZMweOQY1xH4SKuqzhs9281rXLT3UdF6qm3ddRatjm9TYM2iZAR4wM43CWmQYdzIkvg4Mf5VxDIyZRdRczmxME1AdxFN8jBuaPe+Q/x9xuoVHrB9ew7weLTZ1z/9MNzUdgCW/3QPzF7/qeTULPvbsGoZ/Pm7L8HH3j0My/oOwdK+F9kkk5SMmECBAx8OGp3PmG89iXM58JCCyuFPTmvia0ITXwxMbceu52EE19qwchk0b7qbAc1Xg2+saY0N1mQy69+RZAJ4NjAv6tu4k5+h0xHUmhjqTAS1WFKDLypvFDPbi4AjkPW+8Ap/b1l/O2tE2u6x/jz84qOfgZP3fJXNNW16/ZIPwAef3gyLEYg+nIJCGrMfTXD/K6/DAL6TOS6KxixZSPwuAZ8CryzrN4iYGFeWkzVDeD3yeQzHO7bhEf68cPM9UItaW9vo+GWOB9rkZJtRyUEWBH/fAoYgSOrwpEqn1RKCxGkQA9MRLI1ktvA0emH/y1CL9dNXfpjbkyajlwqFRxqwjk6K9Uuv5+8X0fT1/eAV1lik7Qh49E4mWvVRvvUEHEMTzOEXBBGdLD/22rPcX01fOTiIjLS2sUachuEQBiJpZzD8qpe1jaC2rVRaQzjP21u+DjdsewCu/dJnZDwrZt8mMUeN1WRUMmNqKcQxLCbq2m2b0DxOd3EzfJ+PJ9DmzX/vzCtuUP1f3cztyGfqee4QdD/3InTh6xJ+J5m1eiVrwfnbNsIHD+6AORvvZiDRa0DmIClVelJBqMRpnPQaSdNqUmPw/1/msmYEMN0YarKb8QCkN0Xf/pdS/d24BjrR5PaLH8jTAvmHoW9npDzLcbwsiGxmz9ZvwsID/wkzETjLfrIYRtu7cVMXMwjJV+va8Qyb0ab1zrHv2vUd6Nj9HQ59EIhmrFoOH/nek2xuT235HziP4Q3yp+Zt3gBXIRjI1M5YvYr70udh9reSzXXBYadlYxN53MUubO0gIeYv/8KrMIQgJpB9+Ke7+EBB2nEagpCEDkQj6KuGjhqbY+PGOvHADvQ3d3Mf/gmsMtRMkmyp+njvsYiGoczBO7c9wKaRTC6BhkBH/tZba77IGi1aMBumyub244mSMwSG/MCIo//kl9FmNq1cDl0Iyk6O3XUj8Jaheb0d66azD/cGm1VIbS4Fp2vIRzMRv3vgGSNmFCQj4epOYxC6F0M/o3JgINDR/Ge+8g1ox9CNGzNRmzQOjVuD7+WOc7w2uXyui0wyZ7YNLa79dPPfZOK+obu9EIQ0bMM0NLPTOCRCaSY2ldzSvasjXpbTpJoxEjZZTj25PpT+Qn+MTNfYYJ6dfKdVEu2iaTjSdDkGR+R9Lw0ya3mNy2FwOYVLKAYXXzPHHWjQZQh5BgwmSbWVJR8YGQdiPlRw8NiNT4CcAu4glRONazKwexORBLJhakms0w4MI/zRxzDdVETAGfzlYwFIJKCjc611iVPvB6lptBKbK5tAZ+BbqaNXQh6WwaO51NhDzzI4Eu3mYBkZq7O5VJyk0yKbaEhqUUQNRsCKhFAQG7deWlfkFob1hm8w1m4yJ601J74dlef0drBJrjmLkhng0W8cicYBW+YtzkUuS2AlvBDJhmkq3Uf4ITF/9FfZn0DVSXeOetkaHkt9SgcigCgwasaDLimJApff5YH1M/BNQiuhz7RW1rYedGqenTrVkHTkNa3xN0kyfhRo9+xKpjQe//B8pzudFhNYjAZnVduRpL0fZyoNqH6w1qbiZ254m6JZeRMbmFsbjCMKlUFmmXplE7Pp53XAciyUyOVwxfSLoXTAk/48rALdqqY2qTimEWBnn52SMeFNEz/IWuO1lVHGhtd34Pl5HoZWY64VnDyqsgY0u2AZQNafUMP+/l3GjsSc88xq/hQQSruy7sDgAt9J8DcEqa5JrC6OG3kw+joT+HRZZghABoHnNZLsksbOnJgkmB+oNN3MhADsgJUa1PggBTe2JgiR2AAhAeiSWYM1AXhtZG0YKtFik6Iz6Y3gfEgFndaFgAsndFrQhJNnDIiZAl4d51+d2SGqEREkC+29XGfE3FIlsT5cSZJJULl05DjEeCImEqcdGOYQDfcP+G2U7iICJh1ext5sw3mXcBvi5xUwXhgF2imHaS160XpGj5+EqSuWTIRNlpGjxzHr4tqHdUwkHXBEUsob1wprxhFD816Lx0IMpYZUZ6UPqBbNkGQGeMQ2IX5bpVBGg0iVF/f/yDN6Ww78x4RjUNu2j/4jTL/jJmhhcmcrdKz9MmicTLXS1Dv+gnl/l6S+8eFPc1bkwu6D0Ld1Dx8S2JfExi0HtjFrhuqGt56Eq3/N3A1/95ecZamUEQRt5z2PQWFwCOZ/7SFOC76L13V+53PgCKAGmjbeBTNwLQT+M3/2T9xPT/LBGT0TkplcbUiSJiYK/dBTMddKweKrn/o8AGqDC3sOpk6gA5iZKDGVXDQfhmCUTMnfwZE6nSlOwiTloJ7aD+I4BLyGdZ+Azi27XDuKqQVULVoT8fVU+nnuvCzfMDW+YBNyJ2Vazu8/jPncFs7dTsHrmLf3EXjn1vuh48H/gkUHtyPQ1kLvngNQ7h/CgPkcT7Xq3fksxwcjcTUophdnzNxmAnjuJJp8J+6dI1xauHrT3Zi7vR/moBbpxY0EMj8i3Zg1oCyEhls0zlYKTDCBheODRs6gwUT0iTa4B8dtwbwuZUnI7JKmpD6NK5wJvtTaBoPHT6TIoacxJUckAHeiptO4A6vn3hG9HjMTBG4KXn/ktWfwJkIzipmX80deg2bMzlA2pfGzd0HPzmegBTUdkQ4uoWbs2XfYxRgtgS459WZJ42UmHBTey8rEpU3s5JSU49TV33EzZwlUSkJxp7aaYShDGlhM6pTycoVPSO/Uf7R/0JM3SfOR5qIxGyQn3Lf/0DhyKI1ZVLo6zW8cQdUG2rQk1zEqmpEkQpeCSKUd2126bA7eWESfn7n+k/z97Q3/5kmwShqoPKFnQTJDEkh/dX/UvyGyAOVuY0yhFYPGHzr4RKpfH2qKMw8+kQDLOq1J2Q8NHoduuoKD/lwQQkHzutuhAzVVbZATJrJBqQLURJkKpRNzsz07k7I61F6z1n+KP89avYLfiZhw/sjrrE2JnUIcQaJn/enBx7n+3L5DMHS2y123nGZd9sRkInUWSubCKSqRpK70iTIScvpLgakkDp0ja7oWQ3yCDaBlHFEzAhvoSUidhq2EQAgE1277nOMDolaaJtSmodaTDAaAZB06t1LrCSijeCIOaUx1C1vghoAnSOs8geZZW9AN1YUAn4mgjIWh0rb9W+5ZEZMATg9UWZPMHC5CcbE7yV3iq04cfPJ/QqGNvIAaRP07St7XmODBaBtkJC43tbShcMdF8btI6zWuWsb1v9z9PIM2FiCo/HzDo0zRp85KHgiZxwTYDuw7f9PfMuuZgEpkVE370XgUvqFDCmlGou5fQs0eS6Da5W0j/z1rkhHqe8V3gQ7Fteah00+MY6IeXTz6xoT9fTYjCNYmYznRxwbHkT8NCDmATqrumY5Zf70SGlc64F1AMKqBjmy6XwLoJImmQhquB8HUKlT3plXLPZhpU3ImofGT0PVFYlr1hvOkBMieZELjGZP+aZe//gy/xw31zJ8jObvlG6x5wpaL8bRbGMyD5ltJa7Xd81VfP23pB+BD33tCzJVr04WhjBKkEaS51f79L8OCbZuYUUxCz1/kz3Q50ydAUPmTpzeL/zjx3DoyHYzadz0PC1DzLfrSvXD8ri94NyJOJdSSMgVcLCyYySwYrOqeqDwjPl7611XTSlrj/A+OQCeGG4YxnGFs2s+qX7o41W+04hkHRwhdlmoTPrdKoqBjzYPgoQfJ9ZmJLjzNaj0l+0lLqTRWjEtzV6bZHIDw4LHz23D1+tt5LTNQ61F2RVkxqvE4fBKY14RtM7njdwVjTkxUng0iqHGHhjEhgYYPOxvht2kw1WVcjYRCrH/iX01UHET6td4G9bkAunrgyEE6uKxk1KKY5VohaNZIYp/CIaO2LM/ZJqYznNsC+FNwZRqYqE91gU9YFDKpG8uxqdVfnewa75yp234snvFiZXlGNJ7e9S5K70xh5E2rPsGlYQYXJCafi55Fjf0GR/5lvKtnmEkcziJmV5L+enLMWeMdf+pg+bODJoFAgWWZvl7mfAhToRzLIDV34mUmN4p6rnxlAig9rDAl3kaeSKoAjir81ckmZGYnAh1JZjIXatJowRbBlKPNld9c/yGb0AQR8EpYVhJmiRXKlD63QH34yS2hKrE28Voz0aKg8wZ8PiPkzhrrABzbCp+MyxGMVlN9ztdjX9KahH0CadcgOTa5w4zWkAY0ARlU/bvJDDqSPlP7rcvVZeNwYfW5CYlfcc4zFuKm9WDzppZAKFTysjB7XdrMBAcJOYsaBV7iS4GfS8BhHGD0jMPaCJxKVA2l83M93hikZ8sG/L9+4gmlJs1oBlkPq1cbsKYDvl4Y7jEAmQgY503u+ctpO7ygkcwFkB2jN5LArk0ZKwDZIOu2WqP7musNt5uh55WG0FIqnH/9oJssrHivvcrWpMiZPJzR2F+4xihoUDG2frAQgC69DmYl24S8Oo5wOsmEQPezuPnpy9WbWpvPFPAUVGGQwVm7wD1XEISqYtyOJhXe17JJjZ9LPyVoDoBi2Rx6JHjwmaBLQiiFCiKn/3c4/EUZ3ye1EEgD28ukBJ3JnzO1X7+sphOJ41JftjSeHf/Z2MqKirapPuMHMBXdzLhxJho7BIe9YvvU+uwEbey4D3BF122S4a1sTHcB4hPDYI69FV314kVTm79ihxhG/uH0/3XnJvpfV77Zct+tUJWqvAeSqymepvesPyVXlQyJydkO0nb0uQq8qrwvQqC7r+N/fRYjs7SoqmRDoigqRrVjJ1TT+XKoSlV+z4KB85E6a/umxnCi/YZ5r1aCjuRXA+AKHLRt81IAAAAASUVORK5CYII=\"\n      alt=\"Cash On Delivery\"\n      role=\"presentation\"\n    />\n  );\n}\n\nexport default function CashOnDeliveryMethod({\n  setting\n}: CashOnDeliveryMethodProps) {\n  const { checkoutSuccessUrl, orderPlaced, orderId, checkoutData } =\n    useCheckout();\n  const { registerPaymentComponent } = useCheckoutDispatch();\n\n  useEffect(() => {\n    if (orderPlaced && checkoutData.paymentMethod === 'cod') {\n      // Redirect to the checkout success page\n      window.location.href = `${checkoutSuccessUrl}/${orderId}`;\n    }\n  }, [orderPlaced, checkoutSuccessUrl, orderId]);\n\n  useEffect(() => {\n    registerPaymentComponent('cod', {\n      nameRenderer: () => (\n        <div className=\"flex items-center justify-between w-full\">\n          <span>{setting.codDisplayName}</span>\n          <CODLogo />\n        </div>\n      ),\n      formRenderer: () => (\n        <div className=\"flex justify-center text-muted-foreground\">\n          <div className=\"w-2/3 text-center py-3\">\n            {_(\n              'Conveniently pay with cash at your doorstep when your order is delivered.'\n            )}\n          </div>\n        </div>\n      ),\n      checkoutButtonRenderer: () => {\n        const { checkout } = useCheckoutDispatch();\n        const { loadingStates, orderPlaced } = useCheckout();\n        const handleClick = async (e: React.MouseEvent) => {\n          e.preventDefault();\n          try {\n            await checkout();\n          } catch (error) {\n            toast.error(_('Failed to place order. Please try again.'));\n          }\n        };\n\n        const isDisabled = loadingStates.placingOrder || orderPlaced;\n\n        return (\n          <Button\n            variant={'default'}\n            size={'xl'}\n            type=\"button\"\n            onClick={handleClick}\n            disabled={isDisabled}\n            className=\"w-full transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-primary\"\n          >\n            <span className=\"flex items-center justify-center space-x-2\">\n              {loadingStates.placingOrder ? (\n                <>\n                  <svg\n                    className=\"animate-spin -ml-1 mr-3 h-5 w-5 text-white\"\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    fill=\"none\"\n                    viewBox=\"0 0 24 24\"\n                  >\n                    <circle\n                      className=\"opacity-25\"\n                      cx=\"12\"\n                      cy=\"12\"\n                      r=\"10\"\n                      stroke=\"currentColor\"\n                      strokeWidth=\"4\"\n                    ></circle>\n                    <path\n                      className=\"opacity-75\"\n                      fill=\"currentColor\"\n                      d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n                    ></path>\n                  </svg>\n                  <span>{_('Placing Order...')}</span>\n                </>\n              ) : orderPlaced ? (\n                <>\n                  <svg\n                    className=\"w-5 h-5\"\n                    fill=\"currentColor\"\n                    viewBox=\"0 0 24 24\"\n                  >\n                    <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\" />\n                  </svg>\n                  <span>{_('Order Placed')}</span>\n                </>\n              ) : (\n                <>\n                  <span>{_('Place Order')}</span>\n                  <svg\n                    className=\"w-5 h-5\"\n                    fill=\"currentColor\"\n                    viewBox=\"0 0 24 24\"\n                  >\n                    <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\" />\n                  </svg>\n                </>\n              )}\n            </span>\n          </Button>\n        );\n      }\n    });\n  }, [registerPaymentComponent, setting.codDisplayName]);\n\n  return null;\n}\n\nexport const layout = {\n  areaId: 'checkoutForm',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    setting {\n      codDisplayName\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/createCustomer/[bodyParser]createCustomer.ts",
    "content": "import { error } from '../../../../lib/log/logger.js';\nimport { setDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport createCustomer from '../../services/customer/createCustomer.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  try {\n    const customer = await createCustomer(request.body, {\n      routeId: request.currentRoute.id\n    });\n\n    setDelegate('createCustomer', customer, request);\n    response.status(OK);\n    response.$body = {\n      data: {\n        ...customer,\n        links: [\n          {\n            rel: 'customerGrid',\n            href: buildUrl('customerGrid'),\n            action: 'GET',\n            types: ['text/xml']\n          },\n          {\n            rel: 'edit',\n            href: buildUrl('customerEdit', { id: customer.uuid }),\n            action: 'GET',\n            types: ['text/xml']\n          }\n        ]\n      }\n    };\n    next();\n  } catch (e) {\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/createCustomer/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/createCustomer/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/customers\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/createCustomerAddress/[bodyParser]createCustomerAddress.js",
    "content": "import { error } from '../../../../lib/log/logger.js';\nimport { setDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport createCustomerAddress from '../../services/customer/address/createCustomerAddress.js';\n\nexport default async (request, response, next) => {\n  try {\n    const address = await createCustomerAddress(\n      request.params.customer_id,\n      request.body\n    );\n\n    setDelegate('createCustomerAddress', address, request);\n    response.status(OK);\n    response.$body = {\n      data: {\n        ...address,\n        links: [\n          {\n            rel: 'edit',\n            href: buildUrl('updateCustomerAddress', {\n              address_id: address.uuid,\n              customer_id: request.params.customer_id\n            }),\n            action: 'UPDATE',\n            types: ['application/json']\n          },\n          {\n            rel: 'delete',\n            href: buildUrl('deleteCustomerAddress', {\n              address_id: address.uuid,\n              customer_id: request.params.customer_id\n            }),\n            action: 'DELETE',\n            types: ['application/json']\n          }\n        ]\n      }\n    };\n    next();\n  } catch (e) {\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/createCustomerAddress/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/createCustomerAddress/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/customers/:customer_id/addresses\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/deleteCustomer/deleteCustomer.js",
    "content": "import { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport deleteCustomer from '../../services/customer/deleteCustomer.js';\n\nexport default async (request, response, next) => {\n  try {\n    const customer = await deleteCustomer(request.params.id, {\n      routeId: request.currentRoute.id\n    });\n    response.status(OK);\n    response.json({\n      data: customer\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/deleteCustomer/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/customers/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/deleteCustomerAddress/deleteCustomerAddress.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport deleteCustomerAddress from '../../services/customer/address/deleteCustomerAddress.js';\n\nexport default async (request, response, next) => {\n  try {\n    const customer = await select()\n      .from('customer')\n      .where('uuid', '=', request.params.customer_id)\n      .load(pool);\n    if (!customer) {\n      response.status(INVALID_PAYLOAD);\n      return response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid customer'\n        }\n      });\n    }\n    const address = await select()\n      .from('customer_address')\n      .where('uuid', '=', request.params.address_id)\n      .and('customer_id', '=', customer.customer_id)\n      .load(pool);\n    if (!address) {\n      response.status(INVALID_PAYLOAD);\n      return response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid address'\n        }\n      });\n    }\n    const deletedAddress = await deleteCustomerAddress(\n      request.params.address_id,\n      {\n        routeId: request.currentRoute.id\n      }\n    );\n    response.status(OK);\n    return response.json({\n      data: deletedAddress\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    return response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/deleteCustomerAddress/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/customers/:customer_id/addresses/:address_id\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/getCustomerToken/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/getCustomerToken/generateToken.ts",
    "content": "import {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport {\n  generateToken,\n  generateRefreshToken,\n  TOKEN_TYPES\n} from '../../../../lib/util/jwt.js';\nimport { CurrentCustomer, EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  try {\n    const message = 'Invalid email or password';\n    const { body } = request;\n    const { email, password } = body;\n    await request.loginCustomerWithEmail(email, password, (error) => {\n      if (error) {\n        response.status(INTERNAL_SERVER_ERROR);\n        response.json({\n          error: {\n            status: INTERNAL_SERVER_ERROR,\n            message\n          }\n        });\n        return;\n      }\n    });\n    response.status(OK);\n    const accessToken = generateToken(\n      { customer: request.locals.customer as CurrentCustomer },\n      TOKEN_TYPES.CUSTOMER\n    );\n    const refreshToken = generateRefreshToken(\n      { customer: request.locals.customer as CurrentCustomer },\n      TOKEN_TYPES.CUSTOMER\n    );\n    response.json({\n      data: {\n        accessToken,\n        refreshToken\n      }\n    });\n  } catch (error) {\n    response.status(INVALID_PAYLOAD).json({\n      error: {\n        message: error.message,\n        status: INVALID_PAYLOAD\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/getCustomerToken/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"email\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Email must be a string\"\n      }\n    },\n    \"password\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Password must be a string\"\n      }\n    }\n  },\n  \"required\": [\"email\", \"password\"],\n  \"errorMessage\": {\n    \"required\": {\n      \"email\": \"Email is required\",\n      \"password\": \"Password is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/getCustomerToken/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/customer/tokens\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/global/[context]getCurrentCustomer[auth].js",
    "content": "import util from 'util';\nimport { select } from '@evershop/postgres-query-builder';\nimport sessionStorage from 'connect-pg-simple';\nimport session from 'express-session';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getFrontStoreSessionCookieName } from '../../../auth/services/getFrontStoreSessionCookieName.js';\nimport { setContextValue } from '../../../graphql/services/contextHelper.js';\n\n/**\n * This is the session based authentication middleware.\n * We do not implement session middleware on API routes,\n * instead we only load the session from the database and set the customer in the context.\n * @param {*} request\n * @param {*} response\n * @param {*} next\n * @returns\n */\nexport default async (request, response, next) => {\n  // Check if the customer is authenticated\n  // if yes we assume previous authentication middleware has set the customer in the context\n  let currentCustomer = request.getCurrentCustomer();\n  if (!currentCustomer) {\n    try {\n      // Get the sesionID cookies\n      const cookies = request.signedCookies;\n      const storeFrontSessionCookieName = getFrontStoreSessionCookieName();\n      // Check if the sessionID cookie is present\n      const sessionID = cookies[storeFrontSessionCookieName];\n      if (sessionID) {\n        const storage = new (sessionStorage(session))({\n          pool\n        });\n        // Load the session using session storage\n        const getSession = util.promisify(storage.get).bind(storage);\n        const customerSessionData = await getSession(sessionID);\n        if (customerSessionData) {\n          // Set the customer in the context\n          currentCustomer = await select()\n            .from('customer')\n            .where('customer_id', '=', customerSessionData.customerID)\n            .and('status', '=', 1)\n            .load(pool);\n\n          if (currentCustomer) {\n            // Delete the password field\n            delete currentCustomer.password;\n            request.locals.customer = currentCustomer;\n            setContextValue(request, 'customer', currentCustomer);\n          }\n        }\n        // We also keep the session id in the request.\n        // This is for anonymous customer authentication.\n        request.locals.sessionID = sessionID;\n      }\n    } catch (e) {\n      // Do nothing, the customer is not logged in\n    }\n  } else {\n    setContextValue(request, 'customer', currentCustomer);\n  }\n  next();\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/global/[context]jwtCustomerAuth[getCurrentCustomer].ts",
    "content": "import { UNAUTHORIZED } from '../../../../lib/util/httpStatus.js';\nimport {\n  decodeToken,\n  TOKEN_TYPES,\n  verifyToken\n} from '../../../../lib/util/jwt.js';\nimport { EvershopRequest } from '../../../../types/request.js';\n\nexport default (request: EvershopRequest, response, next) => {\n  try {\n    // Get token from Authorization header\n    const authHeader = request.headers.authorization;\n    if (!authHeader || !authHeader.startsWith('Bearer ')) {\n      return next();\n    }\n    const token = authHeader.substring(7);\n    const decodedWithoutVerification = decodeToken(token);\n    if (\n      !decodedWithoutVerification ||\n      decodedWithoutVerification.tokenType !== TOKEN_TYPES.CUSTOMER\n    ) {\n      // If token type is not customer, skip processing\n      return next();\n    }\n    // Verify token\n    const decoded = verifyToken(token, TOKEN_TYPES.CUSTOMER);\n    // Attach user info to request\n    request.locals = request.locals || {};\n    request.locals.customer = decoded.customer;\n\n    return next();\n  } catch (error) {\n    response.status(UNAUTHORIZED);\n    return response.json({\n      error: {\n        status: UNAUTHORIZED,\n        message: error.message || 'Invalid token'\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/refreshCustomerToken/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/refreshCustomerToken/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"refreshToken\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Refresh token must be a string\"\n      }\n    }\n  },\n  \"required\": [\"refreshToken\"],\n  \"errorMessage\": {\n    \"required\": {\n      \"refreshToken\": \"Refresh token is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/refreshCustomerToken/refreshToken.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport {\n  INVALID_PAYLOAD,\n  UNAUTHORIZED\n} from '../../../../lib/util/httpStatus.js';\nimport {\n  generateToken,\n  TOKEN_TYPES,\n  verifyRefreshToken\n} from '../../../../lib/util/jwt.js';\n\nexport default async (request, response, next) => {\n  const { refreshToken } = request.body;\n\n  if (!refreshToken) {\n    return response.status(INVALID_PAYLOAD).json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message: 'Refresh token is required'\n      }\n    });\n  }\n\n  try {\n    // Verify refresh token\n    const decoded = verifyRefreshToken(refreshToken, TOKEN_TYPES.CUSTOMER);\n    // Get fresh customer user data\n    const customer = await select()\n      .from('customer')\n      .where('customer_id', '=', decoded.customer.customer_id)\n      .and('status', '=', 1)\n      .load(pool);\n\n    if (!customer) {\n      return response.status(UNAUTHORIZED).json({\n        error: {\n          status: UNAUTHORIZED,\n          message: 'Customer not found or inactive'\n        }\n      });\n    }\n\n    // Generate new access token\n    const payload = decoded.customer;\n    const newAccessToken = generateToken(\n      {\n        customer: payload\n      },\n      TOKEN_TYPES.CUSTOMER\n    );\n    return response.json({\n      success: true,\n      data: {\n        accessToken: newAccessToken\n      }\n    });\n  } catch (error) {\n    return response.status(UNAUTHORIZED).json({\n      error: {\n        status: UNAUTHORIZED,\n        message: error.message || 'Invalid refresh token'\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/refreshCustomerToken/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/customer/token/refresh\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/resetPassword/[bodyParser]resetPassword.ts",
    "content": "import crypto from 'crypto';\nimport { insert, select } from '@evershop/postgres-query-builder';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport { OK, INTERNAL_SERVER_ERROR } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport { sendResetPasswordEmail } from '../../services/sendResetPasswordEmail.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const { body } = request;\n  const { email } = body;\n  try {\n    const config = getConfig('system.notification_emails.reset_password', {\n      enabled: true\n    });\n    if (config?.enabled === false) {\n      throw new Error('Reset password email is disabled in config.');\n    }\n    // Generate a random token using crypto module\n    const token = crypto.randomBytes(64).toString('hex');\n\n    // Hash the token\n    const hash = crypto.createHash('sha256').update(token).digest('hex');\n\n    // Check if email is existed\n    const existingCustomer = await select()\n      .from('customer')\n      .where('email', '=', email)\n      .load(pool);\n\n    if (existingCustomer) {\n      // Insert token to reset_password_token table\n      await insert('reset_password_token')\n        .given({\n          customer_id: existingCustomer.customer_id,\n          token: hash\n        })\n        .execute(pool);\n    }\n    await sendResetPasswordEmail(email, existingCustomer, token);\n    response.status(OK);\n    response.json({\n      data: {}\n    });\n\n    return;\n  } catch (e) {\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: 'Something went wrong. Please try again later.'\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/resetPassword/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/resetPassword/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    }\n  },\n  \"required\": [\"email\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/resetPassword/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/customers/reset-password\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updateCustomer/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updateCustomer/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    },\n    \"password\": {\n      \"type\": \"string\"\n    },\n    \"full_name\": {\n      \"type\": \"string\"\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updateCustomer/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/customers/:id\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updateCustomer/updateCustomer.js",
    "content": "import { select, update } from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { hashPassword } from '../../../../lib/util/passwordHelper.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  try {\n    const customer = await select()\n      .from('customer')\n      .where('uuid', '=', request.params.id)\n      .load(connection, false);\n\n    if (!customer) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid customer id'\n        }\n      });\n      return;\n    }\n\n    // Check if password is set\n    if (request.body.password) {\n      // Hash the password\n      request.body.password = hashPassword(request.body.password);\n    }\n\n    await update('customer')\n      .given({\n        ...request.body,\n        group_id: 1 // TODO: fix me\n      })\n      .where('uuid', '=', request.params.id)\n      .execute(connection, false);\n\n    // Load updated customer\n    const updatedCustomer = await select()\n      .from('customer')\n      .where('uuid', '=', request.params.id)\n      .load(connection);\n\n    response.status(OK);\n    response.$body = {\n      data: {\n        ...updatedCustomer,\n        links: [\n          {\n            rel: 'customerGrid',\n            href: buildUrl('customerGrid'),\n            action: 'GET',\n            types: ['text/xml']\n          },\n          {\n            rel: 'edit',\n            href: buildUrl('customerEdit', { id: customer.uuid }),\n            action: 'GET',\n            types: ['text/xml']\n          }\n        ]\n      }\n    };\n    next();\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updateCustomerAddress/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updateCustomerAddress/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    },\n    \"password\": {\n      \"type\": \"string\"\n    },\n    \"full_name\": {\n      \"type\": \"string\"\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updateCustomerAddress/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/customers/:customer_id/addresses/:address_id\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updateCustomerAddress/updateCustomerAddress.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport updateCustomerAddress from '../../services/customer/address/updateCustomerAddress.js';\n\nexport default async (request, response, next) => {\n  try {\n    const customer = await select()\n      .from('customer')\n      .where('uuid', '=', request.params.customer_id)\n      .load(pool);\n    if (!customer) {\n      response.status(INVALID_PAYLOAD);\n      return response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid customer'\n        }\n      });\n    }\n    const address = await select()\n      .from('customer_address')\n      .where('uuid', '=', request.params.address_id)\n      .and('customer_id', '=', customer.customer_id)\n      .load(pool);\n    if (!address) {\n      response.status(INVALID_PAYLOAD);\n      return response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid address'\n        }\n      });\n    }\n    const newAddress = await updateCustomerAddress(\n      request.params.address_id,\n      request.body\n    );\n    response.status(OK);\n    response.$body = {\n      data: {\n        ...newAddress,\n        links: [\n          {\n            rel: 'edit',\n            href: buildUrl('updateCustomerAddress', {\n              address_id: address.uuid,\n              customer_id: request.params.customer_id\n            }),\n            action: 'UPDATE',\n            types: ['application/json']\n          },\n          {\n            rel: 'delete',\n            href: buildUrl('deleteCustomerAddress', {\n              address_id: address.uuid,\n              customer_id: request.params.customer_id\n            }),\n            action: 'DELETE',\n            types: ['application/json']\n          }\n        ]\n      }\n    };\n    return next();\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    return response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updatePassword/[bodyParser]updatePassword.js",
    "content": "import crypto from 'crypto';\nimport { select, del } from '@evershop/postgres-query-builder';\nimport dayjs from 'dayjs';\nimport timezone from 'dayjs/plugin/timezone.js';\nimport utc from 'dayjs/plugin/utc.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport {\n  OK,\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD\n} from '../../../../lib/util/httpStatus.js';\nimport updatePassword from '../../services/customer/updatePassword.js';\n\nexport default async (request, response, next) => {\n  const { body } = request;\n\n  try {\n    // Generate a random token using crypto module\n    const { token, password } = body;\n\n    // Hash the token\n    const hash = crypto.createHash('sha256').update(token).digest('hex');\n\n    // Check if token is existed and created not more than 48 hours, created_at is timestamp with time zone UTC\n    const resetTokenLifetime = getConfig(\n      'resetPasswordTokenLifetime',\n      48 * 60 * 60 * 1000\n    );\n\n    // Convert Date object to mysql timestamp\n    dayjs.extend(utc);\n    dayjs.extend(timezone);\n    const timezoneConfig = getConfig('shop.timezone', 'UTC');\n    const now = dayjs\n      .tz(new Date(Date.now() - resetTokenLifetime), timezoneConfig)\n      .format('YYYY-MM-DD HH:mm:ss');\n\n    const existingToken = await select()\n      .from('reset_password_token')\n      .where('token', '=', hash)\n      .and('created_at', '>=', now)\n      .load(pool);\n\n    if (!existingToken) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Token is invalid or expired'\n        }\n      });\n      return;\n    }\n\n    await updatePassword(existingToken.customer_id, password, {\n      routeId: request.currentRoute.id\n    });\n\n    // Delete the token\n    await del('reset_password_token')\n      .where(\n        'reset_password_token_id',\n        '=',\n        existingToken.reset_password_token_id\n      )\n      .execute(pool);\n\n    response.status(OK);\n    response.$body = {};\n    next();\n  } catch (e) {\n    error(e);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updatePassword/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updatePassword/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"password\": {\n      \"type\": \"string\"\n    },\n    \"token\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"password\", \"token\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/api/updatePassword/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/customers/password\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/bootstrap.ts",
    "content": "import { request } from 'express';\nimport { defaultPaginationFilters } from '../../lib/util/defaultPaginationFilters.js';\nimport { hookable } from '../../lib/util/hookable.js';\nimport { addProcessor } from '../../lib/util/registry.js';\nimport type { EvershopRequest } from '../../types/request.js';\nimport loginCustomerWithEmail from './services/customer/loginCustomerWithEmail.js';\nimport logoutCustomer from './services/customer/logoutCustomer.js';\nimport { registerDefaultCustomerCollectionFilters } from './services/registerDefaultCustomerCollectionFilters.js';\nimport { registerDefaultCustomerGroupCollectionFilters } from './services/registerDefaultCustomerGroupCollectionFilters.js';\n\nexport default () => {\n  addProcessor('cartFields', (fields: any[]) => {\n    fields.push({\n      key: 'customer_id',\n      resolvers: [\n        async function resolver() {\n          const triggeredField = this.getTriggeredField();\n          const requestedValue = this.getRequestedValue();\n          return triggeredField === 'customer_id'\n            ? requestedValue\n            : this.getData('customer_id');\n        }\n      ]\n    });\n    fields.push({\n      key: 'customer_group_id',\n      resolvers: [\n        async function resolver() {\n          const triggeredField = this.getTriggeredField();\n          const requestedValue = this.getRequestedValue();\n          return triggeredField === 'customer_group_id'\n            ? requestedValue\n            : this.getData('customer_group_id');\n        }\n      ]\n    });\n    fields.push({\n      key: 'customer_email',\n      resolvers: [\n        async function resolver() {\n          const triggeredField = this.getTriggeredField();\n          const requestedValue = this.getRequestedValue();\n          return triggeredField === 'customer_email'\n            ? requestedValue\n            : this.getData('customer_email');\n        }\n      ]\n    });\n    fields.push({\n      key: 'customer_full_name',\n      resolvers: [\n        async function resolver() {\n          const triggeredField = this.getTriggeredField();\n          const requestedValue = this.getRequestedValue();\n          return triggeredField === 'customer_full_name'\n            ? requestedValue\n            : this.getData('customer_full_name');\n        }\n      ]\n    });\n    return fields;\n  });\n\n  /**\n   * This function will login the customer with email and password\n   * @param {*} email\n   * @param {*} password\n   * @param {*} callback\n   */\n  (request as EvershopRequest).loginCustomerWithEmail = async function login(\n    email,\n    password,\n    callback\n  ) {\n    await hookable(loginCustomerWithEmail.bind(this))(email, password);\n    if (this.session) {\n      this.session.save(callback);\n    }\n  };\n\n  (request as EvershopRequest).logoutCustomer = function logout(callback) {\n    hookable(logoutCustomer.bind(this))();\n    if (this.session) {\n      this.session.save(callback);\n    }\n  };\n\n  (request as EvershopRequest).isCustomerLoggedIn =\n    function isCustomerLoggedIn() {\n      return !!this.session?.customerID;\n    };\n\n  (request as EvershopRequest).getCurrentCustomer =\n    function getCurrentCustomer() {\n      return this.locals?.customer;\n    };\n\n  // Reigtering the default filters for customer collection\n  addProcessor(\n    'customerCollectionFilters',\n    registerDefaultCustomerCollectionFilters,\n    1\n  );\n  addProcessor(\n    'customerCollectionFilters',\n    (filters: any[]) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n\n  // Reigtering the default filters for customer group collection\n  addProcessor(\n    'customerGroupCollectionFilters',\n    registerDefaultCustomerGroupCollectionFilters,\n    1\n  );\n  addProcessor(\n    'customerGroupCollectionFilters',\n    (filters: any[]) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/graphql/types/Customer/Customer.admin.graphql",
    "content": "extend type Customer {\n  editUrl: String!\n  updateApi: String!\n  deleteApi: String!\n}\n\n\"\"\"\nReturn a collection of customers\n\"\"\"\ntype CustomerCollection {\n  items: [Customer]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\nextend type Query {\n  customer(id: String): Customer\n  customers(filters: [FilterInput]): CustomerCollection\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/graphql/types/Customer/Customer.admin.resolvers.js",
    "content": "import { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { CustomerCollection } from '../../../services/CustomerCollection.js';\nimport { getCustomersBaseQuery } from '../../../services/getCustomersBaseQuery.js';\n\nexport default {\n  Query: {\n    customer: async (root, { id }, { pool }) => {\n      const query = getCustomersBaseQuery();\n      query.where('uuid', '=', id);\n      const customer = await query.load(pool);\n      return customer ? camelCase(customer) : null;\n    },\n    customers: async (_, { filters = [] }) => {\n      const query = getCustomersBaseQuery();\n      const root = new CustomerCollection(query);\n      await root.init(filters);\n      return root;\n    }\n  },\n  Customer: {\n    editUrl: ({ uuid }) => buildUrl('customerEdit', { id: uuid }),\n    updateApi: (customer) => buildUrl('updateCustomer', { id: customer.uuid }),\n    deleteApi: (customer) => buildUrl('deleteCustomer', { id: customer.uuid })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/graphql/types/Customer/Customer.graphql",
    "content": "\"\"\"\nRepresent a customer address\n\"\"\"\ntype CustomerAddress implements Address {\n  cartAddressId: Int!\n  uuid: String!\n  fullName: String\n  postcode: String\n  telephone: String\n  country: Country\n  province: Province\n  city: String\n  address1: String\n  address2: String\n  isDefault: Boolean\n  updateApi: String!\n  deleteApi: String!\n}\n\n\"\"\"\nRepresents a customer\n\"\"\"\ntype Customer {\n  customerId: Int!\n  uuid: String!\n  status: Int!\n  email: String!\n  addresses: [CustomerAddress]\n  addAddressApi: String!\n  fullName: String!\n  createdAt: Date!\n}\n\nextend type Query {\n  currentCustomer: Customer\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/graphql/types/Customer/Customer.resolvers.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\n\nexport default {\n  Query: {\n    currentCustomer: async (root, args, { customer }) =>\n      customer ? camelCase(customer) : null\n  },\n  Customer: {\n    addresses: async (customer, args, { pool }) => {\n      const addresses = await select()\n        .from('customer_address')\n        .where('customer_id', '=', customer.customerId)\n        .execute(pool);\n\n      return addresses.map((address) => ({\n        ...camelCase(address),\n        updateApi: buildUrl('updateCustomerAddress', {\n          address_id: address.uuid,\n          customer_id: customer.uuid\n        }),\n        deleteApi: buildUrl('deleteCustomerAddress', {\n          address_id: address.uuid,\n          customer_id: customer.uuid\n        })\n      }));\n    },\n    addAddressApi: (customer) =>\n      buildUrl('createCustomerAddress', {\n        customer_id: customer.uuid\n      })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/graphql/types/CustomerGroup/CustomerGroup.admin.graphql",
    "content": "extend type CustomerGroup {\n  editUrl: String!\n  customers: [Customer]\n}\n\n\"\"\"\nRepresents a collection of customer groups\n\"\"\"\ntype CustomerGroupCollection {\n  items: [CustomerGroup]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\n\nextend type Query {\n  customerGroup: CustomerGroup\n  customerGroups: CustomerGroupCollection\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/graphql/types/CustomerGroup/CustomerGroup.admin.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { CustomerGroupCollection } from '../../../services/CustomerGroupCollection.js';\nimport { getCustomerGroupsBaseQuery } from '../../../services/getCustomerGroupsBaseQuery.js';\n\nexport default {\n  Query: {\n    customerGroup: async (root, { id }, { pool }) => {\n      const group = await select()\n        .from('customer_group')\n        .where('customer_group.customer_group_id', '=', id)\n        .load(pool);\n\n      return group ? camelCase(group) : null;\n    },\n    customerGroups: async (_, { filters = [] }) => {\n      const query = getCustomerGroupsBaseQuery();\n      const root = new CustomerGroupCollection(query);\n      await root.init({}, { filters });\n      return root;\n    }\n  },\n  CustomerGroup: {\n    customers: async (group, _, { pool }) => {\n      const customers = await select()\n        .from('customer')\n        .where('customer.group_id', '=', group.customerGroupId)\n        .execute(pool);\n      return customers.map((customer) => camelCase(customer));\n    },\n    editUrl: (group) =>\n      buildUrl('customerGroupEdit', { id: group.customerGroupId })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/graphql/types/CustomerGroup/CustomerGroup.graphql",
    "content": "\"\"\"\nRepresents a customer group.\n\"\"\"\ntype CustomerGroup {\n  customerGroupId: Int!\n  groupName: String!\n}\n\nextend type Customer {\n  group: CustomerGroup\n}"
  },
  {
    "path": "packages/evershop/src/modules/customer/graphql/types/CustomerGroup/CustomerGroup.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\n\nexport default {\n  Customer: {\n    group: async ({ groupId }, _, { pool }) => {\n      const group = await select()\n        .from('customer_group')\n        .where('customer_group.customer_group_id', '=', groupId)\n        .load(pool);\n      return group ? camelCase(group) : null;\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/migration/Version-1.0.0.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE \"customer_group\" (\n  \"customer_group_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"group_name\" varchar NOT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP\n)`\n  );\n\n  // Add default customer group\n  await execute(\n    connection,\n    \"INSERT INTO customer_group ( group_name ) VALUES ('Default')\"\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"customer\" (\n  \"customer_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"status\" smallint NOT NULL DEFAULT 1,\n  \"group_id\" INT DEFAULT 1,\n  \"email\" varchar NOT NULL,\n  \"password\" varchar NOT NULL,\n  \"full_name\" varchar DEFAULT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"EMAIL_UNIQUE\" UNIQUE (\"email\"),\n  CONSTRAINT \"CUSTOMER_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_CUSTOMER_GROUP\" FOREIGN KEY (\"group_id\") REFERENCES \"customer_group\" (\"customer_group_id\") ON DELETE SET NULL\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_CUSTOMER_GROUP\" ON \"customer\" (\"group_id\")`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"customer_address\" (\n  \"customer_address_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"customer_id\" INT NOT NULL,\n  \"full_name\" varchar DEFAULT NULL,\n  \"telephone\" varchar DEFAULT NULL,\n  \"address_1\" varchar DEFAULT NULL,\n  \"address_2\" varchar DEFAULT NULL,\n  \"postcode\" varchar DEFAULT NULL,\n  \"city\" varchar DEFAULT NULL,\n  \"province\" varchar DEFAULT NULL,\n  \"country\" varchar NOT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"is_default\" smallint DEFAULT NULL,\n  CONSTRAINT \"CUSTOMER_ADDRESS_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"FK_CUSTOMER_ADDRESS\" FOREIGN KEY (\"customer_id\") REFERENCES \"customer\" (\"customer_id\") ON DELETE CASCADE\n)`\n  );\n  await execute(\n    connection,\n    `CREATE INDEX \"FK_CUSTOMER_ADDRESS\" ON \"customer_address\" (\"customer_id\")`\n  );\n\n  // Prevent deleting a default customer group\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION prevent_delete_default_customer_group()\n        RETURNS TRIGGER\n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        IF OLD.customer_group_id = 1 THEN\n          RAISE EXCEPTION 'Cannot delete default customer group';\n        END IF;\n        RETURN OLD;\n      END;\n      $$`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"PREVENT_DELETING_THE_DEFAULT_CUSTOMER_GROUP\"\n        BEFORE DELETE ON customer_group\n        FOR EACH ROW\n        EXECUTE PROCEDURE prevent_delete_default_customer_group();`\n  );\n\n  // Create trigger before insert customer, set default group_id to 1\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION set_default_customer_group()\n        RETURNS TRIGGER\n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        IF NEW.group_id IS NULL THEN\n          NEW.group_id = 1;\n        END IF;\n        RETURN NEW;\n      END;\n      $$`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"SET_DEFAULT_CUSTOMER_GROUP\"\n        BEFORE INSERT ON customer\n        FOR EACH ROW\n        EXECUTE PROCEDURE set_default_customer_group();`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/migration/Version-1.0.1.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Create a function to add event to the event table after a customer is created\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_customer_created_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('customer_created', row_to_json(NEW));\n      RETURN NEW;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a customer is created\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_CUSTOMER_CREATED_EVENT_TRIGGER\"\n    AFTER INSERT ON customer\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_customer_created_event();`\n  );\n\n  // Create a function to add event to the event table after a customer is updated\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_customer_updated_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('customer_updated', row_to_json(NEW));\n      RETURN NEW;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a customer is updated\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_CUSTOMER_UPDATED_EVENT_TRIGGER\"\n    AFTER UPDATE ON customer\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_customer_updated_event();`\n  );\n\n  // Create a function to add event to the event table after a customer is deleted\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION add_customer_deleted_event() RETURNS TRIGGER AS $$\n    BEGIN\n      INSERT INTO event (name, data)\n      VALUES ('customer_deleted', row_to_json(OLD));\n      RETURN OLD;\n    END;\n    $$ LANGUAGE plpgsql;`\n  );\n\n  // Create a trigger to add event to the event table after a customer is deleted\n  await execute(\n    connection,\n    `CREATE TRIGGER \"ADD_CUSTOMER_DELETED_EVENT_TRIGGER\"\n    AFTER DELETE ON customer\n    FOR EACH ROW\n    EXECUTE PROCEDURE add_customer_deleted_event();`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/migration/Version-1.0.2.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Create a reset_password_token table\n  await execute(\n    connection,\n    `CREATE TABLE IF NOT EXISTS reset_password_token (\n      reset_password_token_id INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n      customer_id INT NOT NULL,\n      token text NOT NULL,\n      created_at timestamp with time zone NOT NULL DEFAULT now(),\n      CONSTRAINT \"FK_RESET_PASSWORD_TOKEN_CUSTOMER\" FOREIGN KEY (\"customer_id\") REFERENCES \"customer\" (\"customer_id\") ON DELETE CASCADE\n    );`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/migration/Version-1.0.3.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  // Update the is_default column to be a boolean, default to false\n  await execute(\n    connection,\n    `ALTER TABLE customer_address\nALTER COLUMN is_default TYPE BOOLEAN USING CASE\n    WHEN is_default = 1 THEN TRUE\n    WHEN is_default = 0 THEN FALSE\n    ELSE NULL -- Handle unexpected values\nEND;`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/all/CustomerMenuGroup.jsx",
    "content": "import { NavigationItemGroup } from '@components/admin/NavigationItemGroup';\nimport { User } from 'lucide-react';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport default function CustomerMenuGroup({ customerGrid }) {\n  return (\n    <NavigationItemGroup\n      id=\"customerMenuGroup\"\n      name=\"Customer\"\n      items={[\n        {\n          Icon: User,\n          url: customerGrid,\n          title: 'Customers'\n        }\n      ]}\n    />\n  );\n}\n\nCustomerMenuGroup.propTypes = {\n  customerGrid: PropTypes.string.isRequired\n};\n\nexport const layout = {\n  areaId: 'adminMenu',\n  sortOrder: 40\n};\n\nexport const query = `\n  query Query {\n    customerGrid: url(routeId:\"customerGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerEdit/CustomerEditForm.jsx",
    "content": "import Area from '@components/common/Area';\nimport React from 'react';\nimport './CustomerEditForm.scss';\n\nexport default function CustomerEditForm() {\n  return (\n    <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n      <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n        <Area id=\"leftSide\" noOuter />\n      </div>\n      <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n        <Area id=\"rightSide\" noOuter />\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    gridUrl: url(routeId: \"customerGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerEdit/CustomerEditForm.scss",
    "content": "body.customerEdit {\n  .main-content-inner {\n    max-width: 62.5rem;\n    margin: 1.875rem auto;\n  }\n  .page-heading {\n    max-width: 62.5rem;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerEdit/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (request, response: EvershopResponse, next) => {\n  try {\n    const query = select();\n    query.from('customer');\n    query.andWhere('customer.uuid', '=', request.params.id);\n    const customer = await query.load(pool);\n\n    if (customer === null) {\n      response.status(404);\n      next();\n    } else {\n      setContextValue(request, 'customerId', customer.customer_id);\n      setContextValue(request, 'customerUuid', customer.uuid);\n      setPageMetaInfo(request, {\n        title: customer.full_name,\n        description: customer.full_name\n      });\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerEdit/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/customers/edit/:id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerEdit+customerNew/General.jsx",
    "content": "import Area from '@components/common/Area';\nimport { Card } from '@components/common/ui/Card';\nimport { CardContent, CardTitle } from '@components/common/ui/Card.js';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nfunction FullName({ fullName }) {\n  return (\n    <CardContent>\n      <CardTitle className=\"mb-2\">Full Name</CardTitle>\n      <div>\n        <span>{fullName}</span>\n      </div>\n    </CardContent>\n  );\n}\n\nFullName.propTypes = {\n  fullName: PropTypes.string.isRequired\n};\n\nfunction Group({ group }) {\n  return (\n    <CardContent className=\"pt-3 border-t border-border\">\n      <CardTitle className=\"mb-2\">Group</CardTitle>\n      <div>\n        <span>{group?.groupName || 'Default'}</span>\n      </div>\n    </CardContent>\n  );\n}\n\nGroup.propTypes = {\n  group: PropTypes.shape({\n    groupName: PropTypes.string\n  }).isRequired\n};\n\nfunction Email({ email }) {\n  return (\n    <CardContent className=\"pt-3 border-t border-border\">\n      <CardTitle className=\"mb-2\">Email</CardTitle>\n      <div>\n        <span>{email}</span>\n      </div>\n    </CardContent>\n  );\n}\n\nEmail.propTypes = {\n  email: PropTypes.string.isRequired\n};\n\nfunction Status({ status }) {\n  return (\n    <CardContent className=\"pt-3 border-t border-border\">\n      <CardTitle className=\"mb-2\">Status</CardTitle>\n      <div>\n        <span>{parseInt(status, 10) === 1 ? 'Enabled' : 'Disabled'}</span>\n      </div>\n    </CardContent>\n  );\n}\n\nStatus.propTypes = {\n  status: PropTypes.number.isRequired\n};\n\nexport default function General({ customer }) {\n  return (\n    <Card>\n      <Area\n        id=\"customerEditInformation\"\n        className=\"space-y-3\"\n        coreComponents={[\n          {\n            component: {\n              default: () => <FullName fullName={customer.fullName} />\n            },\n            sortOrder: 10\n          },\n          {\n            component: { default: () => <Email email={customer.email} /> },\n            sortOrder: 15\n          },\n          {\n            component: { default: () => <Group group={customer.group} /> },\n            sortOrder: 20\n          },\n          {\n            component: { default: () => <Status status={customer.status} /> },\n            sortOrder: 25\n          }\n        ]}\n      />\n    </Card>\n  );\n}\n\nGeneral.propTypes = {\n  customer: PropTypes.shape({\n    email: PropTypes.string,\n    fullName: PropTypes.string,\n    group: PropTypes.shape({\n      groupName: PropTypes.string\n    }),\n    status: PropTypes.number\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    customer(id: getContextValue(\"customerUuid\", null)) {\n      customerId\n      fullName\n      email\n      status\n      group {\n        groupName\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerEdit+customerNew/OrderHistory.jsx",
    "content": "import { Card } from '@components/common/ui/Card';\nimport {\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport default function OrderHistory({ customer: { orders = [] } }) {\n  return (\n    <Card title=\"Order History\">\n      <CardHeader>\n        <CardTitle>Order History</CardTitle>\n        <CardDescription>\n          Recently placed orders by this customer\n        </CardDescription>\n      </CardHeader>\n      {orders.length < 1 && (\n        <CardContent>\n          <div>Customer does not have any order yet.</div>\n        </CardContent>\n      )}\n      {orders.length > 0 && (\n        <>\n          {orders.map((order) => (\n            <CardContent key={order.uuid}>\n              <div className=\"flex justify-between items-center gap-2\">\n                <div>\n                  <a\n                    className=\"font-semibold text-interactive\"\n                    href={order.editUrl}\n                  >\n                    #{order.orderNumber}\n                  </a>\n                </div>\n                <div>\n                  <span>{order.createdAt.text}</span>\n                </div>\n                <div>\n                  <span>{order.paymentStatus.name}</span>\n                </div>\n                <div>\n                  <span>{order.shipmentStatus.name}</span>\n                </div>\n                <div>\n                  <span>{order.grandTotal.text}</span>\n                </div>\n              </div>\n            </CardContent>\n          ))}\n        </>\n      )}\n    </Card>\n  );\n}\n\nOrderHistory.propTypes = {\n  customer: PropTypes.shape({\n    orders: PropTypes.arrayOf(\n      PropTypes.shape({\n        orderNumber: PropTypes.string\n      })\n    )\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    customer(id: getContextValue(\"customerUuid\", null)) {\n      orders {\n        orderNumber\n        uuid\n        editUrl\n        createdAt {\n          text\n        }\n        shipmentStatus {\n          name\n        }\n        paymentStatus {\n          name\n        }\n        grandTotal {\n          text\n        }\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerEdit+customerNew/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface CustomerEditPageHeadingProps {\n  backUrl: string;\n  customer?: {\n    fullName: string;\n  };\n}\n\nexport default function CustomerEditPageHeading({\n  backUrl,\n  customer\n}: CustomerEditPageHeadingProps) {\n  return (\n    <PageHeading\n      backUrl={backUrl}\n      heading={\n        customer ? `Editing ${customer.fullName}` : 'Create A New Customer'\n      }\n    />\n  );\n}\n\nCustomerEditPageHeading.defaultProps = {\n  customer: null\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    customer(id: getContextValue(\"customerUuid\", null)) {\n      fullName\n    }\n    backUrl: url(routeId: \"customerGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerGrid/Grid.jsx",
    "content": "import { GridPagination } from '@components/admin/grid/GridPagination';\nimport { SortableHeader } from '@components/admin/grid/header/Sortable';\nimport { Status } from '@components/admin/Status.js';\nimport Area from '@components/common/Area';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { useAlertContext } from '@components/common/modal/Alert';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport {\n  Card,\n  CardAction,\n  CardContent,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectTrigger,\n  SelectValue\n} from '@components/common/ui/Select.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport axios from 'axios';\nimport PropTypes from 'prop-types';\nimport React, { useState } from 'react';\nimport { CustomerName } from './rows/CustomerName.js';\n\nfunction Actions({ customers = [], selectedIds = [] }) {\n  const { openAlert, closeAlert } = useAlertContext();\n\n  const updateCustomers = async (status) => {\n    const promises = customers\n      .filter((customer) => selectedIds.includes(customer.uuid))\n      .map((customer) =>\n        axios.patch(customer.updateApi, {\n          status\n        })\n      );\n    await Promise.all(promises);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const actions = [\n    {\n      name: 'Disable',\n      onAction: () => {\n        openAlert({\n          heading: `Disable ${selectedIds.length} customers`,\n          content: 'Are you sure?',\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Disable',\n            onAction: async () => {\n              await updateCustomers(0);\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    },\n    {\n      name: 'Enable',\n      onAction: () => {\n        openAlert({\n          heading: `Enable ${selectedIds.length} customers`,\n          content: 'Are you sure?',\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Enable',\n            onAction: async () => {\n              await updateCustomers(1);\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    }\n  ];\n\n  return (\n    <TableRow>\n      {selectedIds.length === 0 && null}\n      {selectedIds.length > 0 && (\n        <TableCell colSpan=\"100\">\n          <ButtonGroup>\n            {actions.map((action, i) => (\n              <Button\n                key={i}\n                variant={'outline'}\n                onClick={(e) => {\n                  e.preventDefault();\n                  action.onAction();\n                }}\n              >\n                {action.name}\n              </Button>\n            ))}\n          </ButtonGroup>\n        </TableCell>\n      )}\n    </TableRow>\n  );\n}\n\nActions.propTypes = {\n  selectedIds: PropTypes.arrayOf(PropTypes.string).isRequired,\n  customers: PropTypes.arrayOf(\n    PropTypes.shape({\n      uuid: PropTypes.string.isRequired,\n      updateApi: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport default function CustomerGrid({\n  customers: { items: customers, total, currentFilters = [] }\n}) {\n  const page = currentFilters.find((filter) => filter.key === 'page')\n    ? parseInt(currentFilters.find((filter) => filter.key === 'page').value, 10)\n    : 1;\n  const limit = currentFilters.find((filter) => filter.key === 'limit')\n    ? parseInt(\n        currentFilters.find((filter) => filter.key === 'limit').value,\n        10\n      )\n    : 20;\n  const [selectedRows, setSelectedRows] = useState([]);\n\n  return (\n    <Card>\n      <CardHeader className=\"flex justify-between\">\n        <Form submitBtn={false} id=\"customerGridFilter\">\n          <div className=\"flex gap-5 justify-center items-center\">\n            <Area\n              id=\"customerGridFilter\"\n              noOuter\n              coreComponents={[\n                {\n                  component: {\n                    default: () => (\n                      <InputField\n                        name=\"keyword\"\n                        placeholder=\"Search\"\n                        defaultValue={\n                          currentFilters.find((f) => f.key === 'keyword')?.value\n                        }\n                        onKeyPress={(e) => {\n                          // If the user press enter, we should submit the form\n                          if (e.key === 'Enter') {\n                            const url = new URL(document.location);\n                            const keyword = e.target?.value;\n                            if (keyword) {\n                              url.searchParams.set('keyword', keyword);\n                            } else {\n                              url.searchParams.delete('keyword');\n                            }\n                            window.location.href = url;\n                          }\n                        }}\n                      />\n                    )\n                  },\n                  sortOrder: 5\n                },\n                {\n                  component: {\n                    default: () => (\n                      <Select\n                        value={\n                          currentFilters.find((f) => f.key === 'status')?.value\n                        }\n                        onValueChange={(value) => {\n                          const url = new URL(document.location);\n                          url.searchParams.set('status', value);\n                          window.location.href = url.href;\n                        }}\n                      >\n                        <SelectTrigger>\n                          <SelectValue>Status</SelectValue>\n                        </SelectTrigger>\n                        <SelectContent>\n                          <SelectGroup>\n                            <SelectLabel>Status</SelectLabel>\n                            <SelectItem value=\"1\">Enabled</SelectItem>\n                            <SelectItem value=\"0\">Disabled</SelectItem>\n                          </SelectGroup>\n                        </SelectContent>\n                      </Select>\n                    )\n                  },\n                  sortOrder: 10\n                }\n              ]}\n              currentFilters={currentFilters}\n            />\n          </div>\n        </Form>\n        <CardAction>\n          <Button\n            variant=\"link\"\n            className={'hover:cursor-pointer'}\n            onClick={() => {\n              const url = new URL(document.location);\n              url.search = '';\n              window.location.href = url.href;\n            }}\n          >\n            Clear filter\n          </Button>\n        </CardAction>\n      </CardHeader>\n      <CardContent>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableCell className=\"align-bottom\">\n                <div className=\"form-field mb-0\">\n                  <Checkbox\n                    onCheckedChange={(checked) => {\n                      if (checked)\n                        setSelectedRows(customers.map((c) => c.uuid));\n                      else setSelectedRows([]);\n                    }}\n                  />\n                </div>\n              </TableCell>\n              <Area\n                id=\"customerGridHeader\"\n                noOuter\n                coreComponents={[\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Full Name\"\n                          name=\"full_name\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 10\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Email\"\n                          name=\"email\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 15\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Status\"\n                          name=\"status\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 20\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Created At\"\n                          name=\"created_at\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 25\n                  }\n                ]}\n              />\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            <Actions\n              customers={customers}\n              selectedIds={selectedRows}\n              setSelectedRows={setSelectedRows}\n            />\n            {customers.map((c) => (\n              <TableRow key={c.customerId}>\n                <TableCell>\n                  <div className=\"form-field mb-0\">\n                    <Checkbox\n                      checked={selectedRows.includes(c.uuid)}\n                      onCheckedChange={(checked) => {\n                        if (checked) {\n                          setSelectedRows(selectedRows.concat([c.uuid]));\n                        } else {\n                          setSelectedRows(\n                            selectedRows.filter((row) => row !== c.uuid)\n                          );\n                        }\n                      }}\n                    />\n                  </div>\n                </TableCell>\n                <Area\n                  id=\"customerGridRow\"\n                  row={c}\n                  noOuter\n                  selectedRows={selectedRows}\n                  setSelectedRows={setSelectedRows}\n                  coreComponents={[\n                    {\n                      component: {\n                        default: () => (\n                          <CustomerName name={c.fullName} url={c.editUrl} />\n                        )\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: ({ areaProps }) => (\n                          <TableCell>{c.email}</TableCell>\n                        )\n                      },\n                      sortOrder: 15\n                    },\n                    {\n                      component: {\n                        default: ({ areaProps }) => (\n                          <Status status={parseInt(c.status, 10)} />\n                        )\n                      },\n                      sortOrder: 20\n                    },\n                    {\n                      component: {\n                        default: () => <TableCell>{c.createdAt.text}</TableCell>\n                      },\n                      sortOrder: 25\n                    }\n                  ]}\n                />\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {customers.length === 0 && (\n          <div className=\"flex w-full justify-center mt-3\">\n            There is no customer to display\n          </div>\n        )}\n        <GridPagination total={total} limit={limit} page={page} />\n      </CardContent>\n    </Card>\n  );\n}\n\nCustomerGrid.propTypes = {\n  customers: PropTypes.shape({\n    items: PropTypes.arrayOf(\n      PropTypes.shape({\n        customerId: PropTypes.number.isRequired,\n        uuid: PropTypes.string.isRequired,\n        fullName: PropTypes.string.isRequired,\n        email: PropTypes.string.isRequired,\n        status: PropTypes.number.isRequired,\n        createdAt: PropTypes.shape({\n          value: PropTypes.string.isRequired,\n          text: PropTypes.string.isRequired\n        }).isRequired,\n        editUrl: PropTypes.string.isRequired,\n        updateApi: PropTypes.string.isRequired\n      })\n    ).isRequired,\n    total: PropTypes.number.isRequired,\n    currentFilters: PropTypes.arrayOf(\n      PropTypes.shape({\n        key: PropTypes.string.isRequired,\n        operation: PropTypes.string.isRequired,\n        value: PropTypes.string.isRequired\n      })\n    ).isRequired\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query($filters: [FilterInput]) {\n    customers (filters: $filters) {\n      items {\n        customerId\n        uuid\n        fullName\n        email\n        status\n        createdAt {\n          value\n          text\n        }\n        editUrl\n        updateApi\n      }\n      total\n      currentFilters {\n        key\n        operation\n        value\n      }\n    }\n  }\n`;\n\nexport const variables = `\n{\n  filters: getContextValue('filtersFromUrl')\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerGrid/Heading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport default function CustomerGridHeading() {\n  return <PageHeading heading=\"Customers\" />;\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerGrid/index.ts",
    "content": "import { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default (request: EvershopRequest, response) => {\n  setPageMetaInfo(request, {\n    title: 'Customers',\n    description: 'Customers'\n  });\n  setContextValue(\n    request,\n    'filtersFromUrl',\n    buildFilterFromUrl(request.originalUrl)\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerGrid/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/customers\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/admin/customerGrid/rows/CustomerName.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface CustomerNameProps {\n  name: string;\n  url: string;\n}\n\nexport function CustomerName({ url, name }: CustomerNameProps) {\n  return (\n    <TableCell>\n      <div>\n        <a className=\"hover:underline font-semibold\" href={url}>\n          {name}\n        </a>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/account/MyAccount.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport AccountInfo from '@components/frontStore/customer/AccountInfo.js';\nimport { MyAddresses } from '@components/frontStore/customer/MyAddresses.js';\nimport OrderHistory from '@components/frontStore/customer/OrderHistory.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport default function MyAccount() {\n  return (\n    <div>\n      <h1 className=\"text-center\">{_('My Account')}</h1>\n      <div className=\"page-width mt-7 grid grid-cols-1 md:grid-cols-3 gap-7\">\n        <div className=\"col-span-1 md:col-span-2\">\n          <OrderHistory title={_('Recent Orders')} />\n        </div>\n        <div className=\"col-span-1\">\n          <AccountInfo title={_('Account Information')} showLogout />\n        </div>\n      </div>\n      <div className=\"page-width mt-7\">\n        <MyAddresses title={_('Address Book')} />\n        <Area id=\"accountPageAddressBook\" noOuter />\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/account/index.ts",
    "content": "import { translate } from '../../../../../lib/locale/translate/translate.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request, response: EvershopResponse, next) => {\n  // Check if the customer is logged in\n  if (!request.isCustomerLoggedIn()) {\n    // Redirect to admin dashboard\n    response.redirect(buildUrl('login'));\n  } else {\n    setPageMetaInfo(request, {\n      title: translate('My Account'),\n      description: translate('My Account')\n    });\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/account/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/account\",\n  \"name\": \"Account page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/all/CustomerIcon.tsx",
    "content": "import { CircleUser } from 'lucide-react';\nimport React from 'react';\n\ninterface UserIconProps {\n  customer: {\n    uuid: string;\n    fullName: string;\n    email: string;\n  };\n  accountUrl: string;\n  loginUrl: string;\n}\n\nexport default function UserIcon({\n  customer,\n  accountUrl,\n  loginUrl\n}: UserIconProps) {\n  return (\n    <div className=\"self-center customer-icon\">\n      <a href={customer ? accountUrl : loginUrl}>\n        <CircleUser className=\"w-5 h-5 text-foreground hover:text-primary\" />\n      </a>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'headerMiddleRight',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    customer: currentCustomer {\n      uuid\n      fullName\n      email\n    }\n    accountUrl: url(routeId: \"account\")\n    loginUrl: url(routeId: \"login\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/all/[context]auth.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\n\nexport default async (request, response, next) => {\n  const { customerID } = request.session;\n  if (!customerID) {\n    delete request.locals.customer;\n    next();\n  } else {\n    // Load the customer from the database\n    const customer = await select()\n      .from('customer')\n      .where('customer_id', '=', customerID)\n      .and('status', '=', 1)\n      .load(pool);\n\n    if (!customer) {\n      // The customer may not be logged in, or the account may be disabled\n      // Logout the customer\n      request.logoutCustomer((error) => {\n        if (error) {\n          next(error);\n        } else {\n          response.redirect(buildUrl('homepage'));\n        }\n      });\n    } else {\n      // Delete the password field\n      delete customer.password;\n      request.locals.customer = customer;\n      next();\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/customerLoginJson/[bodyParser]login.ts",
    "content": "import { translate } from '../../../../../lib/locale/translate/translate.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const { body } = request;\n  const { email, password } = body;\n  const message = translate('Invalid email or password');\n  try {\n    await request.loginCustomerWithEmail(email, password, (error) => {\n      if (error) {\n        response.status(INTERNAL_SERVER_ERROR);\n        response.json({\n          error: {\n            status: INTERNAL_SERVER_ERROR,\n            message\n          }\n        });\n      } else {\n        response.status(OK);\n        response.$body = {\n          data: {\n            sid: request.sessionID\n          }\n        };\n        next();\n      }\n    });\n  } catch (error) {\n    response.status(INVALID_PAYLOAD);\n    response.json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message: error.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/customerLoginJson/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    },\n    \"password\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"email\", \"password\"]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/customerLoginJson/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/customer/login\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/customerLogoutJson/logout.js",
    "content": "import {\n  INTERNAL_SERVER_ERROR,\n  OK\n} from '../../../../../lib/util/httpStatus.js';\n\nexport default (request, response, next) => {\n  try {\n    request.logoutCustomer((error) => {\n      if (error) {\n        response.status(INTERNAL_SERVER_ERROR);\n        response.json({\n          error: {\n            status: INTERNAL_SERVER_ERROR,\n            message: error.message\n          }\n        });\n      } else {\n        response.status(OK);\n        response.$body = {\n          data: {}\n        };\n        next();\n      }\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/customerLogoutJson/route.json",
    "content": "{\n  \"methods\": [\"GET\", \"POST\"],\n  \"path\": \"/customer/logout\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/login/LoginPage.tsx",
    "content": "import { Card, CardContent } from '@components/common/ui/Card.js';\nimport { CustomerLoginForm } from '@components/frontStore/customer/LoginForm.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface LoginPageProps {\n  homeUrl: string;\n  registerUrl: string;\n  forgotPasswordUrl: string;\n}\n\nexport default function LoginPage({\n  homeUrl,\n  registerUrl,\n  forgotPasswordUrl\n}: LoginPageProps) {\n  return (\n    <div className=\"login__page flex flex-col items-center py-10 px-4\">\n      <div className=\"w-full max-w-md\">\n        <Card>\n          <CardContent>\n            <CustomerLoginForm\n              title={_('Welcome Back!')}\n              subtitle={_('Please sign in to your account')}\n              redirectUrl={homeUrl}\n              onError={(error) => {\n                toast.error(error.message);\n              }}\n              className=\"w-full\"\n            />\n          </CardContent>\n        </Card>\n        <div className=\"login__page__options text-center mt-4 gap-5 flex justify-center\">\n          <a className=\"text-interactive hover:underline\" href={registerUrl}>\n            {_('Create an account')}\n          </a>\n          <a\n            className=\"text-destructive hover:underline\"\n            href={forgotPasswordUrl}\n          >\n            {_('Forgot your password?')}\n          </a>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    homeUrl: url(routeId: \"homepage\")\n    registerUrl: url(routeId: \"register\")\n    forgotPasswordUrl: url(routeId: \"resetPasswordPage\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/login/index.ts",
    "content": "import { translate } from '../../../../../lib/locale/translate/translate.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request, response, next) => {\n  // Check if the user is logged in\n  if (request.isCustomerLoggedIn()) {\n    // Redirect to homepage\n    response.redirect(buildUrl('homepage'));\n  } else {\n    setPageMetaInfo(request, {\n      title: translate('Login'),\n      description: translate('Login')\n    });\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/login/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/account/login\",\n  \"name\": \"Login page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/register/RegisterPage.tsx",
    "content": "import { Card, CardContent } from '@components/common/ui/Card.js';\nimport { CustomerRegistrationForm } from '@components/frontStore/customer/RegistrationForm.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface RegisterPageProps {\n  homeUrl: string;\n  loginUrl: string;\n}\nexport default function RegisterPage({ homeUrl, loginUrl }: RegisterPageProps) {\n  return (\n    <div className=\"flex flex-col items-center py-10 px-4\">\n      <div className=\"w-full max-w-md\">\n        <Card>\n          <CardContent>\n            <CustomerRegistrationForm\n              title={_('Create an account')}\n              subtitle={_('Join us for exclusive offers and order tracking')}\n              redirectUrl={homeUrl}\n              onError={(error) => {\n                toast.error(error);\n              }}\n              className=\"w-full\"\n            />\n          </CardContent>\n        </Card>\n\n        <div className=\"text-center mt-4\">\n          <span>\n            {_('Already have an account?')}\n            <a className=\"text-primary hover:underline\" href={loginUrl}>\n              {' '}\n              {_('Login')}{' '}\n            </a>\n          </span>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    homeUrl: url(routeId: \"homepage\")\n    loginUrl: url(routeId: \"login\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/register/index.ts",
    "content": "import { translate } from '../../../../../lib/locale/translate/translate.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request: EvershopRequest, response: EvershopResponse, next) => {\n  if (request.getCurrentCustomer()) {\n    response.redirect(buildUrl('homepage'));\n  } else {\n    setPageMetaInfo(request, {\n      title: translate('Create an account'),\n      description: translate('Create an account')\n    });\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/register/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/account/register\",\n  \"name\": \"Register page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/resetPasswordPage/ResetPasswordPage.tsx",
    "content": "import { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { PasswordField } from '@components/common/form/PasswordField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ResetPasswordForm } from '@components/frontStore/customer/ResetPasswordForm.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\n\nfunction Success({ children }: { children?: React.ReactNode }) {\n  return (\n    <div className=\"flex justify-center items-center h-full\">\n      <div className=\"reset__password__success flex justify-center items-center pt-10 md:pt-36\">\n        <div className=\"reset__password__success__inner max-w-md px-4\">\n          <p className=\"text-center text-success\">{children}</p>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nconst UpdateForm: React.FC<{\n  token: string;\n  action: string;\n  loginUrl: string;\n}> = ({ token, action, loginUrl }) => {\n  const [success, setSuccess] = React.useState(false);\n  const [error, setError] = React.useState(null);\n  const form = useForm();\n\n  return success ? (\n    <Success>\n      <div>{_('Your password has been updated successfully.')}</div>\n      <div className=\"flex items-center\">\n        <Button\n          title={_('Go to Login')}\n          type=\"button\"\n          onClick={() => {\n            window.location.href = loginUrl;\n          }}\n        >\n          {_('Go to Login')}\n        </Button>\n      </div>\n    </Success>\n  ) : (\n    <div className=\"flex justify-center items-center\">\n      <div className=\"update-password-form flex justify-center items-center\">\n        <div className=\"update-password-form-inner\">\n          <h2 className=\"text-center mb-5\">{_('Enter your new password')}</h2>\n          {error && <div className=\"text-critical mb-2\">{error}</div>}\n          <Form\n            form={form}\n            id=\"updatePasswordForm\"\n            action={action}\n            method=\"POST\"\n            onSuccess={(response) => {\n              if (!response.error) {\n                setSuccess(true);\n              } else {\n                setError(response.error.message);\n              }\n            }}\n            submitBtn={false}\n          >\n            <PasswordField\n              name=\"password\"\n              placeholder={_('Password')}\n              required\n              validation={{\n                required: _('Password is required')\n              }}\n            />\n            <InputField name=\"token\" type=\"hidden\" defaultValue={token} />\n            <div className=\"form-submit-button flex border-t border-divider mt-2 pt-2\">\n              <Button\n                title={_('UPDATE PASSWORD')}\n                type=\"submit\"\n                onClick={() => {\n                  (\n                    document.getElementById(\n                      'updatePasswordForm'\n                    ) as HTMLFormElement\n                  ).dispatchEvent(\n                    new Event('submit', { cancelable: true, bubbles: true })\n                  );\n                }}\n                isLoading={form.formState.isSubmitting}\n              >\n                {_('UPDATE PASSWORD')}\n              </Button>\n            </div>\n          </Form>\n        </div>\n      </div>\n    </div>\n  );\n};\n\ninterface ResetFormProps {\n  action: string;\n}\n\nfunction ResetForm({ action }: ResetFormProps) {\n  const [success, setSuccess] = React.useState(false);\n  const [token, setToken] = React.useState<string | undefined>('');\n\n  React.useEffect(() => {\n    const urlParams = new URLSearchParams(window.location.search);\n    const tokenParam = urlParams.get('token') as string;\n    setToken(tokenParam);\n  }, []);\n\n  return success ? (\n    <Success>\n      {_(\n        'We have sent you an email with a link to reset your password. Please check your inbox.'\n      )}\n    </Success>\n  ) : (\n    <ResetPasswordForm\n      title={_('Reset Your Password')}\n      subtitle={_('Please enter your email to receive a reset link')}\n      className=\"w-120 max-w-max md:max-w-[80%] bg-white rounded-3xl p-6 shadow-lg border border-divider\"\n      action={action}\n      onSuccess={() => {\n        setSuccess(true);\n      }}\n    />\n  );\n}\n\ninterface ResetPasswordFormProps {\n  requestAction: string;\n  updateAction: string;\n  loginUrl: string;\n}\nexport default function ResetPasswordPage({\n  requestAction,\n  updateAction,\n  loginUrl\n}: ResetPasswordFormProps) {\n  const [token, setToken] = React.useState<string | undefined>('');\n\n  React.useEffect(() => {\n    const urlParams = new URLSearchParams(window.location.search);\n    const tokenParam = urlParams.get('token') as string;\n    setToken(tokenParam);\n  }, []);\n\n  return token ? (\n    <UpdateForm token={token} action={updateAction} loginUrl={loginUrl} />\n  ) : (\n    <ResetForm action={requestAction} />\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    requestAction: url(routeId: \"resetPassword\"),\n    updateAction: url(routeId: \"updatePassword\"),\n    loginUrl: url(routeId: \"login\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/resetPasswordPage/index.ts",
    "content": "import { translate } from '../../../../../lib/locale/translate/translate.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request: EvershopRequest, response: EvershopResponse, next) => {\n  // Check if the customer is logged in\n  if (request.isCustomerLoggedIn()) {\n    // Redirect to homepage\n    response.redirect(buildUrl('homepage'));\n  } else {\n    setPageMetaInfo(request, {\n      title: translate('Reset password'),\n      description: translate('Reset password')\n    });\n    next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/pages/frontStore/resetPasswordPage/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/account/reset-password\",\n  \"name\": \"Reset password page\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/CustomerCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class CustomerCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n    this.baseQuery.orderBy('customer.customer_id', 'DESC');\n  }\n\n  async init(filters = []) {\n    const currentFilters = [];\n\n    // Apply the filters\n    const customerCollectionFilters = await getValue(\n      'customerCollectionFilters',\n      []\n    );\n\n    customerCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(customer.customer_id)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/CustomerGroupCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class CustomerGroupCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n  }\n\n  async init(args, { filters = [] }) {\n    const currentFilters = [];\n\n    // Apply the filters\n    const customerGroupCollectionFilters = await getValue(\n      'customerGroupCollectionFilters',\n      []\n    );\n\n    customerGroupCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(*)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/address/addressValidators.ts",
    "content": "import {\n  addProcessor,\n  getValueSync\n} from '../../../../../lib/util/registry.js';\nimport {\n  Validator,\n  ValidatorManager\n} from '../../../../../lib/util/validator.js';\nimport { Address } from '../../../../../types/customerAddress.js';\n\nconst initialValidators: Validator<Address>[] = [\n  {\n    id: 'fullNameNotEmpty',\n    /**\n     *\n     * @param {Address} address\n     * @returns {boolean}\n     */\n    func: (address: Address) => {\n      if (!address.full_name || address.full_name.trim() === '') {\n        return false;\n      } else {\n        return true;\n      }\n    },\n    errorMessage: 'Full name is required'\n  },\n  {\n    id: 'address1NotEmpty',\n    /**\n     *\n     * @param {Address} address\n     * @returns {boolean}\n     */\n    func: (address: Address) => {\n      if (!address.address_1 || address.address_1.trim() === '') {\n        return false;\n      } else {\n        return true;\n      }\n    },\n    errorMessage: 'Address is required'\n  },\n  {\n    id: 'provinceNotEmpty',\n    /**\n     *\n     * @param {Address} address\n     * @returns {boolean}\n     */\n    func: (address: Address) => {\n      if (!address.province || address.province.trim() === '') {\n        return false;\n      } else {\n        return true;\n      }\n    },\n    errorMessage: 'Province is required'\n  },\n  {\n    id: 'countryNotEmpty',\n    /**\n     *\n     * @param {Address} address\n     * @returns {boolean}\n     */\n    func: (address: Address) => {\n      if (!address.country || address.country.trim() === '') {\n        return false;\n      } else {\n        return true;\n      }\n    },\n    errorMessage: 'Country is required'\n  },\n  {\n    id: 'postcodeNotEmpty',\n    /**\n     *\n     * @param {Address} address\n     * @returns {boolean}\n     */\n    func: (address: Address) => {\n      if (!address.postcode || address.postcode.trim() === '') {\n        return false;\n      } else {\n        return true;\n      }\n    },\n    errorMessage: 'Postcode is required'\n  }\n];\n\nexport function validateAddress(address: Address): {\n  valid: boolean;\n  errors: string[];\n} {\n  const validator = getValueSync<ValidatorManager<Address>>(\n    'addressValidator',\n    () => new ValidatorManager(initialValidators),\n    {},\n    (value) => value instanceof ValidatorManager\n  );\n  return validator.validateSync(address);\n}\n\nexport function addAddressValidationRule(rule: Validator<Address>): void {\n  addProcessor('addressValidator', (validatorManager) => {\n    if (validatorManager instanceof ValidatorManager) {\n      validatorManager.add(rule);\n      return validatorManager;\n    } else {\n      throw new Error(\n        'addressValidator must be an instance of ValidatorManager'\n      );\n    }\n  });\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/address/createCustomerAddress.ts",
    "content": "import {\n  commit,\n  insert,\n  PoolClient,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection, pool } from '../../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../../lib/util/hookable.js';\nimport { getValue } from '../../../../../lib/util/registry.js';\nimport { Address } from '../../../../../types/customerAddress.js';\nimport { validateAddress } from './addressValidators.js';\n\nasync function insertCustomerAddressData(\n  data: Address,\n  connection: PoolClient\n): Promise<Address> {\n  const address = await insert('customer_address')\n    .given(data)\n    .execute(connection);\n  if (address.is_default) {\n    await update('customer_address')\n      .given({\n        is_default: 0\n      })\n      .where('customer_id', '=', address.customer_id)\n      .and('uuid', '<>', address.uuid)\n      .execute(connection);\n  }\n  return address;\n}\n\n/**\n * Create customer address service. This service will create a customer address with all related data\n * @param {String} customerUUID\n * @param {Address} address\n * @param {Object} context\n */\nasync function createCustomerAddress(\n  customerUUID: string,\n  address: Address,\n  context: Record<string, unknown>\n): Promise<Address> {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const customerAddressData = await getValue(\n      'customerAddressDataBeforeCreate',\n      address,\n      context\n    );\n    // Validate customer address data\n    const validationResults = validateAddress(customerAddressData);\n    if (!validationResults.valid) {\n      throw new Error(`${validationResults.errors.join(', ')}`);\n    }\n    const customer = await select()\n      .from('customer')\n      .where('uuid', '=', customerUUID)\n      .load(pool);\n\n    if (!customer) {\n      throw new Error('Invalid customer');\n    }\n    customerAddressData.customer_id = customer.customer_id;\n    // Insert customer address data\n    const customerAddress = await hookable(insertCustomerAddressData, {\n      ...context,\n      connection\n    })(customerAddressData, connection);\n\n    await commit(connection);\n    return customerAddress;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Create customer address service. This service will create a customer address with all related data\n * @param {String} customerUUID\n * @param {Address} addressData\n * @param {Object} context\n * @returns {Promise<Address>}\n * @throws {Error} If context is not an object or if address validation fails\n */\nexport default async (\n  customerUUID: string,\n  addressData: Address,\n  context: Record<string, unknown>\n) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const address = await hookable(createCustomerAddress, context)(\n    customerUUID,\n    addressData,\n    context\n  );\n  return address;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/address/deleteCustomerAddress.ts",
    "content": "import {\n  commit,\n  del,\n  PoolClient,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../../lib/util/hookable.js';\nimport { Address } from '../../../../../types/customerAddress.js';\n\nasync function deleteCustomerAddressData(uuid: string, connection: PoolClient) {\n  await del('customer_address').where('uuid', '=', uuid).execute(connection);\n}\n/**\n * Delete customer address service. This service will delete a customer address with all related data\n * @param {String} uuid\n * @param {Object} context\n * @return {Promise<Address>} The deleted address\n * @throws {Error} If the address does not exist or if there is an error during the transaction\n */\nasync function deleteCustomerAddress(\n  uuid: string,\n  context: Record<string, unknown>\n): Promise<Address> {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const query = select().from('customer_address');\n    const address = await query.where('uuid', '=', uuid).load(connection);\n\n    if (!address) {\n      throw new Error('Invalid address id');\n    }\n    await hookable(deleteCustomerAddressData, {\n      ...context,\n      connection,\n      address\n    })(uuid, connection);\n\n    await commit(connection);\n    return address;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Delete customer address service. This service will delete a customer address with all related data\n * @param {String} uuid\n * @param {Object} context\n * @return {Promise<Address>} The deleted address\n * @throws {Error} If the address does not exist or if there is an error during the transaction\n */\nexport default async (\n  uuid: string,\n  context: Record<string, unknown>\n): Promise<Address> => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const customerAddress = await hookable(deleteCustomerAddress, context)(\n    uuid,\n    context\n  );\n  return customerAddress;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/address/updateCustomerAddress.ts",
    "content": "import {\n  commit,\n  PoolClient,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../../lib/util/hookable.js';\nimport { getValue } from '../../../../../lib/util/registry.js';\nimport { Address } from '../../../../../types/customerAddress.js';\nimport { validateAddress } from './addressValidators.js';\n\nasync function updateCustomerAddressData(\n  uuid: string,\n  data: Partial<Address>,\n  connection: PoolClient\n): Promise<Address> {\n  const query = select().from('customer_address');\n  const address = await query.where('uuid', '=', uuid).load(connection);\n  try {\n    const newAddress = (await update('customer_address')\n      .given(data)\n      .where('uuid', '=', uuid)\n      .execute(connection)) as unknown as Address;\n    if (newAddress.is_default) {\n      await update('customer_address')\n        .given({\n          is_default: 0\n        })\n        .where('customer_id', '=', newAddress.customer_id)\n        .and('uuid', '<>', newAddress.uuid)\n        .execute(connection);\n    }\n    Object.assign(address, newAddress);\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    }\n  }\n  return address;\n}\n\n/**\n * Update customer address service. This service will update a customer address with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n * @return {Promise<Address>} The updated address\n * @throws {Error} If the address does not exist or if there is an error during the transaction\n * @throws {Error} If the address data is invalid\n */\nasync function updateCustomerAddress(\n  uuid: string,\n  data: Partial<Address>,\n  context: Record<string, unknown>\n): Promise<Address> {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const addressData = await getValue('customerDataBeforeUpdate', data);\n    const query = select().from('customer_address');\n    const currentAddress = await query\n      .where('uuid', '=', uuid)\n      .load(connection);\n    if (!currentAddress) {\n      throw new Error('Requested address not found');\n    }\n    // Remove null values from the current address\n    Object.keys(currentAddress).forEach((key) => {\n      if (currentAddress[key] === null) {\n        delete currentAddress[key];\n      }\n    });\n    // Validate address data\n    const validationResults = validateAddress({\n      ...currentAddress,\n      ...addressData\n    });\n    if (!validationResults.valid) {\n      throw new Error(`${validationResults.errors.join(', ')}`);\n    }\n    // Update address data\n    const address = await hookable(updateCustomerAddressData, {\n      ...context,\n      connection\n    })(uuid, addressData, connection);\n\n    await commit(connection);\n    return address;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Update customer address service. This service will update a customer address with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n * @return {Promise<Address>} The updated address\n * @throws {Error} If the address does not exist or if there is an error during the transaction\n * @throws {Error} If the context is not an object\n */\nexport default async (\n  uuid: string,\n  data: Partial<Address>,\n  context: Record<string, unknown>\n) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const address = await hookable(updateCustomerAddress, context)(\n    uuid,\n    data,\n    context\n  );\n  return address;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/createCustomer.ts",
    "content": "import {\n  commit,\n  insert,\n  PoolClient,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { JSONSchemaType } from 'ajv';\nimport { emit } from '../../../../lib/event/emitter.js';\nimport {\n  getConnection,\n  pool\n} from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  hashPassword,\n  verifyPassword\n} from '../../../../lib/util/passwordHelper.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport customerDataSchema from './customerDataSchema.json' with { type: 'json' };\n\nexport interface CustomerData {\n  email?: string,\n  full_name?: string,\n  password?: string,\n  group_id?: number,\n  status?: number,\n};\n\nfunction validateCustomerDataBeforeInsert<T extends CustomerData>(data: T): T {\n  const ajv = getAjv();\n  (customerDataSchema as JSONSchemaType<any>).required = ['email', 'password', 'full_name'];\n  const jsonSchema = getValueSync(\n    'createCustomerDataJsonSchema',\n    customerDataSchema,\n    {}\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    // Validate password\n    const { password } = data;\n    verifyPassword(password || '');\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function insertCustomerData<T extends CustomerData>(data: T, connection: PoolClient) {\n  const customer = await insert('customer').given(data).execute(connection);\n  // Delete password from customer object\n  delete customer.password;\n  return customer;\n}\n\n/**\n * Create customer service. This service will create a customer with all related data\n * @param {Object} data\n * @param {Object} context\n */\nasync function createCustomer<T extends CustomerData>(data: T, context: Record<string, unknown> = {}) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const customerData = await getValue(\n      'customerDataBeforeCreate',\n      data,\n      context\n    );\n    // Validate customer data\n    validateCustomerDataBeforeInsert(customerData);\n    const { email, password } = customerData;\n    // Hash the password\n    const hashedPassword = hashPassword(password || '');\n    // Check if email is already used\n    const existingCustomer = await select()\n      .from('customer')\n      .where('email', '=', email)\n      .load(pool);\n\n    if (existingCustomer) {\n      throw new Error('Email is already used');\n    }\n\n    customerData.status =\n      customerData.status !== undefined ? customerData.status : 1;\n    customerData.password = hashedPassword;\n    customerData.group_id =\n      customerData.group_id !== undefined ? customerData.group_id : 1;\n    // Insert customer data\n    const customer = await hookable(insertCustomerData, {\n      ...context,\n      connection\n    })(customerData, connection);\n\n    // If status = 1, Emit event customer_registered\n    // In case of status = 0, the custom extension will need to emit the event\n    if (parseInt(customer.status, 10) === 1) {\n      await emit('customer_registered', customer);\n    }\n\n    await commit(connection);\n    return customer;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Create customer service. This service will create a customer with all related data\n * @param {Object} data\n * @param {Object} context\n */\nexport default async <T extends CustomerData>(data: T, context: Record<string, unknown> = {}): Promise<T> => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const customer = await hookable(createCustomer, context)(data, context);\n  return customer;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/customerDataSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"status\": {\n      \"type\": [\"string\", \"integer\"],\n      \"enum\": [\"0\", \"1\", 0, 1],\n      \"errorMessage\": {\n        \"type\": \"Status must be a string or number\",\n        \"enum\": \"Status must be either 0, 1, '0', or '1'\"\n      }\n    },\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\",\n      \"errorMessage\": {\n        \"type\": \"Email must be a string\",\n        \"format\": \"Email must be a valid email address (e.g., user@example.com)\"\n      }\n    },\n    \"password\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Password must be a string\",\n        \"minLength\": \"Password is required and cannot be empty\"\n      }\n    },\n    \"full_name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Full name must be a string\",\n        \"minLength\": \"Full name is required and cannot be empty\"\n      }\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/deleteCustomer.ts",
    "content": "import {\n  commit,\n  del,\n  PoolClient,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport { CustomerData } from './createCustomer.js';\n\nasync function deleteCustomerData(uuid: string, connection: PoolClient) {\n  await del('customer').where('uuid', '=', uuid).execute(connection);\n}\n/**\n * Delete customer service. This service will delete a customer with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nasync function deleteCustomer(uuid: string, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const query = select().from('customer');\n    const customer = await query.where('uuid', '=', uuid).load(connection);\n\n    if (!customer) {\n      throw new Error('Invalid customer id');\n    }\n    await hookable(deleteCustomerData, { ...context, connection, customer })(\n      uuid,\n      connection\n    );\n\n    await commit(connection);\n    // Delete password from customer object\n    delete customer.password;\n    return customer;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Delete customer service. This service will delete a customer with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nexport default async (\n  uuid: string,\n  context: Record<string, any>\n): Promise<CustomerData> => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const customer = await hookable(deleteCustomer, context)(uuid, context);\n  return customer;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/loginCustomerWithEmail.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { translate } from '../../../../lib/locale/translate/translate.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { comparePassword } from '../../../../lib/util/passwordHelper.js';\n/**\n * Login a customer with email and password. This function must be accessed from the request object (request.loginCustomerWithEmail(email, password, callback))\n * @param {string} email\n * @param {string} password\n */\nasync function loginCustomerWithEmail(email: string, password: string) {\n  // Escape the email to prevent SQL injection\n  const customerEmail = email.replace(/%/g, '\\\\%');\n  const customer = await select()\n    .from('customer')\n    .where('email', 'ILIKE', customerEmail)\n    .and('status', '=', 1)\n    .load(pool);\n  const result = comparePassword(password, customer ? customer.password : '');\n  if (!customer || !result) {\n    throw new Error(translate('Invalid email or password'));\n  }\n  if (this.session) {\n    this.session.customerID = customer.customer_id;\n  }\n  // Delete the password field\n  delete customer.password;\n  // Save the customer in the request\n  this.locals.customer = customer;\n}\n\nexport default loginCustomerWithEmail;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/logoutCustomer.js",
    "content": "/**\n * Logout the current customer. This function must be accessed from the request object (request.logoutCustomer(callback))\n */\nfunction logoutCustomer() {\n  this.session.customerID = undefined;\n  this.locals.customer = undefined;\n}\n\nexport default logoutCustomer;\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/updateCustomer.ts",
    "content": "import {\n  commit, \n  PoolClient, \n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { JSONSchemaType } from 'ajv';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport { CustomerData } from './createCustomer.js';\nimport customerDataSchema from './customerDataSchema.json' with { type: 'json' };\n\nfunction validateCustomerDataBeforeInsert(data: CustomerData) {\n  const ajv = getAjv();\n  (customerDataSchema as JSONSchemaType<any>).required = [];\n  const jsonSchema = getValueSync(\n    'updateCustomerDataJsonSchema',\n    customerDataSchema,\n    {}\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function updateCustomerData(uuid: string, data: CustomerData, connection: PoolClient) {\n  const query = select().from('customer');\n  const customer = await query.where('uuid', '=', uuid).load(connection);\n  if (!customer) {\n    throw new Error('Requested customer not found');\n  }\n\n  try {\n    const newCustomer = await update('customer')\n      .given(data)\n      .where('uuid', '=', uuid)\n      .execute(connection);\n    Object.assign(customer, newCustomer);\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    }\n  }\n\n  // Delete password from customer object\n  delete customer.password;\n  return customer;\n}\n\n/**\n * Update customer service. This service will update a customer with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nasync function updateCustomer(uuid: string, data: CustomerData, context: Record<string, any>) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const customerData = await getValue('customerDataBeforeUpdate', data);\n    // Validate customer data\n    validateCustomerDataBeforeInsert(customerData);\n\n    // Do not allow to update password here. Use changePassword service instead\n    delete customerData.password;\n    // Update customer data\n    const customer = await hookable(updateCustomerData, {\n      ...context,\n      connection\n    })(uuid, customerData, connection);\n\n    await commit(connection);\n    return customer;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Update customer service. This service will update a customer with all related data\n * @param {String} uuid\n * @param {Object} data\n * @param {Object} context\n */\nexport default async (uuid: string, data: CustomerData, context: Record<string, any>): Promise<CustomerData> => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const customer = await hookable(updateCustomer, context)(uuid, data, context);\n  return customer;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/customer/updatePassword.ts",
    "content": "import {\n  commit,\n  PoolClient,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  hashPassword,\n  verifyPassword\n} from '../../../../lib/util/passwordHelper.js';\n\nasync function updateCustomerPassword(\n  customerId: number,\n  hash: string,\n  connection: PoolClient\n) {\n  await update('customer')\n    .given({\n      password: hash\n    })\n    .where('customer_id', '=', customerId)\n    .execute(connection);\n}\n\n/**\n * Update customer password service.\n * @param {Number} customerId\n * @param {String} newPassword\n * @param {Object} context\n */\nasync function updatePassword(\n  customerId: number,\n  newPassword: string,\n  context: Record<string, unknown>\n): Promise<Record<string, unknown>> {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const query = select().from('customer');\n    const customer = await query\n      .where('customer_id', '=', customerId)\n      .load(connection);\n    if (!customer) {\n      throw new Error('Requested customer not found');\n    }\n    // Verify password\n    verifyPassword(newPassword);\n    // Hash password\n    const hash = hashPassword(newPassword);\n    // Update customer password\n    await hookable(updateCustomerPassword, {\n      ...context,\n      connection\n    })(customerId, hash, connection);\n\n    await commit(connection);\n    return customer;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\n/**\n * Update customer password service.\n * @param {Number} customerId\n * @param {String} password\n * @param {Object} context\n */\nexport default async (\n  customerId: number,\n  password: string,\n  context: Record<string, unknown>\n): Promise<boolean> => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  await hookable(updatePassword, context)(customerId, password, context);\n  return true;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/getCustomerGroupsBaseQuery.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\n\nexport const getCustomerGroupsBaseQuery = () => {\n  const query = select().from('customer_group');\n\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/getCustomersBaseQuery.ts",
    "content": "import { select, SelectQuery } from '@evershop/postgres-query-builder';\n\nexport const getCustomersBaseQuery = (): SelectQuery => {\n  const query = select().from('customer');\n\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/index.ts",
    "content": "import {\n  validateAddress,\n  addAddressValidationRule\n} from './customer/address/addressValidators.js';\nimport createCustomerAddress from './customer/address/createCustomerAddress.js';\nimport deleteCustomerAddress from './customer/address/deleteCustomerAddress.js';\nimport updateCustomerAddress from './customer/address/updateCustomerAddress.js';\nimport createCustomer from './customer/createCustomer.js';\nimport deleteCustomer from './customer/deleteCustomer.js';\nimport updateCustomer from './customer/updateCustomer.js';\nimport updatePassword from './customer/updatePassword.js';\nimport { getCustomersBaseQuery } from './getCustomersBaseQuery.js';\nimport { sendResetPasswordEmail } from './sendResetPasswordEmail.js';\n\nexport {\n  getCustomersBaseQuery,\n  createCustomer,\n  createCustomerAddress,\n  updateCustomerAddress,\n  deleteCustomerAddress,\n  updateCustomer,\n  deleteCustomer,\n  updatePassword,\n  validateAddress,\n  addAddressValidationRule,\n  sendResetPasswordEmail\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/registerDefaultCustomerCollectionFilters.js",
    "content": "import { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport async function registerDefaultCustomerCollectionFilters() {\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'keyword',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query\n          .andWhere('customer.full_name', 'ILIKE', `%${value}%`)\n          .or('customer.email', 'ILIKE', `%${value}%`);\n        currentFilters.push({\n          key: 'keyword',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'full_name',\n      operation: ['like', 'nlike'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'customer.full_name',\n          OPERATION_MAP[operation],\n          `%${value}%`\n        );\n        currentFilters.push({\n          key: 'full_name',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'email',\n      operation: ['eq', 'like', 'nlike'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere('customer.email', OPERATION_MAP[operation], value);\n        currentFilters.push({\n          key: 'email',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'status',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere('customer.status', OPERATION_MAP[operation], value);\n        currentFilters.push({\n          key: 'status',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const customerCollectionSortBy = getValueSync(\n          'customerCollectionSortBy',\n          {\n            email: (query) => query.orderBy('customer.email'),\n            name: (query) => query.orderBy('customer.full_name'),\n            status: (query) => query.orderBy('customer.status'),\n            created_at: (query) => query.orderBy('customer.created_at')\n          }\n        );\n\n        if (customerCollectionSortBy[value]) {\n          customerCollectionSortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        }\n      }\n    }\n  ];\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/registerDefaultCustomerGroupCollectionFilters.js",
    "content": "import { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport async function registerDefaultCustomerGroupCollectionFilters() {\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'name',\n      operation: ['like'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'customer_group.group_name',\n          OPERATION_MAP[operation],\n          `%${value}%`\n        );\n        currentFilters.push({\n          key: 'name',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const customerGroupCollectionSortBy = getValueSync(\n          'customerGroupCollectionSortBy',\n          {\n            name: (query) => query.orderBy('customer_group.group_name')\n          }\n        );\n\n        if (customerGroupCollectionSortBy[value]) {\n          customerGroupCollectionSortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        }\n      }\n    }\n  ];\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/services/sendResetPasswordEmail.ts",
    "content": "import fs from 'fs/promises';\nimport path from 'path';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { translate } from '../../../lib/locale/translate/translate.js';\nimport { debug } from '../../../lib/log/logger.js';\nimport {\n  buildEmailBodyFromTemplate,\n  sendEmail\n} from '../../../lib/mail/emailHelper.js';\nimport { buildAbsoluteUrl } from '../../../lib/router/buildAbsoluteUrl.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nconst TEMPLATE = `<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html dir=\"ltr\" lang=\"en\">\n  <head>\n    {{#if storeInfo.logo}}\n      <link rel=\"preload\" as=\"image\" href=\"{{storeInfo.logo.url}}\" />\n    {{/if}}\n    <meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\" />\n  </head>\n  <div\n    style=\"\n      display: none;\n      overflow: hidden;\n      line-height: 1px;\n      opacity: 0;\n      max-height: 0;\n      max-width: 0;\n    \"\n  >\n    {{storeName}} reset your password\n    <div>\n       ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿\n    </div>\n  </div>\n\n  <body style=\"background-color: #ffffff; padding: 10px 0\">\n    <table\n      align=\"center\"\n      width=\"100%\"\n      border=\"0\"\n      cellpadding=\"0\"\n      cellspacing=\"0\"\n      role=\"presentation\"\n      style=\"max-width: 37.5em; background-color: #ffffff; padding: 45px\"\n    >\n      <tbody>\n        <tr style=\"width: 100%\">\n          <td>\n            {{#if storeInfo.logo}}\n              <img\n                alt=\"{{storeInfo.logo.alt}}\"\n                height=\"{{storeInfo.logo.height}}\"\n                src=\"{{storeInfo.logo.src}}\"\n                style=\"\n                  display: block;\n                  outline: none;\n                  border: none;\n                  text-decoration: none;\n                \"\n                width=\"{{storeInfo.logo.width}}\"\n              />\n            {{/if}}\n            <table\n              align=\"center\"\n              width=\"100%\"\n              border=\"0\"\n              cellpadding=\"0\"\n              cellspacing=\"0\"\n              role=\"presentation\"\n            >\n              <tbody>\n                <tr>\n                  <td>\n                    <p\n                      style=\"font-size:16px;line-height:26px;margin:16px 0;font-family:&#x27;Open Sans&#x27;, &#x27;HelveticaNeue-Light&#x27;, &#x27;Helvetica Neue Light&#x27;, &#x27;Helvetica Neue&#x27;, Helvetica, Arial, &#x27;Lucida Grande&#x27;, sans-serif;font-weight:300;color:#000000\"\n                    >\n                      Hi,\n                    </p>\n                    <p\n                      style=\"font-size:16px;line-height:26px;margin:16px 0;font-family:&#x27;Open Sans&#x27;, &#x27;HelveticaNeue-Light&#x27;, &#x27;Helvetica Neue Light&#x27;, &#x27;Helvetica Neue&#x27;, Helvetica, Arial, &#x27;Lucida Grande&#x27;, sans-serif;font-weight:300;color:#000000\"\n                    >\n                      Someone recently requested a password change for your\n                      {{storeInfo.storeName}} account. If this was you, you can set a new\n                      password here:\n                    </p>\n                    <a\n                      href=\"{{resetPasswordUrl}}\"\n                      style=\"background-color:#000000;border-radius:4px;color:#ffffff;font-family:&#x27;Open Sans&#x27;, &#x27;Helvetica Neue&#x27;, Arial;font-size:15px;text-decoration:none;text-align:center;display:inline-block;width:210px;padding:14px 7px 14px 7px;line-height:100%;max-width:100%\"\n                      target=\"_blank\"\n                      ><span\n                        ><!--[if mso\n                          ]><i\n                            style=\"\n                              letter-spacing: 7px;\n                              mso-font-width: -100%;\n                              mso-text-raise: 21;\n                            \"\n                            hidden\n                            >&nbsp;</i\n                          ><!\n                        [endif]--></span\n                      ><span\n                        style=\"\n                          max-width: 100%;\n                          display: inline-block;\n                          line-height: 120%;\n                          mso-padding-alt: 0px;\n                          mso-text-raise: 10.5px;\n                        \"\n                        >Reset password</span\n                      ><span\n                        ><!--[if mso\n                          ]><i\n                            style=\"letter-spacing: 7px; mso-font-width: -100%\"\n                            hidden\n                            >&nbsp;</i\n                          ><!\n                        [endif]--></span\n                      ></a\n                    >\n                    <p\n                      style=\"font-size:16px;line-height:26px;margin:16px 0;font-family:&#x27;Open Sans&#x27;, &#x27;HelveticaNeue-Light&#x27;, &#x27;Helvetica Neue Light&#x27;, &#x27;Helvetica Neue&#x27;, Helvetica, Arial, &#x27;Lucida Grande&#x27;, sans-serif;font-weight:300;color:#000000\"\n                    >\n                      If you don&#x27;t want to change your password or\n                      didn&#x27;t request this, just ignore and delete this\n                      message.\n                    </p>\n                    <p\n                      style=\"font-size:16px;line-height:26px;margin:16px 0;font-family:&#x27;Open Sans&#x27;, &#x27;HelveticaNeue-Light&#x27;, &#x27;Helvetica Neue Light&#x27;, &#x27;Helvetica Neue&#x27;, Helvetica, Arial, &#x27;Lucida Grande&#x27;, sans-serif;font-weight:300;color:#000000\"\n                    >\n                      To keep your account secure, please don&#x27;t forward\n                      this email to anyone.\n                    </p>\n\n                    <p\n                      style=\"font-size:16px;line-height:26px;margin:16px 0;font-family:&#x27;Open Sans&#x27;, &#x27;HelveticaNeue-Light&#x27;, &#x27;Helvetica Neue Light&#x27;, &#x27;Helvetica Neue&#x27;, Helvetica, Arial, &#x27;Lucida Grande&#x27;, sans-serif;font-weight:300;color:#000000\"\n                    >\n                      Happy Shopping!\n                    </p>\n                  </td>\n                </tr>\n              </tbody>\n            </table>\n          </td>\n        </tr>\n      </tbody>\n    </table>\n  </body>\n</html>`;\n\nexport async function sendResetPasswordEmail(email, existingCustomer, token) {\n  const subject = translate('Reset your password');\n  const url = buildAbsoluteUrl('resetPasswordPage');\n  const resetPasswordUrl = `${url}?token=${token}`;\n  let template = '';\n  const config = getConfig('system.notification_emails.reset_password');\n  // Check if templatePath is set in config and the file is exists. It should be relative to the project root\n  if (config?.templatePath) {\n    const filePath = path.join(CONSTANTS.ROOTPATH, config.templatePath);\n    try {\n      await fs.access(filePath);\n      template = await fs.readFile(filePath, 'utf8');\n    } catch (error) {\n      debug(\n        `Reset password email template file not found at path: ${filePath}. Using default template.`\n      );\n      template = TEMPLATE;\n    }\n  } else {\n    template = TEMPLATE;\n  }\n  const dynamicData = await getValue('resetPasswordEmailData', {\n    token,\n    resetPasswordUrl,\n    customer: existingCustomer\n  });\n  const args = await getValue(\n    'resetPasswordEmailArguments',\n    {\n      to: email,\n      subject,\n      template,\n      data: dynamicData\n    },\n    { customer: existingCustomer, token }\n  );\n  await sendEmail('reset_password', args);\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/customer/subscribers/customer_registered/sendWelcomeEmail.ts",
    "content": "import fs from 'fs/promises';\nimport path from 'path';\nimport { CONSTANTS } from '../../../../lib/helpers.js';\nimport { translate } from '../../../../lib/locale/translate/translate.js';\nimport { debug, error } from '../../../../lib/log/logger.js';\nimport {\n  buildEmailBodyFromTemplate,\n  sendEmail\n} from '../../../../lib/mail/emailHelper.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport { getValue } from '../../../../lib/util/registry.js';\nimport { EventData } from '../../../../types/event.js';\n\nconst TEMPLATE = `<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html dir=\"ltr\" lang=\"en\">\n  <head>\n    {{#if storeInfo.logo}}\n      <link rel=\"preload\" as=\"image\" href=\"{{storeInfo.logo.url}}\" />\n    {{/if}}\n    <meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\" />\n  </head>\n  <div\n    style=\"\n      display: none;\n      overflow: hidden;\n      line-height: 1px;\n      opacity: 0;\n      max-height: 0;\n      max-width: 0;\n    \"\n  >\n    Welcome {{customer.full_name}}. Thanks for joining us.\n    <div>\n       ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿\n    </div>\n  </div>\n\n  <body\n    style=\"\n      background-color: #ffffff;\n      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,\n        Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;\n    \"\n  >\n    <table\n      align=\"center\"\n      width=\"100%\"\n      border=\"0\"\n      cellpadding=\"0\"\n      cellspacing=\"0\"\n      role=\"presentation\"\n      style=\"max-width: 37.5em; margin: 0 auto; padding: 20px 0 48px\"\n    >\n      <tbody>\n        <tr style=\"width: 100%\">\n          <td>\n            {{#if storeInfo.logo}}\n              <img\n                alt=\"{{storeInfo.logo.alt}}\"\n                height=\"{{storeInfo.logo.height}}\"\n                src=\"{{storeInfo.logo.src}}\"\n                style=\"\n                  display: block;\n                  outline: none;\n                  border: none;\n                  text-decoration: none;\n                \"\n                width=\"{{storeInfo.logo.width}}\"\n              />\n            {{/if}}\n            <p style=\"font-size: 16px; line-height: 26px; margin: 16px 0\">\n              Hi {{customer.full_name}},\n            </p>\n            <p style=\"font-size: 16px; line-height: 26px; margin: 16px 0\">\n              Welcome to {{storeInfo.storeName}}. We are excited to have you on board. You\n              can now start shopping and enjoy the best deals on the market.\n            </p>\n            <table\n              align=\"center\"\n              width=\"100%\"\n              border=\"0\"\n              cellpadding=\"0\"\n              cellspacing=\"0\"\n              role=\"presentation\"\n              style=\"text-align: center\"\n            >\n              <tbody>\n                <tr>\n                  <td>\n                    <a\n                      href=\"{{storeInfo.homeUrl}}\"\n                      style=\"\n                        background-color: #000000;\n                        border-radius: 3px;\n                        color: #ffffff;\n                        font-size: 16px;\n                        text-decoration: none;\n                        text-align: center;\n                        display: inline-block;\n                        padding: 12px 12px 12px 12px;\n                        line-height: 100%;\n                        max-width: 100%;\n                      \"\n                      target=\"_blank\"\n                      ><span\n                        ><!--[if mso\n                          ]><i\n                            style=\"\n                              letter-spacing: 12px;\n                              mso-font-width: -100%;\n                              mso-text-raise: 18;\n                            \"\n                            hidden\n                            >&nbsp;</i\n                          ><!\n                        [endif]--></span\n                      ><span\n                        style=\"\n                          max-width: 100%;\n                          display: inline-block;\n                          line-height: 120%;\n                          mso-padding-alt: 0px;\n                          mso-text-raise: 9px;\n                        \"\n                        >Start shopping</span\n                      ><span\n                        ><!--[if mso\n                          ]><i\n                            style=\"letter-spacing: 12px; mso-font-width: -100%\"\n                            hidden\n                            >&nbsp;</i\n                          ><!\n                        [endif]--></span\n                      ></a\n                    >\n                  </td>\n                </tr>\n              </tbody>\n            </table>\n            <p style=\"font-size: 16px; line-height: 26px; margin: 16px 0\">\n              Best,<br />The {{storeInfo.storeName}}\n            </p>\n            <hr\n              style=\"\n                width: 100%;\n                border: none;\n                border-top: 1px solid #000000;\n                margin: 20px 0;\n              \"\n            />\n            <p\n              style=\"\n                font-size: 12px;\n                line-height: 24px;\n                margin: 16px 0;\n                color: #000000;\n              \"\n            >\n              {{storeInfo.address.street}}, {{storeInfo.address.city}}, {{storeInfo.address.state}} {{storeInfo.address.zip}}, {{storeInfo.address.country}}\n            </p>\n          </td>\n        </tr>\n      </tbody>\n    </table>\n  </body>\n</html>\n`;\nexport default async function sendCustomerWelcomeEmail(\n  data: EventData<'customer_registered'>\n) {\n  try {\n    const email = data.email;\n    const subject = translate('Welcome to our store!');\n    const config = getConfig('system.notification_emails.customer_welcome', {\n      enabled: true\n    });\n    if (config?.enabled === false) {\n      return;\n    }\n    let template;\n    if (config?.templatePath) {\n      const filePath = path.join(CONSTANTS.ROOTPATH, config.templatePath);\n      try {\n        await fs.access(filePath);\n        template = await fs.readFile(filePath, 'utf8');\n      } catch (error) {\n        debug(\n          `Customer welcome email template file not found at path: ${filePath}. Using default template.`\n        );\n        template = TEMPLATE;\n      }\n    } else {\n      template = TEMPLATE;\n    }\n    const dynamicData = await getValue('customerWelcomeEmailData', {\n      customer: data\n    });\n    const args = await getValue(\n      'customerWelcomeEmailArguments',\n      {\n        to: email,\n        subject,\n        template,\n        data: dynamicData\n      },\n      { customer: data }\n    );\n    await sendEmail('customer_welcome', args);\n  } catch (e) {\n    error(e);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/api/adminGraphql/[bodyParser]graphql.js",
    "content": "import schema from '../../services/buildSchema.js';\nimport { graphqlMiddleware } from '../../services/graphqlMiddleware.js';\n\nconst middleware = graphqlMiddleware(schema);\nexport default middleware;\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/api/adminGraphql/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/api/adminGraphql/route.json",
    "content": "{\n  \"methods\": [\"GET\", \"POST\"],\n  \"path\": \"/admin/graphql\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/api/graphql/[auth]removeUser[graphql].ts",
    "content": "import { EvershopRequest } from '../../../../types/request.js';\nimport { setContextValue } from '../../services/contextHelper.js';\n\nexport default (request: EvershopRequest, response, next) => {\n  // This /api/graphql endpoint is used for customer GraphQL requests\n  // Set user to undefined as this is for customers\n  setContextValue(request, 'user', undefined);\n  next();\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/api/graphql/[bodyParser]graphql.js",
    "content": "import { isDevelopmentMode } from '../../../../lib/util/isDevelopmentMode.js';\nimport schema, {\n  rebuildStoreFrontSchema\n} from '../../services/buildStoreFrontSchema.js';\nimport { graphqlMiddleware } from '../../services/graphqlMiddleware.js';\n\nlet middleware;\nif (isDevelopmentMode()) {\n  const devSchema = await rebuildStoreFrontSchema();\n  middleware = graphqlMiddleware(devSchema);\n} else {\n  middleware = graphqlMiddleware(schema);\n}\nexport default middleware;\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/api/graphql/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/api/graphql/route.json",
    "content": "{\n  \"methods\": [\"GET\", \"POST\"],\n  \"path\": \"/graphql\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/bootstrap.js",
    "content": "import '../../modules/graphql/services/buildSchema.js';\n\nexport default () => {};\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/graphql/types/Query/Query.graphql",
    "content": "\"\"\"\nThe root query type, represents all of the entry points into our object graph.\n\"\"\"\ntype Query {\n  hello: String!\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/graphql/types/Query/Query.resolvers.js",
    "content": "export default {\n  Query: {\n    hello: () => 'Hello EverShop!'\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/pages/global/[bodyParser,notFound]buildQuery[graphql].js",
    "content": "import { readFileSync } from 'fs';\nimport path from 'path';\nimport JSON5 from 'json5';\nimport uniqid from 'uniqid';\nimport { getDevMiddleware } from '../../../../bin/lib/devEnvHelper.js';\nimport { CONSTANTS } from '../../../../lib/helpers.js';\nimport { error } from '../../../../lib/log/logger.js';\nimport { getRoutes } from '../../../../lib/router/Router.js';\nimport { get } from '../../../../lib/util/get.js';\nimport isDevelopmentMode from '../../../../lib/util/isDevelopmentMode.js';\nimport isProductionMode from '../../../../lib/util/isProductionMode.js';\nimport { getRouteBuildPath } from '../../../../lib/webpack/getRouteBuildPath.js';\nimport { getEnabledWidgets } from '../../../../lib/widget/widgetManager.js';\nimport { loadWidgetInstances } from '../../../cms/services/widget/loadWidgetInstances.js';\nimport { getContextValue } from '../../services/contextHelper.js';\n\nexport default async (request, response, next) => {\n  try {\n    let query;\n    getContextValue(request, 'dummy', null);\n    if (isDevelopmentMode()) {\n      const route = request.currentRoute;\n      const devMiddleware = getDevMiddleware(route.isAdmin);\n      const { outputFileSystem } = devMiddleware.context;\n\n      // Wait for webpack to be ready\n      await new Promise((resolve) => {\n        devMiddleware.waitUntilValid(() => resolve());\n      });\n\n      const { stats } = devMiddleware.context;\n      if (!stats) {\n        throw new Error('Webpack stats not available');\n      }\n\n      const jsonWebpackStats = stats.toJson();\n      const { outputPath } = jsonWebpackStats;\n\n      const queryPath = path.resolve(outputPath, `query-${route.id}.graphql`);\n      query = outputFileSystem.readFileSync(queryPath, 'utf8');\n    } else if (isProductionMode()) {\n      const routes = getRoutes();\n      const route = request.currentRoute;\n      const subPath = getRouteBuildPath(route);\n      const queryPath = path.resolve(\n        CONSTANTS.BUILDPATH,\n        subPath,\n        'server',\n        'query.graphql'\n      );\n      query = readFileSync(queryPath, 'utf8');\n    }\n    const widgetInstances = await loadWidgetInstances(request);\n    const enabledWidgets = getEnabledWidgets();\n    if (query) {\n      // Parse the query\n      // Use regex to replace \"getContextValue_'base64 encoded string'\"\n      // from the query to the actual function\n      const regex = /\\\\\"getContextValue_([a-zA-Z0-9+/=]+)\\\\\"/g;\n      query = query.replace(regex, (match, p1) => {\n        const base64 = p1;\n        const decoded = Buffer.from(base64, 'base64').toString('ascii');\n        let value = eval(`getContextValue(request, ${decoded})`);\n\n        // JSON sringify without adding double quotes to the property name\n        value = JSON5.stringify(value, { quote: '\"' });\n        // Escape the value so we can insert it into the query\n        if (value) {\n          value = value.replace(/\"/g, '\\\\\"');\n        }\n        return value;\n      });\n      const json = JSON5.parse(query);\n      // We need to get the list of applicable widgets and remove all the queries and variable that are not used\n      let applicableWidgets = [];\n      const { currentRoute } = request;\n      if (\n        currentRoute?.isAdmin === false ||\n        ['widgetNew', 'widgetEdit'].includes(currentRoute?.id)\n      ) {\n        applicableWidgets = widgetInstances.map((widget) => {\n          const widgetSpecs = enabledWidgets.find(\n            (enabledWidget) => enabledWidget.type === widget.type\n          );\n          const componentPath = currentRoute?.isAdmin\n            ? widgetSpecs.settingComponent\n            : widgetSpecs.component;\n          const componentKey = currentRoute?.isAdmin\n            ? widgetSpecs.settingComponentKey\n            : widgetSpecs.componentKey;\n          return {\n            uuid: widget.uuid,\n            type: widget.type,\n            settings: widget.settings,\n            component: componentPath,\n            componentKey\n          };\n        });\n      }\n\n      let operation = 'query Query';\n      const { fragments } = json;\n      const { propsMap } = json;\n      let queryStr = '';\n      let variables;\n      if (applicableWidgets.length > 0) {\n        applicableWidgets.forEach((widget) => {\n          const widgetKey = widget.componentKey;\n          let widgetQuery = json.queries[widgetKey];\n          // Deep clone the widget variables\n          let widgetVariables = JSON.parse(\n            JSON.stringify(json.variables[widgetKey])\n          );\n          const regex = /\\\\\"getWidgetSetting_([a-zA-Z0-9+/=]*)\\\\\"/g;\n          widgetQuery = widgetQuery.replace(regex, (match, p1) => {\n            const base64 = p1;\n            const decoded = Buffer.from(base64, 'base64').toString('ascii');\n            // Accept max 2 arguments from the decoded string, the fist one is the path to the setting object (a.b.c) and the second one is the default value\n            // Get the actual value from the setting of the current widget\n\n            const path = decoded.split(',')[0];\n            const defaultValue = decoded.split(',')[1] || undefined;\n            let value = get(widget.settings, path, defaultValue);\n            // JSON sringify without adding double quotes to the property name\n            value = JSON5.stringify(value, { quote: '\"' });\n            // Escape the value so we can insert it into the query\n            if (value) {\n              value = value.replace(/\"/g, '\\\\\"');\n            }\n            return value;\n          });\n          // Use regex to find if there is any variable inside the query by checking if there is any string started with `$variable_` and no special character after that. If there is a match, we will replace it with the another unique name to make it unique\n          const variableRegex = /\\$variable_([a-zA-Z0-9]+)/g;\n          const variableMatch = widgetQuery.match(variableRegex);\n          const variableList = [];\n          if (variableMatch) {\n            widgetQuery = widgetQuery.replace(variableRegex, (match, p1) => {\n              const newId = `${uniqid()}`;\n              variableList.push({\n                origin: `variable_${p1}`,\n                new: `variable_${newId}`\n              });\n              // Check if p1 already exists in the variableList\n              // If it does, we will replace it with the newId\n              const test = variableList.find(\n                (variable) => variable.origin === `variable_${p1}`\n              );\n              if (test) {\n                return `$${test.new}`;\n              } else {\n                return `$variable_${newId}`;\n              }\n            });\n          }\n\n          // Now we need to process the widgetVariables.values and widgetVariables.defs\n          const widgetVariablesValues = Object.keys(\n            widgetVariables.values\n          ).reduce((acc, key) => {\n            const check = variableList.find(\n              (variable) => variable.origin === key\n            );\n            if (check) {\n              const variableRegex = /getWidgetSetting_([a-zA-Z0-9+/=]*)/g;\n              const v = widgetVariables.values[key];\n              if (typeof v === 'string') {\n                // A regext matching \"getContextValue_'base64 encoded string'\"\n                // Check if the value is a string and contains the getContextValue_ string\n                const variableMatch = v.match(variableRegex);\n                if (variableMatch) {\n                  // Replace the getContextValue_ string with the actual function\n                  const base64 = variableMatch[0].replace(\n                    variableRegex,\n                    (match, p1) => p1\n                  );\n                  const decoded = Buffer.from(base64, 'base64')\n                    .toString('ascii')\n                    .split(',')[0]\n                    .replace(/['\"]+/g, '');\n\n                  let actualValue;\n                  if (!decoded.trim()) {\n                    actualValue = widget.settings;\n                  } else {\n                    actualValue = get(widget.settings, decoded);\n                  }\n                  acc[check.new] = actualValue;\n                }\n              } else {\n                acc[check.new] = v;\n              }\n            }\n            return acc;\n          }, {});\n          const widgetVariablesDefs = widgetVariables.defs.reduce(\n            (acc, variable) => {\n              const check = variableList.find(\n                (v) => v.origin === variable.alias\n              );\n              if (check) {\n                acc.push({\n                  ...variable,\n                  alias: check.new\n                });\n              } else {\n                acc.push(variable);\n              }\n              return acc;\n            },\n            []\n          );\n          widgetVariables = {\n            values: widgetVariablesValues,\n            defs: widgetVariablesDefs\n          };\n          const originPropsMap = propsMap[widgetKey]; // [{origin: 'real field name', alias: 'bbbb'}, {origin: 'real field name', alias: 'ccc'}]\n          const widgetUUID = `e${widget.uuid.replace(/-/g, '')}`;\n          propsMap[widgetUUID] = [];\n          originPropsMap.forEach((prop) => {\n            const newAlias = `e${uniqid()}`;\n            widgetQuery = widgetQuery.replace(prop.alias, newAlias);\n            propsMap[widgetUUID].push({\n              origin: prop.origin,\n              alias: newAlias\n            });\n          });\n          json.queries[widgetUUID] = widgetQuery;\n          json.variables[widgetUUID] = widgetVariables;\n          // Now we merge the queries to the query as the string,\n          queryStr = Object.keys(json.queries).reduce((acc, key) => {\n            if (\n              !enabledWidgets.find(\n                (widget) =>\n                  widget.componentKey === key ||\n                  widget.settingComponentKey === key\n              )\n            ) {\n              acc += `\\n${json.queries[key]} `;\n            }\n            return acc;\n          }, '');\n\n          // Now we merge the variables\n          variables = Object.keys(json.variables).reduce(\n            (acc, key) => {\n              if (\n                !enabledWidgets.find(\n                  (widget) =>\n                    widget.componentKey === key ||\n                    widget.settingComponentKey === key\n                )\n              ) {\n                acc.values = { ...acc.values, ...json.variables[key].values };\n                acc.defs = [...acc.defs, ...json.variables[key].defs];\n              }\n              return acc;\n            },\n            { values: {}, defs: [] }\n          );\n        });\n      } else {\n        // Just delete resolvable queries and variables\n        queryStr = Object.keys(json.queries).reduce((acc, key) => {\n          if (\n            enabledWidgets.find(\n              (widget) =>\n                widget.componentKey === key ||\n                widget.settingComponentKey === key\n            )\n          ) {\n            delete json.queries[key];\n          } else {\n            acc += `\\n${json.queries[key]} `;\n          }\n          return acc;\n        }, '');\n\n        variables = Object.keys(json.variables).reduce(\n          (acc, key) => {\n            if (\n              enabledWidgets.find(\n                (widget) =>\n                  widget.componentKey === key ||\n                  widget.settingComponentKey === key\n              )\n            ) {\n              delete json.variables[key];\n            } else {\n              acc.values = { ...acc.values, ...json.variables[key].values };\n              acc.defs = [...acc.defs, ...json.variables[key].defs];\n            }\n            return acc;\n          },\n          { values: {}, defs: [] }\n        );\n      }\n      if (variables.defs.length > 0) {\n        const variablesString = variables.defs\n          .map((variable) => `$${variable.alias}: ${variable.type}`)\n          .join(', ');\n        operation += `(${variablesString})`;\n\n        // Now we need loop through all variables value (variables.values) object and Use regex to replace \"getContextValue_'base64 encoded string'\" from the query to the actual function\n        Object.keys(variables.values).forEach((key) => {\n          const value = variables.values[key];\n          if (typeof value === 'string') {\n            // A regext matching \"getContextValue_'base64 encoded string'\"\n            const variableRegex = /getContextValue_([a-zA-Z0-9+/=]+)/g;\n            // Check if the value is a string and contains the getContextValue_ string\n            const variableMatch = value.match(variableRegex);\n            if (variableMatch) {\n              // Replace the getContextValue_ string with the actual function\n              const base64 = variableMatch[0].replace(\n                variableRegex,\n                (match, p1) => p1\n              );\n              const decoded = Buffer.from(base64, 'base64').toString('ascii');\n\n              const actualValue = eval(`getContextValue(request, ${decoded})`);\n              variables.values[key] = actualValue;\n            }\n          }\n        });\n      }\n      request.body.graphqlQuery = `${operation} { ${queryStr} } ${fragments}`;\n      request.body.graphqlVariables = variables.values;\n      request.body.propsMap = propsMap;\n      next();\n    }\n  } catch (e) {\n    error(e);\n    throw error;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/pages/global/[buildQuery]graphql[response].js",
    "content": "import {\n  execute,\n  NoUnusedFragmentsRule,\n  parse,\n  specifiedRules,\n  validateSchema\n} from 'graphql';\nimport { debug } from '../../../../lib/log/logger.js';\nimport { isDevelopmentMode } from '../../../../lib/util/isDevelopmentMode.js';\nimport adminSchema, { rebuildSchema } from '../../services/buildSchema.js';\nimport storeFrontSchema, {\n  rebuildStoreFrontSchema\n} from '../../services/buildStoreFrontSchema.js';\nimport { getContext } from '../../services/contextHelper.js';\nimport { graphqlErrorMessageFormat } from '../../services/graphqlErrorMessageFormat.js';\n\nexport default async function graphql(request, response, next) {\n  const { currentRoute } = request;\n  let schema;\n  if (isDevelopmentMode()) {\n    schema =\n      currentRoute && currentRoute.isAdmin\n        ? await rebuildSchema()\n        : await rebuildStoreFrontSchema();\n  } else {\n    schema =\n      currentRoute && currentRoute.isAdmin ? adminSchema : storeFrontSchema;\n  }\n  // TODO: Should we wait for previous async middlewares?\n  try {\n    const { body } = request;\n    const { graphqlQuery, graphqlVariables, propsMap } = body;\n    if (!graphqlQuery) {\n      next();\n    } else {\n      // Try remove all white space and line break\n      const query = graphqlQuery.replace(/(\\r\\n|\\n|\\r|\\s)/gm, '');\n      if (query === 'queryQuery{}') {\n        // TODO: oh no, so dirty. find a better way to check if the query is empty\n        next();\n      } else {\n        const document = parse(graphqlQuery);\n        // Validate the query\n        const validationErrors = validateSchema(\n          schema,\n          document,\n          specifiedRules.filter((rule) => rule !== NoUnusedFragmentsRule)\n        );\n        if (validationErrors.length > 0) {\n          const formatedErrorMessage = graphqlErrorMessageFormat(\n            graphqlQuery,\n            validationErrors[0].locations[0].line,\n            validationErrors[0].locations[0].column\n          );\n          debug(`GraphQL validation error: ${formatedErrorMessage}`);\n          next(validationErrors[0]);\n        } else {\n          const context = getContext(request);\n          // Add current user to context\n          context.user = request.locals.user;\n          // Add current customer to context\n          context.customer = request.locals.customer;\n          const data = await execute({\n            schema,\n            contextValue: context,\n            document,\n            variableValues: graphqlVariables\n          });\n          if (data.errors) {\n            next(data.errors[0]);\n          } else {\n            response.locals = response.locals || {};\n            response.locals.graphqlResponse = JSON.parse(\n              JSON.stringify(data.data)\n            );\n            // Get id and props from the queryRaw object and assign to response.locals.propsMap\n            response.locals.propsMap = propsMap;\n            next();\n          }\n        }\n      }\n    }\n  } catch (error) {\n    next(error);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/pages/global/bodyParser[buildQuery].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, () => {\n    bodyParser.urlencoded({ extended: true })(request, response, next);\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/services/buildResolvers.js",
    "content": "import path from 'path';\nimport url from 'url';\nimport { loadFiles } from '@graphql-tools/load-files';\nimport { mergeResolvers } from '@graphql-tools/merge';\nimport { getEnabledExtensions } from '../../../bin/extension/index.js';\nimport { CONSTANTS } from '../../../lib/helpers.js';\nimport { isDevelopmentMode } from '../../../lib/util/isDevelopmentMode.js';\n\nexport async function buildResolvers(isAdmin = false) {\n  const typeSources = [\n    path.join(CONSTANTS.MODULESPATH, '*/graphql/types/**/*.resolvers.{js,ts}')\n  ];\n\n  const extensions = getEnabledExtensions();\n  extensions.forEach((extension) => {\n    typeSources.push(\n      path.join(extension.path, 'graphql/types/**/*.resolvers.{js,ts}')\n    );\n  });\n\n  // Using loadFiles with an array of glob patterns instead of joining them\n  const resolvers = mergeResolvers(\n    await loadFiles(typeSources, {\n      ignoredExtensions: isAdmin\n        ? ['.ts', '.d.ts']\n        : ['.admin.resolvers.js', '.admin.resolvers.ts', '.ts', '.d.ts'],\n      requireMethod: async (path) => {\n        if (isDevelopmentMode()) {\n          const module = await import(\n            `${url.pathToFileURL(path)}?t=${Date.now()}`\n          );\n          return module;\n        } else {\n          const module = await import(url.pathToFileURL(path));\n          return module;\n        }\n      }\n    })\n  );\n\n  return resolvers;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/services/buildSchema.js",
    "content": "import { makeExecutableSchema } from '@graphql-tools/schema';\nimport { buildResolvers } from './buildResolvers.js';\nimport { buildTypeDefs } from './buildTypes.js';\n\nconst resolvers = await buildResolvers(true);\nconst schema = makeExecutableSchema({\n  typeDefs: buildTypeDefs(true),\n  resolvers\n});\n\nexport async function rebuildSchema() {\n  const resolvers = await buildResolvers(true);\n  const schema = makeExecutableSchema({\n    typeDefs: buildTypeDefs(true),\n    resolvers: resolvers\n  });\n  return schema;\n}\n\nexport default schema;\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/services/buildStoreFrontSchema.js",
    "content": "import { makeExecutableSchema } from '@graphql-tools/schema';\nimport { buildResolvers } from './buildResolvers.js';\nimport { buildTypeDefs } from './buildTypes.js';\n\nconst resolvers = await buildResolvers(false);\nconst schema = makeExecutableSchema({\n  typeDefs: buildTypeDefs(),\n  resolvers\n});\n\nexport async function rebuildStoreFrontSchema() {\n  const resolvers = await buildResolvers(false);\n  const schema = makeExecutableSchema({\n    typeDefs: buildTypeDefs(),\n    resolvers: resolvers\n  });\n  return schema;\n}\n\nexport default schema;\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/services/buildTypes.js",
    "content": "import path from 'path';\nimport { loadFilesSync } from '@graphql-tools/load-files';\nimport { mergeTypeDefs } from '@graphql-tools/merge';\nimport { getEnabledExtensions } from '../../../bin/extension/index.js';\nimport { CONSTANTS } from '../../../lib/helpers.js';\n\nexport function buildTypeDefs(isAdmin = false) {\n  const typeSources = [\n    path.join(CONSTANTS.MODULESPATH, '*/graphql/types/**/*.graphql')\n  ];\n\n  const extensions = getEnabledExtensions();\n  extensions.forEach((extension) => {\n    typeSources.push(path.join(extension.path, 'graphql/types/**/*.graphql'));\n  });\n  const typeDefs = mergeTypeDefs(\n    typeSources.map((source) =>\n      loadFilesSync(source, {\n        ignoredExtensions: isAdmin ? [] : ['.admin.graphql']\n      })\n    )\n  );\n\n  return typeDefs;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/services/contextHelper.ts",
    "content": "import { Application } from 'express';\nimport { get } from '../../../lib/util/get.js';\nimport { EvershopRequest } from '../../../types/request.js';\n\nexport function getContextValue<T>(\n  request: EvershopRequest,\n  key: string,\n  defaultValue?: T,\n  toString: boolean = false\n): T {\n  // We check if the request have it, if not we try to get it from the app\n  // So if you set a context which already available in app, the old one will be overwited\n  const value = get(\n    request,\n    `locals.context.${key}`,\n    get(request.app.locals, `context.${key}`, defaultValue)\n  );\n  return toString ? value.toString() : value;\n}\n/**\n * Pass the app instance if you want to set a application level value\n * (This value will be shared across all request)\n * Pass the request instance if you want to set a request level value\n */\nexport function setContextValue<T>(\n  requestOrApp: EvershopRequest | Application,\n  key: string,\n  value: T\n): void {\n  requestOrApp.locals = requestOrApp.locals || {};\n  requestOrApp.locals.context = requestOrApp.locals.context || {};\n  requestOrApp.locals.context[key] = value; // We just overwrite the value if it already exists\n}\n\nexport function getContext(request: EvershopRequest): Record<string, any> {\n  if (!request.app) {\n    throw new Error('A request object must be provided');\n  }\n  const requestLevelContext = get(request, 'locals.context', {});\n  const appLevelContext = get(request.app.locals, 'context', {});\n  return { ...appLevelContext, ...requestLevelContext };\n}\n\nexport function hasContextValue(\n  request: EvershopRequest,\n  key: string\n): boolean {\n  const requestLevelContext = get(request, 'locals.context', {});\n  const appLevelContext = get(request.app.locals, 'context', {});\n\n  return key in requestLevelContext || key in appLevelContext;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/services/graphqlErrorMessageFormat.js",
    "content": "export function graphqlErrorMessageFormat(\n  inputString,\n  lineNumber,\n  columnNumber\n) {\n  if (!inputString) {\n    return '';\n  }\n  const lines = inputString.split('\\n');\n  if (lineNumber <= 0 || lineNumber > lines.length) {\n    return 'Invalid line number';\n  }\n\n  const zeroBasedLineNumber = lineNumber - 1;\n  const line = lines[zeroBasedLineNumber];\n  if (columnNumber <= 0 || columnNumber > line.length) {\n    return 'Invalid column number for the given line';\n  }\n  const zeroBasedColumnNumber = columnNumber - 1;\n  const startIndex = zeroBasedColumnNumber;\n  let endIndex = line.indexOf(')', startIndex);\n\n  if (endIndex === -1) {\n    endIndex = line.length; // If the special character is not found, highlight until the end of the line\n  }\n\n  const ANSI_RESET = '\\x1b[0m';\n  const ANSI_HIGHLIGHT = '\\x1b[33m';\n\n  // Apply highlighting to the text\n  const highlightedText = line.substring(startIndex, endIndex);\n  const highlightedLine = line.replace(\n    highlightedText,\n    `${ANSI_HIGHLIGHT}${highlightedText}${ANSI_RESET}`\n  );\n\n  return highlightedLine;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/services/graphqlMiddleware.js",
    "content": "import { execute, parse, validateSchema } from 'graphql';\nimport { OK } from '../../../lib/util/httpStatus.js';\nimport { getContext } from './contextHelper.js';\n\nexport const graphqlMiddleware = (schema) =>\n  async function graphqlMiddleware(request, response, next) {\n    const { body } = request;\n    const { query, variables } = body;\n    try {\n      if (!query) {\n        response.status(OK).json({\n          data: {}\n        });\n        return;\n      }\n\n      const document = parse(query);\n      // Validate the query\n      const validationErrors = validateSchema(schema, document);\n      if (validationErrors.length > 0) {\n        next(new Error(validationErrors[0].message));\n      } else {\n        const data = await execute({\n          schema,\n          contextValue: getContext(request),\n          document,\n          variableValues: variables\n        });\n        if (data.errors) {\n          // Create an Error instance with message and stack trace\n          next(data.errors[0]);\n        } else {\n          response.status(OK).json({\n            data: data.data\n          });\n        }\n      }\n    } catch (error) {\n      next(error);\n    }\n  };\n"
  },
  {
    "path": "packages/evershop/src/modules/graphql/services/index.ts",
    "content": "export * from './contextHelper.js';\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/cancelOrder/[context]borderParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/cancelOrder/cancelOrder.js",
    "content": "import { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport cancelOrder from '../../services/cancelOrder.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { reason } = request.body;\n    await cancelOrder(request.params.id, reason);\n    response.status(OK);\n    response.json({\n      data: {}\n    });\n  } catch (err) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: err.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/cancelOrder/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"reason\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"reason\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"reason\": \"Reason is mandatory\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/cancelOrder/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/orders/:id/cancel\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/createShipment/[context]borderParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/createShipment/createShipment.ts",
    "content": "import { rollback, startTransaction } from '@evershop/postgres-query-builder';\nimport { debug } from '../../../../lib/log/logger.js';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport createShipment from '../../services/createShipment.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { id } = Array.isArray(request.params.id)\n    ? { id: request.params.id[0] }\n    : { id: request.params.id };\n  const { carrier, tracking_number } = request.body;\n  try {\n    const shipment = await createShipment(id, carrier, tracking_number);\n    response.status(OK);\n    response.$body = {\n      data: shipment\n    };\n    next();\n  } catch (e) {\n    debug(e);\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/createShipment/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"carrier\": {\n      \"type\": \"string\"\n    },\n    \"tracking_number\": {\n      \"type\": \"string\"\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/createShipment/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/orders/:id/shipments\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/lifetimesales/loadData.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\n\nexport default async (request, response, next) => {\n  const query = select();\n  query\n    .from('order')\n    .select('grand_total', 'total')\n    .select('payment_status')\n    .select('shipment_status');\n  const results = await query.execute(pool);\n\n  let total = 0;\n  let cancelled = 0;\n  let completed = 0;\n  results.forEach((result) => {\n    total += parseFloat(result.total);\n    if (\n      result.payment_status === 'paid' &&\n      result.shipment_status === 'delivered'\n    ) {\n      completed += 1;\n    }\n    if (\n      result.payment_status === 'cancelled' &&\n      result.shipment_status === 'cancelled'\n    ) {\n      cancelled += 1;\n    }\n  });\n  const currency = getConfig('shop.currency', 'USD');\n  const language = getConfig('shop.language', 'en');\n  const formatedTotal = new Intl.NumberFormat(language, {\n    style: 'currency',\n    currency\n  }).format(total);\n\n  response.json({\n    orders: results.length,\n    total: formatedTotal,\n    completed_percentage:\n      results.length === 0 ? 0 : Math.round((completed / results.length) * 100),\n    cancelled_percentage:\n      results.length === 0 ? 0 : Math.round((cancelled / results.length) * 100)\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/lifetimesales/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/lifetimesales\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/markDelivered/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/markDelivered/markDelivered.ts",
    "content": "import {\n  commit,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport addOrderActivityLog from '../../services/addOrderActivityLog.js';\nimport { updateShipmentStatus } from '../../services/updateShipmentStatus.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { order_id } = request.body;\n  try {\n    const order = await select()\n      .from('order')\n      .where('order_id', '=', order_id)\n      .load(connection);\n\n    if (!order) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid order id'\n        }\n      });\n      return;\n    }\n    const shipment = await select()\n      .from('shipment')\n      .where('shipment_order_id', '=', order_id)\n      .load(connection);\n\n    if (!shipment) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Shipment was not created'\n        }\n      });\n      return;\n    }\n\n    await updateShipmentStatus(order_id, 'delivered', connection);\n    /* Add an activity log message */\n    await addOrderActivityLog(\n      order.order_id,\n      'Order delivered',\n      false,\n      connection\n    );\n    await commit(connection);\n    response.status(OK);\n    response.$body = {\n      data: {\n        order_id: order.order_id,\n        shipment_id: shipment.shipment_id\n      }\n    };\n    next();\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/markDelivered/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"order_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/markDelivered/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/deliveries\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/salestatistic/loadData.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport dayjs from 'dayjs';\nimport { pool } from '../../../../lib/postgres/connection.js';\n\nexport default async (request, response, next) => {\n  response.$body = [];\n  const { period = 'weekly' } = request.query;\n  let i = 5;\n  const result = [];\n  const today = dayjs().format('YYYY-MM-DD').toString();\n  while (i >= 0) {\n    result[i] = {};\n\n    if (period === 'daily') {\n      result[i].from = `${dayjs(today)\n        .subtract(5 - i, 'day')\n        .format('YYYY-MM-DD')\n        .toString()} 00:00:00`;\n      result[i].to = `${dayjs(today)\n        .subtract(5 - i, 'day')\n        .format('YYYY-MM-DD')\n        .toString()} 23:59:59`;\n    }\n    if (period === 'weekly') {\n      result[i].from = `${dayjs(today)\n        .subtract(5 - i, 'week')\n        .startOf('week')\n        .format('YYYY-MM-DD')\n        .toString()} 00:00:00`;\n      result[i].to = `${dayjs(today)\n        .subtract(5 - i, 'week')\n        .endOf('week')\n        .format('YYYY-MM-DD')\n        .toString()} 23:59:59`;\n    }\n    if (period === 'monthly') {\n      result[i].from = `${dayjs(today)\n        .subtract(5 - i, 'month')\n        .startOf('month')\n        .format('YYYY-MM-DD')\n        .toString()} 00:00:00`;\n      result[i].to = `${dayjs(today)\n        .subtract(5 - i, 'month')\n        .endOf('month')\n        .format('YYYY-MM-DD')\n        .toString()} 23:59:59`;\n    }\n    i -= 1;\n  }\n  const results = await Promise.all(\n    result.map(async (element) => {\n      const query = select();\n      query\n        .from('order')\n        .select('SUM (grand_total)', 'total')\n        .select('COUNT (order_id)', 'count')\n        .where('created_at', '>=', element.from)\n        .and('created_at', '<=', element.to);\n      query.limit(0, 1);\n      const queryResult = await query.execute(pool);\n      return {\n        total: queryResult[0].total || 0,\n        count: queryResult[0].count,\n        time: dayjs(element.to).format('MMM DD').toString()\n      };\n    })\n  );\n  response.json(results);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/salestatistic/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/salestatistic\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/updateShipment/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/updateShipment/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"carrier\": {\n      \"type\": \"string\"\n    },\n    \"tracking_number\": {\n      \"type\": \"string\"\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/updateShipment/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/orders/:order_id/shipments/:shipment_id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/api/updateShipment/updateShipment.ts",
    "content": "import {\n  commit,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection, pool } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport addOrderActivityLog from '../../services/addOrderActivityLog.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { order_id, shipment_id } = request.params;\n  const { carrier, tracking_number } = request.body;\n  try {\n    const order = await select()\n      .from('order')\n      .where('uuid', '=', order_id)\n      .load(connection);\n\n    if (!order) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid order id'\n        }\n      });\n      return;\n    }\n    const shipment = await select()\n      .from('shipment')\n      .where('uuid', '=', shipment_id)\n      .load(connection);\n\n    if (!shipment) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid shipment id'\n        }\n      });\n      return;\n    }\n    await update('shipment')\n      .given({\n        carrier,\n        tracking_number\n      })\n      .where('uuid', '=', shipment_id)\n      .execute(connection);\n    /* Add an activity log message */\n    await addOrderActivityLog(\n      order.order_id,\n      'Shipment information updated',\n      false,\n      connection\n    );\n\n    await commit(connection);\n\n    // Load updated shipment\n    const updatedShipment = await select()\n      .from('shipment')\n      .where('shipment_order_id', '=', order.order_id)\n      .and('uuid', '=', shipment_id)\n      .load(pool);\n\n    response.status(OK);\n    response.$body = {\n      data: updatedShipment\n    };\n    next();\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/bootstrap.ts",
    "content": "import config from 'config';\nimport { defaultPaginationFilters } from '../../lib/util/defaultPaginationFilters.js';\nimport { hookAfter } from '../../lib/util/hookable.js';\nimport { merge } from '../../lib/util/merge.js';\nimport { addProcessor } from '../../lib/util/registry.js';\nimport {\n  CreateOrderResult,\n  SaveOrderArgs,\n  SaveOrderContext\n} from '../checkout/services/orderCreator.js';\nimport createShipment from './services/createShipment.js';\nimport registerDefaultOrderCollectionFilters from './services/registerDefaultOrderCollectionFilters.js';\nimport {\n  changeOrderStatus,\n  resolveOrderStatus\n} from './services/updateOrderStatus.js';\nimport { updateShipmentStatus } from './services/updateShipmentStatus.js';\n\nexport default () => {\n  addProcessor('configurationSchema', (schema) => {\n    merge(schema, {\n      properties: {\n        oms: {\n          type: 'object',\n          properties: {\n            order: {\n              type: 'object',\n              properties: {\n                shipmentStatus: {\n                  type: 'object',\n                  patternProperties: {\n                    '^[a-zA-Z_]+$': {\n                      type: 'object',\n                      properties: {\n                        name: {\n                          type: 'string'\n                        },\n                        badge: {\n                          type: 'string'\n                        },\n                        isDefault: {\n                          type: 'boolean'\n                        },\n                        isCancelable: {\n                          type: 'boolean'\n                        }\n                      },\n                      required: ['name', 'badge']\n                    }\n                  },\n                  additionalProperties: false\n                },\n                paymentStatus: {\n                  type: 'object',\n                  patternProperties: {\n                    '^[a-zA-Z_]+$': {\n                      type: 'object',\n                      properties: {\n                        name: {\n                          type: 'string'\n                        },\n                        badge: {\n                          type: 'string'\n                        },\n                        isDefault: {\n                          type: 'boolean'\n                        },\n                        isCancelable: {\n                          type: 'boolean'\n                        }\n                      },\n                      required: ['name', 'badge']\n                    }\n                  },\n                  additionalProperties: false\n                },\n                status: {\n                  type: 'object',\n                  properties: {\n                    new: {\n                      type: 'object',\n                      properties: {\n                        name: {\n                          type: 'string'\n                        },\n                        badge: {\n                          type: 'string'\n                        },\n                        isDefault: {\n                          type: 'boolean'\n                        },\n                        next: {\n                          type: 'array',\n                          items: {\n                            type: 'string'\n                          }\n                        }\n                      },\n                      required: ['name', 'badge']\n                    },\n                    processing: {\n                      type: 'object',\n                      properties: {\n                        name: {\n                          type: 'string'\n                        },\n                        badge: {\n                          type: 'string'\n                        },\n                        next: {\n                          type: 'array',\n                          items: {\n                            type: 'string'\n                          }\n                        }\n                      },\n                      required: ['name', 'badge']\n                    },\n                    completed: {\n                      type: 'object',\n                      properties: {\n                        name: {\n                          type: 'string'\n                        },\n                        badge: {\n                          type: 'string'\n                        },\n                        next: {\n                          type: 'array',\n                          items: {\n                            type: 'string'\n                          }\n                        }\n                      },\n                      required: ['name', 'badge']\n                    },\n                    canceled: {\n                      type: 'object',\n                      properties: {\n                        name: {\n                          type: 'string'\n                        },\n                        badge: {\n                          type: 'string'\n                        },\n                        next: {\n                          type: 'array',\n                          items: {\n                            type: 'string'\n                          }\n                        }\n                      },\n                      required: ['name', 'badge']\n                    },\n                    closed: {\n                      type: 'object',\n                      properties: {\n                        name: {\n                          type: 'string'\n                        },\n                        badge: {\n                          type: 'string'\n                        },\n                        next: {\n                          type: 'array',\n                          items: {\n                            type: 'string'\n                          }\n                        }\n                      },\n                      required: ['name', 'badge']\n                    }\n                  },\n                  additionalProperties: true\n                },\n                psoMapping: {\n                  type: 'object',\n                  patternProperties: {\n                    '^[a-zA-Z_*]+:[a-zA-Z_*]+$': {\n                      type: 'string'\n                    }\n                  },\n                  additionalProperties: false\n                },\n                reStockAfterCancellation: {\n                  type: 'boolean'\n                }\n              },\n              required: ['shipmentStatus', 'paymentStatus'],\n              additionalProperties: false\n            },\n            carriers: {\n              type: 'object',\n              additionalProperties: {\n                type: 'object',\n                properties: {\n                  name: {\n                    type: 'string'\n                  },\n                  trackingUrl: {\n                    type: 'string'\n                  }\n                },\n                required: ['name']\n              }\n            }\n          }\n        }\n      }\n    });\n    return schema;\n  });\n\n  // Default order configuration\n  const defaultOrderConfig = {\n    order: {\n      shipmentStatus: {\n        pending: {\n          name: 'Pending',\n          badge: 'default',\n          isDefault: true\n        },\n        processing: {\n          name: 'Processing',\n          badge: 'default',\n          isDefault: false\n        },\n        shipped: {\n          name: 'Shipped',\n          badge: 'warning'\n        },\n        delivered: {\n          name: 'Delivered',\n          badge: 'success',\n          isCancelable: false\n        },\n        canceled: {\n          name: 'Canceled',\n          badge: 'destructive',\n          isCancelable: false\n        }\n      },\n      paymentStatus: {\n        pending: {\n          name: 'Pending',\n          badge: 'default',\n          isDefault: true,\n          isCancelable: true\n        },\n        paid: {\n          name: 'Paid',\n          badge: 'success',\n          isCancelable: false\n        },\n        canceled: {\n          name: 'Canceled',\n          badge: 'destructive',\n          isCancelable: true\n        }\n      },\n      status: {\n        new: {\n          name: 'New',\n          badge: 'default',\n          isDefault: true,\n          next: ['processing', 'canceled']\n        },\n        processing: {\n          name: 'Processing',\n          badge: 'default',\n          next: ['completed', 'canceled']\n        },\n        completed: {\n          name: 'Completed',\n          badge: 'success',\n          next: ['closed']\n        },\n        canceled: {\n          name: 'Canceled',\n          badge: 'destructive',\n          next: []\n        },\n        closed: {\n          name: 'Closed',\n          badge: 'outline',\n          next: []\n        }\n      },\n      psoMapping: {\n        'pending:pending': 'new',\n        'pending:*': 'processing',\n        'paid:*': 'processing',\n        'paid:delivered': 'completed',\n        'canceled:*': 'processing',\n        'canceled:canceled': 'canceled'\n      },\n      reStockAfterCancellation: true\n    },\n    carriers: {\n      default: {\n        name: 'Default'\n      },\n      fedex: {\n        name: 'FedEx',\n        trackingUrl: 'https://www.fedex.com/fedextrack/?trknbr={trackingNumber}'\n      },\n      usps: {\n        name: 'USPS',\n        trackingUrl:\n          'https://tools.usps.com/go/TrackConfirmAction?qtc_tLabels1={trackingNumber}'\n      },\n      ups: {\n        name: 'UPS',\n        trackingUrl:\n          'https://www.ups.com/track?loc=en_US&tracknum={trackingNumber}'\n      }\n    }\n  };\n  config.util.setModuleDefaults('oms', defaultOrderConfig);\n\n  // Reigtering the default filters for attribute collection\n  addProcessor(\n    'orderCollectionFilters',\n    registerDefaultOrderCollectionFilters,\n    1\n  );\n  addProcessor<Array<any>>(\n    'orderCollectionFilters',\n    (filters) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n\n  hookAfter(\n    'changePaymentStatus',\n    async (order, orderId, status, connection) => {\n      if (order.status === 'canceled') {\n        throw new Error('Order is already canceled');\n      }\n      if (order.status === 'closed') {\n        throw new Error('Order is already closed');\n      }\n      const orderStatus = resolveOrderStatus(status, order.shipment_status);\n      await changeOrderStatus(orderId, orderStatus, connection);\n    }\n  );\n\n  hookAfter(\n    'changeShipmentStatus',\n    async (order, orderId, status, connection) => {\n      if (order.status === 'canceled') {\n        throw new Error('Order is already canceled');\n      }\n      if (order.status === 'closed') {\n        throw new Error('Order is already closed');\n      }\n      const orderStatus = resolveOrderStatus(order.payment_status, status);\n      await changeOrderStatus(orderId, orderStatus, connection);\n    }\n  );\n\n  hookAfter<SaveOrderContext, CreateOrderResult, SaveOrderArgs>(\n    'saveOrder',\n    async function createShipmentForVirtualProductsOrder(\n      order,\n      cart,\n      connection\n    ) {\n      if (order.no_shipping_required) {\n        // Create a shipment for this order\n        await createShipment(order.uuid, null, null, connection);\n\n        // And update shipment status to delivered\n        await updateShipmentStatus(order.order_id, 'delivered', connection);\n      }\n    }\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/BestSeller/BestSeller.admin.graphql",
    "content": "extend type Product {\n  soldQty: Int\n}\n\nextend type Query {\n  bestSellers: [Product]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/BestSeller/BestSeller.admin.resolvers.js",
    "content": "import { sql } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { getProductsBaseQuery } from '../../../../../modules/catalog/services/getProductsBaseQuery.js';\n\nexport default {\n  Query: {\n    bestSellers: async () => {\n      const query = getProductsBaseQuery();\n      query\n        .leftJoin('order_item')\n        .on('product.product_id', '=', 'order_item.product_id');\n\n      query\n        .select(sql('\"product\".*'))\n        .select(sql('\"product_description\".*'))\n        .select(sql('\"product_inventory\".*'))\n        .select(sql('\"product_image\".*'))\n        .select('SUM(order_item.qty)', 'soldQty')\n        .select('SUM(order_item.product_id)', 'sum')\n        .where('order_item_id', 'IS NOT NULL', null);\n      query\n        .groupBy(\n          'order_item.product_id',\n          'product.product_id',\n          'product_description.product_description_id',\n          'product_inventory.product_inventory_id',\n          'product_image.product_image_id'\n        )\n        .orderBy('soldQty', 'DESC')\n        .limit(0, 5);\n      const results = await query.execute(pool);\n      return results.map((p) => camelCase(p));\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/Carrier/Carrier.admin.graphql",
    "content": "\"\"\"\nThe `Carrier` type defines the shipping carrier.\n\"\"\"\ntype Carrier {\n  name: String!\n  code: String!\n  trackingUrl: String\n}\n\nextend type Query {\n  carriers: [Carrier]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/Carrier/Carrier.admin.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Query: {\n    carriers: () => {\n      const carriers = getConfig('oms.carriers', {});\n      return Object.keys(carriers).map((key) => ({\n        ...carriers[key],\n        code: key\n      }));\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/Order/Order.admin.graphql",
    "content": "extend type Order {\n  customerUrl: String\n  editUrl: String!\n  createShipmentApi: String!\n  cancelApi: String!\n  shipment: Shipment\n}\n\nextend type Shipment {\n  updateShipmentApi: String!\n}\n\nextend type Query {\n  orders(filters: [FilterInput]): OrderCollection\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/Order/Order.admin.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { getOrdersBaseQuery } from '../../../services/getOrdersBaseQuery.js';\nimport { OrderCollection } from '../../../services/OrderCollection.js';\n\nexport default {\n  Query: {\n    orders: async (_, { filters = [] }) => {\n      const query = getOrdersBaseQuery();\n      const root = new OrderCollection(query);\n      await root.init(filters);\n      return root;\n    }\n  },\n  Order: {\n    editUrl: ({ uuid }) => buildUrl('orderEdit', { id: uuid }),\n    createShipmentApi: ({ uuid }) => buildUrl('createShipment', { id: uuid }),\n    cancelApi: ({ uuid }) => buildUrl('cancelOrder', { id: uuid }),\n    customerUrl: async ({ customerId }, _, { pool }) => {\n      const customer = await select()\n        .from('customer')\n        .where('customer_id', '=', customerId)\n        .load(pool);\n      return customer ? buildUrl('customerEdit', { id: customer.uuid }) : null;\n    }\n  },\n  Shipment: {\n    updateShipmentApi: ({ orderUuid, uuid }) =>\n      buildUrl('updateShipment', { order_id: orderUuid, shipment_id: uuid })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/Order/Order.graphql",
    "content": "\"\"\"\nRepresents an Order Address.\n\"\"\"\ntype OrderAddress implements Address {\n  orderAddressId: Int!\n  uuid: String!\n  fullName: String\n  postcode: String\n  telephone: String\n  country: Country\n  province: Province\n  city: String\n  address1: String\n  address2: String\n}\n\n\"\"\"\nRepresents an Order Item.\n\"\"\"\ntype OrderItem implements ShoppingCartItem {\n  orderItemId: ID!\n  uuid: String!\n  orderId: ID!\n  productId: ID!\n  productSku: String!\n  productName: String\n  thumbnail: String\n  noShippingRequired: Boolean!\n  productWeight: Weight!\n  productPrice: Price!\n  productPriceInclTax: Price!\n  qty: Int!\n  finalPrice: Price!\n  finalPriceInclTax: Price!\n  taxPercent: Float!\n  taxAmount: Price!\n  taxAmountBeforeDiscount: Price!\n  discountAmount: Price!\n  lineTotal: Price!\n  subTotal: Price! # Deprecated, this field is equivalent to lineTotal\n  lineTotalWithDiscount: Price!\n  lineTotalWithDiscountInclTax: Price!\n  lineTotalInclTax: Price!\n  total: Price! # Deprecated, this field is equivalent to lineTotalInclTax\n  variantGroupId: Int\n  variantOptions: [VariantOption]\n  productCustomOptions: String\n  productUrl: String\n}\n\n\"\"\"\nRepresents an Order.\n\"\"\"\ntype Order implements ShoppingCart {\n  orderId: ID!\n  uuid: String!\n  orderNumber: String!\n  status: Status\n  items: [OrderItem]\n  noShippingRequired: Boolean!\n  shippingAddress: OrderAddress\n  billingAddress: OrderAddress\n  currency: String!\n  customerId: Int\n  customerGroupId: Int\n  customerEmail: String\n  customerFullName: String\n  userIp: String\n  userId: String\n  coupon: String\n  shippingFeeExclTax: Price!\n  shippingFeeInclTax: Price!\n  shippingTaxAmount: Price!\n  discountAmount: Price!\n  subTotal: Price!\n  subTotalInclTax: Price!\n  subTotalWithDiscount: Price!\n  subTotalWithDiscountInclTax: Price!\n  totalQty: Int!\n  totalWeight: Weight!\n  taxAmount: Price!\n  taxAmountBeforeDiscount: Price!\n  totalTaxAmount: Price!\n  grandTotal: Price!\n  shippingMethod: String\n  shippingMethodName: String\n  shipmentStatus: ShipmentStatus\n  paymentMethod: String\n  paymentMethodName: String\n  paymentStatus: PaymentStatus\n  shippingNote: String\n  createdAt: Date!\n  updatedAt: Date!\n  activities: [Activity]\n  shipment: Shipment\n}\n\n\"\"\"\nRepresents an Order Activity.\n\"\"\"\ntype Activity {\n  orderActivityId: Int!\n  comment: String\n  customerNotified: Int!\n  createdAt: DateTime\n  updatedAt: DateTime\n}\n\n\"\"\"\nRepresents a Shipment.\n\"\"\"\ntype Shipment {\n  shipmentId: Int!\n  uuid: String!\n  carrier: String\n  trackingNumber: String\n  createdAt: DateTime!\n  updatedAt: DateTime\n}\n\n\"\"\"\nRetrieve an list of order.\n\"\"\"\ntype OrderCollection {\n  items: [Order]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\nextend type Customer {\n  orders: [Order]\n}\n\nextend type Query {\n  order(uuid: String!): Order\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/Order/Order.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { getConfig } from '../../../../../lib/util/getConfig.js';\nimport { getOrdersBaseQuery } from '../../../services/getOrdersBaseQuery.js';\n\nexport default {\n  Query: {\n    order: async (_, { uuid }, { pool }) => {\n      const query = getOrdersBaseQuery();\n      query.where('uuid', '=', uuid);\n      const order = await query.load(pool);\n      if (!order) {\n        return null;\n      } else {\n        return camelCase(order);\n      }\n    }\n  },\n  Order: {\n    items: async ({ orderId }, _, { pool }) => {\n      const items = await select()\n        .from('order_item')\n        .where('order_item_order_id', '=', orderId)\n        .execute(pool);\n      return items.map((item) => camelCase(item));\n    },\n    shippingAddress: async ({ shippingAddressId }, _, { pool }) => {\n      const address = await select()\n        .from('order_address')\n        .where('order_address_id', '=', shippingAddressId)\n        .load(pool);\n      return address ? camelCase(address) : null;\n    },\n    billingAddress: async ({ billingAddressId }, _, { pool }) => {\n      const address = await select()\n        .from('order_address')\n        .where('order_address_id', '=', billingAddressId)\n        .load(pool);\n      return address ? camelCase(address) : null;\n    },\n    activities: async ({ orderId }, _, { pool }) => {\n      const query = select().from('order_activity');\n      query.where('order_activity_order_id', '=', orderId);\n      query.orderBy('order_activity_id', 'DESC');\n      const activities = await query.execute(pool);\n      return activities\n        ? activities.map((activity) => camelCase(activity))\n        : null;\n    },\n    shipment: async ({ orderId, uuid }, _, { pool }) => {\n      const shipment = await select()\n        .from('shipment')\n        .where('shipment_order_id', '=', orderId)\n        .load(pool);\n      return shipment ? { ...camelCase(shipment), orderUuid: uuid } : null;\n    },\n    shipmentStatus: ({ shipmentStatus }) => {\n      const statusList = getConfig('oms.order.shipmentStatus', {});\n      const status = statusList[shipmentStatus] || {\n        name: 'Unknown',\n        code: shipmentStatus,\n        badge: 'default'\n      };\n\n      return {\n        ...status,\n        code: shipmentStatus\n      };\n    },\n    paymentStatus: ({ paymentStatus }) => {\n      const statusList = getConfig('oms.order.paymentStatus', {});\n      const status = statusList[paymentStatus] || {\n        name: 'Unknown',\n        code: paymentStatus,\n        badge: 'default'\n      };\n\n      return {\n        ...status,\n        code: paymentStatus\n      };\n    },\n    status: ({ status }) => {\n      const statusList = getConfig('oms.order.status', {});\n      const statusObj = statusList[status] || {\n        name: 'Unknown',\n        code: status,\n        badge: 'default'\n      };\n\n      return {\n        ...statusObj,\n        code: status\n      };\n    }\n  },\n  Customer: {\n    orders: async ({ customerId }, _, { pool }) => {\n      const orders = await select()\n        .from('order')\n        .where('order.customer_id', '=', customerId)\n        .execute(pool);\n      return orders.map((row) => camelCase(row));\n    }\n  },\n  OrderItem: {\n    productUrl: async ({ productId }, _, { pool }) => {\n      const product = await select()\n        .from('product')\n        .where('product_id', '=', productId)\n        .load(pool);\n      return product ? buildUrl('productEdit', { id: product.uuid }) : null;\n    },\n    total: ({ lineTotalInclTax }) =>\n      // This field is deprecated, use lineTotalInclTax instead\n      lineTotalInclTax,\n    subTotal: ({ lineTotal }) =>\n      // This field is deprecated, use lineTotal instead\n      lineTotal,\n    variantOptions: ({ variantOptions }) => {\n      try {\n        return JSON.parse(variantOptions || '[]').map((option) => ({\n          ...camelCase(option),\n          attributeId: parseInt(option.attribute_id, 10),\n          optionId: parseInt(option.option_id, 10)\n        }));\n      } catch (error) {\n        return [];\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/PaymentTransaction/PaymentTransaction.admin.graphql",
    "content": "\"\"\"\nRepresents a payment transaction\n\"\"\"\ntype PaymentTransaction {\n  paymentTransactionId: Int!\n  uuid: String!\n  transactionId: String\n  transactionType: String!\n  amount: Price!\n  parentTransactionId: String\n  paymentAction: String!\n  additionalInformation: String!\n  createdAt: String!\n}\n\nextend type Order {\n  paymentTransactions: [PaymentTransaction]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/PaymentTransaction/PaymentTransaction.admin.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\n\nexport default {\n  Order: {\n    paymentTransactions: async ({ orderId }, _, { pool }) => {\n      const items = await select()\n        .from('payment_transaction')\n        .where('payment_transaction_order_id', '=', orderId)\n        .execute(pool);\n      return items.map((item) => camelCase(item));\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/Status/Status.graphql",
    "content": "\"\"\"\nRepresents a payment status.\n\"\"\"\ntype Status {\n  name: String\n  code: String\n  badge: String\n}\n\n\"\"\"\nRepresents a payment status.\n\"\"\"\ntype PaymentStatus {\n  name: String\n  code: String\n  badge: String\n  isDefault: Boolean\n  isCancelable: Boolean\n}\n\n\"\"\"\nRepresents a shipment status.\n\"\"\"\ntype ShipmentStatus {\n  name: String\n  code: String\n  badge: String\n  isDefault: Boolean\n  isCancelable: Boolean\n}\n\nextend type Query {\n  shipmentStatusList: [ShipmentStatus]\n  paymentStatusList: [PaymentStatus]\n  statusList: [Status]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/graphql/types/Status/Status.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Query: {\n    statusList: () => {\n      const statusList = getConfig('oms.order.status', {});\n      return Object.keys(statusList).map((key) => ({\n        ...statusList[key],\n        code: key\n      }));\n    },\n    shipmentStatusList: () => {\n      const statusList = getConfig('oms.order.shipmentStatus', {});\n      return Object.keys(statusList).map((key) => ({\n        ...statusList[key],\n        code: key\n      }));\n    },\n    paymentStatusList: () => {\n      const statusList = getConfig('oms.order.paymentStatus', {});\n      return Object.keys(statusList).map((key) => ({\n        ...statusList[key],\n        code: key\n      }));\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/migration/Version-1.0.0.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ALTER COLUMN \"shipment_status\" DROP DEFAULT'\n  );\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ALTER COLUMN \"shipment_status\" DROP NOT NULL'\n  );\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ALTER COLUMN \"shipment_status\" SET DEFAULT NULL'\n  );\n\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ALTER COLUMN \"payment_status\" DROP DEFAULT'\n  );\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ALTER COLUMN \"payment_status\" DROP NOT NULL'\n  );\n  await execute(\n    connection,\n    'ALTER TABLE \"order\" ALTER COLUMN \"payment_status\" SET DEFAULT NULL'\n  );\n\n  // Update shipment_status to processing if it is unfullfilled\n  await execute(\n    connection,\n    \"UPDATE \\\"order\\\" SET shipment_status = 'processing' WHERE shipment_status = 'unfullfilled'\"\n  );\n\n  // Update shipment_status to shipped if it is fullfilled\n  await execute(\n    connection,\n    \"UPDATE \\\"order\\\" SET shipment_status = 'shipped' WHERE shipment_status = 'fullfilled'\"\n  );\n\n  // Rename carrier_name to carrier from shipment table\n  await execute(\n    connection,\n    'ALTER TABLE \"shipment\" RENAME COLUMN \"carrier_name\" TO \"carrier\"'\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/migration/Version-1.0.1.js",
    "content": "import { execute, select } from '@evershop/postgres-query-builder';\nimport { warning } from '../../../lib/log/logger.js';\nimport { resolveOrderStatus } from '../services/updateOrderStatus.js';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `ALTER TABLE \"order\" ADD COLUMN IF NOT EXISTS \"status\" varchar DEFAULT NULL;`\n  );\n\n  // Mapping the order status for legacy orders\n  const orders = await select().from('order').execute(connection);\n\n  for (const order of orders) {\n    try {\n      const status = resolveOrderStatus(\n        order.payment_status,\n        order.shipment_status\n      );\n      await execute(\n        connection,\n        `UPDATE \"order\" SET status = '${status}' WHERE order_id = ${order.order_id};`\n      );\n    } catch (err) {\n      warning(\n        `Error while updating order status for order_id: ${order.order_id}. \n        This happened because we can not resolve the order status. You may need to update the order status manually.`\n      );\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/migration/Version-1.0.2.ts",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `ALTER TABLE \"order_item\" ADD COLUMN IF NOT EXISTS no_shipping_required boolean DEFAULT FALSE`\n  );\n\n  await execute(\n    connection,\n    `ALTER TABLE \"order\" ADD COLUMN IF NOT EXISTS no_shipping_required boolean DEFAULT FALSE`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/all/OmsMenuGroup.jsx",
    "content": "import { NavigationItemGroup } from '@components/admin/NavigationItemGroup.js';\nimport { Package } from 'lucide-react';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport default function OmsMenuGroup({ orderGrid }) {\n  return (\n    <NavigationItemGroup\n      id=\"omsMenuGroup\"\n      name=\"Sale\"\n      items={[\n        {\n          Icon: Package,\n          url: orderGrid,\n          title: 'Orders'\n        }\n      ]}\n    />\n  );\n}\n\nOmsMenuGroup.propTypes = {\n  orderGrid: PropTypes.string.isRequired\n};\n\nexport const layout = {\n  areaId: 'adminMenu',\n  sortOrder: 30\n};\n\nexport const query = `\n  query Query {\n    orderGrid: url(routeId:\"orderGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/dashboard/Bestcustomers.jsx",
    "content": "import { useAppState } from '@components/common/context/app';\nimport {\n  Card,\n  CardAction,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport default function BestCustomers({ listUrl, setting }) {\n  const context = useAppState();\n  const customers = context.bestCustomers || [];\n\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Best customers</CardTitle>\n        <CardDescription>\n          A list of customers who have placed the most orders\n        </CardDescription>\n        <CardAction>\n          <a href={listUrl} className=\"text-sm text-primary hover:underline\">\n            View all customers\n          </a>\n        </CardAction>\n      </CardHeader>\n      <CardContent>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>Full name</TableHead>\n              <TableHead>Orders</TableHead>\n              <TableHead>Total</TableHead>\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {customers.map((c, i) => {\n              const grandTotal = new Intl.NumberFormat('en', {\n                style: 'currency',\n                currency: setting.storeCurrency\n              }).format(c.total);\n              return (\n                <TableRow key={i}>\n                  <TableCell>\n                    <a href={c.editUrl || ''}>{c.full_name}</a>\n                  </TableCell>\n                  <TableCell>{c.orders}</TableCell>\n                  <TableCell>{grandTotal}</TableCell>\n                </TableRow>\n              );\n            })}\n          </TableBody>\n        </Table>\n      </CardContent>\n    </Card>\n  );\n}\n\nBestCustomers.propTypes = {\n  setting: PropTypes.shape({\n    storeCurrency: PropTypes.string\n  }).isRequired,\n  listUrl: PropTypes.string.isRequired\n};\n\nexport const query = `\n  query Query {\n    setting {\n      storeCurrency\n    }\n    listUrl: url(routeId: \"productGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/dashboard/Bestsellers.scss",
    "content": ".bestsellers {\n  td,\n  th {\n    &:first-child {\n      padding-left: 0;\n    }\n    &:last-child {\n      padding-right: 0;\n      text-align: right;\n    }\n  }\n  tr {\n    &:first-child {\n      td {\n        border-top: 0;\n      }\n    }\n    &:last-child {\n      td {\n        border-bottom: 0;\n      }\n    }\n  }\n}\n.skeleton-wrapper-bestsellers {\n  width: 100%;\n  display: flex;\n  justify-content: center;\n  flex-direction: column;\n  padding: 20px;\n  box-sizing: border-box;\n  .skeleton:empty {\n    width: 100%;\n    height: 50px;\n    margin-top: 10px;\n    cursor: progress;\n    background: linear-gradient(0.25turn, transparent, #fff, transparent),\n      linear-gradient(#eee, #eee);\n    background-repeat: no-repeat;\n    animation: loading 1.5s infinite;\n  }\n\n  @keyframes loading {\n    to {\n      background-position: 315px 0, 0 0, 0 190px, 50px 195px;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/dashboard/Bestsellers.tsx",
    "content": "import {\n  Card,\n  CardAction,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\nimport './Bestsellers.scss';\nimport { Image } from '@components/common/Image.js';\nimport { ProductNoThumbnail } from '@components/common/ProductNoThumbnail.js';\nimport {\n  Table,\n  TableRow,\n  TableBody,\n  TableCell\n} from '@components/common/ui/Table.js';\n\ninterface BestSellersProps {\n  bestSellers: Array<{\n    name: string;\n    price: {\n      regular: {\n        value: number;\n        text: string;\n      };\n    };\n    soldQty: number;\n    image?: {\n      url?: string;\n    };\n    editUrl?: string;\n  }>;\n  listUrl: string;\n}\n\nexport default function BestSellers({\n  bestSellers,\n  listUrl\n}: BestSellersProps) {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Best Sellers</CardTitle>\n        <CardDescription>A list of best selling products</CardDescription>\n        <CardAction>\n          <a href={listUrl} className=\"text-sm text-primary hover:underline\">\n            View All Products\n          </a>\n        </CardAction>\n      </CardHeader>\n      <CardContent>\n        <Table>\n          <TableBody>\n            {bestSellers.length === 0 && (\n              <TableRow>\n                <TableCell align=\"left\">\n                  Look like you just started. No bestsellers yet.\n                </TableCell>\n                <TableCell> </TableCell>\n              </TableRow>\n            )}\n            {bestSellers.map((p, i) => (\n              <TableRow key={i}>\n                <TableCell>\n                  <div className=\" flex justify-left\">\n                    <div className=\"flex justify-start gap-2 items-center\">\n                      <div className=\"grid-thumbnail text-border border border-divider p-2 rounded\">\n                        {p.image?.url && (\n                          <Image\n                            src={p.image.url}\n                            alt={p.name}\n                            width={50}\n                            height={50}\n                          />\n                        )}\n                        {!p.image?.url && (\n                          <ProductNoThumbnail width={50} height={50} />\n                        )}\n                      </div>\n                      <div>\n                        <a\n                          href={p.editUrl || ''}\n                          className=\"font-semibold hover:underline\"\n                        >\n                          {p.name}\n                        </a>\n                      </div>\n                    </div>\n                  </div>\n                </TableCell>\n                <TableCell />\n                <TableCell>{p.price.regular.text}</TableCell>\n                <TableCell>{p.soldQty} sold</TableCell>\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query {\n    bestSellers {\n      name\n      price {\n        regular {\n          value\n          text\n        }\n      }\n      soldQty\n      image {\n        url\n      }\n      editUrl\n    }\n    listUrl: url(routeId: \"productGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/dashboard/Lifetimesales.jsx",
    "content": "import {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { toast } from 'react-toastify';\nimport { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts';\nimport './Lifetimesales.scss';\n\nconst COLORS = ['#aee9d1', '#fed3d1', '#a4e8f2'];\n\nconst Dot = ({ variant }) => {\n  let bgColor = 'bg-gray-400';\n  if (variant === 'info') {\n    bgColor = 'bg-blue-400';\n  } else if (variant === 'success') {\n    bgColor = 'bg-green-400';\n  } else if (variant === 'critical') {\n    bgColor = 'bg-red-400';\n  }\n  return <span className={`w-3 h-3 rounded-full ${bgColor} inline-block`} />;\n};\n\nDot.propTypes = {\n  variant: PropTypes.oneOf(['info', 'success', 'critical'])\n};\n\nexport default function LifetimeSale({ api }) {\n  const [data, setData] = React.useState({});\n  const [fetching, setFetching] = React.useState(true);\n  const { orders, total, completed_percentage, cancelled_percentage } = data;\n\n  const chartData = [\n    { name: 'Completed', value: completed_percentage },\n    { name: 'Cancelled', value: cancelled_percentage },\n    {\n      name: 'Others',\n      value: 100 - completed_percentage - cancelled_percentage\n    }\n  ];\n\n  React.useEffect(() => {\n    if (window !== undefined) {\n      fetch(api, {\n        method: 'GET',\n        headers: {\n          'Content-Type': 'application/json'\n        }\n      })\n        .then((response) => response.json())\n        .then((json) => {\n          setData(json);\n          setFetching(false);\n        })\n        .catch((error) => {\n          toast.error(error.message);\n        });\n    }\n  }, []);\n\n  if (fetching) {\n    return (\n      <Card title=\"Lifetime Sales\">\n        <CardHeader>\n          <CardTitle>Lifetime Sales</CardTitle>\n        </CardHeader>\n        <CardContent>\n          <div className=\"skeleton-wrapper-lifetime\">\n            <div className=\"skeleton\" />\n            <div className=\"skeleton\" />\n            <div className=\"skeleton\" />\n            <div className=\"skeleton\" />\n          </div>\n        </CardContent>\n        <CardContent>\n          <div className=\"skeleton-wrapper-lifetime\">\n            <div className=\"skeleton-chart\" />\n          </div>\n        </CardContent>\n      </Card>\n    );\n  } else {\n    return (\n      <Card title=\"Lifetime Sales\">\n        <CardHeader>\n          <CardTitle>Lifetime Sales</CardTitle>\n          <CardDescription>\n            Overview of total sales and order status over the lifetime of your\n          </CardDescription>\n        </CardHeader>\n        <CardContent>\n          <div className=\"grid grid-cols-1 gap-2\">\n            <div className=\"flex space-x-2 items-center\">\n              <Dot variant=\"info\" />\n              <div className=\"self-center\">{orders} orders</div>\n            </div>\n            <div className=\"flex space-x-2 items-center\">\n              <Dot variant=\"info\" />\n              <div className=\"self-center\">{total} lifetime sale</div>\n            </div>\n            <div className=\"flex space-x-2 items-center\">\n              <Dot variant=\"success\" />\n              <div className=\"self-center\">\n                {completed_percentage}% of orders completed\n              </div>\n            </div>\n            <div className=\"flex space-x-2 items-center\">\n              <Dot variant=\"critical\" />\n              <div className=\"self-center\">\n                {cancelled_percentage}% of orders cancelled\n              </div>\n            </div>\n          </div>\n        </CardContent>\n        <CardContent>\n          <div style={{ height: '200px' }}>\n            <ResponsiveContainer width=\"100%\" height=\"100%\">\n              <PieChart>\n                <Pie\n                  data={chartData}\n                  labelLine={false}\n                  fill=\"#8884d8\"\n                  dataKey=\"value\"\n                  label\n                >\n                  {chartData.map((entry, index) => (\n                    <Cell\n                      key={`cell-${index}`}\n                      fill={COLORS[index % COLORS.length]}\n                    />\n                  ))}\n                </Pie>\n              </PieChart>\n            </ResponsiveContainer>\n          </div>\n        </CardContent>\n      </Card>\n    );\n  }\n}\n\nLifetimeSale.propTypes = {\n  api: PropTypes.string.isRequired\n};\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    api: url(routeId: \"lifetimesales\")    \n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/dashboard/Lifetimesales.scss",
    "content": ".skeleton-wrapper-lifetime {\n  width: 100%;\n  display: flex;\n  justify-content: center;\n  flex-direction: column;\n  .skeleton-chart:empty {\n    width: 200px;\n    height: 200px;\n    border-radius: 50%;\n    cursor: progress;\n    background: linear-gradient(0.25turn, transparent, #fff, transparent),\n      linear-gradient(#eee, #eee);\n    background-repeat: no-repeat;\n    animation: loading 1.5s infinite;\n  }\n  .skeleton:empty {\n    width: 100%;\n    height: 15px;\n    margin-top: 5px;\n    cursor: progress;\n    background: linear-gradient(0.25turn, transparent, #fff, transparent),\n      linear-gradient(#eee, #eee);\n    background-repeat: no-repeat;\n    animation: loading 1.5s infinite;\n  }\n  @keyframes loading {\n    to {\n      background-position: 315px 0, 0 0, 0 190px, 50px 195px;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/dashboard/Statistic.jsx",
    "content": "import {\n  Card,\n  CardAction,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n  CardContent\n} from '@components/common/ui/Card.js';\nimport PropTypes from 'prop-types';\nimport React, { useEffect, useState } from 'react';\nimport { toast } from 'react-toastify';\nimport {\n  Area,\n  AreaChart,\n  ResponsiveContainer,\n  Tooltip,\n  XAxis,\n  YAxis\n} from 'recharts';\nimport './Statistic.scss';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport { Button } from '@components/common/ui/Button.js';\n\nexport default function SaleStatistic({ api }) {\n  const [data, setData] = useState([]);\n  const [period, setPeriod] = useState('monthly');\n  const [fetching, setFetching] = useState(true);\n\n  useEffect(() => {\n    if (window !== undefined) {\n      fetch(`${api}?period=${period}`, {\n        method: 'GET',\n        headers: {\n          'Content-Type': 'application/json'\n        }\n      })\n        .then((response) => response.json())\n        .then((json) => {\n          setData(json);\n          setFetching(false);\n        })\n        .catch((error) => {\n          toast.error(error.message);\n        });\n    }\n  }, [period]);\n\n  if (fetching) {\n    return (\n      <Card title=\"Sale Statistics\">\n        <CardHeader>\n          <CardTitle>Sale Statistics</CardTitle>\n          <CardDescription>\n            Overview of sales data over selected periods\n          </CardDescription>\n        </CardHeader>\n        <div className=\"skeleton-wrapper-statistic\">\n          <div className=\"skeleton\" />\n        </div>\n      </Card>\n    );\n  } else {\n    return (\n      <Card>\n        <CardHeader>\n          <CardTitle>Sale Statistics</CardTitle>\n          <CardDescription>\n            Overview of sales data over selected periods\n          </CardDescription>\n          <CardAction>\n            <ButtonGroup>\n              <Button onClick={() => setPeriod('daily')} variant={'outline'}>\n                {period === 'daily' ? (\n                  <span className=\"text-primary\">Daily</span>\n                ) : (\n                  'Daily'\n                )}\n              </Button>\n              <Button onClick={() => setPeriod('weekly')} variant={'outline'}>\n                {period === 'weekly' ? (\n                  <span className=\"text-primary\">Weekly</span>\n                ) : (\n                  'Weekly'\n                )}\n              </Button>\n              <Button onClick={() => setPeriod('monthly')} variant={'outline'}>\n                {period === 'monthly' ? (\n                  <span className=\"text-primary\">Monthly</span>\n                ) : (\n                  'Monthly'\n                )}\n              </Button>\n            </ButtonGroup>\n          </CardAction>\n        </CardHeader>\n        <CardContent>\n          {data.length === 0 ? null : (\n            <ResponsiveContainer width=\"100%\" height={300}>\n              <AreaChart\n                data={data}\n                margin={{\n                  top: 5,\n                  right: 0,\n                  left: -25,\n                  bottom: 5\n                }}\n              >\n                <XAxis dataKey=\"time\" />\n                <YAxis />\n                <Tooltip />\n                <Area\n                  type=\"monotone\"\n                  dataKey=\"value\"\n                  stackId=\"1\"\n                  stroke=\"#8884d8\"\n                  fill=\"#8884d8\"\n                />\n                <Area\n                  type=\"monotone\"\n                  dataKey=\"count\"\n                  stackId=\"1\"\n                  stroke=\"#82ca9d\"\n                  fill=\"#82ca9d\"\n                />\n              </AreaChart>\n            </ResponsiveContainer>\n          )}\n        </CardContent>\n      </Card>\n    );\n  }\n}\n\nSaleStatistic.propTypes = {\n  api: PropTypes.string.isRequired\n};\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    api: url(routeId: \"salestatistic\")    \n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/dashboard/Statistic.scss",
    "content": ".skeleton-wrapper-statistic {\n  width: 100%;\n  display: flex;\n  justify-content: center;\n  padding-bottom: 20px;\n  .skeleton:empty {\n    width: 580px;\n    height: 300px;\n    cursor: progress;\n    background: linear-gradient(0.25turn, transparent, #fff, transparent),\n      linear-gradient(#eee, #eee);\n    background-repeat: no-repeat;\n    animation: loading 1.5s infinite;\n  }\n\n  @keyframes loading {\n    to {\n      background-position: 315px 0, 0 0, 0 190px, 50px 195px;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/Activities.jsx",
    "content": "import { DateTime } from 'luxon';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport default function Activities({ order: { activities = [] } }) {\n  const dailyActivities = [];\n  activities.forEach((element) => {\n    const current = dailyActivities[dailyActivities.length - 1];\n    if (!current) {\n      dailyActivities.push({\n        time: element.createdAt.value,\n        date: element.createdAt.date,\n        activities: [\n          {\n            comment: element.comment,\n            customerNotified: element.customerNotified,\n            time: element.createdAt.time\n          }\n        ]\n      });\n    } else if (\n      DateTime.fromSQL(element.createdAt.value).startOf('day') ===\n      DateTime.fromSQL(current.time).startOf('day')\n    ) {\n      current.activities.push({\n        comment: element.comment,\n        customerNotified: element.customerNotified,\n        time: element.createdAt.time\n      });\n    } else {\n      dailyActivities.push({\n        date: element.createdAt.date,\n        activities: [\n          {\n            comment: element.comment,\n            customerNotified: element.customerNotified,\n            time: element.createdAt.time\n          }\n        ]\n      });\n    }\n  });\n\n  return (\n    <div className=\"mt-5\">\n      <h3 className=\"text-base font-semibold pb-5 border-b border-divider\">\n        Activities\n      </h3>\n      <ul className=\"relative py-5 mt-5 before:absolute before:content-[''] before:block before:h-full before:w-0.5 before:top-0 before:left-[0.563rem] before:bg-divider\">\n        {dailyActivities.map((group, i) => (\n          <li key={i} className=\"mt-12 first:mt-0\">\n            <span className=\"uppercase pl-7 text-muted-foreground text-sm\">\n              {group.date}\n            </span>\n            <ul className=\"mt-5 space-y-4\">\n              {group.activities.map((a, k) => (\n                <li key={k} className=\"flex items-center gap-0\">\n                  <span className=\"block w-[0.813rem] h-[0.813rem] bg-border rounded-full z-10 border-2 border-background shrink-0\" />\n                  <div className=\"flex-1 px-6\">\n                    <span className=\"block text-sm\">{a.comment}</span>\n                    {parseInt(a.customerNotified, 10) === 1 && (\n                      <span className=\"block text-muted-foreground italic text-sm mt-1\">\n                        Customer was notified\n                      </span>\n                    )}\n                  </div>\n                  <span className=\"text-muted-foreground text-sm shrink-0\">\n                    {a.time}\n                  </span>\n                </li>\n              ))}\n            </ul>\n          </li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n\nActivities.propTypes = {\n  order: PropTypes.shape({\n    activities: PropTypes.arrayOf(\n      PropTypes.shape({\n        comment: PropTypes.string,\n        customerNotified: PropTypes.number,\n        createdAt: PropTypes.shape({\n          value: PropTypes.string,\n          timezone: PropTypes.string,\n          date: PropTypes.string,\n          time: PropTypes.string\n        })\n      })\n    )\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 30\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      activities {\n        comment\n        customerNotified\n        createdAt {\n          value\n          timezone\n          date: text(format: \"LLL dd\")\n          time: text(format: \"t\")\n        }\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/AddTrackingButton.tsx",
    "content": "import { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\n\ninterface AddTrackingButtonProps {\n  order: {\n    noShippingRequired: boolean;\n    shipment: {\n      carrier: string;\n      trackingNumber: string;\n      updateShipmentApi: string;\n    };\n    createShipmentApi: string;\n  };\n  carriers: {\n    value: string;\n    label: string;\n  }[];\n}\nexport default function AddTrackingButton({\n  order: { noShippingRequired, shipment },\n  carriers\n}: AddTrackingButtonProps) {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const form = useForm();\n  if (noShippingRequired || !shipment) {\n    return null;\n  } else {\n    return (\n      <>\n        <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n          <DialogTrigger>\n            <Button\n              title=\"Edit Tracking Info\"\n              variant=\"outline\"\n              onClick={() => {\n                setDialogOpen(true);\n              }}\n            >\n              Edit Tracking Info\n            </Button>\n          </DialogTrigger>\n          <DialogContent>\n            <DialogHeader>\n              <DialogTitle>Edit Tracking Info</DialogTitle>\n            </DialogHeader>\n            <Form\n              form={form}\n              id=\"editTrackingInfo\"\n              method=\"PATCH\"\n              action={shipment.updateShipmentApi}\n              submitBtn={false}\n              onSuccess={() => {\n                location.reload();\n              }}\n            >\n              <div className=\"grid grid-cols-2 gap-2\">\n                <div>\n                  <InputField\n                    type=\"text\"\n                    name=\"tracking_number\"\n                    label=\"Tracking number\"\n                    placeholder=\"Tracking number\"\n                    defaultValue={shipment.trackingNumber || ''}\n                    required\n                    validation={{\n                      required: 'Tracking number is required'\n                    }}\n                  />\n                </div>\n                <div>\n                  <SelectField\n                    name=\"carrier\"\n                    label=\"Carrier\"\n                    defaultValue={shipment.carrier || ''}\n                    required\n                    options={carriers}\n                    validation={{\n                      required: 'Carrier is required'\n                    }}\n                  />\n                </div>\n              </div>\n              <div className=\"flex justify-end\">\n                <div className=\"grid grid-cols-2 gap-2\"></div>\n              </div>\n            </Form>\n            <DialogFooter>\n              <DialogClose>\n                <Button\n                  title=\"Cancel\"\n                  variant=\"outline\"\n                  onClick={() => {\n                    setDialogOpen(false);\n                  }}\n                >\n                  Cancel\n                </Button>\n              </DialogClose>\n              <Button\n                title=\"Save\"\n                variant=\"default\"\n                isLoading={form.formState.isSubmitting}\n                onClick={async () => {\n                  (\n                    document.getElementById(\n                      'editTrackingInfo'\n                    ) as HTMLFormElement\n                  ).dispatchEvent(\n                    new Event('submit', { cancelable: true, bubbles: true })\n                  );\n                }}\n              >\n                Save\n              </Button>\n            </DialogFooter>\n          </DialogContent>\n        </Dialog>\n      </>\n    );\n  }\n}\n\nexport const layout = {\n  areaId: 'order_actions',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      noShippingRequired\n      shipment {\n        shipmentId\n        carrier\n        trackingNumber\n        updateShipmentApi\n      }\n      createShipmentApi\n    },\n    carriers {\n      label: name\n      value: code\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/CancelButton.tsx",
    "content": "import { Form } from '@components/common/form/Form.js';\nimport { TextareaField } from '@components/common/form/TextareaField.js';\nimport RenderIfTrue from '@components/common/RenderIfTrue.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\nimport { toast } from 'react-toastify';\n\ninterface CancelButtonProps {\n  order: {\n    cancelApi: string;\n    paymentStatus: {\n      code: string;\n      isCancelable: boolean;\n    };\n    shipmentStatus: {\n      code: string;\n      isCancelable: boolean;\n    };\n  };\n}\nexport default function CancelButton({\n  order: { cancelApi, paymentStatus, shipmentStatus }\n}: CancelButtonProps) {\n  const form = useForm();\n  return (\n    <RenderIfTrue\n      condition={\n        paymentStatus.isCancelable !== false &&\n        shipmentStatus.isCancelable !== false\n      }\n    >\n      <Dialog>\n        <DialogTrigger>\n          <Button variant=\"destructive\">Cancel Order</Button>\n        </DialogTrigger>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>Cancel Order</DialogTitle>\n          </DialogHeader>\n          <Form\n            form={form}\n            id=\"cancelReason\"\n            method=\"POST\"\n            action={cancelApi}\n            submitBtn={false}\n            onSuccess={(response) => {\n              if (response.error) {\n                toast.error(response.error.message);\n              } else {\n                // Reload the page\n                window.location.reload();\n              }\n            }}\n          >\n            <div>\n              <TextareaField\n                name=\"reason\"\n                label=\"Reason for cancellation\"\n                placeholder=\"Reason for cancellation\"\n                required\n                validation={{\n                  required: 'Reason is required'\n                }}\n              />\n            </div>\n          </Form>\n          <DialogFooter>\n            <DialogClose>\n              <Button variant=\"outline\">Cancel</Button>\n            </DialogClose>\n            <Button\n              variant=\"default\"\n              isLoading={form.formState.isSubmitting}\n              onClick={async () => {\n                (\n                  document.getElementById('cancelReason') as HTMLFormElement\n                ).dispatchEvent(\n                  new Event('submit', { cancelable: true, bubbles: true })\n                );\n              }}\n            >\n              Submit Cancellation\n            </Button>\n          </DialogFooter>\n        </DialogContent>\n      </Dialog>\n    </RenderIfTrue>\n  );\n}\n\nexport const layout = {\n  areaId: 'pageHeadingRight',\n  sortOrder: 35\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      paymentStatus {\n        code\n        isCancelable\n      }\n      shipmentStatus {\n        code\n        isCancelable\n      }\n      cancelApi\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/Customer.tsx",
    "content": "import { AddressSummary } from '@components/common/customer/address/AddressSummary.js';\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface CustomerProps {\n  order: {\n    customerFullName: string;\n    customerEmail: string;\n    customerUrl?: string;\n    noShippingRequired: boolean;\n    shippingAddress: {\n      fullName: string;\n      city: string;\n      address1: string;\n      address2?: string;\n      postcode: string;\n      telephone: string;\n      province: {\n        code: string;\n        name: string;\n      };\n      country: {\n        code: string;\n        name: string;\n      };\n    };\n    billingAddress: {\n      fullName: string;\n      city: string;\n      address1: string;\n      address2?: string;\n      postcode: string;\n      telephone: string;\n      province: {\n        code: string;\n        name: string;\n      };\n      country: {\n        code: string;\n        name: string;\n      };\n    };\n  };\n}\n\nexport default function Customer({\n  order: {\n    noShippingRequired,\n    shippingAddress,\n    billingAddress,\n    customerFullName,\n    customerEmail,\n    customerUrl\n  }\n}: CustomerProps) {\n  return (\n    <Card className=\"\">\n      <CardHeader>\n        <CardTitle>Customer Information</CardTitle>\n      </CardHeader>\n      <CardContent>\n        {customerUrl && (\n          <a\n            href={customerUrl}\n            className=\"text-interactive hover:underline block\"\n          >\n            {customerFullName}\n          </a>\n        )}\n        {!customerUrl && <span>{customerEmail} (Guest Checkout)</span>}\n      </CardContent>\n      <CardContent className=\"border-t border-border pt-3\">\n        <CardTitle className=\"mb-2\">Contact Information</CardTitle>\n        <div>\n          <a href=\"#\" className=\"text-interactive hover:underline\">\n            {customerEmail}\n          </a>\n        </div>\n        {shippingAddress?.telephone && (\n          <div>\n            <span>{shippingAddress.telephone}</span>\n          </div>\n        )}\n      </CardContent>\n      <CardContent className=\"border-t border-border pt-3\">\n        <CardTitle className=\"mb-2\">Shipping Address</CardTitle>\n        {!noShippingRequired && <AddressSummary address={shippingAddress} />}\n        {noShippingRequired && (\n          <span className=\"text-muted-foreground\">\n            {'No shipping required'}\n          </span>\n        )}\n      </CardContent>\n      <CardContent className=\"border-t border-border pt-3\">\n        <CardTitle className=\"mb-2\">Billing address</CardTitle>\n        <AddressSummary address={billingAddress} />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      customerFullName\n      customerEmail\n      customerUrl\n      noShippingRequired\n      shippingAddress {\n        fullName\n        city\n        address1\n        address2\n        postcode\n        telephone\n        province {\n          code\n          name\n        }\n        country {\n          code\n          name\n        }\n      }\n      billingAddress {\n        fullName\n        city\n        address1\n        address2\n        postcode\n        telephone\n        province {\n          code\n          name\n        }\n        country {\n          code\n          name\n        }\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/CustomerNotes.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface CustomerNotesProps {\n  order: {\n    shippingNote: string;\n  };\n}\nexport default function CustomerNotes({\n  order: { shippingNote }\n}: CustomerNotesProps) {\n  return (\n    <Card className=\"bg-popover\">\n      <CardHeader>\n        <CardTitle>Customer notes</CardTitle>\n      </CardHeader>\n      <CardContent>\n        <Area\n          id=\"orderEditCustomerNotes\"\n          coreComponents={[\n            {\n              component: {\n                default: () => (\n                  <div>\n                    {shippingNote || (\n                      <span className=\"text-muted-foreground\">\n                        No notes from customer\n                      </span>\n                    )}\n                  </div>\n                )\n              },\n              props: {},\n              sortOrder: 10,\n              id: 'title'\n            }\n          ]}\n          noOuter\n        />\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'rightSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      shippingNote\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/Items.jsx",
    "content": "import Area from '@components/common/Area';\nimport { Card } from '@components/common/ui/Card';\nimport {\n  CardContent,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport { Circle } from '@components/common/ui/Circle.js';\nimport { Table, TableBody, TableRow } from '@components/common/ui/Table.js';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Name } from './items/Name.js';\nimport { Price } from './items/Price.js';\nimport { Thumbnail } from './items/Thumbnail.js';\n\nexport default function Items({ order: { items, shipmentStatus } }) {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>\n          <div className=\"flex space-x-2\">\n            <Circle variant={shipmentStatus.badge || 'new'} />\n            <span className=\"block self-center\">\n              {shipmentStatus.name || 'Unknown'}\n            </span>\n          </div>\n        </CardTitle>\n      </CardHeader>\n      <CardContent>\n        <Table className=\"table-fixed\">\n          <TableBody>\n            {items.map((i, k) => (\n              <TableRow key={k}>\n                <Area\n                  key={k}\n                  id={`order_item_row_${i.id}`}\n                  noOuter\n                  item={i}\n                  coreComponents={[\n                    {\n                      component: { default: Thumbnail },\n                      props: { imageUrl: i.thumbnail, qty: i.qty },\n                      sortOrder: 10,\n                      id: 'productThumbnail'\n                    },\n                    {\n                      component: { default: Name },\n                      props: {\n                        name: i.productName,\n                        productSku: i.productSku,\n                        productUrl: i.productUrl,\n                        variantOptions: i.variantOptions\n                      }, // TODO: Implement custom options\n                      sortOrder: 20,\n                      id: 'productName'\n                    },\n                    {\n                      component: { default: Price },\n                      props: { price: i.productPrice.text, qty: i.qty },\n                      sortOrder: 30,\n                      id: 'price'\n                    },\n                    {\n                      component: { default: 'td' },\n                      props: {\n                        children: <span>{i.lineTotal.text}</span>,\n                        key: 'lineTotal',\n                        className: 'w-20 whitespace-nowrap text-right'\n                      },\n                      sortOrder: 40,\n                      id: 'lineTotal'\n                    }\n                  ]}\n                />\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n      </CardContent>\n      <CardContent>\n        <div className=\"flex justify-end gap-2\">\n          <Area id=\"order_actions\" noOuter />\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\nItems.propTypes = {\n  order: PropTypes.shape({\n    items: PropTypes.arrayOf(\n      PropTypes.shape({\n        id: PropTypes.string,\n        qty: PropTypes.number,\n        productName: PropTypes.string,\n        productSku: PropTypes.string,\n        productUrl: PropTypes.string,\n        thumbnail: PropTypes.string,\n        productPrice: PropTypes.shape({\n          value: PropTypes.number,\n          text: PropTypes.string\n        }),\n        variantOptions: PropTypes.string,\n        finalPrice: PropTypes.shape({\n          value: PropTypes.number,\n          text: PropTypes.string\n        }),\n        total: PropTypes.shape({\n          value: PropTypes.number,\n          text: PropTypes.string\n        }),\n        lineTotal: PropTypes.shape({\n          value: PropTypes.number,\n          text: PropTypes.string\n        })\n      })\n    ),\n    shipmentStatus: PropTypes.shape({\n      code: PropTypes.string,\n      badge: PropTypes.string,\n      name: PropTypes.string\n    }),\n    shipment: PropTypes.shape({\n      shipmentId: PropTypes.string,\n      carrier: PropTypes.string,\n      trackingNumber: PropTypes.string,\n      updateShipmentApi: PropTypes.string\n    }),\n    createShipmentApi: PropTypes.string.isRequired\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      currency\n      shipment {\n        shipmentId\n        carrier\n        trackingNumber\n        updateShipmentApi\n      }\n      shipmentStatus {\n        code\n        badge\n        name\n      }\n      items {\n        id: orderItemId\n        qty\n        productName\n        productSku\n        productUrl\n        thumbnail\n        variantOptions {\n          attributeCode\n          attributeName\n          attributeId\n          optionId\n          optionText\n        }\n        productPrice {\n          value\n          text\n        }\n        finalPrice {\n          value\n          text\n        }\n        total {\n          value\n          text\n        }\n        lineTotal {\n          value\n          text\n        }\n      }\n      createShipmentApi\n    },\n    carriers {\n      label: name\n      value: code\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/Layout.jsx",
    "content": "import Area from '@components/common/Area';\nimport React from 'react';\nimport './Layout.scss';\n\nexport default function OrderEdit() {\n  return (\n    <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n      <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n        <Area id=\"leftSide\" noOuter />\n      </div>\n      <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n        <Area id=\"rightSide\" noOuter />\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/Layout.scss",
    "content": "body.orderEdit {\n  .main-content-inner {\n    max-width: 62.5rem;\n    margin: 1.875rem auto;\n  }\n  .page-heading {\n    max-width: 62.5rem;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/MarkDeliveredButton.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface MarkDeliveredButtonProps {\n  order: {\n    orderId: string;\n    noShippingRequired: boolean;\n    shipmentStatus: {\n      code: string;\n    };\n    shipment: {\n      shipmentId: number;\n    } | null;\n  };\n  markDeliveredApi: string;\n}\n\nexport default function MarkDeliveredButton({\n  order: {\n    orderId,\n    noShippingRequired,\n    shipmentStatus: { code },\n    shipment\n  },\n  markDeliveredApi\n}: MarkDeliveredButtonProps) {\n  if (noShippingRequired || !shipment || code === 'delivered') {\n    return null;\n  } else {\n    return (\n      <Button\n        variant=\"default\"\n        onClick={async () => {\n          // Call the updateShipmentApi with the status set to \"delivered\" using fetch post request, include credentials\n          const response = await fetch(markDeliveredApi, {\n            method: 'POST',\n            credentials: 'include',\n            headers: {\n              'Content-Type': 'application/json'\n            },\n            body: JSON.stringify({ order_id: orderId })\n          });\n          const data = await response.json();\n          // If the response is not ok, throw an error\n          if (!data.error) {\n            // Reload the page\n            window.location.reload();\n          } else {\n            toast.error(data.error.message);\n          }\n        }}\n      >\n        Mark Delivered\n      </Button>\n    );\n  }\n}\n\nexport const layout = {\n  areaId: 'order_actions',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      noShippingRequired\n      orderId\n      shipmentStatus {\n        code\n      }\n      shipment {\n        shipmentId\n      }\n    },\n    markDeliveredApi: url(routeId: \"markDelivered\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface OrderEditPageHeadingProps {\n  backUrl: string;\n  order: {\n    orderNumber: string;\n  };\n}\n\nexport default function OrderEditPageHeading({\n  backUrl,\n  order\n}: OrderEditPageHeadingProps) {\n  return (\n    <PageHeading backUrl={backUrl} heading={`Editing #${order.orderNumber}`} />\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\", null)) {\n      orderNumber\n    }\n    backUrl: url(routeId: \"orderGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/Payment.jsx",
    "content": "import Area from '@components/common/Area.js';\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport { Circle } from '@components/common/ui/Circle.js';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Discount } from './payment/Discount.js';\nimport { Shipping } from './payment/Shipping.js';\nimport { SubTotal } from './payment/SubTotal.js';\nimport { Tax } from './payment/Tax.js';\nimport { Total } from './payment/Total.js';\n\nexport default function OrderSummary({\n  order: {\n    orderId,\n    coupon,\n    shippingMethodName,\n    paymentMethodName,\n    totalQty,\n    totalTaxAmount,\n    discountAmount,\n    grandTotal,\n    subTotal,\n    shippingFeeInclTax,\n    currency,\n    paymentStatus\n  }\n}) {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>\n          <div className=\"flex space-x-2\">\n            <Circle variant={paymentStatus.badge} />\n            <span className=\"block self-center\">\n              {`${paymentStatus.name || 'Unknown'} - ${\n                paymentMethodName || 'Unknown'\n              }`}\n            </span>\n          </div>\n        </CardTitle>\n      </CardHeader>\n      <CardContent>\n        <Area\n          id=\"orderSummaryBlock\"\n          orderId={orderId}\n          currency={currency}\n          grandTotal={grandTotal}\n          coupon={coupon}\n          discountAmount={discountAmount}\n          totalTaxAmount={totalTaxAmount}\n          className=\"space-y-3\"\n          coreComponents={[\n            {\n              component: { default: SubTotal },\n              props: { count: totalQty, total: subTotal.text },\n              sortOrder: 5\n            },\n            {\n              component: { default: Shipping },\n              props: {\n                method: shippingMethodName,\n                cost: shippingFeeInclTax.text\n              },\n              sortOrder: 10\n            },\n            {\n              component: { default: Discount },\n              props: { code: coupon, discount: discountAmount.text },\n              sortOrder: 15\n            },\n            {\n              component: { default: Tax },\n              props: { taxClass: '', amount: totalTaxAmount.text },\n              sortOrder: 20\n            },\n\n            {\n              component: { default: Total },\n              props: { total: grandTotal.text },\n              sortOrder: 30\n            }\n          ]}\n        />\n      </CardContent>\n      <Area id=\"orderPaymentActions\" />\n    </Card>\n  );\n}\n\nOrderSummary.propTypes = {\n  order: PropTypes.shape({\n    orderId: PropTypes.string.isRequired,\n    totalQty: PropTypes.number.isRequired,\n    coupon: PropTypes.string,\n    shippingMethod: PropTypes.string,\n    paymentMethodName: PropTypes.string,\n    totalTaxAmount: PropTypes.shape({\n      text: PropTypes.string.isRequired\n    }).isRequired,\n    discountAmount: PropTypes.shape({\n      text: PropTypes.string.isRequired\n    }).isRequired,\n    grandTotal: PropTypes.shape({\n      text: PropTypes.string.isRequired\n    }).isRequired,\n    shippingMethodName: PropTypes.string,\n    subTotal: PropTypes.shape({\n      text: PropTypes.string.isRequired\n    }).isRequired,\n    shippingFeeInclTax: PropTypes.shape({\n      text: PropTypes.string.isRequired\n    }).isRequired,\n    currency: PropTypes.string.isRequired,\n    paymentStatus: PropTypes.shape({\n      code: PropTypes.string,\n      badge: PropTypes.string,\n      name: PropTypes.string\n    }).isRequired\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'leftSide',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      orderId\n      totalQty\n      coupon\n      shippingMethodName\n      paymentMethod\n      paymentMethodName\n      totalTaxAmount {\n        text(currency: getContextValue(\"orderCurrency\"))\n      }\n      discountAmount {\n        text(currency: getContextValue(\"orderCurrency\"))\n      }\n      grandTotal {\n        text(currency: getContextValue(\"orderCurrency\"))\n      }\n      subTotal {\n        text(currency: getContextValue(\"orderCurrency\"))\n      }\n      shippingFeeInclTax {\n        text(currency: getContextValue(\"orderCurrency\"))\n      }\n      currency\n      paymentStatus {\n        code\n        badge\n        name\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/ShipButton.tsx",
    "content": "import { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { useAlertContext } from '@components/common/modal/Alert.js';\nimport RenderIfTrue from '@components/common/RenderIfTrue.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface ShipButtonProps {\n  order: {\n    noShippingRequired: boolean;\n    shipment?: {\n      trackingNumber?: string;\n      carrier?: string;\n    };\n    createShipmentApi: string;\n    shipmentStatus: {\n      code: string;\n    };\n  };\n  carriers: {\n    label: string;\n    value: string;\n  }[];\n}\nexport default function ShipButton({\n  order: { noShippingRequired, shipment, createShipmentApi, shipmentStatus },\n  carriers\n}: ShipButtonProps) {\n  const { openAlert, closeAlert, dispatchAlert } = useAlertContext();\n  if (noShippingRequired) {\n    return (\n      <Button disabled variant=\"secondary\">\n        No Shipping Required\n      </Button>\n    );\n  }\n  if (shipment) {\n    return null;\n  } else {\n    return (\n      <RenderIfTrue condition={shipmentStatus.code !== 'canceled'}>\n        <Button\n          variant=\"default\"\n          onClick={() => {\n            openAlert({\n              heading: 'Ship Items',\n              content: (\n                <div>\n                  <Form\n                    id=\"ship-items\"\n                    method=\"POST\"\n                    action={createShipmentApi}\n                    submitBtn={false}\n                    onSuccess={(response) => {\n                      if (response.error) {\n                        toast.error(response.error.message);\n                        dispatchAlert({\n                          type: 'update',\n                          payload: { secondaryAction: { isLoading: false } }\n                        });\n                      } else {\n                        // Reload the page\n                        window.location.reload();\n                      }\n                    }}\n                    onInvalid={() => {\n                      dispatchAlert({\n                        type: 'update',\n                        payload: { secondaryAction: { isLoading: false } }\n                      });\n                    }}\n                  >\n                    <div className=\"grid grid-cols-2 gap-2\">\n                      <div>\n                        <InputField\n                          type=\"text\"\n                          name=\"tracking_number\"\n                          label=\"Tracking number\"\n                          placeholder=\"Tracking number\"\n                        />\n                      </div>\n                      <div>\n                        <SelectField\n                          name=\"carrier\"\n                          label=\"Carrier\"\n                          options={carriers}\n                        />\n                      </div>\n                    </div>\n                  </Form>\n                </div>\n              ),\n              primaryAction: {\n                title: 'Cancel',\n                onAction: closeAlert,\n                variant: 'outline'\n              },\n              secondaryAction: {\n                title: 'Ship',\n                onAction: () => {\n                  dispatchAlert({\n                    type: 'update',\n                    payload: { secondaryAction: { isLoading: true } }\n                  });\n                  (\n                    document.getElementById('ship-items') as HTMLFormElement\n                  ).dispatchEvent(\n                    new Event('submit', { cancelable: true, bubbles: true })\n                  );\n                },\n                variant: 'default',\n                isLoading: false\n              }\n            });\n          }}\n        >\n          Ship items\n        </Button>\n      </RenderIfTrue>\n    );\n  }\n}\n\nexport const layout = {\n  areaId: 'order_actions',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      noShippingRequired\n      shipment {\n        shipmentId\n        carrier\n        trackingNumber\n        updateShipmentApi\n      }\n      shipmentStatus {\n        code\n      }\n      createShipmentApi\n    },\n    carriers {\n      label: name\n      value: code\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/Status.jsx",
    "content": "import { Badge } from '@components/common/ui/Badge.js';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nexport default function Status({ order: { status } }) {\n  if (status) {\n    return <Badge variant={status.badge}>{status.name}</Badge>;\n  } else {\n    return null;\n  }\n}\n\nStatus.propTypes = {\n  order: PropTypes.shape({\n    status: PropTypes.shape({\n      badge: PropTypes.string,\n      name: PropTypes.string\n    })\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'pageHeadingLeft',\n  sortOrder: 200\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      status {\n        code\n        badge\n        name\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/TrackingButton.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\n\ninterface TrackingButtonProps {\n  order: {\n    shipment: {\n      carrier: string;\n      trackingNumber: string;\n    };\n  };\n  carriers: {\n    name: string;\n    code: string;\n    trackingUrl: string;\n  }[];\n}\n\nexport default function TrackingButton({\n  order: { shipment },\n  carriers\n}: TrackingButtonProps) {\n  if (!shipment || !shipment.trackingNumber || !shipment.carrier) {\n    return null;\n  }\n\n  const carrier = carriers.find((c) => c.code === shipment.carrier);\n\n  if (!carrier || !carrier.trackingUrl) {\n    return null;\n  }\n\n  // Replace {trackingNumber} with the actual tracking number\n  const url = carrier.trackingUrl.replace(\n    /\\{\\s*trackingNumber\\s*\\}/g,\n    shipment.trackingNumber\n  );\n\n  return (\n    <Button\n      variant=\"default\"\n      onClick={() => {\n        window.open(url, '_blank')?.focus();\n      }}\n    >\n      Track shipment\n    </Button>\n  );\n}\n\nexport const layout = {\n  areaId: 'order_actions',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    order(uuid: getContextValue(\"orderId\")) {\n      shipment {\n        shipmentId\n        carrier\n        trackingNumber\n        updateShipmentApi\n      }\n      createShipmentApi\n    },\n    carriers {\n      name\n      code\n      trackingUrl\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  try {\n    const query = select();\n    query.from('order');\n    query.andWhere('order.uuid', '=', request.params.id);\n    const order = await query.load(pool);\n\n    if (order === null) {\n      response.status(404);\n      next();\n    } else {\n      setContextValue(request, 'orderId', order.uuid);\n      setContextValue(request, 'orderCurrency', order.currency);\n      setPageMetaInfo(request, {\n        title: `Order #${order.order_number}`,\n        description: `Order #${order.order_number}`\n      });\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/items/ItemVariantOptions.tsx",
    "content": "import React from 'react';\n\ninterface ItemVariantOptionsProps {\n  options?: Array<{\n    attributeName: string;\n    optionText: string;\n  }>;\n}\n\nexport function ItemVariantOptions({ options = [] }: ItemVariantOptionsProps) {\n  if (!Array.isArray(options) || !options || options.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"cart-item-variant-options mt-2\">\n      <ul>\n        {options.map((o, i) => (\n          <li key={i}>\n            <span className=\"attribute-name font-semibold\">\n              {o.attributeName}:{' '}\n            </span>\n            <span>{o.optionText}</span>\n          </li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/items/Name.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\nimport { ItemVariantOptions } from './ItemVariantOptions.js';\n\ninterface NameProps {\n  name: string;\n  productSku: string;\n  productUrl: string;\n  variantOptions?: Array<{\n    attributeName: string;\n    optionText: string;\n  }>;\n}\n\nexport function Name({\n  name,\n  productSku,\n  productUrl,\n  variantOptions = []\n}: NameProps) {\n  const truncatedName = name.length > 30 ? `${name.substring(0, 30)}...` : name;\n\n  return (\n    <TableCell className=\"w-auto min-w-0\">\n      <div className=\"product-column overflow-hidden\">\n        <div>\n          <span className=\"font-semibold\">\n            <a href={productUrl} title={name}>\n              {truncatedName}\n            </a>\n          </span>\n        </div>\n        <div className=\"text-muted-foreground\">\n          <span className=\"font-semibold\">SKU: </span>\n          <span>{productSku}</span>\n        </div>\n        <ItemVariantOptions options={variantOptions} />\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/items/Price.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface PriceProps {\n  price: string;\n  qty: number;\n}\n\nexport function Price({ price, qty }: PriceProps) {\n  return (\n    <TableCell className=\"w-32 whitespace-nowrap\">\n      <div className=\"product-price\">\n        <span>\n          {price} x {qty}\n        </span>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/items/Thumbnail.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface ThumbnailProps {\n  imageUrl?: string;\n  qty: number;\n}\n\nexport function Thumbnail({ imageUrl, qty }: ThumbnailProps) {\n  return (\n    <TableCell className=\"w-20\">\n      <div className=\"relative w-12 h-12 flex justify-center border border-divider rounded-[3px] p-0.5 box-border\">\n        <div className=\"self-center text-border\">\n          {imageUrl && (\n            <img src={imageUrl} alt=\"\" className=\"max-w-full h-auto\" />\n          )}\n          {!imageUrl && (\n            <svg\n              className=\"w-8\"\n              fill=\"currentcolor\"\n              viewBox=\"0 0 20 20\"\n              focusable=\"false\"\n              aria-hidden=\"true\"\n            >\n              <path\n                fillRule=\"evenodd\"\n                d=\"M6 11h8V9H6v2zm0 4h8v-2H6v2zm0-8h4V5H6v2zm6-5H5.5A1.5 1.5 0 0 0 4 3.5v13A1.5 1.5 0 0 0 5.5 18h9a1.5 1.5 0 0 0 1.5-1.5V6l-4-4z\"\n              />\n            </svg>\n          )}\n        </div>\n        <span className=\"block w-5 h-5 text-xs absolute -top-[0.8rem] -right-[0.8rem] bg-divider rounded-full text-center leading-tight pt-0.5\">\n          {qty}\n        </span>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/payment/Discount.tsx",
    "content": "import React from 'react';\n\ninterface DiscountProps {\n  discount?: string;\n  code?: string;\n}\n\nexport function Discount({ discount, code }: DiscountProps) {\n  return (\n    <div className=\"flex items-start justify-between gap-4\">\n      <span className=\"text-sm text-muted-foreground min-w-[8.75rem]\">\n        Discount\n      </span>\n      <div className=\"flex-1 flex items-start justify-between gap-2\">\n        <div className=\"text-sm text-muted-foreground\">{code}</div>\n        <div className=\"font-semibold text-sm text-green-600 dark:text-green-400\">\n          {discount}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nDiscount.defaultProps = {\n  code: undefined,\n  discount: 0\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/payment/Shipping.tsx",
    "content": "import React from 'react';\n\ninterface ShippingProps {\n  method: string;\n  cost: string;\n}\n\nexport function Shipping({ method, cost }: ShippingProps) {\n  return (\n    <div className=\"flex items-start justify-between gap-4\">\n      <span className=\"text-sm text-muted-foreground min-w-[8.75rem]\">\n        Shipping\n      </span>\n      <div className=\"flex-1 flex items-start justify-between gap-2\">\n        <div className=\"text-sm text-muted-foreground\">{method}</div>\n        <div className=\"font-semibold text-sm\">{cost}</div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/payment/SubTotal.tsx",
    "content": "import React from 'react';\n\ninterface SubTotalProps {\n  count: number;\n  total: string;\n}\n\nexport function SubTotal({ count, total }: SubTotalProps) {\n  return (\n    <div className=\"flex items-start justify-between gap-4\">\n      <span className=\"text-sm text-muted-foreground min-w-[8.75rem]\">\n        Subtotal\n      </span>\n      <div className=\"flex-1 flex items-start justify-between gap-2\">\n        <div className=\"text-sm text-muted-foreground\">{count} items</div>\n        <div className=\"font-semibold text-sm\">{total}</div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/payment/Tax.tsx",
    "content": "import { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\ninterface TaxProps {\n  taxClass: string;\n  amount: string;\n}\n\nexport function Tax({ taxClass, amount }: TaxProps) {\n  return (\n    <div className=\"flex items-start justify-between gap-4\">\n      <span className=\"text-sm text-muted-foreground min-w-[8.75rem]\">\n        {_('Tax')}\n      </span>\n      <div className=\"flex-1 flex items-start justify-between gap-2\">\n        <div className=\"text-sm text-muted-foreground\">{taxClass}</div>\n        <div className=\"font-semibold text-sm\">{amount}</div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/payment/Total.tsx",
    "content": "import React from 'react';\n\ninterface TotalProps {\n  total: string;\n}\n\nexport function Total({ total }: TotalProps) {\n  return (\n    <div className=\"flex items-start justify-between gap-4 pt-3 border-t border-border\">\n      <span className=\"text-base font-semibold min-w-[8.75rem]\">Total</span>\n      <div className=\"flex-1 flex items-start justify-between gap-2\">\n        <span />\n        <div className=\"text-base font-bold\">{total}</div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderEdit/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/order/edit/:id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderGrid/Grid.jsx",
    "content": "import { GridPagination } from '@components/admin/grid/GridPagination';\nimport { SortableHeader } from '@components/admin/grid/header/Sortable';\nimport Area from '@components/common/Area';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { useAlertContext } from '@components/common/modal/Alert';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardAction\n} from '@components/common/ui/Card.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectTrigger,\n  SelectValue\n} from '@components/common/ui/Select.js';\nimport {\n  Table,\n  TableHead,\n  TableHeader,\n  TableRow,\n  TableBody,\n  TableCell\n} from '@components/common/ui/Table.js';\nimport axios from 'axios';\nimport PropTypes from 'prop-types';\nimport React, { useState } from 'react';\nimport { OrderNumber } from './rows/OrderNumber.js';\nimport { PaymentStatus } from './rows/PaymentStatus.js';\nimport { ShipmentStatus } from './rows/ShipmentStatus.js';\n\nfunction Actions({ orders = [], selectedIds = [] }) {\n  const { openAlert, closeAlert } = useAlertContext();\n  const [isLoading, setIsLoading] = useState(false);\n\n  const fullFillOrders = async () => {\n    setIsLoading(true);\n    const promises = orders\n      .filter((order) => selectedIds.includes(order.uuid))\n      .map((order) => axios.post(order.createShipmentApi));\n\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const actions = [\n    {\n      name: 'Mark as shipped',\n      onAction: () => {\n        openAlert({\n          heading: `Fullfill ${selectedIds.length} orders`,\n          content: (\n            <div className=\"form-field mb-0\">\n              Are you sure you want to mark the selected orders as shipped?\n            </div>\n          ),\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Mark as shipped',\n            onAction: async () => {\n              await fullFillOrders();\n            },\n            variant: 'default',\n            isLoading\n          }\n        });\n      }\n    }\n  ];\n\n  return (\n    <TableRow>\n      {selectedIds.length === 0 && null}\n      {selectedIds.length > 0 && (\n        <TableCell style={{ borderTop: 0 }} colSpan=\"100\">\n          <ButtonGroup>\n            {actions.map((action, i) => (\n              <Button\n                key={i}\n                variant={'outline'}\n                onClick={(e) => {\n                  e.preventDefault();\n                  action.onAction();\n                }}\n              >\n                {action.name}\n              </Button>\n            ))}\n          </ButtonGroup>\n        </TableCell>\n      )}\n    </TableRow>\n  );\n}\n\nActions.propTypes = {\n  selectedIds: PropTypes.arrayOf(PropTypes.string).isRequired,\n  orders: PropTypes.arrayOf(\n    PropTypes.shape({\n      uuid: PropTypes.string.isRequired,\n      createShipmentApi: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport default function OrderGrid({\n  orders: { items: orders, total, currentFilters = [] },\n  paymentStatusList,\n  shipmentStatusList\n}) {\n  const page = currentFilters.find((filter) => filter.key === 'page')\n    ? parseInt(currentFilters.find((filter) => filter.key === 'page').value, 10)\n    : 1;\n\n  const limit = currentFilters.find((filter) => filter.key === 'limit')\n    ? parseInt(\n        currentFilters.find((filter) => filter.key === 'limit').value,\n        10\n      )\n    : 20;\n\n  const [selectedRows, setSelectedRows] = useState([]);\n\n  return (\n    <Card>\n      <CardHeader className=\"flex justify-between\">\n        <Form submitBtn={false} id=\"orderGridFilter\">\n          <div className=\"flex gap-5 justify-center items-center\">\n            <Area\n              id=\"orderGridFilter\"\n              noOuter\n              coreComponents={[\n                {\n                  component: {\n                    default: () => (\n                      <InputField\n                        name=\"keyword\"\n                        placeholder=\"Search\"\n                        defaultValue={\n                          currentFilters.find((f) => f.key === 'keyword')?.value\n                        }\n                        onKeyPress={(e) => {\n                          // If the user press enter, we should submit the form\n                          if (e.key === 'Enter') {\n                            const url = new URL(document.location);\n                            const keyword = e.target?.value;\n                            if (keyword) {\n                              url.searchParams.set('keyword', keyword);\n                            } else {\n                              url.searchParams.delete('keyword');\n                            }\n                            window.location.href = url;\n                          }\n                        }}\n                      />\n                    )\n                  },\n                  sortOrder: 5\n                },\n                {\n                  component: {\n                    default: () => (\n                      <>\n                        <Select\n                          value={\n                            currentFilters.find(\n                              (f) => f.key === 'payment_status'\n                            )\n                              ? currentFilters.find(\n                                  (f) => f.key === 'payment_status'\n                                ).value\n                              : undefined\n                          }\n                          onValueChange={(value) => {\n                            const url = new URL(document.location);\n                            url.searchParams.set('payment_status', value);\n                            window.location.href = url;\n                          }}\n                        >\n                          <SelectTrigger>\n                            <SelectValue>Payment Status</SelectValue>\n                          </SelectTrigger>\n                          <SelectContent>\n                            <SelectGroup>\n                              <SelectLabel>Payment Status</SelectLabel>\n                              {paymentStatusList.map((status, index) => (\n                                <SelectItem key={index} value={status.code}>\n                                  {status.name}\n                                </SelectItem>\n                              ))}\n                            </SelectGroup>\n                          </SelectContent>\n                        </Select>\n                      </>\n                    )\n                  },\n                  sortOrder: 10\n                },\n                {\n                  component: {\n                    default: () => (\n                      <Select\n                        value={\n                          currentFilters.find(\n                            (f) => f.key === 'shipment_status'\n                          )\n                            ? currentFilters.find(\n                                (f) => f.key === 'shipment_status'\n                              ).value\n                            : undefined\n                        }\n                        onValueChange={(value) => {\n                          const url = new URL(document.location);\n                          url.searchParams.set('shipment_status', value);\n                          window.location.href = url;\n                        }}\n                      >\n                        <SelectTrigger>\n                          <SelectValue>Shipment Status</SelectValue>\n                        </SelectTrigger>\n                        <SelectContent>\n                          <SelectGroup>\n                            <SelectLabel>Shipment Status</SelectLabel>\n                            {shipmentStatusList.map((status, index) => (\n                              <SelectItem key={index} value={status.code}>\n                                {status.name}\n                              </SelectItem>\n                            ))}\n                          </SelectGroup>\n                        </SelectContent>\n                      </Select>\n                    )\n                  },\n                  sortOrder: 15\n                }\n              ]}\n              currentFilters={currentFilters}\n            />\n          </div>\n        </Form>\n        <CardAction>\n          <Button\n            variant=\"link\"\n            className={'hover:cursor-pointer'}\n            onClick={() => {\n              const url = new URL(document.location);\n              url.search = '';\n              window.location.href = url.href;\n            }}\n          >\n            Clear Filters\n          </Button>\n        </CardAction>\n      </CardHeader>\n      <CardContent>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>\n                <div className=\"form-field mb-0\">\n                  <Checkbox\n                    onCheckedChange={(checked) => {\n                      if (checked) {\n                        setSelectedRows(orders.map((o) => o.uuid));\n                      } else {\n                        setSelectedRows([]);\n                      }\n                    }}\n                  />\n                </div>\n              </TableHead>\n              <Area\n                className=\"\"\n                id=\"orderGridHeader\"\n                noOuter\n                coreComponents={[\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Order Number\"\n                          name=\"number\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 5\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Date\"\n                          name=\"created_at\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 10\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Customer Email\"\n                          name=\"email\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 15\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Shipment Status\"\n                          name=\"shipment_status\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 20\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Payment Status\"\n                          name=\"payment_status\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 25\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Total\"\n                          name=\"total\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 30\n                  }\n                ]}\n              />\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            <Actions\n              orders={orders}\n              selectedIds={selectedRows}\n              setSelectedRows={setSelectedRows}\n            />\n            {orders.map((o) => (\n              <TableRow key={o.orderId}>\n                <TableCell>\n                  <div className=\"form-field mb-0\">\n                    <Checkbox\n                      checked={selectedRows.includes(o.uuid)}\n                      onCheckedChange={(checked) => {\n                        if (checked) {\n                          setSelectedRows(selectedRows.concat([o.uuid]));\n                        } else {\n                          setSelectedRows(\n                            selectedRows.filter((row) => row !== o.uuid)\n                          );\n                        }\n                      }}\n                    />\n                  </div>\n                </TableCell>\n                <Area\n                  className=\"\"\n                  id=\"orderGridRow\"\n                  row={o}\n                  noOuter\n                  coreComponents={[\n                    {\n                      component: {\n                        default: () => (\n                          <OrderNumber\n                            number={o.orderNumber}\n                            editUrl={o.editUrl}\n                          />\n                        )\n                      },\n                      sortOrder: 5\n                    },\n                    {\n                      component: {\n                        default: () => <TableCell>{o.createdAt.text}</TableCell>\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: ({ areaProps }) => (\n                          <TableCell>{o.customerEmail}</TableCell>\n                        )\n                      },\n                      sortOrder: 15\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <ShipmentStatus status={o.shipmentStatus} />\n                        )\n                      },\n                      sortOrder: 20\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <PaymentStatus status={o.paymentStatus} />\n                        )\n                      },\n                      sortOrder: 25\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <TableCell>{o.grandTotal.text}</TableCell>\n                        )\n                      },\n                      sortOrder: 30\n                    }\n                  ]}\n                />\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {orders.length === 0 && (\n          <div className=\"flex w-full justify-center\">\n            There is no order to display\n          </div>\n        )}\n        <GridPagination total={total} limit={limit} page={page} />\n      </CardContent>\n    </Card>\n  );\n}\n\nOrderGrid.propTypes = {\n  orders: PropTypes.shape({\n    items: PropTypes.arrayOf(\n      PropTypes.shape({\n        orderId: PropTypes.string.isRequired,\n        uuid: PropTypes.string.isRequired,\n        orderNumber: PropTypes.string.isRequired,\n        createdAt: PropTypes.shape({\n          value: PropTypes.string.isRequired,\n          text: PropTypes.string.isRequired\n        }).isRequired,\n        customerEmail: PropTypes.string.isRequired,\n        shipmentStatus: PropTypes.shape({\n          name: PropTypes.string.isRequired,\n          code: PropTypes.string.isRequired,\n          badge: PropTypes.string.isRequired\n        }).isRequired,\n        paymentStatus: PropTypes.shape({\n          name: PropTypes.string.isRequired,\n          code: PropTypes.string.isRequired,\n          badge: PropTypes.string.isRequired\n        }).isRequired,\n        grandTotal: PropTypes.shape({\n          value: PropTypes.number.isRequired,\n          text: PropTypes.string.isRequired\n        }).isRequired,\n        editUrl: PropTypes.string.isRequired,\n        createShipmentApi: PropTypes.string.isRequired\n      })\n    ).isRequired,\n    total: PropTypes.number.isRequired,\n    currentFilters: PropTypes.arrayOf(\n      PropTypes.shape({\n        key: PropTypes.string.isRequired,\n        operation: PropTypes.string.isRequired,\n        value: PropTypes.string.isRequired\n      })\n    ).isRequired\n  }).isRequired,\n  paymentStatusList: PropTypes.arrayOf(\n    PropTypes.shape({\n      code: PropTypes.string.isRequired,\n      name: PropTypes.string.isRequired\n    })\n  ).isRequired,\n  shipmentStatusList: PropTypes.arrayOf(\n    PropTypes.shape({\n      code: PropTypes.string.isRequired,\n      name: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query($filters: [FilterInput]) {\n    orders (filters: $filters) {\n      items {\n        orderId\n        uuid\n        orderNumber\n        createdAt {\n          value\n          text\n        }\n        customerEmail\n        shipmentStatus {\n          name\n          code\n          badge\n        }\n        paymentStatus {\n          name\n          code\n          badge\n        }\n        grandTotal {\n          value\n          text\n        }\n        editUrl\n        createShipmentApi\n      }\n      total\n      currentFilters {\n        key\n        operation\n        value\n      }\n    }\n    paymentStatusList {\n      code\n      name\n    }\n    shipmentStatusList {\n      code\n      name\n    }\n  }\n`;\n\nexport const variables = `\n{\n  filters: getContextValue('filtersFromUrl')\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderGrid/Heading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport default function OrderGridHeading() {\n  return <PageHeading heading=\"Orders\" />;\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderGrid/index.ts",
    "content": "import { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default (request: EvershopRequest, response) => {\n  setPageMetaInfo(request, {\n    title: 'Orders',\n    description: 'Orders'\n  });\n  setContextValue(\n    request,\n    'filtersFromUrl',\n    buildFilterFromUrl(request.originalUrl)\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderGrid/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/orders\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderGrid/rows/OrderNumber.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface OrderNumberProps {\n  editUrl: string;\n  number: string;\n}\n\nexport function OrderNumber({ editUrl, number }: OrderNumberProps) {\n  return (\n    <TableCell>\n      <div>\n        <a className=\"hover:underline font-semibold\" href={editUrl}>\n          #{number}\n        </a>\n      </div>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderGrid/rows/PaymentStatus.tsx",
    "content": "import { Badge, badgeVariants } from '@components/common/ui/Badge.js';\nimport { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface PaymentStatusProps {\n  status: {\n    name: string;\n    badge: keyof typeof badgeVariants;\n  };\n}\n\nexport function PaymentStatus({ status }: PaymentStatusProps) {\n  return (\n    <TableCell>\n      <Badge variant={status.badge || 'default'}>{status.name}</Badge>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/pages/admin/orderGrid/rows/ShipmentStatus.tsx",
    "content": "import { Badge, badgeVariants } from '@components/common/ui/Badge.js';\nimport { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface ShipmentStatusProps {\n  status: {\n    name: string;\n    badge: keyof typeof badgeVariants;\n  };\n}\n\nexport function ShipmentStatus({ status }: ShipmentStatusProps) {\n  return (\n    <TableCell>\n      <Badge variant={status.badge || 'default'}>{status.name}</Badge>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/services/OrderCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class OrderCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n    this.baseQuery.orderBy('order.order_id', 'DESC');\n  }\n\n  async init(filters = []) {\n    const currentFilters = [];\n\n    // Apply the filters\n    const orderCollectionFilters = await getValue('orderCollectionFilters', []);\n    orderCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(\"order\".order_id)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/services/addOrderActivityLog.ts",
    "content": "import { insert, PoolClient } from '@evershop/postgres-query-builder';\nimport { hookable } from '../../../lib/util/hookable.js';\n\nasync function addOrderActivityLog(\n  orderId: number,\n  message: string,\n  notifyCustomer: boolean,\n  connection: PoolClient\n) {\n  /* Add an activity log message */\n  const log = await insert('order_activity')\n    .given({\n      order_activity_order_id: orderId,\n      comment: message,\n      customer_notified: notifyCustomer ? 1 : 0\n    })\n    .execute(connection);\n  return log;\n}\n\nexport default async (\n  orderId: number,\n  message: string,\n  notifyCustomer: boolean,\n  connection: PoolClient\n) => {\n  return await hookable(addOrderActivityLog, {\n    orderId,\n    message,\n    notifyCustomer\n  })(orderId, message, notifyCustomer, connection);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/services/cancelOrder.ts",
    "content": "import {\n  commit,\n  execute,\n  getConnection,\n  PoolClient,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { error } from '../../../lib/log/logger.js';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { hookable } from '../../../lib/util/hookable.js';\nimport { PaymentStatus, ShipmentStatus } from '../../../types/order.js';\nimport addOrderActivityLog from './addOrderActivityLog.js';\nimport { updatePaymentStatus } from './updatePaymentStatus.js';\nimport { updateShipmentStatus } from './updateShipmentStatus.js';\n\nfunction validateStatus(paymentStatus: string, shipmentStatus: string) {\n  const shipmentStatusList = getConfig(\n    'oms.order.shipmentStatus',\n    {}\n  ) as Record<string, ShipmentStatus>;\n  const paymentStatusList = getConfig('oms.order.paymentStatus', {}) as Record<\n    string,\n    PaymentStatus\n  >;\n  const paymentStatusConfig = paymentStatusList[paymentStatus];\n  const shipmentStatusConfig = shipmentStatusList[shipmentStatus];\n  if (\n    paymentStatusConfig.isCancelable === false ||\n    shipmentStatusConfig.isCancelable === false\n  ) {\n    return false;\n  }\n\n  return true;\n}\n\nasync function updatePaymentStatusToCancel(\n  orderID: number,\n  connection: PoolClient\n) {\n  await updatePaymentStatus(orderID, 'canceled', connection);\n}\n\nasync function updateShipmentStatusToCancel(\n  orderID: number,\n  connection: PoolClient\n) {\n  await updateShipmentStatus(orderID, 'canceled', connection);\n}\n\nasync function reStockAfterCancel(orderID: number, connection: PoolClient) {\n  const orderItems = await select()\n    .from('order_item')\n    .where('order_item_order_id', '=', orderID)\n    .execute(connection, false);\n\n  await Promise.all(\n    orderItems.map(async (orderItem) => {\n      await execute(\n        connection,\n        `UPDATE product_inventory SET qty = qty + ${orderItem.qty} WHERE product_inventory_product_id = ${orderItem.product_id}`\n      );\n    })\n  );\n}\n\nasync function cancelOrder(uuid: string, reason: string | undefined) {\n  const connection = await getConnection(pool);\n  try {\n    await startTransaction(connection);\n    const order = await select()\n      .from('order')\n      .where('uuid', '=', uuid)\n      .load(connection, false);\n    if (!order) {\n      throw new Error('Order not found');\n    }\n\n    const statusCheck = hookable(validateStatus, { order })(\n      order.payment_status,\n      order.shipment_status\n    );\n\n    if (!statusCheck) {\n      throw new Error('Order is not cancelable at this status');\n    }\n\n    await hookable(updatePaymentStatusToCancel, { order })(\n      order.order_id,\n      connection\n    );\n    await hookable(updateShipmentStatusToCancel, { order })(\n      order.order_id,\n      connection\n    );\n    await addOrderActivityLog(\n      order.order_id,\n      `Order canceled ${reason ? `(${reason})` : ''}`,\n      false,\n      connection\n    );\n    await hookable(reStockAfterCancel, { order })(order.order_id, connection);\n    await commit(connection);\n  } catch (err) {\n    error(err);\n    await rollback(connection);\n    throw err;\n  }\n}\n\n/**\n * Cancels an order by its UUID and adds a cancellation reason.\n * @param uuid - The UUID of the order to cancel.\n * @param reason - The reason for cancellation.\n * @returns A promise that resolves when the order is canceled.\n */\nexport default async (uuid: string, reason: string | undefined) => {\n  await hookable(cancelOrder, { uuid })(uuid, reason);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/services/createShipment.ts",
    "content": "import {\n  commit,\n  insert,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { PoolClient } from 'pg';\nimport { getConnection } from '../../../lib/postgres/connection.js';\nimport { hookable } from '../../../lib/util/hookable.js';\nimport addOrderActivityLog from './addOrderActivityLog.js';\nimport { updateShipmentStatus } from './updateShipmentStatus.js';\n\nasync function createShipment(\n  orderId: number,\n  carrier: string | null,\n  trackingNumber: string | null,\n  connection: PoolClient\n) {\n  const result = await insert('shipment')\n    .given({\n      shipment_order_id: orderId,\n      carrier,\n      tracking_number: trackingNumber\n    })\n    .execute(connection);\n\n  return result;\n}\n\nexport default async (\n  orderUuid: string,\n  carrier: string | null,\n  trackingNumber: string | null,\n  connection?: PoolClient\n) => {\n  const conn = connection || (await getConnection());\n\n  const order = await select()\n    .from('order')\n    .where('uuid', '=', orderUuid)\n    .load(conn, false);\n\n  if (!order) {\n    throw new Error('Invalid order id');\n  }\n  const shipment = await select()\n    .from('shipment')\n    .where('shipment_order_id', '=', order.order_id)\n    .load(conn, false);\n\n  if (shipment) {\n    throw new Error('Shipment was created');\n  }\n\n  if (!connection) {\n    await startTransaction(conn);\n  }\n  try {\n    const result = await hookable(createShipment, {\n      order,\n      carrier,\n      trackingNumber\n    })(order.order_id, carrier, trackingNumber, conn);\n\n    /* Update Shipment status to shipped */\n    await updateShipmentStatus(order.order_id, 'shipped', conn);\n\n    /* Add an activity log message */\n    await addOrderActivityLog(\n      order.order_id,\n      `Order has been shipped`,\n      false,\n      conn\n    );\n    const shipmentData = await select()\n      .from('shipment')\n      .where('shipment_id', '=', result.insertId)\n      .load(conn, false);\n\n    if (!connection) {\n      await commit(conn);\n    }\n    return shipmentData;\n  } catch (e) {\n    if (!connection) {\n      await rollback(conn);\n    }\n    throw e;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/services/getOrdersBaseQuery.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\n\nexport const getOrdersBaseQuery = () => {\n  const query = select().from('order');\n\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/services/index.ts",
    "content": "import addOrderActivityLog from './addOrderActivityLog.js';\nimport cancelOrder from './cancelOrder.js';\nimport createShipment from './createShipment.js';\nexport * from './updatePaymentStatus.js';\nexport * from './updateShipmentStatus.js';\nexport { cancelOrder, addOrderActivityLog, createShipment };\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/services/registerDefaultOrderCollectionFilters.js",
    "content": "import { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport default async function registerDefaultOrderCollectionFilters() {\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'keyword',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query\n          .andWhere('order.customer_email', 'ILIKE', `%${value}%`)\n          .or('order.order_number', 'ILIKE', `%${value}%`)\n          .or('order.customer_full_name', 'ILIKE', `%${value}%`);\n        currentFilters.push({\n          key: 'keyword',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'number',\n      operation: ['eq', 'like'],\n      callback: (query, operation, value, currentFilters) => {\n        if (operation === 'eq') {\n          query.andWhere('order.order_number', OPERATION_MAP[operation], value);\n        } else {\n          query.andWhere(\n            'order.order_number',\n            OPERATION_MAP[operation],\n            `%${value}%`\n          );\n        }\n        currentFilters.push({\n          key: 'order_number',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'email',\n      operation: ['eq', 'like'],\n      callback: (query, operation, value, currentFilters) => {\n        if (operation === 'eq') {\n          query.andWhere(\n            'order.customer_email',\n            OPERATION_MAP[operation],\n            value\n          );\n        } else {\n          query.andWhere(\n            'order.customer_email',\n            OPERATION_MAP[operation],\n            `%${value}%`\n          );\n        }\n        currentFilters.push({\n          key: 'email',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'shipment_status',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'order.shipment_status',\n          OPERATION_MAP[operation],\n          value\n        );\n        currentFilters.push({\n          key: 'shipment_status',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'payment_status',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere('order.payment_status', OPERATION_MAP[operation], value);\n        currentFilters.push({\n          key: 'payment_status',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const orderCollectionSortBy = getValueSync('orderCollectionSortBy', {\n          number: (query) => query.orderBy('order.order_number'),\n          payment_status: (query) => query.orderBy('order.payment_status'),\n          shipment_status: (query) => query.orderBy('order.shipment_status'),\n          total: (query) => query.orderBy('order.grand_total'),\n          created_at: (query) => query.orderBy('order.created_at')\n        });\n\n        if (orderCollectionSortBy[value]) {\n          orderCollectionSortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        }\n      }\n    }\n  ];\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/services/updateOrderStatus.ts",
    "content": "/**\n * This function will be executed automatically after either shipment status or payment status is updated.\n */\nimport {\n  commit,\n  getConnection,\n  insert,\n  PoolClient,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport Topo from '@hapi/topo';\nimport { error } from '../../../lib/log/logger.js';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { hookable } from '../../../lib/util/hookable.js';\nimport { PaymentStatus, ShipmentStatus } from '../../../types/order.js';\n\nfunction getOrderStatusFlow() {\n  try {\n    const orderStatusList = getConfig('oms.order.status', {});\n    const orderStatuses = new Topo.Sorter();\n    Object.keys(orderStatusList).forEach((status) => {\n      orderStatuses.add(status, {\n        before: orderStatusList[status].next,\n        group: status\n      });\n    });\n    return orderStatuses.nodes;\n  } catch (err) {\n    error(err);\n    const message = `Failed to resolve order status. This is mostlikely due to the order status configuration. \n    Please check the configuration and try again. (${err.message})`;\n    throw new Error(message);\n  }\n}\n\nexport function resolveOrderStatus(\n  paymentStatus: string,\n  shipmentStatus: string\n): string {\n  const orderStatusList = getConfig('oms.order.status', {});\n  const shipmentStatusList = getConfig(\n    'oms.order.shipmentStatus',\n    {}\n  ) as Record<string, ShipmentStatus>;\n  const paymentStatusList = getConfig('oms.order.paymentStatus', {}) as Record<\n    string,\n    PaymentStatus\n  >;\n  const psoMapping = getConfig('oms.order.psoMapping', {});\n  const shipmentStatusDefination = shipmentStatusList[shipmentStatus];\n  const paymentStatusDefination = paymentStatusList[paymentStatus];\n  if (!shipmentStatusDefination || !paymentStatusDefination) {\n    throw new Error(\n      'Either shipment status or payment status is invalid. Can not update order status'\n    );\n  }\n  // Reverse the order status list to get the highest priority status first\n  const nextStatus =\n    psoMapping[`${paymentStatus}:${shipmentStatus}`] ||\n    psoMapping[`*:${shipmentStatus}`] ||\n    psoMapping[`${paymentStatus}:*`] ||\n    psoMapping['*:*'];\n  if (!nextStatus || !orderStatusList[nextStatus]) {\n    throw new Error(\n      'Can not found a valid order status from the current shipment and payment status'\n    );\n  }\n  return nextStatus;\n}\n\nasync function updateOrderStatus(\n  orderId: number,\n  status: string,\n  connection: PoolClient\n): Promise<void> {\n  await update('order')\n    .given({\n      status\n    })\n    .where('order_id', '=', orderId)\n    .execute(connection);\n}\n\nasync function addOrderStatusChangeEvents(\n  orderId: number,\n  before: string,\n  after: string,\n  connection: PoolClient\n): Promise<void> {\n  await insert('event')\n    .given({\n      name: 'order_status_updated',\n      data: {\n        order_id: orderId,\n        before,\n        after\n      }\n    })\n    .execute(connection);\n}\n\nexport async function changeOrderStatus(\n  orderId: number,\n  status: string,\n  conn?: PoolClient\n) {\n  const statusFlow = getOrderStatusFlow();\n  const connection = conn || (await getConnection(pool));\n  const order = await select()\n    .from('order')\n    .where('order_id', '=', orderId)\n    .load(connection, false);\n  if (!order) {\n    throw new Error('Order not found');\n  }\n  // Do not allow to revert the status\n  if (statusFlow.indexOf(order.status) > statusFlow.indexOf(status)) {\n    throw new Error('Can not revert the status of the order');\n  }\n\n  try {\n    if (!conn) {\n      await startTransaction(connection);\n    }\n\n    await hookable(updateOrderStatus, {\n      order,\n      status\n    })(order.order_id, status, connection);\n\n    await hookable(addOrderStatusChangeEvents, {\n      order,\n      status\n    })(order.order_id, order.status.toString(), status, connection);\n\n    if (!conn) {\n      await commit(connection);\n    }\n  } catch (err) {\n    error(err);\n    if (!conn) {\n      await rollback(connection);\n    }\n    throw err;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/services/updatePaymentStatus.ts",
    "content": "import {\n  commit,\n  getConnection,\n  PoolClient,\n  rollback,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { error } from '../../../lib/log/logger.js';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { hookable } from '../../../lib/util/hookable.js';\nimport { PaymentStatus } from '../../../types/order.js';\n\nfunction validatePaymentStatusBeforeUpdate(status: string): boolean {\n  const paymentStatusList = getConfig('oms.order.paymentStatus', {}) as Record<\n    string,\n    PaymentStatus\n  >;\n  if (!paymentStatusList[status]) {\n    throw new Error('Invalid status');\n  }\n  return false;\n}\n\nasync function changePaymentStatus(\n  orderId: number,\n  status: string,\n  connection: PoolClient\n) {\n  const order = await update('order')\n    .given({\n      payment_status: status\n    })\n    .where('order_id', '=', orderId)\n    .execute(connection);\n  return order;\n}\n\nexport const updatePaymentStatus = async (\n  orderId: number,\n  status: string,\n  conn?: PoolClient\n): Promise<void> => {\n  const connection = conn || (await getConnection(pool));\n  try {\n    if (!conn) {\n      await startTransaction(connection);\n    }\n    hookable(validatePaymentStatusBeforeUpdate, { orderId })(status);\n    await hookable(changePaymentStatus, { orderId, status })(\n      orderId,\n      status,\n      connection\n    );\n    if (!conn) {\n      await commit(connection);\n    }\n  } catch (err) {\n    error(err);\n    if (!conn) {\n      await rollback(connection);\n    }\n    throw err;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/services/updateShipmentStatus.ts",
    "content": "import {\n  commit,\n  getConnection,\n  PoolClient,\n  rollback,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { error } from '../../../lib/log/logger.js';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { hookable } from '../../../lib/util/hookable.js';\nimport { ShipmentStatus } from '../../../types/order.js';\n\nfunction validateShipmentStatusBeforeUpdate(status: string): boolean {\n  const shipmentStatusList = getConfig(\n    'oms.order.shipmentStatus',\n    {}\n  ) as Record<string, ShipmentStatus>;\n  if (!shipmentStatusList[status]) {\n    throw new Error('Invalid status');\n  }\n  return false;\n}\n\nasync function changeShipmentStatus(\n  orderId: number,\n  status: string,\n  connection: PoolClient\n) {\n  const order = await update('order')\n    .given({\n      shipment_status: status\n    })\n    .where('order_id', '=', orderId)\n    .execute(connection);\n  return order;\n}\n\nexport const updateShipmentStatus = async (\n  orderId: number,\n  status: string,\n  conn?: PoolClient\n): Promise<void> => {\n  const connection = conn || (await getConnection(pool));\n  try {\n    if (!conn) {\n      await startTransaction(connection);\n    }\n    hookable(validateShipmentStatusBeforeUpdate, { orderId })(status);\n    await hookable(changeShipmentStatus, {\n      orderId,\n      status\n    })(orderId, status, connection);\n    if (!conn) {\n      await commit(connection);\n    }\n  } catch (err) {\n    error(err);\n    if (!conn) {\n      await rollback(connection);\n    }\n    throw err;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/oms/subscribers/order_placed/sendOrderConfirmationEmail.ts",
    "content": "import fs from 'fs/promises';\nimport path from 'path';\nimport { select } from '@evershop/postgres-query-builder';\nimport { CONSTANTS } from '../../../../lib/helpers.js';\nimport { countries } from '../../../../lib/locale/countries.js';\nimport { provinces } from '../../../../lib/locale/provinces.js';\nimport { translate } from '../../../../lib/locale/translate/translate.js';\nimport { debug, error } from '../../../../lib/log/logger.js';\nimport {\n  buildEmailBodyFromTemplate,\n  sendEmail\n} from '../../../../lib/mail/emailHelper.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getBaseUrl } from '../../../../lib/util/getBaseUrl.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport { getValue } from '../../../../lib/util/registry.js';\nimport { EventData } from '../../../../types/event.js';\n\nconst TEMPLATE = `<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html dir=\"ltr\" lang=\"en\">\n  <head>\n    {{#if storeInfo.logo}}\n    <link rel=\"preload\" as=\"image\" href=\"{{storeInfo.logo.url}}\" />\n    {{/if}}\n    <meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\" />\n  </head>\n  <div\n    style=\"\n      display: none;\n      overflow: hidden;\n      line-height: 1px;\n      opacity: 0;\n      max-height: 0;\n      max-width: 0;\n    \"\n  >\n    Your order has been confirmed. Thanks for shopping with us.\n    <div>\n       ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿ ‌​‍‎‏﻿\n    </div>\n  </div>\n\n  <body\n    style=\"\n      background-color: #ffffff;\n      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,\n        Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;\n    \"\n  >\n    <table\n      align=\"center\"\n      width=\"100%\"\n      border=\"0\"\n      cellpadding=\"0\"\n      cellspacing=\"0\"\n      role=\"presentation\"\n      style=\"\n        max-width: 100%;\n        margin: 10px auto;\n        width: 600px;\n        border: 1px solid #e5e5e5;\n      \"\n    >\n      <tbody>\n        <tr style=\"width: 100%\">\n          <td>\n            <hr\n              style=\"\n                width: 100%;\n                border: none;\n                border-top: 1px solid #eaeaea;\n                border-color: #e5e5e5;\n                margin: 0;\n              \"\n            />\n            <table\n              align=\"center\"\n              width=\"100%\"\n              border=\"0\"\n              cellpadding=\"0\"\n              cellspacing=\"0\"\n              role=\"presentation\"\n              style=\"padding: 40px 74px; text-align: center\"\n            >\n              <tbody>\n                <tr>\n                  <td>\n                    {{#if storeInfo.logo}}\n                    <img\n                      alt=\"{{storeInfo.logo.alt}}\"\n                      height=\"{{storeInfo.logo.height}}\"\n                      src=\"{{storeInfo.logo.src}}\"\n                      style=\"\n                        display: block;\n                        outline: none;\n                        border: none;\n                        text-decoration: none;\n                      \"\n                      width=\"{{storeInfo.logo.width}}\"\n                    />\n                    {{/if}}\n                    <h1\n                      style=\"\n                        font-size: 32px;\n                        line-height: 1.3;\n                        font-weight: 700;\n                        text-align: center;\n                        letter-spacing: -1px;\n                      \"\n                    >\n                      Thanks for shopping with us.\n                    </h1>\n                    <hr\n                      style=\"\n                        width: 100%;\n                        border: none;\n                        border-top: 1px solid #eaeaea;\n                        border-color: #e5e5e5;\n                        margin: 0;\n                      \"\n                    />\n                    <table\n                      align=\"center\"\n                      width=\"100%\"\n                      border=\"0\"\n                      cellpadding=\"0\"\n                      cellspacing=\"0\"\n                      role=\"presentation\"\n                      style=\"\n                        padding-left: 40px;\n                        padding-right: 40px;\n                        padding-top: 22px;\n                        padding-bottom: 22px;\n                      \"\n                    >\n                      <tbody>\n                        <tr>\n                          <td>\n                            <table\n                              align=\"center\"\n                              width=\"100%\"\n                              border=\"0\"\n                              cellpadding=\"0\"\n                              cellspacing=\"0\"\n                              role=\"presentation\"\n                              style=\"display: inline-flex\"\n                            >\n                              <tbody style=\"width: 100%\">\n                                <tr style=\"width: 100%\">\n                                  <td\n                                    data-id=\"__react-email-column\"\n                                    style=\"width: 170px\"\n                                  >\n                                    <p\n                                      style=\"\n                                        font-size: 14px;\n                                        line-height: 2;\n                                        margin: 0;\n                                        font-weight: bold;\n                                      \"\n                                    >\n                                      Order Number\n                                    </p>\n                                    <p\n                                      style=\"\n                                        font-size: 14px;\n                                        line-height: 1.4;\n                                        margin: 12px 0 0 0;\n                                        font-weight: 500;\n                                        color: #6f6f6f;\n                                      \"\n                                    >\n                                      #{{order.order_number}}\n                                    </p>\n                                  </td>\n                                  <td data-id=\"__react-email-column\">\n                                    <p\n                                      style=\"\n                                        font-size: 14px;\n                                        line-height: 2;\n                                        margin: 0;\n                                        font-weight: bold;\n                                      \"\n                                    >\n                                      Order Date\n                                    </p>\n                                    <p\n                                      style=\"\n                                        font-size: 14px;\n                                        line-height: 1.4;\n                                        margin: 12px 0 0 0;\n                                        font-weight: 500;\n                                        color: #6f6f6f;\n                                      \"\n                                    >\n                                      {{date order.created_at}}\n                                    </p>\n                                  </td>\n                                </tr>\n                              </tbody>\n                            </table>\n                          </td>\n                        </tr>\n                      </tbody>\n                    </table>\n                  </td>\n                </tr>\n              </tbody>\n            </table>\n            {{#if shippingAddress}}\n              <hr\n                style=\"\n                  width: 100%;\n                  border: none;\n                  border-top: 1px solid #eaeaea;\n                  border-color: #e5e5e5;\n                  margin: 0;\n                \"\n              />\n              <table\n                align=\"center\"\n                width=\"100%\"\n                border=\"0\"\n                cellpadding=\"0\"\n                cellspacing=\"0\"\n                role=\"presentation\"\n                style=\"\n                  padding-left: 40px;\n                  padding-right: 40px;\n                  padding-top: 22px;\n                  padding-bottom: 22px;\n                \"\n              >\n                <tbody>\n                  <tr>\n                    <td>\n                      <p\n                        style=\"\n                          font-size: 15px;\n                          line-height: 2;\n                          margin: 0;\n                          font-weight: bold;\n                        \"\n                      >\n                        Shipping to: {{shippingAddress.full_name}}\n                      </p>\n                      <p\n                        style=\"\n                          font-size: 14px;\n                          line-height: 2;\n                          margin: 0;\n                          color: #747474;\n                          font-weight: 500;\n                        \"\n                      >\n                        {{shippingAddress.address_1}}, {{shippingAddress.city}},\n                        {{shippingAddress.province_name}}\n                        {{shippingAddress.postcode}}\n                      </p>\n                    </td>\n                  </tr>\n                </tbody>\n              </table>\n            {{/if}}\n            <hr\n              style=\"\n                width: 100%;\n                border: none;\n                border-top: 1px solid #eaeaea;\n                border-color: #e5e5e5;\n                margin: 0;\n              \"\n            />\n            <table\n              align=\"center\"\n              width=\"100%\"\n              border=\"0\"\n              cellpadding=\"0\"\n              cellspacing=\"0\"\n              role=\"presentation\"\n              style=\"\n                padding-left: 40px;\n                padding-right: 40px;\n                padding-top: 40px;\n                padding-bottom: 40px;\n              \"\n            >\n              <tbody>\n                <tr>\n                  <td>\n                    <table\n                      align=\"center\"\n                      width=\"100%\"\n                      border=\"0\"\n                      cellpadding=\"0\"\n                      cellspacing=\"0\"\n                      role=\"presentation\"\n                    >\n                      <tbody style=\"width: 100%\">\n                        {{#each order.items}}\n                        <tr style=\"width: 100%\">\n                          <td data-id=\"__react-email-column\">\n                            <img\n                              alt=\"{{this.product_name}}\"\n                              src=\"{{this.thumbnail}}\"\n                              style=\"\n                                display: block;\n                                outline: none;\n                                border: none;\n                                text-decoration: none;\n                                float: left;\n                              \"\n                              width=\"100px\"\n                            />\n                          </td>\n                          <td\n                            data-id=\"__react-email-column\"\n                            style=\"vertical-align: top; padding-left: 12px\"\n                          >\n                            <p\n                              style=\"\n                                font-size: 14px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 500;\n                              \"\n                            >\n                              {{this.product_name}}\n                            </p>\n                            <p\n                              style=\"\n                                font-size: 14px;\n                                line-height: 2;\n                                margin: 0;\n                                color: #747474;\n                                font-weight: 500;\n                              \"\n                            >\n                              {{this.qty}} | {{currency this.final_price}}\n                            </p>\n                          </td>\n                        </tr>\n                        {{/each}}\n                      </tbody>\n                    </table>\n                  </td>\n                </tr>\n              </tbody>\n            </table>\n            <hr\n              style=\"\n                width: 100%;\n                border: none;\n                border-top: 1px solid #eaeaea;\n                border-color: #e5e5e5;\n                margin: 0;\n              \"\n            />\n            <table\n              align=\"center\"\n              width=\"100%\"\n              border=\"0\"\n              cellpadding=\"0\"\n              cellspacing=\"0\"\n              role=\"presentation\"\n              style=\"\n                padding-left: 40px;\n                padding-right: 40px;\n                padding-top: 40px;\n                padding-bottom: 40px;\n              \"\n            >\n              <tbody>\n                <tr>\n                  <td>\n                    <table\n                      align=\"center\"\n                      width=\"100%\"\n                      border=\"0\"\n                      cellpadding=\"0\"\n                      cellspacing=\"0\"\n                      role=\"presentation\"\n                    >\n                      <tbody style=\"width: 100%\">\n                        <tr style=\"width: 100%\">\n                          <td data-id=\"__react-email-column\">\n                            <p\n                              style=\"\n                                font-size: 14px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 500;\n                              \"\n                            >\n                              Subtotal:\n                            </p>\n                          </td>\n                          <td\n                            data-id=\"__react-email-column\"\n                            style=\"vertical-align: top; padding-left: 12px; text-align: right\"\n                          >\n                            <p\n                              style=\"\n                                font-size: 14px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 500;\n                              \"\n                            >\n                              {{currency order.sub_total}}\n                            </p>\n                          </td>\n                        </tr>\n                        {{#if order.discount_amount}}\n                        <tr style=\"width: 100%\">\n                          <td data-id=\"__react-email-column\">\n                            <p\n                              style=\"\n                                font-size: 14px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 500;\n                              \"\n                            >\n                              Discount:\n                            </p>\n                          </td>\n                          <td\n                            data-id=\"__react-email-column\"\n                            style=\"vertical-align: top; padding-left: 12px; text-align: right\"\n                          >\n                            <p\n                              style=\"\n                                font-size: 14px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 500;\n                              \"\n                            >\n                              -{{currency order.discount_amount}}\n                            </p>\n                          </td>\n                        </tr>\n                        {{/if}}\n                        <tr style=\"width: 100%\">\n                          <td data-id=\"__react-email-column\">\n                            <p\n                              style=\"\n                                font-size: 14px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 500;\n                              \"\n                            >\n                              Shipping:\n                            </p>\n                          </td>\n                          <td\n                            data-id=\"__react-email-column\"\n                            style=\"vertical-align: top; padding-left: 12px; text-align: right\"\n                          >\n                            <p\n                              style=\"\n                                font-size: 14px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 500;\n                              \"\n                            >\n                              {{currency order.shipping_fee_incl_tax}}\n                            </p>\n                          </td>\n                        </tr>\n                        <tr style=\"width: 100%\">\n                          <td data-id=\"__react-email-column\">\n                            <p\n                              style=\"\n                                font-size: 14px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 500;\n                              \"\n                            >\n                              Tax:\n                            </p>\n                          </td>\n                          <td\n                            data-id=\"__react-email-column\"\n                            style=\"vertical-align: top; padding-left: 12px; text-align: right\"\n                          >\n                            <p\n                              style=\"\n                                font-size: 14px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 500;\n                              \"\n                            >\n                              {{currency order.tax_amount}}\n                            </p>\n                          </td>\n                        </tr>\n                        <tr style=\"width: 100%\">\n                          <td data-id=\"__react-email-column\" style=\"padding-top: 12px\">\n                            <p\n                              style=\"\n                                font-size: 16px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 600;\n                              \"\n                            >\n                              Total:\n                            </p>\n                          </td>\n                          <td\n                            data-id=\"__react-email-column\"\n                            style=\"vertical-align: top; padding-left: 12px; padding-top: 12px; text-align: right\"\n                          >\n                            <p\n                              style=\"\n                                font-size: 16px;\n                                line-height: 2;\n                                margin: 0;\n                                font-weight: 600;\n                              \"\n                            >\n                              {{currency order.grand_total}}\n                            </p>\n                          </td>\n                        </tr>\n                      </tbody>\n                    </table>\n                  </td>\n                </tr>\n              </tbody>\n            </table>\n            <hr\n              style=\"\n                width: 100%;\n                border: none;\n                border-top: 1px solid #eaeaea;\n                border-color: #e5e5e5;\n                margin: 0;\n              \"\n            />\n            <table\n              align=\"center\"\n              width=\"100%\"\n              border=\"0\"\n              cellpadding=\"0\"\n              cellspacing=\"0\"\n              role=\"presentation\"\n              style=\"padding-top: 22px; padding-bottom: 22px\"\n            >\n              <tbody>\n                <tr>\n                  <td>\n                    <table\n                      align=\"center\"\n                      width=\"100%\"\n                      border=\"0\"\n                      cellpadding=\"0\"\n                      cellspacing=\"0\"\n                      role=\"presentation\"\n                    >\n                      <tbody style=\"width: 100%\">\n                        <tr style=\"width: 100%\">\n                          <p\n                            style=\"\n                              font-size: 13px;\n                              line-height: 24px;\n                              margin: 0;\n                              color: #afafaf;\n                              text-align: center;\n                              padding-top: 30px;\n                              padding-bottom: 30px;\n                            \"\n                          >\n                            Please contact us if you have any questions.\n                          </p>\n                        </tr>\n                      </tbody>\n                    </table>\n                    <table\n                      align=\"center\"\n                      width=\"100%\"\n                      border=\"0\"\n                      cellpadding=\"0\"\n                      cellspacing=\"0\"\n                      role=\"presentation\"\n                    >\n                      <tbody style=\"width: 100%\">\n                        <tr style=\"width: 100%\">\n                          <p\n                            style=\"\n                              font-size: 13px;\n                              line-height: 24px;\n                              margin: 0;\n                              color: #afafaf;\n                              text-align: center;\n                            \"\n                          >\n                            {{storeInfo.address.street}},\n                            {{storeInfo.address.city}},\n                            {{storeInfo.address.state}}\n                            {{storeInfo.address.zip}},\n                            {{storeInfo.address.country}}\n                          </p>\n                        </tr>\n                      </tbody>\n                    </table>\n                  </td>\n                </tr>\n              </tbody>\n            </table>\n          </td>\n        </tr>\n      </tbody>\n    </table>\n  </body>\n</html>`;\n\nexport default async function sendOrderConfirmationEmail(\n  data: EventData<'order_placed'>\n) {\n  try {\n    const config = getConfig('system.notification_emails.order_confirmation', {\n      enabled: true\n    });\n\n    if (config?.enabled === false) {\n      return;\n    }\n    // Build the email data\n    const orderId = data.order_id;\n    const order = await select()\n      .from('order')\n      .where('order_id', '=', orderId)\n      .load(pool);\n\n    if (!order) {\n      return;\n    }\n\n    const items = await select()\n      .from('order_item')\n      .where('order_item_order_id', '=', order.order_id)\n      .execute(pool);\n    // Loop through each item to add the base Url before the thumbnail path\n    order.items = items.map((item) => {\n      if (item.thumbnail) {\n        const baseUrl = getBaseUrl();\n        item.thumbnail = `${baseUrl}${item.thumbnail}`;\n      }\n      return item;\n    });\n    const shippingAddress = await select()\n      .from('order_address')\n      .where('order_address_id', '=', order.shipping_address_id)\n      .load(pool);\n    if (!data.no_shipping_required) {\n      shippingAddress.country_name =\n        countries.find((c) => c.code === shippingAddress.country)?.name || '';\n      shippingAddress.province_name =\n        provinces.find((p) => p.code === shippingAddress.province)?.name || '';\n    }\n\n    const billingAddress = await select()\n      .from('order_address')\n      .where('order_address_id', '=', order.billing_address_id)\n      .load(pool);\n\n    billingAddress.country_name =\n      countries.find((c) => c.code === billingAddress.country)?.name || '';\n\n    billingAddress.province_name =\n      provinces.find((p) => p.code === billingAddress.province)?.name || '';\n\n    let template;\n    if (config?.templatePath) {\n      const filePath = path.join(CONSTANTS.ROOTPATH, config.templatePath);\n      try {\n        await fs.access(filePath);\n        template = await fs.readFile(filePath, 'utf8');\n      } catch (error) {\n        debug(\n          `Order confirmation email template file not found at path: ${filePath}. Using default template.`\n        );\n        template = TEMPLATE;\n      }\n    } else {\n      template = TEMPLATE;\n    }\n    const dynamicData = await getValue('orderConfirmationEmailData', {\n      order,\n      shippingAddress,\n      billingAddress\n    });\n    const subject = translate('Your order has been confirmed!');\n    if (data.customer_email) {\n      const args = await getValue(\n        'orderConfirmationEmailArguments',\n        {\n          to: data.customer_email,\n          subject,\n          template,\n          data: dynamicData\n        },\n        { order }\n      );\n      await sendEmail('order_confirmation', args);\n    }\n  } catch (e) {\n    error(e);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalAuthorizePayment/[bodyParser]authorize.ts",
    "content": "import {\n  getConnection,\n  insert,\n  select\n} from '@evershop/postgres-query-builder';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport addOrderActivityLog from '../../../oms/services/addOrderActivityLog.js';\nimport { updatePaymentStatus } from '../../../oms/services/updatePaymentStatus.js';\nimport { createAxiosInstance } from '../../services/requester.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  try {\n    const { order_id } = request.body;\n    // Validate the order;\n    const order = await select()\n      .from('order')\n      .where('uuid', '=', order_id)\n      .and('payment_method', '=', 'paypal')\n      .and('payment_status', '=', 'pending')\n      .load(pool);\n\n    if (!order) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid order'\n        }\n      });\n    } else {\n      // Call API to authorize the paypal order using axios\n      const axiosInstance = await createAxiosInstance(request);\n      const responseData = await axiosInstance.post(\n        `/v2/checkout/orders/${order.integration_order_id}/authorize`\n      );\n\n      if (responseData.data.status === 'COMPLETED') {\n        await updatePaymentStatus(order.order_id, 'authorized');\n        // Add transaction data to database\n        await insert('payment_transaction')\n          .given({\n            payment_transaction_order_id: order.order_id,\n            transaction_id:\n              responseData.data.purchase_units[0].payments.authorizations[0].id,\n            amount:\n              responseData.data.purchase_units[0].payments.authorizations[0]\n                .amount.value,\n            currency:\n              responseData.data.purchase_units[0].payments.authorizations[0]\n                .amount.currency_code,\n            status:\n              responseData.data.purchase_units[0].payments.authorizations[0]\n                .status,\n            payment_action: 'authorize',\n            transaction_type: 'online',\n            additional_information: JSON.stringify(responseData.data)\n          })\n          .execute(pool);\n\n        // Save order activities\n        await addOrderActivityLog(\n          order.order_id,\n          `Customer authorized the payment using PayPal. Transaction ID: ${responseData.data.purchase_units[0].payments.authorizations[0].id}`,\n          false,\n          await getConnection(pool)\n        );\n\n        response.status(OK);\n        response.json({\n          data: {}\n        });\n      } else {\n        response.status(INTERNAL_SERVER_ERROR);\n        response.json({\n          error: {\n            status: INTERNAL_SERVER_ERROR,\n            message: responseData.data.message\n          }\n        });\n      }\n    }\n  } catch (err) {\n    error(err);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: err.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalAuthorizePayment/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalAuthorizePayment/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"order_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"order_id\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalAuthorizePayment/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/paypal/authorizedTransactions\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCaptureAuthorizedPayment/[bodyParser]capture.ts",
    "content": "import {\n  commit,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { AxiosError } from 'axios';\nimport { error } from '../../../../lib/log/logger.js';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport addOrderActivityLog from '../../../oms/services/addOrderActivityLog.js';\nimport { updatePaymentStatus } from '../../../oms/services/updatePaymentStatus.js';\nimport { createAxiosInstance } from '../../services/requester.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const { order_id } = request.body;\n    // Validate the order;\n    const order = await select()\n      .from('order')\n      .where('uuid', '=', order_id)\n      .and('payment_method', '=', 'paypal')\n      .load(connection);\n\n    if (!order) {\n      await rollback(connection);\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid order'\n        }\n      });\n    } else {\n      // Get the payment transaction\n      const transaction = await select()\n        .from('payment_transaction')\n        .where('payment_transaction_order_id', '=', order.order_id)\n        .load(connection);\n      if (!transaction) {\n        await rollback(connection);\n        response.status(INVALID_PAYLOAD);\n        response.json({\n          error: {\n            status: INVALID_PAYLOAD,\n            message: 'Can not find payment transaction'\n          }\n        });\n        return;\n      }\n      const axiosInstance = await createAxiosInstance(request);\n      // Get the transaction details from Paypal\n      const transactionDetails = await axiosInstance.get(\n        `/v2/payments/authorizations/${transaction.transaction_id}`\n      );\n      if (transactionDetails.data.status === 'CAPTURED') {\n        await updatePaymentStatus(order.order_id, 'paid');\n        // Save order activities\n        await addOrderActivityLog(\n          order.order_id,\n          `Captured the payment. Transaction ID: ${transaction.transaction_id}`,\n          false,\n          connection\n        );\n        await commit(connection);\n        response.status(OK);\n        response.json({\n          data: {}\n        });\n        return;\n      } else {\n        // Call API to authorize the paypal order using axios\n        const responseData = await axiosInstance.post(\n          `/v2/payments/authorizations/${transaction.transaction_id}/capture`\n        );\n        if (responseData.data.status === 'COMPLETED') {\n          // Update payment status\n          await updatePaymentStatus(order.order_id, 'paid', connection);\n          // Save order activities\n          await addOrderActivityLog(\n            order.order_id,\n            `Captured the payment. Transaction ID: ${transaction.transaction_id}`,\n            false,\n            connection\n          );\n          await commit(connection);\n          response.status(OK);\n          response.json({\n            data: {}\n          });\n          return;\n        } else {\n          await rollback(connection);\n          response.status(INTERNAL_SERVER_ERROR);\n          response.json({\n            error: {\n              status: INTERNAL_SERVER_ERROR,\n              message: responseData.data.message\n            }\n          });\n          return;\n        }\n      }\n    }\n  } catch (err) {\n    await rollback(connection);\n    error(err);\n    if (err instanceof AxiosError) {\n      response.status(\n        err.response?.status ? err.response?.status : INTERNAL_SERVER_ERROR\n      );\n      response.json({\n        error: {\n          status: err.response?.status,\n          message: err.response?.data.message\n        }\n      });\n    } else {\n      response.status(INTERNAL_SERVER_ERROR);\n      response.json({\n        error: {\n          status: INTERNAL_SERVER_ERROR,\n          message: 'Internal server error'\n        }\n      });\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCaptureAuthorizedPayment/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCaptureAuthorizedPayment/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"order_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"order_id\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCaptureAuthorizedPayment/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/paypal/authorizations/capture\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCapturePayment/[bodyParser]capture.ts",
    "content": "import {\n  commit,\n  insert,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { error } from '../../../../lib/log/logger.js';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport addOrderActivityLog from '../../../oms/services/addOrderActivityLog.js';\nimport { updatePaymentStatus } from '../../../oms/services/updatePaymentStatus.js';\nimport { createAxiosInstance } from '../../services/requester.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const { order_id } = request.body;\n    // Validate the order;\n    const order = await select()\n      .from('order')\n      .where('uuid', '=', order_id)\n      .load(connection);\n\n    if (!order) {\n      await rollback(connection);\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid order id'\n        }\n      });\n    } else {\n      // Call API to authorize the paypal order using axios\n      const axiosInstance = await createAxiosInstance(request);\n      const responseData = await axiosInstance.post(\n        `/v2/checkout/orders/${order.integration_order_id}/capture`\n      );\n\n      if (responseData.data.status === 'COMPLETED') {\n        // Update payment status\n        await updatePaymentStatus(order.order_id, 'paid', connection);\n        // Add transaction data to database\n        await insert('payment_transaction')\n          .given({\n            payment_transaction_order_id: order.order_id,\n            transaction_id:\n              responseData.data.purchase_units[0].payments.captures[0].id,\n            amount:\n              responseData.data.purchase_units[0].payments.captures[0].amount\n                .value,\n            currency:\n              responseData.data.purchase_units[0].payments.captures[0].amount\n                .currency_code,\n            status:\n              responseData.data.purchase_units[0].payments.captures[0].status,\n            payment_action: 'capture',\n            transaction_type: 'online',\n            additional_information: JSON.stringify(responseData.data)\n          })\n          .execute(connection);\n\n        // Save order activities\n        await addOrderActivityLog(\n          order.order_id,\n          `Captured the payment. Transaction ID: ${responseData.data.purchase_units[0].payments.captures[0].id}`,\n          false,\n          connection\n        );\n        await commit(connection);\n        response.status(OK);\n        response.json({\n          data: {}\n        });\n      } else {\n        await rollback(connection);\n        response.status(INTERNAL_SERVER_ERROR);\n        response.json({\n          error: {\n            status: INTERNAL_SERVER_ERROR,\n            message: responseData.data.message\n          }\n        });\n      }\n    }\n  } catch (err) {\n    error(err);\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: 'Internal server error'\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCapturePayment/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCapturePayment/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"order_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"order_id\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCapturePayment/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/paypal/captureTransactions\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCreateOrder/[bodyParser]createOrder.ts",
    "content": "import { select, update } from '@evershop/postgres-query-builder';\nimport type { PurchaseUnit, CreateOrderRequestBody } from '@paypal/paypal-js';\nimport { debug, error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { getValueSync } from '../../../../lib/util/registry.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport { toPrice } from '../../../checkout/services/toPrice.js';\nimport { getContextValue } from '../../../graphql/services/contextHelper.js';\nimport { getSetting } from '../../../setting/services/setting.js';\nimport { createAxiosInstance } from '../../services/requester.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  try {\n    const { order_id } = request.body;\n    const order = await select()\n      .from('order')\n      .where('uuid', '=', order_id)\n      .and('payment_method', '=', 'paypal')\n      .and('payment_status', '=', 'pending')\n      .load(pool);\n\n    if (!order) {\n      return response.status(INVALID_PAYLOAD).json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid order'\n        }\n      });\n    } else {\n      // Build the order for createOrder API PayPal\n      const items = await select()\n        .from('order_item')\n        .where('order_item_order_id', '=', order.order_id)\n        .execute(pool);\n      const catalogPriceInclTax = getConfig(\n        'pricing.tax.price_including_tax',\n        false\n      );\n      const amount = {\n        currency_code: order.currency,\n        value: toPrice(order.grand_total),\n        breakdown: {\n          item_total: {\n            currency_code: order.currency,\n            value: catalogPriceInclTax\n              ? toPrice(order.sub_total_incl_tax)\n              : toPrice(order.sub_total)\n          },\n          shipping: {\n            currency_code: order.currency,\n            value: catalogPriceInclTax\n              ? toPrice(order.shipping_fee_incl_tax)\n              : toPrice(order.shipping_fee_excl_tax)\n          },\n          discount: {\n            currency_code: order.currency,\n            value: toPrice(order.discount_amount)\n          },\n          tax_total: !catalogPriceInclTax\n            ? {\n                currency_code: order.currency,\n                value: toPrice(order.total_tax_amount)\n              }\n            : undefined\n        }\n      } as PurchaseUnit['amount'];\n\n      const finalAmount = getValueSync('paypalFinalAmount', amount, {\n        order,\n        items\n      });\n\n      const orderData = {\n        intent: await getSetting('paypalPaymentIntent', 'CAPTURE'),\n        purchase_units: [\n          {\n            items: items.map((item) => ({\n              name: item.product_name,\n              sku: item.product_sku,\n              quantity: item.qty,\n              unit_amount: {\n                currency_code: order.currency,\n                value: catalogPriceInclTax\n                  ? toPrice(item.final_price_incl_tax)\n                  : toPrice(item.final_price)\n              },\n              category: item.no_shipping_required\n                ? 'DIGITAL_GOODS'\n                : 'PHYSICAL_GOODS'\n            })),\n            amount: finalAmount\n          }\n        ],\n        application_context: {\n          cancel_url: `${getContextValue<string>(\n            request,\n            'homeUrl',\n            ''\n          )}${buildUrl('paypalCancel', { order_id })}`,\n          return_url: `${getContextValue<string>(\n            request,\n            'homeUrl',\n            ''\n          )}${buildUrl('paypalReturn', { order_id })}`,\n          shipping_preference: 'SET_PROVIDED_ADDRESS',\n          user_action: 'PAY_NOW',\n          brand_name: await getSetting('storeName', 'Evershop')\n        }\n      } as CreateOrderRequestBody;\n      const shippingAddress = await select()\n        .from('order_address')\n        .where('order_address_id', '=', order.shipping_address_id)\n        .load(pool);\n\n      // Add shipping address\n      if (shippingAddress) {\n        const address: any = {\n          address_line_1: shippingAddress.address_1,\n          postal_code: shippingAddress.postcode,\n          country_code: shippingAddress.country\n        };\n        if (shippingAddress.address_2) {\n          address.address_line_2 = shippingAddress.address_2;\n        }\n        if (shippingAddress.city) {\n          address.admin_area_2 = shippingAddress.city;\n        }\n        if (shippingAddress.province) {\n          address.admin_area_1 = shippingAddress.province.split('-').pop();\n        }\n        orderData.purchase_units[0].shipping = {\n          name: {\n            full_name: `${shippingAddress.full_name}`\n          },\n          type: 'SHIPPING',\n          address\n        };\n      } else {\n        orderData.application_context = orderData.application_context || {};\n        orderData.application_context.shipping_preference = 'NO_SHIPPING';\n      }\n      const finalPaypalOrderData = getValueSync<CreateOrderRequestBody>(\n        'finalPaypalOrderData',\n        orderData,\n        {\n          order,\n          items,\n          shippingAddress\n        }\n      );\n      // Call PayPal API to create order using axios\n      const axiosInstance = await createAxiosInstance(request);\n      const { data } = await axiosInstance.post(\n        `/v2/checkout/orders`,\n        finalPaypalOrderData,\n        {\n          validateStatus: (status) => status < 500\n        }\n      );\n\n      if (data.id) {\n        // Update order and insert papal order id\n        await update('order')\n          .given({ integration_order_id: data.id })\n          .where('uuid', '=', order_id)\n          .execute(pool);\n\n        response.status(OK);\n        return response.json({\n          data: {\n            paypalOrderId: data.id,\n            approveUrl: data.links.find((link) => link.rel === 'approve').href\n          }\n        });\n      } else {\n        debug('PayPal create order error');\n        debug(data);\n        // Re-active the cart\n        await update('cart')\n          .given({ status: true })\n          .where('cart_id', '=', order.cart_id)\n          .execute(pool);\n        response.status(INTERNAL_SERVER_ERROR);\n        return response.json({\n          error: {\n            status: INTERNAL_SERVER_ERROR,\n            message: data.message\n          }\n        });\n      }\n    }\n  } catch (err) {\n    error(err);\n    return next(err);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCreateOrder/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCreateOrder/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"order_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"order_id\"],\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/api/paypalCreateOrder/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/paypal/orders\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/bootstrap.js",
    "content": "import { getConfig } from '../../lib/util/getConfig.js';\nimport { hookAfter } from '../../lib/util/hookable.js';\nimport { addProcessor } from '../../lib/util/registry.js';\nimport { registerPaymentMethod } from '../checkout/services/getAvailablePaymentMethods.js';\nimport { getSetting } from '../setting/services/setting.js';\nimport { voidPaymentTransaction } from './services/voidPaymentTransaction.js';\n\nexport default async () => {\n  hookAfter('changePaymentStatus', async (order, orderID, status) => {\n    if (status !== 'canceled') {\n      return;\n    }\n    if (order.payment_method !== 'paypal') {\n      return;\n    }\n    await voidPaymentTransaction(orderID);\n  });\n\n  registerPaymentMethod({\n    init: async () => ({\n      code: 'paypal',\n      name: await getSetting('paypalDisplayName', 'PayPal')\n    }),\n    validator: async () => {\n      const paypalConfig = getConfig('system.paypal', {});\n      let paypalStatus;\n      if (paypalConfig.status) {\n        paypalStatus = paypalConfig.status;\n      } else {\n        paypalStatus = await getSetting('paypalPaymentStatus', 0);\n      }\n      if (parseInt(paypalStatus, 10) === 1) {\n        return true;\n      } else {\n        return false;\n      }\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/graphql/types/PaypalSetting/PaypalSetting.admin.graphql",
    "content": "extend type Setting {\n  paypalPaymentStatus: Int\n  paypalClientId: String\n  paypalClientSecret: String\n  paypalWebhookSecret: String\n  paypalPaymentIntent: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/graphql/types/PaypalSetting/PaypalSetting.admin.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Setting: {\n    paypalPaymentStatus: (setting) => {\n      const paypalConfig = getConfig('system.paypal', {});\n      if (paypalConfig.status) {\n        return paypalConfig.status;\n      }\n      const paypalPaymentStatus = setting.find(\n        (s) => s.name === 'paypalPaymentStatus'\n      );\n      if (paypalPaymentStatus) {\n        return parseInt(paypalPaymentStatus.value, 10);\n      } else {\n        return 0;\n      }\n    },\n    paypalPaymentIntent: (setting) => {\n      const paypalPaymentIntent = setting.find(\n        (s) => s.name === 'paypalPaymentIntent'\n      );\n      if (paypalPaymentIntent) {\n        return paypalPaymentIntent.value;\n      } else {\n        return 'CAPTURE';\n      }\n    },\n    paypalClientId: (setting) => {\n      const paypalConfig = getConfig('system.paypal', {});\n      if (paypalConfig.clientId) {\n        return paypalConfig.clientId;\n      }\n      const paypalClientId = setting.find((s) => s.name === 'paypalClientId');\n      if (paypalClientId) {\n        return paypalClientId.value;\n      } else {\n        return null;\n      }\n    },\n    paypalClientSecret: (setting, _, { user }) => {\n      const paypalConfig = getConfig('system.paypal', {});\n      if (paypalConfig.clientSecret) {\n        return '*******************************';\n      }\n      if (user) {\n        const paypalClientSecret = setting.find(\n          (s) => s.name === 'paypalClientSecret'\n        );\n        if (paypalClientSecret) {\n          return paypalClientSecret.value;\n        } else {\n          return null;\n        }\n      } else {\n        return null;\n      }\n    },\n    paypalWebhookSecret: (setting, _, { user }) => {\n      const paypalConfig = getConfig('system.paypal', {});\n      if (paypalConfig.webhookSecret) {\n        return '*******************************';\n      }\n      if (user) {\n        const paypalWebhookSecret = setting.find(\n          (s) => s.name === 'paypalWebhookSecret'\n        );\n        if (paypalWebhookSecret) {\n          return paypalWebhookSecret.value;\n        } else {\n          return null;\n        }\n      } else {\n        return null;\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/graphql/types/PaypalSetting/PaypalSetting.graphql",
    "content": "extend type Setting {\n  paypalDisplayName: String\n  paypalEnvironment: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/graphql/types/PaypalSetting/PaypalSetting.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Setting: {\n    paypalDisplayName: (setting) => {\n      const paypalDisplayName = setting.find(\n        (s) => s.name === 'paypalDisplayName'\n      );\n      if (paypalDisplayName) {\n        return paypalDisplayName.value;\n      } else {\n        return 'Paypal';\n      }\n    },\n    paypalEnvironment: (setting) => {\n      const paypalConfig = getConfig('system.paypal', {});\n      if (paypalConfig.environment) {\n        return paypalConfig.environment;\n      }\n      const paypalEnvironment = setting.find(\n        (s) => s.name === 'paypalEnvironment'\n      );\n      if (paypalEnvironment) {\n        return paypalEnvironment.value;\n      } else {\n        return 'https://api-m.sandbox.paypal.com';\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/pages/admin/orderEdit/PaypalCaptureButton.tsx",
    "content": "import RenderIfTrue from '@components/common/RenderIfTrue.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { Card, CardContent } from '@components/common/ui/Card.js';\nimport axios from 'axios';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface Props {\n  captureAPI: string;\n  order: {\n    paymentStatus: {\n      code: string;\n    };\n    uuid: string;\n    paymentMethod: string;\n  };\n}\n\nexport default function PaypalCaptureButton({\n  captureAPI,\n  order: { paymentStatus, uuid, paymentMethod }\n}: Props) {\n  const [isLoading, setIsLoading] = React.useState(false);\n\n  const onAction = async () => {\n    try {\n      setIsLoading(true);\n      // Use Axios to call the capture API\n      const response = await axios.post(captureAPI, { order_id: uuid });\n      if (!response.data.error) {\n        // Reload the page\n        window.location.reload();\n      } else {\n        toast.error(response.data.error.message);\n      }\n      setIsLoading(false);\n    } catch (e) {\n      setIsLoading(false);\n      toast.error(e.message);\n    }\n  };\n\n  return (\n    <RenderIfTrue\n      condition={\n        paymentMethod === 'paypal' && paymentStatus.code === 'authorized'\n      }\n    >\n      <CardContent>\n        <div className=\"flex justify-end\">\n          <Button onClick={onAction} isLoading={isLoading}>\n            Capture Payment\n          </Button>\n        </div>\n      </CardContent>\n    </RenderIfTrue>\n  );\n}\n\nexport const layout = {\n  areaId: 'orderPaymentActions',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    captureAPI: url(routeId: \"paypalCaptureAuthorizedPayment\")\n    order(uuid: getContextValue(\"orderId\")) {\n      uuid\n      paymentStatus {\n        code\n      }\n      paymentMethod\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/pages/admin/paymentSetting/PaypalSetting.tsx",
    "content": "import { InputField } from '@components/common/form/InputField.js';\nimport { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport { ToggleField } from '@components/common/form/ToggleField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface PaypalPaymentProps {\n  setting: {\n    paypalPaymentStatus: true | false | 0 | 1;\n    paypalDisplayName: string;\n    paypalClientId: string;\n    paypalClientSecret: string;\n    paypalEnvironment: string;\n    paypalPaymentIntent: string;\n  };\n}\nexport default function PaypalPayment({\n  setting: {\n    paypalPaymentStatus,\n    paypalDisplayName,\n    paypalClientId,\n    paypalClientSecret,\n    paypalEnvironment,\n    paypalPaymentIntent\n  }\n}: PaypalPaymentProps) {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Paypal Payment</CardTitle>\n        <CardDescription>\n          Configure your Paypal payment gateway settings\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Enable?</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <ToggleField\n              name=\"paypalPaymentStatus\"\n              defaultValue={paypalPaymentStatus}\n              trueValue={1}\n              falseValue={0}\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Dislay Name</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <InputField\n              name=\"paypalDisplayName\"\n              placeholder=\"Display Name\"\n              defaultValue={paypalDisplayName}\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Client ID</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <InputField\n              name=\"paypalClientId\"\n              placeholder=\"Client ID\"\n              defaultValue={paypalClientId}\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Client Secret</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <InputField\n              name=\"paypalClientSecret\"\n              placeholder=\"Secret Key\"\n              defaultValue={paypalClientSecret}\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Environment</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <RadioGroupField\n              name=\"paypalEnvironment\"\n              defaultValue={paypalEnvironment}\n              options={[\n                {\n                  label: 'Sandbox',\n                  value: 'https://api-m.sandbox.paypal.com'\n                },\n                {\n                  label: 'Live',\n                  value: 'https://api-m.paypal.com'\n                }\n              ]}\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Payment mode</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <RadioGroupField\n              name=\"paypalPaymentIntent\"\n              defaultValue={paypalPaymentIntent}\n              options={[\n                { label: 'Authorize only', value: 'AUTHORIZE' },\n                { label: 'Capture', value: 'CAPTURE' }\n              ]}\n            />\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'paymentSetting',\n  sortOrder: 15\n};\n\nexport const query = `\n  query Query {\n    setting {\n      paypalPaymentStatus\n      paypalDisplayName\n      paypalClientId\n      paypalClientSecret\n      paypalEnvironment\n      paypalPaymentIntent\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/pages/frontStore/checkout/Paypal.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  useCheckout,\n  useCheckoutDispatch\n} from '@components/frontStore/checkout/CheckoutContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { ApiResponse } from '@evershop/evershop/types/apiResponse';\nimport React, { useEffect } from 'react';\nimport { toast } from 'react-toastify';\n\ninterface PaypalMethodProps {\n  createOrderAPI: string;\n  setting: {\n    paypalDisplayName: string;\n  };\n}\n\ninterface PaypalLogoProps {\n  width?: number;\n  height?: number;\n}\n\nfunction PaypalLogo({ width = 70, height = 30 }: PaypalLogoProps) {\n  return (\n    <img\n      width={width}\n      height={height}\n      src=\"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAxcHgiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAxMDEgMzIiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHhtbG5zPSJodHRwOiYjeDJGOyYjeDJGO3d3dy53My5vcmcmI3gyRjsyMDAwJiN4MkY7c3ZnIj48cGF0aCBmaWxsPSIjMDAzMDg3IiBkPSJNIDEyLjIzNyAyLjggTCA0LjQzNyAyLjggQyAzLjkzNyAyLjggMy40MzcgMy4yIDMuMzM3IDMuNyBMIDAuMjM3IDIzLjcgQyAwLjEzNyAyNC4xIDAuNDM3IDI0LjQgMC44MzcgMjQuNCBMIDQuNTM3IDI0LjQgQyA1LjAzNyAyNC40IDUuNTM3IDI0IDUuNjM3IDIzLjUgTCA2LjQzNyAxOC4xIEMgNi41MzcgMTcuNiA2LjkzNyAxNy4yIDcuNTM3IDE3LjIgTCAxMC4wMzcgMTcuMiBDIDE1LjEzNyAxNy4yIDE4LjEzNyAxNC43IDE4LjkzNyA5LjggQyAxOS4yMzcgNy43IDE4LjkzNyA2IDE3LjkzNyA0LjggQyAxNi44MzcgMy41IDE0LjgzNyAyLjggMTIuMjM3IDIuOCBaIE0gMTMuMTM3IDEwLjEgQyAxMi43MzcgMTIuOSAxMC41MzcgMTIuOSA4LjUzNyAxMi45IEwgNy4zMzcgMTIuOSBMIDguMTM3IDcuNyBDIDguMTM3IDcuNCA4LjQzNyA3LjIgOC43MzcgNy4yIEwgOS4yMzcgNy4yIEMgMTAuNjM3IDcuMiAxMS45MzcgNy4yIDEyLjYzNyA4IEMgMTMuMTM3IDguNCAxMy4zMzcgOS4xIDEzLjEzNyAxMC4xIFoiPjwvcGF0aD48cGF0aCBmaWxsPSIjMDAzMDg3IiBkPSJNIDM1LjQzNyAxMCBMIDMxLjczNyAxMCBDIDMxLjQzNyAxMCAzMS4xMzcgMTAuMiAzMS4xMzcgMTAuNSBMIDMwLjkzNyAxMS41IEwgMzAuNjM3IDExLjEgQyAyOS44MzcgOS45IDI4LjAzNyA5LjUgMjYuMjM3IDkuNSBDIDIyLjEzNyA5LjUgMTguNjM3IDEyLjYgMTcuOTM3IDE3IEMgMTcuNTM3IDE5LjIgMTguMDM3IDIxLjMgMTkuMzM3IDIyLjcgQyAyMC40MzcgMjQgMjIuMTM3IDI0LjYgMjQuMDM3IDI0LjYgQyAyNy4zMzcgMjQuNiAyOS4yMzcgMjIuNSAyOS4yMzcgMjIuNSBMIDI5LjAzNyAyMy41IEMgMjguOTM3IDIzLjkgMjkuMjM3IDI0LjMgMjkuNjM3IDI0LjMgTCAzMy4wMzcgMjQuMyBDIDMzLjUzNyAyNC4zIDM0LjAzNyAyMy45IDM0LjEzNyAyMy40IEwgMzYuMTM3IDEwLjYgQyAzNi4yMzcgMTAuNCAzNS44MzcgMTAgMzUuNDM3IDEwIFogTSAzMC4zMzcgMTcuMiBDIDI5LjkzNyAxOS4zIDI4LjMzNyAyMC44IDI2LjEzNyAyMC44IEMgMjUuMDM3IDIwLjggMjQuMjM3IDIwLjUgMjMuNjM3IDE5LjggQyAyMy4wMzcgMTkuMSAyMi44MzcgMTguMiAyMy4wMzcgMTcuMiBDIDIzLjMzNyAxNS4xIDI1LjEzNyAxMy42IDI3LjIzNyAxMy42IEMgMjguMzM3IDEzLjYgMjkuMTM3IDE0IDI5LjczNyAxNC42IEMgMzAuMjM3IDE1LjMgMzAuNDM3IDE2LjIgMzAuMzM3IDE3LjIgWiI+PC9wYXRoPjxwYXRoIGZpbGw9IiMwMDMwODciIGQ9Ik0gNTUuMzM3IDEwIEwgNTEuNjM3IDEwIEMgNTEuMjM3IDEwIDUwLjkzNyAxMC4yIDUwLjczNyAxMC41IEwgNDUuNTM3IDE4LjEgTCA0My4zMzcgMTAuOCBDIDQzLjIzNyAxMC4zIDQyLjczNyAxMCA0Mi4zMzcgMTAgTCAzOC42MzcgMTAgQyAzOC4yMzcgMTAgMzcuODM3IDEwLjQgMzguMDM3IDEwLjkgTCA0Mi4xMzcgMjMgTCAzOC4yMzcgMjguNCBDIDM3LjkzNyAyOC44IDM4LjIzNyAyOS40IDM4LjczNyAyOS40IEwgNDIuNDM3IDI5LjQgQyA0Mi44MzcgMjkuNCA0My4xMzcgMjkuMiA0My4zMzcgMjguOSBMIDU1LjgzNyAxMC45IEMgNTYuMTM3IDEwLjYgNTUuODM3IDEwIDU1LjMzNyAxMCBaIj48L3BhdGg+PHBhdGggZmlsbD0iIzAwOWNkZSIgZD0iTSA2Ny43MzcgMi44IEwgNTkuOTM3IDIuOCBDIDU5LjQzNyAyLjggNTguOTM3IDMuMiA1OC44MzcgMy43IEwgNTUuNzM3IDIzLjYgQyA1NS42MzcgMjQgNTUuOTM3IDI0LjMgNTYuMzM3IDI0LjMgTCA2MC4zMzcgMjQuMyBDIDYwLjczNyAyNC4zIDYxLjAzNyAyNCA2MS4wMzcgMjMuNyBMIDYxLjkzNyAxOCBDIDYyLjAzNyAxNy41IDYyLjQzNyAxNy4xIDYzLjAzNyAxNy4xIEwgNjUuNTM3IDE3LjEgQyA3MC42MzcgMTcuMSA3My42MzcgMTQuNiA3NC40MzcgOS43IEMgNzQuNzM3IDcuNiA3NC40MzcgNS45IDczLjQzNyA0LjcgQyA3Mi4yMzcgMy41IDcwLjMzNyAyLjggNjcuNzM3IDIuOCBaIE0gNjguNjM3IDEwLjEgQyA2OC4yMzcgMTIuOSA2Ni4wMzcgMTIuOSA2NC4wMzcgMTIuOSBMIDYyLjgzNyAxMi45IEwgNjMuNjM3IDcuNyBDIDYzLjYzNyA3LjQgNjMuOTM3IDcuMiA2NC4yMzcgNy4yIEwgNjQuNzM3IDcuMiBDIDY2LjEzNyA3LjIgNjcuNDM3IDcuMiA2OC4xMzcgOCBDIDY4LjYzNyA4LjQgNjguNzM3IDkuMSA2OC42MzcgMTAuMSBaIj48L3BhdGg+PHBhdGggZmlsbD0iIzAwOWNkZSIgZD0iTSA5MC45MzcgMTAgTCA4Ny4yMzcgMTAgQyA4Ni45MzcgMTAgODYuNjM3IDEwLjIgODYuNjM3IDEwLjUgTCA4Ni40MzcgMTEuNSBMIDg2LjEzNyAxMS4xIEMgODUuMzM3IDkuOSA4My41MzcgOS41IDgxLjczNyA5LjUgQyA3Ny42MzcgOS41IDc0LjEzNyAxMi42IDczLjQzNyAxNyBDIDczLjAzNyAxOS4yIDczLjUzNyAyMS4zIDc0LjgzNyAyMi43IEMgNzUuOTM3IDI0IDc3LjYzNyAyNC42IDc5LjUzNyAyNC42IEMgODIuODM3IDI0LjYgODQuNzM3IDIyLjUgODQuNzM3IDIyLjUgTCA4NC41MzcgMjMuNSBDIDg0LjQzNyAyMy45IDg0LjczNyAyNC4zIDg1LjEzNyAyNC4zIEwgODguNTM3IDI0LjMgQyA4OS4wMzcgMjQuMyA4OS41MzcgMjMuOSA4OS42MzcgMjMuNCBMIDkxLjYzNyAxMC42IEMgOTEuNjM3IDEwLjQgOTEuMzM3IDEwIDkwLjkzNyAxMCBaIE0gODUuNzM3IDE3LjIgQyA4NS4zMzcgMTkuMyA4My43MzcgMjAuOCA4MS41MzcgMjAuOCBDIDgwLjQzNyAyMC44IDc5LjYzNyAyMC41IDc5LjAzNyAxOS44IEMgNzguNDM3IDE5LjEgNzguMjM3IDE4LjIgNzguNDM3IDE3LjIgQyA3OC43MzcgMTUuMSA4MC41MzcgMTMuNiA4Mi42MzcgMTMuNiBDIDgzLjczNyAxMy42IDg0LjUzNyAxNCA4NS4xMzcgMTQuNiBDIDg1LjczNyAxNS4zIDg1LjkzNyAxNi4yIDg1LjczNyAxNy4yIFoiPjwvcGF0aD48cGF0aCBmaWxsPSIjMDA5Y2RlIiBkPSJNIDk1LjMzNyAzLjMgTCA5Mi4xMzcgMjMuNiBDIDkyLjAzNyAyNCA5Mi4zMzcgMjQuMyA5Mi43MzcgMjQuMyBMIDk1LjkzNyAyNC4zIEMgOTYuNDM3IDI0LjMgOTYuOTM3IDIzLjkgOTcuMDM3IDIzLjQgTCAxMDAuMjM3IDMuNSBDIDEwMC4zMzcgMy4xIDEwMC4wMzcgMi44IDk5LjYzNyAyLjggTCA5Ni4wMzcgMi44IEMgOTUuNjM3IDIuOCA5NS40MzcgMyA5NS4zMzcgMy4zIFoiPjwvcGF0aD48L3N2Zz4\"\n      alt=\"Paypal\"\n      role=\"presentation\"\n    />\n  );\n}\n\nexport default function PaypalMethod({\n  createOrderAPI,\n  setting: { paypalDisplayName }\n}: PaypalMethodProps) {\n  const {\n    checkoutSuccessUrl,\n    orderPlaced,\n    orderId,\n    checkoutData: { paymentMethod }\n  } = useCheckout();\n  const { registerPaymentComponent } = useCheckoutDispatch();\n\n  useEffect(() => {\n    const createOrder = async () => {\n      const response = await fetch(createOrderAPI, {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify({\n          order_id: orderId\n        })\n      });\n      const data = (await response.json()) as ApiResponse<{\n        approveUrl: string;\n      }>;\n      if (!data.error) {\n        const { approveUrl } = data.data;\n        // Redirect to PayPal for payment approval\n        window.location.href = approveUrl;\n      } else {\n        toast.error(data.error.message);\n        // Wait for 2 seconds and reload the checkout page\n        setTimeout(() => {\n          window.location.reload();\n        }, 2000);\n      }\n    };\n\n    if (orderPlaced && orderId && paymentMethod === 'paypal') {\n      // Call the API to create the order\n      createOrder();\n    }\n  }, [orderPlaced, checkoutSuccessUrl, orderId]);\n\n  useEffect(() => {\n    registerPaymentComponent('paypal', {\n      nameRenderer: () => (\n        <div className=\"flex items-center justify-between w-full\">\n          <span>{paypalDisplayName}</span>\n          <PaypalLogo />\n        </div>\n      ),\n      formRenderer: () => (\n        <div className=\"flex justify-center text-muted-foreground\">\n          <div className=\"w-2/3 text-center py-3\">\n            {_('You will be redirected to PayPal for payment processing.')}\n          </div>\n        </div>\n      ),\n      checkoutButtonRenderer: () => {\n        const { checkout } = useCheckoutDispatch();\n        const { loadingStates, orderPlaced } = useCheckout();\n        const handleClick = async (e: React.MouseEvent) => {\n          e.preventDefault();\n          await checkout();\n        };\n\n        const isDisabled = loadingStates.placingOrder || orderPlaced;\n\n        return (\n          <Button\n            variant={'default'}\n            size={'xl'}\n            type=\"button\"\n            onClick={handleClick}\n            disabled={isDisabled}\n            className=\"w-full text-white py-4 px-6 rounded-lg font-semibold text-lg shadow-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n            style={{\n              backgroundColor: isDisabled ? '#0070ba80' : '#0070ba'\n            }}\n            onMouseEnter={(e) => {\n              if (!isDisabled) {\n                e.currentTarget.style.backgroundColor = '#005ea6';\n              }\n            }}\n            onMouseLeave={(e) => {\n              if (!isDisabled) {\n                e.currentTarget.style.backgroundColor = '#0070ba';\n              }\n            }}\n          >\n            <span className=\"flex items-center justify-center space-x-2\">\n              {loadingStates.placingOrder ? (\n                <>\n                  <svg\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    x=\"0px\"\n                    y=\"0px\"\n                    width=\"24\"\n                    height=\"24\"\n                    viewBox=\"0 0 48 48\"\n                  >\n                    <path\n                      fill=\"#1565C0\"\n                      d=\"M18.7,13.767l0.005,0.002C18.809,13.326,19.187,13,19.66,13h13.472c0.017,0,0.034-0.007,0.051-0.006C32.896,8.215,28.887,6,25.35,6H11.878c-0.474,0-0.852,0.335-0.955,0.777l-0.005-0.002L5.029,33.813l0.013,0.001c-0.014,0.064-0.039,0.125-0.039,0.194c0,0.553,0.447,0.991,1,0.991h8.071L18.7,13.767z\"\n                    ></path>\n                    <path\n                      fill=\"#039BE5\"\n                      d=\"M33.183,12.994c0.053,0.876-0.005,1.829-0.229,2.882c-1.281,5.995-5.912,9.115-11.635,9.115c0,0-3.47,0-4.313,0c-0.521,0-0.767,0.306-0.88,0.54l-1.74,8.049l-0.305,1.429h-0.006l-1.263,5.796l0.013,0.001c-0.014,0.064-0.039,0.125-0.039,0.194c0,0.553,0.447,1,1,1h7.333l0.013-0.01c0.472-0.007,0.847-0.344,0.945-0.788l0.018-0.015l1.812-8.416c0,0,0.126-0.803,0.97-0.803s4.178,0,4.178,0c5.723,0,10.401-3.106,11.683-9.102C42.18,16.106,37.358,13.019,33.183,12.994z\"\n                    ></path>\n                    <path\n                      fill=\"#283593\"\n                      d=\"M19.66,13c-0.474,0-0.852,0.326-0.955,0.769L18.7,13.767l-2.575,11.765c0.113-0.234,0.359-0.54,0.88-0.54c0.844,0,4.235,0,4.235,0c5.723,0,10.432-3.12,11.713-9.115c0.225-1.053,0.282-2.006,0.229-2.882C33.166,12.993,33.148,13,33.132,13H19.66z\"\n                    ></path>\n                  </svg>\n                  <span>{_('Redirecting to PayPal...')}</span>\n                </>\n              ) : orderPlaced ? (\n                <>\n                  <svg\n                    className=\"w-5 h-5\"\n                    fill=\"currentColor\"\n                    viewBox=\"0 0 24 24\"\n                  >\n                    <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\" />\n                  </svg>\n                  <span>{_('Redirecting to PayPal...')}</span>\n                </>\n              ) : (\n                <>\n                  <svg\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    x=\"0px\"\n                    y=\"0px\"\n                    width=\"24\"\n                    height=\"24\"\n                    viewBox=\"0 0 48 48\"\n                  >\n                    <path\n                      fill=\"#1565C0\"\n                      d=\"M18.7,13.767l0.005,0.002C18.809,13.326,19.187,13,19.66,13h13.472c0.017,0,0.034-0.007,0.051-0.006C32.896,8.215,28.887,6,25.35,6H11.878c-0.474,0-0.852,0.335-0.955,0.777l-0.005-0.002L5.029,33.813l0.013,0.001c-0.014,0.064-0.039,0.125-0.039,0.194c0,0.553,0.447,0.991,1,0.991h8.071L18.7,13.767z\"\n                    ></path>\n                    <path\n                      fill=\"#039BE5\"\n                      d=\"M33.183,12.994c0.053,0.876-0.005,1.829-0.229,2.882c-1.281,5.995-5.912,9.115-11.635,9.115c0,0-3.47,0-4.313,0c-0.521,0-0.767,0.306-0.88,0.54l-1.74,8.049l-0.305,1.429h-0.006l-1.263,5.796l0.013,0.001c-0.014,0.064-0.039,0.125-0.039,0.194c0,0.553,0.447,1,1,1h7.333l0.013-0.01c0.472-0.007,0.847-0.344,0.945-0.788l0.018-0.015l1.812-8.416c0,0,0.126-0.803,0.97-0.803s4.178,0,4.178,0c5.723,0,10.401-3.106,11.683-9.102C42.18,16.106,37.358,13.019,33.183,12.994z\"\n                    ></path>\n                    <path\n                      fill=\"#283593\"\n                      d=\"M19.66,13c-0.474,0-0.852,0.326-0.955,0.769L18.7,13.767l-2.575,11.765c0.113-0.234,0.359-0.54,0.88-0.54c0.844,0,4.235,0,4.235,0c5.723,0,10.432-3.12,11.713-9.115c0.225-1.053,0.282-2.006,0.229-2.882C33.166,12.993,33.148,13,33.132,13H19.66z\"\n                    ></path>\n                  </svg>\n                  <span>{_('Pay with PayPal')}</span>\n                </>\n              )}\n            </span>\n          </Button>\n        );\n      }\n    });\n  }, [registerPaymentComponent, paypalDisplayName]);\n\n  return null;\n}\n\nexport const layout = {\n  areaId: 'checkoutForm',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    setting {\n      paypalDisplayName\n    }\n    createOrderAPI: url(routeId: \"paypalCreateOrder\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/pages/frontStore/paypalCancel/index.ts",
    "content": "import { select, update } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\n\nexport default async (request: EvershopRequest, response: EvershopResponse) => {\n  // When the user cancelled the payment from PayPal\n  // he/she will be redirected to the checkout page.\n  // We need to check if the cart is still valid.\n  // Get the paypal token from query string\n  const paypalToken = request.query.token;\n\n  const { order_id } = request.params;\n  if (paypalToken) {\n    // This token actually the paypal order id\n    const order = await select()\n      .from('order')\n      .where('integration_order_id', '=', paypalToken)\n      .and('uuid', '=', order_id)\n      .and('payment_method', '=', 'paypal')\n      .and('payment_status', '=', 'pending')\n      .load(pool);\n    if (order) {\n      // We re-activate the cart\n      await update('cart')\n        .given({ status: 1 })\n        .where('cart_id', '=', order.cart_id)\n        .execute(pool);\n    }\n  }\n  // Redirect to the checkout page\n  response.redirect(302, buildUrl('checkout'));\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/pages/frontStore/paypalCancel/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/paypal/cancelling/:order_id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/pages/frontStore/paypalReturn/Error.tsx",
    "content": "import { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport React from 'react';\n\nexport default function Error() {\n  return (\n    <div className=\"text-center\">\n      <h1>{_('Error')}</h1>\n      <p>\n        {_(\n          'We are sorry. There was an error processing your payment. Your card was not charged. Please try again.'\n        )}\n      </p>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/pages/frontStore/paypalReturn/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport axios from 'axios';\nimport { emit } from '../../../../../lib/event/emitter.js';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { getContextValue } from '../../../../graphql/services/contextHelper.js';\nimport { getSetting } from '../../../../setting/services/setting.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  // Get paypal token from query string\n  const paypalToken = request.query.token;\n  if (paypalToken) {\n    const { order_id } = request.params;\n    const query = select().from('order');\n    query\n      .where('uuid', '=', order_id)\n      .and('integration_order_id', '=', paypalToken)\n      .and('payment_method', '=', 'paypal')\n      .and('payment_status', '=', 'pending');\n\n    const order = await query.load(pool);\n    if (!order) {\n      response.redirect(302, buildUrl('homepage'));\n    } else {\n      try {\n        // Call API using Axios to capture/authorize the payment\n        const paymentIntent = await getSetting(\n          'paypalPaymentIntent',\n          'CAPTURE'\n        );\n        const responseData = await axios.post(\n          `${getContextValue(request, 'homeUrl')}${buildUrl(\n            paymentIntent === 'CAPTURE'\n              ? 'paypalCapturePayment'\n              : 'paypalAuthorizePayment'\n          )}`,\n          {\n            order_id\n          },\n          {\n            headers: {\n              'Content-Type': 'application/json',\n              // Include all cookies from the current request\n              Cookie: request.headers.cookie\n            }\n          }\n        );\n        if (responseData.data.error) {\n          throw new Error(responseData.data.error.message);\n        }\n        // Emit event to add order placed event\n        await emit('order_placed', { ...order });\n        // Redirect to order success page\n\n        response.redirect(302, `${buildUrl('checkoutSuccess')}/${order_id}`);\n      } catch (e) {\n        next();\n      }\n    }\n  } else {\n    // Redirect to homepage if no token\n    response.redirect(302, buildUrl('homepage'));\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/pages/frontStore/paypalReturn/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/paypal/proccessing/:order_id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/services/getApiBaseUrl.js",
    "content": "import { getSetting } from '../../setting/services/setting.js';\n\nexport async function getApiBaseUrl() {\n  const url = await getSetting(\n    'paypalEnvironment',\n    'https://api-m.sandbox.paypal.com'\n  );\n  return url;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/services/requester.js",
    "content": "import axios from 'axios';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { getSetting } from '../../setting/services/setting.js';\nimport { getApiBaseUrl } from './getApiBaseUrl.js';\n\nexport async function createAxiosInstance(request) {\n  const axiosInstance = axios.create({\n    baseURL: await getApiBaseUrl(),\n    headers: {\n      'Content-Type': 'application/json'\n    }\n  });\n\n  axiosInstance.interceptors.request.use(async (config) => {\n    const tokenObj = request.app.locals.paypalAccessToken; // {access_token: , expires_in: , created_at: }\n    const now = new Date().getTime();\n    if (!tokenObj || now - tokenObj.created_at > tokenObj.expires_in * 1000) {\n      const paypalAccessToken = await requestAccessToken();\n      request.app.locals.paypalAccessToken = {\n        access_token: paypalAccessToken.data.access_token,\n        expires_in: paypalAccessToken.data.expires_in,\n        created_at: new Date().getTime()\n      };\n\n      config.headers.Authorization = `Bearer ${paypalAccessToken.data.access_token}`;\n    } else {\n      config.headers.Authorization = `Bearer ${tokenObj.access_token}`;\n    }\n    return config;\n  });\n  return axiosInstance;\n}\n\nasync function requestAccessToken() {\n  const paypalConfig = getConfig('system.paypal', {});\n  let clientId;\n  let clientSecret;\n  if (paypalConfig.clientSecret) {\n    clientSecret = paypalConfig.clientSecret;\n  } else {\n    clientSecret = await getSetting('paypalClientSecret', '');\n  }\n\n  if (paypalConfig.clientId) {\n    clientId = paypalConfig.clientId;\n  } else {\n    clientId = await getSetting('paypalClientId', '');\n  }\n\n  const params = new URLSearchParams({ grant_type: 'client_credentials' });\n  // Get paypal access token using Axios\n  const paypalAccessToken = await axios.post(\n    `${await getApiBaseUrl()}/v1/oauth2/token`,\n    params,\n    {\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n        Authorization: `Basic ${Buffer.from(\n          `${clientId}:${clientSecret}`\n        ).toString('base64')}`\n      }\n    }\n  );\n  return paypalAccessToken;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/paypal/services/voidPaymentTransaction.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { error } from '../../../lib/log/logger.js';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { createAxiosInstance } from './requester.js';\n\nexport async function voidPaymentTransaction(orderID) {\n  try {\n    const transaction = await select()\n      .from('payment_transaction')\n      .where('payment_transaction_order_id', '=', orderID)\n      .load(pool);\n    if (!transaction) {\n      return;\n    }\n    const axiosInstance = await createAxiosInstance({\n      app: {\n        locals: {}\n      }\n    });\n\n    // Get the transaction details from Paypal\n    const responseData = await axiosInstance.get(\n      `/v2/payments/authorizations/${transaction.transaction_id}`\n    );\n    // If the transaction is already voided, return\n    if (responseData.data.status === 'VOIDED') {\n      return;\n    } else if (responseData.data.status === 'CREATED') {\n      // If the transaction is not yet captured, void it\n      await axiosInstance.post(\n        `/v2/payments/authorizations/${transaction.transaction_id}/void`\n      );\n    } else {\n      // Thrown an error if the transaction is already captured\n      throw new Error('Transaction is either pending or already captured');\n    }\n  } catch (err) {\n    error(err);\n    throw err;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/couponApply/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/couponApply/[validateCouponCode]applyCoupon.ts",
    "content": "import {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { getCartByUUID } from '../../../checkout/services/getCartByUUID.js';\nimport { saveCart } from '../../../checkout/services/saveCart.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { cart_id } = request.params;\n    const { coupon } = request.body;\n    const cart = await getCartByUUID(cart_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          message: 'Invalid cart',\n          status: INVALID_PAYLOAD\n        }\n      });\n      return;\n    }\n    await cart.setData('coupon', coupon);\n    await saveCart(cart);\n    response.status(OK);\n    response.$body = {\n      data: {\n        coupon\n      }\n    };\n    next();\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR).json({\n      error: {\n        message: e.message,\n        status: INTERNAL_SERVER_ERROR\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/couponApply/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"coupon\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"coupon\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"coupon\": \"Coupon is invalid\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/couponApply/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/carts/:cart_id/coupons\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/couponApply/validateCouponCode.js",
    "content": "import { INVALID_PAYLOAD } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  if (!request.body.coupon || !/^\\S*$/.test(request.body.coupon)) {\n    return response.status(INVALID_PAYLOAD).json({\n      error: {\n        message: 'Invalid coupon',\n        status: INVALID_PAYLOAD\n      }\n    });\n  } else {\n    return next();\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/couponRemove/removeCoupon.ts",
    "content": "import { translate } from '../../../../lib/locale/translate/translate.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\nimport { EvershopRequest } from '../../../../types/request.js';\nimport { EvershopResponse } from '../../../../types/response.js';\nimport { getCartByUUID } from '../../../checkout/services/getCartByUUID.js';\nimport { saveCart } from '../../../checkout/services/saveCart.js';\n\nexport default async (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next\n) => {\n  try {\n    const coupon = Array.isArray(request.params.coupon)\n      ? request.params.coupon[0]\n      : request.params.coupon;\n    const cart_id = Array.isArray(request.params.cart_id)\n      ? request.params.cart_id[0]\n      : request.params.cart_id;\n    const cart = await getCartByUUID(cart_id);\n    if (!cart) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          message: translate('Invalid cart'),\n          status: INVALID_PAYLOAD\n        }\n      });\n      return;\n    }\n    if (cart.getData('coupon') !== coupon) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          message: translate('Coupon does not match'),\n          status: INVALID_PAYLOAD\n        }\n      });\n      return;\n    }\n    await cart.setData('coupon', null);\n    await saveCart(cart);\n    response.status(OK);\n    response.$body = {\n      data: {\n        coupon\n      }\n    };\n    next();\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR).json({\n      error: {\n        message: e.message,\n        status: INTERNAL_SERVER_ERROR\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/couponRemove/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/carts/:cart_id/coupons/:coupon\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/createCoupon/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/createCoupon/createCoupon[finish].js",
    "content": "import createCoupon from '../../services/coupon/createCoupon.js';\n\nexport default async (request, response) => {\n  const coupon = await createCoupon(request.body, {\n    routeId: request.currentRoute.id\n  });\n\n  return coupon;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/createCoupon/finish[apiResponse].js",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const coupon = await getDelegate('createCoupon', request);\n  response.status(OK);\n  response.json({\n    data: {\n      ...coupon,\n      links: [\n        {\n          rel: 'couponGrid',\n          href: buildUrl('couponGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('couponEdit', { id: coupon.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/createCoupon/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/coupons\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/deleteCoupon/deleteCoupon.js",
    "content": "import { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport deleteCoupon from '../../services/coupon/deleteCoupon.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { id } = request.params;\n    const coupon = await deleteCoupon(id, {\n      routeId: request.currentRoute.id\n    });\n    response.status(OK);\n    response.json({\n      data: coupon\n    });\n  } catch (e) {\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/deleteCoupon/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/coupons/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/updateCoupon/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/updateCoupon/finish[apiResponse].js",
    "content": "import { getDelegate } from '../../../../lib/middleware/delegate.js';\nimport { buildUrl } from '../../../../lib/router/buildUrl.js';\nimport { OK } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const coupon = await getDelegate('updateCoupon', request);\n  response.status(OK);\n  response.json({\n    data: {\n      ...coupon,\n      links: [\n        {\n          rel: 'couponGrid',\n          href: buildUrl('couponGrid'),\n          action: 'GET',\n          types: ['text/xml']\n        },\n        {\n          rel: 'edit',\n          href: buildUrl('couponEdit', { id: coupon.uuid }),\n          action: 'GET',\n          types: ['text/xml']\n        }\n      ]\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/updateCoupon/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/coupons/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/api/updateCoupon/updateCoupon[finish].js",
    "content": "import updateCoupon from '../../services/coupon/updateCoupon.js';\n\nexport default async (request, response) => {\n  const coupon = await updateCoupon(request.params.id, request.body, {\n    routeId: request.currentRoute.id\n  });\n\n  return coupon;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/bootstrap.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../lib/postgres/connection.js';\nimport { defaultPaginationFilters } from '../../lib/util/defaultPaginationFilters.js';\nimport { addProcessor } from '../../lib/util/registry.js';\nimport { registerCartItemPromotionFields } from './services/registerCartItemPromotionFields.js';\nimport { registerCartPromotionFields } from './services/registerCartPromotionFields.js';\nimport { registerDefaultCalculators } from './services/registerDefaultCalculators.js';\nimport { registerDefaultCouponCollectionFilters } from './services/registerDefaultCouponCollectionFilters.js';\nimport { registerDefaultValidators } from './services/registerDefaultValidators.js';\n\nexport default () => {\n  addProcessor(\n    'couponLoaderFunction',\n    () => async (couponCode) => {\n      const coupon = await select()\n        .from('coupon')\n        .where('coupon', '=', couponCode)\n        .load(pool);\n      return coupon;\n    },\n    0\n  );\n  addProcessor('cartFields', registerCartPromotionFields, 0);\n  addProcessor('cartItemFields', registerCartItemPromotionFields, 11);\n  addProcessor('couponValidatorFunctions', registerDefaultValidators);\n  addProcessor('discountCalculatorFunctions', registerDefaultCalculators);\n\n  // Reigtering the default filters for attribute collection\n  addProcessor(\n    'couponCollectionFilters',\n    registerDefaultCouponCollectionFilters,\n    1\n  );\n  addProcessor(\n    'couponCollectionFilters',\n    (filters) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.admin.graphql",
    "content": "\"\"\"\nRepresents a coupon\n\"\"\"\ntype Coupon {\n  couponId: Int\n  uuid: String!\n  status: Int!\n  description: String!\n  discountAmount: Float!\n  freeShipping: Int!\n  discountType: String!\n  coupon: String!\n  usedTime: Int\n  targetProducts: TargetProducts\n  condition: OrderCondition\n  userCondition: UserCondition\n  buyxGety: [ByXGetY]\n  maxUsesTimePerCoupon: Int\n  maxUsesTimePerCustomer: Int\n  startDate: DateTime\n  endDate: DateTime\n  editUrl: String!\n  updateApi: String!\n  deleteApi: String!\n}\n\n\"\"\"\nRepresents a signle product used in the condition of a coupon.\n\"\"\"\ntype MatchProductFilter {\n  key: String!\n  operator: String!\n  value: JSON\n  qty: String\n}\n\n\"\"\"\nRepresents the target products of a coupon.\n\"\"\"\ntype TargetProducts {\n  maxQty: String\n  products: [MatchProductFilter]\n}\n\n\"\"\"\nRepresents the condition of a coupon.\n\"\"\"\ntype OrderCondition {\n  orderTotal: String\n  orderQty: String\n  requiredProducts: [MatchProductFilter]\n}\n\n\"\"\"\nRepresents the buy x get y condition of a coupon.\n\"\"\"\ntype ByXGetY {\n  sku: String!\n  buyQty: String\n  getQty: String\n  maxY: String\n  discount: String\n}\n\n\"\"\"\nRepresents the user condition of a coupon.\n\"\"\"\ntype UserCondition {\n  groups: [String]\n  emails: [String]\n  purchased: String\n}\n\n\"\"\"\nReturns a collection of coupons\n\"\"\"\ntype CouponCollection {\n  items: [Coupon]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\nextend type Query {\n  coupon(id: Int): Coupon\n  coupons(filters: [FilterInput]): CouponCollection\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.admin.resolvers.js",
    "content": "import { GraphQLJSON } from 'graphql-type-json';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { CouponCollection } from '../../../services/CouponCollection.js';\nimport { getCouponsBaseQuery } from '../../../services/getCouponsBaseQuery.js';\n\nexport default {\n  JSON: GraphQLJSON,\n  Query: {\n    coupon: async (root, { id }, { pool }) => {\n      const query = getCouponsBaseQuery();\n      query.where('coupon_id', '=', id);\n      const coupon = await query.load(pool);\n      return coupon ? camelCase(coupon) : null;\n    },\n    coupons: async (_, { filters = [] }, { user }) => {\n      // This field is for admin only\n      if (!user) {\n        return [];\n      }\n      const query = getCouponsBaseQuery();\n      const root = new CouponCollection(query);\n      await root.init(filters);\n      return root;\n    }\n  },\n  Coupon: {\n    targetProducts: ({ targetProducts }) => {\n      if (!targetProducts) {\n        return null;\n      } else {\n        return camelCase(targetProducts);\n      }\n    },\n    condition: ({ condition }) => {\n      if (!condition) {\n        return null;\n      } else {\n        return camelCase(condition);\n      }\n    },\n    userCondition: ({ userCondition }) => {\n      if (!userCondition) {\n        return null;\n      } else {\n        return {\n          ...camelCase(userCondition),\n          emails: Array.isArray(userCondition.emails) // For backward compatibility\n            ? userCondition.emails\n            : userCondition.emails.split(',').map((email) => email.trim())\n        };\n      }\n    },\n    buyxGety: ({ buyxGety }) => {\n      if (!buyxGety) {\n        return [];\n      } else {\n        return buyxGety.map((item) => camelCase(item));\n      }\n    },\n    editUrl: ({ uuid }) => buildUrl('couponEdit', { id: uuid }),\n    updateApi: (coupon) => buildUrl('updateCoupon', { id: coupon.uuid }),\n    deleteApi: (coupon) => buildUrl('deleteCoupon', { id: coupon.uuid })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.graphql",
    "content": "scalar JSON\n\nextend type Cart {\n  applyCouponApi: String!\n  removeCouponApi: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.resolvers.js",
    "content": "import { buildUrl } from '../../../../../lib/router/buildUrl.js';\n\nexport default {\n  Cart: {\n    applyCouponApi: (cart) => buildUrl('couponApply', { cart_id: cart.uuid }),\n    removeCouponApi: (cart) => {\n      if (cart.coupon) {\n        return buildUrl('couponRemove', {\n          cart_id: cart.uuid,\n          coupon: cart.coupon\n        });\n      }\n      return null; // Return null if no coupon is applied, or handle as needed\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/migration/Version-1.0.0.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE \"coupon\" (\n  \"coupon_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"status\" boolean NOT NULL DEFAULT TRUE,\n  \"description\" varchar NOT NULL,\n  \"discount_amount\" decimal(12,4) NOT NULL,\n  \"free_shipping\" boolean NOT NULL DEFAULT FALSE,\n  \"discount_type\" varchar NOT NULL DEFAULT '1',\n  \"coupon\" varchar NOT NULL,\n  \"used_time\" INT NOT NULL DEFAULT 0,\n  \"target_products\" text DEFAULT NULL,\n  \"condition\" text NULL DEFAULT NULL,\n  \"user_condition\" text DEFAULT NULL,\n  \"buyx_gety\" text DEFAULT NULL,\n  \"max_uses_time_per_coupon\" INT DEFAULT NULL,\n  \"max_uses_time_per_customer\" INT DEFAULT NULL,\n  \"start_date\" TIMESTAMP WITH TIME ZONE DEFAULT NULL,\n  \"end_date\" TIMESTAMP WITH TIME ZONE DEFAULT NULL,\n  \"created_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  \"updated_at\" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n  CONSTRAINT \"POSITIVE_DISCOUNT_AMOUNT\" CHECK(discount_amount >= 0),\n  CONSTRAINT \"VALID_PERCENTAGE_DISCOUNT\" CHECK(discount_amount <= 100 OR discount_type != 'percentage'),\n  CONSTRAINT \"COUPON_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"COUPON_UNIQUE\" UNIQUE (\"coupon\")\n)`\n  );\n\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION set_coupon_used_time()\n        RETURNS TRIGGER \n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        UPDATE \"coupon\" SET used_time = used_time + 1 WHERE coupon = NEW.coupon;\n        RETURN NEW;\n      END;\n      $$;`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"TRIGGER_UPDATE_COUPON_USED_TIME_AFTER_CREATE_ORDER\" AFTER INSERT ON \"order\"\n     FOR EACH ROW \n     EXECUTE PROCEDURE set_coupon_used_time();\n    `\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/migration/Version-1.0.1.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `ALTER TABLE \"coupon\" ALTER COLUMN \"target_products\" TYPE jsonb USING \"target_products\"::jsonb`\n  );\n\n  await execute(\n    connection,\n    `ALTER TABLE \"coupon\" ALTER COLUMN \"condition\" TYPE jsonb USING \"condition\"::jsonb`\n  );\n\n  await execute(\n    connection,\n    `ALTER TABLE \"coupon\" ALTER COLUMN \"user_condition\" TYPE jsonb USING \"user_condition\"::jsonb`\n  );\n\n  await execute(\n    connection,\n    `ALTER TABLE \"coupon\" ALTER COLUMN \"buyx_gety\" TYPE jsonb USING \"buyx_gety\"::jsonb`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/all/CouponMenuGroup.tsx",
    "content": "import { NavigationItemGroup } from '@components/admin/NavigationItemGroup.js';\nimport { GiftIcon } from 'lucide-react';\nimport React from 'react';\n\ninterface CouponMenuGroupProps {\n  couponGrid: string;\n}\n\nexport default function CatalogMenuGroup({ couponGrid }: CouponMenuGroupProps) {\n  return (\n    <NavigationItemGroup\n      id=\"couponMenuGroup\"\n      name=\"Promotion\"\n      items={[\n        {\n          Icon: GiftIcon,\n          url: couponGrid,\n          title: 'Coupons'\n        }\n      ]}\n    />\n  );\n}\n\nexport const layout = {\n  areaId: 'adminMenu',\n  sortOrder: 50\n};\n\nexport const query = `\n  query Query {\n    couponGrid: url(routeId:\"couponGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/all/NewCouponQuickLink.tsx",
    "content": "import { NavigationItem } from '@components/admin/NavigationItem.js';\nimport { GiftIcon } from 'lucide-react';\nimport React from 'react';\n\ninterface NewCouponQuickLinkProps {\n  couponNew: string;\n}\n\nexport default function NewProductQuickLink({\n  couponNew\n}: NewCouponQuickLinkProps) {\n  return <NavigationItem Icon={GiftIcon} title=\"New Coupon\" url={couponNew} />;\n}\n\nexport const layout = {\n  areaId: 'quickLinks',\n  sortOrder: 30\n};\n\nexport const query = `\n  query Query {\n    couponNew: url(routeId:\"couponNew\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit/CouponEditForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface CouponEditFormProps {\n  action: string;\n  gridUrl: string;\n}\nexport default function CouponEditForm({\n  action,\n  gridUrl\n}: CouponEditFormProps) {\n  return (\n    <Form method=\"PATCH\" action={action} submitBtn={false} id=\"couponEditForm\">\n      <div className=\"grid grid-cols-1 gap-5\">\n        <Card>\n          <CardHeader>\n            <CardTitle>General Information</CardTitle>\n            <CardDescription>\n              The general information about the coupon.\n            </CardDescription>\n          </CardHeader>\n          <CardContent>\n            <Area id=\"couponEditGeneral\" noOuter />\n          </CardContent>\n        </Card>\n        <Card>\n          <CardHeader>\n            <CardTitle>Discount Type</CardTitle>\n            <CardDescription>\n              The type of discount applied by the coupon.\n            </CardDescription>\n          </CardHeader>\n          <CardContent>\n            <Area id=\"couponEditDiscountType\" noOuter />\n          </CardContent>\n        </Card>\n        <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n          <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n            <Card>\n              <CardHeader>\n                <CardTitle>Order conditions</CardTitle>\n                <CardDescription>\n                  The conditions related to the order for the coupon to be\n                  applied.\n                </CardDescription>\n              </CardHeader>\n              <CardContent>\n                <Area id=\"couponEditLeft\" noOuter className=\"col-8\" />\n              </CardContent>\n            </Card>\n          </div>\n          <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n            <Card>\n              <CardHeader>\n                <CardTitle>Customer conditions</CardTitle>\n                <CardDescription>\n                  The conditions related to the customer for the coupon to be\n                  applied.\n                </CardDescription>\n              </CardHeader>\n              <CardContent>\n                <Area id=\"couponEditRight\" className=\"col-4\" noOuter />\n              </CardContent>\n            </Card>\n          </div>\n        </div>\n      </div>\n      <FormButtons cancelUrl={gridUrl} formId=\"couponEditForm\" />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"updateCoupon\", params: [{key: \"id\", value: getContextValue(\"couponUuid\")}]),\n    gridUrl: url(routeId: \"couponGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit/index.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { EvershopResponse } from '../../../../../types/response.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default async (request, response: EvershopResponse, next) => {\n  try {\n    const query = select();\n    query.from('coupon');\n    query.andWhere('coupon.uuid', '=', request.params.id);\n    const coupon = await query.load(pool);\n\n    if (coupon === null) {\n      response.redirect(302, buildUrl('couponGrid'));\n    } else {\n      setContextValue(request, 'couponId', parseInt(coupon.coupon_id, 10));\n      setContextValue(request, 'couponUuid', coupon.uuid);\n      setPageMetaInfo(request, {\n        title: coupon.coupon,\n        description: coupon.coupon\n      });\n      next();\n    }\n  } catch (e) {\n    next(e);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/coupon/edit/:id\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/CustomerCondition.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { ReactSelectCreatableField } from '@components/common/form/ReactSelectCreatableField.js';\nimport { ReactSelectField } from '@components/common/form/ReactSelectField.js';\nimport React from 'react';\nimport { Coupon } from './General.js';\n\nconst customStyles = {\n  container: (provided) => ({\n    ...provided,\n    zIndex: 1000\n  })\n};\n\ninterface CustomerConditionProps {\n  coupon?: Coupon;\n  groups: {\n    items: {\n      value: number;\n      name: string;\n    }[];\n  };\n}\n\nexport default function CustomerCondition({\n  coupon,\n  groups: { items: customerGroups }\n}: CustomerConditionProps) {\n  const condition = coupon?.userCondition;\n  const selectedGroups = condition?.groups || [];\n\n  return (\n    <Area\n      id=\"couponCustomerCondition\"\n      className=\"space-y-3\"\n      coreComponents={[\n        {\n          component: {\n            default: () => (\n              <ReactSelectField\n                label=\"Customer groups\"\n                name=\"user_condition.groups\"\n                options={customerGroups.map((group) => ({\n                  value: group.value.toString(),\n                  label: group.name\n                }))}\n                hideSelectedOptions\n                isMulti={true}\n                defaultValue={selectedGroups}\n                styles={customStyles}\n              />\n            )\n          },\n          props: {},\n          sortOrder: 10,\n          id: 'couponCustomerConditionGroup'\n        },\n        {\n          component: {\n            default: (\n              <ReactSelectCreatableField\n                name=\"user_condition.emails\"\n                label=\"Customer emails\"\n                placeholder=\"Enter customer emails\"\n                isMulti={true}\n                options={(condition?.emails || []).map((email) => ({\n                  value: email as string,\n                  label: email as string\n                }))}\n                defaultValue={condition?.emails || []}\n              />\n            )\n          },\n          sortOrder: 20,\n          id: 'couponCustomerConditionEmail'\n        },\n        {\n          component: {\n            default: (\n              <NumberField\n                label=\"Customer's purchase\"\n                placeholder=\"Enter purchased amount\"\n                defaultValue={\n                  parseInt(condition?.purchased as unknown as string) || 0\n                }\n                name=\"user_condition.purchased\"\n                min={0}\n                helperText=\"Minimum purchased amount. This only applies to registered customers.\"\n              />\n            )\n          },\n          sortOrder: 30,\n          id: 'couponCustomerConditionPurchased'\n        }\n      ]}\n    />\n  );\n}\n\nexport const layout = {\n  areaId: 'couponEditRight',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    coupon(id: getContextValue('couponId', null)) {\n      userCondition {\n        groups\n        emails\n        purchased\n      }\n    }\n    groups: customerGroups {\n      items {\n        value: customerGroupId\n        name: groupName\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/DiscountType.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport React from 'react';\nimport { get } from '../../../../../lib/util/get.js';\nimport { BuyXGetY } from './components/BuyXGetY.js';\nimport { TargetProducts } from './components/TargetProducts.js';\nimport { Coupon } from './General.js';\n\ninterface DiscountTypeProps {\n  coupon: Coupon;\n}\nexport default function DiscountType({ coupon }: DiscountTypeProps) {\n  const targetProducts = get(coupon, 'targetProducts', {});\n  const buyxGety = get(coupon, 'buyxGety', []);\n\n  return (\n    <div>\n      <div>\n        <Area\n          id=\"couponFormDiscountType\"\n          coreComponents={[\n            {\n              component: {\n                default: (\n                  <RadioGroupField\n                    required\n                    validation={{ required: 'Discount type is required' }}\n                    options={[\n                      {\n                        value: 'fixed_discount_to_entire_order',\n                        label: 'Fixed discount to entire order'\n                      },\n                      {\n                        value: 'percentage_discount_to_entire_order',\n                        label: 'Percentage discount to entire order'\n                      },\n                      {\n                        value: 'fixed_discount_to_specific_products',\n                        label: 'Fixed discount to specific products'\n                      },\n                      {\n                        value: 'percentage_discount_to_specific_products',\n                        label: 'Percentage discount to specific products'\n                      },\n                      { value: 'buy_x_get_y', label: 'Buy X get Y' }\n                    ]}\n                    defaultValue={get(coupon, 'discountType', '')}\n                    name=\"discount_type\"\n                  />\n                )\n              },\n              sortOrder: 10\n            }\n          ]}\n        />\n      </div>\n      <div className=\"mt-2\">\n        <TargetProducts\n          products={get(targetProducts, 'products', [])}\n          maxQty={get<number>(targetProducts, 'maxQty', 0)}\n        />\n        <BuyXGetY\n          requireProducts={buyxGety}\n          discountType={get(coupon, 'discountType', '')}\n        />\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'couponEditDiscountType',\n  sortOrder: 30\n};\n\nexport const query = `\n  query Query {\n    coupon(id: getContextValue('couponId', null)) {\n      discountType\n      targetProducts {\n        maxQty\n        products {\n          key\n          operator\n          value\n          qty\n        }\n      }\n      buyxGety {\n        sku\n        buyQty\n        getQty\n        maxY\n        discount\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/General.scss",
    "content": "body.couponEdit,\nbody.couponNew {\n  .main-content-inner {\n    max-width: 62.5rem;\n    margin: 1.875rem auto;\n  }\n  .page-heading {\n    max-width: 62.5rem;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/General.tsx",
    "content": "import Area from '@components/common/Area.js';\nimport { CheckboxField } from '@components/common/form/CheckboxField.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport { TextareaField } from '@components/common/form/TextareaField.js';\nimport React from 'react';\nimport { get } from '../../../../../lib/util/get.js';\nimport { Setting } from './components/Setting.js';\nimport './General.scss';\n\nexport interface Coupon {\n  coupon: string;\n  status: number;\n  description: string;\n  discountAmount: number;\n  freeShipping: number;\n  startDate: { text: string; value: string };\n  endDate: { text: string; value: string };\n  targetProducts?: {\n    products: { productId: number; productName: string }[];\n    maxQty: number;\n  };\n  buyxGety?: {\n    items: {\n      productId: number;\n      productName: string;\n      qty: number;\n      discount: number;\n    }[];\n  };\n  discountType?: string;\n  condition?: {\n    orderTotal?: number;\n    orderQty?: number;\n    requiredProducts?: {\n      key: string;\n      operator: string;\n      value: string;\n      qty: number;\n    }[];\n  };\n  userCondition?: {\n    groups?: number[] | string[];\n    emails?: string[];\n    purchased: number;\n  };\n}\n\nexport default function General({ coupon }: { coupon?: Coupon }) {\n  return (\n    <Area\n      id=\"couponFormGeneral\"\n      className=\"space-y-3\"\n      coreComponents={[\n        {\n          component: {\n            default: (\n              <InputField\n                name=\"coupon\"\n                label=\"Coupon Code\"\n                defaultValue={coupon?.coupon || ''}\n                placeholder=\"Enter coupon code\"\n                required\n                validation={{\n                  required: 'Coupon code is required',\n                  pattern: {\n                    value: /^[a-zA-Z0-9_-]+$/,\n                    message:\n                      'Coupon code can only contain letters, numbers, underscores, and hyphens'\n                  }\n                }}\n              />\n            )\n          },\n          sortOrder: 10\n        },\n        {\n          component: {\n            default: (\n              <TextareaField\n                name=\"description\"\n                label=\"Description\"\n                defaultValue={coupon?.description || ''}\n                placeholder=\"Enter description\"\n                required\n                validation={{\n                  required: 'Description is required'\n                }}\n              />\n            )\n          },\n          sortOrder: 20\n        },\n        {\n          component: {\n            default: (\n              <RadioGroupField\n                name=\"status\"\n                label=\"Status\"\n                options={[\n                  { label: 'Enabled', value: 1 },\n                  { label: 'Disabled', value: 0 }\n                ]}\n                defaultValue={coupon?.status === 0 ? 0 : 1}\n                required\n                validation={{\n                  required: 'Status is required',\n                  valueAsNumber: true\n                }}\n              />\n            )\n          },\n          sortOrder: 30\n        },\n        {\n          component: { default: Setting },\n          props: {\n            startDate: get(coupon, 'startDate.text', ''),\n            endDate: get(coupon, 'endDate.text', ''),\n            discountAmount: get(coupon, 'discountAmount', '')\n          },\n          sortOrder: 40\n        },\n        {\n          component: {\n            default: (\n              <CheckboxField\n                name=\"free_shipping\"\n                defaultValue={parseInt(get(coupon, 'freeShipping'), 10) === 1}\n                label=\"Free shipping?\"\n              />\n            )\n          },\n          sortOrder: 50\n        }\n      ]}\n    />\n  );\n}\n\nexport const layout = {\n  areaId: 'couponEditGeneral',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    coupon(id: getContextValue('couponId', null)) {\n      coupon\n      status\n      description\n      discountAmount\n      freeShipping\n      startDate {\n        text\n      }\n      endDate {\n        text\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/OrderCondition.tsx",
    "content": "import { NumberField } from '@components/common/form/NumberField.js';\nimport React from 'react';\nimport {\n  RequiredProduct,\n  RequiredProducts\n} from './components/RequireProducts.js';\n\ninterface OrderConditionProps {\n  coupon?: {\n    condition?: {\n      orderTotal?: number;\n      orderQty?: number;\n      requiredProducts?: RequiredProduct[];\n    };\n  };\n}\nexport default function OrderCondition({ coupon = {} }: OrderConditionProps) {\n  const condition = coupon?.condition || {};\n\n  return (\n    <div className=\"space-y-2\">\n      <NumberField\n        name=\"condition.order_total\"\n        label=\"Minimum purchase amount\"\n        placeholder=\"Minimum purchase amount\"\n        defaultValue={condition.orderTotal || 0}\n        helperText=\"The minimum total amount required for the order to qualify for this coupon.\"\n      />\n\n      <NumberField\n        name=\"condition.order_qty\"\n        label=\"Minimum purchase qty\"\n        placeholder=\"Minimum purchase quantity\"\n        defaultValue={condition.orderQty || 0}\n        helperText=\"The minimum quantity of items required in the order to qualify for this coupon.\"\n        allowDecimals={false}\n        min={0}\n      />\n      <RequiredProducts requiredProducts={condition.requiredProducts || []} />\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'couponEditLeft',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    coupon(id: getContextValue('couponId', null)) {\n      condition {\n        orderTotal\n        orderQty\n        requiredProducts {\n          key\n          operator\n          value\n          qty\n        }\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/PageHeading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport interface CouponEditPageHeadingProps {\n  backUrl: string;\n  coupon?: { coupon: string };\n}\n\nexport default function CouponEditPageHeading({\n  backUrl,\n  coupon\n}: CouponEditPageHeadingProps) {\n  return (\n    <PageHeading\n      backUrl={backUrl}\n      heading={coupon ? `Editing ${coupon.coupon}` : 'Create a new coupon'}\n    />\n  );\n}\n\nCouponEditPageHeading.defaultProps = {\n  coupon: null\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    coupon(id: getContextValue(\"couponId\", null)) {\n      coupon\n    }\n    backUrl: url(routeId: \"couponGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/AttributeGroupConditionValueSelector.tsx",
    "content": "import { AttributeGroupSelector } from '@components/admin/AttributeGroupSelector.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport React from 'react';\n\nexport const AttributeGroupConditionValueSelector: React.FC<{\n  selectedValues: Array<number> | number;\n  updateCondition: (values: number | Array<number>) => void;\n  isMulti: boolean;\n}> = ({ selectedValues, updateCondition, isMulti }) => {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const skus = Array.isArray(selectedValues) ? selectedValues : [];\n  const selectedIds = React.useRef(skus || []);\n\n  const onSelect = (id) => {\n    if (!isMulti) {\n      selectedIds.current = [id];\n      setDialogOpen(false);\n    } else {\n      const prev = selectedIds.current;\n      if (!prev.includes(id)) {\n        selectedIds.current = [id, ...prev];\n      }\n    }\n  };\n\n  const onUnSelect = async (id) => {\n    const prev = selectedIds.current;\n    selectedIds.current = prev.filter((s) => s !== id);\n  };\n\n  return (\n    <Dialog\n      open={dialogOpen}\n      onOpenChange={(open) => setDialogOpen(open)}\n      onOpenChangeComplete={(open) => {\n        if (!open) {\n          updateCondition(selectedIds.current);\n        }\n      }}\n    >\n      <DialogTrigger>\n        <Button variant={'link'}>\n          {selectedIds.current.map((id, index) => (\n            <span key={id}>\n              {index === 0 && (\n                <span className=\"italic\">&lsquo;{id}&rsquo;</span>\n              )}\n              {index === 1 && (\n                <span> and {selectedIds.current.length - 1} more</span>\n              )}\n            </span>\n          ))}\n          {selectedIds.current.length === 0 && (\n            <span>Choose Attribute Groups</span>\n          )}\n        </Button>\n      </DialogTrigger>\n      <DialogContent className={'max-w-[60vw]'}>\n        <DialogHeader>\n          <DialogTitle>Choose Attribute Groups</DialogTitle>\n        </DialogHeader>\n        <AttributeGroupSelector\n          onSelect={onSelect}\n          onUnSelect={onUnSelect}\n          selectedAttributeGroups={selectedIds.current.map((id) => ({\n            attributeGroupId: id,\n            uuid: undefined\n          }))}\n        />\n      </DialogContent>\n    </Dialog>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/BuyXGetY.tsx",
    "content": "import { ProductSelector } from '@components/admin/ProductSelector.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTrigger,\n  DialogTitle\n} from '@components/common/ui/Dialog.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport React, { useEffect } from 'react';\nimport { useFieldArray, useFormContext } from 'react-hook-form';\n\ninterface Product {\n  sku: string;\n  buyQty: string | number;\n  getQty: string | number;\n  maxY: string | number;\n  discount: string | number;\n}\nconst SkuSelector: React.FC<{\n  product: Product;\n  updateProduct: (product: Product) => void;\n}> = ({ product, updateProduct }) => {\n  const onSelect = (sku) => {\n    updateProduct({\n      ...product,\n      sku\n    });\n  };\n\n  return (\n    <Dialog>\n      <DialogTrigger>\n        <Button variant={'link'}>\n          {product.sku ? (\n            <span className=\"italic\">&lsquo;{product.sku}&rsquo;</span>\n          ) : (\n            <span>Choose SKU</span>\n          )}\n        </Button>\n      </DialogTrigger>\n      <DialogContent className={'max-w-[80vw]'}>\n        <DialogHeader>\n          <DialogTitle>Choose Product SKU</DialogTitle>\n        </DialogHeader>\n        <ProductSelector\n          selectedProducts={[product].map((p) => ({\n            sku: p.sku,\n            uuid: undefined,\n            productId: undefined\n          }))}\n          onSelect={onSelect}\n          onUnSelect={() => {}}\n        />\n      </DialogContent>\n    </Dialog>\n  );\n};\n\ninterface Field {\n  sku: string;\n  buyQty: number;\n  getQty: number;\n  maxY: number;\n  discount: number;\n}\n\ninterface BuyXGetY {\n  buyx_gety: Field[];\n}\n\nconst BuyXGetYList: React.FC<{\n  requireProducts: Array<Product>;\n}> = ({ requireProducts }) => {\n  const { unregister } = useFormContext();\n  const { fields, append, remove, update, replace } = useFieldArray<BuyXGetY>({\n    name: 'buyx_gety'\n  });\n\n  useEffect(() => {\n    replace(\n      requireProducts.map(\n        (product) =>\n          ({\n            sku: product.sku,\n            buyQty:\n              typeof product.buyQty === 'string'\n                ? parseInt(product.buyQty) || 1\n                : product.buyQty,\n            getQty:\n              typeof product.getQty === 'string'\n                ? parseInt(product.getQty) || 1\n                : product.getQty,\n            maxY:\n              typeof product.maxY === 'string'\n                ? parseInt(product.maxY) || 2\n                : product.maxY,\n            discount:\n              typeof product.discount === 'string'\n                ? parseInt(product.discount) || 100\n                : product.discount\n          } as Field)\n      )\n    );\n    return () => {\n      unregister('buyx_gety'); // Cleanup: unregister field when component unmounts\n    };\n  }, []);\n\n  return (\n    <div>\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead>\n              <span>Sku</span>\n            </TableHead>\n            <TableHead>\n              <span>X</span>\n            </TableHead>\n            <TableHead>\n              <span>Y</span>\n            </TableHead>\n            <TableHead>\n              <span>Max of Y</span>\n            </TableHead>\n            <TableHead>\n              <span>Discount percent</span>\n            </TableHead>\n            <TableHead> </TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {fields.map((p, i) => (\n            <TableRow key={p.id}>\n              <TableCell>\n                <SkuSelector\n                  product={p}\n                  updateProduct={(product) => {\n                    update(i, {\n                      ...p,\n                      sku: product.sku\n                    });\n                  }}\n                />\n              </TableCell>\n              <TableCell>\n                <NumberField\n                  name={`buyx_gety.${i}.buy_qty`}\n                  defaultValue={p.buyQty}\n                  placeholder=\"Buy qty\"\n                  required\n                  validation={{\n                    required: 'Buy qty is required'\n                  }}\n                />\n              </TableCell>\n              <TableCell>\n                <NumberField\n                  name={`buyx_gety.${i}.get_qty`}\n                  defaultValue={p.getQty}\n                  placeholder=\"Get qty\"\n                  required\n                  validation={{\n                    required: 'Get qty is required'\n                  }}\n                />\n              </TableCell>\n              <TableCell>\n                <NumberField\n                  name={`buyx_gety.${i}.max_y`}\n                  defaultValue={p.maxY}\n                  placeholder=\"Max of Y\"\n                  required\n                  validation={{\n                    required: 'Max of Y is required'\n                  }}\n                />\n              </TableCell>\n              <TableCell>\n                <NumberField\n                  name={`buyx_gety.${i}.discount`}\n                  defaultValue={p.discount}\n                  placeholder=\"Discount percent\"\n                  required\n                  validation={{\n                    required: 'Discount percent is required'\n                  }}\n                  unit=\"%\"\n                />\n              </TableCell>\n              <TableCell>\n                <a\n                  className=\"text-destructive\"\n                  href=\"#\"\n                  onClick={(e) => {\n                    e.preventDefault();\n                    remove(i);\n                  }}\n                >\n                  <svg\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    width=\"1.5rem\"\n                    height=\"1.5rem\"\n                    fill=\"none\"\n                    viewBox=\"0 0 24 24\"\n                    stroke=\"currentColor\"\n                    strokeWidth={2}\n                  >\n                    <path\n                      strokeLinecap=\"round\"\n                      strokeLinejoin=\"round\"\n                      d=\"M18 12H6\"\n                    />\n                  </svg>\n                </a>\n              </TableCell>\n            </TableRow>\n          ))}\n        </TableBody>\n      </Table>\n      <div className=\"mt-2 flex justify-start\">\n        <div className=\"items-center flex\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"1.5rem\"\n            height=\"1.5rem\"\n            fill=\"none\"\n            viewBox=\"0 0 24 24\"\n            stroke=\"currentColor\"\n            strokeWidth={2}\n          >\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              d=\"M12 6v6m0 0v6m0-6h6m-6 0H6\"\n            />\n          </svg>\n        </div>\n        <div className=\"pl-2\">\n          <a\n            href=\"#\"\n            onClick={(e) => {\n              e.preventDefault();\n              append({\n                sku: '',\n                buyQty: 1,\n                getQty: 1,\n                maxY: 2,\n                discount: 100\n              } as Field);\n            }}\n          >\n            <span>Add product</span>\n          </a>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst BuyXGetY: React.FC<{\n  requireProducts: Array<Product>;\n  discountType: string;\n}> = ({ requireProducts }) => {\n  const { watch } = useFormContext();\n  const watchDiscountType = watch('discount_type');\n\n  if (watchDiscountType !== 'buy_x_get_y') {\n    return null;\n  }\n\n  return <BuyXGetYList requireProducts={requireProducts} />;\n};\n\nexport { BuyXGetY };\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/CategoryConditionValueSelector.tsx",
    "content": "import { CategorySelector } from '@components/admin/CategorySelector.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport React from 'react';\n\nexport const CategoryConditionValueSelector: React.FC<{\n  selectedValues: Array<number> | number;\n  updateCondition: (values: number | Array<number>) => void;\n  isMulti: boolean;\n}> = ({ selectedValues, updateCondition, isMulti }) => {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const selectedIds = React.useRef<number[]>(\n    Array.isArray(selectedValues) ? selectedValues.map(Number) : []\n  );\n\n  const onSelect = (id) => {\n    if (!isMulti) {\n      selectedIds.current = [id];\n      setDialogOpen(false);\n    } else {\n      const prev = selectedIds.current;\n      if (!prev.includes(id)) {\n        selectedIds.current = [id, ...prev];\n      }\n    }\n  };\n\n  const onUnSelect = (id) => {\n    const prev = selectedIds.current;\n    selectedIds.current = prev.filter((s) => s !== id);\n  };\n\n  return (\n    <Dialog\n      open={dialogOpen}\n      onOpenChange={(open) => setDialogOpen(open)}\n      onOpenChangeComplete={(open) => {\n        if (!open) {\n          updateCondition(selectedIds.current);\n        }\n      }}\n    >\n      <DialogTrigger>\n        <Button variant={'link'}>\n          {selectedIds.current.map((id, index) => (\n            <span key={id}>\n              {index === 0 && (\n                <span className=\"italic\">&lsquo;{id}&rsquo;</span>\n              )}\n              {index === 1 && (\n                <span> and {selectedIds.current.length - 1} more</span>\n              )}\n            </span>\n          ))}\n          {selectedIds.current.length === 0 && <span>Choose Categories</span>}\n        </Button>\n      </DialogTrigger>\n      <DialogContent className={'max-w-[60vw]'}>\n        <DialogHeader>\n          <DialogTitle>Choose Categories</DialogTitle>\n        </DialogHeader>\n        <CategorySelector\n          onSelect={onSelect}\n          onUnSelect={onUnSelect}\n          selectedCategories={selectedIds.current.map((id) => ({\n            categoryId: id,\n            uuid: undefined\n          }))}\n        />\n      </DialogContent>\n    </Dialog>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/CollectionConditionValueSelector.tsx",
    "content": "import { CollectionSelector } from '@components/admin/CollectionSelector.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport React from 'react';\n\nexport const CollectionConditionValueSelector: React.FC<{\n  selectedValues: Array<number> | number;\n  updateCondition: (values: number | Array<number>) => void;\n  isMulti: boolean;\n}> = ({ selectedValues, updateCondition, isMulti }) => {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const selectedIds = React.useRef<number[]>(\n    Array.isArray(selectedValues) ? selectedValues.map(Number) : []\n  );\n\n  const onSelect = async (id) => {\n    if (!isMulti) {\n      selectedIds.current = [id];\n      setDialogOpen(false);\n      return Promise.resolve();\n    }\n\n    const prev = selectedIds.current;\n    if (!prev.includes(id)) {\n      selectedIds.current = [id, ...prev];\n    }\n    return Promise.resolve();\n  };\n\n  const onUnSelect = async (id) => {\n    const prev = selectedIds.current;\n    selectedIds.current = prev.filter((s) => s !== id);\n    return Promise.resolve();\n  };\n\n  return (\n    <Dialog\n      open={dialogOpen}\n      onOpenChange={(open) => setDialogOpen(open)}\n      onOpenChangeComplete={(open) => {\n        if (!open) {\n          updateCondition(selectedIds.current);\n        }\n      }}\n    >\n      <DialogTrigger>\n        <Button variant={'link'}>\n          {selectedIds.current.map((id, index) => (\n            <span key={id}>\n              {index === 0 && (\n                <span className=\"italic\">&lsquo;{id}&rsquo;</span>\n              )}\n              {index === 1 && (\n                <span> and {selectedIds.current.length - 1} more</span>\n              )}\n            </span>\n          ))}\n          {selectedIds.current.length === 0 && <span>Choose Collections</span>}\n        </Button>\n      </DialogTrigger>\n      <DialogContent>\n        <DialogHeader>\n          <DialogTitle>Choose Collections</DialogTitle>\n        </DialogHeader>\n        <CollectionSelector\n          onSelect={onSelect}\n          onUnSelect={onUnSelect}\n          selectedCollections={selectedIds.current.map((id) => ({\n            collectionId: id,\n            uuid: undefined\n          }))}\n        />\n      </DialogContent>\n    </Dialog>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/PriceConditionValueSelector.tsx",
    "content": "import { NumberField } from '@components/common/form/NumberField.js';\nimport React from 'react';\nimport { Operator } from './conditionCriterias.js';\n\nexport const PriceConditionValueSelector = ({\n  updateCondition,\n  condition\n}: {\n  updateCondition: (\n    values: string | number | Array<string> | Array<number>\n  ) => void;\n  condition: {\n    key: string;\n    operator: Operator;\n    value: number;\n  };\n}) => {\n  return (\n    <div>\n      <NumberField\n        name={`dummy__` + Math.random().toString(36).substring(2, 15)}\n        wrapperClassName=\"form-field mb-0\"\n        defaultValue={condition.value}\n        placeholder=\"Value\"\n        required\n        validation={{\n          required: 'Value is required'\n        }}\n        onChange={(value) => {\n          updateCondition(value || 0);\n        }}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/RequireProducts.tsx",
    "content": "import { InputField } from '@components/common/form/InputField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport React, { useEffect } from 'react';\nimport { useFieldArray, useFormContext } from 'react-hook-form';\nimport { options, operators, Operator } from './conditionCriterias.js';\nimport { ValueSelector } from './ValueSelector.js';\n\nexport interface RequiredProduct {\n  key: string;\n  operator: Operator;\n  value: string | number | Array<string> | Array<number>;\n  qty: string;\n  editable?: boolean;\n}\n\nexport interface RequiredProductsProps {\n  requiredProducts: Array<RequiredProduct>;\n}\n\ninterface RequiredProducts {\n  condition: {\n    required_products: Array<RequiredProduct>;\n  };\n}\n\nexport function RequiredProducts({\n  requiredProducts = []\n}: RequiredProductsProps) {\n  const { setValue, watch } = useFormContext();\n  const { fields, append, remove, replace } = useFieldArray<RequiredProducts>({\n    name: 'condition.required_products'\n  });\n\n  useEffect(() => {\n    replace(requiredProducts);\n  }, []);\n\n  const fieldsWatch = watch('condition.required_products');\n  return (\n    <div style={{ marginTop: '1rem', marginBottom: '1rem' }}>\n      <div>\n        <span>Order must contains product matched bellow conditions(All)</span>\n      </div>\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead>\n              <span>Key</span>\n            </TableHead>\n            <TableHead>\n              <span>Operator</span>\n            </TableHead>\n            <TableHead>\n              <span>Value</span>\n            </TableHead>\n            <TableHead>\n              <span>Minimum quantity</span>\n            </TableHead>\n            <TableHead> </TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {fields.map((p, i) => (\n            <TableRow key={p.id}>\n              <TableCell>\n                {p.editable ? (\n                  <SelectField\n                    name={`condition.required_products.${i}.key`}\n                    defaultValue={p.key}\n                    options={options.map((option) => ({\n                      value: option.key,\n                      label: option.label\n                    }))}\n                    wrapperClassName=\"form-field mb-0\"\n                  />\n                ) : (\n                  <>\n                    <InputField\n                      type=\"hidden\"\n                      name={`condition.required_products.${i}.key`}\n                      readOnly\n                      value={p.key}\n                      wrapperClassName=\"form-field mb-0\"\n                    />\n                    <InputField\n                      name={`condition.required_products.${i}.keylabel`}\n                      readOnly\n                      value={\n                        options.find((c) => c.key === p.key)?.label || 'Unknown'\n                      }\n                      wrapperClassName=\"form-field mb-0\"\n                    />\n                  </>\n                )}\n              </TableCell>\n              <TableCell>\n                {p.editable ? (\n                  <SelectField\n                    options={operators.map((operator) => ({\n                      value: operator.key,\n                      label: operator.label\n                    }))}\n                    name={`condition.required_products.${i}.operator`}\n                    defaultValue={p.operator}\n                    wrapperClassName=\"form-field mb-0\"\n                  />\n                ) : (\n                  <>\n                    <InputField\n                      type=\"hidden\"\n                      name={`condition.required_products.${i}.operator`}\n                      readOnly\n                      value={p.operator}\n                      wrapperClassName=\"form-field mb-0\"\n                    />\n                    <InputField\n                      readOnly\n                      name={`condition.required_products.${i}.operatorlabel`}\n                      value={\n                        operators.find(\n                          (c) => c.key === fieldsWatch[i]?.operator\n                        )?.label || 'Unknown'\n                      }\n                      wrapperClassName=\"form-field mb-0\"\n                    />\n                  </>\n                )}\n              </TableCell>\n              <TableCell>\n                {fieldsWatch[i].key === 'price' && (\n                  <NumberField\n                    name={`condition.required_products.${i}.value`}\n                    defaultValue={p.value as number}\n                    wrapperClassName=\"form-field mb-0\"\n                  />\n                )}\n                {fieldsWatch[i].key !== 'price' && (\n                  <>\n                    <InputField\n                      type=\"hidden\"\n                      name={`condition.required_products.${i}.value`}\n                      value={p.value as number}\n                      wrapperClassName=\"form-field mb-0\"\n                    />\n                    <ValueSelector\n                      condition={fieldsWatch[i]}\n                      updateCondition={(values) => {\n                        setValue(\n                          `condition.required_products.${i}.value`,\n                          values\n                        );\n                      }}\n                    />\n                  </>\n                )}\n              </TableCell>\n              <TableCell>\n                <div style={{ width: '80px' }}>\n                  <NumberField\n                    name={`condition.required_products.${i}.qty`}\n                    defaultValue={\n                      typeof p.qty === 'number'\n                        ? p.qty\n                        : parseInt(p.qty, 10) || 1\n                    }\n                    placeholder=\"Enter the quantity\"\n                    required\n                    validation={{\n                      required: 'Minimum quantity is required',\n                      min: {\n                        value: 1,\n                        message: ''\n                      }\n                    }}\n                    wrapperClassName=\"form-field mb-0\"\n                  />\n                </div>\n              </TableCell>\n              <TableCell>\n                <a\n                  href=\"#\"\n                  className=\"text-destructive\"\n                  onClick={(e) => {\n                    e.preventDefault();\n                    remove(i);\n                  }}\n                >\n                  <svg\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    width=\"1.5rem\"\n                    height=\"1.5rem\"\n                    fill=\"none\"\n                    viewBox=\"0 0 24 24\"\n                    stroke=\"currentColor\"\n                    strokeWidth={2}\n                  >\n                    <path\n                      strokeLinecap=\"round\"\n                      strokeLinejoin=\"round\"\n                      d=\"M18 12H6\"\n                    />\n                  </svg>\n                </a>\n              </TableCell>\n            </TableRow>\n          ))}\n        </TableBody>\n      </Table>\n      <div className=\"mt-2 flex justify-start\">\n        <div className=\"pl-2\">\n          <Button\n            variant={'outline'}\n            onClick={(e) => {\n              e.preventDefault();\n              append({\n                key: 'category',\n                operator: Operator.EQUAL,\n                value: '',\n                qty: '',\n                editable: true\n              });\n            }}\n          >\n            <span>Add product</span>\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/Setting.tsx",
    "content": "import { DateField } from '@components/common/form/DateField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport React from 'react';\n\nexport const Setting: React.FC<{\n  discountAmount?: number;\n  startDate?: string;\n  endDate?: string;\n}> = ({ discountAmount, startDate, endDate }) => {\n  return (\n    <div className=\"grid grid-cols-3 gap-5 form-field-container\">\n      <div>\n        <NumberField\n          name=\"discount_amount\"\n          defaultValue={discountAmount}\n          placeholder=\"Discount amount\"\n          required\n          label=\"Discount amount\"\n          validation={{\n            required: 'Discount amount is required'\n          }}\n        />\n      </div>\n      <div>\n        <DateField\n          name=\"start_date\"\n          label=\"Start date\"\n          placeholder=\"Start date\"\n          defaultValue={startDate}\n        />\n      </div>\n      <div>\n        <DateField\n          placeholder=\"End date\"\n          name=\"end_date\"\n          label=\"End date\"\n          defaultValue={endDate}\n        />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/SkuConditionValueSelector.tsx",
    "content": "import { ProductSelector } from '@components/admin/ProductSelector.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport React from 'react';\n\nexport const SkuConditionValueSelector: React.FC<{\n  selectedValues: Array<string> | string;\n  updateCondition: (values: string | Array<string>) => void;\n  isMulti: boolean;\n}> = ({ selectedValues, updateCondition, isMulti }) => {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const skus = Array.isArray(selectedValues) ? selectedValues : [];\n  const selectedSKUs = React.useRef(skus || []);\n\n  const onSelect = (sku) => {\n    if (!isMulti) {\n      selectedSKUs.current = [sku];\n      setDialogOpen(false);\n    } else {\n      selectedSKUs.current = [...selectedSKUs.current, sku];\n    }\n  };\n\n  const onUnSelect = (sku) => {\n    const prev = selectedSKUs.current;\n    selectedSKUs.current = prev.filter((s) => s !== sku);\n  };\n\n  return (\n    <Dialog\n      open={dialogOpen}\n      onOpenChange={(open) => setDialogOpen(open)}\n      onOpenChangeComplete={(open) => {\n        if (!open) {\n          updateCondition(selectedSKUs.current);\n        }\n      }}\n    >\n      <DialogTrigger>\n        <Button variant={'link'}>\n          {selectedSKUs.current.map((sku, index) => (\n            <span key={sku}>\n              {index === 0 && (\n                <span className=\"italic\">&lsquo;{sku}&rsquo;</span>\n              )}\n              {index === 1 && (\n                <span> and {selectedSKUs.current.length - 1} more</span>\n              )}\n            </span>\n          ))}\n          {selectedSKUs.current.length === 0 && <span>Choose SKUs</span>}\n        </Button>\n      </DialogTrigger>\n      <DialogContent className={'max-w-[80vw]'}>\n        <DialogHeader>\n          <DialogTitle>Select Products by SKU</DialogTitle>\n        </DialogHeader>\n        <ProductSelector\n          onSelect={onSelect}\n          onUnSelect={onUnSelect}\n          selectedProducts={selectedSKUs.current.map((sku) => ({\n            sku,\n            uuid: undefined,\n            productId: undefined\n          }))}\n        />\n      </DialogContent>\n    </Dialog>\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/TargetProducts.tsx",
    "content": "import { InputField } from '@components/common/form/InputField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { Item, ItemContent, ItemTitle } from '@components/common/ui/Item.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport React, { useEffect } from 'react';\nimport { useFieldArray, useFormContext } from 'react-hook-form';\nimport { options, operators, Operator } from './conditionCriterias.js';\nimport { ValueSelector } from './ValueSelector.js';\n\nexport interface Product {\n  key: string;\n  operator: Operator;\n  value: string | number | string[] | number[];\n  editable?: boolean;\n}\n\nfunction Products({\n  targetProducts,\n  maxQty\n}: {\n  targetProducts: Product[];\n  maxQty: number;\n}) {\n  const { setValue, watch, unregister } = useFormContext();\n  const { fields, append, remove, replace } = useFieldArray<{\n    target_products: {\n      products: Product[];\n    };\n  }>({\n    name: 'target_products.products'\n  });\n  const watchDiscountType = watch('discount_type');\n  const fieldWatch = watch('target_products.products');\n\n  useEffect(() => {\n    replace(\n      targetProducts.map((product) => ({\n        key: product.key,\n        operator: product.operator,\n        value: product.value\n      }))\n    );\n    return () => {\n      unregister('target_products.products');\n    };\n  }, []);\n\n  if (\n    watchDiscountType !== 'fixed_discount_to_specific_products' &&\n    watchDiscountType !== 'percentage_discount_to_specific_products'\n  ) {\n    return null;\n  }\n  return (\n    <div>\n      <div className=\"mb-2 mt-2\">\n        <div className=\"flex justify-start items-center\">\n          <div>Maximum</div>\n          <div style={{ width: '100px', padding: '0 1rem' }}>\n            <NumberField\n              name=\"target_products.maxQty\"\n              defaultValue={maxQty}\n              placeholder=\"10\"\n              required\n              validation={{\n                required: 'Maximum quantity is required',\n                min: {\n                  value: 0,\n                  message: 'Maximum quantity must be greater than or equal to 0'\n                }\n              }}\n              min={0}\n              wrapperClassName=\"form-field mb-0\"\n            />\n          </div>\n          <div>quantity of products are matched bellow conditions(All)</div>\n        </div>\n      </div>\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead>\n              <span>Key</span>\n            </TableHead>\n            <TableHead>\n              <span>Operator</span>\n            </TableHead>\n            <TableHead>\n              <span>Value</span>\n            </TableHead>\n            <TableHead> </TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {fields.map((product, index) => (\n            <TableRow key={product.id}>\n              <TableCell>\n                {product.editable ? (\n                  <SelectField\n                    name={`target_products.products.${index}.key`}\n                    wrapperClassName=\"form-field mb-0\"\n                    defaultValue={product.key}\n                    disabled={!product.editable}\n                    options={options.map((option) => ({\n                      value: option.key,\n                      label: option.label\n                    }))}\n                  />\n                ) : (\n                  <>\n                    <InputField\n                      type=\"hidden\"\n                      name={`target_products.products.${index}.key`}\n                      readOnly\n                      wrapperClassName=\"form-field mb-0\"\n                      value={product.key}\n                    />\n                    <InputField\n                      name={`target_products.products.${index}.keylabel`}\n                      readOnly\n                      wrapperClassName=\"form-field mb-0\"\n                      value={\n                        options.find((c) => c.key === product.key)?.label ||\n                        'Unknown'\n                      }\n                    />\n                  </>\n                )}\n              </TableCell>\n              <TableCell>\n                {product.editable ? (\n                  <SelectField\n                    name={`target_products.products.${index}.operator`}\n                    defaultValue={product.operator}\n                    options={operators.map((operator) => ({\n                      value: operator.key,\n                      label: operator.label\n                    }))}\n                    wrapperClassName=\"form-field mb-0\"\n                    placeholder=\"Select operator\"\n                  />\n                ) : (\n                  <>\n                    <InputField\n                      type=\"hidden\"\n                      name={`target_products.products.${index}.operator`}\n                      readOnly\n                      wrapperClassName=\"form-field mb-0\"\n                      value={product.operator}\n                    />\n                    <InputField\n                      name={`target_products.products.${index}.operatorlabel`}\n                      type=\"text\"\n                      readOnly\n                      wrapperClassName=\"form-field mb-0\"\n                      value={\n                        operators.find(\n                          (c) => c.key === fieldWatch[index]?.operator\n                        )?.label || 'Unknown'\n                      }\n                    />\n                  </>\n                )}\n              </TableCell>\n              <TableCell>\n                {fieldWatch[index].key === 'price' && (\n                  <NumberField\n                    name={`target_products.products.${index}.value`}\n                    defaultValue={product.value as number}\n                    wrapperClassName=\"form-field mb-0\"\n                  />\n                )}\n                {fieldWatch[index].key !== 'price' && (\n                  <>\n                    <InputField\n                      type=\"hidden\"\n                      name={`target_products.products.${index}.value`}\n                      value={product.value as string}\n                      wrapperClassName=\"form-field mb-0\"\n                    />\n                    <ValueSelector\n                      condition={fieldWatch[index]}\n                      updateCondition={(values) => {\n                        setValue(\n                          `target_products.products.${index}.value`,\n                          values\n                        );\n                      }}\n                    />\n                  </>\n                )}\n              </TableCell>\n              <TableCell>\n                <a\n                  href=\"#\"\n                  className=\"text-destructive\"\n                  onClick={(e) => {\n                    e.preventDefault();\n                    remove(index);\n                  }}\n                >\n                  <svg\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    width=\"1.5rem\"\n                    height=\"1.5rem\"\n                    fill=\"none\"\n                    viewBox=\"0 0 24 24\"\n                    stroke=\"currentColor\"\n                    strokeWidth={2}\n                  >\n                    <path\n                      strokeLinecap=\"round\"\n                      strokeLinejoin=\"round\"\n                      d=\"M18 12H6\"\n                    />\n                  </svg>\n                </a>\n              </TableCell>\n            </TableRow>\n          ))}\n        </TableBody>\n      </Table>\n      <div className=\"mt-2 flex justify-start\">\n        <div className=\"pl-2\">\n          <Button\n            variant={'outline'}\n            onClick={(e) => {\n              e.preventDefault();\n              append({\n                key: 'category',\n                operator: Operator.EQUAL,\n                value: '',\n                editable: true\n              });\n            }}\n          >\n            <span>Add product</span>\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n}\n\ninterface TargetProductsProps {\n  products: Array<Product>;\n  maxQty: number;\n}\nexport function TargetProducts({\n  products,\n  maxQty\n}: TargetProductsProps): React.ReactElement | null {\n  const { watch } = useFormContext();\n  const watchDiscountType = watch('discount_type');\n  if (\n    watchDiscountType !== 'fixed_discount_to_specific_products' &&\n    watchDiscountType !== 'percentage_discount_to_specific_products'\n  ) {\n    return null;\n  }\n\n  return (\n    <Item variant={'outline'} className=\"mt-6\">\n      <ItemContent>\n        <ItemTitle>Target Products</ItemTitle>\n        <Products targetProducts={products} maxQty={maxQty} />\n      </ItemContent>\n    </Item>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/ValueSelector.tsx",
    "content": "import React from 'react';\nimport { AttributeGroupConditionValueSelector } from './AttributeGroupConditionValueSelector.js';\nimport { CategoryConditionValueSelector } from './CategoryConditionValueSelector.js';\nimport { CollectionConditionValueSelector } from './CollectionConditionValueSelector.js';\nimport { Operator } from './conditionCriterias.js';\nimport { PriceConditionValueSelector } from './PriceConditionValueSelector.js';\nimport { SkuConditionValueSelector } from './SkuConditionValueSelector.js';\n\nexport const ValueSelector: React.FC<{\n  condition: {\n    key: string;\n    operator: Operator;\n    value: string | number | Array<string> | Array<number>;\n  };\n  updateCondition: (\n    values: string | number | Array<string> | Array<number>\n  ) => void;\n}> = ({ condition, updateCondition }) => {\n  switch (condition.key) {\n    case 'category':\n      return (\n        <CategoryConditionValueSelector\n          selectedValues={\n            Array.isArray(condition.value) ? condition.value.map(Number) : []\n          }\n          updateCondition={updateCondition}\n          isMulti={\n            condition.operator === Operator.IN ||\n            condition.operator === Operator.NOT_IN\n          }\n        />\n      );\n    case 'collection':\n      return (\n        <CollectionConditionValueSelector\n          selectedValues={\n            Array.isArray(condition.value) ? condition.value.map(Number) : []\n          }\n          updateCondition={updateCondition}\n          isMulti={\n            condition.operator === Operator.IN ||\n            condition.operator === Operator.NOT_IN\n          }\n        />\n      );\n    case 'sku':\n      return (\n        <SkuConditionValueSelector\n          selectedValues={\n            Array.isArray(condition.value) ? condition.value.map(String) : []\n          }\n          updateCondition={updateCondition}\n          isMulti={\n            condition.operator === Operator.IN ||\n            condition.operator === Operator.NOT_IN\n          }\n        />\n      );\n    case 'attribute_group':\n      return (\n        <AttributeGroupConditionValueSelector\n          selectedValues={\n            Array.isArray(condition.value) ? condition.value.map(Number) : []\n          }\n          updateCondition={updateCondition}\n          isMulti={\n            condition.operator === Operator.IN ||\n            condition.operator === Operator.NOT_IN\n          }\n        />\n      );\n    case 'price':\n      return (\n        <PriceConditionValueSelector\n          updateCondition={updateCondition}\n          condition={{ ...condition, value: Number(condition.value) }}\n        />\n      );\n    default:\n      return null;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/components/conditionCriterias.ts",
    "content": "export enum Operator {\n  EQUAL = '=',\n  NOT_EQUAL = '!=',\n  GREATER = '>',\n  GREATER_OR_EQUAL = '>=',\n  SMALLER = '<',\n  SMALLER_OR_EQUAL = '<=',\n  IN = 'IN',\n  NOT_IN = 'NOT IN'\n}\n\nexport type ConditionKey = {\n  key: string;\n  label: string;\n};\n\nexport type OperatorOption = {\n  key: Operator;\n  label: string;\n};\n\nconst options: ConditionKey[] = [\n  {\n    key: 'category',\n    label: 'Category'\n  },\n  {\n    key: 'collection',\n    label: 'Collection'\n  },\n  {\n    key: 'attribute_group',\n    label: 'Attribute Group'\n  },\n  {\n    key: 'sku',\n    label: 'Sku'\n  },\n  {\n    key: 'price',\n    label: 'Price'\n  }\n];\n\nconst operators: OperatorOption[] = [\n  {\n    key: Operator.EQUAL,\n    label: 'Equal'\n  },\n  {\n    key: Operator.NOT_EQUAL,\n    label: 'Not equal'\n  },\n  {\n    key: Operator.GREATER,\n    label: 'Greater'\n  },\n  {\n    key: Operator.GREATER_OR_EQUAL,\n    label: 'Greater or equal'\n  },\n  {\n    key: Operator.SMALLER,\n    label: 'Smaller'\n  },\n  {\n    key: Operator.SMALLER_OR_EQUAL,\n    label: 'Equal or smaller'\n  },\n  {\n    key: Operator.IN,\n    label: 'In'\n  },\n  {\n    key: Operator.NOT_IN,\n    label: 'Not in'\n  }\n];\n\nexport { options, operators };\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponGrid/Grid.jsx",
    "content": "import { GridPagination } from '@components/admin/grid/GridPagination';\nimport { DummyColumnHeader } from '@components/admin/grid/header/Dummy';\nimport { SortableHeader } from '@components/admin/grid/header/Sortable';\nimport { Status } from '@components/admin/Status.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { useAlertContext } from '@components/common/modal/Alert';\nimport { Button } from '@components/common/ui/Button.js';\nimport { ButtonGroup } from '@components/common/ui/ButtonGroup.js';\nimport { Card } from '@components/common/ui/Card';\nimport {\n  CardAction,\n  CardContent,\n  CardHeader\n} from '@components/common/ui/Card.js';\nimport { Checkbox } from '@components/common/ui/Checkbox.js';\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectTrigger,\n  SelectValue\n} from '@components/common/ui/Select.js';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport axios from 'axios';\nimport PropTypes from 'prop-types';\nimport React, { useState } from 'react';\nimport { CouponName } from './rows/CouponName.js';\n\nfunction Actions({ coupons = [], selectedIds = [] }) {\n  const { openAlert, closeAlert } = useAlertContext();\n  const [isLoading, setIsLoading] = useState(false);\n\n  const updateCoupons = async (status) => {\n    setIsLoading(true);\n    const promises = coupons\n      .filter((coupon) => selectedIds.includes(coupon.uuid))\n      .map((coupon) =>\n        axios.patch(coupon.updateApi, {\n          status,\n          coupon: coupon.coupon\n        })\n      );\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const deleteCoupons = async () => {\n    setIsLoading(true);\n    const promises = coupons\n      .filter((coupon) => selectedIds.includes(coupon.uuid))\n      .map((coupon) => axios.delete(coupon.deleteApi));\n    await Promise.all(promises);\n    setIsLoading(false);\n    // Refresh the page\n    window.location.reload();\n  };\n\n  const actions = [\n    {\n      name: 'Disable',\n      onAction: () => {\n        openAlert({\n          heading: `Disable ${selectedIds.length} coupons`,\n          content: 'Are you sure?',\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Disable',\n            onAction: async () => {\n              await updateCoupons(0);\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    },\n    {\n      name: 'Enable',\n      onAction: () => {\n        openAlert({\n          heading: `Enable ${selectedIds.length} coupons`,\n          content: 'Are you sure?',\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Enable',\n            onAction: async () => {\n              await updateCoupons(1);\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    },\n    {\n      name: 'Delete',\n      onAction: () => {\n        openAlert({\n          heading: `Delete ${selectedIds.length} coupons`,\n          content: <div>Can&apos;t be undone</div>,\n          primaryAction: {\n            title: 'Cancel',\n            onAction: closeAlert,\n            variant: 'secondary'\n          },\n          secondaryAction: {\n            title: 'Delete',\n            onAction: async () => {\n              await deleteCoupons();\n            },\n            variant: 'destructive'\n          }\n        });\n      }\n    }\n  ];\n\n  return (\n    <TableRow>\n      {selectedIds.length === 0 && null}\n      {selectedIds.length > 0 && (\n        <TableCell colSpan=\"100\">\n          <ButtonGroup>\n            {actions.map((action, i) => (\n              <Button\n                key={i}\n                variant={'outline'}\n                onClick={(e) => {\n                  e.preventDefault();\n                  action.onAction();\n                }}\n              >\n                {action.name}\n              </Button>\n            ))}\n          </ButtonGroup>\n        </TableCell>\n      )}\n    </TableRow>\n  );\n}\n\nActions.propTypes = {\n  selectedIds: PropTypes.arrayOf(PropTypes.string).isRequired,\n  coupons: PropTypes.arrayOf(\n    PropTypes.shape({\n      uuid: PropTypes.string.isRequired,\n      updateApi: PropTypes.string.isRequired,\n      deleteApi: PropTypes.string.isRequired,\n      coupon: PropTypes.string.isRequired\n    })\n  ).isRequired\n};\n\nexport default function CouponGrid({\n  coupons: { items: coupons, total, currentFilters = [] }\n}) {\n  const page = currentFilters.find((filter) => filter.key === 'page')\n    ? parseInt(currentFilters.find((filter) => filter.key === 'page').value, 10)\n    : 1;\n  const limit = currentFilters.find((filter) => filter.key === 'limit')\n    ? parseInt(\n        currentFilters.find((filter) => filter.key === 'limit').value,\n        10\n      )\n    : 20;\n  const [selectedRows, setSelectedRows] = useState([]);\n\n  return (\n    <Card>\n      <CardHeader className=\"flex justify-between\">\n        <Form submitBtn={false} id=\"couponGridFilter\">\n          <div className=\"flex gap-5 justify-center items-center\">\n            <Area\n              id=\"couponGridFilter\"\n              noOuter\n              coreComponents={[\n                {\n                  component: {\n                    default: () => (\n                      <InputField\n                        name=\"coupon\"\n                        placeholder=\"Search\"\n                        defaultValue={\n                          currentFilters.find((f) => f.key === 'coupon')?.value\n                        }\n                        onKeyPress={(e) => {\n                          // If the user press enter, we should submit the form\n                          if (e.key === 'Enter') {\n                            const url = new URL(document.location);\n                            const coupon = e.target?.value;\n                            if (coupon) {\n                              url.searchParams.set('coupon[operation]', 'like');\n                              url.searchParams.set('coupon[value]', coupon);\n                            } else {\n                              url.searchParams.delete('coupon[operation]');\n                              url.searchParams.delete('coupon[value]');\n                            }\n                            window.location.href = url;\n                          }\n                        }}\n                      />\n                    )\n                  },\n                  sortOrder: 5\n                },\n                {\n                  component: {\n                    default: () => (\n                      <Select\n                        value={\n                          currentFilters.find((f) => f.key === 'status')?.value\n                        }\n                        onValueChange={(value) => {\n                          const url = new URL(document.location);\n                          url.searchParams.set('status', value);\n                          window.location.href = url.href;\n                        }}\n                      >\n                        <SelectTrigger>\n                          <SelectValue>Status</SelectValue>\n                        </SelectTrigger>\n                        <SelectContent>\n                          <SelectGroup>\n                            <SelectLabel>Status</SelectLabel>\n                            <SelectItem value=\"1\">Enabled</SelectItem>\n                            <SelectItem value=\"0\">Disabled</SelectItem>\n                          </SelectGroup>\n                        </SelectContent>\n                      </Select>\n                    )\n                  },\n                  sortOrder: 10\n                },\n                {\n                  component: {\n                    default: () => (\n                      <Select\n                        value={\n                          currentFilters.find((f) => f.key === 'free_shipping')\n                            ?.value\n                        }\n                        onValueChange={(value) => {\n                          const url = new URL(document.location);\n                          url.searchParams.set('free_shipping', value);\n                          window.location.href = url.href;\n                        }}\n                      >\n                        <SelectTrigger>\n                          <SelectValue>Free shipping ?</SelectValue>\n                        </SelectTrigger>\n                        <SelectContent>\n                          <SelectGroup>\n                            <SelectLabel>Free shipping ?</SelectLabel>\n                            <SelectItem value=\"1\">Free shipping</SelectItem>\n                            <SelectItem value=\"0\">No free shipping</SelectItem>\n                          </SelectGroup>\n                        </SelectContent>\n                      </Select>\n                    )\n                  },\n                  sortOrder: 10\n                }\n              ]}\n              currentFilters={currentFilters}\n            />\n          </div>\n        </Form>\n        <CardAction>\n          <Button\n            variant=\"link\"\n            onClick={() => {\n              const url = new URL(document.location);\n              url.search = '';\n              window.location.href = url.href;\n            }}\n          >\n            Clear filter\n          </Button>\n        </CardAction>\n      </CardHeader>\n      <CardContent>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>\n                <div className=\"form-field mb-0\">\n                  <Checkbox\n                    onCheckedChange={(checked) => {\n                      if (checked) setSelectedRows(coupons.map((c) => c.uuid));\n                      else setSelectedRows([]);\n                    }}\n                  />\n                </div>\n              </TableHead>\n              <Area\n                id=\"couponGridHeader\"\n                noOuter\n                coreComponents={[\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Coupon Code\"\n                          name=\"coupon\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 10\n                  },\n                  {\n                    component: {\n                      default: () => <DummyColumnHeader title=\"State Date\" />\n                    },\n                    sortOrder: 20\n                  },\n                  {\n                    component: {\n                      default: () => <DummyColumnHeader title=\"End Date\" />\n                    },\n                    sortOrder: 30\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Status\"\n                          name=\"status\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 40\n                  },\n                  {\n                    component: {\n                      default: () => (\n                        <SortableHeader\n                          title=\"Used Times\"\n                          name=\"used_time\"\n                          currentFilters={currentFilters}\n                        />\n                      )\n                    },\n                    sortOrder: 50\n                  }\n                ]}\n              />\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            <Actions\n              coupons={coupons}\n              selectedIds={selectedRows}\n              setSelectedRows={setSelectedRows}\n            />\n            {coupons.map((c) => (\n              <TableRow key={c.couponId}>\n                <TableHead>\n                  <div className=\"form-field mb-0\">\n                    <Checkbox\n                      checked={selectedRows.includes(c.uuid)}\n                      onCheckedChange={(checked) => {\n                        if (checked) {\n                          setSelectedRows(selectedRows.concat([c.uuid]));\n                        } else {\n                          setSelectedRows(\n                            selectedRows.filter((row) => row !== c.uuid)\n                          );\n                        }\n                      }}\n                    />\n                  </div>\n                </TableHead>\n                <Area\n                  id=\"couponGridRow\"\n                  row={c}\n                  noOuter\n                  selectedRows={selectedRows}\n                  setSelectedRows={setSelectedRows}\n                  coreComponents={[\n                    {\n                      component: {\n                        default: () => (\n                          <CouponName url={c.editUrl} name={c.coupon} />\n                        )\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <TableCell>{c.startDate?.text || '--'}</TableCell>\n                        )\n                      },\n                      sortOrder: 20\n                    },\n                    {\n                      component: {\n                        default: () => (\n                          <TableCell>{c.endDate?.text || '--'}</TableCell>\n                        )\n                      },\n                      sortOrder: 30\n                    },\n                    {\n                      component: {\n                        default: ({ areaProps }) => (\n                          <Status status={parseInt(c.status, 10)} />\n                        )\n                      },\n                      sortOrder: 40\n                    },\n                    {\n                      component: {\n                        default: ({ areaProps }) => (\n                          <TableCell>{c.usedTime}</TableCell>\n                        )\n                      },\n                      sortOrder: 50\n                    }\n                  ]}\n                />\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {coupons.length === 0 && (\n          <div className=\"flex w-full justify-center mt-2\">\n            There is no coupon to display\n          </div>\n        )}\n        <GridPagination total={total} limit={limit} page={page} />\n      </CardContent>\n    </Card>\n  );\n}\n\nCouponGrid.propTypes = {\n  coupons: PropTypes.shape({\n    items: PropTypes.arrayOf(\n      PropTypes.shape({\n        couponId: PropTypes.number.isRequired,\n        uuid: PropTypes.string.isRequired,\n        coupon: PropTypes.string.isRequired,\n        status: PropTypes.number.isRequired,\n        usedTime: PropTypes.number.isRequired,\n        startDate: PropTypes.shape({\n          text: PropTypes.string.isRequired\n        }),\n        endDate: PropTypes.shape({\n          text: PropTypes.string.isRequired\n        }),\n        editUrl: PropTypes.string.isRequired,\n        updateApi: PropTypes.string.isRequired,\n        deleteApi: PropTypes.string.isRequired\n      })\n    ).isRequired,\n    total: PropTypes.number.isRequired,\n    currentFilters: PropTypes.arrayOf(\n      PropTypes.shape({\n        key: PropTypes.string.isRequired,\n        operation: PropTypes.string.isRequired,\n        value: PropTypes.string.isRequired\n      })\n    ).isRequired\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query($filters: [FilterInput]) {\n    coupons (filters: $filters) {\n      items {\n        couponId\n        uuid\n        coupon\n        status\n        usedTime\n        startDate {\n          text\n        }\n        endDate {\n          text\n        }\n        editUrl\n        updateApi\n        deleteApi\n      }\n      total\n      currentFilters {\n        key\n        operation\n        value\n      }\n    }\n  }\n`;\n\nexport const variables = `\n{\n  filters: getContextValue('filtersFromUrl')\n}`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponGrid/Heading.tsx",
    "content": "import { PageHeading } from '@components/admin/PageHeading.js';\nimport React from 'react';\n\nexport default function CouponGridHeading() {\n  return <PageHeading heading=\"Coupons\" />;\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponGrid/NewCouponButton.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\n\ninterface NewCouponButtonProps {\n  newCouponUrl: string;\n}\n\nexport default function NewCouponButton({\n  newCouponUrl\n}: NewCouponButtonProps) {\n  return (\n    <Button\n      onClick={() => (window.location.href = newCouponUrl)}\n      title=\"New Coupon\"\n    >\n      {' '}\n      New Coupon{' '}\n    </Button>\n  );\n}\n\nexport const layout = {\n  areaId: 'pageHeadingRight',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    newCouponUrl: url(routeId: \"couponNew\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponGrid/index.ts",
    "content": "import { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';\nimport { EvershopRequest } from '../../../../../types/request.js';\nimport { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\nimport { setContextValue } from '../../../../graphql/services/contextHelper.js';\n\nexport default (request: EvershopRequest, response) => {\n  setPageMetaInfo(request, {\n    title: 'Coupons',\n    description: 'Coupons'\n  });\n  setContextValue(\n    request,\n    'filtersFromUrl',\n    buildFilterFromUrl(request.originalUrl)\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponGrid/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/coupons\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponGrid/rows/CouponName.tsx",
    "content": "import { TableCell } from '@components/common/ui/Table.js';\nimport React from 'react';\n\ninterface CouponNameProps {\n  name: string;\n  url: string;\n}\n\nexport function CouponName({ url, name }: CouponNameProps) {\n  return (\n    <TableCell>\n      <a className=\"hover:underline font-semibold\" href={url}>\n        {name}\n      </a>\n    </TableCell>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponNew/CouponNewForm.tsx",
    "content": "import { FormButtons } from '@components/admin/FormButtons.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface CouponNewFormProps {\n  action: string;\n  gridUrl: string;\n}\nexport default function CouponNewForm({ action, gridUrl }: CouponNewFormProps) {\n  return (\n    <Form\n      action={action}\n      method=\"POST\"\n      id=\"couponNewForm\"\n      onSuccess={(response) => {\n        toast.success('Coupon created successfully!');\n        const editUrl = response.data.links.find(\n          (link) => link.rel === 'edit'\n        ).href;\n        window.location.href = editUrl;\n      }}\n      submitBtn={false}\n    >\n      <div className=\"grid grid-cols-1 gap-5\">\n        <Card>\n          <CardHeader>\n            <CardTitle>General Information</CardTitle>\n            <CardDescription>\n              The general information about the coupon.\n            </CardDescription>\n          </CardHeader>\n          <CardContent>\n            <Area id=\"couponEditGeneral\" noOuter />\n          </CardContent>\n        </Card>\n        <Card>\n          <CardHeader>\n            <CardTitle>Discount Type</CardTitle>\n            <CardDescription>\n              The type of discount applied by the coupon.\n            </CardDescription>\n          </CardHeader>\n          <CardContent>\n            <Area id=\"couponEditDiscountType\" noOuter />\n          </CardContent>\n        </Card>\n        <div className=\"grid grid-cols-3 gap-x-5 grid-flow-row \">\n          <div className=\"col-span-2 grid grid-cols-1 gap-5 auto-rows-max\">\n            <Card>\n              <CardHeader>\n                <CardTitle>Order conditions</CardTitle>\n                <CardDescription>\n                  The conditions related to the order for the coupon to be\n                  applied.\n                </CardDescription>\n              </CardHeader>\n              <CardContent>\n                <Area id=\"couponEditLeft\" noOuter className=\"col-8\" />\n              </CardContent>\n            </Card>\n          </div>\n          <div className=\"col-span-1 grid grid-cols-1 gap-5 auto-rows-max\">\n            <Card>\n              <CardHeader>\n                <CardTitle>Customer conditions</CardTitle>\n                <CardDescription>\n                  The conditions related to the customer for the coupon to be\n                  applied.\n                </CardDescription>\n              </CardHeader>\n              <CardContent>\n                <Area id=\"couponEditRight\" className=\"col-4\" noOuter />\n              </CardContent>\n            </Card>\n          </div>\n        </div>\n      </div>\n      <FormButtons cancelUrl={gridUrl} formId=\"couponNewForm\" />\n    </Form>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    action: url(routeId: \"createCoupon\")\n    gridUrl: url(routeId: \"couponGrid\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponNew/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request) => {\n  setPageMetaInfo(request, {\n    title: 'Create a new coupon',\n    description: 'Create a new coupon'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/couponNew/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/coupon/new\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/navigation/CouponNewMenuItem.tsx",
    "content": "import { NavigationItem } from '@components/admin/NavigationItem.js';\nimport { Gift } from 'lucide-react';\nimport React from 'react';\n\ninterface CouponNewMenuItemProps {\n  url: string;\n}\n\nexport default function CouponNewMenuItem({ url }: CouponNewMenuItemProps) {\n  return <NavigationItem Icon={Gift} title=\"New coupon\" url={url} />;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/pages/admin/navigation/CouponsMenuItem.tsx",
    "content": "import { NavigationItem } from '@components/admin/NavigationItem.js';\nimport { Gift } from 'lucide-react';\nimport React from 'react';\n\ninterface CouponsMenuItemProps {\n  url: string;\n}\n\nexport default function CouponsMenuItem({ url }: CouponsMenuItemProps) {\n  return <NavigationItem Icon={Gift} title=\"Coupons\" url={url} />;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/CouponCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class CouponCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n  }\n\n  async init(filters = []) {\n    const currentFilters = [];\n\n    // Apply the filters\n    const couponCollectionFilters = await getValue(\n      'couponCollectionFilters',\n      []\n    );\n\n    couponCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(coupon.coupon_id)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/coupon/couponDataSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"coupon\": {\n      \"type\": \"string\",\n      \"pattern\": \"^[a-zA-Z0-9]+$\",\n      \"minLength\": 1,\n      \"errorMessage\": {\n        \"type\": \"Coupon code must be a string\",\n        \"pattern\": \"Coupon code can only contain letters and numbers (a-z, A-Z, 0-9)\",\n        \"minLength\": \"Coupon code is required and cannot be empty\"\n      }\n    },\n    \"status\": {\n      \"type\": [\"string\", \"integer\"],\n      \"enum\": [\"0\", \"1\", 0, 1],\n      \"errorMessage\": {\n        \"type\": \"Status must be a string or number\",\n        \"enum\": \"Status must be either 0, 1, '0', or '1'\"\n      }\n    },\n    \"description\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Description must be a string\"\n      }\n    },\n    \"discount_amount\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d*\\\\.?\\\\d*$\",\n      \"errorMessage\": {\n        \"type\": \"Discount amount must be a string or number\",\n        \"pattern\": \"Discount amount must be a valid number (e.g., 10, 10.5, 10.99)\"\n      }\n    },\n    \"free_shipping\": {\n      \"type\": [\"string\", \"integer\", \"boolean\"],\n      \"enum\": [\"0\", \"1\", 0, 1, true, false],\n      \"errorMessage\": {\n        \"type\": \"Free shipping must be a boolean, number, or string\",\n        \"enum\": \"Free shipping must be either 0, 1, '0', '1', true, or false\"\n      }\n    },\n    \"discount_type\": {\n      \"type\": \"string\",\n      \"errorMessage\": {\n        \"type\": \"Discount type must be a string\"\n      }\n    },\n    \"target_products\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"maxQty\": {\n          \"type\": [\"string\", \"integer\"],\n          \"pattern\": \"^[0-9]*$\",\n          \"errorMessage\": {\n            \"type\": \"Max quantity must be a string or number\",\n            \"pattern\": \"Max quantity must be a valid whole number (e.g., 1, 10, 100)\"\n          }\n        },\n        \"products\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"key\": {\n                \"type\": \"string\",\n                \"errorMessage\": {\n                  \"type\": \"Product condition key must be a string\"\n                }\n              },\n              \"operator\": {\n                \"type\": \"string\",\n                \"enum\": [\"IN\", \"NOT IN\", \"=\", \"!=\", \">\", \">=\", \"<\", \"<=\"],\n                \"errorMessage\": {\n                  \"type\": \"Operator must be a string\",\n                  \"enum\": \"Operator must be one of: IN, NOT IN, =, !=, >, >=, <, <=\"\n                }\n              },\n              \"value\": {\n                \"anyOf\": [\n                  {\n                    \"type\": \"string\"\n                  },\n                  {\n                    \"type\": \"number\"\n                  },\n                  {\n                    \"type\": \"array\",\n                    \"items\": {\n                      \"type\": [\n                        \"string\",\n                        \"number\",\n                        \"boolean\",\n                        \"null\",\n                        \"array\",\n                        \"object\"\n                      ]\n                    }\n                  }\n                ],\n                \"errorMessage\": {\n                  \"anyOf\": \"Value must be a string, number, or array\"\n                }\n              }\n            },\n            \"required\": [\"key\", \"operator\", \"value\"],\n            \"additionalProperties\": true,\n            \"errorMessage\": {\n              \"type\": \"Product condition must be an object\",\n              \"required\": {\n                \"key\": \"Product condition key is required\",\n                \"operator\": \"Product condition operator is required\",\n                \"value\": \"Product condition value is required\"\n              }\n            }\n          },\n          \"errorMessage\": {\n            \"type\": \"Products must be an array\"\n          }\n        }\n      },\n      \"errorMessage\": {\n        \"type\": \"Target products must be an object\"\n      }\n    },\n    \"condition\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"order_total\": {\n          \"type\": [\"string\", \"number\"],\n          \"pattern\": \"^\\\\d*\\\\.?\\\\d*$\",\n          \"errorMessage\": {\n            \"type\": \"Order total must be a string or number\",\n            \"pattern\": \"Order total must be a valid number (e.g., 100, 100.50)\"\n          }\n        },\n        \"order_qty\": {\n          \"type\": [\"string\", \"integer\"],\n          \"pattern\": \"^[0-9]*$\",\n          \"errorMessage\": {\n            \"type\": \"Order quantity must be a string or number\",\n            \"pattern\": \"Order quantity must be a valid whole number (e.g., 1, 5, 10)\"\n          }\n        },\n        \"required_products\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"key\": {\n                \"type\": \"string\",\n                \"errorMessage\": {\n                  \"type\": \"Required product key must be a string\"\n                }\n              },\n              \"operator\": {\n                \"type\": \"string\",\n                \"enum\": [\"IN\", \"NOT IN\", \"=\", \"!=\", \">\", \">=\", \"<\", \"<=\"],\n                \"errorMessage\": {\n                  \"type\": \"Operator must be a string\",\n                  \"enum\": \"Operator must be one of: IN, NOT IN, =, !=, >, >=, <, <=\"\n                }\n              },\n              \"qty\": {\n                \"type\": [\"string\", \"integer\"],\n                \"pattern\": \"^[0-9]*$\",\n                \"errorMessage\": {\n                  \"type\": \"Quantity must be a string or number\",\n                  \"pattern\": \"Quantity must be a valid whole number (e.g., 1, 5, 10)\"\n                }\n              },\n              \"value\": {\n                \"anyOf\": [\n                  {\n                    \"type\": \"string\"\n                  },\n                  {\n                    \"type\": \"number\"\n                  },\n                  {\n                    \"type\": \"array\",\n                    \"items\": {\n                      \"type\": [\n                        \"string\",\n                        \"number\",\n                        \"boolean\",\n                        \"null\",\n                        \"array\",\n                        \"object\"\n                      ]\n                    }\n                  }\n                ],\n                \"errorMessage\": {\n                  \"anyOf\": \"Value must be a string, number, or array\"\n                }\n              }\n            },\n            \"required\": [\"key\", \"qty\", \"operator\", \"value\"],\n            \"additionalProperties\": true,\n            \"errorMessage\": {\n              \"type\": \"Required product condition must be an object\",\n              \"required\": {\n                \"key\": \"Required product key is required\",\n                \"qty\": \"Required product quantity is required\",\n                \"operator\": \"Required product operator is required\",\n                \"value\": \"Required product value is required\"\n              }\n            }\n          },\n          \"errorMessage\": {\n            \"type\": \"Required products must be an array\"\n          }\n        },\n        \"additionalProperties\": true\n      },\n      \"errorMessage\": {\n        \"type\": \"Condition must be an object\"\n      }\n    },\n    \"user_condition\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"groups\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": [\"string\", \"integer\"],\n            \"pattern\": \"^[0-9]*$\",\n            \"errorMessage\": {\n              \"type\": \"Group ID must be a string or number\",\n              \"pattern\": \"Group ID must be a valid number\"\n            }\n          },\n          \"errorMessage\": {\n            \"type\": \"Customer groups must be an array\"\n          }\n        },\n        \"emails\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"errorMessage\": {\n              \"type\": \"Email must be a string\"\n            }\n          },\n          \"errorMessage\": {\n            \"type\": \"Customer emails must be an array\"\n          }\n        },\n        \"purchased\": {\n          \"type\": \"number\",\n          \"errorMessage\": {\n            \"type\": \"Purchased amount must be a number\"\n          }\n        }\n      },\n      \"additionalProperties\": true,\n      \"errorMessage\": {\n        \"type\": \"User condition must be an object\"\n      }\n    },\n    \"max_uses_time_per_coupon\": {\n      \"type\": \"string\",\n      \"pattern\": \"^[0-9]*$\",\n      \"errorMessage\": {\n        \"type\": \"Max uses per coupon must be a string\",\n        \"pattern\": \"Max uses per coupon must be a valid number (e.g., 1, 10, 100)\"\n      }\n    },\n    \"max_uses_time_per_customer\": {\n      \"type\": \"string\",\n      \"pattern\": \"^[0-9]*$\",\n      \"errorMessage\": {\n        \"type\": \"Max uses per customer must be a string\",\n        \"pattern\": \"Max uses per customer must be a valid number (e.g., 1, 5, 10)\"\n      }\n    },\n    \"start_date\": {\n      \"anyOf\": [\n        { \"type\": \"string\", \"format\": \"date\" },\n        { \"type\": \"string\", \"maxLength\": 0 },\n        { \"type\": \"null\" }\n      ],\n      \"default\": null,\n      \"errorMessage\": {\n        \"anyOf\": \"Start date must be a valid date string, empty string, or null (e.g., 2025-01-01)\"\n      }\n    },\n    \"end_date\": {\n      \"anyOf\": [\n        { \"type\": \"string\", \"format\": \"date\" },\n        { \"type\": \"string\", \"maxLength\": 0 },\n        { \"type\": \"null\" }\n      ],\n      \"default\": null,\n      \"errorMessage\": {\n        \"anyOf\": \"End date must be a valid date string, empty string, or null (e.g., 2025-12-31)\"\n      }\n    }\n  },\n  \"buyx_gety\": {\n    \"type\": \"array\",\n    \"items\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"buy_qty\": {\n          \"type\": [\"string\", \"integer\"],\n          \"pattern\": \"^[0-9]*$\",\n          \"errorMessage\": {\n            \"type\": \"Buy quantity must be a string or number\",\n            \"pattern\": \"Buy quantity must be a valid whole number (e.g., 1, 2, 5)\"\n          }\n        },\n        \"get_qty\": {\n          \"type\": [\"string\", \"integer\"],\n          \"pattern\": \"^[0-9]*$\",\n          \"errorMessage\": {\n            \"type\": \"Get quantity must be a string or number\",\n            \"pattern\": \"Get quantity must be a valid whole number (e.g., 1, 2, 5)\"\n          }\n        },\n        \"max_y\": {\n          \"type\": [\"string\", \"integer\"],\n          \"pattern\": \"^[0-9]*$\",\n          \"errorMessage\": {\n            \"type\": \"Max Y must be a string or number\",\n            \"pattern\": \"Max Y must be a valid whole number (e.g., 1, 5, 10)\"\n          }\n        },\n        \"sku\": {\n          \"type\": \"string\",\n          \"minLength\": 1,\n          \"errorMessage\": {\n            \"type\": \"SKU must be a string\",\n            \"minLength\": \"SKU is required and cannot be empty\"\n          }\n        }\n      },\n      \"required\": [\"buy_qty\", \"get_qty\", \"max_y\", \"sku\"],\n      \"additionalProperties\": true,\n      \"errorMessage\": {\n        \"type\": \"Buy X Get Y item must be an object\",\n        \"required\": {\n          \"buy_qty\": \"Buy quantity is required\",\n          \"get_qty\": \"Get quantity is required\",\n          \"max_y\": \"Max Y is required\",\n          \"sku\": \"SKU is required\"\n        }\n      }\n    },\n    \"errorMessage\": {\n      \"type\": \"Buy X Get Y must be an array\"\n    }\n  },\n  \"start_date\": {\n    \"type\": \"string\",\n    \"format\": \"date\",\n    \"errorMessage\": {\n      \"type\": \"Start date must be a string\",\n      \"format\": \"Start date must be a valid date (e.g., 2025-01-01)\"\n    }\n  },\n  \"end_date\": {\n    \"type\": \"string\",\n    \"format\": \"date\",\n    \"errorMessage\": {\n      \"type\": \"End date must be a string\",\n      \"format\": \"End date must be a valid date (e.g., 2025-12-31)\"\n    }\n  },\n  \"additionalProperties\": true\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/coupon/createCoupon.js",
    "content": "import {\n  commit,\n  insert,\n  rollback,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport couponDataSchema from './couponDataSchema.json' with { type: 'json' };\n\nfunction validateCouponDataBeforeInsert(data) {\n  const ajv = getAjv();\n  couponDataSchema.required = [\n    'coupon',\n    'status',\n    'discount_amount',\n    'discount_type'\n  ];\n  const jsonSchema = getValueSync(\n    'createCouponDataJsonSchema',\n    couponDataSchema\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function insertCouponData(data, connection) {\n  const coupon = await insert('coupon').given(data).execute(connection);\n  return coupon;\n}\n\n/**\n * Create coupon service. This service will create a coupon with all related data\n * @param {Object} data\n * @param {Object} context\n */\nasync function createCoupon(data, context) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const couponData = await getValue('couponDataBeforeCreate', data);\n    // Validate coupon data\n    validateCouponDataBeforeInsert(couponData);\n\n    // Insert coupon data\n    const coupon = await hookable(insertCouponData, { ...context, connection })(\n      couponData,\n      connection\n    );\n    await commit(connection);\n    return coupon;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\nexport default async (data, context) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const coupon = await hookable(createCoupon, context)(data, context);\n  return coupon;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/coupon/deleteCoupon.js",
    "content": "import {\n  commit,\n  del,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\n\nasync function deleteCouponData(uuid, connection) {\n  await del('coupon').where('uuid', '=', uuid).execute(connection);\n}\n\n/**\n * Delete coupon service. This service will delete a coupon with all related data\n * @param {String} uuid\n * @param {Object} context\n */\nasync function deleteCoupon(uuid, context) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const query = select().from('coupon');\n    const coupon = await query.where('uuid', '=', uuid).load(connection);\n\n    if (!coupon) {\n      throw new Error('Invalid coupon id');\n    }\n    await hookable(deleteCouponData, { ...context, coupon, connection })(\n      uuid,\n      connection\n    );\n    await commit(connection);\n    return coupon;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\nexport default async (uuid, context) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const coupon = await hookable(deleteCoupon, context)(uuid, context);\n  return coupon;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/coupon/updateCoupon.js",
    "content": "import {\n  commit,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { hookable } from '../../../../lib/util/hookable.js';\nimport {\n  getValue,\n  getValueSync\n} from '../../../../lib/util/registry.js';\nimport { getAjv } from '../../../base/services/getAjv.js';\nimport couponDataSchema from './couponDataSchema.json' with { type: 'json' };\n\nfunction validateCouponDataBeforeInsert(data) {\n  const ajv = getAjv();\n  couponDataSchema.required = [];\n  const jsonSchema = getValueSync(\n    'updateCouponDataJsonSchema',\n    couponDataSchema\n  );\n  const validate = ajv.compile(jsonSchema);\n  const valid = validate(data);\n  if (valid) {\n    return data;\n  } else {\n    throw new Error(validate.errors[0].message);\n  }\n}\n\nasync function updateCouponData(uuid, data, connection) {\n  const coupon = await select()\n    .from('coupon')\n    .where('uuid', '=', uuid)\n    .load(connection);\n\n  if (!coupon) {\n    throw new Error('Requested coupon not found');\n  }\n\n  try {\n    const newCoupon = await update('coupon')\n      .given(data)\n      .where('uuid', '=', uuid)\n      .execute(connection);\n\n    return newCoupon;\n  } catch (e) {\n    if (!e.message.includes('No data was provided')) {\n      throw e;\n    } else {\n      return coupon;\n    }\n  }\n}\n\n/**\n * Update coupon service. This service will update a coupon with all related data\n * @param {Object} data\n * @param {Object} context\n */\nasync function updateCoupon(uuid, data, context) {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  try {\n    const couponData = await getValue('couponDataBeforeUpdate', data);\n    // Validate coupon data\n    validateCouponDataBeforeInsert(couponData);\n\n    // Insert coupon data\n    const coupon = await hookable(updateCouponData, { ...context, connection })(\n      uuid,\n      couponData,\n      connection\n    );\n\n    await commit(connection);\n    return coupon;\n  } catch (e) {\n    await rollback(connection);\n    throw e;\n  }\n}\n\nexport default async (uuid, data, context) => {\n  // Make sure the context is either not provided or is an object\n  if (context && typeof context !== 'object') {\n    throw new Error('Context must be an object');\n  }\n  const coupon = await hookable(updateCoupon, context)(uuid, data, context);\n  return coupon;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/couponValidator.js",
    "content": "import { info } from '../../../lib/log/logger.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\n/**\n * This method validate a coupon.\n * @param {Cart} cart\n * @param {String} couponCode\n * @returns {Boolean}\n */\nexport async function validateCoupon(cart, couponCode) {\n  const validatorFunctions = getValueSync('couponValidatorFunctions', []);\n  const couponLoader = getValueSync('couponLoaderFunction');\n  let flag = true;\n  const coupon = await couponLoader(couponCode);\n  if (!coupon) {\n    return false;\n  }\n  // Loop an object\n  await Promise.all(\n    validatorFunctions.map(async (func) => {\n      try {\n        const check = await func(cart, coupon);\n        if (!check) {\n          flag = false;\n        }\n      } catch (e) {\n        info(e);\n        flag = false;\n      }\n    })\n  );\n  return flag;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/discountCalculator.js",
    "content": "import { getValueSync } from '../../../lib/util/registry.js';\n\nexport async function calculateDiscount(cart, couponCode = null) {\n  const calculatorFunctions = getValueSync('discountCalculatorFunctions', []);\n  const couponLoader = getValueSync('couponLoaderFunction');\n  const coupon = await couponLoader(couponCode);\n  // Calling calculator functions\n  for (let i = 0; i < calculatorFunctions.length; i += 1) {\n    await calculatorFunctions[i](cart, coupon);\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/getCartTotalBeforeDiscount.js",
    "content": "/**\n * @param cart\n * @return float|int\n */\nexports.getCartTotalBeforeDiscount = function getCartTotalBeforeDiscount(cart) {\n  let total = 0;\n  const items = cart.getItems();\n  items.forEach((item) => {\n    total += item.getData('final_price') * item.getData('qty');\n  });\n\n  return total;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/getCouponsBaseQuery.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\n\nexport const getCouponsBaseQuery = () => {\n  const query = select().from('coupon');\n\n  return query;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/registerCartItemPromotionFields.js",
    "content": "import { getConfig } from '../../../lib/util/getConfig.js';\nimport { toPrice } from '../../checkout/services/toPrice.js';\n\nexport function registerCartItemPromotionFields(fields) {\n  const newFields = fields.concat(\n    /** Adding fields to the Cart Item object */\n    [\n      {\n        key: 'discount_amount',\n        resolvers: [\n          async function resolver() {\n            const requestedField = this.getTriggeredField();\n            const requestedValue = this.getRequestedValue();\n            if (requestedField === 'discount_amount') {\n              return toPrice(requestedValue);\n            } else {\n              return 0;\n            }\n          }\n        ]\n      },\n      {\n        key: 'line_total_with_discount',\n        resolvers: [\n          async function resolver() {\n            const priceIncludingTax = getConfig(\n              'pricing.tax.price_including_tax',\n              false\n            );\n            if (!priceIncludingTax) {\n              return (\n                this.getData('line_total') - this.getData('discount_amount')\n              );\n            } else {\n              return (\n                this.getData('line_total_incl_tax') -\n                this.getData('discount_amount') -\n                this.getData('tax_amount')\n              );\n            }\n          }\n        ],\n        dependencies: ['line_total', 'line_total_incl_tax', 'tax_amount']\n      },\n      {\n        key: 'line_total_with_discount_incl_tax',\n        resolvers: [\n          async function resolver() {\n            const priceIncludingTax = getConfig(\n              'pricing.tax.price_including_tax',\n              false\n            );\n            if (!priceIncludingTax) {\n              return toPrice(\n                this.getData('line_total') -\n                  this.getData('discount_amount') +\n                  this.getData('tax_amount')\n              );\n            } else {\n              return toPrice(\n                this.getData('line_total_incl_tax') -\n                  this.getData('discount_amount')\n              );\n            }\n          }\n        ],\n        dependencies: [\n          'line_total_incl_tax',\n          'line_total',\n          'qty',\n          'tax_amount',\n          'discount_amount'\n        ]\n      }\n    ]\n  );\n  return newFields;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/registerCartPromotionFields.js",
    "content": "import { getConfig } from '../../../lib/util/getConfig.js';\nimport { toPrice } from '../../checkout/services/toPrice.js';\nimport { validateCoupon } from './couponValidator.js';\nimport { calculateDiscount } from './discountCalculator.js';\n\nexport function registerCartPromotionFields(fields) {\n  const newFields = fields.concat(\n    /** Adding fields to the Cart object */\n    [\n      {\n        key: 'coupon',\n        resolvers: [\n          async function resolver(coupon) {\n            if (coupon) {\n              const check = await validateCoupon(this, coupon);\n              if (check === true) {\n                return coupon;\n              } else {\n                return null;\n              }\n            } else {\n              return null;\n            }\n          }\n        ],\n        dependencies: ['items'] // TODO: Add customer id and customer group id as a dependency\n      },\n      {\n        key: 'discount_amount',\n        resolvers: [\n          async function resolver() {\n            const coupon = this.getData('coupon');\n            const items = this.getItems();\n            if (!coupon) {\n              await Promise.all(\n                items.map(async (item) => {\n                  item.setData('discount_amount', 0);\n                })\n              );\n              return 0;\n            }\n            // Start calculate discount amount\n            await calculateDiscount(this, coupon);\n            let discountAmount = 0;\n\n            for (const item of items) {\n              discountAmount += item.getData('discount_amount');\n            }\n            return discountAmount;\n          }\n        ],\n        dependencies: ['coupon', 'sub_total', 'sub_total_incl_tax']\n      },\n      {\n        key: 'tax_amount',\n        resolvers: [\n          async function resolver(previousValue) {\n            return previousValue;\n          }\n        ],\n        dependencies: ['discount_amount']\n      },\n      {\n        key: 'sub_total_with_discount',\n        resolvers: [\n          async function resolver() {\n            const priceIncludingTax = getConfig(\n              'pricing.tax.price_including_tax',\n              false\n            );\n            if (!priceIncludingTax) {\n              return toPrice(\n                this.getData('sub_total') - this.getData('discount_amount')\n              );\n            } else {\n              return toPrice(\n                this.getData('sub_total_incl_tax') -\n                  this.getData('discount_amount') -\n                  this.getData('tax_amount')\n              );\n            }\n          }\n        ],\n        dependencies: [\n          'sub_total',\n          'sub_total_incl_tax',\n          'discount_amount',\n          'tax_amount'\n        ]\n      },\n      {\n        key: 'sub_total_with_discount_incl_tax',\n        resolvers: [\n          async function resolver() {\n            return toPrice(\n              this.getData('sub_total_with_discount') +\n                this.getData('tax_amount')\n            );\n          }\n        ],\n        dependencies: ['sub_total_with_discount', 'tax_amount']\n      },\n      {\n        key: 'shipping_fee_excl_tax', // This is to make sure the shipping fee is calculated after the coupon validation\n        resolvers: [],\n        dependencies: ['coupon']\n      },\n      {\n        key: 'grand_total',\n        resolvers: [\n          async function resolver() {\n            return toPrice(\n              this.getData('sub_total_with_discount_incl_tax') +\n                this.getData('shipping_fee_incl_tax')\n            );\n          }\n        ],\n        dependencies: [\n          'sub_total_with_discount_incl_tax',\n          'shipping_fee_incl_tax'\n        ]\n      }\n    ]\n  );\n  return newFields;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/registerDefaultCalculators.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { toPrice } from '../../checkout/services/toPrice.js';\n\nexport function registerDefaultCalculators() {\n  return [\n    async function percentageDiscountToEntireOrderCalculator(cart, coupon) {\n      const priceIncludingTax = getConfig(\n        'pricing.tax.price_including_tax',\n        false\n      );\n      if (coupon.discount_type !== 'percentage_discount_to_entire_order') {\n        return false;\n      }\n      const discountPercent = parseInt(coupon.discount_amount, 10);\n      if (discountPercent <= 0 || discountPercent > 100) {\n        return false;\n      }\n\n      const cartSubTotal = priceIncludingTax\n        ? cart.getData('sub_total_incl_tax')\n        : cart.getData('sub_total');\n      const cartDiscountAmount = toPrice(\n        (discountPercent * cartSubTotal) / 100\n      );\n      let distributedAmount = 0;\n      const discounts = {};\n      const items = cart.getItems();\n      items.forEach((item, index) => {\n        let sharedDiscount = 0;\n        if (index === items.length - 1) {\n          const precision = getConfig('pricing.precision', '2');\n          const precisionFix = 10 ** precision;\n          sharedDiscount =\n            (cartDiscountAmount * precisionFix -\n              distributedAmount * precisionFix) /\n            precisionFix;\n          // Fix for rounding error\n          sharedDiscount = parseFloat(sharedDiscount.toFixed(precision));\n        } else {\n          const lineTotal = priceIncludingTax\n            ? item.getData('line_total_incl_tax')\n            : item.getData('line_total');\n          sharedDiscount = toPrice(\n            (lineTotal * cartDiscountAmount) / cartSubTotal,\n            0\n          );\n        }\n        if (\n          discounts[item.getId()] ||\n          discounts[item.getId()] !== sharedDiscount\n        ) {\n          discounts[item.getId()] = sharedDiscount;\n        }\n        distributedAmount += sharedDiscount;\n      });\n      await Promise.all(\n        items.map(async (item) => {\n          await item.setData('discount_amount', discounts[item.getId()] || 0);\n        })\n      );\n\n      return true;\n    },\n    async function fixedDiscountToEntireOrderCalculator(cart, coupon) {\n      const priceIncludingTax = getConfig(\n        'pricing.tax.price_including_tax',\n        false\n      );\n      if (coupon.discount_type !== 'fixed_discount_to_entire_order')\n        return false;\n\n      let cartDiscountAmount = toPrice(parseFloat(coupon.discount_amount) || 0);\n      if (cartDiscountAmount < 0) {\n        return false;\n      }\n      const cartSubTotal = priceIncludingTax\n        ? cart.getData('sub_total_incl_tax')\n        : cart.getData('sub_total');\n      cartDiscountAmount =\n        cartSubTotal > cartDiscountAmount ? cartDiscountAmount : cartSubTotal;\n      let distributedAmount = 0;\n      const discounts = {};\n      const items = cart.getItems();\n      items.forEach((item, index) => {\n        let sharedDiscount = 0;\n        if (index === items.length - 1) {\n          const precision = getConfig('pricing.precision', '2');\n          const precisionFix = parseInt(`1${'0'.repeat(precision)}`, 10);\n          sharedDiscount =\n            (cartDiscountAmount * precisionFix -\n              distributedAmount * precisionFix) /\n            precisionFix;\n          // Fix for rounding error\n          sharedDiscount = parseFloat(sharedDiscount.toFixed(precision));\n        } else {\n          const lineTotal = priceIncludingTax\n            ? item.getData('line_total_incl_tax')\n            : item.getData('line_total');\n          sharedDiscount = toPrice(\n            (lineTotal * cartDiscountAmount) / cartSubTotal,\n            0\n          );\n        }\n        if (\n          !discounts[item.getId()] ||\n          discounts[item.getId()] !== sharedDiscount\n        ) {\n          discounts[item.getId()] = sharedDiscount;\n        }\n        distributedAmount += sharedDiscount;\n      });\n      await Promise.all(\n        items.map(async (item) => {\n          await item.setData('discount_amount', discounts[item.getId()] || 0);\n        })\n      );\n      return true;\n    },\n    async function discountToSpecificProductsCalculator(cart, coupon) {\n      const priceIncludingTax = getConfig(\n        'pricing.tax.price_including_tax',\n        false\n      );\n      if (\n        ![\n          'fixed_discount_to_specific_products',\n          'percentage_discount_to_specific_products'\n        ].includes(coupon.discount_type)\n      ) {\n        return false;\n      }\n      const targetConfig = coupon.target_products;\n\n      const maxQty = parseInt(targetConfig.maxQty, 10) || 0;\n      if (maxQty <= 0) {\n        return false;\n      }\n      const targetProducts = targetConfig.products || [];\n      let discountAmount = toPrice(parseFloat(coupon.discount_amount) || 0);\n      const discounts = {};\n      const items = cart.getItems();\n      // Get collections of all products\n      const collections = await select()\n        .from('product_collection')\n        .where(\n          'product_id',\n          'IN',\n          items.map((item) => item.getData('product_id'))\n        )\n        .execute(pool);\n\n      items.forEach((item) => {\n        // Check if the item is in the target products\n        let flag = true;\n        targetProducts.forEach((targetProduct) => {\n          if (flag === false) {\n            return;\n          }\n          const { key } = targetProduct;\n          let { operator } = targetProduct;\n          const { value } = targetProduct;\n          // Check attribute group based items\n          if (key === 'attribute_group') {\n            // If key is attribute group, we only support IN and NOT IN operator\n            if (!['IN', 'NOT IN'].includes(operator) || !Array.isArray(value)) {\n              flag = false;\n              return false;\n            }\n            const attributeGroupIds = value.map((id) =>\n              parseInt(id.trim(), 10)\n            );\n            flag =\n              operator === 'IN'\n                ? attributeGroupIds.includes(item.getData('group_id'))\n                : !attributeGroupIds.includes(item.getData('group_id'));\n          }\n\n          // Check category based items\n          if (key === 'category') {\n            const productCategoryId = item.getData('category_id');\n            // If key is category, we only support IN and NOT IN operator\n            if (!['IN', 'NOT IN'].includes(operator) || !Array.isArray(value)) {\n              flag = false;\n              return false;\n            }\n\n            const requiredCategoryIds = value.map((id) =>\n              parseInt(id.trim(), 10)\n            );\n            if (operator === 'IN') {\n              flag = requiredCategoryIds.includes(productCategoryId);\n            } else {\n              flag = !requiredCategoryIds.includes(productCategoryId);\n            }\n          }\n          // Check collection based items\n          if (key === 'collection') {\n            // If key is category, we only support IN and NOT IN operator\n            if (!['IN', 'NOT IN'].includes(operator) || !Array.isArray(value)) {\n              flag = false;\n              return false;\n            }\n\n            const requiredCollectionIDs = value.map((id) =>\n              parseInt(id.trim(), 10)\n            );\n            if (operator === 'IN') {\n              flag = collections.some(\n                (collection) =>\n                  requiredCollectionIDs.includes(collection.collection_id) &&\n                  collection.product_id === item.getData('product_id')\n              );\n            } else {\n              flag = !collections.some(\n                (collection) =>\n                  requiredCollectionIDs.includes(collection.collection_id) &&\n                  collection.product_id === item.getData('product_id')\n              );\n            }\n          }\n          // Check price based items\n          if (key === 'price') {\n            // If key is price, we do not support IN and NOT IN operator\n            if (['=', '!=', '>', '>=', '<', '<='].includes(operator)) {\n              const price = parseFloat(value);\n              if (operator === '=') {\n                operator = '===';\n              }\n              if (!price) {\n                flag = false;\n                return false;\n              } else {\n                flag = eval(\n                  `${item.getData('final_price')} ${operator} ${price}`\n                );\n              }\n            } else {\n              // For 'price' type of condition, we do not others operators\n              flag = false;\n              return false;\n            }\n          }\n\n          // Check sku based items\n          if (key === 'sku') {\n            if (['IN', 'NOT IN'].includes(operator) && Array.isArray(value)) {\n              const skus = value.map((v) => v.trim());\n              flag =\n                operator === 'IN'\n                  ? skus.includes(item.getData('product_sku'))\n                  : !skus.includes(item.getData('product_sku'));\n            } else {\n              // For 'sku' type of condition, we only support 'IN', 'NOT IN' operators\n              flag = false;\n              return false;\n            }\n          }\n        });\n\n        // If cart item does not match the target products, we do not apply the discount\n        if (flag === false) {\n          return;\n        }\n        if (coupon.discount_type === 'fixed_discount_to_specific_products') {\n          discountAmount = Math.min(\n            discountAmount,\n            priceIncludingTax\n              ? item.getData('final_price_incl_tax')\n              : item.getData('final_price')\n          );\n          discounts[item.getId()] =\n            Math.min(item.getData('qty'), maxQty) * discountAmount;\n        } else {\n          const discountPercent = Math.min(discountAmount, 100);\n          discounts[item.getId()] = toPrice(\n            ((discountPercent *\n              (priceIncludingTax\n                ? item.getData('final_price_incl_tax')\n                : item.getData('final_price'))) /\n              100) *\n              Math.min(item.getData('qty'), maxQty)\n          );\n        }\n      });\n\n      await Promise.all(\n        items.map(async (item) => {\n          await item.setData('discount_amount', discounts[item.getId()] || 0);\n        })\n      );\n      return true;\n    },\n    async function buyXGetYCalculator(cart, coupon) {\n      const priceIncludingTax = getConfig(\n        'pricing.tax.price_including_tax',\n        false\n      );\n      if (coupon.discount_type !== 'buy_x_get_y') {\n        return true;\n      }\n      const configs = coupon.buyx_gety;\n      const items = cart.getItems();\n      const discounts = {};\n      configs.forEach((row) => {\n        const sku = row.sku ?? null;\n        const buyQty = parseInt(row.buy_qty, 10) || null;\n        const getQty = parseInt(row.get_qty, 10) || null;\n        const maxY = row.max_y.trim()\n          ? Math.max(parseInt(row.max_y, 10), 0)\n          : 10000000;\n        const discount = row.discount ? parseFloat(row.discount) || 0 : 100;\n        if (!sku || !buyQty || !getQty || discount <= 0 || discount > 100) {\n          return;\n        }\n\n        for (let i = 0; i < items.length; i += 1) {\n          const item = items[i];\n          if (\n            item.getData('product_sku') === sku.trim() &&\n            item.getData('qty') >= buyQty + getQty\n          ) {\n            const discountPerUnit = toPrice(\n              (discount *\n                (priceIncludingTax\n                  ? item.getData('final_price_incl_tax')\n                  : item.getData('final_price'))) /\n                100\n            );\n            const discountAbleUnits =\n              Math.floor(item.getData('qty') / buyQty) * getQty;\n            let discountAmount;\n            if (discountAbleUnits < maxY) {\n              discountAmount = toPrice(discountAbleUnits * discountPerUnit);\n            } else {\n              discountAmount = toPrice(discountPerUnit * maxY);\n            }\n\n            if (\n              discounts[item.getId()] ||\n              discounts[item.getId()] !== discountAmount\n            ) {\n              discounts[item.getId()] = discountAmount;\n            }\n          }\n        }\n      });\n\n      await Promise.all(\n        items.map(async (item) => {\n          await item.setData('discount_amount', discounts[item.getId()] || 0);\n        })\n      );\n    }\n  ];\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/registerDefaultCouponCollectionFilters.js",
    "content": "import { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport async function registerDefaultCouponCollectionFilters() {\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'coupon',\n      operation: ['eq', 'like'],\n      callback: (query, operation, value, currentFilters) => {\n        if (operation === 'eq') {\n          query.andWhere('coupon.coupon', '=', value);\n        } else {\n          query.andWhere('coupon.coupon', 'ILIKE', `%${value}%`);\n        }\n        currentFilters.push({\n          key: 'name',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'status',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere('coupon.status', OPERATION_MAP[operation], value);\n        currentFilters.push({\n          key: 'status',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'free_shipping',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere('coupon.free_shipping', OPERATION_MAP[operation], value);\n        currentFilters.push({\n          key: 'free_shipping',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const couponCollectionSortBy = getValueSync('couponCollectionSortBy', {\n          coupon: (query) => query.orderBy('coupon.coupon'),\n          status: (query) => query.orderBy('coupon.status'),\n          used_time: (query) => query.orderBy('coupon.used_time')\n        });\n\n        if (couponCollectionSortBy[value]) {\n          couponCollectionSortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        }\n      }\n    }\n  ];\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/promotion/services/registerDefaultValidators.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { DateTime } from 'luxon';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\n\nexport function registerDefaultValidators() {\n  return [\n    function generalValidator(cart, coupon) {\n      if (coupon.status !== true) {\n        return false;\n      }\n      const discountAmount = parseFloat(coupon.discount_amount);\n      if (\n        (!discountAmount || discountAmount) <= 0 &&\n        coupon.discount_type !== 'buy_x_get_y'\n      ) {\n        return false;\n      }\n      const today = DateTime.local().setZone('UTC');\n      const startDate = coupon.start_date\n        ? DateTime.fromJSDate(coupon.start_date, { zone: 'UTC' })\n        : today.minus({ days: 1 });\n      const endDate = coupon.end_date\n        ? DateTime.fromJSDate(coupon.end_date, { zone: 'UTC' })\n        : today.plus({ days: 1 });\n\n      if (startDate < today && endDate > today) {\n        return true;\n      } else {\n        return false;\n      }\n    },\n    async function timeUsedValidator(cart, coupon) {\n      if (\n        coupon.max_uses_time_per_coupon &&\n        parseInt(coupon.used_time, 10) >=\n          parseInt(coupon.max_uses_time_per_coupon, 10)\n      ) {\n        return false;\n      }\n      if (coupon.max_uses_time_per_customer) {\n        const customerId = cart.getData('customer_id');\n        if (customerId) {\n          const flag = await select()\n            .from('customer_coupon_use')\n            .where('customer_id', '=', customerId)\n            .andWhere('coupon', '=', coupon.coupon)\n            .andWhere(\n              'used_time',\n              '>=',\n              parseInt(coupon.max_uses_time_per_customer, 10)\n            )\n            .execute(pool);\n          if (flag) {\n            return false;\n          }\n        }\n      }\n\n      return true;\n    },\n    function customerGroupValidator(cart, coupon) {\n      const conditions = coupon.condition;\n      const userConditions = coupon.user_condition;\n      if (userConditions.group && !userConditions.customer_group.includes(0)) {\n        const customerGroupId = cart.getData('customer_group_id');\n        if (!conditions.customer_group.includes(customerGroupId)) {\n          return false;\n        }\n      }\n      return true;\n    },\n    function subTotalValidator(cart, coupon) {\n      const priceIncludingTax = getConfig(\n        'pricing.tax.price_including_tax',\n        false\n      );\n      const conditions = coupon.condition;\n      const minimumSubTotal = !Number.isNaN(parseFloat(conditions.order_total))\n        ? parseFloat(conditions.order_total)\n        : null;\n      if (minimumSubTotal === null) {\n        return true;\n      }\n      let check = false;\n      if (priceIncludingTax) {\n        check = cart.getData('sub_total_incl_tax') >= minimumSubTotal;\n      } else {\n        check = cart.getData('sub_total') >= minimumSubTotal;\n      }\n      return check;\n    },\n    function minimumQtyValidator(cart, coupon) {\n      const conditions = coupon.condition;\n      const minimumQty = !Number.isNaN(parseInt(conditions.order_qty, 10))\n        ? parseInt(conditions.order_qty, 10)\n        : null;\n      if (minimumQty && cart.getData('total_qty') < minimumQty) {\n        return false;\n      }\n      return true;\n    },\n    async function requiredProductByCategoryValidator(cart, coupon) {\n      let flag = true;\n      const items = cart.getItems();\n      const conditions = coupon.condition;\n      const requiredProducts = conditions.required_products || [];\n      if (requiredProducts.length === 0) {\n        return true;\n      }\n      for (let index = 0; index < requiredProducts.length; index += 1) {\n        const condition = requiredProducts[index];\n        const { operator } = condition;\n        const { value } = condition;\n        const minQty = parseInt(condition.qty, 10) || 1;\n        let qty = 0;\n        // Continue to next item if key is not category\n        if (condition.key !== 'category') {\n          continue;\n        } else if (\n          ['IN', 'NOT IN'].includes(operator) &&\n          Array.isArray(value)\n        ) {\n          const requiredCategoryIds = value.map((v) => parseInt(v.trim(), 10));\n          if (operator === 'IN') {\n            items.forEach((item) => {\n              const categoryId = item.getData('category_id');\n              if (requiredCategoryIds.includes(categoryId)) {\n                qty += item.getData('qty');\n              }\n            });\n          } else {\n            items.forEach((item) => {\n              const categoryId = item.getData('category_id');\n              if (!requiredCategoryIds.includes(categoryId)) {\n                qty += item.getData('qty');\n              }\n            });\n          }\n        } else {\n          // For category based condition, we only support 'IN' and 'NOT IN' operators\n          flag = false;\n          return false;\n        }\n        if (qty < minQty) {\n          flag = false;\n        }\n      }\n      return flag;\n    },\n    async function requiredProductByCollectionValidator(cart, coupon) {\n      let flag = true;\n      const items = cart.getItems();\n      const conditions = coupon.condition;\n      const requiredProducts = conditions.required_products || [];\n      if (requiredProducts.length === 0) {\n        return true;\n      }\n      let collections;\n      for (let index = 0; index < requiredProducts.length; index += 1) {\n        const condition = requiredProducts[index];\n        const { operator } = condition;\n        const { value } = condition;\n\n        const minQty = parseInt(condition.qty, 10) || 1;\n        let qty = 0;\n        // Continue to next item if key is not collection based requirement\n        if (condition.key !== 'collection') {\n          continue;\n        } else if (\n          ['IN', 'NOT IN'].includes(operator) &&\n          Array.isArray(value)\n        ) {\n          const requiredCollectionIDs = value.map((v) =>\n            parseInt(v.trim(), 10)\n          );\n          const productIds = items.map((item) => item.getData('product_id'));\n          // Load the collections of all item\n          if (!collections) {\n            collections = await select()\n              .from('product_collection')\n              .where('product_id', 'IN', productIds)\n              .execute(pool);\n          }\n          if (operator === 'IN') {\n            items.forEach((item) => {\n              const productId = item.getData('product_id');\n              const collectionIDs = collections.filter(\n                (collection) =>\n                  parseInt(collection.product_id, 10) === productId &&\n                  requiredCollectionIDs.includes(\n                    parseInt(collection.collection_id, 10)\n                  )\n              );\n              if (collectionIDs.length > 0) {\n                qty += item.getData('qty');\n              }\n            });\n          } else {\n            items.forEach((item) => {\n              const productId = item.getData('product_id');\n              const collectionIDs = collections.filter(\n                (collection) =>\n                  parseInt(collection.product_id, 10) === productId &&\n                  requiredCollectionIDs.includes(\n                    parseInt(collection.collection_id, 10)\n                  )\n              );\n              if (collectionIDs.length === 0) {\n                qty += item.getData('qty');\n              }\n            });\n          }\n        } else {\n          // For collection based condition, we only support 'IN' and 'NOT IN' operators\n          flag = false;\n          return false;\n        }\n        if (qty < minQty) {\n          flag = false;\n        }\n      }\n      return flag;\n    },\n    async function requiredProductByAttributeGroupValidator(cart, coupon) {\n      let flag = true;\n      const items = cart.getItems();\n      const conditions = coupon.condition;\n\n      const requiredProducts = conditions.required_products || [];\n      if (requiredProducts.length === 0) {\n        return true;\n      }\n      for (let index = 0; index < requiredProducts.length; index += 1) {\n        const condition = requiredProducts[index];\n        const { operator } = condition;\n        let { value } = condition;\n        const minQty = parseInt(condition.qty, 10) || 1;\n        let qty = 0;\n        // Continue to next item if key is not attribute_group\n        if (condition.key !== 'attribute_group') {\n          continue;\n        } else if (\n          ['IN', 'NOT IN'].includes(operator) &&\n          Array.isArray(value)\n        ) {\n          value = value.map((v) => parseInt(v.trim(), 10));\n          if (operator === 'IN') {\n            items.forEach((item) => {\n              if (value.includes(item.getData('group_id'))) {\n                qty += item.getData('qty');\n              }\n            });\n          } else {\n            items.forEach((item) => {\n              if (!value.includes(item.getData('group_id'))) {\n                qty += item.getData('qty');\n              }\n            });\n          }\n        } else {\n          // For 'attribute group' type of condition, we only support 'IN' and 'NOT IN' operators\n          flag = false;\n          return false;\n        }\n        if (qty < minQty) {\n          flag = false;\n        }\n      }\n      return flag;\n    },\n    async function requiredProductByPriceValidator(cart, coupon) {\n      const priceIncludingTax = getConfig(\n        'pricing.tax.price_including_tax',\n        false\n      );\n      let flag = true;\n      const items = cart.getItems();\n      const conditions = coupon.condition;\n      const requiredProducts = conditions.required_products || [];\n      if (requiredProducts.length === 0) {\n        return true;\n      }\n      for (let index = 0; index < requiredProducts.length; index += 1) {\n        const condition = requiredProducts[index];\n        let { operator } = condition;\n        const value = parseFloat(condition.value);\n        const minQty = parseInt(condition.qty, 10) || 1;\n        let qty = 0;\n        if (Number.isNaN(value) || value === null || value < 0) {\n          flag = false;\n          break;\n        }\n        // Continue to next item if key is not price\n        if (condition.key !== 'price') {\n          continue;\n        } else if (['=', '!=', '>', '>=', '<', '<='].includes(operator)) {\n          if (operator === '=') {\n            operator = '===';\n          }\n          items.forEach((item) => {\n            const price = priceIncludingTax\n              ? item.getData('final_price_incl_tax')\n              : item.getData('final_price');\n\n            if (eval(`${price} ${operator} ${value}`)) {\n              qty += item.getData('qty');\n            }\n          });\n        } else {\n          // For 'price' type of condition, we do not others operators\n          flag = false;\n          return false;\n        }\n        if (qty < minQty) {\n          flag = false;\n        }\n      }\n      return flag;\n    },\n    async function requiredProductBySkuValidator(cart, coupon) {\n      let flag = true;\n      const items = cart.getItems();\n      const conditions = coupon.condition;\n      const requiredProducts = conditions.required_products || [];\n      if (requiredProducts.length === 0) {\n        return true;\n      }\n      for (let index = 0; index < requiredProducts.length; index += 1) {\n        const condition = requiredProducts[index];\n        const { operator } = condition;\n        let { value } = condition;\n        const minQty = parseInt(condition.qty, 10) || 1;\n        let qty = 0;\n        // Continue to next item if key is not attribute_group\n        if (condition.key !== 'sku') {\n          continue;\n        } else if (\n          ['IN', 'NOT IN'].includes(operator) &&\n          Array.isArray(value)\n        ) {\n          value = value.map((v) => v.trim());\n          if (operator === 'IN') {\n            items.forEach((item) => {\n              if (value.includes(item.getData('product_sku'))) {\n                qty += item.getData('qty');\n              }\n            });\n          } else {\n            items.forEach((item) => {\n              if (!value.includes(item.getData('product_sku'))) {\n                qty += item.getData('qty');\n              }\n            });\n          }\n        } else {\n          // For 'attribute group' type of condition, we only support 'IN' and 'NOT IN' operators\n          flag = false;\n          return false;\n        }\n        if (qty < minQty) {\n          flag = false;\n        }\n      }\n\n      return flag;\n    },\n    async function customerGroupValidator() {\n      return true;\n    },\n    async function customerEmailValidator(cart, coupon) {\n      const conditions = coupon.user_condition;\n      const allowEmails = conditions.emails || [];\n\n      // No email means all emails\n      if (allowEmails.length === 0) {\n        return true;\n      }\n      // When emails is set, no guest are allowed\n      if (!cart.getData('customer_id')) {\n        return false;\n      }\n\n      if (!allowEmails.includes(cart.getData('customer_email'))) {\n        return false;\n      }\n\n      return true;\n    },\n    async function customerPurchasesAmountValidator(cart, coupon) {\n      const conditions = coupon.user_condition;\n      const purchasedAmount = conditions.purchased\n        ? parseFloat(conditions.purchased.trim()) || null\n        : null;\n\n      // Null means no condition\n      if (purchasedAmount === null) {\n        return true;\n      }\n      // When purchased amount is set, no guest are allowed\n      if (!cart.getData('customer_id')) {\n        return false;\n      }\n\n      const query = select();\n      query\n        .from('order')\n        .select('SUM(grand_total)', 'total')\n        .where('customer_id', '=', cart.getData('customer_id'))\n        .andWhere('payment_status', '=', 'paid');\n      const grandTotal = await query.load(pool);\n      if (grandTotal.total < purchasedAmount) {\n        return false;\n      }\n      return true;\n    }\n  ];\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/api/saveSetting/[context]bodyParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/api/saveSetting/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/settings\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/api/saveSetting/saveSetting.js",
    "content": "import {\n  commit,\n  insertOnUpdate,\n  rollback\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\nimport { refreshSetting } from '../../services/setting.js';\n\nexport default async (request, response, next) => {\n  const { body } = request;\n  const connection = await getConnection();\n  try {\n    // Loop through the body and insert the data\n    const promises = [];\n    Object.keys(body).forEach((key) => {\n      const value = body[key];\n      // Check if the value is a object or array\n      if (typeof value === 'object') {\n        promises.push(\n          insertOnUpdate('setting', ['name'])\n            .given({\n              name: key,\n              value: JSON.stringify(value),\n              is_json: 1\n            })\n            .execute(connection, false)\n        );\n      } else {\n        promises.push(\n          insertOnUpdate('setting', ['name'])\n            .given({\n              name: key,\n              value,\n              is_json: 0\n            })\n            .execute(connection, false)\n        );\n      }\n    });\n    await Promise.all(promises);\n    await commit(connection);\n    // Refresh the setting\n    await refreshSetting();\n    response.status(OK);\n    response.json({\n      data: {}\n    });\n  } catch (error) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: error.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/graphql/types/Setting/Setting.graphql",
    "content": "\"\"\"\nSingle store setting\n\"\"\"\ntype Setting {\n  storeName: String\n}\n\nextend type Query {\n  setting: Setting\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/graphql/types/Setting/Setting.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\n\nexport default {\n  Query: {\n    setting: async (root, _, { pool }) => {\n      const setting = await select().from('setting').execute(pool);\n      return setting;\n    }\n  },\n  Setting: {\n    storeName: (setting) => {\n      const storeName = setting.find((s) => s.name === 'storeName');\n      if (storeName) {\n        return storeName.value;\n      } else {\n        return 'EverShop Store';\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/graphql/types/ShippingSetting/ShippingSetting.graphql",
    "content": "extend type Setting {\n  allowedCountries: [String]\n  weightUnit: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/graphql/types/ShippingSetting/ShippingSetting.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Setting: {\n    allowedCountries: (setting) => {\n      const allowedCountries = setting.find(\n        (s) => s.name === 'allowedCountries'\n      );\n      if (allowedCountries && allowedCountries.value) {\n        return JSON.parse(allowedCountries.value);\n      } else {\n        return ['US'];\n      }\n    },\n    weightUnit: () => getConfig('shop.weightUnit', 'kg')\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/graphql/types/StoreSetting/StoreSetting.graphql",
    "content": "extend type Setting {\n  storeName: String\n  storeDescription: String\n  storeLanguage: String\n  storeCurrency: String\n  storeTimeZone: String\n  storePhoneNumber: String\n  storeEmail: String\n  storeCountry: String\n  storeAddress: String\n  storeCity: String\n  storeProvince: String\n  storePostalCode: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/graphql/types/StoreSetting/StoreSetting.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Setting: {\n    storeName: (setting) => {\n      const storeName = setting.find((s) => s.name === 'storeName');\n      if (storeName) {\n        return storeName.value;\n      } else {\n        return 'An Amazing EverShop Store';\n      }\n    },\n    storeDescription: (setting) => {\n      const storeDescription = setting.find(\n        (s) => s.name === 'storeDescription'\n      );\n      if (storeDescription) {\n        return storeDescription.value;\n      } else {\n        return 'An Amazing EverShop Store';\n      }\n    },\n    storeLanguage: () => getConfig('shop.language', 'en'),\n    storeCurrency: () => getConfig('shop.currency', 'USD'),\n    storeTimeZone: (setting) => {\n      const storeTimeZone = setting.find((s) => s.name === 'storeTimeZone');\n      if (storeTimeZone) {\n        return storeTimeZone.value;\n      } else {\n        return 'America/New_York';\n      }\n    },\n    storePhoneNumber: (setting) => {\n      const storePhoneNumber = setting.find(\n        (s) => s.name === 'storePhoneNumber'\n      );\n      if (storePhoneNumber) {\n        return storePhoneNumber.value;\n      } else {\n        return null;\n      }\n    },\n    storeEmail: (setting) => {\n      const storeEmail = setting.find((s) => s.name === 'storeEmail');\n      if (storeEmail) {\n        return storeEmail.value;\n      } else {\n        return null;\n      }\n    },\n    storeCountry: (setting) => {\n      const storeCountry = setting.find((s) => s.name === 'storeCountry');\n      if (storeCountry) {\n        return storeCountry.value;\n      } else {\n        return 'US';\n      }\n    },\n    storeAddress: (setting) => {\n      const storeAddress = setting.find((s) => s.name === 'storeAddress');\n      if (storeAddress) {\n        return storeAddress.value;\n      } else {\n        return null;\n      }\n    },\n    storeCity: (setting) => {\n      const storeCity = setting.find((s) => s.name === 'storeCity');\n      if (storeCity) {\n        return storeCity.value;\n      } else {\n        return null;\n      }\n    },\n    storeProvince: (setting) => {\n      const storeProvince = setting.find((s) => s.name === 'storeProvince');\n      if (storeProvince) {\n        return storeProvince.value;\n      } else {\n        return null;\n      }\n    },\n    storePostalCode: (setting) => {\n      const storePostalCode = setting.find((s) => s.name === 'storePostalCode');\n      if (storePostalCode) {\n        return storePostalCode.value;\n      } else {\n        return null;\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/migration/Version-1.0.0.js",
    "content": "import { execute } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE \"setting\" (\n  \"setting_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"name\" varchar NOT NULL,\n  \"value\" text DEFAULT NULL,\n  \"is_json\" boolean NOT NULL DEFAULT FALSE,\n  CONSTRAINT \"SETTING_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"SETTING_NAME_UNIQUE\" UNIQUE (\"name\")\n)`\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/pages/admin/all/PaymentSettingMenu.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { Cog, Settings } from 'lucide-react';\nimport React from 'react';\n\ninterface PaymentSettingMenuProps {\n  paymentSettingUrl: string;\n}\n\nexport default function PaymentSettingMenu({\n  paymentSettingUrl\n}: PaymentSettingMenuProps) {\n  const isActive =\n    typeof window !== 'undefined' &&\n    new URL(paymentSettingUrl, window.location.origin).pathname ===\n      window.location.pathname;\n\n  return (\n    <Item\n      variant={'outline'}\n      className={cn(\n        isActive && 'bg-primary/5 border-primary/20 dark:bg-primary/10'\n      )}\n      data-active={isActive ? 'true' : 'false'}\n    >\n      <ItemContent>\n        <ItemTitle>\n          <div>\n            <a\n              href={paymentSettingUrl}\n              className={cn(\n                'uppercase text-xs font-semibold',\n                isActive && 'text-primary'\n              )}\n            >\n              Payment Setting\n            </a>\n          </div>\n        </ItemTitle>\n        <ItemDescription>\n          <div>Configure the available payment methods</div>\n        </ItemDescription>\n      </ItemContent>\n      <ItemActions>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          onClick={() => (window.location.href = paymentSettingUrl)}\n        >\n          <Settings className=\"h-4 w-4 mr-1\" />\n        </Button>\n      </ItemActions>\n    </Item>\n  );\n}\n\nexport const layout = {\n  areaId: 'settingPageMenu',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    paymentSettingUrl: url(routeId: \"paymentSetting\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/pages/admin/all/SettingMenuGroup.tsx",
    "content": "import { NavigationItemGroup } from '@components/admin/NavigationItemGroup.js';\nimport { Settings } from 'lucide-react';\nimport React from 'react';\n\ninterface CmsMenuGroupProps {\n  storeSetting: string;\n}\n\nexport default function CmsMenuGroup({ storeSetting }: CmsMenuGroupProps) {\n  return (\n    <NavigationItemGroup\n      id=\"settingMenuGroup\"\n      name=\"Setting\"\n      Icon={() => <Settings width={15} height={15} />}\n      url={storeSetting}\n    />\n  );\n}\n\nexport const layout = {\n  areaId: 'adminMenu',\n  sortOrder: 500\n};\n\nexport const query = `\n  query Query {\n    storeSetting: url(routeId:\"storeSetting\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/pages/admin/all/StoreSettingMenu.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { Settings } from 'lucide-react';\nimport React from 'react';\n\ninterface StoreSettingMenuProps {\n  storeSettingUrl: string;\n}\n\nexport default function StoreSettingMenu({\n  storeSettingUrl\n}: StoreSettingMenuProps) {\n  const isActive =\n    typeof window !== 'undefined' &&\n    new URL(storeSettingUrl, window.location.origin).pathname ===\n      window.location.pathname;\n\n  return (\n    <Item\n      variant={'outline'}\n      className={cn(\n        isActive && 'bg-primary/5 border-primary/20 dark:bg-primary/10'\n      )}\n      data-active={isActive ? 'true' : 'false'}\n    >\n      <ItemContent>\n        <ItemTitle>\n          <div>\n            <a\n              href={storeSettingUrl}\n              className={cn(\n                'uppercase text-xs font-semibold',\n                isActive && 'text-primary'\n              )}\n            >\n              Store Setting\n            </a>\n          </div>\n        </ItemTitle>\n        <ItemDescription>\n          <div>Configure your store information</div>\n        </ItemDescription>\n      </ItemContent>\n      <ItemActions>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          onClick={() => (window.location.href = storeSettingUrl)}\n        >\n          <Settings className=\"h-4 w-4 mr-1\" />\n        </Button>\n      </ItemActions>\n    </Item>\n  );\n}\n\nexport const layout = {\n  areaId: 'settingPageMenu',\n  sortOrder: 5\n};\n\nexport const query = `\n  query Query {\n    storeSettingUrl: url(routeId: \"storeSetting\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/pages/admin/paymentSetting/PaymentSetting.tsx",
    "content": "import { SettingMenu } from '@components/admin/SettingMenu.js';\nimport Area from '@components/common/Area.js';\nimport { Form } from '@components/common/form/Form.js';\nimport React from 'react';\n\ninterface PaymentSettingProps {\n  saveSettingApi: string;\n}\n\nexport default function PaymentSetting({\n  saveSettingApi\n}: PaymentSettingProps) {\n  return (\n    <div className=\"main-content-inner\">\n      <div className=\"grid grid-cols-6 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2\">\n          <SettingMenu />\n        </div>\n        <div className=\"col-span-4\">\n          <Form\n            id=\"paymentSettingForm\"\n            method=\"POST\"\n            action={saveSettingApi}\n            successMessage=\"Payment setting saved\"\n          >\n            <Area id=\"paymentSetting\" className=\"grid gap-5\" />\n          </Form>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    saveSettingApi: url(routeId: \"saveSetting\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/pages/admin/paymentSetting/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request) => {\n  setPageMetaInfo(request, {\n    title: 'Payment Setting',\n    description: 'Payment Setting'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/pages/admin/paymentSetting/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/setting/payments\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/pages/admin/storeSetting/StoreSetting.tsx",
    "content": "import { SettingMenu } from '@components/admin/SettingMenu.js';\nimport Spinner from '@components/admin/Spinner.js';\nimport Area from '@components/common/Area.js';\nimport { EmailField } from '@components/common/form/EmailField.js';\nimport { Form, useFormContext } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { TelField } from '@components/common/form/TelField.js';\nimport { TextareaField } from '@components/common/form/TextareaField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport { Item } from '@components/common/ui/Item.js';\nimport { Skeleton } from '@components/common/ui/Skeleton.js';\nimport React, { useEffect } from 'react';\nimport { useQuery } from 'urql';\n\nconst ProvincesQuery = `\n  query Province($countries: [String]) {\n    provinces (countries: $countries) {\n      code\n      name\n      countryCode\n    }\n  }\n`;\n\nconst CountriesQuery = `\n  query Country($countries: [String]) {\n    countries (countries: $countries) {\n      code\n      name\n    }\n  }\n`;\n\nconst Province: React.FC<{\n  selectedCountry: string;\n  selectedProvince: string;\n  allowedCountries?: string[];\n  fieldName?: string;\n}> = ({\n  selectedCountry = 'US',\n  selectedProvince,\n  allowedCountries = [],\n  fieldName = 'storeProvince'\n}) => {\n  const { setValue } = useFormContext();\n\n  const [result] = useQuery({\n    query: ProvincesQuery,\n    variables: { countries: allowedCountries }\n  });\n  const { data, fetching, error } = result;\n  useEffect(() => {\n    if (fetching || !data) return;\n    const provinces = data.provinces.filter(\n      (p) => p.countryCode === selectedCountry\n    );\n    if (provinces.every((p) => p.code !== selectedProvince)) {\n      setValue(fieldName, '');\n    }\n  }, [selectedCountry, fetching]);\n  if (fetching)\n    return (\n      <div className=\"flex flex-col gap-3\">\n        <Skeleton className=\"w-1/2 h-5 rounded-md\" />\n        <Skeleton className=\"w-full h-9 rounded-md\" />\n      </div>\n    );\n  if (error) {\n    return <p className=\"text-destructive\">{error.message}</p>;\n  }\n  const provinces = data.provinces.filter(\n    (p) => p.countryCode === selectedCountry\n  );\n  if (!provinces.length) {\n    return null;\n  }\n\n  return (\n    <div>\n      <SelectField\n        id=\"storeProvince\"\n        defaultValue={selectedProvince}\n        name={fieldName}\n        label=\"Province\"\n        placeholder=\"Province\"\n        required\n        options={provinces.map((p) => ({ value: p.code, label: p.name }))}\n      />\n    </div>\n  );\n};\n\nconst Country: React.FC<{\n  selectedCountry: string;\n  setSelectedCountry: (country: string) => void;\n  allowedCountries?: string[];\n  fieldName?: string;\n}> = ({\n  selectedCountry,\n  setSelectedCountry,\n  allowedCountries = [],\n  fieldName = 'storeCountry'\n}) => {\n  const onChange = (value: string) => {\n    setSelectedCountry(value);\n  };\n  const [result] = useQuery({\n    query: CountriesQuery,\n    variables: { countries: allowedCountries }\n  });\n\n  const { data, fetching, error } = result;\n\n  if (fetching)\n    return (\n      <Item variant={'outline'}>\n        <Spinner width={'2rem'} height={'2rem'} />\n      </Item>\n    );\n  if (error) {\n    return <p className=\"text-destructive\">{error.message}</p>;\n  }\n\n  return (\n    <div style={{ marginTop: '1rem' }}>\n      <SelectField\n        defaultValue={selectedCountry}\n        name={fieldName}\n        label=\"Country\"\n        placeholder=\"Country\"\n        onChange={onChange}\n        required\n        options={data.countries.map((c) => ({ value: c.code, label: c.name }))}\n      />\n    </div>\n  );\n};\n\nconst StorePhoneNumber: React.FC<{ storePhoneNumber: string }> = ({\n  storePhoneNumber\n}) => {\n  return (\n    <div>\n      <TelField\n        name=\"storePhoneNumber\"\n        label=\"Store Phone Number\"\n        placeholder=\"Store Phone Number\"\n        defaultValue={storePhoneNumber}\n      />\n    </div>\n  );\n};\n\nconst StoreEmail: React.FC<{ storeEmail: string }> = ({ storeEmail }) => {\n  return (\n    <div>\n      <EmailField\n        name=\"storeEmail\"\n        label=\"Store Email\"\n        placeholder=\"Store Email\"\n        defaultValue={storeEmail}\n      />\n    </div>\n  );\n};\n\ninterface StoreSettingProps {\n  saveSettingApi: string;\n  setting: {\n    storeName: string;\n    storeDescription: string;\n    storePhoneNumber: string;\n    storeEmail: string;\n    storeCountry: string;\n    storeAddress: string;\n    storeCity: string;\n    storeProvince: string;\n    storePostalCode: string;\n  };\n}\n\nexport default function StoreSetting({\n  saveSettingApi,\n  setting: {\n    storeName,\n    storeDescription,\n    storePhoneNumber,\n    storeEmail,\n    storeCountry,\n    storeAddress,\n    storeCity,\n    storeProvince,\n    storePostalCode\n  }\n}: StoreSettingProps) {\n  const [selectedCountry, setSelectedCountry] = React.useState(() => {\n    const country = storeCountry;\n    if (!country) {\n      return 'US';\n    } else {\n      return country;\n    }\n  });\n\n  return (\n    <div className=\"main-content-inner\">\n      <div className=\"grid grid-cols-6 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2\">\n          <SettingMenu />\n        </div>\n        <div className=\"col-span-4\">\n          <Form method=\"POST\" id=\"storeSetting\" action={saveSettingApi}>\n            <Card>\n              <CardHeader>\n                <CardTitle>Store Settings</CardTitle>\n                <CardDescription>\n                  Configure your store information\n                </CardDescription>\n              </CardHeader>\n              <CardContent>\n                <Area\n                  id=\"storeInfoSetting\"\n                  className=\"space-y-3\"\n                  coreComponents={[\n                    {\n                      component: {\n                        default: (\n                          <InputField\n                            name=\"storeName\"\n                            label=\"Store Name\"\n                            required\n                            placeholder=\"Store Name\"\n                            defaultValue={storeName}\n                          />\n                        )\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: (\n                          <TextareaField\n                            name=\"storeDescription\"\n                            label=\"Store Description\"\n                            placeholder=\"Store Description\"\n                            defaultValue={storeDescription}\n                            required\n                          />\n                        )\n                      },\n                      sortOrder: 20\n                    }\n                  ]}\n                />\n              </CardContent>\n              <CardContent className=\"pt-3 border-t border-border\">\n                <CardTitle>Contact Information</CardTitle>\n                <Area\n                  id=\"storeContactSetting\"\n                  coreComponents={[\n                    {\n                      component: {\n                        default: StorePhoneNumber\n                      },\n                      props: {\n                        storePhoneNumber\n                      },\n                      sortOrder: 10\n                    },\n                    {\n                      component: {\n                        default: StoreEmail\n                      },\n                      props: {\n                        storeEmail\n                      },\n                      sortOrder: 20\n                    }\n                  ]}\n                  className=\"grid grid-cols-2 gap-5 mt-5\"\n                />\n              </CardContent>\n              <CardContent className=\"pt-3 border-t border-border\">\n                <CardTitle>Address</CardTitle>\n                <div className=\"space-y-3\">\n                  <Country\n                    selectedCountry={storeCountry}\n                    setSelectedCountry={setSelectedCountry}\n                  />\n                  <InputField\n                    name=\"storeAddress\"\n                    label=\"Address\"\n                    defaultValue={storeAddress}\n                    placeholder=\"Store Address\"\n                  />\n                </div>\n                <div className=\"grid grid-cols-3 gap-5 mt-5\">\n                  <div>\n                    <InputField\n                      name=\"storeCity\"\n                      label=\"City\"\n                      defaultValue={storeCity}\n                      placeholder=\"City\"\n                    />\n                  </div>\n                  <Province\n                    selectedProvince={storeProvince}\n                    selectedCountry={selectedCountry}\n                  />\n                  <div>\n                    <InputField\n                      name=\"storePostalCode\"\n                      label=\"Postal Code\"\n                      defaultValue={storePostalCode}\n                      placeholder=\"Postal Code\"\n                    />\n                  </div>\n                </div>\n              </CardContent>\n            </Card>\n          </Form>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    saveSettingApi: url(routeId: \"saveSetting\")\n    setting {\n      storeName\n      storeDescription\n      storeTimeZone\n      storePhoneNumber\n      storeEmail\n      storeCountry\n      storeAddress\n      storeCity\n      storeProvince\n      storePostalCode\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/pages/admin/storeSetting/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request) => {\n  setPageMetaInfo(request, {\n    title: 'Store Setting',\n    description: 'Store Setting'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/pages/admin/storeSetting/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/setting/store\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/services/index.ts",
    "content": "export * from './setting.js';\n"
  },
  {
    "path": "packages/evershop/src/modules/setting/services/setting.ts",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\n\nexport type Setting = {\n  name: string;\n  value: any;\n};\n\nlet setting: Setting[] | undefined;\n\nexport async function getSetting<T>(name: string, defaultValue: T): Promise<T> {\n  if (!setting) {\n    setting = await select().from('setting').execute(pool);\n  }\n  const row = setting.find((s) => s.name === name);\n  if (row) {\n    return row.value;\n  } else {\n    return defaultValue;\n  }\n}\n\nexport async function refreshSetting(): Promise<void> {\n  setting = await select().from('setting').execute(pool);\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/capturePaymentIntent/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/capturePaymentIntent/capturePaymentIntent.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport stripePayment from 'stripe';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport {\n  OK,\n  INVALID_PAYLOAD,\n  INTERNAL_SERVER_ERROR\n} from '../../../../lib/util/httpStatus.js';\nimport { updatePaymentStatus } from '../../../oms/services/updatePaymentStatus.js';\nimport { getSetting } from '../../../setting/services/setting.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { order_id } = request.body;\n    // Load the order\n    const order = await select()\n      .from('order')\n      .where('uuid', '=', order_id)\n      .load(pool);\n    if (!order || order.payment_method !== 'stripe') {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid order'\n        }\n      });\n      return;\n    }\n\n    // Get the payment transaction\n    const paymentTransaction = await select()\n      .from('payment_transaction')\n      .where('payment_transaction_order_id', '=', order.order_id)\n      .load(pool);\n    if (!paymentTransaction) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Can not find payment transaction'\n        }\n      });\n      return;\n    }\n\n    const stripeConfig = getConfig('system.stripe', {});\n    let stripeSecretKey;\n\n    if (stripeConfig.secretKey) {\n      stripeSecretKey = stripeConfig.secretKey;\n    } else {\n      stripeSecretKey = await getSetting('stripeSecretKey', '');\n    }\n    const stripe = stripePayment(stripeSecretKey);\n    // Retrieve the PaymentIntent\n    const paymentIntent = await stripe.paymentIntents.retrieve(\n      paymentTransaction.transaction_id\n    );\n    if (!paymentIntent) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid payment intent'\n        }\n      });\n    }\n    if (paymentIntent.status !== 'requires_capture') {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message:\n            'Payment intent is not in the correct state (requires_capture)'\n        }\n      });\n    }\n    // Capture the PaymentIntent\n    await stripe.paymentIntents.capture(paymentTransaction.transaction_id);\n    // Update the order status to paid\n    await updatePaymentStatus(order.order_id, 'paid');\n    response.status(OK);\n    response.json({\n      data: {\n        amount: paymentIntent.amount\n      }\n    });\n  } catch (err) {\n    error(err);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: 'Internal server error'\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/capturePaymentIntent/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"order_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"order_id\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"order_id\": \"Order is invalid\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/capturePaymentIntent/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/stripe/paymentIntents/capture\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/createPaymentIntent/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/createPaymentIntent/createPaymentIntent.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport stripePayment from 'stripe';\nimport smallestUnit from 'zero-decimal-currencies';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport { OK, INVALID_PAYLOAD } from '../../../../lib/util/httpStatus.js';\nimport { getSetting } from '../../../setting/services/setting.js';\n\nexport default async (request, response, next) => {\n  const { cart_id, order_id } = request.body;\n  // Check the cart\n  const cart = await select()\n    .from('cart')\n    .where('uuid', '=', cart_id)\n    .load(pool);\n\n  if (!cart) {\n    response.status(INVALID_PAYLOAD);\n    response.json({\n      error: {\n        status: INVALID_PAYLOAD,\n        message: 'Invalid cart'\n      }\n    });\n  } else {\n    const stripeConfig = getConfig('system.stripe', {});\n    let stripeSecretKey;\n\n    if (stripeConfig.secretKey) {\n      stripeSecretKey = stripeConfig.secretKey;\n    } else {\n      stripeSecretKey = await getSetting('stripeSecretKey', '');\n    }\n    const stripePaymentMode = await getSetting('stripePaymentMode', 'capture');\n\n    const stripe = stripePayment(stripeSecretKey);\n\n    // Create a PaymentIntent with the order amount and currency\n    const paymentIntent = await stripe.paymentIntents.create({\n      amount: smallestUnit.default(cart.grand_total, cart.currency),\n      currency: cart.currency,\n      metadata: {\n        cart_id,\n        order_id\n      },\n      automatic_payment_methods: {\n        enabled: true\n      },\n      capture_method:\n        stripePaymentMode === 'capture' ? 'automatic_async' : 'manual'\n    });\n\n    response.status(OK);\n    response.json({\n      data: {\n        clientSecret: paymentIntent.client_secret\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/createPaymentIntent/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"cart_id\": {\n      \"type\": \"string\"\n    },\n    \"order_id\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"cart_id\", \"order_id\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"cart_id\": \"Cart is invalid\",\n      \"order_id\": \"Order is invalid\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/createPaymentIntent/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/stripe/paymentIntents\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/refundPaymentIntent/[context]bodyParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/refundPaymentIntent/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"order_id\": {\n      \"type\": \"string\"\n    },\n    \"amount\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n      \"errorMessage\": {\n        \"pattern\": \"Amount should be a number with maximum 2 decimal places\"\n      }\n    }\n  },\n  \"required\": [\"order_id\"],\n  \"additionalProperties\": true,\n  \"errorMessage\": {\n    \"properties\": {\n      \"order_id\": \"Order is invalid\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/refundPaymentIntent/refundPaymentIntent.js",
    "content": "import {\n  select,\n  getConnection,\n  startTransaction,\n  insert,\n  commit,\n  rollback\n} from '@evershop/postgres-query-builder';\nimport stripePayment from 'stripe';\nimport smallestUnit from 'zero-decimal-currencies';\nimport { error } from '../../../../lib/log/logger.js';\nimport { pool } from '../../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport {\n  OK,\n  INVALID_PAYLOAD,\n  INTERNAL_SERVER_ERROR\n} from '../../../../lib/util/httpStatus.js';\nimport addOrderActivityLog from '../../../oms/services/addOrderActivityLog.js';\nimport { updatePaymentStatus } from '../../../oms/services/updatePaymentStatus.js';\nimport { getSetting } from '../../../setting/services/setting.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection(pool);\n  try {\n    await startTransaction(connection);\n\n    const { order_id, amount } = request.body;\n    // Load the order\n    const order = await select()\n      .from('order')\n      .where('order_id', '=', order_id)\n      .load(connection);\n    if (!order || order.payment_method !== 'stripe') {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid order'\n        }\n      });\n      return;\n    }\n\n    // Get the payment transaction\n    const paymentTransaction = await select()\n      .from('payment_transaction')\n      .where('payment_transaction_order_id', '=', order.order_id)\n      .load(connection);\n    if (!paymentTransaction) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Can not find payment transaction'\n        }\n      });\n      return;\n    }\n\n    const stripeConfig = getConfig('system.stripe', {});\n    let stripeSecretKey;\n\n    if (stripeConfig.secretKey) {\n      stripeSecretKey = stripeConfig.secretKey;\n    } else {\n      stripeSecretKey = await getSetting('stripeSecretKey', '');\n    }\n    const stripe = stripePayment(stripeSecretKey);\n    // Refund\n    const refund = await stripe.refunds.create({\n      payment_intent: paymentTransaction.transaction_id,\n      amount: smallestUnit.default(amount, order.currency)\n    });\n    const charge = await stripe.charges.retrieve(refund.charge);\n    // Update the order status\n    const status = charge.refunded === true ? 'refunded' : 'partial_refunded';\n    await updatePaymentStatus(order.order_id, status, connection);\n\n    // Add order activity log\n    await addOrderActivityLog(\n      order.order_id,\n      `Refunded ${amount} ${charge.currency}`,\n      false,\n      connection\n    );\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: {\n        amount: refund.amount\n      }\n    });\n  } catch (err) {\n    error(err);\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: err.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/refundPaymentIntent/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/stripe/paymentIntents/refund\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/stripeWebHook/[bodyJson]webhook.js",
    "content": "import {\n  insert,\n  startTransaction,\n  commit,\n  rollback,\n  select,\n  insertOnUpdate\n} from '@evershop/postgres-query-builder';\nimport stripePgk from 'stripe';\nimport { display } from 'zero-decimal-currencies';\nimport { emit } from '../../../../lib/event/emitter.js';\nimport { debug, error } from '../../../../lib/log/logger.js';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../../lib/util/getConfig.js';\nimport addOrderActivityLog from '../../../oms/services/addOrderActivityLog.js';\nimport { updatePaymentStatus } from '../../../oms/services/updatePaymentStatus.js';\nimport { getSetting } from '../../../setting/services/setting.js';\n\nexport default async (request, response, next) => {\n  const sig = request.headers['stripe-signature'];\n\n  let event;\n  const connection = await getConnection();\n  try {\n    const stripeConfig = getConfig('system.stripe', {});\n    let stripeSecretKey;\n    if (stripeConfig.secretKey) {\n      stripeSecretKey = stripeConfig.secretKey;\n    } else {\n      stripeSecretKey = await getSetting('stripeSecretKey', '');\n    }\n    const stripe = stripePgk(stripeSecretKey);\n\n    // Webhook enpoint secret\n    let endpointSecret;\n    if (stripeConfig.endpointSecret) {\n      endpointSecret = stripeConfig.endpointSecret;\n    } else {\n      endpointSecret = await getSetting('stripeEndpointSecret', '');\n    }\n\n    event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);\n    await startTransaction(connection);\n    const paymentIntent = event.data.object;\n    const { order_id } = paymentIntent.metadata;\n    const transaction = await select()\n      .from('payment_transaction')\n      .where('transaction_id', '=', paymentIntent.id)\n      .load(connection);\n    // Load the order\n    const order = await select()\n      .from('order')\n      .where('uuid', '=', order_id)\n      .load(connection);\n    if (!order) {\n      throw new Error(`Order with id ${order_id} not found`);\n    }\n    // Handle the event\n    switch (event.type) {\n      case 'payment_intent.succeeded': {\n        debug('payment_intent.succeeded event received');\n        // Update the order\n        // Create payment transaction\n        await insertOnUpdate('payment_transaction', [\n          'transaction_id',\n          'payment_transaction_order_id'\n        ])\n          .given({\n            amount: parseFloat(\n              display(paymentIntent.amount, paymentIntent.currency)\n            ),\n            payment_transaction_order_id: order.order_id,\n            transaction_id: paymentIntent.id,\n            transaction_type: 'online',\n            payment_action:\n              paymentIntent.capture_method === 'manual' ? 'Manual' : 'Automatic'\n          })\n          .execute(connection);\n\n        if (!transaction) {\n          await updatePaymentStatus(order.order_id, 'paid', connection);\n\n          // Add an activity log\n          await addOrderActivityLog(\n            order.order_id,\n            `Customer paid by using Stripe. Transaction ID: ${paymentIntent.id}`,\n            false,\n            connection\n          );\n\n          // Emit event to add order placed event\n          await emit('order_placed', { ...order });\n        }\n        break;\n      }\n      case 'payment_intent.amount_capturable_updated': {\n        debug('payment_intent.amount_capturable_updated event received');\n        // Create payment transaction\n        await insertOnUpdate('payment_transaction', [\n          'transaction_id',\n          'payment_transaction_order_id'\n        ])\n          .given({\n            amount: parseFloat(\n              display(paymentIntent.amount, paymentIntent.currency)\n            ),\n            payment_transaction_order_id: order.order_id,\n            transaction_id: paymentIntent.id,\n            transaction_type: 'online',\n            payment_action:\n              paymentIntent.capture_method === 'manual'\n                ? 'authorize'\n                : 'capture'\n          })\n          .execute(connection);\n\n        if (!transaction) {\n          await updatePaymentStatus(order.order_id, 'authorized', connection);\n          // Add an activity log\n          await addOrderActivityLog(\n            order.order_id,\n            `Customer authorized by using Stripe. Transaction ID: ${paymentIntent.id}`,\n            false,\n            connection\n          );\n          // Emit event to add order placed event\n          await emit('order_placed', { ...order });\n        }\n        break;\n      }\n      case 'payment_intent.canceled': {\n        debug('payment_intent.canceled event received');\n        await updatePaymentStatus(order.order_id, 'canceled', connection);\n        break;\n      }\n      default: {\n        debug(`Unhandled event type ${event.type}`);\n      }\n    }\n    await commit(connection);\n    // Return a response to acknowledge receipt of the event\n    response.json({ received: true });\n  } catch (err) {\n    error(err);\n    await rollback(connection);\n    response.status(400).send(`Webhook Error: ${err.message}`);\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/stripeWebHook/bodyJson.js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.raw({ type: '*/*' })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/api/stripeWebHook/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/stripe/webhook\",\n  \"access\": \"public\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/bootstrap.js",
    "content": "import config from 'config';\nimport { getConfig } from '../../lib/util/getConfig.js';\nimport { hookAfter } from '../../lib/util/hookable.js';\nimport { addProcessor } from '../../lib/util/registry.js';\nimport { registerPaymentMethod } from '../checkout/services/getAvailablePaymentMethods.js';\nimport { getSetting } from '../setting/services/setting.js';\nimport { cancelPaymentIntent } from './services/cancelPayment.js';\n\nexport default async () => {\n  const authorizedPaymentStatus = {\n    order: {\n      paymentStatus: {\n        authorized: {\n          name: 'Authorized',\n          badge: 'warning'\n        },\n        failed: {\n          name: 'Failed',\n          badge: 'critical'\n        },\n        refunded: {\n          name: 'Refunded',\n          badge: 'critical'\n        },\n        partial_refunded: {\n          name: 'Partial Refunded',\n          badge: 'critical'\n        }\n      },\n      psoMapping: {\n        'authorized:*': 'processing',\n        'failed:*': 'new',\n        'refunded:*': 'closed',\n        'partial_refunded:*': 'processing',\n        'partial_refunded:delivered': 'completed'\n      }\n    }\n  };\n  config.util.setModuleDefaults('oms', authorizedPaymentStatus);\n\n  hookAfter('changePaymentStatus', async (order, orderID, status) => {\n    if (status !== 'canceled') {\n      return;\n    }\n    if (order.payment_method !== 'stripe') {\n      return;\n    }\n    await cancelPaymentIntent(orderID);\n  });\n\n  registerPaymentMethod({\n    init: async () => ({\n      code: 'stripe',\n      name: await getSetting('stripeDisplayName', 'Stripe')\n    }),\n    validator: async () => {\n      const stripeConfig = getConfig('system.stripe', {});\n      let stripeStatus;\n      if (stripeConfig.status) {\n        stripeStatus = stripeConfig.status;\n      } else {\n        stripeStatus = await getSetting('stripePaymentStatus', 0);\n      }\n      if (parseInt(stripeStatus, 10) === 1) {\n        return true;\n      } else {\n        return false;\n      }\n    }\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/graphql/types/StripeSetting/StripeSetting.admin.graphql",
    "content": "extend type Setting {\n  stripeSecretKey: String\n  stripeEndpointSecret: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/graphql/types/StripeSetting/StripeSetting.admin.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Setting: {\n    stripePublishableKey: (setting) => {\n      const stripeConfig = getConfig('system.stripe', {});\n      if (stripeConfig.publishableKey) {\n        return stripeConfig.publishableKey;\n      }\n      const stripePublishableKey = setting.find(\n        (s) => s.name === 'stripePublishableKey'\n      );\n      if (stripePublishableKey) {\n        return stripePublishableKey.value;\n      } else {\n        return null;\n      }\n    },\n    stripeSecretKey: (setting, _, { user }) => {\n      const stripeConfig = getConfig('system.stripe', {});\n      if (stripeConfig.secretKey) {\n        return `${stripeConfig.secretKey.substr(\n          0,\n          5\n        )}*******************************`;\n      }\n      if (user) {\n        const stripeSecretKey = setting.find(\n          (s) => s.name === 'stripeSecretKey'\n        );\n        if (stripeSecretKey) {\n          return stripeSecretKey.value;\n        } else {\n          return null;\n        }\n      } else {\n        return null;\n      }\n    },\n    stripeEndpointSecret: (setting, _, { user }) => {\n      const stripeConfig = getConfig('system.stripe', {});\n      if (stripeConfig.endpointSecret) {\n        return `${stripeConfig.endpointSecret.substr(\n          0,\n          5\n        )}*******************************`;\n      }\n      if (user) {\n        const stripeEndpointSecret = setting.find(\n          (s) => s.name === 'stripeEndpointSecret'\n        );\n        if (stripeEndpointSecret) {\n          return stripeEndpointSecret.value;\n        } else {\n          return null;\n        }\n      } else {\n        return null;\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/graphql/types/StripeSetting/StripeSetting.graphql",
    "content": "extend type Setting {\n  stripePaymentStatus: Int\n  stripeDisplayName: String\n  stripePublishableKey: String\n  stripePaymentMode: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/graphql/types/StripeSetting/StripeSetting.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Setting: {\n    stripePaymentStatus: (setting) => {\n      const stripeConfig = getConfig('system.stripe', {});\n      if (stripeConfig.status) {\n        return stripeConfig.status;\n      }\n      const stripePaymentStatus = setting.find(\n        (s) => s.name === 'stripePaymentStatus'\n      );\n      if (stripePaymentStatus) {\n        return parseInt(stripePaymentStatus.value, 10);\n      } else {\n        return 0;\n      }\n    },\n    stripeDisplayName: (setting) => {\n      const stripeDisplayName = setting.find(\n        (s) => s.name === 'stripeDisplayName'\n      );\n      if (stripeDisplayName) {\n        return stripeDisplayName.value;\n      } else {\n        return 'Credit Card';\n      }\n    },\n    stripePublishableKey: (setting) => {\n      const stripeConfig = getConfig('system.stripe', {});\n      if (stripeConfig.publishableKey) {\n        return stripeConfig.publishableKey;\n      }\n      const stripePublishableKey = setting.find(\n        (s) => s.name === 'stripePublishableKey'\n      );\n      if (stripePublishableKey) {\n        return stripePublishableKey.value;\n      } else {\n        return null;\n      }\n    },\n    stripePaymentMode: (setting) => {\n      const stripePaymentMode = setting.find(\n        (s) => s.name === 'stripePaymentMode'\n      );\n      if (stripePaymentMode) {\n        return stripePaymentMode.value;\n      } else {\n        return 'capture';\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/pages/admin/orderEdit/StripeCaptureButton.jsx",
    "content": "import RenderIfTrue from '@components/common/RenderIfTrue';\nimport { Button } from '@components/common/ui/Button';\nimport { Card } from '@components/common/ui/Card';\nimport { CardContent } from '@components/common/ui/Card.js';\nimport axios from 'axios';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\nexport default function StripeCaptureButton({\n  captureAPI,\n  order: { paymentStatus, uuid, paymentMethod }\n}) {\n  const [isLoading, setIsLoading] = React.useState(false);\n\n  const onAction = async () => {\n    setIsLoading(true);\n    // Use Axios to call the capture API\n    const response = await axios.post(\n      captureAPI,\n      { order_id: uuid },\n      { validateStatus: false }\n    );\n    if (!response.data.error) {\n      // Reload the page\n      window.location.reload();\n    } else {\n      toast.error(response.data.error.message);\n    }\n    setIsLoading(false);\n  };\n\n  return (\n    <RenderIfTrue\n      condition={\n        paymentStatus.code === 'authorized' && paymentMethod === 'stripe'\n      }\n    >\n      <CardContent>\n        <div className=\"flex justify-end\">\n          <Button onClick={onAction} isLoading={isLoading}>\n            Capture Payment\n          </Button>\n        </div>\n      </CardContent>\n    </RenderIfTrue>\n  );\n}\n\nStripeCaptureButton.propTypes = {\n  captureAPI: PropTypes.string.isRequired,\n  order: PropTypes.shape({\n    paymentStatus: PropTypes.shape({\n      code: PropTypes.string.isRequired\n    }).isRequired,\n    uuid: PropTypes.string.isRequired,\n    paymentMethod: PropTypes.string.isRequired\n  }).isRequired\n};\n\nexport const layout = {\n  areaId: 'orderPaymentActions',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    captureAPI: url(routeId: \"capturePaymentIntent\")\n    order(uuid: getContextValue(\"orderId\")) {\n      uuid\n      paymentStatus {\n        code\n      }\n      paymentMethod\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/pages/admin/orderEdit/StripeRefundButton.tsx",
    "content": "import { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { useAlertContext } from '@components/common/modal/Alert.js';\nimport RenderIfTrue from '@components/common/RenderIfTrue.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { CardContent } from '@components/common/ui/Card.js';\nimport React from 'react';\nimport { toast } from 'react-toastify';\n\ninterface StripeRefundButtonProps {\n  refundAPI: string;\n  order: {\n    paymentStatus: {\n      code: string;\n    };\n    orderId: string;\n    paymentMethod: string;\n    grandTotal: {\n      value: number;\n      currency: string;\n    };\n  };\n}\nexport default function StripeRefundButton({\n  refundAPI,\n  order: { paymentStatus, orderId, paymentMethod, grandTotal }\n}: StripeRefundButtonProps) {\n  const { openAlert, closeAlert, dispatchAlert } = useAlertContext();\n  return (\n    <RenderIfTrue\n      condition={\n        paymentMethod === 'stripe' &&\n        ['paid', 'partial_refunded'].includes(paymentStatus.code)\n      }\n    >\n      <CardContent>\n        <div className=\"flex justify-end\">\n          <Button\n            variant=\"destructive\"\n            onClick={() => {\n              openAlert({\n                heading: 'Refund',\n                content: (\n                  <div>\n                    <Form\n                      id=\"stripeRefund\"\n                      method=\"POST\"\n                      action={refundAPI}\n                      submitBtn={false}\n                      onSuccess={(response) => {\n                        if (response.error) {\n                          toast.error(response.error.message);\n                          dispatchAlert({\n                            type: 'update',\n                            payload: { secondaryAction: { isLoading: false } }\n                          });\n                        } else {\n                          // Reload the page\n                          window.location.reload();\n                        }\n                      }}\n                      onInvalid={() => {\n                        dispatchAlert({\n                          type: 'update',\n                          payload: { secondaryAction: { isLoading: false } }\n                        });\n                      }}\n                    >\n                      <div>\n                        <NumberField\n                          name=\"amount\"\n                          label=\"Refund amount\"\n                          placeholder=\"Refund amount\"\n                          defaultValue={grandTotal.value}\n                          required\n                          validation={{\n                            required: 'This field is required',\n                            min: {\n                              value: 0,\n                              message:\n                                'Amount must be greater than or equal to 0'\n                            },\n                            max: {\n                              value: grandTotal.value,\n                              message: `Amount must be less than or equal to ${grandTotal.value} ${grandTotal.currency}`\n                            }\n                          }}\n                          helperText={`Maximum amount is ${grandTotal.value} ${grandTotal.currency}`}\n                          unit={grandTotal.currency}\n                        />\n                      </div>\n                      <InputField\n                        type=\"hidden\"\n                        name=\"order_id\"\n                        value={orderId}\n                      />\n                    </Form>\n                  </div>\n                ),\n                primaryAction: {\n                  title: 'Cancel',\n                  onAction: closeAlert,\n                  variant: ''\n                },\n                secondaryAction: {\n                  title: 'Refund',\n                  onAction: () => {\n                    dispatchAlert({\n                      type: 'update',\n                      payload: { secondaryAction: { isLoading: true } }\n                    });\n                    (\n                      document.getElementById('stripeRefund') as HTMLFormElement\n                    ).dispatchEvent(\n                      new Event('submit', { cancelable: true, bubbles: true })\n                    );\n                  },\n                  variant: 'secondary',\n                  isLoading: false\n                }\n              });\n            }}\n          >\n            Refund\n          </Button>\n        </div>\n      </CardContent>\n    </RenderIfTrue>\n  );\n}\n\nexport const layout = {\n  areaId: 'orderPaymentActions',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    refundAPI: url(routeId: \"refundPaymentIntent\")\n    order(uuid: getContextValue(\"orderId\")) {\n      orderId\n      grandTotal {\n        value\n        currency\n      }\n      paymentStatus {\n        code\n      }\n      paymentMethod\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/pages/admin/paymentSetting/StripePayment.tsx",
    "content": "import { InputField } from '@components/common/form/InputField.js';\nimport { RadioGroupField } from '@components/common/form/RadioGroupField.js';\nimport { ToggleField } from '@components/common/form/ToggleField.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport React from 'react';\n\ninterface StripePaymentProps {\n  setting: {\n    stripePaymentStatus: true | false | 0 | 1;\n    stripeDisplayName: string;\n    stripePublishableKey: string;\n    stripeSecretKey: string;\n    stripeEndpointSecret: string;\n    stripePaymentMode: string;\n  };\n}\nexport default function StripePayment({\n  setting: {\n    stripePaymentStatus,\n    stripeDisplayName,\n    stripePublishableKey,\n    stripeSecretKey,\n    stripeEndpointSecret,\n    stripePaymentMode\n  }\n}: StripePaymentProps) {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Stripe Payment</CardTitle>\n        <CardDescription>\n          Configure your Stripe payment gateway settings\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Enable?</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <ToggleField\n              name=\"stripePaymentStatus\"\n              defaultValue={stripePaymentStatus}\n              trueValue={1}\n              falseValue={0}\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Dislay Name</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <InputField\n              name=\"stripeDisplayName\"\n              placeholder=\"Display Name\"\n              defaultValue={stripeDisplayName || ''}\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Publishable Key</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <InputField\n              name=\"stripePublishableKey\"\n              placeholder=\"Publishable Key\"\n              defaultValue={stripePublishableKey || ''}\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Secret Key</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <InputField\n              name=\"stripeSecretKey\"\n              placeholder=\"Secret Key\"\n              defaultValue={stripeSecretKey || ''}\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Webhook Secret Key</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <InputField\n              name=\"stripeEndpointSecret\"\n              placeholder=\"Secret Key\"\n              defaultValue={stripeEndpointSecret || ''}\n              helperText=\"Your webhook url should be: https://yourdomain.com/api/stripe/webhook\"\n            />\n          </div>\n        </div>\n      </CardContent>\n      <CardContent className=\"pt-4 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div className=\"col-span-1 items-center flex\">\n            <h4>Payment mode</h4>\n          </div>\n          <div className=\"col-span-2\">\n            <RadioGroupField\n              name=\"stripePaymentMode\"\n              defaultValue={stripePaymentMode}\n              options={[\n                { label: 'Authorize only', value: 'authorizeOnly' },\n                { label: 'Capture', value: 'capture' }\n              ]}\n            />\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\nexport const layout = {\n  areaId: 'paymentSetting',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    setting {\n      stripeDisplayName\n      stripePaymentStatus\n      stripePublishableKey\n      stripeSecretKey\n      stripeEndpointSecret\n      stripePaymentMode\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/pages/frontStore/checkout/Stripe.tsx",
    "content": "import Spinner from '@components/admin/Spinner.js';\nimport RenderIfTrue from '@components/common/RenderIfTrue.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport { useCartState } from '@components/frontStore/cart/CartContext.js';\nimport {\n  useCheckout,\n  useCheckoutDispatch\n} from '@components/frontStore/checkout/CheckoutContext.js';\nimport { _ } from '@evershop/evershop/lib/locale/translate/_';\nimport { Elements } from '@stripe/react-stripe-js';\nimport {\n  PaymentElement,\n  useElements,\n  useStripe\n} from '@stripe/react-stripe-js';\nimport { loadStripe, StripeElementsOptions } from '@stripe/stripe-js';\nimport React, { useEffect, useState } from 'react';\nimport { toast } from 'react-toastify';\nimport smallUnit from 'zero-decimal-currencies';\n\nconst TestCards: React.FC<{\n  showTestCard: 'success' | 'failure';\n  testSuccess: () => void;\n  testFailure: () => void;\n}> = ({ showTestCard, testSuccess, testFailure }) => {\n  return (\n    <div>\n      <div\n        style={{\n          border: '1px solid #dddddd',\n          borderRadius: '3px',\n          padding: '5px',\n          boxSizing: 'border-box',\n          marginBottom: '10px'\n        }}\n      >\n        {showTestCard === 'success' && (\n          <div>\n            <div>\n              <b>Test success:</b>\n            </div>\n            <div className=\"text-xs text-gray-600\">\n              Test card number: 4242 4242 4242 4242\n            </div>\n            <div className=\"text-xs text-gray-600\">Test card expiry: 04/26</div>\n            <div className=\"text-xs text-gray-600\">Test card CVC: 242</div>\n          </div>\n        )}\n        {showTestCard === 'failure' && (\n          <div>\n            <div>\n              <b>Test failure:</b>\n            </div>\n            <div className=\"text-xs text-gray-600\">\n              Test card number: 4000 0000 0000 9995\n            </div>\n            <div className=\"text-xs text-gray-600\">Test card expiry: 04/26</div>\n            <div className=\"text-xs text-gray-600\">Test card CVC: 242</div>\n          </div>\n        )}\n      </div>\n      <div className=\"stripe-form-heading flex justify-between\">\n        <div className=\"self-center\">\n          <svg\n            id=\"Layer_1\"\n            data-name=\"Layer 1\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            viewBox=\"0 0 150 34\"\n          >\n            <defs />\n            <title>Powered by Stripe</title>\n            <path d=\"M146,0H3.73A3.73,3.73,0,0,0,0,3.73V30.27A3.73,3.73,0,0,0,3.73,34H146a4,4,0,0,0,4-4V4A4,4,0,0,0,146,0Zm3,30a3,3,0,0,1-3,3H3.73A2.74,2.74,0,0,1,1,30.27V3.73A2.74,2.74,0,0,1,3.73,1H146a3,3,0,0,1,3,3Z\" />\n            <path d=\"M17.07,11.24h-4.3V22h1.92V17.84h2.38c2.4,0,3.9-1.16,3.9-3.3S19.47,11.24,17.07,11.24Zm-.1,5H14.69v-3.3H17c1.38,0,2.11.59,2.11,1.65S18.35,16.19,17,16.19Z\" />\n            <path d=\"M25.1,14a3.77,3.77,0,0,0-3.8,4.09,3.81,3.81,0,1,0,7.59,0A3.76,3.76,0,0,0,25.1,14Zm0,6.67c-1.22,0-2-1-2-2.58s.76-2.58,2-2.58,2,1,2,2.58S26.31,20.66,25.1,20.66Z\" />\n            <polygon points=\"36.78 19.35 35.37 14.13 33.89 14.13 32.49 19.35 31.07 14.13 29.22 14.13 31.59 22.01 33.15 22.01 34.59 16.85 36.03 22.01 37.59 22.01 39.96 14.13 38.18 14.13 36.78 19.35\" />\n            <path d=\"M44,14a3.83,3.83,0,0,0-3.75,4.09,3.79,3.79,0,0,0,3.83,4.09A3.47,3.47,0,0,0,47.49,20L46,19.38a1.78,1.78,0,0,1-1.83,1.26A2.12,2.12,0,0,1,42,18.47h5.52v-.6C47.54,15.71,46.32,14,44,14Zm-1.93,3.13A1.92,1.92,0,0,1,44,15.5a1.56,1.56,0,0,1,1.69,1.62Z\" />\n            <path d=\"M50.69,15.3V14.13h-1.8V22h1.8V17.87a1.89,1.89,0,0,1,2-2,4.68,4.68,0,0,1,.66,0v-1.8c-.14,0-.3,0-.51,0A2.29,2.29,0,0,0,50.69,15.3Z\" />\n            <path d=\"M57.48,14a3.83,3.83,0,0,0-3.75,4.09,3.79,3.79,0,0,0,3.83,4.09A3.47,3.47,0,0,0,60.93,20l-1.54-.59a1.78,1.78,0,0,1-1.83,1.26,2.12,2.12,0,0,1-2.1-2.17H61v-.6C61,15.71,59.76,14,57.48,14Zm-1.93,3.13a1.92,1.92,0,0,1,1.92-1.62,1.56,1.56,0,0,1,1.69,1.62Z\" />\n            <path d=\"M67.56,15a2.85,2.85,0,0,0-2.26-1c-2.21,0-3.47,1.85-3.47,4.09s1.26,4.09,3.47,4.09a2.82,2.82,0,0,0,2.26-1V22h1.8V11.24h-1.8Zm0,3.35a2,2,0,0,1-2,2.28c-1.31,0-2-1-2-2.52s.7-2.52,2-2.52c1.11,0,2,.81,2,2.29Z\" />\n            <path d=\"M79.31,14A2.88,2.88,0,0,0,77,15V11.24h-1.8V22H77v-.83a2.86,2.86,0,0,0,2.27,1c2.2,0,3.46-1.86,3.46-4.09S81.51,14,79.31,14ZM79,20.6a2,2,0,0,1-2-2.28v-.47c0-1.48.84-2.29,2-2.29,1.3,0,2,1,2,2.52S80.25,20.6,79,20.6Z\" />\n            <path d=\"M86.93,19.66,85,14.13H83.1L86,21.72l-.3.74a1,1,0,0,1-1.14.79,4.12,4.12,0,0,1-.6,0v1.51a4.62,4.62,0,0,0,.73.05,2.67,2.67,0,0,0,2.78-2l3.24-8.62H88.82Z\" />\n            <path d=\"M125,12.43a3,3,0,0,0-2.13.87l-.14-.69h-2.39V25.53l2.72-.59V21.81a3,3,0,0,0,1.93.7c1.94,0,3.72-1.59,3.72-5.11C128.71,14.18,126.91,12.43,125,12.43Zm-.65,7.63a1.61,1.61,0,0,1-1.28-.52l0-4.11a1.64,1.64,0,0,1,1.3-.55c1,0,1.68,1.13,1.68,2.58S125.36,20.06,124.35,20.06Z\" />\n            <path d=\"M133.73,12.43c-2.62,0-4.21,2.26-4.21,5.11,0,3.37,1.88,5.08,4.56,5.08a6.12,6.12,0,0,0,3-.73V19.64a5.79,5.79,0,0,1-2.7.62c-1.08,0-2-.39-2.14-1.7h5.38c0-.15,0-.74,0-1C137.71,14.69,136.35,12.43,133.73,12.43Zm-1.47,4.07c0-1.26.77-1.79,1.45-1.79s1.4.53,1.4,1.79Z\" />\n            <path d=\"M113,13.36l-.17-.82h-2.32v9.71h2.68V15.67a1.87,1.87,0,0,1,2.05-.58V12.54A1.8,1.8,0,0,0,113,13.36Z\" />\n            <path d=\"M99.46,15.46c0-.44.36-.61.93-.61a5.9,5.9,0,0,1,2.7.72V12.94a7,7,0,0,0-2.7-.51c-2.21,0-3.68,1.18-3.68,3.16,0,3.1,4.14,2.6,4.14,3.93,0,.52-.44.69-1,.69a6.78,6.78,0,0,1-3-.9V22a7.38,7.38,0,0,0,3,.64c2.26,0,3.82-1.15,3.82-3.16C103.62,16.12,99.46,16.72,99.46,15.46Z\" />\n            <path d=\"M107.28,10.24l-2.65.58v8.93a2.77,2.77,0,0,0,2.82,2.87,4.16,4.16,0,0,0,1.91-.37V20c-.35.15-2.06.66-2.06-1V15h2.06V12.66h-2.06Z\" />\n            <polygon points=\"116.25 11.7 118.98 11.13 118.98 8.97 116.25 9.54 116.25 11.7\" />\n            <rect x=\"116.25\" y=\"12.61\" width=\"2.73\" height=\"9.64\" />\n          </svg>\n        </div>\n        <div className=\"self-center flex space-x-2 pb-2\">\n          <Button onClick={testSuccess} title=\"Test success\" variant=\"default\">\n            Test success\n          </Button>\n          <Button\n            onClick={testFailure}\n            title=\"Test failure\"\n            variant=\"destructive\"\n          >\n            Test failure\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n};\n\ninterface CheckoutFormProps {\n  stripePublishableKey: string;\n  createPaymentIntentApi: string;\n  returnUrl: string;\n}\n\nexport function CheckoutForm({\n  stripePublishableKey,\n  createPaymentIntentApi,\n  returnUrl\n}: CheckoutFormProps) {\n  const [clientSecret, setClientSecret] = React.useState('');\n  const [showTestCard, setShowTestCard] = useState<'success' | 'failure'>(\n    'success'\n  );\n  const stripe = useStripe();\n  const elements = useElements();\n  const {\n    cartId,\n    orderId,\n    orderPlaced,\n    checkoutData: { paymentMethod }\n  } = useCheckout();\n\n  const {\n    data: { billingAddress, shippingAddress, customerFullName, customerEmail }\n  } = useCartState();\n\n  useEffect(() => {\n    const validateStripe = async () => {\n      if (!stripe || !elements) {\n        toast.error(_('Stripe is not loaded. Please try again.'));\n        return false;\n      }\n\n      const submit = await elements.submit();\n      if (submit?.error) {\n        toast.error(\n          submit.error.message ||\n            _('Can not process payment. Please try again later.')\n        );\n        return false;\n      }\n\n      return true;\n    };\n\n    // Make validation function available globally\n    if (typeof window !== 'undefined') {\n      (window as any).validateStripePayment = validateStripe;\n    }\n\n    return () => {\n      if (typeof window !== 'undefined') {\n        delete (window as any).validateStripePayment;\n      }\n    };\n  }, [stripe, elements]);\n\n  useEffect(() => {\n    if (orderId && orderPlaced && paymentMethod === 'stripe') {\n      window\n        .fetch(createPaymentIntentApi, {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json'\n          },\n          body: JSON.stringify({ cart_id: cartId, order_id: orderId })\n        })\n        .then((res) => res.json())\n        .then((data) => {\n          if (data.error) {\n            toast.error(_('Some error occurred. Please try again later.'));\n          } else {\n            setClientSecret(data.data.clientSecret);\n          }\n        });\n    }\n  }, [orderId, orderPlaced]);\n\n  useEffect(() => {\n    const confirmPayment = async () => {\n      const payload = await stripe?.confirmPayment({\n        clientSecret: clientSecret as string,\n        elements: elements as any,\n        confirmParams: {\n          payment_method_data: {\n            billing_details: {\n              name:\n                billingAddress?.fullName ||\n                shippingAddress?.fullName ||\n                customerFullName ||\n                '',\n              email: customerEmail,\n              phone:\n                billingAddress?.telephone || shippingAddress?.telephone || '',\n              address: {\n                line1:\n                  billingAddress?.address1 || shippingAddress?.address1 || '',\n                country:\n                  billingAddress?.country?.code ||\n                  shippingAddress?.country?.code ||\n                  '',\n                state:\n                  billingAddress?.province?.code ||\n                  shippingAddress?.province?.code ||\n                  '',\n                postal_code:\n                  billingAddress?.postcode || shippingAddress?.postcode || '',\n                city: billingAddress?.city || shippingAddress?.city || ''\n              }\n            }\n          },\n          return_url: `${returnUrl}?order_id=${orderId}`\n        }\n      });\n      if (payload?.error) {\n        // Get the payment intent ID\n        const paymentIntent = payload.error.payment_intent;\n        // Redirect to the return URL with the payment intent ID\n        window.location.href = `${returnUrl}?order_id=${orderId}&payment_intent=${paymentIntent?.id}`;\n      }\n    };\n    if (orderPlaced && clientSecret) {\n      confirmPayment();\n    }\n  }, [orderPlaced, clientSecret]);\n\n  const testSuccess = () => {\n    setShowTestCard('success');\n  };\n\n  const testFailure = () => {\n    setShowTestCard('failure');\n  };\n\n  return (\n    <>\n      <RenderIfTrue condition={!!(stripe && elements)}>\n        <div>\n          <div className=\"stripe-form float-left w-full\">\n            {stripePublishableKey &&\n              stripePublishableKey.startsWith('pk_test') && (\n                <TestCards\n                  showTestCard={showTestCard}\n                  testSuccess={testSuccess}\n                  testFailure={testFailure}\n                />\n              )}\n            <PaymentElement id=\"payment-element\" />\n          </div>\n        </div>\n      </RenderIfTrue>\n      <RenderIfTrue condition={!!(!stripe || !elements)}>\n        <div className=\"flex justify-center p-3\">\n          <Spinner width={20} height={20} />\n        </div>\n      </RenderIfTrue>\n    </>\n  );\n}\n\n// Make sure to call loadStripe outside of a component’s render to avoid\n// recreating the Stripe object on every render.\n// loadStripe is initialized with your real test publishable API key.\nlet stripe;\nconst stripeLoader = (publishKey) => {\n  if (!stripe) {\n    stripe = loadStripe(publishKey);\n  }\n  return stripe;\n};\n\ninterface StripeAppProps {\n  total: number;\n  currency: string;\n  stripePublishableKey: string;\n  returnUrl: string;\n  createPaymentIntentApi: string;\n  stripePaymentMode: string;\n}\n\ninterface CardsProps {\n  width?: number;\n  height?: number;\n}\n\nfunction Cards({ width = 24, height = 24 }: CardsProps) {\n  return (\n    <img\n      width={width}\n      height={height}\n      src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAAAmCAYAAAAMe5M4AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAd2SURBVHgB7ZxtaBRHGMefe01y5s3UqiUmlaqlNgm1JU2ICdSqIEk/qIEE/dBGsNBKG7GhSi2FplCKtRAa0mItLTb1g/YC0S+NBNparGeI2GIxIQULamJpFJEkJppcsrfOfzdzt3vZ83YuZ+64ux+Me7s7O5eb5z/PPPPiWihABktpLFkpsZBYmmRpisRIivqwz17MxufOzs6C8vLyKkogPIz6+vpB9tHB0rjJx5KqPqDyvDt37nwuJyj4bfiNLKWbqKOkqg8b+yfT7XavLC0tPU4JisvlqpQk6ei5c+dmSHV/jyKp6gP9m62SQQnOnj17ashcf55U9ZFoAU4KQVICSHIeqwB814fINzJmOr/sHSP53hClWDjsFEWmf++hqfYO8rIjjM+x5GaTfV0Rpe2qI8crFWRbWaBch8F9V90kDXaTfLdfOfc/k1dEVpZsq+vI+lQFpXg8REUAMPz9T1qUoxEy8wK4h2Rlxs/Yt5ucm4lmLrfojK57hglCQvrXTZbMAnJWq8cU0WXeAnjQ+h1N7Gs2nV++PUS2s/uI/peJVuWyv8AS/pnxIZrqqGBepInsLzZRiugxLwGg1d9vbjGd3+Iiyto6Qk7nBNEwC0DGp8n7wpMkmxABgMcAKRFEj4gFMHW6W8j4wFVxTzX+LBYmANuNMZpZlWO6DIgAMYF1eWzighOdVwyvV5YXUmH+3N/huThIgzdHlc+FK3KosqyQun65SqNjk4bPeXpZ/v/U/DnZ6ZSTlcaOaVS8dtncsjV5edmiRCyAifeaRbKTc900ZayY29/bb46T74l08uWmkVmm/2gi59ZusjizaaGBUS4wo55hRuSVz/nrt7epIEgEjR900dBsvrZDNcpx78GfmQDUtanTx3cqAkBZe1leCMaIs6d36UQAETW80+k/r9m8JiIBRDQMnPzBrYvyzZDxzGjIe/Yb90gExAQSGz3EAlT0px9uoj+Zsdu/rtW1XhhFC1rokEYkMFDfwG2/8UHxc0sV4297/URI4xt5gDPB3xXi2XBE5AEmW78Xym9bKpEjK/RqrHVkSkkiXsDHho5U9CbFEogBBv7os1+V876BW7r7J0/1+T/v2F6seIcrAwHDFa9dqngUGFMrFHiFEiYMcOWf27p7AII5eUrfFUFUyFeQb747BcIeAEO6mcv9Qs84ljwIm8cyMUMi+IZ7Qg4hFxKIgOO5GPCKwUY60Fg1myfQUos1RuagtcOI2UwYSPAaO7aX6L7zizaPPy/EwjnfK+4FhAUganzgWBZ+L4Z13EuiyMM9FGtgLBgCoAXy4O6CxhiVZQX+ltk/EDA2AkD1fqDvRkveuO0YnQwRbAIuoupNar/Pv79fIySzCAtAui4+VWt1ymHzWCYlEkWeGqV4AIbg9M0a4fBXHv+1HbUlmvuBboJ7AHgRbSuHCBoPdtFLG7+ZE2hCGLxLeKuhVDlycfUNLIAAUsxlvaYFwwhdmj4dQSI3rjYADA7sMELY31ipCypRBrwB9yrg6I+X/OXy57mQtOIyi7AArIvFggwgTYSPNWW7uBYtWfExNVxVrhXALfpJ0/fzVsrvcbjRtBx4t0rp07VxBQTDg0mMKngrR6vHuUfpaiz+vMGBaDiERwFYzBFl5i77mlWPziMyAuBgwSgegDGQhpTAr093rzpEkGg0scPLwvBy9ctf+r0F9wDashEHeN6YG/ThO0KVbYSwALCy59hQEXLhxwhpLLxxfTliAsBKYSwmgkIBQ3/bfkl3jQ/9OFoXvb5M9V7o02E0PF+Yr/4eGFo7V4Bg0WjoZ8Rj9wDAuW2LkACmbzpoejiNHMuNRwO+TCfJmWJ/iq1oN8UTRi5dG/yp7jkQpJXMtlIYH4YNZVx0IYj0D7ed919D//9+Y2DXGjwPHxpqvYwZIgoC0xvqlWVdEe7/nRXynpS/iETAsrBtdT3FEzWbn9WdY5KnskwfG2jvBTyD7B/GacE1zDgiAa37R7C4kwWWPCF2MBqKmiEiD4BuIOtYC42+Wmf6GXiAyRuLKP3pCd11KT+TpOUuEsFe3kzxBgyAtQCA1h5sVCzWoG/HdW2k33botbBlo7z9mhZvNOePsoOHjGaIeDEIcYDr4yZlSdgsExeyyZ7lJXvetHIuZzpoZqVYP46lYFvhFopHeKsuyDe+JzpNy4FodgbNBgYDUUSylXle8wCu5iZFBGaRvVYa7V5C3hEXSctcQnsBAFo+NoWkiB7zngiCCBZf6zEfE7hySdrQSnJDK1GuueVLrP07qzvI/nxsF38SkajsCcQmz7xr6p6/yfYOkth6gXbNgG8KdW7dQmm7WACZq7p9uHIs6khXO+ZuCmWBHgxvW1MXs80fyUBUdwUjLkDiKHsGcnP8Bg9GjeYDEb0iAO9oavPnAhJVAQQjOlRUJnbiaHInGUgtBiU5KQEkORCA78iRI12U4ODFCKS+HSMcSVcfeGlC3sjIyCE5QcFvI/WFCGZWnJKqPvgsTCZLTrfbXZho/ze+t7f3fG1tLVZIsOnQ7CbCpKkP7TQcWgfUb6PEwkfqC5HC70zVkxT18RD84H/IwaDFuAAAAABJRU5ErkJggg==\"\n      alt=\"Stripe\"\n      role=\"presentation\"\n    />\n  );\n}\n\nconst StripeApp: React.FC<StripeAppProps> = React.memo(\n  ({\n    total,\n    currency,\n    stripePublishableKey,\n    returnUrl,\n    createPaymentIntentApi,\n    stripePaymentMode\n  }: StripeAppProps) => {\n    const options = React.useMemo(\n      () =>\n        ({\n          mode: 'payment',\n          currency: currency.toLowerCase(),\n          amount: Number(smallUnit(total, currency)),\n          capture_method:\n            stripePaymentMode === 'capture' ? 'automatic_async' : 'manual'\n        } as StripeElementsOptions),\n      [total, currency, stripePaymentMode]\n    );\n\n    return (\n      <div className=\"stripe__app py-3\">\n        <Elements stripe={stripeLoader(stripePublishableKey)} options={options}>\n          <CheckoutForm\n            stripePublishableKey={stripePublishableKey}\n            returnUrl={returnUrl}\n            createPaymentIntentApi={createPaymentIntentApi}\n          />\n        </Elements>\n      </div>\n    );\n  }\n);\n\ninterface StripeMethodProps {\n  setting: {\n    stripeDisplayName: string;\n    stripePublishableKey: string;\n    stripePaymentMode: string;\n  };\n  cart: {\n    grandTotal: {\n      value: number;\n    };\n    currency: string;\n  };\n  returnUrl: string;\n  createPaymentIntentApi: string;\n}\n\nexport default function StripeMethod({\n  setting,\n  cart: { grandTotal, currency },\n  returnUrl,\n  createPaymentIntentApi\n}: StripeMethodProps) {\n  const { registerPaymentComponent } = useCheckoutDispatch();\n  useEffect(() => {\n    registerPaymentComponent('stripe', {\n      nameRenderer: () => (\n        <div className=\"flex items-center justify-between w-full\">\n          <span>{setting.stripeDisplayName}</span>\n          <Cards width={100} />\n        </div>\n      ),\n      formRenderer: () => (\n        <StripeApp\n          total={grandTotal.value}\n          currency={currency}\n          stripePublishableKey={setting.stripePublishableKey}\n          returnUrl={returnUrl}\n          createPaymentIntentApi={createPaymentIntentApi}\n          stripePaymentMode={setting.stripePaymentMode}\n        />\n      ),\n      checkoutButtonRenderer: () => {\n        const { checkout } = useCheckoutDispatch();\n        const { loadingStates, orderPlaced } = useCheckout();\n        const handleClick = async (e: React.MouseEvent) => {\n          e.preventDefault();\n          const validateStripe = (window as any)?.validateStripePayment;\n          if (validateStripe) {\n            await validateStripe();\n          }\n          // If validation passed, proceed with order placement\n          await checkout();\n        };\n\n        const isDisabled = loadingStates.placingOrder || orderPlaced;\n\n        return (\n          <Button\n            variant={'default'}\n            size={'xl'}\n            type=\"button\"\n            onClick={handleClick}\n            disabled={isDisabled}\n            className=\"w-full bg-linear-to-r from-indigo-500 to-purple-600 text-white py-4 px-6 rounded-lg font-semibold text-lg shadow-lg hover:from-indigo-600 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transform transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:from-indigo-500 disabled:to-purple-600\"\n          >\n            <span className=\"flex items-center justify-center space-x-2\">\n              {loadingStates.placingOrder ? (\n                <>\n                  <svg\n                    className=\"animate-spin -ml-1 mr-3 h-5 w-5 text-white\"\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    fill=\"none\"\n                    viewBox=\"0 0 24 24\"\n                  >\n                    <circle\n                      className=\"opacity-25\"\n                      cx=\"12\"\n                      cy=\"12\"\n                      r=\"10\"\n                      stroke=\"currentColor\"\n                      strokeWidth=\"4\"\n                    ></circle>\n                    <path\n                      className=\"opacity-75\"\n                      fill=\"currentColor\"\n                      d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n                    ></path>\n                  </svg>\n                  <span>{_('Processing Payment...')}</span>\n                </>\n              ) : orderPlaced ? (\n                <>\n                  <svg\n                    className=\"w-5 h-5\"\n                    fill=\"currentColor\"\n                    viewBox=\"0 0 24 24\"\n                  >\n                    <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\" />\n                  </svg>\n                  <span>{_('Order Placed')}</span>\n                </>\n              ) : (\n                <>\n                  <span>{_('Pay with Stripe')}</span>\n                  <svg\n                    className=\"w-5 h-5\"\n                    fill=\"currentColor\"\n                    viewBox=\"0 0 24 24\"\n                  >\n                    <path d=\"M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z\" />\n                  </svg>\n                </>\n              )}\n            </span>\n          </Button>\n        );\n      }\n    });\n  }, [registerPaymentComponent, setting.stripeDisplayName]);\n\n  return null;\n}\n\nexport const layout = {\n  areaId: 'checkoutForm',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    setting {\n      stripeDisplayName\n      stripePublishableKey\n      stripePaymentMode\n    }\n    cart: myCart {\n      grandTotal {\n        value\n      }\n      currency\n    }\n    returnUrl: url(routeId: \"stripeReturn\")\n    createPaymentIntentApi: url(routeId: \"createPaymentIntent\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/pages/frontStore/stripeReturn/index.js",
    "content": "import { select, update } from '@evershop/postgres-query-builder';\nimport stripePayment from 'stripe';\nimport { error } from '../../../../../lib/log/logger.js';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { getConfig } from '../../../../../lib/util/getConfig.js';\nimport { addNotification } from '../../../../../modules/base/services/notifications.js';\nimport { updatePaymentStatus } from '../../../../../modules/oms/services/updatePaymentStatus.js';\nimport { getSetting } from '../../../../../modules/setting/services/setting.js';\n\nexport default async (request, response, next) => {\n  try {\n    const { order_id, payment_intent } = request.query;\n    // Check if order exist\n    const order = await select()\n      .from('order')\n      .where('uuid', '=', order_id)\n      .load(pool);\n\n    if (!order) {\n      // Redirect to the home page\n      response.redirect(buildUrl('homepage'));\n      return;\n    }\n\n    const stripeConfig = getConfig('system.stripe', {});\n    let stripeSecretKey;\n    if (stripeConfig.secretKey) {\n      stripeSecretKey = stripeConfig.secretKey;\n    } else {\n      stripeSecretKey = await getSetting('stripeSecretKey', '');\n    }\n    const stripePaymentMode = await getSetting('stripePaymentMode', 'capture');\n    const stripe = stripePayment(stripeSecretKey);\n    const paymentIntent = await stripe.paymentIntents.retrieve(payment_intent);\n    // Check if the payment intent is succeeded\n    if (\n      (stripePaymentMode === 'capture' &&\n        paymentIntent.status === 'succeeded') ||\n      (stripePaymentMode === 'authorizeOnly' &&\n        paymentIntent.status === 'requires_capture')\n    ) {\n      // Redirect to the order success page\n      response.redirect(buildUrl('checkoutSuccess', { orderId: order_id }));\n      return;\n    } else {\n      // Redirect back to the shopping cart\n      // Active the cart\n      await update('cart')\n        .given({ status: true })\n        .where('cart_id', '=', order.cart_id)\n        .execute(pool);\n      await updatePaymentStatus(order.order_id, 'failed');\n      // Add a error notification\n      addNotification(request, 'Payment failed', 'error');\n      request.session.save(() => {\n        // Redirect to the shopping cart\n        response.redirect(buildUrl('cart'));\n      });\n      return;\n    }\n  } catch (e) {\n    error(e);\n    response.redirect(buildUrl('homepage'));\n    return;\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/pages/frontStore/stripeReturn/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/stripe/return\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/stripe/services/cancelPayment.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport stripePayment from 'stripe';\nimport { error } from '../../../lib/log/logger.js';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getConfig } from '../../../lib/util/getConfig.js';\nimport { getSetting } from '../../setting/services/setting.js';\n\nexport async function cancelPaymentIntent(orderID) {\n  try {\n    const transaction = await select()\n      .from('payment_transaction')\n      .where('payment_transaction_order_id', '=', orderID)\n      .load(pool);\n    if (!transaction) {\n      return;\n    }\n    const stripeConfig = getConfig('system.stripe', {});\n    let stripeSecretKey;\n\n    if (stripeConfig.secretKey) {\n      stripeSecretKey = stripeConfig.secretKey;\n    } else {\n      stripeSecretKey = await getSetting('stripeSecretKey', '');\n    }\n    const stripe = stripePayment(stripeSecretKey);\n\n    // Get the payment intent\n    const paymentIntent = await stripe.paymentIntents.retrieve(\n      transaction.transaction_id\n    );\n    if (!paymentIntent) {\n      throw new Error('Can not find payment intent');\n    }\n    if (paymentIntent.status === 'canceled') {\n      return;\n    }\n    await stripe.paymentIntents.cancel(transaction.transaction_id);\n  } catch (err) {\n    error(err);\n    throw err;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/createTaxClass/[context]borderParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/createTaxClass/createTaxClass.ts",
    "content": "import {\n  commit,\n  insert,\n  rollback,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport { INTERNAL_SERVER_ERROR, OK } from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { name } = request.body;\n  try {\n    const taxClass = await insert('tax_class')\n      .given({\n        name\n      })\n      .execute(connection);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: taxClass\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/createTaxClass/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\"\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\"name\"]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/createTaxClass/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/tax/classes\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/createTaxRate/[context]borderParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/createTaxRate/createTaxRate.ts",
    "content": "import {\n  commit,\n  insert,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { class_id } = request.params;\n  const { name, country, province, postcode, rate, is_compound, priority } =\n    request.body;\n  try {\n    const taxClass = await select()\n      .from('tax_class')\n      .where('uuid', '=', class_id)\n      .load(connection);\n\n    if (!taxClass) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Tax class not found'\n        }\n      });\n      return;\n    }\n\n    const taxRate = await insert('tax_rate')\n      .given({\n        name,\n        country,\n        province,\n        postcode,\n        rate,\n        is_compound,\n        priority,\n        tax_class_id: taxClass.tax_class_id\n      })\n      .execute(connection);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: taxRate\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/createTaxRate/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"maxLength\": 255,\n      \"errorMessage\": {\n        \"type\": \"Tax rate name must be a string\",\n        \"minLength\": \"Tax rate name cannot be empty\",\n        \"maxLength\": \"Tax rate name cannot exceed 255 characters\"\n      }\n    },\n    \"country\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"pattern\": \"^(\\\\*|[A-Z]{2})$\",\n      \"errorMessage\": {\n        \"type\": \"Country must be a string\",\n        \"minLength\": \"Country cannot be empty\",\n        \"pattern\": \"Country must be either '*' (all countries) or a valid 2-letter ISO code (e.g., US, CA, GB)\"\n      }\n    },\n    \"province\": {\n      \"type\": \"string\",\n      \"maxLength\": 255,\n      \"errorMessage\": {\n        \"type\": \"Province/state must be a string\",\n        \"maxLength\": \"Province/state cannot exceed 255 characters. Use '*' for all provinces\"\n      }\n    },\n    \"postcode\": {\n      \"type\": \"string\",\n      \"maxLength\": 20,\n      \"errorMessage\": {\n        \"type\": \"Postcode must be a string\",\n        \"maxLength\": \"Postcode cannot exceed 20 characters. Use '*' for all postcodes\"\n      }\n    },\n    \"rate\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n      \"minimum\": 0,\n      \"maximum\": 100,\n      \"errorMessage\": {\n        \"type\": \"Tax rate must be a number or numeric string\",\n        \"pattern\": \"Tax rate must be a valid number with up to 2 decimal places (e.g., 10, 15.5, 20.75)\",\n        \"minimum\": \"Tax rate cannot be negative\",\n        \"maximum\": \"Tax rate cannot exceed 100%\"\n      }\n    },\n    \"is_compound\": {\n      \"type\": [\"string\", \"number\", \"boolean\"],\n      \"enum\": [0, 1, \"0\", \"1\", true, false],\n      \"errorMessage\": {\n        \"type\": \"Compound tax flag must be a number, string, or boolean\",\n        \"enum\": \"Compound tax flag must be 0, 1, true, or false\"\n      }\n    },\n    \"priority\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^[0-9]+$\",\n      \"minimum\": 0,\n      \"errorMessage\": {\n        \"type\": \"Priority must be a number or numeric string\",\n        \"pattern\": \"Priority must be a non-negative integer\",\n        \"minimum\": \"Priority cannot be negative\"\n      }\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\"name\", \"rate\"],\n  \"errorMessage\": {\n    \"type\": \"Request body must be an object\",\n    \"required\": {\n      \"name\": \"Tax rate name is required\",\n      \"rate\": \"Tax rate is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/createTaxRate/route.json",
    "content": "{\n  \"methods\": [\"POST\"],\n  \"path\": \"/tax/classes/:class_id/rates\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/deleteTaxRate/[context]borderParser[auth].ts",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/deleteTaxRate/deleteTaxRate.ts",
    "content": "import {\n  commit,\n  del,\n  rollback,\n  select,\n  startTransaction\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { id } = request.params;\n  try {\n    const taxRate = await select()\n      .from('tax_rate')\n      .where('uuid', '=', id)\n      .load(connection);\n\n    if (!taxRate) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Tax rate not found'\n        }\n      });\n      return;\n    }\n\n    await del('tax_rate').where('uuid', '=', id).execute(connection);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: taxRate\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/deleteTaxRate/route.json",
    "content": "{\n  \"methods\": [\"DELETE\"],\n  \"path\": \"/tax/rates/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/updateTaxClass/[context]borderParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/updateTaxClass/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\"\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\"name\"]\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/updateTaxClass/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/tax/classes/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/updateTaxClass/updateTaxClass.js",
    "content": "import {\n  commit,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const { id } = request.params;\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { name } = request.body;\n  try {\n    // Load the tax class\n    const taxClass = await select()\n      .from('tax_class')\n      .where('uuid', '=', id)\n      .load(connection);\n\n    if (!taxClass) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Invalid class id'\n        }\n      });\n      return;\n    }\n    const newClass = await update('tax_class')\n      .given({\n        name\n      })\n      .where('uuid', '=', id)\n      .execute(connection);\n\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: newClass\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/updateTaxRate/[context]borderParser[auth].js",
    "content": "import bodyParser from 'body-parser';\n\nexport default (request, response, next) => {\n  bodyParser.json({ inflate: false })(request, response, next);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/updateTaxRate/payloadSchema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"maxLength\": 255,\n      \"errorMessage\": {\n        \"type\": \"Tax rate name must be a string\",\n        \"minLength\": \"Tax rate name cannot be empty\",\n        \"maxLength\": \"Tax rate name cannot exceed 255 characters\"\n      }\n    },\n    \"country\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"pattern\": \"^(\\\\*|[A-Z]{2})$\",\n      \"errorMessage\": {\n        \"type\": \"Country must be a string\",\n        \"minLength\": \"Country cannot be empty\",\n        \"pattern\": \"Country must be either '*' (all countries) or a valid 2-letter ISO code (e.g., US, CA, GB)\"\n      }\n    },\n    \"province\": {\n      \"type\": \"string\",\n      \"maxLength\": 255,\n      \"errorMessage\": {\n        \"type\": \"Province/state must be a string\",\n        \"maxLength\": \"Province/state cannot exceed 255 characters. Use '*' for all provinces\"\n      }\n    },\n    \"postcode\": {\n      \"type\": \"string\",\n      \"maxLength\": 20,\n      \"errorMessage\": {\n        \"type\": \"Postcode must be a string\",\n        \"maxLength\": \"Postcode cannot exceed 20 characters. Use '*' for all postcodes\"\n      }\n    },\n    \"rate\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^\\\\d+(\\\\.\\\\d{1,2})?$\",\n      \"minimum\": 0,\n      \"maximum\": 100,\n      \"errorMessage\": {\n        \"type\": \"Tax rate must be a number or numeric string\",\n        \"pattern\": \"Tax rate must be a valid number with up to 2 decimal places (e.g., 10, 15.5, 20.75)\",\n        \"minimum\": \"Tax rate cannot be negative\",\n        \"maximum\": \"Tax rate cannot exceed 100%\"\n      }\n    },\n    \"is_compound\": {\n      \"type\": [\"string\", \"number\", \"boolean\"],\n      \"enum\": [0, 1, \"0\", \"1\", true, false],\n      \"errorMessage\": {\n        \"type\": \"Compound tax flag must be a number, string, or boolean\",\n        \"enum\": \"Compound tax flag must be 0, 1, true, or false\"\n      }\n    },\n    \"priority\": {\n      \"type\": [\"string\", \"number\"],\n      \"pattern\": \"^[0-9]+$\",\n      \"minimum\": 0,\n      \"errorMessage\": {\n        \"type\": \"Priority must be a number or numeric string\",\n        \"pattern\": \"Priority must be a non-negative integer\",\n        \"minimum\": \"Priority cannot be negative\"\n      }\n    }\n  },\n  \"additionalProperties\": true,\n  \"required\": [\"name\", \"rate\"],\n  \"errorMessage\": {\n    \"type\": \"Request body must be an object\",\n    \"required\": {\n      \"name\": \"Tax rate name is required\",\n      \"rate\": \"Tax rate is required\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/updateTaxRate/route.json",
    "content": "{\n  \"methods\": [\"PATCH\"],\n  \"path\": \"/tax/rates/:id\",\n  \"access\": \"private\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/api/updateTaxRate/updateTaxRate.js",
    "content": "import {\n  commit,\n  rollback,\n  select,\n  startTransaction,\n  update\n} from '@evershop/postgres-query-builder';\nimport { getConnection } from '../../../../lib/postgres/connection.js';\nimport {\n  INTERNAL_SERVER_ERROR,\n  INVALID_PAYLOAD,\n  OK\n} from '../../../../lib/util/httpStatus.js';\n\nexport default async (request, response, next) => {\n  const connection = await getConnection();\n  await startTransaction(connection);\n  const { id } = request.params;\n  const { name, country, province, postcode, rate, is_compound, priority } =\n    request.body;\n  try {\n    const taxRate = await select()\n      .from('tax_rate')\n      .where('uuid', '=', id)\n      .load(connection);\n\n    if (!taxRate) {\n      response.status(INVALID_PAYLOAD);\n      response.json({\n        error: {\n          status: INVALID_PAYLOAD,\n          message: 'Tax rate not found'\n        }\n      });\n      return;\n    }\n\n    const newRate = await update('tax_rate')\n      .given({\n        name,\n        country,\n        province,\n        postcode,\n        rate,\n        is_compound,\n        priority\n      })\n      .where('uuid', '=', id)\n      .execute(connection);\n    await commit(connection);\n    response.status(OK);\n    response.json({\n      data: newRate\n    });\n  } catch (e) {\n    await rollback(connection);\n    response.status(INTERNAL_SERVER_ERROR);\n    response.json({\n      error: {\n        status: INTERNAL_SERVER_ERROR,\n        message: e.message\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/bootstrap.js",
    "content": "import config from 'config';\nimport { defaultPaginationFilters } from '../../lib/util/defaultPaginationFilters.js';\nimport { merge } from '../../lib/util/merge.js';\nimport { addProcessor } from '../../lib/util/registry.js';\nimport { registerCartItemTaxPercentField } from './services/registerCartItemTaxPercentField.js';\nimport { registerDefaultTaxClassCollectionFilters } from './services/registerDefaultTaxClassCollectionFilters.js';\n\nexport default () => {\n  addProcessor('cartItemFields', registerCartItemTaxPercentField, 0);\n  addProcessor('configurationSchema', (schema) => {\n    merge(schema, {\n      properties: {\n        pricing: {\n          type: 'object',\n          properties: {\n            tax: {\n              type: 'object',\n              properties: {\n                rounding: {\n                  type: 'string',\n                  enum: ['round', 'ceil', 'floor']\n                },\n                precision: {\n                  type: 'integer'\n                },\n                round_level: {\n                  type: 'string',\n                  enum: ['total', 'line', 'unit']\n                },\n                price_including_tax: {\n                  type: 'boolean'\n                }\n              }\n            }\n          }\n        }\n      }\n    });\n    return schema;\n  });\n  // Default tax configuration\n  const defaultTaxConfig = {\n    tax: {\n      rounding: 'round',\n      precision: 2,\n      round_level: 'total',\n      price_including_tax: true\n    }\n  };\n  config.util.setModuleDefaults('pricing', defaultTaxConfig);\n  // Getting config value like this: config.get('pricing.tax.rounding');\n\n  // Reigtering the default filters for tax class collection\n  addProcessor(\n    'taxClassCollectionFilters',\n    registerDefaultTaxClassCollectionFilters,\n    1\n  );\n  addProcessor(\n    'taxClassCollectionFilters',\n    (filters) => [...filters, ...defaultPaginationFilters],\n    2\n  );\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/graphql/types/Product/Price/ProductPrice.resolvers.js",
    "content": "import { toPrice } from '../../../../../../modules/checkout/services/toPrice.js';\n\nexport default {\n  Product: {\n    price: async (product) => {\n      const price = toPrice(product.price);\n      return {\n        regular: price,\n        special: price // TODO: implement special price\n      };\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/graphql/types/TaxClass/TaxClass.admin.graphql",
    "content": "\"\"\"\nRepresents a tax rate.\n\"\"\"\ntype TaxRate {\n  taxRateId: Int!\n  taxClassId: Int!\n  uuid: String!\n  name: String!\n  rate: Float!\n  isCompound: Boolean!\n  country: String!\n  province: String!\n  postcode: String!\n  priority: Int!\n  updateApi: String!\n  deleteApi: String!\n}\n\n\"\"\"\nRepresents a tax class.\n\"\"\"\ntype TaxClass {\n  taxClassId: Int!\n  uuid: String!\n  name: String!\n  rates: [TaxRate]\n  addRateApi: String!\n}\n\n\"\"\"\nReturns a collection of tax classes.\n\"\"\"\ntype TaxClassCollection {\n  items: [TaxClass]\n  currentPage: Int!\n  total: Int!\n  currentFilters: [Filter]\n}\n\nextend type Query {\n  taxClasses: TaxClassCollection\n  taxClass(id: String!): TaxClass\n}"
  },
  {
    "path": "packages/evershop/src/modules/tax/graphql/types/TaxClass/TaxClass.admin.resolvers.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../../../lib/postgres/connection.js';\nimport { buildUrl } from '../../../../../lib/router/buildUrl.js';\nimport { camelCase } from '../../../../../lib/util/camelCase.js';\nimport { TaxClassCollection } from '../../../services/TaxClassCollection.js';\n\nexport default {\n  Query: {\n    taxClasses: async (_, { filters }) => {\n      const query = select().from('tax_class');\n      const root = new TaxClassCollection(query);\n      await root.init({}, { filters });\n      return root;\n    },\n    taxClass: async (_, { id }) => {\n      const taxClass = await select()\n        .from('tax_class')\n        .where('uuid', '=', id)\n        .load(pool);\n      return camelCase(taxClass);\n    }\n  },\n  TaxClass: {\n    rates: async (parent) => {\n      const query = select().from('tax_rate');\n      query.where('tax_class_id', '=', parent.taxClassId);\n      const rates = await query.execute(pool);\n      return rates.map((row) => camelCase(row));\n    },\n    addRateApi: async ({ uuid }) =>\n      buildUrl('createTaxRate', { class_id: uuid })\n  },\n  TaxRate: {\n    updateApi: async ({ uuid }) => buildUrl('updateTaxRate', { id: uuid }),\n    deleteApi: async ({ uuid }) => buildUrl('deleteTaxRate', { id: uuid })\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/graphql/types/TaxSetting/TaxSetting.admin.graphql",
    "content": "extend type Setting {\n  defaultProductTaxClassId: Int\n  defaultShippingTaxClassId: Int\n  baseCalculationAddress: String\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/graphql/types/TaxSetting/TaxSetting.admin.resolvers.js",
    "content": "export default {\n  Setting: {\n    defaultProductTaxClassId: (setting) => {\n      const defaultProductTaxClassId = setting.find(\n        (s) => s.name === 'defaultProductTaxClassId'\n      );\n      if (defaultProductTaxClassId && defaultProductTaxClassId.value) {\n        return defaultProductTaxClassId.value;\n      } else {\n        return null;\n      }\n    },\n    defaultShippingTaxClassId: (setting) => {\n      const defaultShippingTaxClassId = setting.find(\n        (s) => s.name === 'defaultShippingTaxClassId'\n      );\n      if (defaultShippingTaxClassId && defaultShippingTaxClassId.value) {\n        return defaultShippingTaxClassId.value;\n      } else {\n        return null;\n      }\n    },\n    baseCalculationAddress: (setting) => {\n      const baseCalculationAddress = setting.find(\n        (s) => s.name === 'baseCalculationAddress'\n      );\n      if (baseCalculationAddress && baseCalculationAddress.value) {\n        return baseCalculationAddress.value;\n      } else {\n        return null;\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/graphql/types/TaxSetting/TaxSetting.graphql",
    "content": "extend type Setting {\n  priceIncludingTax: Boolean\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/graphql/types/TaxSetting/TaxSetting.resolvers.js",
    "content": "import { getConfig } from '../../../../../lib/util/getConfig.js';\n\nexport default {\n  Setting: {\n    priceIncludingTax: () => getConfig('pricing.tax.price_including_tax', false)\n  }\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/migration/Version-1.0.0.js",
    "content": "import { execute, insert } from '@evershop/postgres-query-builder';\n\nexport default async (connection) => {\n  await execute(\n    connection,\n    `CREATE TABLE \"tax_class\" (\n  \"tax_class_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"name\" varchar NOT NULL,\n  CONSTRAINT \"TAX_CLASS_UUID_UNIQUE\" UNIQUE (\"uuid\")\n)`\n  );\n\n  // Create default tax class\n  const taxClass = await insert('tax_class')\n    .given({\n      name: 'Taxable Goods'\n    })\n    .execute(connection);\n\n  // Add a constraint to product table\n  await execute(\n    connection,\n    `ALTER TABLE \"product\" ADD CONSTRAINT \"FK_TAX_CLASS\" FOREIGN KEY (\"tax_class\") REFERENCES \"tax_class\" (\"tax_class_id\") ON DELETE SET NULL`\n  );\n\n  // Prevent deleting the default tax class\n  await execute(\n    connection,\n    `CREATE OR REPLACE FUNCTION prevent_delete_default_tax_class()\n        RETURNS TRIGGER\n        LANGUAGE PLPGSQL\n        AS\n      $$\n      BEGIN\n        IF OLD.tax_class_id = 1 THEN\n          RAISE EXCEPTION 'Cannot delete default tax class';\n        END IF;\n        RETURN OLD;\n      END;\n      $$`\n  );\n  await execute(\n    connection,\n    `CREATE TRIGGER \"PREVENT_DELETING_THE_DEFAULT_TAX_CLASS\"\n        BEFORE DELETE ON tax_class\n        FOR EACH ROW\n        EXECUTE PROCEDURE prevent_delete_default_tax_class();`\n  );\n\n  await execute(\n    connection,\n    `CREATE TABLE \"tax_rate\" (\n  \"tax_rate_id\" INT GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,\n  \"uuid\" UUID NOT NULL DEFAULT gen_random_uuid (),\n  \"name\" varchar NOT NULL,\n  \"tax_class_id\" INT DEFAULT NULL,\n  \"country\" varchar NOT NULL DEFAULT '*'::character varying,\n  \"province\" varchar NOT NULL DEFAULT '*'::character varying,\n  \"postcode\" varchar NOT NULL DEFAULT '*'::character varying,\n  \"rate\" decimal(12,4) NOT NULL,\n  \"is_compound\" boolean NOT NULL DEFAULT FALSE,\n  \"priority\" INT NOT NULL,\n  CONSTRAINT \"TAX_RATE_UUID_UNIQUE\" UNIQUE (\"uuid\"),\n  CONSTRAINT \"TAX_RATE_PRIORITY_UNIQUE\" UNIQUE (\"priority\", \"tax_class_id\"),\n  CONSTRAINT \"UNSIGNED_RATE\" CHECK(rate >= 0),\n  CONSTRAINT \"UNSIGNED_PRIORITY\" CHECK(priority >= 0),\n  CONSTRAINT \"FK_TAX_RATE_TAX_CLASS\" FOREIGN KEY (\"tax_class_id\") REFERENCES \"tax_class\" (\"tax_class_id\") ON DELETE CASCADE\n)`\n  );\n\n  // Create default tax rate for tax class\n  await insert('tax_rate')\n    .given({\n      name: 'Tax',\n      tax_class_id: taxClass.insertId,\n      country: '*',\n      province: '*',\n      postcode: '*',\n      rate: 0,\n      is_compound: false,\n      priority: 0\n    })\n    .execute(connection);\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/pages/admin/all/TaxSettingMenu.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  Item,\n  ItemActions,\n  ItemContent,\n  ItemDescription,\n  ItemTitle\n} from '@components/common/ui/Item.js';\nimport { cn } from '@evershop/evershop/lib/util/cn';\nimport { Settings } from 'lucide-react';\nimport React from 'react';\n\ninterface TaxSettingMenuProps {\n  taxSettingUrl: string;\n}\n\nexport default function TaxSettingMenu({ taxSettingUrl }: TaxSettingMenuProps) {\n  const isActive =\n    typeof window !== 'undefined' &&\n    new URL(taxSettingUrl, window.location.origin).pathname ===\n      window.location.pathname;\n\n  return (\n    <Item\n      variant={'outline'}\n      className={cn(\n        isActive && 'bg-primary/5 border-primary/20 dark:bg-primary/10'\n      )}\n      data-active={isActive ? 'true' : 'false'}\n    >\n      <ItemContent>\n        <ItemTitle>\n          <div>\n            <a\n              href={taxSettingUrl}\n              className={cn(\n                'uppercase text-xs font-semibold',\n                isActive && 'text-primary'\n              )}\n            >\n              Tax Setting\n            </a>\n          </div>\n        </ItemTitle>\n        <ItemDescription>\n          <div>Configure tax classes and tax rates</div>\n        </ItemDescription>\n      </ItemContent>\n      <ItemActions>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          onClick={() => (window.location.href = taxSettingUrl)}\n        >\n          <Settings className=\"h-4 w-4 mr-1\" />\n        </Button>\n      </ItemActions>\n    </Item>\n  );\n}\n\nexport const layout = {\n  areaId: 'settingPageMenu',\n  sortOrder: 20\n};\n\nexport const query = `\n  query Query {\n    taxSettingUrl: url(routeId: \"taxSetting\")\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/pages/admin/taxSetting/TaxSetting.tsx",
    "content": "import { SettingMenu } from '@components/admin/SettingMenu.js';\nimport Spinner from '@components/admin/Spinner.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { SelectField } from '@components/common/form/SelectField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@components/common/ui/Card.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport React from 'react';\nimport { useQuery } from 'urql';\nimport { TaxClasses } from './components/TaxClasses.js';\nimport { TaxClassForm } from './components/TaxClassForm.js';\n\nconst CountriesQuery = `\n  query Country($countries: [String]) {\n    countries (countries: $countries) {\n      value: code\n      label: name\n      provinces {\n        value: code\n        label: name\n      }\n    }\n  }\n`;\n\nconst TaxClassesQuery = `\n  query TaxClasses {\n    taxClasses {\n      items {\n        taxClassId\n        uuid\n        name\n        rates {\n          taxRateId\n          uuid\n          name\n          rate\n          isCompound\n          country\n          province\n          postcode\n          priority\n          updateApi\n          deleteApi\n        }\n        addRateApi\n      }\n    }\n  }\n`;\n\ninterface TaxSettingProps {\n  createTaxClassApi: string;\n  saveSettingApi: string;\n  setting: {\n    defaultProductTaxClassId?: number;\n    defaultShippingTaxClassId?: number;\n    baseCalculationAddress?: string;\n  };\n}\nexport default function TaxSetting({\n  createTaxClassApi,\n  saveSettingApi,\n  setting\n}: TaxSettingProps) {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const [countriesQueryData] = useQuery({\n    query: CountriesQuery\n  });\n\n  const [taxClassesQueryData, reexecuteQuery] = useQuery({\n    query: TaxClassesQuery\n  });\n\n  if (countriesQueryData.fetching || taxClassesQueryData.fetching) {\n    return (\n      <div className=\"main-content-inner\">\n        <div className=\"grid grid-cols-6 gap-x-5 grid-flow-row \">\n          <div className=\"col-span-2\">\n            <SettingMenu />\n          </div>\n          <div className=\"col-span-4\">\n            <Card>\n              <CardContent>\n                <Spinner width={30} height={30} />\n              </CardContent>\n            </Card>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"main-content-inner\">\n      <div className=\"grid grid-cols-6 gap-x-5 grid-flow-row \">\n        <div className=\"col-span-2\">\n          <SettingMenu />\n        </div>\n        <div className=\"col-span-4 grid grid-cols-1 gap-5\">\n          <Card>\n            <CardHeader>\n              <CardTitle>Tax calculation configuration</CardTitle>\n              <CardDescription>\n                Configure the tax classes that will be available to your\n                customers at checkout.\n              </CardDescription>\n            </CardHeader>\n            <CardContent title=\"Basic configuration\">\n              <Form\n                id=\"taxBasicConfig\"\n                method=\"POST\"\n                action={saveSettingApi}\n                successMessage=\"Tax setting has been saved successfully!\"\n              >\n                <div className=\"grid grid-cols-2 gap-5\">\n                  <div>\n                    <SelectField\n                      name=\"defaultShippingTaxClassId\"\n                      label=\"Shipping tax class\"\n                      defaultValue={setting.defaultShippingTaxClassId}\n                      placeholder=\"None\"\n                      options={[\n                        {\n                          value: -1,\n                          label: 'Proportional allocation based on cart items'\n                        },\n                        {\n                          value: 0,\n                          label: 'Higest tax rate based on cart items'\n                        }\n                      ].concat(\n                        taxClassesQueryData.data.taxClasses.items.map(\n                          (taxClass) => ({\n                            value: taxClass.taxClassId,\n                            label: taxClass.name\n                          })\n                        ) || []\n                      )}\n                      helperText=\"This is the tax class applied to shipping costs.\"\n                    />\n                  </div>\n                  <div>\n                    <SelectField\n                      name=\"baseCalculationAddress\"\n                      label=\"Base calculation address\"\n                      defaultValue={setting.baseCalculationAddress || ''}\n                      options={[\n                        {\n                          value: 'shippingAddress',\n                          label: 'Shipping address'\n                        },\n                        {\n                          value: 'billingAddress',\n                          label: 'Billing address'\n                        },\n                        {\n                          value: 'storeAddress',\n                          label: 'Store address'\n                        }\n                      ]}\n                      helperText=\"This is the address used to calculate tax rates.\"\n                    />\n                  </div>\n                </div>\n              </Form>\n            </CardContent>\n          </Card>\n          <Card title=\"Tax classes\">\n            <CardHeader>\n              <CardTitle>Tax classes</CardTitle>\n              <CardDescription>\n                Manage tax classes and tax rates for different regions.\n              </CardDescription>\n            </CardHeader>\n            <TaxClasses\n              classes={taxClassesQueryData.data.taxClasses.items}\n              getTaxClasses={reexecuteQuery}\n            />\n            <CardContent>\n              <div>\n                <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n                  <DialogTrigger>\n                    <Button\n                      title=\"Create new tax class\"\n                      variant=\"outline\"\n                      onClick={() => setDialogOpen(true)}\n                    >\n                      Create new tax class\n                    </Button>\n                  </DialogTrigger>\n                  <DialogContent>\n                    <DialogHeader>\n                      <DialogTitle>Create New Tax Class</DialogTitle>\n                    </DialogHeader>\n                    <TaxClassForm\n                      saveTaxClassApi={createTaxClassApi}\n                      closeModal={() => setDialogOpen(false)}\n                      getTaxClasses={reexecuteQuery}\n                    />\n                  </DialogContent>\n                </Dialog>\n              </div>\n            </CardContent>\n          </Card>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport const layout = {\n  areaId: 'content',\n  sortOrder: 10\n};\n\nexport const query = `\n  query Query {\n    createTaxClassApi: url(routeId: \"createTaxClass\")\n    saveSettingApi: url(routeId: \"saveSetting\")\n    setting {\n      defaultProductTaxClassId\n      defaultShippingTaxClassId\n      baseCalculationAddress\n    }\n  }\n`;\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/pages/admin/taxSetting/components/Rate.tsx",
    "content": "import {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport React from 'react';\nimport { RateForm } from './RateForm.js';\n\nexport interface TaxRate {\n  uuid: string;\n  name: string;\n  rate: number;\n  isCompound: boolean;\n  priority: number;\n  country: string;\n  province: string;\n  postcode: string;\n  updateApi: string;\n  deleteApi: string;\n}\ninterface RateProps {\n  rate: TaxRate;\n  getTaxClasses: (options?: { requestPolicy?: string }) => Promise<void> | void;\n}\n\nfunction Rate({ rate, getTaxClasses }: RateProps) {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  return (\n    <>\n      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n        <td className=\"border-none py-2 w-1/5\">{rate.name}</td>\n        <td className=\"border-none py-2\">{rate.country}</td>\n        <td className=\"border-none py-2\">{rate.rate}%</td>\n        <td className=\"border-none py-2\">{rate.isCompound ? 'Yes' : 'No'}</td>\n        <td className=\"border-none py-2\">{rate.priority}</td>\n        <td className=\"border-none py-2\">\n          <DialogTrigger>\n            <a\n              href=\"#\"\n              onClick={(e) => {\n                e.preventDefault();\n              }}\n            >\n              Edit\n            </a>\n          </DialogTrigger>\n          <a\n            href=\"#\"\n            className=\"text-destructive ml-5\"\n            onClick={async (e) => {\n              e.preventDefault();\n              await fetch(rate.deleteApi, {\n                method: 'DELETE'\n              });\n              await getTaxClasses({ requestPolicy: 'network-only' });\n            }}\n          >\n            Delete\n          </a>\n        </td>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>Edit Tax Rate</DialogTitle>\n          </DialogHeader>\n          <RateForm\n            saveRateApi={rate.updateApi}\n            closeModal={() => setDialogOpen(false)}\n            getTaxClasses={getTaxClasses}\n            rate={rate}\n          />\n        </DialogContent>\n      </Dialog>\n    </>\n  );\n}\n\nexport { Rate };\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/pages/admin/taxSetting/components/RateForm.tsx",
    "content": "import Spinner from '@components/admin/Spinner.js';\nimport { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { NumberField } from '@components/common/form/NumberField.js';\nimport { ToggleField } from '@components/common/form/ToggleField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\nimport { toast } from 'react-toastify';\nimport { useQuery } from 'urql';\nimport { TaxRate } from './Rate.js';\n\nconst MethodsQuery = `\n  query Methods {\n    shippingMethods {\n      value: shippingMethodId\n      label: name\n    }\n    createShippingMethodApi: url(routeId: \"createShippingMethod\")\n  }\n`;\n\ninterface MethodFormProps {\n  saveRateApi: string;\n  closeModal: () => void;\n  getTaxClasses: (options?: { requestPolicy?: string }) => Promise<void> | void;\n  rate?: TaxRate;\n}\n\nfunction RateForm({\n  saveRateApi,\n  closeModal,\n  getTaxClasses,\n  rate\n}: MethodFormProps) {\n  const form = useForm({\n    shouldUnregister: true\n  });\n  const [saving, setSaving] = React.useState(false);\n  const [result] = useQuery({\n    query: MethodsQuery\n  });\n\n  if (result.fetching) {\n    return (\n      <div className=\"flex justify-center p-2\">\n        <Spinner width={25} height={25} />\n      </div>\n    );\n  }\n\n  return (\n    <Form\n      form={form}\n      id=\"taxRateForm\"\n      method={rate ? 'PATCH' : 'POST'}\n      action={saveRateApi}\n      submitBtn={false}\n      onError={(error: string) => {\n        toast.error(error);\n        setSaving(false);\n      }}\n      onSuccess={async (response) => {\n        if (!response.error) {\n          await getTaxClasses({ requestPolicy: 'network-only' });\n          closeModal();\n          toast.success('Tax rate has been saved successfully!');\n        } else {\n        }\n        setSaving(false);\n      }}\n    >\n      <div className=\"py-3 border-t border-border\">\n        <div className=\"grid grid-cols-2 gap-5\">\n          <div>\n            <InputField\n              name=\"name\"\n              placeholder=\"Name\"\n              required\n              validation={{ required: 'Name is required' }}\n              label=\"Name\"\n              defaultValue={rate?.name}\n            />\n          </div>\n          <div>\n            <NumberField\n              name=\"rate\"\n              label=\"Rate\"\n              placeholder=\"Rate\"\n              required\n              validation={{ required: 'Rate is required' }}\n              defaultValue={rate?.rate}\n            />\n          </div>\n        </div>\n      </div>\n      <div className=\"py-3 border-t border-border\">\n        <div className=\"grid grid-cols-3 gap-5\">\n          <div>\n            <InputField\n              name=\"country\"\n              label=\"Country\"\n              placeholder=\"Country\"\n              required\n              validation={{ required: 'Country is required' }}\n              defaultValue={rate?.country}\n              helperText='Country code (e.g., \"US\"). Use \"*\" for all countries.'\n            />\n          </div>\n          <div>\n            <InputField\n              name=\"province\"\n              label=\"Provinces\"\n              placeholder=\"Provinces\"\n              required\n              validation={{ required: 'Provinces is required' }}\n              defaultValue={rate?.province}\n              helperText='Province code (e.g., \"CA\"). Use \"*\" for all provinces.'\n            />\n          </div>\n          <div>\n            <InputField\n              name=\"postcode\"\n              label=\"Postcode\"\n              placeholder=\"Postcode\"\n              required\n              validation={{ required: 'Postcode is required' }}\n              defaultValue={rate?.postcode}\n              helperText='Postcode (e.g., \"90210\"). Empty for all postcodes.'\n            />\n          </div>\n        </div>\n        <div className=\"grid grid-cols-2 gap-5 mt-5\">\n          <div>\n            <ToggleField\n              name=\"is_compound\"\n              label=\"Is compound\"\n              defaultValue={rate?.isCompound || false}\n            />\n          </div>\n          <div />\n        </div>\n        <div className=\"grid grid-cols-2 gap-5 mt-5\">\n          <div>\n            <NumberField\n              name=\"priority\"\n              label=\"Priority\"\n              placeholder=\"Priority\"\n              validation={{ required: 'Priority is required' }}\n              required\n              defaultValue={rate?.priority}\n            />\n          </div>\n          <div />\n        </div>\n      </div>\n      <div className=\"flex justify-end gap-2\">\n        <Button title=\"Cancel\" variant=\"secondary\" onClick={closeModal}>\n          Cancel\n        </Button>\n        <Button\n          title=\"Save\"\n          variant=\"default\"\n          onClick={async () => {\n            const result = await form.trigger();\n            if (!result) {\n              return;\n            }\n            setSaving(true);\n            (\n              document.getElementById('taxRateForm') as HTMLFormElement\n            ).dispatchEvent(\n              new Event('submit', {\n                cancelable: true,\n                bubbles: true\n              })\n            );\n          }}\n          isLoading={saving}\n        >\n          Save\n        </Button>\n      </div>\n    </Form>\n  );\n}\n\nexport { RateForm };\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/pages/admin/taxSetting/components/Rates.tsx",
    "content": "import { Button } from '@components/common/ui/Button.js';\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger\n} from '@components/common/ui/Dialog.js';\nimport {\n  Table,\n  TableBody,\n  TableHead,\n  TableHeader,\n  TableRow\n} from '@components/common/ui/Table.js';\nimport React from 'react';\nimport { Rate, TaxRate } from './Rate.js';\nimport { RateForm } from './RateForm.js';\n\ninterface RatesProps {\n  getTaxClasses: (options?: { requestPolicy?: string }) => Promise<void> | void;\n  rates: Array<TaxRate>;\n  addRateApi: string;\n}\nexport function Rates({ getTaxClasses, rates, addRateApi }: RatesProps) {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  return (\n    <>\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead className=\"border-none\">Name</TableHead>\n            <TableHead className=\"border-none\">Country</TableHead>\n            <TableHead className=\"border-none\">Rate</TableHead>\n            <TableHead className=\"border-none\">Compound</TableHead>\n            <TableHead className=\"border-none\">Priority</TableHead>\n            <TableHead className=\"border-none\">Action</TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {rates.map((rate) => (\n            <TableRow key={rate.uuid} className=\"border-divider py-5\">\n              <Rate rate={rate} getTaxClasses={getTaxClasses} />\n            </TableRow>\n          ))}\n        </TableBody>\n      </Table>\n      <div className=\"mt-2\">\n        <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n          <DialogTrigger>\n            <Button\n              variant=\"link\"\n              onClick={(e) => {\n                e.preventDefault();\n                setDialogOpen(true);\n              }}\n            >\n              + Add Rate\n            </Button>\n          </DialogTrigger>\n          <DialogContent>\n            <DialogHeader>\n              <DialogTitle>Add Tax Rate</DialogTitle>\n            </DialogHeader>\n            <RateForm\n              saveRateApi={addRateApi}\n              closeModal={() => setDialogOpen(false)}\n              getTaxClasses={getTaxClasses}\n            />\n          </DialogContent>\n        </Dialog>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/pages/admin/taxSetting/components/TaxClass.tsx",
    "content": "import { CardContent } from '@components/common/ui/Card.js';\nimport React from 'react';\nimport { TaxRate } from './Rate.js';\nimport { Rates } from './Rates.js';\n\ninterface TaxClassProps {\n  taxClass: {\n    name: string;\n    rates: Array<TaxRate>;\n    addRateApi: string;\n  };\n  getTaxClasses: (options?: { requestPolicy?: string }) => Promise<void> | void;\n}\n\nfunction TaxClass({ taxClass, getTaxClasses }: TaxClassProps) {\n  return (\n    <CardContent className=\"py-3 border-t border-border\">\n      <div className=\"text-xs uppercase font-semibold py-2\">\n        {taxClass.name}\n      </div>\n      <div className=\"divide-y border rounded border-divider\">\n        <div className=\"flex justify-start items-center border-divider mt-5\">\n          <div className=\"grow px-2\">\n            <Rates\n              rates={taxClass.rates}\n              addRateApi={taxClass.addRateApi}\n              getTaxClasses={getTaxClasses}\n            />\n          </div>\n        </div>\n      </div>\n    </CardContent>\n  );\n}\n\nexport { TaxClass };\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/pages/admin/taxSetting/components/TaxClassForm.tsx",
    "content": "import { Form } from '@components/common/form/Form.js';\nimport { InputField } from '@components/common/form/InputField.js';\nimport { Button } from '@components/common/ui/Button.js';\nimport React from 'react';\n\ninterface TaxClassFormProps {\n  saveTaxClassApi: string;\n  closeModal: () => void;\n  getTaxClasses: (options?: { requestPolicy?: string }) => Promise<void> | void;\n}\n\nfunction TaxClassForm({\n  saveTaxClassApi,\n  closeModal,\n  getTaxClasses\n}: TaxClassFormProps) {\n  return (\n    <Form\n      id=\"createTaxClass\"\n      method=\"POST\"\n      action={saveTaxClassApi}\n      submitBtn={false}\n      onSuccess={async () => {\n        await getTaxClasses({ requestPolicy: 'network-only' });\n        closeModal();\n      }}\n    >\n      <InputField\n        name=\"name\"\n        type=\"text\"\n        label=\"Tax class name\"\n        defaultValue=\"\"\n        placeholder=\"Enter tax class name\"\n        required\n        validation={{ required: 'Tax class name is required' }}\n      />\n      <div className=\"flex justify-end gap-2 mt-3\">\n        <Button title=\"Cancel\" variant=\"secondary\" onClick={closeModal}>\n          Cancel\n        </Button>\n        <Button\n          title=\"Save\"\n          variant=\"default\"\n          onClick={() => {\n            (\n              document.getElementById('createTaxClass') as HTMLFormElement\n            ).dispatchEvent(\n              new Event('submit', {\n                cancelable: true,\n                bubbles: true\n              })\n            );\n          }}\n        >\n          Save\n        </Button>\n      </div>\n    </Form>\n  );\n}\n\nexport { TaxClassForm };\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/pages/admin/taxSetting/components/TaxClasses.tsx",
    "content": "import React from 'react';\nimport { TaxRate } from './Rate.js';\nimport { TaxClass } from './TaxClass.js';\n\ninterface TaxClassesProps {\n  getTaxClasses: (options?: { requestPolicy?: string }) => Promise<void> | void;\n  classes: Array<{\n    uuid: string;\n    name: string;\n    rates: Array<TaxRate>;\n    addRateApi: string;\n  }>;\n}\n\nexport function TaxClasses({ getTaxClasses, classes }: TaxClassesProps) {\n  return (\n    <>\n      {classes.map((taxClass) => (\n        <TaxClass\n          key={taxClass.uuid}\n          taxClass={taxClass}\n          getTaxClasses={getTaxClasses}\n        />\n      ))}\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/pages/admin/taxSetting/index.ts",
    "content": "import { setPageMetaInfo } from '../../../../cms/services/pageMetaInfo.js';\n\nexport default (request) => {\n  setPageMetaInfo(request, {\n    title: 'Tax Setting',\n    description: 'Tax Setting'\n  });\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/pages/admin/taxSetting/route.json",
    "content": "{\n  \"methods\": [\"GET\"],\n  \"path\": \"/setting/tax\"\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/services/TaxClassCollection.js",
    "content": "import { pool } from '../../../lib/postgres/connection.js';\nimport { camelCase } from '../../../lib/util/camelCase.js';\nimport { getValue } from '../../../lib/util/registry.js';\n\nexport class TaxClassCollection {\n  constructor(baseQuery) {\n    this.baseQuery = baseQuery;\n  }\n\n  async init(args, { filters = [] }) {\n    const currentFilters = [];\n\n    // Apply the filters\n    const taxClassCollectionFilters = await getValue(\n      'taxClassCollectionFilters',\n      []\n    );\n\n    taxClassCollectionFilters.forEach((filter) => {\n      const check = filters.find(\n        (f) => f.key === filter.key && filter.operation.includes(f.operation)\n      );\n      if (filter.key === '*' || check) {\n        filter.callback(\n          this.baseQuery,\n          check?.operation,\n          check?.value,\n          currentFilters\n        );\n      }\n    });\n\n    // Clone the main query for getting total right before doing the paging\n    const totalQuery = this.baseQuery.clone();\n    totalQuery.select('COUNT(*)', 'total');\n    totalQuery.removeOrderBy();\n    totalQuery.removeLimit();\n\n    this.currentFilters = currentFilters;\n    this.totalQuery = totalQuery;\n  }\n\n  async items() {\n    const items = await this.baseQuery.execute(pool);\n    return items.map((row) => camelCase(row));\n  }\n\n  async total() {\n    // Call items to get the total\n    const total = await this.totalQuery.execute(pool);\n    return total[0].total;\n  }\n\n  currentFilters() {\n    return this.currentFilters;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/services/calculateTaxAmount.js",
    "content": "import { getConfig } from '../../../lib/util/getConfig.js';\n\nexport function calculateTaxAmount(\n  taxPercentage,\n  price,\n  quantity = 1,\n  priceIncludingTax = false\n) {\n  const rounding = getConfig('pricing.tax.rounding', 'round');\n  const roundingLevel = getConfig('pricing.tax.round_level', 'unit');\n  const precision = getConfig('pricing.tax.precision', '2');\n  const precisionFix = 10 ** precision;\n\n  const taxAmountUnit =\n    priceIncludingTax === false\n      ? (price * taxPercentage) / 100\n      : (price * taxPercentage) / (100 + taxPercentage);\n  if (roundingLevel === 'unit') {\n    // Calculate the tax amount\n    let taxAmount = 0;\n    switch (rounding) {\n      case 'up':\n        taxAmount = Math.ceil(taxAmountUnit * precisionFix) / precisionFix;\n        break;\n      case 'down':\n        taxAmount = Math.floor(taxAmountUnit * precisionFix) / precisionFix;\n        break;\n      case 'round':\n        taxAmount = Math.round(taxAmountUnit * precisionFix) / precisionFix;\n        break;\n      default:\n        taxAmount = Math.round(taxAmountUnit * precisionFix) / precisionFix;\n        break;\n    }\n    return Math.round(taxAmount * precisionFix * quantity) / precisionFix;\n  } else if (roundingLevel === 'line') {\n    // Calculate the tax amount\n    let taxAmount = taxAmountUnit * quantity;\n    switch (rounding) {\n      case 'up':\n        taxAmount = Math.ceil(taxAmount * precisionFix) / precisionFix;\n        break;\n      case 'down':\n        taxAmount = Math.floor(taxAmount * precisionFix) / precisionFix;\n        break;\n      case 'round':\n        taxAmount = Math.round(taxAmount * precisionFix) / precisionFix;\n        break;\n      default:\n        taxAmount = Math.round(taxAmount * precisionFix) / precisionFix;\n        break;\n    }\n    return taxAmount;\n  } else {\n    return taxAmountUnit * quantity; // Rounding will be done in the resolver of the total tax amount in the cart\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/services/getTaxPercent.js",
    "content": "export function getTaxPercent(rates) {\n  let taxPercent = 0;\n\n  rates.forEach((rate) => {\n    const taxRate = rate.rate / 100;\n    if (rate.is_compound === true) {\n      taxPercent = taxPercent + taxRate + taxPercent * taxRate;\n    } else {\n      taxPercent += taxRate;\n    }\n  });\n\n  return taxPercent * 100;\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/services/getTaxRates.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\n\nexport async function getTaxRates(\n  taxClassId,\n  country,\n  province,\n  postcode = null\n) {\n  if (!country) {\n    return [];\n  }\n  const taxRatesQuery = select().from('tax_rate');\n  taxRatesQuery.where('tax_class_id', '=', taxClassId);\n  taxRatesQuery.orderBy('priority', 'ASC');\n  let taxRates = await taxRatesQuery.execute(pool);\n\n  if (!taxRates) {\n    return [];\n  } else {\n    // Country, postcode, province is a text field, comma separated incase of multiple values.\n    // We need to convert them to array for comparison.\n    taxRates.forEach((taxRate) => {\n      // Remove empty values (all whitespace)\n      taxRate.country = taxRate.country\n        ? taxRate.country.split(',').filter((item) => item.trim() !== '')\n        : [];\n      taxRate.province = taxRate.province\n        ? taxRate.province.split(',').filter((item) => item.trim() !== '')\n        : [];\n      taxRate.postcode = taxRate.postcode\n        ? taxRate.postcode.split(',').filter((item) => item.trim() !== '')\n        : [];\n\n      // If country, province, postcode is empty, we need to set it to * so that it can be compared with the request.\n      if (taxRate.country.length === 0) {\n        taxRate.country.push('*');\n      }\n      if (taxRate.province.length === 0) {\n        taxRate.province.push('*');\n      }\n      if (taxRate.postcode.length === 0) {\n        taxRate.postcode.push('*');\n      }\n    });\n\n    // Filter and get the applicable tax rates based on the provided address.\n    taxRates = taxRates.filter((taxRate) => {\n      if (\n        (taxRate.country.includes(country) || taxRate.country.includes('*')) &&\n        (taxRate.province.includes(province) ||\n          taxRate.province.includes('*') ||\n          province === null) &&\n        (taxRate.postcode.includes(postcode) ||\n          taxRate.postcode.includes('*') ||\n          postcode === null)\n      ) {\n        return true;\n      }\n      return false;\n    });\n\n    return taxRates;\n  }\n}\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/services/registerCartItemTaxPercentField.js",
    "content": "import { select } from '@evershop/postgres-query-builder';\nimport { pool } from '../../../lib/postgres/connection.js';\nimport { getSetting } from '../../setting/services/setting.js';\nimport { getTaxPercent } from './getTaxPercent.js';\nimport { getTaxRates } from './getTaxRates.js';\n\nexport const registerCartItemTaxPercentField = (fields) => {\n  const newFields = fields.concat([\n    {\n      key: 'tax_percent',\n      resolvers: [\n        async function resolver() {\n          if (!this.getData('tax_class_id')) {\n            return 0;\n          } else {\n            const taxClass = await select()\n              .from('tax_class')\n              .where('tax_class_id', '=', this.getData('tax_class_id'))\n              .load(pool);\n            if (!taxClass) {\n              return 0;\n            } else {\n              const baseCalculationAddress = await getSetting(\n                'baseCalculationAddress',\n                'shippingAddress'\n              );\n              if (baseCalculationAddress === 'storeAddress') {\n                const percentage = getTaxPercent(\n                  await getTaxRates(\n                    this.getData('tax_class_id'),\n                    await getSetting('storeCountry', null),\n                    await getSetting('storeProvince', null),\n                    await getSetting('storePostalCode', null)\n                  )\n                );\n                return percentage;\n              } else {\n                const cart = this.getCart();\n                const addressId =\n                  baseCalculationAddress === 'billingAddress' ||\n                  cart.getData('no_shipping_required') === true\n                    ? cart.getData('billing_address_id')\n                    : cart.getData('shipping_address_id');\n\n                if (!addressId) {\n                  return 0;\n                } else {\n                  const address = await select()\n                    .from('cart_address')\n                    .where('cart_address_id', '=', addressId)\n                    .load(pool);\n                  if (!address) {\n                    return 0;\n                  } else {\n                    const percentage = getTaxPercent(\n                      await getTaxRates(\n                        this.getData('tax_class_id'),\n                        address.country,\n                        address.province,\n                        address.postcode\n                      )\n                    );\n                    return percentage;\n                  }\n                }\n              }\n            }\n          }\n        }\n      ],\n      dependencies: ['cart_id', 'tax_class_id', 'no_shipping_required']\n    }\n  ]);\n  return newFields;\n};\n"
  },
  {
    "path": "packages/evershop/src/modules/tax/services/registerDefaultTaxClassCollectionFilters.js",
    "content": "import { OPERATION_MAP } from '../../../lib/util/filterOperationMap.js';\nimport { getValueSync } from '../../../lib/util/registry.js';\n\nexport async function registerDefaultTaxClassCollectionFilters() {\n  // List of default supported filters\n  const defaultFilters = [\n    {\n      key: 'name',\n      operation: ['like'],\n      callback: (query, operation, value, currentFilters) => {\n        query.andWhere(\n          'tax_class.name',\n          OPERATION_MAP[operation],\n          `%${value}%`\n        );\n        currentFilters.push({\n          key: 'name',\n          operation,\n          value\n        });\n      }\n    },\n    {\n      key: 'ob',\n      operation: ['eq'],\n      callback: (query, operation, value, currentFilters) => {\n        const taxClassCollectionSortBy = getValueSync(\n          'taxClassCollectionSortBy',\n          {\n            name: (query) => query.orderBy('tax_class.name')\n          }\n        );\n\n        if (taxClassCollectionSortBy[value]) {\n          taxClassCollectionSortBy[value](query, operation);\n          currentFilters.push({\n            key: 'ob',\n            operation,\n            value\n          });\n        }\n      }\n    }\n  ];\n\n  return defaultFilters;\n}\n"
  },
  {
    "path": "packages/evershop/src/types/apiResponse.ts",
    "content": "export interface ErrorResponse {\n  error: {\n    status: number;\n    message: string;\n  };\n}\n\nexport interface SuccessResponse<T> {\n  error: undefined;\n  data: T;\n}\n\nexport type ApiResponse<T> = ErrorResponse | SuccessResponse<T>;\n"
  },
  {
    "path": "packages/evershop/src/types/appContext.tsx",
    "content": "import { PageMetaInfo } from './pageMeta.js';\n\ntype GraphqlScalar = string | number | boolean | null;\ntype GraphqlResponseValue =\n  | GraphqlScalar\n  | GraphqlResponseValue[]\n  | { [key: string]: GraphqlResponseValue };\n\ninterface Config {\n  pageMeta: PageMetaInfo;\n  tax: {\n    priceIncludingTax: boolean;\n  };\n  catalog: {\n    imageDimensions: { width: number; height: number };\n  };\n}\n\ninterface AppStateContextValue {\n  graphqlResponse: Record<string, GraphqlResponseValue>;\n  config: Config;\n  propsMap: Record<string, any[]>;\n  widgets?: {\n    areaId: string[];\n    id: string;\n    type: string;\n    sortOrder: number;\n  }[];\n  fetching: boolean;\n}\n\ninterface AppContextDispatchValue {\n  setData: React.Dispatch<React.SetStateAction<AppStateContextValue>>;\n  fetchPageData: (url: string | URL) => Promise<void>;\n}\n\nexport { AppStateContextValue, Config, AppContextDispatchValue };\n"
  },
  {
    "path": "packages/evershop/src/types/atLeastOne.ts",
    "content": "export type AtLeastOne<T> = {\n  [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;\n}[keyof T];\n"
  },
  {
    "path": "packages/evershop/src/types/checkoutData.ts",
    "content": "import { Address } from './customerAddress.js';\n\nexport interface CheckoutData {\n  customer?: {\n    id?: string;\n    email: string;\n    fullName?: string;\n  };\n  shippingAddress?: Address;\n  billingAddress?: Address;\n  paymentMethod?: string;\n  shippingMethod?: string;\n  [key: string]: unknown;\n}\n"
  },
  {
    "path": "packages/evershop/src/types/componentLayout.ts",
    "content": "export type ComponentLayout = {\n  areaId: string;\n  sortOrder: number;\n};\n"
  },
  {
    "path": "packages/evershop/src/types/cronjob.ts",
    "content": "export interface Job {\n  name: string;\n  resolve: string;\n  schedule: string;\n  enabled: boolean;\n}\n"
  },
  {
    "path": "packages/evershop/src/types/customerAddress.ts",
    "content": "export interface CustomerAddressGraphql {\n  uuid?: string;\n  fullName?: string | null;\n  telephone?: string | null;\n  address1?: string | null;\n  address2?: string | null;\n  city?: string | null;\n  postcode?: string | null;\n  province?: {\n    code: string;\n    name: string;\n  } | null;\n  country?: {\n    code: string;\n    name: string;\n  };\n}\n\nexport interface Address {\n  uuid?: string | null;\n  full_name?: string | null;\n  address_1?: string | null;\n  address_2?: string | null;\n  city?: string | null;\n  province?: string | null;\n  country?: string | null;\n  postcode?: string | null;\n  telephone?: string | number | null;\n  [key: string]: unknown;\n}\n"
  },
  {
    "path": "packages/evershop/src/types/db/index.ts",
    "content": "/**\n * ============================================================================\n * EVERSHOP DATABASE TYPES\n * ============================================================================\n *\n * This file contains TypeScript type definitions for all database tables.\n * These types are auto-generated from the PostgreSQL schema and should be\n * used throughout the codebase for type safety.\n *\n * Usage:\n *   import type { OrderRow, ProductRow } from '@evershop/evershop/src/types/db';\n *\n * Conventions:\n *   - `XxxRow`: Represents a full row from the table (SELECT *)\n *   - `XxxInsert`: Fields for inserting (omits auto-generated fields)\n *   - `XxxUpdate`: Fields for updating (all optional except PK)\n */\n\n// =============================================================================\n// ADMIN USER\n// =============================================================================\n\nexport interface AdminUserRow {\n  admin_user_id: number;\n  uuid: string;\n  status: boolean;\n  email: string;\n  password: string;\n  full_name: string | null;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type AdminUserInsert = Omit<\n  AdminUserRow,\n  'admin_user_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type AdminUserUpdate = Partial<Omit<AdminUserRow, 'admin_user_id'>>;\n\n// =============================================================================\n// ATTRIBUTE\n// =============================================================================\n\nexport interface AttributeRow {\n  attribute_id: number;\n  uuid: string;\n  attribute_code: string;\n  attribute_name: string;\n  type: string;\n  is_required: boolean;\n  display_on_frontend: boolean;\n  sort_order: number;\n  is_filterable: boolean;\n}\n\nexport type AttributeInsert = Omit<AttributeRow, 'attribute_id' | 'uuid'>;\nexport type AttributeUpdate = Partial<Omit<AttributeRow, 'attribute_id'>>;\n\n// =============================================================================\n// ATTRIBUTE GROUP\n// =============================================================================\n\nexport interface AttributeGroupRow {\n  attribute_group_id: number;\n  uuid: string;\n  group_name: string;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type AttributeGroupInsert = Omit<\n  AttributeGroupRow,\n  'attribute_group_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type AttributeGroupUpdate = Partial<\n  Omit<AttributeGroupRow, 'attribute_group_id'>\n>;\n\n// =============================================================================\n// ATTRIBUTE GROUP LINK\n// =============================================================================\n\nexport interface AttributeGroupLinkRow {\n  attribute_group_link_id: number;\n  attribute_id: number;\n  group_id: number;\n}\n\nexport type AttributeGroupLinkInsert = Omit<\n  AttributeGroupLinkRow,\n  'attribute_group_link_id'\n>;\nexport type AttributeGroupLinkUpdate = Partial<\n  Omit<AttributeGroupLinkRow, 'attribute_group_link_id'>\n>;\n\n// =============================================================================\n// ATTRIBUTE OPTION\n// =============================================================================\n\nexport interface AttributeOptionRow {\n  attribute_option_id: number;\n  uuid: string;\n  attribute_id: number;\n  attribute_code: string;\n  option_text: string;\n}\n\nexport type AttributeOptionInsert = Omit<\n  AttributeOptionRow,\n  'attribute_option_id' | 'uuid'\n>;\nexport type AttributeOptionUpdate = Partial<\n  Omit<AttributeOptionRow, 'attribute_option_id'>\n>;\n\n// =============================================================================\n// CART\n// =============================================================================\n\nexport interface CartRow {\n  cart_id: number;\n  uuid: string;\n  sid: string | null;\n  currency: string;\n  customer_id: number | null;\n  customer_group_id: number | null;\n  customer_email: string | null;\n  customer_full_name: string | null;\n  user_ip: string | null;\n  status: boolean;\n  coupon: string | null;\n  shipping_fee_excl_tax: number | null;\n  shipping_fee_incl_tax: number | null;\n  discount_amount: number | null;\n  sub_total: number;\n  sub_total_incl_tax: number;\n  sub_total_with_discount: number;\n  sub_total_with_discount_incl_tax: number;\n  total_qty: number;\n  total_weight: number | null;\n  tax_amount: number;\n  tax_amount_before_discount: number;\n  shipping_tax_amount: number;\n  grand_total: number;\n  shipping_method: string | null;\n  shipping_method_name: string | null;\n  shipping_zone_id: number | null;\n  shipping_address_id: number | null;\n  payment_method: string | null;\n  payment_method_name: string | null;\n  billing_address_id: number | null;\n  shipping_note: string | null;\n  created_at: Date;\n  updated_at: Date;\n  total_tax_amount: number | null;\n  no_shipping_required: boolean;\n}\n\nexport type CartInsert = Omit<\n  CartRow,\n  'cart_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type CartUpdate = Partial<Omit<CartRow, 'cart_id'>>;\n\n// =============================================================================\n// CART ADDRESS\n// =============================================================================\n\nexport interface CartAddressRow {\n  cart_address_id: number;\n  uuid: string;\n  full_name: string | null;\n  postcode: string | null;\n  telephone: string | null;\n  country: string | null;\n  province: string | null;\n  city: string | null;\n  address_1: string | null;\n  address_2: string | null;\n}\n\nexport type CartAddressInsert = Omit<\n  CartAddressRow,\n  'cart_address_id' | 'uuid'\n>;\nexport type CartAddressUpdate = Partial<\n  Omit<CartAddressRow, 'cart_address_id'>\n>;\n\n// =============================================================================\n// CART ITEM\n// =============================================================================\n\nexport interface CartItemRow {\n  cart_item_id: number;\n  uuid: string;\n  cart_id: number;\n  product_id: number;\n  product_sku: string;\n  product_name: string;\n  thumbnail: string | null;\n  product_weight: number | null;\n  product_price: number;\n  product_price_incl_tax: number;\n  qty: number;\n  final_price: number;\n  final_price_incl_tax: number;\n  tax_percent: number;\n  tax_amount: number;\n  tax_amount_before_discount: number;\n  discount_amount: number;\n  line_total: number;\n  line_total_with_discount: number;\n  line_total_incl_tax: number;\n  line_total_with_discount_incl_tax: number;\n  variant_group_id: number | null;\n  variant_options: string | null;\n  product_custom_options: string | null;\n  created_at: Date;\n  updated_at: Date;\n  no_shipping_required: boolean;\n}\n\nexport type CartItemInsert = Omit<\n  CartItemRow,\n  'cart_item_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type CartItemUpdate = Partial<Omit<CartItemRow, 'cart_item_id'>>;\n\n// =============================================================================\n// CATEGORY\n// =============================================================================\n\nexport interface CategoryRow {\n  category_id: number;\n  uuid: string;\n  status: boolean;\n  parent_id: number | null;\n  include_in_nav: boolean;\n  position: number | null;\n  show_products: boolean;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type CategoryInsert = Omit<\n  CategoryRow,\n  'category_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type CategoryUpdate = Partial<Omit<CategoryRow, 'category_id'>>;\n\n// =============================================================================\n// CATEGORY DESCRIPTION\n// =============================================================================\n\nexport interface CategoryDescriptionRow {\n  category_description_id: number;\n  category_description_category_id: number;\n  name: string;\n  short_description: string | null;\n  description: string | null;\n  image: string | null;\n  meta_title: string | null;\n  meta_keywords: string | null;\n  meta_description: string | null;\n  url_key: string;\n}\n\nexport type CategoryDescriptionInsert = Omit<\n  CategoryDescriptionRow,\n  'category_description_id'\n>;\nexport type CategoryDescriptionUpdate = Partial<\n  Omit<CategoryDescriptionRow, 'category_description_id'>\n>;\n\n// =============================================================================\n// CMS PAGE\n// =============================================================================\n\nexport interface CmsPageRow {\n  cms_page_id: number;\n  uuid: string;\n  status: boolean | null;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type CmsPageInsert = Omit<\n  CmsPageRow,\n  'cms_page_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type CmsPageUpdate = Partial<Omit<CmsPageRow, 'cms_page_id'>>;\n\n// =============================================================================\n// CMS PAGE DESCRIPTION\n// =============================================================================\n\nexport interface CmsPageDescriptionRow {\n  cms_page_description_id: number;\n  cms_page_description_cms_page_id: number | null;\n  url_key: string;\n  name: string;\n  content: string | null;\n  meta_title: string | null;\n  meta_keywords: string | null;\n  meta_description: string | null;\n}\n\nexport type CmsPageDescriptionInsert = Omit<\n  CmsPageDescriptionRow,\n  'cms_page_description_id'\n>;\nexport type CmsPageDescriptionUpdate = Partial<\n  Omit<CmsPageDescriptionRow, 'cms_page_description_id'>\n>;\n\n// =============================================================================\n// COLLECTION\n// =============================================================================\n\nexport interface CollectionRow {\n  collection_id: number;\n  uuid: string;\n  name: string;\n  description: string | null;\n  code: string;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type CollectionInsert = Omit<\n  CollectionRow,\n  'collection_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type CollectionUpdate = Partial<Omit<CollectionRow, 'collection_id'>>;\n\n// =============================================================================\n// COUPON\n// =============================================================================\n\nexport interface CouponRow {\n  coupon_id: number;\n  uuid: string;\n  status: boolean;\n  description: string;\n  discount_amount: number;\n  free_shipping: boolean;\n  discount_type: string;\n  coupon: string;\n  used_time: number;\n  target_products: Record<string, unknown> | null;\n  condition: Record<string, unknown> | null;\n  user_condition: Record<string, unknown> | null;\n  buyx_gety: Record<string, unknown> | null;\n  max_uses_time_per_coupon: number | null;\n  max_uses_time_per_customer: number | null;\n  start_date: Date | null;\n  end_date: Date | null;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type CouponInsert = Omit<\n  CouponRow,\n  'coupon_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type CouponUpdate = Partial<Omit<CouponRow, 'coupon_id'>>;\n\n// =============================================================================\n// CUSTOMER\n// =============================================================================\n\nexport interface CustomerRow {\n  customer_id: number;\n  uuid: string;\n  status: number;\n  group_id: number | null;\n  email: string;\n  password: string;\n  full_name: string | null;\n  created_at: Date;\n  updated_at: Date;\n  is_google_login: boolean;\n}\n\nexport type CustomerInsert = Omit<\n  CustomerRow,\n  'customer_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type CustomerUpdate = Partial<Omit<CustomerRow, 'customer_id'>>;\n\n// =============================================================================\n// CUSTOMER ADDRESS\n// =============================================================================\n\nexport interface CustomerAddressRow {\n  customer_address_id: number;\n  uuid: string;\n  customer_id: number;\n  full_name: string | null;\n  telephone: string | null;\n  address_1: string | null;\n  address_2: string | null;\n  postcode: string | null;\n  city: string | null;\n  province: string | null;\n  country: string;\n  created_at: Date;\n  updated_at: Date;\n  is_default: boolean | null;\n}\n\nexport type CustomerAddressInsert = Omit<\n  CustomerAddressRow,\n  'customer_address_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type CustomerAddressUpdate = Partial<\n  Omit<CustomerAddressRow, 'customer_address_id'>\n>;\n\n// =============================================================================\n// CUSTOMER GROUP\n// =============================================================================\n\nexport interface CustomerGroupRow {\n  customer_group_id: number;\n  uuid: string;\n  group_name: string;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type CustomerGroupInsert = Omit<\n  CustomerGroupRow,\n  'customer_group_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type CustomerGroupUpdate = Partial<\n  Omit<CustomerGroupRow, 'customer_group_id'>\n>;\n\n// =============================================================================\n// EVENT\n// =============================================================================\n\nexport interface EventRow {\n  event_id: number;\n  uuid: string;\n  name: string;\n  data: Record<string, unknown> | null;\n  created_at: Date;\n}\n\nexport type EventInsert = Omit<EventRow, 'event_id' | 'uuid' | 'created_at'>;\nexport type EventUpdate = Partial<Omit<EventRow, 'event_id'>>;\n\n// =============================================================================\n// MIGRATION\n// =============================================================================\n\nexport interface MigrationRow {\n  migration_id: number;\n  module: string;\n  version: string;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type MigrationInsert = Omit<\n  MigrationRow,\n  'migration_id' | 'created_at' | 'updated_at'\n>;\nexport type MigrationUpdate = Partial<Omit<MigrationRow, 'migration_id'>>;\n\n// =============================================================================\n// ORDER\n// =============================================================================\n\nexport interface OrderRow {\n  order_id: number;\n  uuid: string;\n  integration_order_id: string | null;\n  sid: string | null;\n  order_number: string;\n  cart_id: number;\n  currency: string;\n  customer_id: number | null;\n  customer_email: string | null;\n  customer_full_name: string | null;\n  user_ip: string | null;\n  user_agent: string | null;\n  coupon: string | null;\n  shipping_fee_excl_tax: number | null;\n  shipping_fee_incl_tax: number | null;\n  discount_amount: number | null;\n  sub_total: number;\n  sub_total_incl_tax: number;\n  sub_total_with_discount: number;\n  sub_total_with_discount_incl_tax: number;\n  total_qty: number;\n  total_weight: number | null;\n  tax_amount: number;\n  tax_amount_before_discount: number;\n  shipping_tax_amount: number;\n  shipping_note: string | null;\n  grand_total: number;\n  shipping_method: string | null;\n  shipping_method_name: string | null;\n  shipping_address_id: number | null;\n  payment_method: string | null;\n  payment_method_name: string | null;\n  billing_address_id: number | null;\n  shipment_status: string;\n  payment_status: string;\n  created_at: Date;\n  updated_at: Date;\n  total_tax_amount: number | null;\n  status: string | null;\n  no_shipping_required: boolean;\n}\n\nexport type OrderInsert = Omit<\n  OrderRow,\n  'order_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type OrderUpdate = Partial<Omit<OrderRow, 'order_id'>>;\n\n// =============================================================================\n// ORDER ACTIVITY\n// =============================================================================\n\nexport interface OrderActivityRow {\n  order_activity_id: number;\n  uuid: string;\n  order_activity_order_id: number;\n  comment: string;\n  customer_notified: boolean;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type OrderActivityInsert = Omit<\n  OrderActivityRow,\n  'order_activity_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type OrderActivityUpdate = Partial<\n  Omit<OrderActivityRow, 'order_activity_id'>\n>;\n\n// =============================================================================\n// ORDER ADDRESS\n// =============================================================================\n\nexport interface OrderAddressRow {\n  order_address_id: number;\n  uuid: string;\n  full_name: string | null;\n  postcode: string | null;\n  telephone: string | null;\n  country: string | null;\n  province: string | null;\n  city: string | null;\n  address_1: string | null;\n  address_2: string | null;\n}\n\nexport type OrderAddressInsert = Omit<\n  OrderAddressRow,\n  'order_address_id' | 'uuid'\n>;\nexport type OrderAddressUpdate = Partial<\n  Omit<OrderAddressRow, 'order_address_id'>\n>;\n\n// =============================================================================\n// ORDER ITEM\n// =============================================================================\n\nexport interface OrderItemRow {\n  order_item_id: number;\n  uuid: string;\n  order_item_order_id: number;\n  product_id: number;\n  referer: number | null;\n  product_sku: string;\n  product_name: string;\n  thumbnail: string | null;\n  product_weight: number | null;\n  product_price: number;\n  product_price_incl_tax: number;\n  qty: number;\n  final_price: number;\n  final_price_incl_tax: number;\n  tax_percent: number;\n  tax_amount: number;\n  tax_amount_before_discount: number;\n  discount_amount: number;\n  line_total: number;\n  line_total_with_discount: number;\n  line_total_incl_tax: number;\n  line_total_with_discount_incl_tax: number;\n  variant_group_id: number | null;\n  variant_options: string | null;\n  product_custom_options: string | null;\n  requested_data: string | null;\n  no_shipping_required: boolean;\n}\n\nexport type OrderItemInsert = Omit<OrderItemRow, 'order_item_id' | 'uuid'>;\nexport type OrderItemUpdate = Partial<Omit<OrderItemRow, 'order_item_id'>>;\n\n// =============================================================================\n// PAYMENT TRANSACTION\n// =============================================================================\n\nexport interface PaymentTransactionRow {\n  payment_transaction_id: number;\n  uuid: string;\n  payment_transaction_order_id: number;\n  transaction_id: string | null;\n  transaction_type: string;\n  amount: number;\n  parent_transaction_id: string | null;\n  payment_action: string | null;\n  additional_information: string | null;\n  created_at: Date;\n}\n\nexport type PaymentTransactionInsert = Omit<\n  PaymentTransactionRow,\n  'payment_transaction_id' | 'uuid' | 'created_at'\n>;\nexport type PaymentTransactionUpdate = Partial<\n  Omit<PaymentTransactionRow, 'payment_transaction_id'>\n>;\n\n// =============================================================================\n// PRODUCT\n// =============================================================================\n\nexport interface ProductRow {\n  product_id: number;\n  uuid: string;\n  type: string;\n  variant_group_id: number | null;\n  visibility: boolean;\n  group_id: number | null;\n  sku: string;\n  price: number;\n  weight: number | null;\n  tax_class: number | null;\n  status: boolean;\n  created_at: Date;\n  updated_at: Date;\n  category_id: number | null;\n  no_shipping_required: boolean;\n}\n\nexport type ProductInsert = Omit<\n  ProductRow,\n  'product_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type ProductUpdate = Partial<Omit<ProductRow, 'product_id'>>;\n\n// =============================================================================\n// PRODUCT ATTRIBUTE VALUE INDEX\n// =============================================================================\n\nexport interface ProductAttributeValueIndexRow {\n  product_attribute_value_index_id: number;\n  product_id: number;\n  attribute_id: number;\n  option_id: number | null;\n  option_text: string | null;\n}\n\nexport type ProductAttributeValueIndexInsert = Omit<\n  ProductAttributeValueIndexRow,\n  'product_attribute_value_index_id'\n>;\nexport type ProductAttributeValueIndexUpdate = Partial<\n  Omit<ProductAttributeValueIndexRow, 'product_attribute_value_index_id'>\n>;\n\n// =============================================================================\n// PRODUCT CATEGORY (Junction Table)\n// =============================================================================\n\nexport interface ProductCategoryRow {\n  product_category_id: number;\n  category_id: number;\n  product_id: number;\n}\n\nexport type ProductCategoryInsert = Omit<\n  ProductCategoryRow,\n  'product_category_id'\n>;\nexport type ProductCategoryUpdate = Partial<\n  Omit<ProductCategoryRow, 'product_category_id'>\n>;\n\n// =============================================================================\n// PRODUCT COLLECTION (Junction Table)\n// =============================================================================\n\nexport interface ProductCollectionRow {\n  product_collection_id: number;\n  collection_id: number;\n  product_id: number;\n}\n\nexport type ProductCollectionInsert = Omit<\n  ProductCollectionRow,\n  'product_collection_id'\n>;\nexport type ProductCollectionUpdate = Partial<\n  Omit<ProductCollectionRow, 'product_collection_id'>\n>;\n\n// =============================================================================\n// PRODUCT CUSTOM OPTION\n// =============================================================================\n\nexport interface ProductCustomOptionRow {\n  product_custom_option_id: number;\n  uuid: string;\n  product_custom_option_product_id: number;\n  option_name: string;\n  option_type: string;\n  is_required: boolean;\n  sort_order: number | null;\n}\n\nexport type ProductCustomOptionInsert = Omit<\n  ProductCustomOptionRow,\n  'product_custom_option_id' | 'uuid'\n>;\nexport type ProductCustomOptionUpdate = Partial<\n  Omit<ProductCustomOptionRow, 'product_custom_option_id'>\n>;\n\n// =============================================================================\n// PRODUCT CUSTOM OPTION VALUE\n// =============================================================================\n\nexport interface ProductCustomOptionValueRow {\n  product_custom_option_value_id: number;\n  uuid: string;\n  option_id: number;\n  extra_price: number | null;\n  sort_order: number | null;\n  value: string;\n}\n\nexport type ProductCustomOptionValueInsert = Omit<\n  ProductCustomOptionValueRow,\n  'product_custom_option_value_id' | 'uuid'\n>;\nexport type ProductCustomOptionValueUpdate = Partial<\n  Omit<ProductCustomOptionValueRow, 'product_custom_option_value_id'>\n>;\n\n// =============================================================================\n// PRODUCT DESCRIPTION\n// =============================================================================\n\nexport interface ProductDescriptionRow {\n  product_description_id: number;\n  product_description_product_id: number;\n  name: string;\n  description: string | null;\n  short_description: string | null;\n  url_key: string;\n  meta_title: string | null;\n  meta_description: string | null;\n  meta_keywords: string | null;\n}\n\nexport type ProductDescriptionInsert = Omit<\n  ProductDescriptionRow,\n  'product_description_id'\n>;\nexport type ProductDescriptionUpdate = Partial<\n  Omit<ProductDescriptionRow, 'product_description_id'>\n>;\n\n// =============================================================================\n// PRODUCT IMAGE\n// =============================================================================\n\nexport interface ProductImageRow {\n  product_image_id: number;\n  product_image_product_id: number;\n  origin_image: string;\n  thumb_image: string | null;\n  listing_image: string | null;\n  single_image: string | null;\n  is_main: boolean;\n}\n\nexport type ProductImageInsert = Omit<ProductImageRow, 'product_image_id'>;\nexport type ProductImageUpdate = Partial<\n  Omit<ProductImageRow, 'product_image_id'>\n>;\n\n// =============================================================================\n// PRODUCT INVENTORY\n// =============================================================================\n\nexport interface ProductInventoryRow {\n  product_inventory_id: number;\n  product_inventory_product_id: number;\n  qty: number;\n  manage_stock: boolean;\n  stock_availability: boolean;\n}\n\nexport type ProductInventoryInsert = Omit<\n  ProductInventoryRow,\n  'product_inventory_id'\n>;\nexport type ProductInventoryUpdate = Partial<\n  Omit<ProductInventoryRow, 'product_inventory_id'>\n>;\n\n// =============================================================================\n// RESET PASSWORD TOKEN\n// =============================================================================\n\nexport interface ResetPasswordTokenRow {\n  reset_password_token_id: number;\n  customer_id: number;\n  token: string;\n  created_at: Date;\n}\n\nexport type ResetPasswordTokenInsert = Omit<\n  ResetPasswordTokenRow,\n  'reset_password_token_id' | 'created_at'\n>;\nexport type ResetPasswordTokenUpdate = Partial<\n  Omit<ResetPasswordTokenRow, 'reset_password_token_id'>\n>;\n\n// =============================================================================\n// SESSION\n// =============================================================================\n\nexport interface SessionRow {\n  sid: string;\n  sess: Record<string, unknown>;\n  expire: Date;\n}\n\nexport type SessionInsert = SessionRow;\nexport type SessionUpdate = Partial<Omit<SessionRow, 'sid'>>;\n\n// =============================================================================\n// SETTING\n// =============================================================================\n\nexport interface SettingRow {\n  setting_id: number;\n  uuid: string;\n  name: string;\n  value: string | null;\n  is_json: boolean;\n}\n\nexport type SettingInsert = Omit<SettingRow, 'setting_id' | 'uuid'>;\nexport type SettingUpdate = Partial<Omit<SettingRow, 'setting_id'>>;\n\n// =============================================================================\n// SHIPMENT\n// =============================================================================\n\nexport interface ShipmentRow {\n  shipment_id: number;\n  uuid: string;\n  shipment_order_id: number;\n  carrier: string | null;\n  tracking_number: string | null;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type ShipmentInsert = Omit<\n  ShipmentRow,\n  'shipment_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type ShipmentUpdate = Partial<Omit<ShipmentRow, 'shipment_id'>>;\n\n// =============================================================================\n// SHIPPING METHOD\n// =============================================================================\n\nexport interface ShippingMethodRow {\n  shipping_method_id: number;\n  uuid: string;\n  name: string;\n}\n\nexport type ShippingMethodInsert = Omit<\n  ShippingMethodRow,\n  'shipping_method_id' | 'uuid'\n>;\nexport type ShippingMethodUpdate = Partial<\n  Omit<ShippingMethodRow, 'shipping_method_id'>\n>;\n\n// =============================================================================\n// SHIPPING ZONE\n// =============================================================================\n\nexport interface ShippingZoneRow {\n  shipping_zone_id: number;\n  uuid: string;\n  name: string;\n  country: string;\n}\n\nexport type ShippingZoneInsert = Omit<\n  ShippingZoneRow,\n  'shipping_zone_id' | 'uuid'\n>;\nexport type ShippingZoneUpdate = Partial<\n  Omit<ShippingZoneRow, 'shipping_zone_id'>\n>;\n\n// =============================================================================\n// SHIPPING ZONE METHOD\n// =============================================================================\n\nexport interface ShippingZoneMethodRow {\n  shipping_zone_method_id: number;\n  method_id: number;\n  zone_id: number;\n  is_enabled: boolean;\n  cost: number | null;\n  calculate_api: string | null;\n  condition_type: string | null;\n  max: number | null;\n  min: number | null;\n  price_based_cost: Record<string, unknown> | null;\n  weight_based_cost: Record<string, unknown> | null;\n}\n\nexport type ShippingZoneMethodInsert = Omit<\n  ShippingZoneMethodRow,\n  'shipping_zone_method_id'\n>;\nexport type ShippingZoneMethodUpdate = Partial<\n  Omit<ShippingZoneMethodRow, 'shipping_zone_method_id'>\n>;\n\n// =============================================================================\n// SHIPPING ZONE PROVINCE\n// =============================================================================\n\nexport interface ShippingZoneProvinceRow {\n  shipping_zone_province_id: number;\n  uuid: string;\n  zone_id: number;\n  province: string;\n}\n\nexport type ShippingZoneProvinceInsert = Omit<\n  ShippingZoneProvinceRow,\n  'shipping_zone_province_id' | 'uuid'\n>;\nexport type ShippingZoneProvinceUpdate = Partial<\n  Omit<ShippingZoneProvinceRow, 'shipping_zone_province_id'>\n>;\n\n// =============================================================================\n// TAX CLASS\n// =============================================================================\n\nexport interface TaxClassRow {\n  tax_class_id: number;\n  uuid: string;\n  name: string;\n}\n\nexport type TaxClassInsert = Omit<TaxClassRow, 'tax_class_id' | 'uuid'>;\nexport type TaxClassUpdate = Partial<Omit<TaxClassRow, 'tax_class_id'>>;\n\n// =============================================================================\n// TAX RATE\n// =============================================================================\n\nexport interface TaxRateRow {\n  tax_rate_id: number;\n  uuid: string;\n  name: string;\n  tax_class_id: number | null;\n  country: string;\n  province: string;\n  postcode: string;\n  rate: number;\n  is_compound: boolean;\n  priority: number;\n}\n\nexport type TaxRateInsert = Omit<TaxRateRow, 'tax_rate_id' | 'uuid'>;\nexport type TaxRateUpdate = Partial<Omit<TaxRateRow, 'tax_rate_id'>>;\n\n// =============================================================================\n// URL REWRITE\n// =============================================================================\n\nexport interface UrlRewriteRow {\n  url_rewrite_id: number;\n  language: string;\n  request_path: string;\n  target_path: string;\n  entity_uuid: string | null;\n  entity_type: string | null;\n}\n\nexport type UrlRewriteInsert = Omit<UrlRewriteRow, 'url_rewrite_id'>;\nexport type UrlRewriteUpdate = Partial<Omit<UrlRewriteRow, 'url_rewrite_id'>>;\n\n// =============================================================================\n// VARIANT GROUP\n// =============================================================================\n\nexport interface VariantGroupRow {\n  variant_group_id: number;\n  uuid: string;\n  attribute_group_id: number;\n  attribute_one: number | null;\n  attribute_two: number | null;\n  attribute_three: number | null;\n  attribute_four: number | null;\n  attribute_five: number | null;\n  visibility: boolean;\n}\n\nexport type VariantGroupInsert = Omit<\n  VariantGroupRow,\n  'variant_group_id' | 'uuid'\n>;\nexport type VariantGroupUpdate = Partial<\n  Omit<VariantGroupRow, 'variant_group_id'>\n>;\n\n// =============================================================================\n// WIDGET\n// =============================================================================\n\nexport interface WidgetRow {\n  widget_id: number;\n  uuid: string;\n  name: string;\n  type: string;\n  route: Record<string, unknown>[];\n  area: Record<string, unknown>[];\n  sort_order: number;\n  settings: Record<string, unknown>;\n  status: boolean | null;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type WidgetInsert = Omit<\n  WidgetRow,\n  'widget_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type WidgetUpdate = Partial<Omit<WidgetRow, 'widget_id'>>;\n\n// =============================================================================\n// SITE (Cloud specific - may not exist in all installations)\n// =============================================================================\n\nexport interface SiteRow {\n  site_id: number;\n  uuid: string;\n  order_id: number;\n  user_id: number;\n  name: string;\n  type: string;\n  domain: string;\n  configuration: Record<string, unknown> | null;\n  db_name: string;\n  db_username: string;\n  db_password: string;\n  admin_email: string;\n  admin_password: string | null;\n  github_installation_id: string | null;\n  github_repository: string | null;\n  github_branch: string | null;\n  version: string | null;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport type SiteInsert = Omit<\n  SiteRow,\n  'site_id' | 'uuid' | 'created_at' | 'updated_at'\n>;\nexport type SiteUpdate = Partial<Omit<SiteRow, 'site_id'>>;\n\n// =============================================================================\n// QUERY BUILDER RESULT TYPES\n// =============================================================================\n\n/**\n * Result type from insert operations via postgres-query-builder\n */\nexport interface InsertResult<T = Record<string, unknown>> {\n  insertId: number;\n  [key: string]: unknown;\n}\n\n/**\n * Result type from update operations via postgres-query-builder\n */\nexport interface UpdateResult {\n  rowCount: number;\n}\n\n/**\n * Helper type for creating insert results that include all row data\n */\nexport type InsertResultWithRow<T> = T & { insertId: number };\n"
  },
  {
    "path": "packages/evershop/src/types/event.ts",
    "content": "import {\n  CategoryRow,\n  CustomerRow,\n  OrderRow,\n  ProductImageRow,\n  ProductInventoryRow,\n  ProductRow\n} from './db/index.js';\n/**\n * Event registry that maps event names to their data types.\n * Extend this interface in your modules to register custom events.\n *\n * @example\n * ```typescript\n * // In your module\n * declare module '@evershop/evershop/types/event' {\n *   interface EventDataRegistry {\n *     'order_placed': {\n *       orderId: number;\n *       customerId: number;\n *       total: number;\n *       items: Array<{ productId: number; quantity: number }>;\n *     };\n *     'customer_registered': {\n *       customerId: number;\n *       email: string;\n *       name: string;\n *     };\n *   }\n * }\n * ```\n */\nexport interface EventDataRegistry {\n  /**\n   * Fired when a new product is created\n   * Data: Complete product table row\n   */\n  product_created: ProductRow;\n\n  /**\n   * Fired when a product is updated\n   * Data: Complete product table row\n   */\n  product_updated: ProductRow;\n\n  /**\n   * Fired when a product is deleted\n   * Data: Complete product table row\n   */\n  product_deleted: ProductRow;\n\n  /**\n   * Fired when a product image is added\n   * Data: Complete product_image table row\n   */\n  product_image_added: ProductImageRow;\n\n  /**\n   * Fired when a new category is created\n   * Data: Complete category table row\n   */\n  category_created: CategoryRow;\n\n  /**\n   * Fired when a category is updated\n   * Data: Complete category table row\n   */\n  category_updated: CategoryRow;\n  /**\n   * Fired when a category is deleted\n   * Data: Complete category table row\n   */\n  category_deleted: CategoryRow;\n\n  /**\n   * Fired when product inventory is updated\n   * Data: Complete product_inventory table row\n   */\n  inventory_updated: {\n    old: ProductInventoryRow;\n    new: ProductInventoryRow;\n  };\n\n  /**\n   * Fired when a new customer is registered\n   * Data: Complete customer table row\n   */\n  customer_registered: CustomerRow;\n\n  /**\n   * Fired when a new order is created\n   * Data: Complete order table row\n   */\n  order_created: OrderRow;\n\n  /**\n   * Fired when a new order is placed\n   * Data: Complete order table row\n   */\n  order_placed: OrderRow;\n\n  /**\n   * Fired when a new customer is created by admin\n   * Data: Complete customer table row\n   */\n  customer_created: CustomerRow;\n\n  /**\n   * Fired when a customer is updated by admin\n   * Data: Complete customer table row\n   */\n  customer_updated: CustomerRow;\n\n  /**\n   * Fired when a customer is deleted by admin\n   * Data: Complete customer table row\n   */\n  customer_deleted: CustomerRow;\n}\n\n/**\n * Extract event names from the registry\n */\nexport type EventName = keyof EventDataRegistry;\n\n/**\n * Get the data type for a specific event\n */\nexport type EventData<T extends EventName> = EventDataRegistry[T];\n"
  },
  {
    "path": "packages/evershop/src/types/extension.ts",
    "content": "export type Extension = {\n  name: string;\n  resolve: string;\n  srcPath?: string;\n  path: string;\n  enabled: boolean;\n  priority: number;\n};\n"
  },
  {
    "path": "packages/evershop/src/types/graphqlFilter.ts",
    "content": "export type GraphQLFilter = {\n  key: string;\n  operation: GraphQLFilterOperation;\n  value: any;\n};\nexport type GraphQLFilterOperation =\n  | 'eq'\n  | 'neq'\n  | 'gt'\n  | 'gte'\n  | 'lt'\n  | 'lte'\n  | 'in'\n  | 'nin'\n  | 'like'\n  | 'nlike';\n"
  },
  {
    "path": "packages/evershop/src/types/middleware.ts",
    "content": "import { NextFunction } from 'express';\nimport { EvershopRequest } from './request.js';\nimport { EvershopResponse } from './response.js';\n\nexport type SyncMiddlewareFunction<T, D> = (\n  req: EvershopRequest,\n  res: EvershopResponse,\n  next?: NextFunction\n) => T;\n\nexport type AsyncMiddlewareFunction<T, D> = (\n  req: EvershopRequest,\n  res: EvershopResponse,\n  next?: NextFunction\n) => Promise<T>;\n\nexport type ErrorMiddlewareFunction = (\n  err: Error,\n  req: EvershopRequest,\n  res: EvershopResponse,\n  next?: NextFunction\n) => void;\n\nexport interface SyncMiddleware<T, D> extends Middleware {\n  callback: SyncMiddlewareFunction<T, D>;\n}\n\nexport interface AsyncMiddleware<T, D> extends Middleware {\n  callback: AsyncMiddlewareFunction<T, D>;\n}\n\n// --------------------------------------------------------------------------\n\nexport type ENext = (error?: Error, ...args: any[]) => void;\n\nexport type MiddlewareFunction = (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next: ENext\n) => void;\n\nexport type MiddlewareFunctionWrapper = (\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next: ENext\n) => void;\n\nexport type ErrorMiddlewareFunctionWrapper = (\n  error: Error,\n  request: EvershopRequest,\n  response: EvershopResponse,\n  next: ENext\n) => void;\n\nexport interface Middleware {\n  routeId: string;\n  id: string;\n  path: string;\n  scope: 'app' | 'route';\n  region: 'pages' | 'api';\n  before?: string[];\n  after?: string[];\n  middleware: MiddlewareFunctionWrapper | ErrorMiddlewareFunctionWrapper;\n}\n"
  },
  {
    "path": "packages/evershop/src/types/order.ts",
    "content": "export type PaymentStatus = {\n  name: string;\n  badge: string;\n  isDefault: boolean;\n  isCancelable?: boolean;\n};\n\nexport type ShipmentStatus = {\n  name: string;\n  badge: string;\n  isDefault: boolean;\n  isCancelable?: boolean;\n};\n\nexport type OrderStatus = {\n  name: string;\n  badge: string;\n  isDefault: boolean;\n  next: string[];\n};\n"
  },
  {
    "path": "packages/evershop/src/types/pageMeta.ts",
    "content": "export interface OgInfo {\n  locale: string;\n  title: string;\n  description: string;\n  image: string;\n  url: string;\n  type: 'website' | 'article' | 'product' | string;\n  siteName: string;\n  twitterCard: 'summary' | 'summary_large_image' | 'app' | 'player' | string;\n  twitterSite: string;\n  twitterCreator: string;\n  twitterImage: string;\n}\nexport interface PageMetaInfo {\n  route: {\n    id: string;\n    isAdmin: boolean;\n    path: string;\n    params: Record<string, string>;\n    url: string;\n  };\n  title: string;\n  description: string;\n  ogInfo: Partial<OgInfo>;\n  robots: string;\n  canonicalUrl: string;\n  keywords: string[];\n  baseUrl: string;\n}\n"
  },
  {
    "path": "packages/evershop/src/types/request.d.ts",
    "content": "import { Request as ExpressRequest } from 'express';\nimport { Route } from './route.js';\nexport interface EvershopRequest extends ExpressRequest {\n  isAdmin?: boolean;\n  session?: any;\n  currentRoute?: Route;\n}\n"
  },
  {
    "path": "packages/evershop/src/types/request.ts",
    "content": "import { Request as ExpressRequest } from 'express';\nimport { Route } from './route.js';\n\nexport interface CurrentCustomer {\n  customer_id: number;\n  group_id: number;\n  uuid: string;\n  email: string;\n  full_name: string;\n  status: number;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport interface CurrentUser {\n  admin_user_id: number;\n  uuid: string;\n  email: string;\n  full_name: string;\n  status: number;\n  roles: string;\n  created_at: Date;\n  updated_at: Date;\n}\n\nexport interface EvershopRequest extends ExpressRequest {\n  isAdmin: boolean;\n  currentRoute: Route;\n  locals: {\n    sessionID: string | null;\n    delegates: {\n      setOnce: (key: string, value: any) => void;\n      get: (key: string) => any;\n      has: (key: string) => boolean;\n      keys: () => string[];\n      getAll: () => Record<string, unknown>;\n    };\n    user: CurrentUser | null;\n    customer: CurrentCustomer | null;\n    context: Record<string, any>;\n    webpackMatchedRoute: Route | null;\n  };\n  loginCustomerWithEmail: (\n    email: string,\n    password: string,\n    callback: (err: Error | null, customer?: any) => void\n  ) => Promise<void>;\n  logoutCustomer: (callback: (err: Error | null) => void) => void;\n  isCustomerLoggedIn: () => boolean;\n  getCurrentCustomer: () => CurrentCustomer | null;\n  loginUserWithEmail: (\n    email: string,\n    password: string,\n    callback: (err: Error | null, user?: any) => void\n  ) => Promise<void>;\n  logoutUser: (callback: (err: Error | null) => void) => void;\n  isUserLoggedIn: () => boolean;\n  getCurrentUser: () => CurrentUser | null;\n}\n"
  },
  {
    "path": "packages/evershop/src/types/response.ts",
    "content": "import { Response as ExpressResponse } from 'express';\n\nexport interface EvershopResponse extends ExpressResponse {\n  debugMiddlewares: { id: string; time: number }[];\n  $body: Record<string, unknown>;\n}\n"
  },
  {
    "path": "packages/evershop/src/types/route.ts",
    "content": "export interface Route {\n  id: string;\n  name: string;\n  method: string | string[];\n  path: string;\n  isAdmin: boolean;\n  isApi: boolean;\n  folder: string;\n  payloadSchema?: Record<string, any>;\n  access?: string;\n}\n"
  },
  {
    "path": "packages/evershop/src/types/widget.ts",
    "content": "export interface Widget<T = any> {\n  name: string;\n  type: string;\n  description: string;\n  settingComponent: string;\n  settingComponentKey?: string;\n  component: string;\n  componentKey?: string;\n  enabled: boolean;\n  defaultSettings: Record<string, T>;\n}\n\nexport interface WidgetInstance<T = any> extends Widget<T> {\n  id: string;\n  type: string;\n  settings: Record<string, T>;\n}\n"
  },
  {
    "path": "packages/evershop/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"NodeNext\",\n    \"target\": \"ES2018\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"sourceMap\": true,\n    \"allowJs\": true,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"outDir\": \"./dist\",\n    \"resolveJsonModule\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"allowArbitraryExtensions\": true,\n    \"strictNullChecks\": true,\n    \"isolatedModules\": false,\n    \"baseUrl\": \".\",\n    \"rootDir\": \"./src\",\n    \"paths\": {\n      \"@components/*\": [\"./src/components/*\"],\n      \"@evershop/evershop/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/postgres-query-builder/.swcrc",
    "content": "{\n  \"$schema\": \"https://swc.rs/schema.json\",\n  \"jsc\": {\n    \"parser\": {\n      \"syntax\": \"typescript\",\n      \"tsx\": true,\n      \"dynamicImport\": true,\n      \"privateMethod\": false,\n      \"functionBind\": true,\n      \"exportDefaultFrom\": true,\n      \"exportNamespaceFrom\": false,\n      \"decorators\": true,\n      \"decoratorsBeforeExport\": false,\n      \"topLevelAwait\": true,\n      \"importMeta\": true,\n      \"importAs\": true,\n      \"preserveAllComments\": false\n    },\n    \"target\": \"es2022\",\n    \"experimental\": { \"keepImportAssertions\": true },\n    \"loose\": false,\n    \"keepClassNames\": false\n  },\n  \"module\": {\n    \"type\": \"es6\"\n  }\n}\n"
  },
  {
    "path": "packages/postgres-query-builder/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 kt65\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/postgres-query-builder/README.md",
    "content": "# PostgreSQL query builder for Node\n\nA PostgreSQL query builder for NodeJS.\n\n## Installation\n\n```javascript\nnpm install @evershop/postgres-query-builder\n```\n\n## Usage guide\n\nIt implements async/await.\n\n### Simple select\n\n```javascript\nconst { select } = require('@evershop/postgres-query-builder');\n\nconst products = await select('*')\n  .from('product')\n  .where('product_id', '>', 1)\n  .execute(pool);\n```\n\n### More complex where\n\n```javascript\nconst { select } = require('@evershop/postgres-query-builder');\n\nconst products = await select('*')\n  .from('product')\n  .where('product_id', '>', 1)\n  .and('sku', 'LIKE', 'sku')\n  .execute(pool);\n```\n\n### Event more complex where\n\n```javascript\nconst { select } = require('@evershop/postgres-query-builder');\n\nconst query = select('*').from('product');\nquery.where('product_id', '>', 1).and('sku', 'LIKE', 'sku');\nquery.orWhere('price', '>', 100);\n\nconst products = await query.execute(pool);\n```\n\n### Join table\n\n```javascript\nconst { select } = require('@evershop/postgres-query-builder');\n\nconst query = select('*').from('product');\nquery.leftJoin('price').on('product.`product_id`', '=', 'price.`product_id`');\nquery.where('product_id', '>', 1).and('sku', 'LIKE', 'sku');\nquery.andWhere('price', '>', 100);\n\nconst products = await query.execute(pool);\n```\n\n### Insert&update\n\n<table>\n<tr>\n<th> user_id </th>\n<th> name </th>\n<th> email </th>\n<th> phone </th>\n<th> status </th>\n</tr>\n<tr>\n<td>\n  1\n</td>\n<td>\n  David\n</td>\n<td>\n  emai@email.com\n</td>\n<td>\n  123456\n</td>\n<td>\n  1\n</td>\n</tr>\n</table>\n\n````javascript\n```javascript\nconst {insert} = require('@evershop/postgres-query-builder')\n\nconst query = insert(\"user\")\n.given({name: \"David\", email: \"email@email.com\", \"phone\": \"123456\", status: 1, notExistedColumn: \"This will not be a part of the query\"});\nawait query.execute(pool);\n````\n\n```javascript\nconst { update } = require('@evershop/postgres-query-builder');\n\nconst query = update('user')\n  .given({\n    name: 'David',\n    email: 'email@email.com',\n    phone: '123456',\n    status: 1,\n    notExistedColumn: 'This will not be a part of query'\n  })\n  .where('user_id', '=', 1);\nawait query.execute(pool);\n```\n\n### Working with transaction\n\n```javascript\nconst { Pool } = require('pg');\nconst {\n  insert,\n  getConnection,\n  startTransaction,\n  commit,\n  rollback\n} = require('@evershop/postgres-query-builder');\n\nconst pool = new Pool(connectionSetting);\n\n// Create a connection from the pool\nconst connection = await getConnection(pool);\n\n// Start a transaction\nawait startTransaction(connection);\ntry {\n  await insert('user')\n    .given({\n      name: 'David',\n      email: 'email@email.com',\n      phone: '123456',\n      status: 1,\n      notExistedColumn: 'This will not be a part of the query'\n    })\n    .execute(connection);\n  await commit(connection);\n} catch (e) {\n  await rollback(connection);\n}\n```\n\n## Security\n\nAll user provided data will be escaped.\n"
  },
  {
    "path": "packages/postgres-query-builder/package.json",
    "content": "{\n  \"name\": \"@evershop/postgres-query-builder\",\n  \"version\": \"2.0.1\",\n  \"type\": \"module\",\n  \"description\": \"A PostgreSQL query builder for NodeJS\",\n  \"main\": \"./dist/index.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/evershopcommerce/evershop/tree/main/packages/postgres-query-builder\"\n  },\n  \"keywords\": [\n    \"PostgreSQL\",\n    \"query builder\",\n    \"PostgreSQL query builder\",\n    \"nodejs PostgreSQL query builder\"\n  ],\n  \"files\": [\n    \"dist\",\n    \"LICENSE\",\n    \"README.md\"\n  ],\n  \"scripts\": {\n    \"compile\": \"tsc\"\n  },\n  \"author\": \"The Nguyen (https://evershop.io/)\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/evershopcommerce/evershop/issues\"\n  },\n  \"homepage\": \"https://github.com/evershopcommerce/evershop/tree/main/packages/postgres-query-builder\",\n  \"dependencies\": {\n    \"pg\": \"^8.10.0\",\n    \"uniqid\": \"^5.3.0\"\n  },\n  \"engines\": {\n    \"node\": \">= 18.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/postgres-query-builder/src/fieldResolve.js",
    "content": "export function fieldResolve(fieldName) {\n  // Check if field name is a SQL function\n  if (\n    /^([A-Za-z_][A-Za-z0-9_]*\\()?(DISTINCT )?\"?([A-Za-z_][A-Za-z0-9_]*\"?\\.)?\"?[A-Za-z_][A-Za-z0-9_]*\"?(\\))$/.test(\n      fieldName\n    )\n  ) {\n    return fieldName;\n  }\n  // replace all regex\n  const tokens = fieldName\n    .replace(/'|\"|`/g, '')\n    .split('.')\n    .filter((token) => token !== '');\n  if (tokens.length === 1) {\n    return `\"${tokens[0]}\"`;\n  } else if (tokens.length === 2) {\n    return `\"${tokens[0]}\".\"${tokens[1]}\"`;\n  } else {\n    throw new Error(`Invalid field name ${fieldName}`);\n  }\n}\n"
  },
  {
    "path": "packages/postgres-query-builder/src/index.ts",
    "content": "import { PoolClient as PgPoolClient, Pool, QueryResult } from 'pg';\nimport uniqid from 'uniqid';\nimport { fieldResolve } from './fieldResolve.js';\nimport { isValueASQL } from './isValueASQL.js';\nimport { toString } from './toString.js';\n\ninterface SQLValue {\n  value: any;\n  isSQL: boolean;\n}\n\ntype Binding = Record<string, any>;\n\ninterface PoolClient extends PgPoolClient {\n  INTRANSACTION?: boolean;\n  COMMITTED?: boolean;\n}\n\nclass Select {\n  _fields: string[] = [];\n\n  constructor() {}\n\n  select(field: string | SQLValue, alias?: string): Select {\n    // Resolve field name\n    let f = '';\n    if (isValueASQL(field) || field === '*') {\n      if (typeof field === 'object' && field.isSQL === true) {\n        f = field.value;\n      } else {\n        f = field as string;\n      }\n    } else {\n      f += `\"${field}\"`;\n    }\n    if (alias) {\n      f += ` AS \"${alias}\"`;\n    }\n\n    this._fields.push(f);\n    return this;\n  }\n\n  render(): string {\n    let stm = 'SELECT ';\n    if (this._fields.length === 0) {\n      stm = stm + '*  ';\n    } else {\n      this._fields.forEach((element) => {\n        stm += `${element}, `;\n      });\n    }\n    return stm.slice(0, -2);\n  }\n\n  clone(): Select {\n    const cp = new Select();\n    cp._fields = [...this._fields];\n    return cp;\n  }\n}\n\nclass RawLeaf {\n  _link: string;\n  _binding: Binding;\n  _rawSql: string;\n  _parent?: Node;\n\n  constructor(link: string, rawSql: string, binding: Binding = {}) {\n    this._link = link;\n    this._binding = binding;\n    this._rawSql = rawSql;\n  }\n\n  getBinding(): Binding {\n    return this._binding;\n  }\n\n  parent(): Node | undefined {\n    return this._parent;\n  }\n\n  render(): string {\n    return `${this._link} ${this._rawSql}`;\n  }\n\n  clone(node: Node): RawLeaf {\n    const cp = new RawLeaf(this._link, this._rawSql, this._binding);\n    cp._parent = node;\n    return cp;\n  }\n}\n\nclass Leaf {\n  _binding: Binding = {};\n  _value: string = '';\n  _link: string;\n  _field: string;\n  _operator: string;\n  _parent?: Node;\n\n  constructor(\n    link: string,\n    field: string,\n    operator: string,\n    value: any,\n    node?: Node\n  ) {\n    if (value.isSQL === true) {\n      this._value = value.value;\n    } else {\n      value = value.value;\n      if (\n        operator.toUpperCase() === 'IN' ||\n        operator.toUpperCase() === 'NOT IN'\n      ) {\n        if (Array.isArray(value) && value.length > 0) {\n          this._value = '(';\n          value.forEach((element) => {\n            const key = uniqid();\n            this._value = this._value + `:${key}, `;\n            this._binding[key] = element;\n          });\n          this._value = this._value.slice(0, -2) + ')';\n        } else if (Array.isArray(value) && value.length === 0) {\n          if (operator.toUpperCase() === 'IN') {\n            this._value = '(SELECT 1 WHERE 1=0)';\n          } else {\n            this._value = '(SELECT 1 WHERE 1=1)';\n          }\n        } else {\n          throw new Error(`Expect an array, got ${typeof value}`);\n        }\n      } else if (\n        operator.toUpperCase() === 'IS NULL' ||\n        operator.toUpperCase() === 'IS NOT NULL'\n      ) {\n        this._value = '';\n      } else {\n        const key = uniqid();\n        this._binding[key] = toString(value);\n        this._value = `:${key}`;\n      }\n    }\n    this._link = link;\n    this._field = fieldResolve(field);\n    this._operator = operator.toUpperCase();\n    this._parent = node;\n  }\n\n  getBinding(): Binding {\n    return this._binding;\n  }\n\n  parent(): Node | undefined {\n    return this._parent;\n  }\n\n  render(): string {\n    return `${this._link} ${this._field} ${this._operator} ${this._value}`;\n  }\n\n  clone(node: Node): Leaf {\n    const cp = new Leaf('AND', 'dummy', '=', { value: 'dummy', isSQL: false });\n    cp._binding = { ...this._binding };\n    cp._field = this._field;\n    cp._link = this._link;\n    cp._operator = this._operator;\n    cp._value = this._value;\n    cp._parent = node;\n    return cp;\n  }\n}\n\ntype TreeElement = Leaf | RawLeaf | Node;\n\ntype ValueTreatment = 'value' | 'sql';\n\nclass Node {\n  _defaultValueTreatment: ValueTreatment;\n  _tree: TreeElement[] = [];\n  _link?: string;\n  _parent?: Node | Where | Having;\n  _query?: Query | SelectQuery;\n\n  constructor(\n    query?: Query | SelectQuery,\n    defaultValueTreatment: ValueTreatment = 'value'\n  ) {\n    this._defaultValueTreatment = defaultValueTreatment;\n    this._query = query;\n  }\n\n  addLeaf(link: string, field: string, operator: string, value: any): Node {\n    if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n      this._tree.push(new Leaf(link, field, operator, value, this));\n    } else {\n      this._tree.push(\n        new Leaf(\n          link,\n          field,\n          operator,\n          { value: value, isSQL: this._defaultValueTreatment === 'sql' },\n          this\n        )\n      );\n    }\n    return this;\n  }\n\n  addRaw(link: string, sql: string, binding: Binding = {}): Node {\n    this._tree.push(new RawLeaf(link, sql, binding));\n    return this;\n  }\n\n  addNode(node: Node): Node {\n    node._parent = this;\n    this._tree.push(node);\n    return node;\n  }\n\n  empty(): Node {\n    this._tree = [];\n    return this;\n  }\n\n  getLeafs(): (Leaf | RawLeaf)[] {\n    return this._tree.filter(\n      (e) => e instanceof Leaf || e instanceof RawLeaf\n    ) as (Leaf | RawLeaf)[];\n  }\n\n  getNodes(): Node[] {\n    return this._tree.filter((e) => e instanceof Node) as Node[];\n  }\n\n  isEmpty(): boolean {\n    return !(this.getLeafs().length > 0 || this.getNodes().length > 0);\n  }\n\n  findLeaf(\n    link: string,\n    field: string,\n    operator: string,\n    value: any\n  ): Leaf | undefined {\n    for (const element of this._tree) {\n      if (\n        element instanceof Leaf &&\n        element._link === link &&\n        element._field === fieldResolve(field) &&\n        element._binding[field] === value\n      ) {\n        return element;\n      } else if (element instanceof Node) {\n        const result = element.findLeaf(link, field, operator, value);\n        if (result) return result;\n      }\n    }\n    return undefined;\n  }\n\n  getBinding(): Binding {\n    const binding: Binding = {};\n    this._tree.forEach((element) => {\n      if (\n        element instanceof Leaf ||\n        element instanceof RawLeaf ||\n        element instanceof Node\n      ) {\n        Object.assign(binding, element.getBinding());\n      }\n    });\n    return binding;\n  }\n\n  and(field: string, operator: string, value: any): Node {\n    this.addLeaf('AND', field, operator, value);\n    return this;\n  }\n\n  or(field: string, operator: string, value: any): Node {\n    this.addLeaf('OR', field, operator, value);\n    return this;\n  }\n\n  render(): string {\n    if (this._tree.length === 0) {\n      return '';\n    }\n    let statement = `${this._link} (`;\n    this._tree.forEach((element, index) => {\n      if (index === 0) {\n        statement += ` ${element.render()}`.slice(this._link === 'AND' ? 5 : 4);\n      } else {\n        statement += ` ${element.render()}`;\n      }\n    });\n    statement += ')';\n    return statement;\n  }\n\n  async execute(\n    connection: PoolClient | Pool,\n    releaseConnection = true\n  ): Promise<any[]> {\n    return await this._query!.execute(connection, releaseConnection);\n  }\n\n  async load(\n    connection: PoolClient | Pool,\n    releaseConnection = true\n  ): Promise<any> {\n    if (this._query instanceof SelectQuery) {\n      return await this._query!.load(connection, releaseConnection);\n    } else {\n      throw new Error('`load` method is only available on `SelectQuery`');\n    }\n  }\n\n  clone(query?: Query | SelectQuery, parent?: Node): Node {\n    const cp = new Node(query);\n    cp._link = this._link;\n    cp._parent = parent;\n    cp._tree = this._tree.map((t) => {\n      if (t instanceof Leaf || t instanceof RawLeaf) {\n        return t.clone(cp);\n      } else if (t instanceof Node) {\n        return t.clone(query, cp);\n      }\n      return t;\n    });\n    return cp;\n  }\n}\n\nexport interface JoinDefinition {\n  type: string;\n  table: string;\n  alias: string;\n  on: Node;\n}\n\nclass Join {\n  _joins: JoinDefinition[] = [];\n  _query: SelectQuery;\n\n  constructor(query: SelectQuery) {\n    this._query = query;\n  }\n\n  add(type: string, table: string, alias?: string): Join {\n    this._joins.push({\n      type,\n      table,\n      alias: alias || table,\n      on: new Node(this._query, 'sql')\n    });\n    return this;\n  }\n\n  on(column: string, operator: string, referencedColumn: any): Node {\n    if (this._joins.length === 0) {\n      throw new Error('Invalid call');\n    }\n    const node = this._joins[this._joins.length - 1]['on'];\n    node._link = 'ON';\n    node.addLeaf('AND', column, operator, referencedColumn);\n    return node;\n  }\n\n  render(): string {\n    if (this._joins.length === 0) {\n      return '';\n    }\n    let stm = '';\n    this._joins.forEach((join) => {\n      stm += `${join.type} \"${join.table}\" AS \"${\n        join.alias\n      }\" ${join.on.render()} `;\n      Object.assign(this._query._binding, join.on.getBinding());\n    });\n    return stm;\n  }\n\n  clone(query: SelectQuery): Join {\n    const cp = new Join(query);\n    cp._joins = [...this._joins];\n    return cp;\n  }\n}\n\nclass Where extends Node {\n  constructor(query: Query) {\n    super(query);\n  }\n\n  render(): string {\n    Object.assign(this._query!._binding, this.getBinding());\n    const render = super.render();\n    if (render === '') {\n      return '';\n    } else {\n      return 'WHERE ' + render.slice(4);\n    }\n  }\n\n  andWhere(field: string, operator: string, value: any): Node {\n    const node = new Node(this._query);\n    node._link = 'AND';\n    node._parent = this;\n    node.addLeaf('AND', field, operator, value);\n    this.addNode(node);\n    return node;\n  }\n\n  orWhere(field: string, operator: string, value: any): Node {\n    const node = new Node(this._query);\n    node._link = 'OR';\n    node._parent = this;\n    node.addLeaf('OR', field, operator, value);\n    this.addNode(node);\n    return node;\n  }\n\n  clone(query: Query): Where {\n    const cp = new Where(query);\n    cp._link = this._link;\n    cp._tree = this._tree.map((t) => {\n      if (t instanceof Leaf) {\n        return t.clone(cp);\n      } else if (t instanceof Node) {\n        return t.clone(query, cp);\n      }\n      return t;\n    });\n    return cp;\n  }\n}\n\nclass Having extends Node {\n  constructor(query: SelectQuery) {\n    super();\n    this._query = query;\n    this._link = 'HAVING';\n  }\n\n  render(): string {\n    Object.assign(this._query!._binding, this.getBinding());\n    return super.render();\n  }\n\n  clone(query: SelectQuery): Having {\n    const cp = new Having(query);\n    cp._tree = this._tree.map((t) => {\n      if (t instanceof Leaf) {\n        return t.clone(cp);\n      } else if (t instanceof Node) {\n        return t.clone(query, cp);\n      }\n      return t;\n    });\n    return cp;\n  }\n}\n\nclass Limit {\n  _offset: number | null;\n  _limit: number | null;\n\n  constructor(offset: number | null = null, limit: number | null = null) {\n    this._offset = offset;\n    this._limit = limit;\n  }\n\n  render(): string {\n    if (this._offset === null && this._limit === null) {\n      return '';\n    }\n    return `LIMIT ${this._limit === null ? null : this._limit} OFFSET ${\n      +this._offset! || 0\n    } `;\n  }\n\n  clone(): Limit {\n    return new Limit(this._offset, this._limit);\n  }\n}\n\nclass GroupBy {\n  _fields: string[] = [];\n\n  constructor() {}\n\n  add(field: string): GroupBy {\n    this._fields.push(fieldResolve(field));\n    return this;\n  }\n\n  render(): string {\n    if (this._fields.length === 0) {\n      return '';\n    }\n    return `GROUP BY ${this._fields.join(',')}`;\n  }\n\n  clone(): GroupBy {\n    const cp = new GroupBy();\n    cp._fields = [...this._fields];\n    return cp;\n  }\n}\n\nclass OrderBy {\n  _field: string | null = null;\n  _direction: string = 'DESC';\n\n  constructor() {}\n\n  add(field: string, direction?: string): OrderBy {\n    this._field = fieldResolve(field);\n    this._direction = direction == null ? 'DESC' : direction;\n    return this;\n  }\n\n  render(): string {\n    if (this._field === null) {\n      return '';\n    }\n    return `ORDER BY ${this._field} ${this._direction}`;\n  }\n\n  clone(): OrderBy {\n    const cp = new OrderBy();\n    cp._field = this._field;\n    cp._direction = this._direction;\n    return cp;\n  }\n}\n\nclass Query {\n  _where: Where;\n  _binding: Binding = {};\n\n  constructor() {\n    this._where = new Where(this);\n    this._where._link = 'AND';\n  }\n\n  where(field: string, operator: string, value: any): Where {\n    this._where = new Where(this);\n    this._where._link = 'AND';\n    this._where.addLeaf('AND', field, operator, value);\n    return this._where;\n  }\n\n  andWhere(field: string, operator: string, value: any): Node {\n    if (this._where.isEmpty() === true) {\n      return this.where(field, operator, value) as Node;\n    }\n    return this._where.andWhere(field, operator, value);\n  }\n\n  orWhere(field: string, operator: string, value: any): Node {\n    if (this._where.isEmpty() === true) {\n      return this.where(field, operator, value) as Node;\n    }\n    return this._where.orWhere(field, operator, value);\n  }\n\n  getWhere(): Where {\n    return this._where;\n  }\n\n  getBinding(): Binding {\n    return this._binding;\n  }\n\n  async sql(connection?: PoolClient | Pool): Promise<string> {\n    throw new Error('Method not implemented');\n  }\n\n  async execute(\n    connection: PoolClient | Pool,\n    releaseConnection = true\n  ): Promise<any[]> {\n    let sql = await this.sql(connection);\n    const binding: any[] = [];\n    let id = 0;\n    for (const key in this._binding) {\n      if (this._binding.hasOwnProperty(key)) {\n        id += 1;\n        sql = sql.replace(`:${key}`, `$${id}`);\n        binding.push(this._binding[key]);\n      }\n    }\n    const { rows } = await connection.query({\n      text: sql,\n      values: binding\n    });\n    if (releaseConnection) {\n      release(connection);\n    }\n    return rows;\n  }\n}\n\nclass SelectQuery extends Query {\n  _table?: string;\n  _alias?: string;\n  _select: Select;\n  _having: Having;\n  _join: Join;\n  _limit: Limit;\n  _groupBy: GroupBy;\n  _orderBy: OrderBy;\n\n  constructor() {\n    super();\n    this._select = new Select();\n    this._having = new Having(this);\n    this._join = new Join(this);\n    this._limit = new Limit();\n    this._groupBy = new GroupBy();\n    this._orderBy = new OrderBy();\n  }\n\n  select(field: string | SQLValue, alias?: string): SelectQuery {\n    this._select.select(field, alias);\n    return this;\n  }\n\n  from(table: string, alias?: string): SelectQuery {\n    this._table = table;\n    this._alias = alias;\n    return this;\n  }\n\n  having(field: string, operator: string, value: any): Having {\n    this._having.and(field, operator, value);\n    return this._having;\n  }\n\n  leftJoin(table: string, alias?: string): Join {\n    this._join.add('LEFT JOIN', table, alias);\n    return this._join;\n  }\n\n  rightJoin(table: string, alias?: string): Join {\n    this._join.add('RIGHT JOIN', table, alias);\n    return this._join;\n  }\n\n  innerJoin(table: string, alias?: string): Join {\n    this._join.add('INNER JOIN', table, alias);\n    return this._join;\n  }\n\n  limit(offset: number, limit: number): SelectQuery {\n    this._limit = new Limit(offset, limit);\n    return this;\n  }\n\n  groupBy(...args: string[]): SelectQuery {\n    args.forEach((element) => {\n      this._groupBy.add(String(element));\n    });\n    return this;\n  }\n\n  orderBy(field: string, direction = 'ASC'): SelectQuery {\n    this._orderBy.add(field, direction);\n    return this;\n  }\n\n  orderDirection(direction: string): SelectQuery {\n    this._orderBy._direction = direction;\n    return this;\n  }\n\n  async sql(): Promise<string> {\n    if (!this._table) {\n      throw Error('You must specific table by calling `from` method');\n    }\n    let from = `\"${this._table}\"`;\n    if (this._alias) {\n      from += ` AS \"${this._alias}\"`;\n    }\n    return [\n      this._select.render().trim(),\n      'FROM',\n      from.trim(),\n      this._join.render().trim(),\n      this._where.render().trim(),\n      this._groupBy.render().trim(),\n      this._having.render().trim(),\n      this._orderBy.render().trim(),\n      this._limit.render().trim()\n    ]\n      .filter((e) => e !== '')\n      .join(' ');\n  }\n\n  async load(\n    connection: PoolClient | Pool,\n    releaseConnection = true\n  ): Promise<any> {\n    this.limit(0, 1);\n    const rows = await this.execute(connection, releaseConnection);\n    return rows[0] || null;\n  }\n\n  async execute(\n    connection: PoolClient | Pool,\n    releaseConnection = true\n  ): Promise<any[]> {\n    if (connection instanceof Pool) {\n      connection = await getConnection(connection);\n    }\n    let sql = await this.sql();\n    const binding: any[] = [];\n    let i = 0;\n    for (const key in this._binding) {\n      if (this._binding.hasOwnProperty(key)) {\n        i += 1;\n        sql = sql.replace(`:${key}`, `$${i}`);\n        binding.push(this._binding[key]);\n      }\n    }\n    try {\n      const { rows } = await (connection as PoolClient).query({\n        text: sql,\n        values: binding\n      });\n      return rows;\n    } catch (e: any) {\n      if ((connection as PoolClient).INTRANSACTION === true) {\n        throw e;\n      }\n      if (e.code === '42703') {\n        this.removeOrderBy();\n        return await super.execute(connection as PoolClient, false);\n      } else if (e.code.toLowerCase() === '22p02') {\n        const countField = this._select._fields.find((f) =>\n          /COUNT\\s*\\(/i.test(f)\n        );\n        if (countField) {\n          const alias = countField.match(/(?<=as\\s)(.*)/i) as RegExpMatchArray;\n          let aliasResult = '';\n          if (alias) {\n            aliasResult = alias[0].trim();\n            aliasResult = aliasResult.replace(/'/g, '').replace(/\"/g, '');\n          } else {\n            aliasResult = 'count';\n          }\n          return [{ [aliasResult]: 0 }];\n        } else {\n          return [];\n        }\n      } else {\n        throw e;\n      }\n    } finally {\n      if (releaseConnection) {\n        release(connection as PoolClient);\n      }\n    }\n  }\n\n  clone(): SelectQuery {\n    const cp = new SelectQuery();\n    cp._table = this._table;\n    cp._alias = this._alias;\n    cp._where = this._where.clone(cp);\n    cp._having = this._having.clone(cp);\n    cp._join = this._join.clone(cp);\n    cp._limit = this._limit.clone();\n    cp._groupBy = this._groupBy.clone();\n    cp._orderBy = this._orderBy.clone();\n    return cp;\n  }\n\n  removeOrderBy(): SelectQuery {\n    this._orderBy = new OrderBy();\n    return this;\n  }\n\n  removeGroupBy(): SelectQuery {\n    this._groupBy = new GroupBy();\n    return this;\n  }\n\n  removeLimit(): SelectQuery {\n    this._limit = new Limit();\n    return this;\n  }\n}\n\nclass UpdateQuery extends Query {\n  _table: string;\n  _primaryColumn: string | null = null;\n  _data: Record<string, any> = {};\n\n  constructor(table: string) {\n    super();\n    this._table = table;\n  }\n\n  given(data: Record<string, any>): UpdateQuery {\n    if (typeof data !== 'object' || data === null) {\n      throw new Error('Data must be an object and not null');\n    }\n    const copy: Record<string, any> = {};\n    Object.keys(data).forEach((key) => {\n      copy[key] = toString(data[key]);\n    });\n    this._data = copy;\n    return this;\n  }\n\n  prime(field: string, value: any): UpdateQuery {\n    this._data[field] = toString(value);\n    return this;\n  }\n\n  async sql(connection: PoolClient): Promise<string> {\n    if (!this._table) {\n      throw Error('You need to call specific method first');\n    }\n    if (Object.keys(this._data).length === 0) {\n      throw Error('You need provide data first');\n    }\n\n    const { rows } = await connection.query(\n      `SELECT \n        table_name, \n        column_name, \n        data_type, \n        is_nullable, \n        column_default, \n        is_identity, \n        identity_generation \n      FROM information_schema.columns \n      WHERE table_name = '${this._table}'`\n    );\n    const set: string[] = [];\n    rows.forEach((field: any) => {\n      if (['BY DEFAULT', 'ALWAYS'].includes(field['identity_generation'])) {\n        this._primaryColumn = field['column_name'];\n        return;\n      }\n      if (this._data[field['column_name']] === undefined) {\n        return;\n      }\n      const key = uniqid();\n      set.push(`\"${field['column_name']}\" = :${key}`);\n      this._binding[key] = this._data[field['column_name']];\n    });\n\n    if (set.length === 0) {\n      throw new Error('No data was provided' + this._table);\n    }\n\n    const sql = [\n      'UPDATE',\n      `\"${this._table}\"`,\n      'SET',\n      set.join(', '),\n      this._where.render(),\n      'RETURNING *'\n    ]\n      .filter((e) => e !== '')\n      .join(' ');\n    return sql;\n  }\n\n  async execute(\n    connection: PoolClient | Pool,\n    releaseConnection = true\n  ): Promise<any> {\n    const rows = await super.execute(connection, releaseConnection);\n    const updatedRow = rows[0];\n    if (this._primaryColumn && updatedRow) {\n      updatedRow['updatedId'] = updatedRow[this._primaryColumn];\n    }\n    return updatedRow;\n  }\n}\n\nclass InsertQuery extends Query {\n  _table: string;\n  _primaryColumn: string | null = null;\n  _data: Record<string, any> = {};\n\n  constructor(table: string) {\n    super();\n    this._table = table;\n  }\n\n  given(data: Record<string, any>): InsertQuery {\n    if (typeof data !== 'object' || data === null) {\n      throw new Error('Data must be an object and not null');\n    }\n    const copy: Record<string, any> = {};\n    Object.keys(data).forEach((key) => {\n      copy[key] = toString(data[key]);\n    });\n    this._data = copy;\n    return this;\n  }\n\n  prime(field: string, value: any): InsertQuery {\n    this._data[field] = toString(value);\n    return this;\n  }\n\n  async sql(connection: PoolClient): Promise<string> {\n    if (!this._table) {\n      throw Error('You need to call specific method first');\n    }\n    if (Object.keys(this._data).length === 0) {\n      throw Error('You need provide data first');\n    }\n\n    const { rows } = await connection.query(\n      `SELECT \n        table_name, \n        column_name, \n        data_type, \n        is_nullable, \n        column_default, \n        is_identity, \n        identity_generation \n      FROM information_schema.columns \n      WHERE table_name = '${this._table}'`\n    );\n\n    const fs: string[] = [];\n    const vs: string[] = [];\n\n    rows.forEach((field: any) => {\n      if (['BY DEFAULT', 'ALWAYS'].includes(field['identity_generation'])) {\n        this._primaryColumn = field['column_name'];\n        return;\n      }\n      if (this._data[field['column_name']] === undefined) {\n        return;\n      }\n      const key = uniqid();\n      fs.push(`\"${field['column_name']}\"`);\n      vs.push(`:${key}`);\n      this._binding[key] = this._data[field['column_name']];\n    });\n\n    const sql = [\n      'INSERT INTO',\n      `\"${this._table}\"`,\n      '(',\n      fs.join(', '),\n      ')',\n      'VALUES',\n      '(',\n      vs.join(', '),\n      ')',\n      'RETURNING *'\n    ]\n      .filter((e) => e !== '')\n      .join(' ');\n    return sql;\n  }\n\n  async execute(\n    connection: PoolClient | Pool,\n    releaseConnection = true\n  ): Promise<any> {\n    const rows = await super.execute(connection, releaseConnection);\n    const insertedRow = rows[0];\n    if (this._primaryColumn) {\n      insertedRow['insertId'] = insertedRow[this._primaryColumn];\n    }\n    return insertedRow;\n  }\n}\n\nclass InsertOnUpdateQuery extends Query {\n  _table: string;\n  _data: Record<string, any> = {};\n  _conflictColumns: string[];\n  _primaryColumn: string | null = null;\n\n  constructor(table: string, conflictColumns: string[]) {\n    super();\n    this._table = table;\n    this._conflictColumns = conflictColumns;\n  }\n\n  given(data: Record<string, any>): InsertOnUpdateQuery {\n    if (typeof data !== 'object' || data === null) {\n      throw new Error('Data must be an object and not null');\n    }\n    const copy: Record<string, any> = {};\n    Object.keys(data).forEach((key) => {\n      copy[key] = toString(data[key]);\n    });\n    this._data = copy;\n    return this;\n  }\n\n  prime(field: string, value: any): InsertOnUpdateQuery {\n    this._data[field] = toString(value);\n    return this;\n  }\n\n  async sql(connection: PoolClient): Promise<string> {\n    if (!this._table) {\n      throw Error('You need to call specific method first');\n    }\n    if (Object.keys(this._data).length === 0) {\n      throw Error('You need provide data first');\n    }\n\n    const { rows } = await connection.query({\n      text: `SELECT \n        table_name, \n        column_name, \n        data_type, \n        is_nullable, \n        column_default, \n        is_identity, \n        identity_generation \n      FROM information_schema.columns \n      WHERE table_name = $1`,\n      values: [this._table]\n    });\n\n    const fs: string[] = [];\n    const vs: string[] = [];\n    const us: string[] = [];\n    const usp: Record<string, any> = {};\n\n    rows.forEach((field: any) => {\n      if (['BY DEFAULT', 'ALWAYS'].includes(field['identity_generation'])) {\n        return;\n      }\n      if (this._data[field['column_name']] === undefined) {\n        return;\n      }\n      const key = uniqid();\n      const ukey = uniqid();\n      fs.push(`\"${field['column_name']}\"`);\n      vs.push(`:${key}`);\n      us.push(`\"${field['column_name']}\" = :${ukey}`);\n      usp[ukey] = this._data[field['column_name']];\n      this._binding[key] = this._data[field['column_name']];\n    });\n\n    this._binding = { ...this._binding, ...usp };\n\n    const sql = [\n      'INSERT INTO',\n      `\"${this._table}\"`,\n      '(',\n      fs.join(', '),\n      ')',\n      'VALUES',\n      '(',\n      vs.join(', '),\n      ')',\n      `ON CONFLICT (${this._conflictColumns.join(',')}) DO UPDATE SET`,\n      us.join(', '),\n      'RETURNING *'\n    ]\n      .filter((e) => e !== '')\n      .join(' ');\n    return sql;\n  }\n\n  async execute(\n    connection: PoolClient | Pool,\n    releaseConnection = true\n  ): Promise<any> {\n    const rows = await super.execute(connection, releaseConnection);\n    const insertedRow = rows[0];\n    if (this._primaryColumn) {\n      insertedRow['insertId'] = insertedRow[this._primaryColumn];\n    }\n    return insertedRow;\n  }\n}\n\nclass DeleteQuery extends Query {\n  _table: string;\n\n  constructor(table: string) {\n    super();\n    this._table = table;\n  }\n\n  async sql(): Promise<string> {\n    if (!this._table) {\n      throw Error('You need to call specific method first');\n    }\n    return [\n      'DELETE FROM',\n      `\"${this._table}\"`,\n      this._where.render().trim()\n    ].join(' ');\n  }\n}\n\n// Connection management functions\nasync function getConnection(pool: Pool): Promise<PoolClient> {\n  return await pool.connect();\n}\n\nasync function startTransaction(connection: PoolClient): Promise<void> {\n  await connection.query('BEGIN');\n  connection.INTRANSACTION = true;\n  connection.COMMITTED = false;\n}\n\nasync function commit(connection: PoolClient): Promise<void> {\n  await connection.query('COMMIT');\n  connection.INTRANSACTION = false;\n  connection.COMMITTED = true;\n  release(connection);\n}\n\nasync function rollback(connection: PoolClient): Promise<void> {\n  await connection.query('ROLLBACK');\n  connection.INTRANSACTION = false;\n  connection.release();\n}\n\nfunction release(connection: PoolClient | Pool): void {\n  // Check if connection is a Pool instance\n  if (connection instanceof Pool) {\n    return;\n  }\n  if (connection.INTRANSACTION === true) {\n    return;\n  }\n  connection.release();\n}\n\nasync function execute(\n  connection: PoolClient | Pool,\n  query: string\n): Promise<QueryResult> {\n  return await connection.query(query);\n}\n\nfunction sql(value: any): SQLValue {\n  return {\n    value: value,\n    isSQL: true\n  };\n}\n\nfunction value(val: any): SQLValue {\n  return {\n    value: val,\n    isSQL: false\n  };\n}\n\n// Factory functions\nfunction select(...args: string[]): SelectQuery {\n  const selectQuery = new SelectQuery();\n\n  if (args[0] === '*') {\n    selectQuery.select('*');\n    return selectQuery;\n  }\n\n  args.forEach((arg) => {\n    if (typeof arg === 'string') selectQuery.select(arg);\n  });\n\n  return selectQuery;\n}\n\nfunction insert(table: string): InsertQuery {\n  return new InsertQuery(table);\n}\n\nfunction insertOnUpdate(\n  table: string,\n  conflictColumns: string[]\n): InsertOnUpdateQuery {\n  // Check if conflictColumns is an array and not empty\n  if (!Array.isArray(conflictColumns) || conflictColumns.length === 0) {\n    throw new Error('conflictColumns must be an array and not empty');\n  }\n  return new InsertOnUpdateQuery(table, conflictColumns);\n}\n\nfunction update(table: string): UpdateQuery {\n  return new UpdateQuery(table);\n}\n\nfunction del(table: string): DeleteQuery {\n  return new DeleteQuery(table);\n}\n\nfunction node(link: string): Node {\n  const nodeInstance = new Node();\n  nodeInstance._link = link;\n  return nodeInstance;\n}\n\nexport {\n  select,\n  insert,\n  update,\n  node,\n  del,\n  insertOnUpdate,\n  getConnection,\n  startTransaction,\n  commit,\n  rollback,\n  release,\n  execute,\n  sql,\n  value,\n  // Types for external usage\n  SelectQuery,\n  UpdateQuery,\n  InsertQuery,\n  InsertOnUpdateQuery,\n  DeleteQuery,\n  SQLValue,\n  Binding,\n  Pool,\n  PoolClient\n};\n"
  },
  {
    "path": "packages/postgres-query-builder/src/isValueASQL.js",
    "content": "export function isValueASQL(value) {\n  // Check if value is an object and has property \"isSQL\" and it's true\n  if (typeof value === 'object' && value.isSQL === true) {\n    return true;\n  }\n  return (\n    /^([A-Za-z_][A-Za-z0-9_]*\\()?(DISTINCT )?\"?([A-Za-z_][A-Za-z0-9_]*\"?\\.)?\"?[A-Za-z_][A-Za-z0-9_]*\"?(\\))$/.test(\n      value\n    ) ||\n    /^(\"[A-Za-z_][A-Za-z0-9_]*\"|([A-Za-z_][A-Za-z0-9_]*))\\.(\"[A-Za-z_][A-Za-z0-9_]*\"|([A-Za-z_][A-Za-z0-9_]*))$/.test(\n      value\n    ) ||\n    /^[A-Z ]+([(])[a-zA-Z0-9* _=<>(,&).`!']+([)])$/.test(value)\n  );\n}\n"
  },
  {
    "path": "packages/postgres-query-builder/src/toString.js",
    "content": "export function toString(value) {\n  // Check if value is an array or object\n  if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {\n    // If value is an array, return a string with comma separated values\n    return JSON.stringify(value);\n  } else {\n    return value;\n  }\n}\n"
  },
  {
    "path": "packages/postgres-query-builder/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"NodeNext\",\n    \"target\": \"ES2018\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"sourceMap\": true,\n    \"allowJs\": true,\n    \"checkJs\": false,\n    \"outDir\": \"./dist\",\n    \"resolveJsonModule\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"allowArbitraryExtensions\": true,\n    \"strictNullChecks\": true,\n    \"baseUrl\": \".\"\n  },\n  \"include\": [\n    \"./src/*.ts\",\n    \"./src/fieldResolve.js\",\n    \"./src/isValueASQL.js\",\n    \"./src/toString.js\"\n  ],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "translations/de/account.csv",
    "content": "Login, Anmelden\nLogout, Abmelden\nEmail, E-Mail\nPassword, Passwort\nForgot your password?, Passwort vergessen?\nCreate an account, Ein Konto erstellen\nRegister, Registrieren\nFull Name, Vollständiger Name\nFull name, Vollständiger Name\nSIGN IN, ANMELDEN\nOrder History, Bestellverlauf\nAccount Details, Kontodetails\nYou have not placed any orders yet, Sie haben noch keine Bestellungen aufgegeben\nMy Account, Mein Konto\nAlready have an account?, Haben Sie bereits ein Konto?\nContact information, Kontaktinformationen\nCity, Stadt\nAccount Information, Kontoinformationen\nAddress, Adresse\nPostcode, Postleitzahl\nPostcode is required, Postleitzahl ist erforderlich\nTelephone is required, Telefonnummer ist erforderlich\nTelephone, Telefon\nFull name is required, Vollständiger Name ist erforderlich\nCountry is required, Land ist erforderlich\nCountry, Land\nProvince is required, Provinz ist erforderlich\nProvince, Provinz\nBilling Address, Rechnungsadresse\nShipping Address, Lieferadresse\nCreate A New Account, Ein neues Konto erstellen\nContact, Kontakt\nCustomer information, Kundeninformationen"
  },
  {
    "path": "translations/de/catalog.csv",
    "content": "Featured collection, Ausgewählte Kollektion\nSHOP BY, Einkaufen nach\nSort By, Sortieren nach\nThere is no product to display, Es gibt keine Produkte zum Anzeigen\n${count} products, ${count} Produkte\nSearch results for \"${keyword}\", Suchergebnisse für \\\"${keyword}\\\""
  },
  {
    "path": "translations/de/checkout.csv",
    "content": "Total, Gesamt\nOrder, Bestellung\nSku, SKU\nContinue to shipping, Weiter zum Versand\nContinue shopping, Weiter einkaufen\nContinue Shopping, Weiter Einkaufen\nCONTINUE SHOPPING, WEITER EINKAUFEN\nShopping cart, Einkaufswagen\nProduct, Produkt\nPrice, Preis\nQuantity, Menge\nRemove, Entfernen\nQty, Menge\nSub total, Zwischensumme\nDiscount(${coupon}), Rabatt(${coupon})\nOrder summary, Bestellübersicht\nTaxes and shipping calculated at checkout, Steuern und Versandkosten werden beim Bezahlen berechnet\nCHECKOUT, ZUR KASSE\nChange, Ändern\nPayment Method, Zahlungsmethode\nShipping Address, Lieferadresse\nShipping Method, Versandmethode\nNo payment methods available, Keine Zahlungsmethoden verfügbar\nPlace Order, Bestellung aufgeben\nMy billing address is same as shipping address, Mein Rechnungsadresse ist gleich wie die Versandadresse\n${count} items, ${count} Artikel\nDiscount, Rabatt\nShipping, Versand\nNo tax, Keine Steuern\nTax, Steuer\nSorry, there is no available method for your address, Entschuldigung, es gibt keine verfügbare Methode für Ihre Adresse\nPlease enter a shipping address in order to see shipping quotes, Bitte geben Sie eine Versandadresse ein, um Versandkosten zu sehen\nContinue to payment, Weiter zur Zahlung\nNo payment method available, Keine Zahlungsmethode verfügbar\nShip to, Versenden an\nShipment, Versand\nThank you ${name}!, Danke, ${name}!\nContact information, Kontaktinformationen\nOrder #${orderNumber}, Bestellung #${orderNumber}\nADD TO CART, IN DEN WARENKORB\nPlease select variant options, Bitte wählen Sie Variantenoptionen\nVIEW CART (${count}), WARENKORB ANZEIGEN (${count})\nJUST ADDED TO YOUR CART, SOEBEN ZU IHREM WARENKORB HINZUGEFÜGT\nPromotion code?, Aktionscode?\nEnter coupon code, Gutscheincode eingeben\nApply, Anwenden\nYour cart is empty!, Ihr Warenkorb ist leer!\nFull name, Voller Name\nTelephone, Telefon\nAddress, Adresse\nCity, Stadt\nCountry, Land\nPostcode, Postleitzahl\nInclusive of tax, Enthaltene Steuern\nSorry, there is no available method for your address,Entschuldigung, es gibt keine verfügbare Methode für Ihre Adresse.\nFull name is required, Bitte angeben\nTelephone is required, Bitte angeben\nAddress is required, Bitte angeben\nCity is required, Bitte angeben\nCountry is required, Bitte angeben\nPostcode is required, Bitte angeben\nContact, Kontakt\n"
  },
  {
    "path": "translations/de/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, Rabatt von ${discount} auf alle Bestellungen über ${price}\nPlease select, Bitte auswählen...\nThe page you requested does not exist., Die von Ihnen angeforderte Seite existiert nicht.\nContinue shopping, Weiter einkaufen\n404 Page Not Found, 404 Seite nicht gefunden\nHome, Startseite\nNot found, Nicht gefunden"
  },
  {
    "path": "translations/de/paypal.csv",
    "content": "You will be redirected to PayPal, Du wirst zu PayPal weitergeleitet.\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., Es tut uns leid. Bei der Bearbeitung Ihrer Zahlung ist ein Fehler aufgetreten. Ihre Karte wurde nicht belastet. Bitte versuchen Sie es erneut."
  },
  {
    "path": "translations/es/account.csv",
    "content": "Login, Iniciar sesión\nLogout, Cerrar sesión\nEmail, Correo electrónico\nPassword, Contraseña\nForgot your password?, ¿Olvidaste tu contraseña?\nCreate an account, Crear una cuenta\nRegister, Registrarse\nFull Name, Nombre completo\nFull name, Nombre completo\nSIGN IN, INICIAR SESIÓN\nOrder History, Historial de pedidos\nAccount Details, Detalles de la cuenta\nYou have not placed any orders yet, Aún no has realizado ningún pedido\nMy Account, Mi cuenta\nAlready have an account?, ¿Ya tienes una cuenta?\nContact information, Información de contacto\nCity, Ciudad\nAccount Information, Información de la cuenta\nAddress, Dirección\nPostcode, Código postal\nPostcode is required, El código postal es obligatorio\nTelephone is required, El número de teléfono es obligatorio\nTelephone, Teléfono\nFull name is required, El nombre completo es obligatorio\nCountry is required, El país es obligatorio\nCountry, País\nProvince is required, La provincia es obligatoria\nProvince, Provincia\nBilling Address, Dirección de facturación\nShipping Address, Dirección de envío\nCreate A New Account, Crear una nueva cuenta\nContact, Contacto\nCustomer information, Información del cliente"
  },
  {
    "path": "translations/es/catalog.csv",
    "content": "Featured collection, Colección destacada\nSHOP BY, Comprar por\nSort By, Ordenar por\nThere is no product to display, No hay productos para mostrar\n${count} products, ${count} productos\nSearch results for \"${keyword}\", Resultados de búsqueda para \"${keyword}\""
  },
  {
    "path": "translations/es/checkout.csv",
    "content": "Total, Total\nOrder, Pedido\nSku, SKU\nContinue to shipping, Continuar al envío\nContinue shopping, Seguir comprando\nContinue Shopping, Seguir Comprando\nCONTINUE SHOPPING, SEGUIR COMPRANDO\nShopping cart, Carrito de compras\nProduct, Producto\nPrice, Precio\nQuantity, Cantidad\nRemove, Eliminar\nQty, Cant\nSub total, Subtotal\nDiscount($coupon), Descuento($cupón)\nOrder summary, Resumen del pedido\nTaxes and shipping calculated at checkout, Impuestos y envío calculados al finalizar la compra\nCHECKOUT, FINALIZAR COMPRA\nChange, Cambiar\nPayment Method, Método de pago\nShipping Address, Dirección de envío\nShipping Method, Método de envío\nNo payment methods available, No hay métodos de pago disponibles\nPlace Order, Realizar pedido\nMy billing address is same as shipping address, Mi dirección de facturación es la misma que la de envío\n$count items, $count artículos\nDiscount, Descuento\nShipping, Envío\nNo tax, Sin impuestos\nTax, Impuesto\nSorry, there is no available method for your address, Lo sentimos, no hay un método disponible para su dirección\nPlease enter a shipping address in order to see shipping quotes, Por favor ingrese una dirección de envío para ver presupuestos de envío\nContinue to payment, Continuar al pago\nNo payment method available, No hay métodos de pago disponibles\nShip to, Enviar a\nShipment, Envío\nThank you $name!, ¡Gracias $name!\nContact information, Información de contacto\nOrder #$orderNumber, Pedido #$orderNumber\nADD TO CART, AÑADIR AL CARRITO\nPlease select variant options, Por favor selecciona opciones de variante\nVIEW CART ($count), VER CARRITO ($count)\nJUST ADDED TO YOUR CART, ACABA DE AÑADIR A SU CARRITO\nPromotion code?, ¿Código de promoción?\nEnter coupon code, Ingrese código de cupón\nApply, Aplicar\nYour cart is empty!, ¡Su carrito está vacío!\nFull name, Nombre completo\nTelephone, Teléfono\nAddress, Dirección\nCity, Ciudad\nCountry, País\nPostcode, Código postal\nInclusive of tax, Impuestos incluidos\nSorry, there is no available method for your address, Lo sentimos, no hay un método disponible para su dirección\nFull name is required, Se requiere el nombre completo\nTelephone is required, Se requiere el teléfono\nAddress is required, Se requiere la dirección\nCity is required, Se requiere la ciudad\nCountry is required, Se requiere el país\nPostcode is required, Se requiere el código postal\nContact, Contacto"
  },
  {
    "path": "translations/es/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, Descuento de ${discount} para todos los pedidos superiores a ${price}.\nPlease select, Por favor, selecciona...\nThe page you requested does not exist., La página que solicitaste no existe\nContinue shopping, Continuar comprando\n404 Page Not Found, 404 Página no encontrada\nHome, Inicio\nNot found, No encontrado"
  },
  {
    "path": "translations/es/paypal.csv",
    "content": "You will be redirected to PayPal, Serás redirigido a PayPal.\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., Lo sentimos. Hubo un error al procesar tu pago. Tu tarjeta no fue cargada. Por favor, inténtalo nuevamente."
  },
  {
    "path": "translations/fa/account.csv",
    "content": "Login, ورود\nLogout, خروج\nEmail, ایمیل\nPassword, رمز عبور\nForgot your password?, رمز عبورت را فراموش کرده‌ای؟\nCreate an account, ایجاد حساب کاربری\nRegister, ثبت‌نام\nFull Name, نام کامل\nFull name, نام کامل\nSIGN IN, ورود\nOrder History, تاریخچه سفارش‌ها\nAccount Details, جزئیات حساب\nYou have not placed any orders yet, هنوز هیچ سفارشی ثبت نکرده‌ای\nMy Account, حساب من\nAlready have an account?, حساب کاربری داری؟\nContact information, اطلاعات تماس\nCity, شهر\nAccount Information, اطلاعات حساب\nAddress, آدرس\nPostcode, کد پستی\nPostcode is required, وارد کردن کد پستی الزامی است\nTelephone is required, وارد کردن شماره تلفن الزامی است\nTelephone, شماره تلفن\nFull name is required, وارد کردن نام کامل الزامی است\nCountry is required, انتخاب کشور الزامی است\nCountry, کشور\nProvince is required, وارد کردن استان الزامی است\nProvince, استان\nBilling Address, آدرس صورتحساب\nShipping Address, آدرس ارسال\nCreate A New Account, ایجاد حساب کاربری جدید\nContact, تماس\nCustomer information, اطلاعات مشتری"
  },
  {
    "path": "translations/fa/catalog.csv",
    "content": "Featured collection, مجموعه ویژه\nSHOP BY, خرید بر اساس\nSort By, مرتب‌سازی بر اساس\nThere is no product to display, هیچ محصولی برای نمایش وجود ندارد\n${count} products, ${count} محصول\nSearch results for \"${keyword}\", نتایج جستجو برای \"${keyword}\""
  },
  {
    "path": "translations/fa/checkout.csv",
    "content": "Total, مجموع\nOrder, سفارش\nSku, شناسه کالا (SKU)\nContinue to shipping, ادامه به مرحله ارسال\nContinue shopping, ادامه خرید\nContinue Shopping, ادامه خرید\nCONTINUE SHOPPING, ادامه خرید\nShopping cart, سبد خرید\nProduct, محصول\nPrice, قیمت\nQuantity, تعداد\nRemove, حذف\nQty, تعداد\nSub total, جمع جزئی\nDiscount(${coupon}), تخفیف (${coupon})\nOrder summary, خلاصه سفارش\nTaxes and shipping calculated at checkout, مالیات و هزینه ارسال در مرحله پرداخت محاسبه می‌شود\nCHECKOUT, ادامه فرایند پرداخت\nChange, تغییر\nPayment Method, روش پرداخت\nShipping Method, روش ارسال\nNo payment methods available, هیچ روش پرداختی در دسترس نیست\nPlace Order, ثبت سفارش\nMy billing address is same as shipping address, آدرس صورتحساب من با آدرس ارسال یکسان است\n${count} items, ${count} کالا\nDiscount, تخفیف\nShipping, ارسال\nNo tax, بدون مالیات\nTax, مالیات\n\"Sorry, there is no available method for your address\", \"متأسفیم، برای آدرس شما هیچ روش ارسال در دسترس نیست\"\nPlease enter a shipping address in order to see shipping quotes, برای مشاهده هزینه ارسال، لطفاً آدرس ارسال را وارد کنید\nContinue to payment, ادامه به مرحله پرداخت\nNo payment method available, هیچ روش پرداختی در دسترس نیست\nShip to, ارسال به\nShipment, ارسال\nThank you ${name}!, متشکریم ${name}!\nContact information, اطلاعات تماس\nOrder #${orderNumber}, سفارش شماره ${orderNumber}\nADD TO CART, افزودن به سبد خرید\nPlease select variant options, لطفاً گزینه‌های محصول را انتخاب کنید\nVIEW CART (${count}), مشاهده سبد خرید (${count})\nJUST ADDED TO YOUR CART, به‌تازگی به سبد خرید شما افزوده شد\nPromotion code?, کد تخفیف؟\nEnter coupon code, کد تخفیف را وارد کنید\nApply, اعمال\nYour cart is empty!, سبد خرید شما خالی است!"
  },
  {
    "path": "translations/fa/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, تخفیف ${discount} برای تمام سفارش‌های بیش از ${price}\nPlease select, لطفاً انتخاب کنید...\nThe page you requested does not exist., صفحه‌ای که درخواست کرده‌اید وجود ندارد.\nContinue shopping, ادامه خرید\n404 Page Not Found, صفحه ۴۰۴ – یافت نشد\nHome, صفحه اصلی\nNot found, یافت نشد"
  },
  {
    "path": "translations/fa/paypal.csv",
    "content": "You will be redirected to PayPal, به صفحه‌ی PayPal هدایت خواهید شد.\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., متأسفیم، در پردازش پرداخت شما خطایی رخ داد. از کارت شما مبلغی کسر نشد. لطفاً دوباره تلاش کنید."
  },
  {
    "path": "translations/fr/account.csv",
    "content": "Login, Connexion\nLogout, Déconnexion\nEmail, Email\nPassword, Mot de passe\nForgot your password?, Mot de passe oublié ?\nCreate an account, Créer un compte\nRegister, S'inscrire\nFull Name, Nom complet\nSIGN IN, SE CONNECTER\nOrder History, Historique des commandes\nAccount Details, Détails du compte\nYou have not placed any orders yet, Vous n'avez pas encore passé de commande\nMy Account, Mon compte\nAlready have an account?, Vous avez déjà un compte ?\nContact information, Informations de contact\nCity, Ville\nAccount Information, Informations du compte\nAddress, Adresse\nPostcode, Code postal\nPostcode is required, Le code postal est requis\nTelephone is required, Le téléphone est requis\nTelephone, Téléphone\nFull name is required, Le nom complet est requis\nCountry is required, Le pays est requis\nCountry, Pays\nProvince is required, La province est requise\nProvince, Province\nBilling Address, Adresse de facturation\nShipping Address, Adresse de livraison\nCreate A New Account, Créer un nouveau compte\nContact, Contact\nCustomer information, Informations client"
  },
  {
    "path": "translations/fr/catalog.csv",
    "content": "Featured collection, Collection en vedette\nSHOP BY, Acheter par\nSort By, Trier par\nThere is no product to display, Aucun produit à afficher\n${count} products, ${count} produits\nSearch results for \"${keyword}\", Résultats de recherche pour \"${keyword}\"\n"
  },
  {
    "path": "translations/fr/checkout.csv",
    "content": "Total, Total\nOrder, Commande\nSku, SKU\nContinue to shipping, Continuer vers l'expédition\nContinue shopping, Continuer vos achats\nCONTINUE SHOPPING, CONTINUER VOS ACHATS\nShopping cart, Panier\nProduct, Produit\nPrice, Prix\nQuantity, Quantité\nRemove, Supprimer\nQty, Qté\nSub total, Sous-total\nDiscount($coupon), Remise ($coupon)\nOrder summary, Résumé de la commande\nTaxes and shipping calculated at checkout, Taxes et expédition calculés lors du paiement\nCHECKOUT, PASSER À LA CAISSE\nChange, Changer\nPayment Method, Mode de paiement\nShipping Address, Adresse de livraison\nShipping Method, Mode de livraison\nNo payment methods available, Aucun mode de paiement disponible\nPlace Order, Passer la commande\nMy billing address is same as shipping address, Mon adresse de facturation est la même que mon adresse de livraison\n$count items, $count articles\nDiscount, Remise\nShipping, Expédition\nNo tax, Pas de taxe\nTax, Taxe\nSorry, there is no available method for your address, Désolé, aucun mode de livraison n'est disponible pour votre adresse\nPlease enter a shipping address in order to see shipping quotes, Veuillez entrer une adresse de livraison pour voir les frais de livraison\nContinue to payment, Continuer vers le paiement\nNo payment method available, Aucun mode de paiement disponible\nShip to, Expédier à\nShipment, Envoi\nThank you $name!, Merci $name !\nContact information, Informations de contact\nOrder #$orderNumber, Commande #$orderNumber\nADD TO CART, AJOUTER AU PANIER\nPlease select variant options, Veuillez sélectionner les options de variantes\nVIEW CART ($count), VOIR LE PANIER ($count)\nJUST ADDED TO YOUR CART, AJOUTÉ À VOTRE PANIER\nPromotion code?, Code promotionnel ?\nEnter coupon code, Entrez le code coupon\nApply, Appliquer\nYour cart is empty!, Votre panier est vide !\nFull name, Nom complet\nTelephone, Téléphone\nAddress, Adresse\nCity, Ville\nCountry, Pays\nPostcode, Code postal\nInclusive of tax, Taxes incluses\nSorry, there is no available method for your address, Désolé, aucun mode de livraison n'est disponible pour votre adresse\nFull name is required, Le nom complet est requis\nTelephone is required, Le téléphone est requis\nAddress is required, L'adresse est requise\nCity is required, La ville est requise\nCountry is required, Le pays est requis\nPostcode is required, Le code postal est requis\nContact, Contact\n"
  },
  {
    "path": "translations/fr/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, Remise de ${discount} pour toutes les commandes de plus de ${price}.\nPlease select, Veuillez sélectionner\nThe page you requested does not exist., La page que vous avez demandée n'existe pas.\nContinue shopping, Continuer vos achats\n404 Page Not Found, 404 Page non trouvée\nHome, Accueil\nNot found, Non trouvé\n"
  },
  {
    "path": "translations/fr/paypal.csv",
    "content": "You will be redirected to PayPal, Vous serez redirigé vers PayPal.\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., Nous sommes désolés. Une erreur est survenue lors du traitement de votre paiement. Votre carte n'a pas été débitée. Veuillez réessayer."
  },
  {
    "path": "translations/gr/account.csv",
    "content": "Login,Σύνδεση\nLogout,Αποσύνδεση\nEmail,Ηλεκτρονικό Ταχυδρομείο\nPassword,Κωδικός Πρόσβασης\nForgot your password?,Ξεχάσατε τον κωδικό σας;\nCreate an account,Δημιουργία λογαριασμού\nRegister,Εγγραφή\nFull Name,Πλήρες Όνομα\nFull name,Πλήρες όνομα\nSIGN IN,ΕΙΣΟΔΟΣ\nOrder History,Ιστορικό Παραγγελιών\nAccount Details,Λεπτομέρειες Λογαριασμού\nYou have not placed any orders yet,Δεν έχετε κάνει καμία παραγγελία ακόμα\nMy Account,Ο Λογαριασμός μου\nAlready have an account?,Έχετε ήδη λογαριασμό;\nContact information,Στοιχεία επικοινωνίας\nCity,Πόλη\nAccount Information,Πληροφορίες Λογαριασμού\nAddress,Διεύθυνση\nPostcode,Ταχυδρομικός Κώδικας\nPostcode is required,Απαιτείται Ταχυδρομικός Κώδικας\nTelephone is required,Απαιτείται Τηλέφωνο\nTelephone,Τηλέφωνο\nFull name is required,Απαιτείται πλήρες όνομα\nCountry is required,Απαιτείται χώρα\nCountry,Χώρα\nProvince is required,Απαιτείται Επαρχία\nProvince,Επαρχία\nBilling Address,Διεύθυνση Τιμολόγησης\nShipping Address,Διεύθυνση Αποστολής\nCreate A New Account,Δημιουργία Νέου Λογαριασμού\nContact,Επικοινωνία\nCustomer information,Πληροφορίες Πελάτη\n"
  },
  {
    "path": "translations/gr/catalog.csv",
    "content": "Featured collection,Επιλεγμένη Συλλογή\nSHOP BY,Αγοράστε ανά\nSort By,Ταξινόμηση ανά\nThere is no product to display,Δεν υπάρχουν προϊόντα για προβολή\n${count} products,${count} Προϊόντα\n\"Search results for \"${keyword}\"\",\"Αποτελέσματα αναζήτησης για \"${keyword}\"\"\n"
  },
  {
    "path": "translations/gr/checkout.csv",
    "content": "Total,Σύνολο\nOrder,Παραγγελία\nSku,Κωδικός SKU\nContinue to shipping,Συνέχεια στην αποστολή\nContinue shopping,Συνέχιση αγορών\nContinue Shopping,Συνέχιση Αγορών\nCONTINUE SHOPPING,ΣΥΝΕΧΙΣΕ ΤΙΣ ΑΓΟΡΕΣ\nShopping cart,Καλάθι Αγορών\nProduct,Προϊόν\nPrice,Τιμή\nQuantity,Ποσότητα\nRemove,Αφαίρεση\nQty,Ποσότητα\nSub total,Μερικό σύνολο\nDiscount(${coupon}),Έκπτωση(${coupon})\nOrder summary,Περίληψη Παραγγελίας\nTaxes and shipping calculated at checkout,Οι φόροι και τα έξοδα αποστολής υπολογίζονται κατά την ολοκλήρωση της παραγγελίας\nCHECKOUT,ΟΛΟΚΛΗΡΩΣΗ ΠΑΡΑΓΓΕΛΙΑΣ\nChange,Αλλαγή\nPayment Method,Μέθοδος Πληρωμής\nShipping Address,Διεύθυνση Αποστολής\nShipping Method,Μέθοδος Αποστολής\nNo payment methods available,Δεν υπάρχουν διαθέσιμες μέθοδοι πληρωμής\nPlace Order,Ολοκλήρωση Παραγγελίας\nMy billing address is same as shipping address,Η διεύθυνση χρέωσης μου είναι ίδια με τη διεύθυνση αποστολής\n${count} items,${count} αντικείμενα\nDiscount,Έκπτωση\nShipping,Αποστολή\nNo tax,Χωρίς φόρο\nTax,Φόρος\nSorry,Λυπούμαστε\nPlease enter a shipping address in order to see shipping quotes,Παρακαλώ εισάγετε μια διεύθυνση αποστολής για να δείτε τα τελικά έξοδα αποστολής\nContinue to payment,Συνέχεια στην πληρωμή\nNo payment method available,Δεν υπάρχει διαθέσιμη μέθοδος πληρωμής\nShip to,Αποστολή προς\nShipment,Αποστολή\nThank you ${name}!,Ευχαριστούμε ${name}!\nContact information,Στοιχεία Επικοινωνίας\nOrder #${orderNumber},Παραγγελία #${orderNumber}\nADD TO CART,ΠΡΟΣΘΗΚΗ ΣΤΟ ΚΑΛΑΘΙ\nPlease select variant options,Παρακαλώ επιλέξτε παραλλαγές\nVIEW CART (${count}),ΠΡΟΒΟΛΗ ΚΑΛΑΘΙΟΥ (${count})\nJUST ADDED TO YOUR CART,ΠΡΟΣΤΙΘΕΜΕΝΟ ΣΤΟ ΚΑΛΑΘΙ ΣΑΣ\nPromotion code?,Κωδικός Προώθησης;\nEnter coupon code,Εισαγωγή κωδικού κουπονιού\nApply,Εφαρμογή\nYour cart is empty!,Το καλάθι αγορών σας είναι άδειο!\nFull name,Ονοματεπώνυμο\nTelephone,Τηλέφωνο\nAddress,Διεύθυνση\nCity,Πόλη\nCountry,Χώρα\nPostcode,Ταχυδρομικός Κώδικας\nInclusive of tax,Συμπεριλαμβανομένου του φόρου\nSorry,Λυπούμαστε\nFull name is required,Απαιτείται ονοματεπώνυμο\nTelephone is required,Απαιτείται τηλέφωνο\nAddress is required,Απαιτείται διεύθυνση\nCity is required,Απαιτείται πόλη\nCountry is required,Απαιτείται χώρα\nPostcode is required,Απαιτείται ταχυδρομικός κώδικας\nContact,Επικοινωνία\n"
  },
  {
    "path": "translations/gr/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price},Έκπτωση ${discount} Για Όλες τις Παραγγελίες Άνω των ${price}\nPlease select,Παρακαλώ επιλέξτε\nThe page you requested does not exist.,Η σελίδα που ζητήσατε δεν υπάρχει.\nContinue shopping,Συνέχιση αγορών\n404 Page Not Found,404 Σελίδα Δεν Βρέθηκε\nHome,Αρχική Σελίδα\nNot found,Δεν βρέθηκε\n"
  },
  {
    "path": "translations/gr/paypal.csv",
    "content": "You will be redirected to PayPal,Θα ανακατευθυνθείτε στο PayPal\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again.,Λυπούμαστε. Παρουσιάστηκε ένα σφάλμα κατά την επεξεργασία της πληρωμής σας. Η κάρτα σας δεν χρεώθηκε. Παρακαλούμε προσπαθήστε ξανά.\n"
  },
  {
    "path": "translations/hu/account.csv",
    "content": "Login, Belépés\nLogout, Kilépés\nEmail, E-Mail\nPassword, Jelszó\nForgot your password?, Elfelejtetted a jelszavad?\nCreate an account, Új fiók létrehozása\nRegister, Regisztráció\nFull Name, Teljes Név\nFull name, Teljes név\nSIGN IN, LÉPJ BE\nOrder History, Rendeléseim\nAccount Details, Fiókadatok\nYou have not placed any orders yet, Még nincs feladott rendelésed\nMy Account, Fiókom\nAlready have an account?, Már van fiókod?\nContact information, Elérhetőségek\nCity, Város\nAccount Information, Fiókinformációk\nAddress, Cím\nPostcode, IRSZ\nPostcode is required, Az irányítószám megadása kötelező\nTelephone is required, A telefonszám megadása kötelező\nTelephone, Telefon\nCountry is required, Az ország megadása kötelező\nCountry, Ország\nProvince is required, A megye megadása kötelező\nProvince, Megye\nBilling Address, Számlázási cím\nShipping Address, Szállítási cím\nCreate A New Account, Új fiók létrehozása\nContact, Elérhetőség\nCustomer information, Ügyfélinformációk"
  },
  {
    "path": "translations/hu/catalog.csv",
    "content": "Featured collection, Kiemelt termékek\nSHOP BY, Vásárlás\nSort By, Rendezés\nThere is no product to display, Nincs megjeleníthető termék\n${count} products, ${count} Termék\nSearch results for \"${keyword}\", \\\"${keyword}\\\" keresési eredményei"
  },
  {
    "path": "translations/hu/checkout.csv",
    "content": "Total, Összesen\nOrder, Megrendelés\nSku, SKU\nContinue to shipping, Tovább a szállításhoz\nContinue shopping, Vásárlás folytatása\nContinue Shopping, Vásárlás Folytatása\nCONTINUE SHOPPING, VÁSÁRLÁS FOLYTATÁSA\nShopping cart, Bevásárlókosár\nProduct, Termék\nPrice, Ár\nQuantity, Mennyiség\nRemove, Eltávolít\nQty, Menny.\nSub total, Részösszeg\nDiscount(${coupon}), Kedvezmény(${coupon})\nOrder summary, Megrendelés összesítő\nTaxes and shipping calculated at checkout, Az ÁFA és a szállítási költség a fizetéskor kerül véglegesítésre\nCHECKOUT, Fizetés\nChange, Módosít\nPayment Method, Fizetési mód\nShipping Address, Szállítási cím\nShipping Method, Szállítási mód\nNo payment methods available, Nincs elérhető fizetési mód\nPlace Order, Megrendelés feladása\nMy billing address is same as shipping address, A számlázási és a szállítási címem megeggyezik\n${count} items, ${count} tétel\nDiscount, Kedvezmény\nShipping, Szállítás\nNo tax, Nincs ÁFA\nTax, ÁFA\nSorry, there is no available method for your address, Sajnáljuk, de a címedhez nincs elérhető szállítási mód\nPlease enter a shipping address in order to see shipping quotes, A szállítási díj megjelenítéséhez kérjük add meg a szállítási címedet\nContinue to payment, Tovább a fizetéshez\nShip to, Szállítási cím\nShipment, Szállítás\nThank you ${name}!, Köszönjük ${name}!\nContact information, Elérhetőség\nOrder #${orderNumber}, Megrendelés #${orderNumber}\nADD TO CART, KOSÁRBA\nPlease select variant options, Válassz a lehetőségek közül!\nVIEW CART (${count}), KOSÁR MEGTEKINTÉSE (${count})\nJUST ADDED TO YOUR CART, HOZZÁADTUK A KOSARADHOZ!\nPromotion code?, Van promóciós kódod?\nEnter coupon code, Add meg a kuponkódodat!\nApply, Jóváhagy\nYour cart is empty!, A bevásárlókosarad üres!\nFull name, Teljes név\nTelephone, Telefonszám\nAddress, Cím\nCity, Város\nCountry, Ország\nPostcode, IRSZ\nInclusive of tax, ÁFA nélküli\nSorry, there is no available method for your address,Sajnáljuk, a címedre jelenleg nincs elérhető szállítási mód\nFull name is required, Kérjük add meg a teljes nevedet!\nTelephone is required, Kérjük add meg a telefonszámodat!\nAddress is required, Kérjük add meg a címedet!\nCity is required, Kérjük add meg a várost!\nCountry is required, Kérjük add meg az országot!\nPostcode is required, Kérjük add meg az irányítószámot!\nContact, Elérhetőség"
  },
  {
    "path": "translations/hu/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, ${discount} kedvezmény minden ${price} rendelési összeg felett\nPlease select, Kérjük válassz...\nThe page you requested does not exist., A kért oldal nem elérhető!\nContinue shopping, Vásárlás folytatása\n404 Page Not Found, 404 Nincs ilyen oldal\nHome, Kezdőlap\nNot found, Nem található"
  },
  {
    "path": "translations/it/account.csv",
    "content": "Login, Login\nLogout, Logout\nEmail, E-mail\nPassword, Password\nForgot your password?, Hai dimenticato la password？\nCreate an account, Crea un account\nRegister, Registrati\nFull Name, Nome e cognome\nFull name, Nome e cognome\nSIGN IN, login\nOrder History, Cronologia ordini\nAccount Details, Dettagli account\nYou have not placed any orders yet, Non hai ancora effettuato un ordine\nMy Account, Il mio account\nAlready have an account?, Non hai ancora un account？\nContact information, Informazioni di contatto\nCity, Città\nAccount Information, Informazioni account\nAddress, Indirizzo\nPostcode, CAP\nPostcode is required, Il CAP è un campo obbligatorio\nTelephone is required, Il telefono è un campo obbligatorio\nTelephone, Telefono\nFull name is required, Nome e cognome è un campo obbligatorio\nCountry is required, Paese è un campo obbligatorio\nCountry, Paese\nProvince is required, Provincia è un campo obbligatorio\nProvince, Provincia\nBilling Address, Indirizzo di fatturazione\nShipping Address, Indirizzo di spedizione\nCreate A New Account, Crea un nuovo account\nContact, Contatti\nCustomer information, Informazioni cliente\n"
  },
  {
    "path": "translations/it/catalog.csv",
    "content": "Featured collection, In primo piano\nSHOP BY, FILTRA\nSort By, Ordina per\nThere is no product to display, Non ci sono prodotti da mostrare\n${count} products, ${count} prodotti\nSearch results for \"${keyword}\", Risultati per “${keyword}”\n"
  },
  {
    "path": "translations/it/checkout.csv",
    "content": "Total, Totale\nOrder, Ordine\nSku, SKU\nContinue to shipping, Prosegui con la spedizione\nContinue shopping, Continua gli acquisti\nContinue Shopping, Continua gli acquisti\nCONTINUE SHOPPING, CONTINUA GLI ACQUISTI\nShopping cart, Carrello\nProduct, Prodotto\nPrice, Prezzo\nQuantity, Quantità\nRemove, Rimuovi\nQty, Qta\nSub total, Subtotale\nDiscount(${coupon}), Sconto (${coupon})\nOrder summary, Riepilogo ordine\nTaxes and shipping calculated at checkout, Tasse e spedizione saranno calcolate al momento del pagamento\nCHECKOUT, PAGAMENTO\nChange, Cambia\nPayment Method, Metodo di pagamento\nShipping Method, Metodo di spedizione\nNo payment methods available, Nessun metodo di pagamento disponibile\nPlace Order, Effettua ordine\nMy billing address is the same as the shipping address, L'indirizzo di fatturazione è lo stesso dell'indirizzo di spedizione\n${count} items, ${count} articoli\nDiscount, Sconto\nShipping, Spedizione\nNo tax, Nessuna tassa\nTax, Tassa\nSorry, Spiacente\nPlease enter a shipping address in order to see shipping quotes, Inserisci un indirizzo di spedizione per visualizzare i costi di spedizione\nContinue to payment, Prosegui con il pagamento\nNo payment method available, Nessun metodo di pagamento disponibile\nShip to, Spedisci a\nShipment, Spedizione\nThank you ${name}!, Grazie ${name}!\nContact information, Informazioni di contatto\nOrder #${orderNumber}, Ordine n. ${orderNumber}\nADD TO CART, AGGIUNGI AL CARRELLO\nPlease select variant options, Seleziona una delle opzioni\nVIEW CART (${count}), VEDI CARRELLO (${count})\nJUST ADDED TO YOUR CART, AGGIUNTO AL CARRELLO\nPromotion code?, Hai un codice promo?\nEnter coupon code, Inserisci il codice coupon\nApply, Applica\nYour cart is empty!, Il tuo carrello è vuoto!\nInclusive of tax ${taxAmount}, Incluso ${taxAmount} di tasse.\n"
  },
  {
    "path": "translations/it/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, Sconto del ${discount} per tutti gli ordini al di sopra di ${price}\nPlease select, Seleziona un'opzione\nThe page you requested does not exist., Pare che la pagina richiesta non esista.\nContinue shopping, Continua con gli acquisti\n404 Page Not Found, 404 Pagina non trovata\nHome, Home\nNot found, Nessun risultato\n"
  },
  {
    "path": "translations/it/paypal.csv",
    "content": "You will be redirected to PayPal, Verrai reindirizzato su PayPal.\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again.,Siamo spiacenti. Si è verificato un errore nell'elaborazione del pagamento. Non è stato addebitato alcun importo. Si prega di riprovare."
  },
  {
    "path": "translations/mn/account.csv",
    "content": "Login, Нэвтрэх\r\nLogout, Гарах\r\nEmail, И-мэйл\r\nPassword, Нууц үг\r\nForgot your password?, Нууц үгээ мартсан уу?\r\nCreate an account, Бүртгэл үүсгээх\r\nRegister, Бүртгүүлэх\r\nFull Name, Бүтэн нэр\r\nFull name, Бүтэн нэр\r\nSIGN IN, НЭВТРЭХ\r\nOrder History, Захиалгын түүх\r\nAccount Details, Бүртгэлийн дэлгэрэнгүй\r\nYou have not placed any orders yet, Та одоогоор ямар ч захиалга өгөөгүй байна\r\nMy Account, Миний бүртгэл\r\nAlready have an account?, Өмнө нь бүртгэл үүсгэсэн үү?\r\nContact information, Холбоо барих мэдээлэл\r\nCity, Хот\r\nAccount Information, Бүртгэлийн мэдээлэл\r\nAddress, Хаяг\r\nPostcode, Шуудангийн код\r\nPostcode is required, Шуудангийн код шаардлагатай\r\nTelephone is required, Утасны дугаар шаардлагатай\r\nTelephone, Утас\r\nFull name is required, Бүтэн нэр шаардлагатай\r\nCountry is required, Улс шаардлагатай\r\nCountry, Улс\r\nProvince is required, Муж/аймаг шаардлагатай\r\nProvince, Муж/аймаг\r\nBilling Address, Төлбөрийн хаяг\r\nShipping Address, Хүргэлтийн хаяг\r\nCreate A New Account, Шинэ бүртгэл үүсгэх\r\nContact, Холбоо\r\nCustomer information, Хэрэглэгчийн мэдээлэл\r\n"
  },
  {
    "path": "translations/mn/catalog.csv",
    "content": "Featured collection, Онцлох цуглуулга\r\nSHOP BY, Ангиллаар худалдан авах\r\nSort By, Эрэмбэлэх\r\nThere is no product to display, Харавчлах бүтээгдэхүүн байхгүй\r\n${count} products, ${count} бүтээгдэхүүн\r\nSearch results for \"${keyword}\", \"${keyword}\"-ээр хайсан үр дүн\r\n"
  },
  {
    "path": "translations/mn/checkout.csv",
    "content": "Total, Нийт\r\nOrder, Захиалга\r\nSku, SKU\r\nContinue to shipping, Хүргэлт рүү үргэлжлүүлэх\r\nContinue shopping, Худалдан авалтаа үргэлжлүүлэх\r\nContinue Shopping, Худалдан авалтаа үргэлжлүүлэх\r\nCONTINUE SHOPPING, ХУДАЛДАН АВАЛТАА ҮРГЭЛЖЛҮҮЛЭХ\r\nShopping cart, Сагс\r\nProduct, Бүтээгдэхүүн\r\nPrice, Үнэ\r\nQuantity, Тоо хэмжээ\r\nRemove, Устгах\r\nQty, Тоо\r\nSub total, Дүнгийн дүн\r\nDiscount(${coupon}), Хөнгөлөлт(${coupon})\r\nOrder summary, Захиалгын тойм\r\nTaxes and shipping calculated at checkout, Татвар болон хүргэлт нь захиалга баталгаажуулах үед тооцогдоно\r\nCHECKOUT, ТӨЛӨЛТ\r\nChange, Өөрчлөх\r\nPayment Method, Төлбөрийн арга\r\nShipping Method, Хүргэлтийн төрөл\r\nNo payment methods available, Авах боломжтой төлбөрийн арга байхгүй\r\nPlace Order, ЗАХИАЛГА ӨГӨХ\r\nMy billing address is same as shipping address, Миний төлбөрийн хаяг нь хүргэлтийн хаягтай ижил\r\n${count} items, ${count} ширхэг\r\nDiscount, Хөнгөлөлт\r\nShipping, Хүргэлт\r\nNo tax, Татваргүй\r\nTax, Татвар\r\nSorry, there is no available method for your address, Уучлаарай, таны хаягт боломжтой арга байхгүй байна\r\nPlease enter a shipping address in order to see shipping quotes, Хүргэлтийн үнэ үзэхийн тулд хүргэлтийн хаяг оруулна уу\r\nContinue to payment, Төлбөр рүү шилжих\r\nNo payment method available, Төлбөр хийх боломжгүй\r\nShip to, Илгээх хаяг\r\nShipment, Ачилт\r\nThank you ${name}!, Баярлалаа ${name}!\r\nContact information, Холбоо барих мэдээлэл\r\nOrder #${orderNumber}, Захиалга #${orderNumber}\r\nADD TO CART, САГСАД НЭМЭХ\r\nPlease select variant options, Хувилбарын сонголтыг сонгоно уу\r\nVIEW CART (${count}), САГСЫГ ХАРАХ (${count})\r\nJUST ADDED TO YOUR CART, Сагс руу зүгээр шинэ нэмэгдсэн\r\nPromotion code?, Хөнгөлөлтийн код?\r\nEnter coupon code, Купоны код оруулна уу\r\nApply, Оруулах\r\nYour cart is empty!, Таны сагс хоосон байна!\r\n"
  },
  {
    "path": "translations/mn/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, ${price}-с дээш бүх захиалгад ${discount} хөнгөлөлт\r\nPlease select, Сонгоно уу\r\nThe page you requested does not exist., Та хүссэн хуудсандаа хүрж чадахгүй байна.\r\nContinue shopping, Худалдан авалтаа үргэлжлүүлэх\r\n404 Page Not Found, 404 Хуудас олдсонгүй\r\nHome, Нүүр хуудас\r\nNot found, Олдсонгүй\r\n"
  },
  {
    "path": "translations/mn/paypal.csv",
    "content": "You will be redirected to PayPal, Та PayPal руу шилжих болно\r\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., Уучлаарай. Таны төлбөрийг боловсруулах явцад алдаа гарлаа. Таны картаас ямар ч мөнгө хасагдаагүй. Дахин оролдоно уу.\r\n"
  },
  {
    "path": "translations/nb/account.csv",
    "content": "Login, Logg inn\nLogout, Logg ut\nEmail, E-post\nPassword, Passord\nForgot your password?, Glemt passordet ditt?\nCreate an account, Opprett en konto\nRegister, Registrer\nFull Name, Fullt navn\nFull name, Fullt navn\nSIGN IN, Logg inn\nOrder History, Bestillingshistorikk\nAccount Details, Kontodetaljer\nYou have not placed any orders yet, Du har ikke lagt inn noen bestillinger ennå\nMy Account, Min konto\nAlready have an account?, Har du allerede en konto?\nContact information, Kontaktinformasjon\nCity, By\nAccount Information, Kontoopplysninger\nAddress, Adresse\nPostcode, Postnummer\nPostcode is required, Postnummer er påkrevd\nTelephone is required, Telefonnummer er påkrevd\nTelephone, Telefon\nFull name is required, Fullt navn er påkrevd\nCountry is required, Land er påkrevd\nCountry, Land\nProvince is required, Provins er påkrevd\nProvince, Provins\nBilling Address, Fakturaadresse\nShipping Address, Leveringsadresse\nCreate A New Account, Opprett en ny konto\nContact, Kontakt\nCustomer information, Kundeinformasjon\n"
  },
  {
    "path": "translations/nb/catalog.csv",
    "content": "Featured collection, Utvalgt samling\nSHOP BY, Handle etter\nSort By, Sorter etter\nThere is no product to display, Det er ingen produkter å vise\n${count} products, ${count} produkter\nSearch results for \"${keyword}\", Søkeresultater for \"${keyword}\"\n"
  },
  {
    "path": "translations/nb/checkout.csv",
    "content": "Total, Totalt\nOrder, Bestilling\nSku, SKU\nContinue to shipping, Fortsett til frakt\nContinue shopping, Fortsett å handle\nContinue Shopping, Fortsett å handle\nCONTINUE SHOPPING, FORTSETT Å HANDLE\nShopping cart, Handlekurv\nProduct, Produkt\nPrice, Pris\nQuantity, Antall\nRemove, Fjern\nQty, Antall\nSub total, Delsum\nDiscount(${coupon}), Rabatt(${coupon})\nOrder summary, Ordresammendrag\nTaxes and shipping calculated at checkout, Skatter og frakt beregnes ved kassen\nCHECKOUT, Til kassen\nChange, Endre\nPayment Method, Betalingsmåte\nShipping Method, Fraktmetode\nNo payment methods available, Ingen betalingsmetoder tilgjengelig\nPlace Order, Legg inn bestilling\nMy billing address is same as shipping address, Fakturaadressen er den samme som leveringsadressen\n${count} items, ${count} varer\nDiscount, Rabatt\nShipping, Frakt\nNo tax, Ingen skatt\nTax, Skatt\nSorry, there is no available method for your address, Beklager, det finnes ingen tilgjengelig metode for din adresse\nPlease enter a shipping address in order to see shipping quotes, Vennligst oppgi en leveringsadresse for å se fraktpriser\nContinue to payment, Fortsett til betaling\nNo payment method available, Ingen betalingsmetode tilgjengelig\nShip to, Send til\nShipment, Forsendelse\nThank you ${name}!, Takk, ${name}!\nContact information, Kontaktinformasjon\nOrder #${orderNumber}, Bestilling #${orderNumber}\nADD TO CART, LEGG TIL I HANDLEKURVEN\nPlease select variant options, Velg variantalternativer\nVIEW CART (${count}), SE HANDLEKURV (${count})\nJUST ADDED TO YOUR CART, NETTOPP LAGT TIL I HANDLEKURVEN\nPromotion code?, Kampanjekode？\nEnter coupon code, Skriv inn rabattkode\nApply, Bruk\nYour cart is empty!, Handlekurven din er tom！\n"
  },
  {
    "path": "translations/nb/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, Rabatt på ${discount} for alle bestillinger over ${price}\nPlease select, Vennligst velg...\nThe page you requested does not exist., Siden du ba om eksisterer ikke.\nContinue shopping, Fortsett å handle\n404 Page Not Found, 404 Side ikke funnet\nHome, Hjem\nNot found, Ikke funnet\n"
  },
  {
    "path": "translations/nb/paypal.csv",
    "content": "You will be redirected to PayPal, Du vil bli omdirigert til PayPal.\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., Beklager. Det oppstod en feil under behandling av betalingen din. Kortet ditt ble ikke belastet. Vennligst prøv igjen.\n"
  },
  {
    "path": "translations/ne/account.csv",
    "content": "Login, लग इन\nLogout, लग आउट\nEmail, इमेल\nPassword, पासवर्ड\nForgot your password?, तपाईंले आफ्नो पासवर्ड बिर्सनुभयो?\nCreate an account, खाता बनाउनुहोस्\nRegister, दर्ता गर्नुहोस्\nFull Name, पुरा नाम\nFull name, पुरा नाम\nSIGN IN, साइन इन गर्नुहोस्\nOrder History, अर्डर इतिहास\nAccount Details, खाता विवरण\nYou have not placed any orders yet, तपाईंले अहिलेसम्म कुनै अर्डर गर्नुभएको छैन\nMy Account, मेरो खाता\nAlready have an account?, पहिले नै खाता छ?\nContact information, सम्पर्क जानकारी\nCity, शहर\nAccount Information, खाता जानकारी\nAddress, ठेगाना\nPostcode, पिन कोड\nPostcode is required, पिन कोड आवश्यक छ\nTelephone is required, टेलिफोन आवश्यक छ\nTelephone, टेलिफोन\nFull name is required, पुरा नाम आवश्यक छ\nCountry is required, देश आवश्यक छ\nCountry, देश\nProvince is required, प्रदेश आवश्यक छ\nProvince, प्रदेश\nBilling Address, बिल पठाउने ठेगाना\nShipping Address, ढुवानी ठेगाना\nCreate A New Account, नयाँ खाता बनाउनुहोस्\nContact, सम्पर्क\nCustomer information, ग्राहक जानकारी"
  },
  {
    "path": "translations/ne/catalog.csv",
    "content": "Featured collection, विशेष संग्रह\nSHOP BY, बाट किन्नुहोस् \nSort By, द्वारा क्रमबद्ध\nThere is no product to display, देखाउन कुनै पनि सामान छैन\n${count} products, ${count} सामानहरू\nSearch results for \"${keyword}\", \"${keyword}\"को लागि खोज परिणाम\n"
  },
  {
    "path": "translations/ne/checkout.csv",
    "content": "Total, जम्मा\nOrder, अर्डर\nSku, SKU\nContinue to shipping, ढुवानी गर्न जारी राख्नुहोस्\nContinue shopping, किनमेल जारी राख्नुहोस्\nContinue Shopping, किनमेल जारी राख्नुहोस्\nCONTINUE SHOPPING, किनमेल जारी राख्नुहोस्\nShopping cart, खरिदी कार्ट\nProduct, सामान\nPrice, मूल्य\nQuantity, मात्रा\nRemove, हटाउनुहोस्\nQty, मात्रा\nSub total, उप-जम्मा\nDiscount(${coupon}), छुट(${coupon})\nOrder summary, अर्डर सारांश\nTaxes and shipping calculated at checkout, चेकआउट मा कर र ढुवानी हिसाब\nCHECKOUT, चेकआउट\nChange, परिवर्तन\nPayment Method, भुक्तानी विधि\nShipping Method, ढुवानी विधि\nNo payment methods available, कुनै पनि भुक्तानी विधि उपलब्ध छैन\nPlace Order, अर्डर राख्नुहोस्\nMy billing address is same as shipping address, मेरो बिलिङ ठेगाना ढुवानी ठेगाना एउटै हो\n${count} items, ${count} वस्तुहरू\nDiscount, छुट\nShipping, ढुवानी\nNo tax, कुनै पनि कर छैन\nTax, कर\n\"Sorry, there is no available method for your address\", \"माफ गर्नुहोस्, तपाईंको ठेगानाको लागि कुनै पनि उपलब्ध तरिका छैन\"\nPlease enter a shipping address in order to see shipping quotes, ढुवानी उद्धरणहरू हेर्नको लागि कृपया ढुवानी ठेगाना प्रविष्ट गर्नुहोस्\nContinue to payment, भुक्तानीमा जारी रहनुहोस्\nNo payment method available, कुनै पनि भुक्तानी विधि उपलब्ध छैन\nShip to, पठाउनुहोस्\nShipment, ढुवानी\nThank you ${name}!, धन्यवाद, ${name}!\nContact information, सम्पर्क जानकारी\nOrder #${orderNumber}, अर्डर #${orderNumber}\nADD TO CART, कार्टमा थप्नुहोस्\nPlease select variant options, कृपया विविध विकल्पहरू चयन गर्नुहोस्\nVIEW CART (${count}), कार्ट हेर्नुहोस् (${count})\nJUST ADDED TO YOUR CART, तपाईंको कार्टमा मात्र थपिएको\nPromotion code?, प्रवर्धन कोड？\nEnter coupon code, कुपन कोड प्रविष्ट गर्नुहोस्\nApply, लागू गर्नुहोस्\nYour cart is empty!, तपाईंको कार्ट खाली छ！\n"
  },
  {
    "path": "translations/ne/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, ${price} मा भन्दा बढी सबै अर्डरको लागि ${discount} छुट\nPlease select, कृपया चयन गर्नुहोस्...\nThe page you requested does not exist., तपाईंले अनुरोध गरेको पृष्ठ मौजूद छैन।\nContinue shopping, किनमेल जारी राख्नुहोस्\n404 Page Not Found, 404 पृष्ठ फेला परेन\nHome, गृहपृष्ठ\nNot found, फेला परेन\n"
  },
  {
    "path": "translations/ne/paypal.csv",
    "content": "You will be redirected to PayPal, तपाईंलाई PayPal मा पुन: निर्देशित गरिनेछ।\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., हामीलाई माफ गर्नुहोस। तपाईंको भुक्तानी प्रक्रिया गर्दा त्रुटि भयो। तपाईंको कार्ड चार्ज गरिएको छैन। कृपया पुन: प्रयास गर्नुहोस्।"
  },
  {
    "path": "translations/nl/account.csv",
    "content": "Login, Inloggen\nLogout, Uitloggen\nEmail, E-mail\nPassword, Wachtwoord\nForgot your password?, Wachtwoord vergeten？\nCreate an account, Maak een account aan\nRegister, Registreren\nFull Name, Volledige Naam\nFull name, Volledige naam\nSIGN IN, Inloggen\nOrder History, Bestelgeschiedenis\nAccount Details, Accountsgegevens\nYou have not placed any orders yet, U heeft nog geen bestellingen geplaatst\nMy Account, Mijn account\nAlready have an account?, Heeft u al een account？\nContact information, Contactinformatie\nCity, Stad\nAccount Information, Account informatie\nAddress, Adres\nPostcode, Postcode\nPostcode is required, Postcode is verplicht\nTelephone is required, Telefoon is verplicht\nTelephone, Telefoon\nFull name is required, Volledige naam is verplicht\nCountry is required, Land is verplicht\nCountry, Land\nProvince is required, Provincie is verplicht\nProvince, Provincie\nBilling Address, Factuuradres\nShipping Address, Verzendadres\nCreate A New Account, Maak een nieuw account aan\nContact, Contact\nCustomer information, Klant informatie"
  },
  {
    "path": "translations/nl/catalog.csv",
    "content": "Featured collection, Uitgelichte collectie\nSHOP BY, Winkel op\nSort By, Sorteren op\nThere is no product to display, Er zijn geen producten om te tonen\n${count} products, ${count} producten\nSearch results for \"${keyword}\", Zoekresultaten voor “${keyword}”"
  },
  {
    "path": "translations/nl/checkout.csv",
    "content": "Total, Totaal\nOrder, Bestelling\nSku, SKU\nContinue to shipping, Doorgaan naar verzenden\nContinue shopping, Doorgaan met winkelen\nContinue Shopping, Doorgaan met Winkelen\nCONTINUE SHOPPING, DOORGAAN MET WINKELEN\nShopping cart, Winkelmandje\nProduct, Product\nPrice, Prijs\nQuantity, Aantal\nRemove, Verwijderen\nQty, Aantal\nSub total, Subtotaal\nDiscount(${coupon}), Korting(${coupon})\nOrder summary, Besteloverzicht\nTaxes and shipping calculated at checkout, BTW en verzendkosten worden berekend bij betaling\nCHECKOUT, Betalen\nChange, Veranderen\nPayment Method, Betaalmethode\nShipping Method, Verzendmethode\nNo payment methods available, Geen betaalmethodes beschikbaar\nPlace Order, Plaats Bestelling\nMy billing address is same as shipping address, Factuuradres is hetzelfde als het verzendadres\n${count} items, ${count} producten\nDiscount, Korting\nShipping, Verzending\nNo tax, Geen BTW\nTax, BTW\nSorry, there is no available method for your address, Excuses, er is geen beschikbare methode voor uw adres\nPlease enter a shipping address in order to see shipping quotes, Voer een verzendadres in om verzendkosten te berekenen\nContinue to payment, Doorgaan naar betaling\nNo payment method available, Geen betaalmethodes beschikbaar\nShip to, Verzenden naar\nShipment, Verzending\nThank you ${name}!, Bedankt, ${name}!\nContact information, Contactinformatie\nOrder #${orderNumber}, Bestelling #${orderNumber}\nADD TO CART, TOEVOEGEN AAN WINKELMANDJE\nPlease select variant options, Selecteer alstublieft opties\nVIEW CART (${count}), WINKELMANDJE BEKIJKEN (${count})\nJUST ADDED TO YOUR CART, TOEGEVOEGD AAN WINKELMANDJE\nPromotion code?, Kortingscode？\nEnter coupon code, Voer kortingscode in\nApply, Toepassen\nYour cart is empty!, Uw winkelmandje is leeg！"
  },
  {
    "path": "translations/nl/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, Korting van ${discount} voor alle bestellingen over ${price}\nPlease select, Selecteren...\nThe page you requested does not exist., De opgevraagde pagina bestaat niet.\nContinue shopping, Doorgaan met winkelen\n404 Page Not Found, 404 pagina niet gevonden\nHome, Home\nNot found, Niet gevonden"
  },
  {
    "path": "translations/nl/paypal.csv",
    "content": "You will be redirected to PayPal, U wordt doorgestuurd naar PayPal.\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again.,Het spijt ons. Er is een fout opgetreden bij het verwerken van je betaling. Je kaart is niet gedebiteerd. Probeer het opnieuw."
  },
  {
    "path": "translations/pt/account.csv",
    "content": "Login, Login\nLogout, Logout\nEmail, E-Mail\nPassword, Senha\nForgot your password?, Esqueceu sua senha?\nCreate an account, Criar uma conta\nRegister, Registrar\nFull Name, Nome Completo\nFull name, Nome completo\nSIGN IN, REGISTRAR\nOrder History, Histórico de Compras\nAccount Details, Detalhes da Conta\nYou have not placed any orders yet, Você não fez nenhuma compra ainda\nMy Account, Minha Conta\nAlready have an account?, Já possui uma conta?\nContact information, Informações de contato\nCity, Cidade\nAccount Information, Informações da Conta\nAddress, Endereço\nPostcode, CEP\nPostcode is required, CEP é obrigatório\nTelephone is required, Telefone é obrigatório\nTelephone, Telefone\nFull name is required, Nome completo é obrigatório\nCountry is required, País é obrigatório\nCountry, País\nProvince is required, Estado é obrigatório\nProvince, Estado\nBilling Address, Endereço de Cobrança\nShipping Address, Endereço de Entrega\nCreate A New Account, Criar uma Nova Conta\nContact, Contato\nCustomer information, Informações do cliente"
  },
  {
    "path": "translations/pt/catalog.csv",
    "content": "Featured collection, Coleções em destaque\nSHOP BY, COMPRAR POR\nSort By, Ordenar Por\nThere is no product to display, Nenhum há nenhum produto a ser exibido\n${count} products, ${count} produtos\nSearch results for \"${keyword}\", Resultados da busca por \\\"${keyword}\\\""
  },
  {
    "path": "translations/pt/checkout.csv",
    "content": "Total, Total\nOrder, Pedido\nSku, SKU\nContinue to shipping, Continuar comprando\nContinue shopping, Continue comprando\nContinue Shopping, Continue Comprando\nCONTINUE SHOPPING, CONTINUE COMPRANDO\nShopping cart, Carrinho de compras\nProduct, Produto\nPrice, Preço\nQuantity, Quantidade\nRemove, Remover\nQty, Quant\nSub total, Sub total\nDiscount(${coupon}), Desconto(${coupon})\nOrder summary, Resumo do pedido\nTaxes and shipping calculated at checkout, Impostos e frete calculados no checkout\nCHECKOUT, CHECKOUT\nChange, Alterar\nPayment Method, Metodo de Pagamento\nShipping Address, Endereço de Entrega\nShipping Method, Método de Entrega\nNo payment methods available, Nenhum método de pagamento disponível\nPlace Order, Fazer o pedido\nMy billing address is same as shipping address, Meu endereço de pagamento é igual ao endereço de entrega\n${count} items, ${count} itens\nDiscount, Desconto\nShipping, Entrega\nNo tax, Sem impostos\nTax, Impostos\nSorry, there is no available method for your address, Desculpe, não existe método disponível para seu endereço.\nPlease enter a shipping address in order to see shipping quotes, Por favor insira um endereço de entrega para calcular o frete\nContinue to payment, Continuar para o pagamento\nNo payment method available, Nenhum método de pagamento disponível\nShip to, Enviar para\nShipment, Envio\nThank you ${name}!, Obrigado(a), ${name}!\nContact information, Informações de contato\nOrder #${orderNumber}, Pedido #${orderNumber}\nADD TO CART, ADICIONAR AO CARRINHO\nPlease select variant options, Por favor escolha as opções de variação\nVIEW CART (${count}), VER CARRINHO (${count})\nJUST ADDED TO YOUR CART, ADICIONADO AO CARRINHO\nPromotion code?, Código promocional?\nEnter coupon code, Insira seu coupon\nApply, Aplicar\nYour cart is empty!, Seu carrinho está vazio!\nFull name, Nome completo\nTelephone, Telefone\nAddress, Endereço\nCity, Cidade\nCountry, País\nPostcode, CEP\nInclusive of tax, Impostos inclusos\nSorry, there is no available method for your address,Desculpe, não existem opções para seu endereço.\nFull name is required, Nome completo é obrigatório\nTelephone is required, Telefone é obrigatório\nAddress is required, Endereço é obrigatório\nCity is required, Cidade é obrigatória\nCountry is required, País é obrigatório\nPostcode is required, CEP é obrigatório\nContact, Contato\n"
  },
  {
    "path": "translations/pt/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, Desconto de ${discount} para todos os pedidos acima de ${price}\nPlease select, Por favor selecione...\nThe page you requested does not exist., A página que você está tentando acessar não existe.\nContinue shopping, Continue comprando\n404 Page Not Found, 404 Página Não Encontrada\nHome, Home\nNot found, Não encontrado"
  },
  {
    "path": "translations/pt/paypal.csv",
    "content": "You will be redirected to PayPal, Você será redirecionado para o PayPal.\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., Sentimos muito. Ocorreu um erro ao processar seu pagamento. A cobrança em seu cartão não foi efetuada. Por favor tente novamente."
  },
  {
    "path": "translations/rs/account.csv",
    "content": "Login, Login\nLogout, Logout\nEmail, E-mail\nPassword, Password\nForgot your password?, Zaboravili ste lozinku?\nCreate an account, Napravite nalog\nRegister, Registrujte se\nFull Name, Ime i prezime\nFull name, Ime i prezime\nSIGN IN, Prijavite se\nOrder History, Istorija porudžbina\nAccount Details, Detalji naloga\nYou have not placed any orders yet, Još niste obavili nijednu porudžbinu\nMy Account, Moj nalog\nAlready have an account?, Već imate nalog?\nContact information, Kontakt informacije\nCity, Grad\nAccount Information, Podaci o nalogu\nAddress, Adresa\nPostcode, Poštanski broj\nPostcode is required, Poštanski broj je obavezan\nTelephone is required, Telefon je obavezan\nTelephone, Telefon\nFull name is required, Ime i prezime su obavezni\nCountry is required, Država je obavezna\nCountry, Država\nProvince is required, Pokrajina je obavezna\nProvince, Pokrajina\nBilling Address, Adresa za naplatu\nShipping Address, Adresa za isporuku\nCreate A New Account, Napravite novi nalog\nContact, Kontakt\nCustomer information, Podaci o kupcu\n"
  },
  {
    "path": "translations/rs/catalog.csv",
    "content": "SHOP BY, FILTRIRAJ\nSort By, Sortiraj po\nThere is no product to display, Nema proizvoda za prikaz\n${count} products, ${count} proizvoda\nSearch results for \"${keyword}\", Rezultati pretrage za „${keyword}”"
  },
  {
    "path": "translations/rs/checkout.csv",
    "content": "Total, Ukupno\nOrder, Porudžbina\nSku, SKU\nContinue to shipping, Nastavi na isporuku\nContinue shopping, Nastavi kupovinu\nContinue Shopping, Nastavi kupovinu\nCONTINUE SHOPPING, NASTAVI KUPOVINU\nShopping cart, Korpa\nProduct, Proizvod\nPrice, Cena\nQuantity, Količina\nRemove, Ukloni\nQty, Kom\nSub total, Subtotal\nDiscount(${coupon}), Popust (${coupon})\nOrder summary, Pregled porudžbine\nTaxes and shipping calculated at checkout, Porezi i isporuka se računaju prilikom plaćanja\nCHECKOUT, PLAĆANJE\nChange, Promeni\nPayment Method, Način plaćanja\nShipping Method, Način isporuke\nNo payment methods available, Nema dostupnih metoda plaćanja\nPlace Order, Završi porudžbinu\nMy billing address is the same as the shipping address, Moja adresa za naplatu je ista kao adresa za isporuku\n${count} items, ${count} artikala\nDiscount, Popust\nShipping, Isporuka\nNo tax, Bez poreza\nTax, Porez\nSorry, Izvinjavamo se\nPlease enter a shipping address in order to see shipping quotes, Unesite adresu za isporuku da biste videli troškove isporuke\nContinue to payment, Nastavi na plaćanje\nNo payment method available, Nema dostupnih metoda plaćanja\nShip to, Isporuka na\nShipment, Isporuka\nThank you ${name}!, Hvala, ${name}!\nContact information, Kontakt informacije\nOrder #${orderNumber}, Porudžbina br. ${orderNumber}\nADD TO CART, DODAJ U KORPU\nPlease select variant options, Izaberite varijantu proizvoda\nVIEW CART (${count}), POGLEDAJ KORPU (${count})\nJUST ADDED TO YOUR CART, DODATO U KORPU\nPromotion code?, Imate promo kod?\nEnter coupon code, Unesite kod kupona\nApply, Primeni\nYour cart is empty!, Vaša korpa je prazna!\nInclusive of tax ${taxAmount}, Uključujući ${taxAmount} poreza.\n"
  },
  {
    "path": "translations/rs/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, Popust od ${discount} za sve porudžbine iznad ${price}\nPlease select, Izaberite opciju\nThe page you requested does not exist., Čini se da tražena stranica ne postoji.\nContinue shopping, Nastavi kupovinu\n404 Page Not Found, 404 Stranica nije pronađena\nHome, Početna\nNot found, Nije pronađeno\n"
  },
  {
    "path": "translations/rs/paypal.csv",
    "content": "You will be redirected to PayPal, Bićete preusmereni na PayPal.\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., Žao nam je. Došlo je do greške prilikom obrade plaćanja. Vaša kartica nije naplaćena. Molimo pokušajte ponovo.\n"
  },
  {
    "path": "translations/ru/account.csv",
    "content": "Login, Войти\nLogout, Выйти\nEmail, Email\nPassword, Пароль\nForgot your password?, Забыли пароль?\nCreate an account, Создать аккаунт\nRegister, Зарегистрироваться\nFull Name, Фамилия Имя\nFull name, Фамилия Имя\nSIGN IN, ВОЙТИ\nSIGN UP, ЗАРЕГИСТРИРОВАТЬСЯ\nOrder History, История заказов\nAccount Details, Параметры Аккаунта\nAccount details, Параметры аккаунта\nYou have not placed any orders yet, Вы еще ничего не заказали\nMy Account, Мой аккаунт\nAlready have an account?, У вас уже есть аккаунт?\nContact information, Контактные данные\nCity, Город\nAccount Information, Информация об аккаунте\nAddress, Адрес\nPostcode, Почтовый индекс\nPostcode is required, Почтовый индекс обязателен для заполнения\nTelephone is required, Телефон обязателен для заполнения\nTelephone, Телефон\nFull name is required, Фамилию и Имя обязательны для заполнения\nCountry is required, Страна обязательна для заполнения\nCountry, Страна\nProvince is required, Область обязательна для заполнения\nProvince, Область\nBilling Address, Платежный адрес\nShipping Address, Адрес доставки\nCreate A New Account, Создать новый аккаунт\nContact, Контакт\nCustomer information, Информация о заказчике"
  },
  {
    "path": "translations/ru/catalog.csv",
    "content": "Featured collection, Избранные товары\nSHOP BY, ФИЛЬТРЫ\nSort By, Сортировать по\nThere is no product to display, Нет товаров с такими параметрами\n${count} products, Число товаров: ${count}\nSearch results for \"${keyword}\", Результаты поиска для \\\"${keyword}\\\"\nPrice, Цена\nName, Имя\nDefault, По умолчанию\nCategory, Категория\nSearch, Поиск"
  },
  {
    "path": "translations/ru/checkout.csv",
    "content": "Total, Всего\nOrder, Заказ\nSku, SKU\nContinue to shipping, Продолжить покупки\nContinue shopping, Продолжить покупки\nContinue Shopping, Продолжить Покупки\nCONTINUE SHOPPING, ПРОДОЛЖИТЬ ПОКУПКИ\nShopping cart, Корзина\nProduct, Товар\nPrice, Цена\nQuantity, Количество\nRemove, Удалить\nQty, Кол-во\nSub total, Промежуточные итоги\nDiscount(${coupon}), Скидка(${coupon})\nOrder summary, Описание заказа\nTaxes and shipping calculated at checkout, Стоимость налогов и доставки рассчитываются при оформлении заказа\nCHECKOUT, ОФОРМИТЬ ЗАКАЗ\nChange, Изменить\nPayment Method, Метод оплаты\nShipping Address, Адрез доставки\nShipping Method, Способ доставки\nNo payment methods available, Нет доступных способов оплаты\nPlace Order, Разместить заказ\nMy billing address is same as shipping address, Мой платежный адрес совпадает с адресом доставки\n${count} items, Позиций: ${count}\nDiscount, Скидка\nShipping, Доставка\nNo tax, Без налога\nTax, Налог\nSorry, there is no available method for your address, К сожалению, для вашего адреса нет доступного способа доставки\nPlease enter a shipping address in order to see shipping quotes, Пожалуйста, введите адрес, чтобы рассчитать стоимость доставки\nContinue to payment, Перейти к оплате\nNo payment method available, Нет доступных способов оплаты\nShip to, Адрес получателя\nShipment, Доставка\nThank you ${name}!, ${name}, спасибо за заказ!\nContact information, Контактные данные\nOrder #${orderNumber}, Заказ #${orderNumber}\nADD TO CART, ДОБАВИТЬ В КОЗИНУ\nPlease select variant options, Пожалуйста, выберите другой вариант\nVIEW CART (${count}), ПОСМОТРЕТЬ КОРЗИНУ (${count})\nJUST ADDED TO YOUR CART, ДОБАВЛЕНО В КОРЗИНУ\nPromotion code?, Есть промокод?\nEnter coupon code, Введите промокод\nApply, Принять\nYour cart is empty!, Ваша корзина пуста!\nFull name, Фамилия Имя\nTelephone, Телефон\nAddress, Адрес\nCity, Город\nCountry, Страна\nPostcode, Почтовый индекс\nInclusive of tax ${taxAmount}, Включая налог ${taxAmount}\nFull name is required, Фамилия и Имя обязательны для заполнения\nTelephone is required, Телефон обязателен для заполнения\nAddress is required, Адрес обязателен для заполнения\nCity is required, Город обязателен для заполнения\nCountry is required, Страна обязательна для заполнения\nPostcode is required, Почтовый обязателен для заполнения\nContact, Контакт\nPayment, Оплата\n"
  },
  {
    "path": "translations/ru/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, Скидка на все заказы ${discount} на сумму свыше ${price}\nPlease select, Пожалуйста, выберите...\nThe page you requested does not exist., Запрошенная вами страница не существует.\nContinue shopping, Продолжить покупки\n404 Page Not Found, 404 Страница не найдена\nHome, Главная\nNot found, Не найдено"
  },
  {
    "path": "translations/ru/paypal.csv",
    "content": "You will be redirected to PayPal, Вы будете перенаправлены на PayPal.\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., Мы сожалеем. При обработке вашего платежа произошла ошибка. Средства с вашей карты не были списаны. Пожалуйста, попробуйте еще раз."
  },
  {
    "path": "translations/ta/account.csv",
    "content": "Login,உள்நுழைவு\r\nLogout,வெளியேறு\r\nEmail,மின்னஞ்சல்\r\nPassword,கடவுச்சொல்\r\nForgot your password?,உங்கள் கடவுச்சொல்லை மறந்துவிட்டீர்களா?\r\nCreate an account,கணக்கை உருவாக்கு\r\nRegister,பதிவு செய்யவும்\r\nFull Name,முழுப் பெயர்\r\nFull name,முழுப் பெயர்\r\nSIGN IN,உள்நுழைக\r\nOrder History,ஆர்டர் வரலாறு\r\nAccount Details,கணக்கு விவரங்கள்\r\nYou have not placed any orders yet,நீங்கள் இதுவரை எந்த ஆர்டரும் வைக்கவில்லை\r\nMy Account,என் கணக்கு\r\nAlready have an account?,ஏற்கனவே கணக்கு உள்ளதா?\r\nContact information,தொடர்பு தகவல்\r\nCity,நகரம்\r\nAccount Information,கணக்கு தகவல்\r\nAddress,முகவரி\r\nPostcode,அஞ்சல் குறியீடு\r\nPostcode is required,அஞ்சல் குறியீடு தேவையானது\r\nTelephone is required,தொலைபேசி எண் தேவையானது\r\nTelephone,தொலைபேசி\r\nFull name is required,முழுப் பெயர் தேவையானது\r\nCountry is required,நாடு தேவையானது\r\nCountry,நாடு\r\nProvince is required,மாநிலம் தேவையானது\r\nProvince,மாநிலம்\r\nBilling Address,பில்லிங் முகவரி\r\nShipping Address,அனுப்பும் முகவரி\r\nCreate A New Account,புதிய கணக்கை உருவாக்கவும்\r\nContact,தொடர்பு\r\nCustomer information,வாடிக்கையாளர் தகவல்"
  },
  {
    "path": "translations/ta/catalog.csv",
    "content": "Featured collection,சிறப்புப் தொகுப்பு\r\nSHOP BY,வாங்கும் வகை\r\nSort By,வரிசைப்படுத்து\r\nThere is no product to display,காட்ட எந்தப் பொருளும் இல்லை\r\n${count} products,${count} பொருட்கள்\r\n\"Search results for \"\"${keyword}\"\"\",\"\"\"${keyword}\"\" எனும் தேடல் முடிவுகள்\""
  },
  {
    "path": "translations/ta/checkout.csv",
    "content": "Total,மொத்தம்\r\nOrder,ஆர்டர்\r\nSku,பொருள் குறியீடு\r\nContinue to shipping,அனுப்புதலுக்கு தொடரவும்\r\nContinue shopping,வாங்குவதைக் தொடரவும்\r\nContinue Shopping,வாங்குவதைக் தொடரவும்\r\nCONTINUE SHOPPING,வாங்குவதைக் தொடரவும்\r\nShopping cart,வணிக வண்டி\r\nProduct,பொருள்\r\nPrice,விலை\r\nQuantity,அளவு\r\nRemove,அகற்று\r\nQty,அளவு\r\nSub total,துணை மொத்தம்\r\nDiscount($coupon),தள்ளுபடி ($coupon)\r\nOrder summary,ஆர்டர் சுருக்கம்\r\nTaxes and shipping calculated at checkout,வரி மற்றும் அனுப்புதல் கட்டணம் செக்அவுட் போது கணக்கிடப்படும்\r\nCHECKOUT,செக்அவுட் செய்\r\nChange,மாற்று\r\nPayment Method,கட்டண முறைகள்\r\nShipping Address,அனுப்பும் முகவரி\r\nShipping Method,அனுப்பும் முறை\r\nNo payment methods available,கட்டண முறைகள் இல்லை\r\nPlace Order,ஆர்டரை வைக்கவும்\r\nMy billing address is same as shipping address,என் பில்லிங் முகவரி அனுப்பும் முகவரியுடன் ஒரே மாதிரியாகும்\r\n$count items,$count பொருட்கள்\r\nDiscount,தள்ளுபடி\r\nShipping,அனுப்புதல்\r\nNo tax,வரி இல்லை\r\nTax,வரி\r\nSorry,மன்னிக்கவும்\r\nPlease enter a shipping address in order to see shipping quotes,அனுப்புதல் கட்டணங்களைப் பார்க்க அனுப்பும் முகவரியை உள்ளிடவும்\r\nContinue to payment,கட்டணத்துக்கு தொடரவும்\r\nNo payment method available,கட்டண முறை இல்லை\r\nShip to,அனுப்பும் இடம்\r\nShipment,அனுப்புதல்\r\nThank you $name!,நன்றி $name!\r\nContact information,தொடர்பு தகவல்\r\nOrder #$orderNumber,ஆர்டர் எண் #$orderNumber\r\nADD TO CART,வண்டியில் சேர்\r\nPlease select variant options,மாறுபாடு விருப்பங்களைத் தேர்ந்தெடுக்கவும்\r\nVIEW CART ($count),வண்டியைப் பார்க்கவும் ($count)\r\nJUST ADDED TO YOUR CART,உங்கள் வண்டியில் சேர்க்கப்பட்டது\r\nPromotion code?,பிரச்சார குறியீடு?\r\nEnter coupon code,கூப்பன் குறியீட்டை உள்ளிடவும்\r\nApply,பயன்படுத்தவும்\r\nYour cart is empty!,உங்கள் வண்டி காலியாக உள்ளது!\r\nFull name,முழுப் பெயர்\r\nTelephone,தொலைபேசி\r\nAddress,முகவரி\r\nCity,நகரம்\r\nCountry,நாடு\r\nPostcode,அஞ்சல் குறியீடு\r\nInclusive of tax,வரியுடன் சேர்த்து\r\nSorry,மன்னிக்கவும்\r\nFull name is required,முழுப் பெயர் தேவையானது\r\nTelephone is required,தொலைபேசி எண் தேவையானது\r\nAddress is required,முகவரி தேவையானது\r\nCity is required,நகரம் தேவையானது\r\nCountry is required,நாடு தேவையானது\r\nPostcode is required,அஞ்சல் குறியீடு தேவையானது\r\nContact,தொடர்பு"
  },
  {
    "path": "translations/ta/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price},${price} க்கும் மேற்பட்ட அனைத்து ஆர்டர்களுக்கும் ${discount} தள்ளுபடி\r\nPlease select,தயவுசெய்து தேர்வு செய்யவும்\r\nThe page you requested does not exist.,நீங்கள் கேட்ட பக்கம் இல்லை.\r\nContinue shopping,வாங்குவதைக் தொடரவும்\r\n404 Page Not Found,404 பக்கம் கிடைக்கவில்லை\r\nHome,முகப்பு\r\nNot found,கிடைக்கவில்லை"
  },
  {
    "path": "translations/ta/paypal.csv",
    "content": "You will be redirected to PayPal,நீங்கள் PayPal-க்கு மாற்றப்படுவீர்கள்\r\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again.,மன்னிக்கவும். உங்கள் கட்டணத்தை செயலாக்கும் போது பிழை ஏற்பட்டது. உங்கள் கார்டில் தொகை வசூலிக்கப்படவில்லை. தயவுசெய்து மீண்டும் முயற்சிக்கவும்."
  },
  {
    "path": "translations/vn/account.csv",
    "content": "Login, Đăng Nhập\nLogout, Đăng Xuất\nWelcome Back!, Chào mừng trở lại!\nPlease sign in to your account , Vui lòng đăng nhập\nJoin us for exclusive offers and order tracking, Tham gia cùng chúng tôi để nhận ưu đãi và theo dõi đơn hàng\nSign In, Đăng Nhập\nSign Up, Đăng Ký\nSigning Up..., Đang đăng ký...\nSigning In..., Đang đăng nhập...\nRecent Orders, Đơn hàng gần đây\nAddress Book, Sổ địa chỉ\nYou have no addresses saved, Bạn chưa lưu địa chỉ nào\nAdd new address, Thêm địa chỉ mới\nAddress 2, Địa chỉ 2\nSet as default, Đặt làm mặc định\nSave, Lưu\nEdit: Chỉnh sửa\nDelete, Xóa\nEdit Address, Chỉnh sửa địa chỉ\nEmail, Email\nPassword, Mật khẩu\nForgot your password?, Quên mật khẩu?\nCreate an account, Tạo tài khoản\nRegister, Đăng ký\nFull Name, Họ và tên\nFull name, Họ và tên\nSIGN IN, ĐĂNG NHẬP\nOrder History, Lịch sử đơn hàng\nAccount Details, Chi tiết tài khoản\nYou have not placed any orders yet, Bạn chưa đặt đơn hàng nào\nMy Account, Tài khoản của tôi\nAlready have an account?, Bạn đã có tài khoản?\nContact information, Thông tin liên hệ\nCity, Thành phố\nAccount Information, Thông tin tài khoản\nAddress, Địa chỉ\nPostcode, Mã bưu điện\nPostcode is required, Mã bưu điện là bắt buộc\nTelephone is required, Số điện thoại là bắt buộc\nTelephone, Số điện thoại\nFull name is required, Họ và tên là bắt buộc\nCountry is required, Quốc gia là bắt buộc\nCountry, Quốc gia\nProvince is required, Tỉnh/Thành phố là bắt buộc\nProvince, Tỉnh/Thành phố\nBilling Address, Địa chỉ thanh toán\nShipping Address, Địa chỉ giao hàng\nCreate A New Account, Tạo tài khoản mới\nContact, Liên hệ\nCustomer information, Thông tin khách hàng"
  },
  {
    "path": "translations/vn/catalog.csv",
    "content": "Featured collection, Sản phẩm nổi bật\nSort By, Sắp xếp\nThere is no product to display, Không có sản phẩm để hiển thị\n${count} products, ${count} sản phẩm\nSearch results for \"${keyword}\", Kết quả tìm kiếm của từ khóa “${keyword}”\nProduct Filters, Lọc sản phẩm\nNo products found, Không tìm thấy sản phẩm nào\n${count} Products, ${count} sản phẩm\nDefault, Mặc định\nName, Tên"
  },
  {
    "path": "translations/vn/checkout.csv",
    "content": "Total, Tổng cộng\nOrder, Đơn hàng\nSku, SKU\nContinue to shipping, Đến phần vận chuyển\nContinue shopping, Tiếp tục mua sắm\nContinue Shopping, Tiếp tục mua sắm\nCONTINUE SHOPPING, TIẾP TỤC MUA SẮM\nShopping cart, Giỏ hàng\nProduct, Sản phẩm\nPrice, Giá\nQuantity, Số lượng\nRemove, Xóa\nQty, Số lượng\nSub total, Tổng phụ\nDiscount(${coupon}), Giảm giá(${coupon})\nOrder summary, Tóm tắt đơn hàng\nTaxes and shipping calculated at checkout, Thuế và phí vận chuyển được tính khi thanh toán\nCHECKOUT, THANH TOÁN\nChange, Thay đổi\nPayment Method, Phương thức thanh toán\nShipping Method, Phương thức vận chuyển\nNo payment methods available, Không có phương thức thanh toán nào khả dụng\nPlace Order, Mua hàng\nMy billing address is same as shipping address, Địa chỉ thanh toán của tôi giống với địa chỉ giao hàng\n${count} items, ${count} sản phẩm\nDiscount, Giảm giá\nShipping, Phí vận chuyển\nNo tax, Không có thuế\nTax, Thuế\nSorry, there is no available method for your address, Xin lỗi, không có phương thức nào khả dụng cho địa chỉ của bạn\nPlease enter a shipping address in order to see shipping quotes, Vui lòng nhập địa chỉ giao hàng để xem báo giá vận chuyển\nContinue to payment, Đến phần thanh toán\nNo payment method available, Không có phương thức thanh toán nào khả dụng\nShip to, Giao đến\nShipment, Vận chuyển\nThank you ${name}!, Cảm ơn ${name}!\nContact Information, Thông tin liên hệ\nOrder #${orderNumber}, Đơn hàng #${orderNumber}\nADD TO CART, Thêm vào giỏ hàng\nPlease select variant options, Vui lòng chọn thuộc tính sản phẩm\nVIEW CART (${count}), Xem giỏ hàng (${count})\nJUST ADDED TO YOUR CART, Vừa thêm vào giỏ hàng của bạn\nPromotion code?, Mã khuyến mãi?\nEnter coupon code, Nhập mã khuyến mãi\nApply, Áp dụng\nYour cart is empty!, Giỏ hàng của bạn trống!\nYour Cart, Giỏ hàng của bạn\nYour cart is empty, Giỏ hàng của bạn trống\nSubtotal, Tổng phụ\nView Cart (${totalQty}), Xem giỏ hàng (${totalQty})\nCheckout, Thanh toán\nShopping Cart, Giỏ hàng\nLogged in as, Đã đăng nhập với tài khoản\nPayment Information, Thông tin thanh toán\nSame as shipping address, Giống với địa chỉ giao hàng\nUse a different billing address, Sử dụng địa chỉ thanh toán khác\nPick a payment method, Chọn phương thức thanh toán\nSelect a payment method, Vui lòng chọn phương thức thanh toán\nComplete Order, Hoàn tất đơn hàng\nAdd to Cart, Thêm vào giỏ hàng"
  },
  {
    "path": "translations/vn/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, Tất cả các đơn hàng trên ${price} sẽ được giảm ${discount}\nPlease select, Vui lòng chọn\nThe page you requested does not exist., Trang bạn yêu cầu không tồn tại.\nContinue shopping, Tiếp tục mua sắm\n404 Page Not Found, 404 Trang không tìm thấy\nHome, Trang chủ\nNot found, Không tìm thấy\nSearch, Tìm kiếm"
  },
  {
    "path": "translations/vn/paypal.csv",
    "content": "You will be redirected to PayPal, Bạn sẽ được chuyển hướng đến PayPal\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again., Chúng tôi rất tiếc. Đã xảy ra lỗi khi xử lý thanh toán. Thẻ của bạn chưa bị tính phí. Vui lòng thử lại."
  },
  {
    "path": "translations/zh/account.csv",
    "content": "Login, 登录\nLogout, 登出\nEmail, 邮箱\nPassword, 密码\nForgot your password?, 忘记密码？\nCreate an account, 创建账户\nRegister, 注册\nFull Name, 全名\nFull name, 全名\nSIGN IN, 登录\nOrder History, 订单历史\nAccount Details, 账户详情\nYou have not placed any orders yet, 您还没有下过订单\nMy Account, 我的账户\nAlready have an account?, 已经有账户了？\nContact information, 联系信息\nCity, 城市\nAccount Information, 账户信息\nAddress, 地址\nPostcode, 邮编\nPostcode is required, 邮编是必填项\nTelephone is required, 电话是必填项\nTelephone, 电话\nFull name is required, 全名是必填项\nCountry is required, 国家是必填项\nCountry, 国家\nProvince is required, 省份是必填项\nProvince, 省份\nBilling Address, 账单地址\nShipping Address, 配送地址\nCreate A New Account, 创建新账户\nContact, 联系\nCustomer information, 客户信息"
  },
  {
    "path": "translations/zh/catalog.csv",
    "content": "Featured collection, 精选合集\nSHOP BY, 按类别购买\nSort By, 按类别排序\nThere is no product to display, 没有产品可显示\n${count} products, ${count} 个产品\nSearch results for \"${keyword}\", 搜索结果为 “${keyword}”"
  },
  {
    "path": "translations/zh/checkout.csv",
    "content": "Total, 全部的\nOrder, 订单\nSku, SKU\nContinue to shipping, 继续运输\nContinue shopping, 继续购物\nContinue Shopping, 继续购物\nCONTINUE SHOPPING, 继续购物\nShopping cart, 购物车\nProduct, 产品\nPrice, 价格\nQuantity, 数量\nRemove, 删除\nQty, 数量\nSub total, 小计\nDiscount(${coupon}), 折扣(${coupon})\nOrder summary, 订单摘要\nTaxes and shipping calculated at checkout, 税费和运费在结账时计算\nCHECKOUT, 结账\nChange, 更改\nPayment Method, 付款方式\nShipping Method, 运输方式\nNo payment methods available, 没有可用的付款方式\nPlace Order, 下订单\nMy billing address is same as shipping address, 我的账单地址与送货地址相同\n${count} items, ${count} 件商品\nDiscount, 折扣\nShipping, 运费\nNo tax, 没有税\nTax, 税\nSorry, there is no available method for your address, 抱歉，您的地址没有可用的方法\nPlease enter a shipping address in order to see shipping quotes, 请输入送货地址以查看送货报价\nContinue to payment, 继续付款\nNo payment method available, 没有可用的付款方式\nShip to, 发货至\nShipment, 装运\nThank you ${name}!, 谢谢 ${name}!\nContact information, 联系信息\nOrder #${orderNumber}, 订单 #${orderNumber}\nADD TO CART, 添加到购物车\nPlease select variant options, 请选择变体选项\nVIEW CART (${count}), 查看购物车 (${count})\nJUST ADDED TO YOUR CART, 刚刚添加到您的购物车\nPromotion code?, 促销代码？\nEnter coupon code, 输入优惠券代码\nApply, 应用\nYour cart is empty!, 您的购物车是空的！"
  },
  {
    "path": "translations/zh/general.csv",
    "content": "Discount ${discount} For All Orders Over ${price}, 所有超过 ${price} 的订单可享受 ${discount} 折扣\nPlease select, 请选择\nThe page you requested does not exist., 您请求的页面不存在。\nContinue shopping, 继续购物\n404 Page Not Found, 404 页面未找到\nHome, 首页\nNot found, 未找到"
  },
  {
    "path": "translations/zh/paypal.csv",
    "content": "You will be redirected to PayPal, 您将被重定向至 PayPal\nWe are sorry. There was an error processing your payment. Your card was not charged. Please try again.,我们很抱歉。在处理您的付款时发生了错误。您的银行卡尚未扣款。请重试。"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"NodeNext\",\n    \"target\": \"ES2018\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"declarationDir\": \"./dist/types\",\n    \"sourceMap\": true,\n    \"allowJs\": true,\n    \"checkJs\": false,\n    \"jsx\": \"preserve\",\n    \"outDir\": \"./dist\",\n    \"resolveJsonModule\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"allowArbitraryExtensions\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@components/*\": [\"packages/evershop/src/components/*\"],\n      \"@evershop/postgres-query-builder\": [\"packages/postgres-query-builder/src/index.ts\"],\n      \"@evershop/postgres-query-builder/*\": [\"packages/postgres-query-builder/src/*\"]\n    }\n  },\n}\n"
  }
]