├── .distignore ├── .editorconfig ├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── 1-Bug_report.md │ ├── 2-Question.md │ └── 3-Feature_request.md ├── SECURITY.md ├── dependabot.yml ├── hookdoc-tmpl │ ├── README.md │ ├── layout.tmpl │ └── static │ │ └── README.md └── workflows │ ├── build-docs.yml │ ├── manual-release.yml │ ├── release.yml │ ├── sync-wporg-assets.yml │ ├── tests.yml │ ├── update-pot.yml │ ├── wp-engine.yml │ └── wporg-deploy.yml ├── .gitignore ├── .php-cs-fixer.php ├── .phpcs.xml.dist ├── .phpunit.xml.dist ├── .vscode ├── launch.json └── settings.json ├── .wordpress-org ├── banner-772x250.jpg ├── blueprint.json ├── icon-128x128-old.png ├── icon-128x128.png ├── icon-256x256.png └── screenshot-1.png ├── .wp-env.json ├── LICENSE ├── README.md ├── assets ├── css │ └── README.md ├── img │ ├── README.md │ ├── animated │ │ ├── drawn-letters.svg │ │ ├── pulsing-letters.svg │ │ └── slide-down.svg │ ├── icon-128x128.png │ ├── icon-256x256.png │ ├── icon-round.svg │ ├── icon-square.png │ ├── icon-square.svg │ ├── wcpos-icon-animated.svg │ ├── wcpos-icon-b&w.svg │ ├── wcpos-icon-transparent.svg │ ├── wcpos-icon.svg │ └── wp-menu-icon.svg └── js │ ├── README.md │ └── indexeddb.worker.js ├── composer.json ├── generate_autoload.php ├── hookdoc-conf.json ├── includes ├── AJAX.php ├── API.php ├── API │ ├── Auth.php │ ├── Customers_Controller.php │ ├── Data_Order_Statuses_Controller.php │ ├── Orders_Controller.php │ ├── Payment_Gateways.php │ ├── Product_Categories_Controller.php │ ├── Product_Tags_Controller.php │ ├── Product_Variations_Controller.php │ ├── Products_Controller.php │ ├── Settings.php │ ├── Shipping_Methods_Controller.php │ ├── Stores.php │ ├── Tax_Classes_Controller.php │ ├── Taxes_Controller.php │ └── Traits │ │ ├── Product_Helpers.php │ │ ├── Query_Helpers.php │ │ ├── Uuid_Handler.php │ │ └── WCPOS_REST_API.php ├── Abstracts │ └── Store.php ├── Activator.php ├── Admin.php ├── Admin │ ├── Analytics.php │ ├── Menu.php │ ├── Notices.php │ ├── Orders │ │ ├── HPOS_List_Orders.php │ │ ├── HPOS_Single_Order.php │ │ ├── List_Orders.php │ │ └── Single_Order.php │ ├── Permalink.php │ ├── Plugins.php │ ├── Products │ │ ├── List_Products.php │ │ ├── Single_Product.php │ │ └── templates │ │ │ ├── post-metabox-visibility-select.php │ │ │ ├── quick-edit-visibility-select.php │ │ │ ├── variation-metabox-pos-barcode.php │ │ │ └── variation-metabox-visibility-select.php │ ├── Settings.php │ ├── Updaters │ │ └── Pro_Plugin_Updater.php │ └── templates │ │ └── upgrade.php ├── Deactivator.php ├── Form_Handler.php ├── Gateways.php ├── Gateways │ ├── Card.php │ └── Cash.php ├── Init.php ├── Integrations │ ├── WPSEO.php │ └── WePOS.php ├── Logger.php ├── Orders.php ├── Pro.php ├── Products.php ├── Registry.php ├── Server.php ├── Services │ ├── Auth.php │ ├── Cache.php │ ├── README.md │ └── Settings.php ├── Templates.php ├── Templates │ ├── Frontend.php │ ├── Login.php │ ├── Payment.php │ ├── Receipt.php │ └── Received.php ├── WC_API.php ├── i18n.php ├── updates │ ├── update-0.4.6.php │ ├── update-0.4.php │ ├── update-1.0.0-beta.1.php │ └── update-1.6.1.php ├── wcpos-functions.php └── wcpos-store-functions.php ├── languages └── woocommerce-pos.pot ├── package.json ├── packages ├── analytics │ ├── babel.config.js │ ├── package.json │ ├── src │ │ ├── index.tsx │ │ └── translations │ │ │ ├── index.ts │ │ │ └── locales.json │ ├── tsconfig.json │ └── webpack.config.js ├── eslint │ ├── .github │ │ ├── SECURITY.md │ │ ├── dependabot.yml │ │ └── workflows │ │ │ └── publish-npm.yml │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── index.js.old │ ├── package.json │ └── tsconfig.json └── settings │ ├── .gitignore │ ├── README.md │ ├── assets │ ├── book.svg │ ├── check.svg │ ├── chevron-down.svg │ ├── close-icon.svg │ ├── comment-question.svg │ ├── discord.svg │ ├── drag-icon.svg │ ├── email.svg │ └── wcpos-icon.svg │ ├── babel.config.js │ ├── package.json │ ├── postcss.config.js │ ├── src │ ├── components │ │ ├── combobox.tsx │ │ ├── error.tsx │ │ ├── label.tsx │ │ ├── notice.tsx │ │ ├── select.tsx │ │ ├── snackbar │ │ │ ├── index.tsx │ │ │ ├── provider.tsx │ │ │ ├── snackbar-list.tsx │ │ │ ├── snackbar.tsx │ │ │ └── use-snackbar.tsx │ │ └── tabs │ │ │ ├── index.tsx │ │ │ ├── tab-bar.tsx │ │ │ └── tab-item.tsx │ ├── custom.d.ts │ ├── hooks │ │ ├── use-notices.tsx │ │ ├── use-ready-state.ts │ │ └── use-settings-api.tsx │ ├── index.css │ ├── index.tsx │ ├── screens │ │ ├── access │ │ │ └── index.tsx │ │ ├── checkout │ │ │ ├── gateway-modal.tsx │ │ │ ├── gateways.tsx │ │ │ ├── index.tsx │ │ │ └── order-status-select.tsx │ │ ├── footer.tsx │ │ ├── general │ │ │ ├── barcode-select.tsx │ │ │ ├── index.tsx │ │ │ └── user-select.tsx │ │ ├── header.tsx │ │ ├── index.tsx │ │ ├── license │ │ │ └── index.tsx │ │ └── tools │ │ │ └── index.tsx │ └── translations │ │ ├── index.ts │ │ └── locales.json │ ├── tailwind.config.js │ ├── tsconfig.json │ └── webpack.config.js ├── php-scoper ├── composer.json └── scoper.inc.php ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── readme.txt ├── scripts └── clean-node-modules.js ├── templates ├── login.php ├── payment.php ├── pos.php ├── receipt.php └── received.php ├── tests ├── Helpers │ ├── CouponHelper.php │ ├── CustomerHelper.php │ ├── HPOSToggleTrait.php │ ├── OrderHelper.php │ ├── ProductHelper.php │ └── ShippingHelper.php ├── Tools │ ├── CodeHacking │ │ ├── CodeHacker.php │ │ ├── Hacks │ │ │ ├── BypassFinalsHack.php │ │ │ ├── CodeHack.php │ │ │ ├── FunctionsMockerHack.php │ │ │ └── StaticMockerHack.php │ │ └── README.md │ ├── DependencyManagement │ │ └── MockableLegacyProxy.php │ ├── DynamicDecorator.php │ └── FakeQueue.php ├── bootstrap.php ├── classes-with-mockable-static-methods.php ├── framework │ ├── class-wc-rest-unit-test-case.php │ ├── class-wc-unit-test-case.php │ ├── class-wc-unit-test-factory.php │ ├── class-wp-test-spy-rest-server.php │ └── wp-http-testcase.php ├── includes │ ├── API │ │ ├── Test_Customers_Controller.php │ │ ├── Test_HPOS_Orders_Controller.php │ │ ├── Test_Order_Taxes.php │ │ ├── Test_Orders_Controller.php │ │ ├── Test_Product_Categories_Controller.php │ │ ├── Test_Product_Tags_Controller.php │ │ ├── Test_Product_Variations_Controller.php │ │ ├── Test_Products_Controller.php │ │ ├── Test_Settings_API.php │ │ ├── Test_Stores_API.php │ │ ├── Test_Taxes_Controller.php │ │ ├── WCPOS_REST_HPOS_Unit_Test_Case.php │ │ └── WCPOS_REST_Unit_Test_Case.php │ ├── Abstracts │ │ └── Test_Store_Abstract.php │ ├── Helpers │ │ ├── TaxHelper.php │ │ └── Utils.php │ ├── Services │ │ └── Test_Settings_Service.php │ ├── Test_Products.php │ ├── Test_Setup.php │ ├── Test_Templates.php │ ├── Test_WC_API.php │ └── Test_Wcpos_Functions.php └── mockable-functions.php ├── turbo.json ├── uninstall.php ├── vendor_prefixed ├── autoload.php ├── firebase │ └── php-jwt │ │ └── src │ │ ├── BeforeValidException.php │ │ ├── CachedKeySet.php │ │ ├── ExpiredException.php │ │ ├── JWK.php │ │ ├── JWT.php │ │ ├── JWTExceptionWithPayloadInterface.php │ │ ├── Key.php │ │ └── SignatureInvalidException.php └── phpfastcache │ └── phpfastcache │ ├── bin │ └── dependencies │ │ └── Psr │ │ ├── Cache │ │ └── src │ │ │ ├── CacheException.php │ │ │ ├── CacheItemInterface.php │ │ │ ├── CacheItemPoolInterface.php │ │ │ └── InvalidArgumentException.php │ │ └── SimpleCache │ │ └── src │ │ ├── CacheException.php │ │ ├── CacheInterface.php │ │ └── InvalidArgumentException.php │ └── lib │ └── Phpfastcache │ ├── Api.php │ ├── Autoload │ └── Autoload.php │ ├── CacheManager.php │ ├── Cluster │ ├── AggregatablePoolInterface.php │ ├── AggregatorInterface.php │ ├── ClusterAggregator.php │ ├── ClusterPoolAbstract.php │ ├── ClusterPoolInterface.php │ ├── ClusterPoolTrait.php │ ├── Drivers │ │ ├── FullReplication │ │ │ ├── FullReplicationCluster.php │ │ │ └── Item.php │ │ ├── MasterSlaveReplication │ │ │ ├── Item.php │ │ │ └── MasterSlaveReplicationCluster.php │ │ ├── RandomReplication │ │ │ ├── Item.php │ │ │ └── RandomReplicationCluster.php │ │ └── SemiReplication │ │ │ ├── Item.php │ │ │ └── SemiReplicationCluster.php │ └── ItemAbstract.php │ ├── Config │ ├── Config.php │ ├── ConfigurationOption.php │ ├── ConfigurationOptionInterface.php │ └── IOConfigurationOptionTrait.php │ ├── Core │ ├── Item │ │ ├── ExtendedCacheItemInterface.php │ │ ├── ItemBaseTrait.php │ │ ├── ItemExtendedTrait.php │ │ ├── TaggableCacheItemInterface.php │ │ └── TaggableCacheItemTrait.php │ └── Pool │ │ ├── AbstractDriverPoolTrait.php │ │ ├── CacheItemPoolTrait.php │ │ ├── DriverBaseTrait.php │ │ ├── ExtendedCacheItemPoolInterface.php │ │ ├── ExtendedCacheItemPoolTrait.php │ │ ├── IO │ │ └── IOHelperTrait.php │ │ ├── TaggableCacheItemPoolInterface.php │ │ └── TaggableCacheItemPoolTrait.php │ ├── Drivers │ ├── Apcu │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Cassandra │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Cookie │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Couchbase │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Couchbasev3 │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Couchdb │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Devfalse │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Devnull │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Devtrue │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Files │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Leveldb │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Memcache │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Memcached │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Memstatic │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Mongodb │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Predis │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Redis │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Sqlite │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Ssdb │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Wincache │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Zenddisk │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ └── Zendshm │ │ ├── Config.php │ │ ├── Driver.php │ │ └── Item.php │ ├── Entities │ ├── DriverIO.php │ ├── DriverStatistic.php │ └── ItemBatch.php │ ├── Event │ ├── EventManagerDispatcherInterface.php │ ├── EventManagerDispatcherTrait.php │ └── EventManagerInterface.php │ ├── EventManager.php │ ├── Exceptions │ ├── PhpfastcacheCoreException.php │ ├── PhpfastcacheDeprecatedException.php │ ├── PhpfastcacheDriverCheckException.php │ ├── PhpfastcacheDriverConnectException.php │ ├── PhpfastcacheDriverException.php │ ├── PhpfastcacheDriverNotFoundException.php │ ├── PhpfastcacheExceptionInterface.php │ ├── PhpfastcacheIOException.php │ ├── PhpfastcacheInstanceNotFoundException.php │ ├── PhpfastcacheInvalidArgumentException.php │ ├── PhpfastcacheInvalidArgumentTypeException.php │ ├── PhpfastcacheInvalidConfigurationException.php │ ├── PhpfastcacheLogicException.php │ ├── PhpfastcacheReplicationException.php │ ├── PhpfastcacheRootException.php │ ├── PhpfastcacheSimpleCacheException.php │ └── PhpfastcacheUnsupportedOperationException.php │ ├── Helper │ ├── CacheConditionalHelper.php │ └── Psr16Adapter.php │ ├── Proxy │ └── PhpfastcacheAbstractProxy.php │ └── Util │ ├── ArrayObject.php │ ├── ClassNamespaceResolverInterface.php │ ├── ClassNamespaceResolverTrait.php │ ├── Directory.php │ └── MemcacheDriverCollisionDetectorTrait.php └── woocommerce-pos.php /.distignore: -------------------------------------------------------------------------------- 1 | .* 2 | node_modules 3 | packages 4 | tests 5 | build 6 | coverage 7 | languages 8 | report.html 9 | README.md 10 | composer.json 11 | composer.lock 12 | hookdoc-conf.json 13 | package.json 14 | yarn.lock 15 | php-scoper 16 | generate_autoload.php 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | end_of_line = lf 11 | insert_final_newline = true 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | 15 | [*.php] 16 | indent_style = tab 17 | indent_size = 4 18 | 19 | [{*.ts, *.tsx, .jshintrc, *.json}] 20 | indent_style = tab 21 | indent_size = 4 22 | 23 | [*.yml] 24 | indent_style = space 25 | indent_size = 4 26 | 27 | [{*.txt, wp-config-sample.php}] 28 | end_of_line = crlf 29 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Use the development flag to use live js 2 | DEVELOPMENT=true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: You're having technical issues. 🐞 4 | labels: "bug" 5 | --- 6 | 7 | 8 | 9 | ## Expected Behavior 10 | 11 | 12 | 13 | ## Current Behavior 14 | 15 | 16 | 17 | ## Steps to Reproduce 18 | 19 | 20 | 21 | 22 | 1. 23 | 24 | 2. 25 | 26 | 3. 27 | 28 | 4. 29 | 30 | ## Your Environment 31 | 32 | 33 | 34 | - Operating System and version : 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question.❓ 4 | labels: "question" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: You want something added to the boilerplate. 🎉 4 | labels: "enhancement" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Security Announcements 4 | 5 | ## Reporting a Vulnerability 6 | 7 | Please report (suspected) security vulnerabilities to security@wcpos.com. You will receive a response from us within 48 8 | hours. If the issue is confirmed, we will release a patch as soon as possible depending on complexity but historically 9 | within a few days. 10 | 11 | ## Supported Versions 12 | 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | 10 | # Maintain dependencies for npm 11 | - package-ecosystem: "npm" 12 | directory: "/" 13 | schedule: 14 | interval: "weekly" 15 | 16 | # Maintain dependencies for Composer 17 | - package-ecosystem: "composer" 18 | directory: "/" 19 | schedule: 20 | interval: "weekly" 21 | -------------------------------------------------------------------------------- /.github/hookdoc-tmpl/README.md: -------------------------------------------------------------------------------- 1 | Work in progress ... 2 | -------------------------------------------------------------------------------- /.github/hookdoc-tmpl/layout.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <?js= title ?> - 10up Distributor Hook Docs 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | class="home"> 20 | 21 |
22 | 23 | 24 |

25 | 26 | 27 | 28 | 29 | 34 | 35 | 36 |
37 | 38 | 41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /.github/hookdoc-tmpl/static/README.md: -------------------------------------------------------------------------------- 1 | Static files for HookDocs - https://wcpos.github.io/woocommerce-pos/ 2 | -------------------------------------------------------------------------------- /.github/workflows/build-docs.yml: -------------------------------------------------------------------------------- 1 | name: Hook Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "**.php" 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v3 17 | 18 | - name: Build 19 | env: 20 | YARN_ENABLE_IMMUTABLE_INSTALLS: false 21 | run: | 22 | yarn install 23 | yarn build:docs 24 | 25 | - name: Deploy to GH Pages 26 | uses: peaceiris/actions-gh-pages@v3 27 | with: 28 | github_token: ${{ secrets.GITHUB_TOKEN }} 29 | publish_dir: ./hookdocs 30 | -------------------------------------------------------------------------------- /.github/workflows/manual-release.yml: -------------------------------------------------------------------------------- 1 | name: Manual Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseVersion: 7 | description: 'Release Version' 8 | required: true 9 | default: '0.0.0' 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: 🏗 Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: 🔖 Setup Release Version 23 | run: echo "VERSION=${{ github.event.inputs.releaseVersion }}" >> $GITHUB_ENV 24 | 25 | - name: 📦 Create Release 26 | id: create_release 27 | uses: actions/create-release@v1 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | with: 31 | tag_name: v${{ env.VERSION }} 32 | release_name: Release v${{ env.VERSION }} 33 | draft: true 34 | prerelease: ${{ contains(env.VERSION, '-beta') }} 35 | 36 | - name: 🏗 Setup pnpm 37 | uses: pnpm/action-setup@v4 38 | with: 39 | version: '10.8.0' 40 | 41 | - name: 👷 Build 42 | run: | 43 | pnpm install 44 | composer prefix-dependencies 45 | composer install --no-dev 46 | pnpm settings build 47 | pnpm analytics build 48 | 49 | - name: 📂 Sync to Temporary Directory 50 | run: | 51 | mkdir temp_dir 52 | rsync -av --prune-empty-dirs --exclude-from='.distignore' ./ temp_dir/ 53 | 54 | - name: 📦 Compress and Upload ZIP 55 | run: | 56 | cd temp_dir 57 | zip -r ../woocommerce-pos.zip . 58 | cd .. 59 | gh release upload v${{ env.VERSION }} woocommerce-pos.zip --clobber 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | -------------------------------------------------------------------------------- /.github/workflows/sync-wporg-assets.yml: -------------------------------------------------------------------------------- 1 | name: Sync assets with wordpress.org 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | run: 8 | runs-on: ubuntu-latest 9 | name: Push assets to wporg 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: WordPress.org plugin asset/readme update 13 | uses: 10up/action-wordpress-plugin-asset-update@stable 14 | env: 15 | SLUG: woocommerce-pos 16 | IGNORE_OTHER_FILES: true 17 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} 18 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }} 19 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - "**.php" 8 | - "**.tsx" 9 | - "**.ts" 10 | workflow_dispatch: 11 | 12 | jobs: 13 | test: 14 | name: Test 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup PHP with tools 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: "7.4" 26 | tools: composer, cs2pr, phpcs 27 | 28 | - name: 🏗 Setup pnpm 29 | uses: pnpm/action-setup@v4 30 | with: 31 | version: '10.8.0' 32 | 33 | - name: 👷 Build 34 | run: | 35 | pnpm install 36 | composer prefix-dependencies 37 | composer install 38 | 39 | - name: Start WordPress Environment 40 | run: pnpm run wp-env start --xdebug=coverage 41 | 42 | - name: Check WooCommerce Activation 43 | run: pnpm run wp-env run cli -- wp plugin list 44 | 45 | - name: Linting the code 46 | run: | 47 | vendor/bin/phpcs -i 48 | vendor/bin/phpcs --config-show 49 | # vendor/bin/phpcs --report=checkstyle | cs2pr 50 | pnpm run lint 51 | continue-on-error: true 52 | 53 | - name: Running the tests 54 | run: | 55 | pnpm run test 56 | # cat phpunit.xml | cs2pr 57 | -------------------------------------------------------------------------------- /.github/workflows/update-pot.yml: -------------------------------------------------------------------------------- 1 | name: Update POT file 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '**.php' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | update-pot: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup PHP with tools 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: '7.3' 23 | tools: composer, wp-cli 24 | 25 | - name: Install dependencies 26 | run: | 27 | wp package install wp-cli/i18n-command:2.2.8 28 | 29 | - name: Update POT file 30 | run: wp i18n make-pot . languages/woocommerce-pos.pot --domain=woocommerce-pos --slug=woocommerce-pos --package-name="WooCommerce POS" --headers="{\"Report-Msgid-Bugs-To\":\"https://github.com/wcpos/woocommerce-pos/issues\"}" 31 | 32 | - name: Check for changes 33 | id: git-diff 34 | run: | 35 | # Extract the current and previous versions of the .pot file 36 | git show HEAD:languages/woocommerce-pos.pot > old.pot 37 | tail -n +16 languages/woocommerce-pos.pot > new-trimmed.pot 38 | tail -n +16 old.pot > old-trimmed.pot 39 | 40 | # Compare the trimmed files 41 | if diff old-trimmed.pot new-trimmed.pot; then 42 | echo "No changes detected." 43 | else 44 | echo "::set-output name=changes::true" 45 | fi 46 | 47 | # Clean up temporary files 48 | rm old.pot new-trimmed.pot old-trimmed.pot 49 | 50 | - name: Commit updated POT file 51 | if: steps.git-diff.outputs.changes == 'true' 52 | uses: stefanzweifel/git-auto-commit-action@v4 53 | with: 54 | commit_message: 'chore(i18n): update languages/woocommerce-pos.pot' 55 | file_pattern: '*.pot' 56 | 57 | 58 | -------------------------------------------------------------------------------- /.github/workflows/wp-engine.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to WP Engine 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: 🏗 Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: 🏗 Setup pnpm 17 | uses: pnpm/action-setup@v4 18 | with: 19 | version: '10.8.0' 20 | 21 | - name: 👷 Build 22 | run: | 23 | pnpm install 24 | composer prefix-dependencies 25 | composer install --no-dev 26 | pnpm settings build 27 | pnpm analytics build 28 | 29 | - name: 🚀 Deploy to WP Engine 30 | uses: wpengine/github-action-wpe-site-deploy@v3 31 | with: 32 | WPE_SSHG_KEY_PRIVATE: ${{ secrets.WPE_SSHG_KEY_PRIVATE }} 33 | WPE_ENV: wcposdev 34 | REMOTE_PATH: "wp-content/plugins/woocommerce-pos" 35 | FLAGS: >- 36 | -azvr --inplace --delete --delete-excluded --exclude-from=.distignore 37 | -e "ssh -o StrictHostKeyChecking=no -o ControlMaster=no -p 22 -i /github/home/.ssh/wpe_id_rsa" 38 | CACHE_CLEAR: TRUE -------------------------------------------------------------------------------- /.github/workflows/wporg-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to WordPress.org 2 | 3 | on: 4 | release: 5 | types: [ released ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | tag: 10 | name: New release 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: 🏗 Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: 🏗 Setup pnpm 19 | uses: pnpm/action-setup@v4 20 | with: 21 | version: '10.8.0' 22 | 23 | - name: 👷 Build 24 | run: | 25 | pnpm install 26 | composer prefix-dependencies 27 | composer install --no-dev 28 | pnpm settings build 29 | pnpm analytics build 30 | 31 | - name: 📦 WordPress Plugin Deploy 32 | id: deploy 33 | uses: 10up/action-wordpress-plugin-deploy@stable 34 | env: 35 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }} 36 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} 37 | SLUG: woocommerce-pos 38 | continue-on-error: true 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | wp-cli.local.yml 4 | node_modules/ 5 | *.zip 6 | *.tar.gz 7 | .idea 8 | coverage/ 9 | build/ 10 | vendor/ 11 | c3.php 12 | !/tests 13 | /tests/*.suite.yml 14 | /tests/_output 15 | .env 16 | .codeception.yml 17 | composer.lock 18 | .wp-env.override.json 19 | assets/js/* 20 | !assets/js/README.md 21 | !assets/js/indexeddb.worker.js 22 | assets/css/* 23 | !assets/css/README.md 24 | assets/asset-manifest.json 25 | assets/static 26 | assets/report.html 27 | yarn.lock 28 | .yarn/install-state.gz 29 | .yarn/cache 30 | hookdocs 31 | *.cache 32 | phpunit.xml 33 | tests/logs/junit.xml 34 | .turbo 35 | -------------------------------------------------------------------------------- /.phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | ./tests/ 13 | ./tests/ 14 | 15 | 16 | 17 | 18 | includes 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Listen for Xdebug", 9 | "type": "php", 10 | "request": "launch", 11 | "port": 9003 12 | }, 13 | { 14 | "name": "Launch currently open script", 15 | "type": "php", 16 | "request": "launch", 17 | "program": "${file}", 18 | "cwd": "${fileDirname}", 19 | "port": 0, 20 | "runtimeArgs": [ 21 | "-dxdebug.start_with_request=yes" 22 | ], 23 | "env": { 24 | "XDEBUG_MODE": "debug,develop", 25 | "XDEBUG_CONFIG": "client_port=${port}" 26 | } 27 | }, 28 | { 29 | "name": "Launch Built-in web server", 30 | "type": "php", 31 | "request": "launch", 32 | "runtimeArgs": [ 33 | "-dxdebug.mode=debug", 34 | "-dxdebug.start_with_request=yes", 35 | "-S", 36 | "localhost:0" 37 | ], 38 | "program": "", 39 | "cwd": "${workspaceRoot}", 40 | "port": 9003, 41 | "serverReadyAction": { 42 | "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started", 43 | "uriFormat": "http://localhost:%s", 44 | "action": "openExternally" 45 | } 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": false, 3 | "editor.detectIndentation": true, 4 | "editor.formatOnSave": true, 5 | "eslint.validate": [ 6 | "javascript", 7 | "javascriptreact", 8 | { 9 | "language": "typescript", 10 | "autoFix": true 11 | }, 12 | { 13 | "language": "typescriptreact", 14 | "autoFix": true 15 | } 16 | ], 17 | "typescript.tsdk": "node_modules/typescript/lib", 18 | "jest.jestCommandLine": "yarn jest" 19 | } -------------------------------------------------------------------------------- /.wordpress-org/banner-772x250.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcpos/woocommerce-pos/ace6334c9611bd802e2a5f23529a5bf273d6040f/.wordpress-org/banner-772x250.jpg -------------------------------------------------------------------------------- /.wordpress-org/icon-128x128-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcpos/woocommerce-pos/ace6334c9611bd802e2a5f23529a5bf273d6040f/.wordpress-org/icon-128x128-old.png -------------------------------------------------------------------------------- /.wordpress-org/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcpos/woocommerce-pos/ace6334c9611bd802e2a5f23529a5bf273d6040f/.wordpress-org/icon-128x128.png -------------------------------------------------------------------------------- /.wordpress-org/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcpos/woocommerce-pos/ace6334c9611bd802e2a5f23529a5bf273d6040f/.wordpress-org/icon-256x256.png -------------------------------------------------------------------------------- /.wordpress-org/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcpos/woocommerce-pos/ace6334c9611bd802e2a5f23529a5bf273d6040f/.wordpress-org/screenshot-1.png -------------------------------------------------------------------------------- /.wp-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": null, 3 | "phpVersion": null, 4 | "plugins": [ 5 | "https://downloads.wordpress.org/plugin/woocommerce.zip", 6 | "." 7 | ], 8 | "config": { 9 | "WP_DEBUG": true, 10 | "SCRIPT_DEBUG": true, 11 | "WP_PHP_BINARY": "php", 12 | "WP_TESTS_EMAIL": "admin@example.org", 13 | "WP_TESTS_TITLE": "Test Blog", 14 | "WP_TESTS_DOMAIN": "http://localhost", 15 | "WP_SITEURL": "http://localhost", 16 | "WP_HOME": "http://localhost" 17 | }, 18 | "mappings": { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /assets/css/README.md: -------------------------------------------------------------------------------- 1 | CSS assets are compiled during CI 2 | -------------------------------------------------------------------------------- /assets/img/README.md: -------------------------------------------------------------------------------- 1 | IMG assets are compiled during CI 2 | -------------------------------------------------------------------------------- /assets/img/animated/drawn-letters.svg: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /assets/img/animated/pulsing-letters.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /assets/img/animated/slide-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /assets/img/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcpos/woocommerce-pos/ace6334c9611bd802e2a5f23529a5bf273d6040f/assets/img/icon-128x128.png -------------------------------------------------------------------------------- /assets/img/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcpos/woocommerce-pos/ace6334c9611bd802e2a5f23529a5bf273d6040f/assets/img/icon-256x256.png -------------------------------------------------------------------------------- /assets/img/icon-round.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /assets/img/icon-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcpos/woocommerce-pos/ace6334c9611bd802e2a5f23529a5bf273d6040f/assets/img/icon-square.png -------------------------------------------------------------------------------- /assets/img/icon-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /assets/img/wcpos-icon-b&w.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/img/wcpos-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /assets/img/wp-menu-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/js/README.md: -------------------------------------------------------------------------------- 1 | JS assets are compiled during CI 2 | -------------------------------------------------------------------------------- /hookdoc-conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "destination": "hookdocs", 4 | "template": "node_modules/wp-hookdoc/template", 5 | "recurse": true, 6 | "readme": "./.github/hookdoc-tmpl/README.md" 7 | }, 8 | "source": { 9 | "include": [ 10 | "./woocommerce-pos.php", 11 | "./includes" 12 | ], 13 | "includePattern": ".+\\.(php)?$" 14 | }, 15 | "plugins": [ 16 | "node_modules/wp-hookdoc/plugin", 17 | "plugins/markdown" 18 | ], 19 | "templates": { 20 | "default": { 21 | "layoutFile": "./.github/hookdoc-tmpl/layout.tmpl", 22 | "staticFiles": { 23 | "include": [ 24 | "./.github/hookdoc-tmpl/static" 25 | ] 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /includes/API/Shipping_Methods_Controller.php: -------------------------------------------------------------------------------- 1 | wcpos_get_barcode_field(); 19 | 20 | // _sku is_internal_meta_key, don't use get_meta() for this. 21 | return '_sku' === $barcode_field ? $object->get_sku() : $object->get_meta( $barcode_field ); 22 | } 23 | 24 | /** 25 | * Get barcode field from settings. 26 | * 27 | * @return string 28 | */ 29 | public function wcpos_get_barcode_field() { 30 | $barcode_field = woocommerce_pos_get_settings( 'general', 'barcode_field' ); 31 | 32 | // Check for WP_Error 33 | if ( is_wp_error( $barcode_field ) ) { 34 | Logger::log( 'Error retrieving barcode_field: ' . $barcode_field->get_error_message() ); 35 | 36 | return ''; 37 | } 38 | 39 | // Check for non-string values 40 | if ( ! \is_string( $barcode_field ) ) { 41 | Logger::log( 'Unexpected data type for barcode_field. Expected string, got: ' . \gettype( $barcode_field ) ); 42 | 43 | return ''; 44 | } 45 | 46 | return $barcode_field; 47 | } 48 | 49 | /** 50 | * Get barcode field from settings. 51 | * 52 | * @return bool 53 | */ 54 | public function wcpos_pos_only_products_enabled() { 55 | $pos_only_products_enabled = woocommerce_pos_get_settings( 'general', 'pos_only_products' ); 56 | 57 | // Check for WP_Error 58 | if ( is_wp_error( $pos_only_products_enabled ) ) { 59 | Logger::log( 'Error retrieving pos_only_products: ' . $pos_only_products_enabled->get_error_message() ); 60 | 61 | return false; 62 | } 63 | 64 | // make sure it's true, just in case there's a corrupt setting 65 | return true === $pos_only_products_enabled; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /includes/API/Traits/Query_Helpers.php: -------------------------------------------------------------------------------- 1 | 'AND' ) ); 27 | return $combined; 28 | } 29 | 30 | // If both meta_queries are not empty and do not both have 'AND', combine them with 'AND' relation. 31 | return array( 32 | 'relation' => 'AND', 33 | $meta_query1, 34 | $meta_query2, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /includes/Admin/Analytics.php: -------------------------------------------------------------------------------- 1 | .woocommerce-pos-upgrade-notice { margin: 0 0 24px 0; }'; 21 | } 22 | 23 | /** 24 | * 25 | */ 26 | public function enqueue_assets() { 27 | $is_development = isset( $_ENV['DEVELOPMENT'] ) && $_ENV['DEVELOPMENT']; 28 | $dir = $is_development ? 'build' : 'assets'; 29 | 30 | wp_enqueue_script( 31 | PLUGIN_NAME . '-analytics', 32 | PLUGIN_URL . $dir . '/js/analytics.js', 33 | array( 34 | 'wp-hooks', 35 | 'wc-components', 36 | ), 37 | VERSION, 38 | true 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /includes/Admin/Notices.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * @see http://wcpos.com 10 | */ 11 | 12 | namespace WCPOS\WooCommercePOS\Admin; 13 | 14 | class Notices { 15 | // @var 16 | private static $notices = array(); 17 | 18 | /** 19 | * Constructor. 20 | */ 21 | public function __construct() { 22 | add_action( 'admin_notices', array( $this, 'admin_notices' ) ); 23 | } 24 | 25 | /** 26 | * Add a message for display. 27 | * 28 | * @param string $message 29 | * @param string $type (error | warning | success | info) 30 | * @param bool $dismissable 31 | */ 32 | public static function add( $message = '', $type = 'error', $dismissable = true ): void { 33 | self::$notices[] = array( 34 | 'type' => $type, 35 | 'message' => $message, 36 | 'dismissable' => $dismissable, 37 | ); 38 | } 39 | 40 | /** 41 | * Display the admin notices. 42 | */ 43 | public function admin_notices(): void { 44 | /** 45 | * Filters the POS admin notices. 46 | * 47 | * @since 1.0.0 48 | * 49 | * @param array $notices 50 | * 51 | * @return array $notices 52 | * 53 | * @hook woocommerce_pos_admin_notices 54 | */ 55 | $notices = apply_filters( 'woocommerce_pos_admin_notices', self::$notices ); 56 | if ( empty( $notices ) ) { 57 | return; 58 | } 59 | 60 | foreach ( $notices as $notice ) { 61 | $classes = 'notice notice-' . $notice['type']; 62 | if ( $notice['dismissable'] ) { 63 | $classes .= ' is-dismissable'; 64 | } 65 | if ( $notice['message'] ) { 66 | echo '

' . 67 | wp_kses( $notice['message'], wp_kses_allowed_html( 'post' ) ) . 68 | '

'; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /includes/Admin/Orders/HPOS_Single_Order.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |
12 |
13 | 23 |
24 |
25 | 64 |
65 | -------------------------------------------------------------------------------- /includes/Admin/Products/templates/variation-metabox-pos-barcode.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |

12 | 16 | 17 |

18 |
19 | 20 | -------------------------------------------------------------------------------- /includes/Admin/Products/templates/variation-metabox-visibility-select.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |

12 | 14 | 22 |

23 |
24 | -------------------------------------------------------------------------------- /includes/Admin/templates/upgrade.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @see http://www.kilbot.com 8 | */ 9 | ?> 10 | 11 |
12 | 13 | 18 |

19 | 20 |
21 | 22 |
23 | -------------------------------------------------------------------------------- /includes/Integrations/WPSEO.php: -------------------------------------------------------------------------------- 1 | 37 |

' . esc_html__( 'WooCommerce POS cannot run alongside the wePOS plugin due to compatibility issues. WooCommerce POS has been deactivated.', 'woocommerce-pos' ) . '

38 | '; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /includes/Logger.php: -------------------------------------------------------------------------------- 1 | log( self::$log_level, $message, array( 'source' => self::WC_LOG_FILENAME ) ); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /includes/Pro.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @see https://wcpos.com 9 | */ 10 | 11 | namespace WCPOS\WooCommercePOS; 12 | 13 | class Pro { 14 | } 15 | -------------------------------------------------------------------------------- /includes/Registry.php: -------------------------------------------------------------------------------- 1 | 8 | * @see https://wcpos.com 9 | * @package WCPOS\WooCommercePOS 10 | */ 11 | 12 | namespace WCPOS\WooCommercePOS; 13 | 14 | /** 15 | * 16 | */ 17 | class Registry { 18 | /** 19 | * Singleton instance. 20 | * 21 | * @var Registry 22 | */ 23 | private static $instance = null; 24 | 25 | /** 26 | * Storage for the registry. 27 | * 28 | * @var array 29 | */ 30 | private $storage = array(); 31 | 32 | /** 33 | * Gets the singleton instance of the registry. 34 | */ 35 | public static function get_instance() { 36 | if ( null === self::$instance ) { 37 | self::$instance = new self(); 38 | } 39 | return self::$instance; 40 | } 41 | 42 | /** 43 | * Registers a new instance. 44 | * 45 | * @param string $key 46 | * @param object $object 47 | */ 48 | public function set( $key, $object ) { 49 | $this->storage[ $key ] = $object; 50 | } 51 | 52 | /** 53 | * Retrieves an instance by key. 54 | * 55 | * @param string $key 56 | */ 57 | public function get( $key ) { 58 | return isset( $this->storage[ $key ] ) ? $this->storage[ $key ] : null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /includes/Server.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @see https://wcpos.com 8 | */ 9 | 10 | namespace WCPOS\WooCommercePOS; 11 | 12 | use WP_REST_Request; 13 | 14 | class Server { 15 | public function __construct() { 16 | add_filter( 'woocommerce_rest_check_permissions', array( $this, 'check_permissions' ) ); 17 | } 18 | 19 | /** 20 | * @TODO - add authentication check 21 | * 22 | * @return bool 23 | */ 24 | public function check_permissions(): bool { 25 | return true; 26 | } 27 | 28 | /** 29 | * @param string $route 30 | * @param string $method 31 | * @param array $attributes 32 | * 33 | * @return false|string 34 | */ 35 | public function wp_rest_request( string $route, string $method = 'GET', array $attributes = array() ) { 36 | $request = new WP_REST_Request( $method, $route ); 37 | $server = rest_get_server(); 38 | $response = $server->dispatch( $request ); 39 | $result = $server->response_to_data( $response, false ); 40 | $result = wp_json_encode( $result, 0 ); 41 | 42 | $json_error_message = $this->get_json_last_error(); 43 | 44 | if ( $json_error_message ) { 45 | $this->set_status( 500 ); 46 | $json_error_obj = new WP_Error( 47 | 'rest_encode_error', 48 | $json_error_message, 49 | array( 'status' => 500 ) 50 | ); 51 | 52 | $result = rest_convert_error_to_response( $json_error_obj ); 53 | $result = wp_json_encode( $result->data, 0 ); 54 | } 55 | 56 | return $result; 57 | } 58 | 59 | /** 60 | * Returns if an error occurred during most recent JSON encode/decode. 61 | * 62 | * @See - wp-includes/rest-api/class-wp-rest-server.php 63 | * 64 | * Strings to be translated will be in format like 65 | * "Encoding error: Maximum stack depth exceeded". 66 | */ 67 | protected function get_json_last_error() { 68 | $last_error_code = json_last_error(); 69 | 70 | if ( JSON_ERROR_NONE === $last_error_code || empty( $last_error_code ) ) { 71 | return false; 72 | } 73 | 74 | return json_last_error_msg(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /includes/Services/Cache.php: -------------------------------------------------------------------------------- 1 | $cache_dir, 38 | ) 39 | ); 40 | 41 | $event_manager = EventManager::getInstance(); 42 | 43 | // Generate a unique instance ID for each logged-in user. 44 | $driver = new Driver( $config, $instance_id ); 45 | $driver->setEventManager( $event_manager ); 46 | 47 | $cache = new Psr16Adapter( $driver ); 48 | } 49 | 50 | return $cache; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /includes/Services/README.md: -------------------------------------------------------------------------------- 1 | ## Services 2 | 3 | Shared service classes, eg: between Frontend, Admin and API. Also note that the Pro plugin may use these Services. -------------------------------------------------------------------------------- /includes/Templates/Received.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @see http://wcpos.com 6 | * @package WooCommercePOS\Templates 7 | */ 8 | 9 | namespace WCPOS\WooCommercePOS\Templates; 10 | 11 | use Exception; 12 | use WCPOS\WooCommercePOS\Server; 13 | 14 | class Received { 15 | /** 16 | * @var int 17 | */ 18 | private $order_id; 19 | 20 | public function __construct( int $order_id ) { 21 | $this->order_id = $order_id; 22 | 23 | add_filter( 'show_admin_bar', '__return_false' ); 24 | } 25 | 26 | 27 | public function get_template(): void { 28 | try { 29 | // get order 30 | $order = \wc_get_order( $this->order_id ); 31 | 32 | // Order or receipt url is invalid. 33 | if ( ! $order ) { 34 | wp_die( esc_html__( 'Sorry, this order is invalid.', 'woocommerce-pos' ) ); 35 | } 36 | 37 | // if ( ! $order->is_paid() ) { 38 | // wp_die( esc_html__( 'Sorry, this order has not been paid.', 'woocommerce-pos' ) ); 39 | // } 40 | 41 | /** 42 | * @TODO - this is a hack and needs to be fixed 43 | * @NOTE - the received template will be removed once we move to session based checkout 44 | * 45 | * - hardcoding the rest endpoint is a receipe for disaster 46 | */ 47 | $server = new Server(); 48 | $order_json = $server->wp_rest_request( '/wcpos/v1/orders/' . $this->order_id ); 49 | $completed_status_setting = woocommerce_pos_get_settings( 'checkout', 'order_status' ); 50 | $completed_status = 'wc-' === substr( $completed_status_setting, 0, 3 ) ? substr( $completed_status_setting, 3 ) : $completed_status_setting; 51 | $order_complete = $completed_status !== 'pos-open'; 52 | 53 | // @TODO - display message for errors 54 | 55 | include woocommerce_pos_locate_template( 'received.php' ); 56 | exit; 57 | } catch ( Exception $e ) { 58 | \wc_print_notice( $e->getMessage(), 'error' ); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /includes/i18n.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * @see http://wcpos.com 12 | */ 13 | 14 | namespace WCPOS\WooCommercePOS; 15 | 16 | class i18n { 17 | 18 | /** 19 | * Load the plugin text domain for translation. 20 | */ 21 | public function construct() { 22 | load_plugin_textdomain( 'woocommerce-pos', false, PLUGIN_PATH . '/languages/' ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /includes/updates/update-0.4.6.php: -------------------------------------------------------------------------------- 1 | array( 'shop_order' ), 16 | 'post_status' => array( 'any' ), 17 | 'posts_per_page' => - 1, 18 | 'fields' => 'ids', 19 | 'meta_query' => array( 20 | array( 21 | 'key' => '_pos', 22 | 'value' => 1, 23 | 'compare' => '=', 24 | ), 25 | ), 26 | ); 27 | 28 | $query = new WP_Query( $args ); 29 | 30 | foreach ( $query->posts as $order_id ) { 31 | // check _order_tax and _order_shipping_tax for reports 32 | if ( ! get_post_meta( $order_id, '_order_tax', true ) ) { 33 | update_post_meta( $order_id, '_order_tax', 0 ); 34 | } 35 | if ( ! get_post_meta( $order_id, '_order_shipping_tax', true ) ) { 36 | update_post_meta( $order_id, '_order_shipping_tax', 0 ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /includes/updates/update-0.4.php: -------------------------------------------------------------------------------- 1 | add_cap( $cap ); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /includes/updates/update-1.0.0-beta.1.php: -------------------------------------------------------------------------------- 1 | get_name(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wcpos/woocommerce-pos", 3 | "version": "1.7.10", 4 | "description": "A simple front-end for taking WooCommerce orders at the Point of Sale.", 5 | "main": "index.js", 6 | "workspaces": { 7 | "packages": [ 8 | "packages/*" 9 | ] 10 | }, 11 | "scripts": { 12 | "clean": "node scripts/clean-node-modules", 13 | "wp-env": "wp-env", 14 | "lint:php": "composer run lint-report | cs2pr", 15 | "test": "pnpm run test:unit:php", 16 | "pretest": "wp-env start --xdebug=coverage && wp-env run cli --env-cwd='wp-content/plugins/woocommerce-pos' composer install", 17 | "test:unit:php": "wp-env run cli -- wp plugin activate woocommerce && wp-env run --env-cwd='wp-content/plugins/woocommerce-pos' tests-cli -- vendor/bin/phpunit -c .phpunit.xml.dist --verbose --log-junit phpunit.xml", 18 | "build:docs": "jsdoc -c hookdoc-conf.json", 19 | "settings": "pnpm --filter=@wcpos/settings", 20 | "analytics": "pnpm --filter=@wcpos/analytics" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/wcpos/woocommerce-pos.git" 25 | }, 26 | "keywords": [ 27 | "wordpress", 28 | "woocommerce", 29 | "ecommerce", 30 | "point-of-sale" 31 | ], 32 | "author": "kilbot", 33 | "license": "ISC", 34 | "bugs": { 35 | "url": "https://github.com/wcpos/woocommerce-pos/issues" 36 | }, 37 | "homepage": "https://wcpos.com", 38 | "devDependencies": { 39 | "@wordpress/babel-plugin-makepot": "6.23.0", 40 | "@wordpress/env": "10.23.0", 41 | "jsdoc": "^4.0.3", 42 | "turbo": "2.4.4", 43 | "wp-hookdoc": "0.2.0" 44 | }, 45 | "packageManager": "pnpm@10.8.0" 46 | } 47 | -------------------------------------------------------------------------------- /packages/analytics/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | 4 | const presets = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript']; 5 | 6 | const plugins = [ 7 | [ 8 | '@babel/plugin-transform-runtime', 9 | { 10 | regenerator: true, 11 | }, 12 | ], 13 | ]; 14 | 15 | return { 16 | presets, 17 | plugins, 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/analytics/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Notice } from '@wordpress/components'; 4 | 5 | import { t, txPromise } from './translations'; 6 | 7 | const globalHooks = window.wp && window.wp.hooks; 8 | 9 | const WrappedReport = ({ Component, ...props }) => { 10 | const [isReady, setIsReady] = React.useState(false); 11 | 12 | React.useEffect(() => { 13 | txPromise.then(() => setIsReady(true)); 14 | }, []); 15 | 16 | return ( 17 | <> 18 | 19 | {t('Do you want analytics for your POS orders?', { _tags: 'wp-admin-analytics' })}{' '} 20 | 21 | {t('Upgrade to WooCommerce POS Pro', { _tags: 'wp-admin-analytics' })} 22 | 23 | . 24 | 25 | 26 | 27 | ); 28 | }; 29 | 30 | if (globalHooks) { 31 | globalHooks.addFilter('woocommerce_admin_reports_list', 'woocommerce-pos', (pages) => { 32 | return pages.map((item) => { 33 | if (item.report === 'orders') { 34 | return { 35 | ...item, 36 | component: (props) => , 37 | }; 38 | } 39 | return item; 40 | }); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /packages/analytics/src/translations/index.ts: -------------------------------------------------------------------------------- 1 | import * as Transifex from '@transifex/native'; 2 | 3 | import localesData from './locales.json'; 4 | 5 | const tx = Transifex.tx; 6 | const t = Transifex.t; 7 | 8 | tx.init({ 9 | token: '1/09853773ef9cda3be96c8c451857172f26927c0f', 10 | filterTags: 'wp-admin-analytics', 11 | }); 12 | 13 | interface Locale { 14 | name: string; 15 | nativeName?: string; 16 | code: string; 17 | locale: string; 18 | } 19 | 20 | interface Locales { 21 | [key: string]: Locale; 22 | } 23 | 24 | const locales: Locales = localesData; 25 | const htmlElement = document.documentElement; 26 | const lang = htmlElement.getAttribute('lang') || 'en'; 27 | const { locale } = locales[lang.toLowerCase()] || locales[lang.split('-')[0]] || locales['en']; 28 | 29 | const txPromise = tx.setCurrentLocale(locale).catch(console.error); 30 | 31 | export { tx, t, txPromise }; 32 | -------------------------------------------------------------------------------- /packages/analytics/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "react", 19 | }, 20 | "include": [ 21 | "src" 22 | ] 23 | } -------------------------------------------------------------------------------- /packages/eslint/.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Security Announcements 4 | 5 | ## Reporting a Vulnerability 6 | 7 | Please report (suspected) security vulnerabilities to security@wcpos.com. You will receive a response from us within 48 8 | hours. If the issue is confirmed, we will release a patch as soon as possible depending on complexity but historically 9 | within a few days. 10 | 11 | ## Supported Versions 12 | 13 | -------------------------------------------------------------------------------- /packages/eslint/.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | 10 | # Maintain dependencies for npm 11 | - package-ecosystem: "npm" 12 | directory: "/" 13 | schedule: 14 | interval: "weekly" 15 | -------------------------------------------------------------------------------- /packages/eslint/.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | 3 | jobs: 4 | publish: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - uses: actions/setup-node@v2.4.1 9 | with: 10 | node-version: 16.x 11 | - run: npm install 12 | - run: npm test 13 | - uses: JS-DevTools/npm-publish@v1 14 | with: 15 | token: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /packages/eslint/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 WooCommerce POS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/eslint/README.md: -------------------------------------------------------------------------------- 1 | # Shared Eslint configuration for [WooCommerce POS](https://wcpos.com) React Native app -------------------------------------------------------------------------------- /packages/eslint/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['eslint-config-universe', 'plugin:react-hooks/recommended'], 3 | plugins: ['@typescript-eslint', 'jest', 'react-native', 'react-hooks'], 4 | rules: { 5 | 'prettier/prettier': [ 6 | 'error', 7 | { 8 | useTabs: true, 9 | singleQuote: true, 10 | trailingComma: 'es5', 11 | printWidth: 100, 12 | }, 13 | ], 14 | 'import/order': [ 15 | 'error', 16 | { 17 | alphabetize: { 18 | order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */, 19 | caseInsensitive: true /* ignore case. Options: [true, false] */, 20 | }, 21 | pathGroups: [ 22 | { 23 | pattern: 'react+(-native|)', 24 | group: 'external', 25 | position: 'before', 26 | }, 27 | { 28 | pattern: '@wcpos/**', 29 | group: 'external', 30 | position: 'after', 31 | }, 32 | ], 33 | pathGroupsExcludedImportTypes: ['react', 'react-native'], 34 | groups: ['builtin', 'external', ['parent', 'sibling', 'index'], 'type'], 35 | 'newlines-between': 'always', 36 | }, 37 | ], 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /packages/eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wcpos/eslint-config", 3 | "version": "1.2.0", 4 | "description": "Eslint configuration for React Native", 5 | "author": "kilbot ", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "scripts": { 9 | "lint": "node_modules/.bin/eslint .", 10 | "build": "echo 'Nothing to build'", 11 | "test": "echo 'Nothing to test'" 12 | }, 13 | "dependencies": { 14 | "@rushstack/eslint-patch": "^1.10.3", 15 | "@typescript-eslint/eslint-plugin": "^7.13.0", 16 | "@typescript-eslint/parser": "7.13.0", 17 | "@wcpos/tsconfig": "*", 18 | "eslint": "9.5.0", 19 | "eslint-config-airbnb": "19.0.4", 20 | "eslint-config-prettier": "9.1.0", 21 | "eslint-config-universe": "^13.0.0", 22 | "eslint-plugin-import": "2.29.1", 23 | "eslint-plugin-jest": "28.6.0", 24 | "eslint-plugin-jsx-a11y": "6.8.0", 25 | "eslint-plugin-prettier": "5.1.3", 26 | "eslint-plugin-react": "7.34.2", 27 | "eslint-plugin-react-hooks": "4.6.2", 28 | "eslint-plugin-react-native": "4.1.0", 29 | "prettier": "3.3.2", 30 | "typescript": "^5.4.5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/eslint/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@wcpos/tsconfig/base.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/settings/.gitignore: -------------------------------------------------------------------------------- 1 | compilation-stats.json -------------------------------------------------------------------------------- /packages/settings/README.md: -------------------------------------------------------------------------------- 1 | Source folder for admin settings javascript and css -------------------------------------------------------------------------------- /packages/settings/assets/book.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/settings/assets/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/settings/assets/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/settings/assets/close-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/settings/assets/comment-question.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/settings/assets/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/settings/assets/drag-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/settings/assets/email.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/settings/assets/wcpos-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 33 | 43 | 44 | -------------------------------------------------------------------------------- /packages/settings/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | 4 | const presets = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript']; 5 | 6 | const plugins = [ 7 | [ 8 | '@babel/plugin-transform-runtime', 9 | { 10 | regenerator: true, 11 | }, 12 | ], 13 | ]; 14 | 15 | return { 16 | presets, 17 | plugins, 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/settings/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/settings/src/components/error.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { get } from 'lodash'; 4 | import { FallbackProps } from 'react-error-boundary'; 5 | 6 | import Notice from './notice'; 7 | import { t } from '../translations'; 8 | 9 | const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => { 10 | const message = get(error, 'message', 'Unknown error'); 11 | 12 | return ( 13 |
14 | 15 |

16 | {t('Something went wrong', { _tags: 'wp-admin-settings' })}: {message} 17 |

18 |
19 |
20 | ); 21 | }; 22 | 23 | export default ErrorFallback; 24 | -------------------------------------------------------------------------------- /packages/settings/src/components/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Tooltip, Icon } from '@wordpress/components'; 4 | 5 | interface LabelProps { 6 | children: React.ReactNode; 7 | tip?: string; 8 | } 9 | 10 | const Label = ({ children, tip }: LabelProps) => { 11 | return ( 12 |
13 | {children} 14 | {tip && ( 15 | 16 | 17 | 18 | 19 | 20 | )} 21 |
22 | ); 23 | }; 24 | 25 | export default Label; 26 | -------------------------------------------------------------------------------- /packages/settings/src/components/notice.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Icon } from '@wordpress/components'; 4 | import classNames from 'classnames'; 5 | 6 | import CloseIcon from '../../assets/close-icon.svg'; 7 | 8 | interface NoticeProps { 9 | status?: 'error' | 'info' | 'success'; 10 | onRemove?: () => void; 11 | children: React.ReactNode; 12 | isDismissible?: boolean; 13 | } 14 | 15 | const Notice = ({ status, children, onRemove, isDismissible = true }: NoticeProps) => { 16 | return ( 17 |
25 |
{children}
26 | {isDismissible && ( 27 | } onClick={onRemove} className="wcpos-h-5 wcpos-w-5" /> 28 | )} 29 |
30 | ); 31 | }; 32 | 33 | export default Notice; 34 | -------------------------------------------------------------------------------- /packages/settings/src/components/snackbar/index.tsx: -------------------------------------------------------------------------------- 1 | export { SnackbarProvider } from './provider'; 2 | export { useSnackbar as default } from './use-snackbar'; 3 | -------------------------------------------------------------------------------- /packages/settings/src/components/snackbar/provider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SnackbarList } from './snackbar-list'; 4 | 5 | type Snackbar = import('./snackbar').SnackbarProps; 6 | 7 | interface SnackbarContextProps { 8 | addSnackbar: (snackbar: Snackbar) => void; 9 | // removeSnackbar: (id: string) => void; 10 | } 11 | 12 | export const SnackbarContext = React.createContext({ 13 | addSnackbar: () => {}, 14 | // removeSnackbar: () => {}, 15 | }); 16 | 17 | interface Props { 18 | children: React.ReactNode; 19 | } 20 | 21 | export const SnackbarProvider = ({ children }: Props) => { 22 | const [snackbars, setSnackbars] = React.useState([]); 23 | 24 | /** 25 | * Note: snackbars is an array of objects, but for now we only support one snackbar at a time. 26 | */ 27 | const addSnackbar = (snackbar: Snackbar) => { 28 | // setSnackbars((prev) => [...prev, snackbar]); 29 | setSnackbars([snackbar]); 30 | }; 31 | 32 | const removeSnackbar = (id: string) => { 33 | setSnackbars((prev) => prev.filter((snackbar) => snackbar.id !== id)); 34 | }; 35 | 36 | return ( 37 | 38 | {children} 39 |
40 | 41 |
42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /packages/settings/src/components/snackbar/snackbar-list.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Snackbar, SnackbarProps } from './snackbar'; 4 | 5 | export interface SnackbarListProps { 6 | snackbars: SnackbarProps[]; 7 | removeSnackbar: (id: string) => void; 8 | } 9 | 10 | export const SnackbarList = ({ snackbars, removeSnackbar }: SnackbarListProps) => { 11 | return ( 12 | <> 13 | {snackbars.map((snackbar) => ( 14 | removeSnackbar(snackbar.id)} key={snackbar.id} {...snackbar} /> 15 | ))} 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /packages/settings/src/components/snackbar/snackbar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Snackbar as WPSnackbar } from '@wordpress/components'; 4 | 5 | export interface SnackbarProps { 6 | id: string; 7 | message: string; 8 | onRemove?: () => void; 9 | timeout?: boolean; 10 | } 11 | 12 | export const Snackbar = ({ message, onRemove, timeout = true }: SnackbarProps) => { 13 | React.useEffect(() => { 14 | const timer = setTimeout(() => { 15 | timeout && onRemove && onRemove(); 16 | }, 3000); 17 | return () => clearTimeout(timer); 18 | }, [message, onRemove, timeout]); 19 | 20 | return message ? {message} : null; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/settings/src/components/snackbar/use-snackbar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { SnackbarContext } from './provider'; 4 | 5 | /** 6 | * Get a function for showing a Snackbar with the specified options. 7 | * 8 | * Simply call the function to show the Snackbar, which will be automatically 9 | * dismissed. 10 | * 11 | * @example 12 | * const showSnackbar = useSnackbar({ message: 'This is a Snackbar!' }) 13 | * 14 | */ 15 | export const useSnackbar = () => { 16 | const context = React.useContext(SnackbarContext); 17 | 18 | if (!context) { 19 | throw new Error(`useSnackbar must be called within SnackbarProvider`); 20 | } 21 | 22 | return context; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/settings/src/components/tabs/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { TabBar } from './tab-bar'; 4 | 5 | export type Route = { 6 | key: string; 7 | // icon?: string; 8 | title: string | ((props: { focused: boolean }) => React.ReactNode); 9 | }; 10 | 11 | export type NavigationState = { 12 | index: number; 13 | routes: T[]; 14 | }; 15 | 16 | export type TabsProps = { 17 | onIndexChange: (index: number) => void; 18 | onTabItemHover?: (index: number, route: Route) => void; 19 | navigationState: NavigationState; 20 | renderScene: (props: { route: T }) => React.ReactNode; 21 | // renderLazyPlaceholder?: (props: { route: T }) => React.ReactNode; 22 | // renderTabBar?: (props: { navigationState: NavigationState }) => React.ReactNode; 23 | tabBarPosition?: 'top' | 'bottom' | 'left' | 'right'; 24 | }; 25 | 26 | const Tabs = ({ 27 | onIndexChange, 28 | onTabItemHover, 29 | navigationState, 30 | renderScene, 31 | tabBarPosition = 'top', 32 | }: TabsProps) => { 33 | return ( 34 | <> 35 | 41 | {renderScene({ route: navigationState.routes[navigationState.index] })} 42 | 43 | ); 44 | }; 45 | 46 | export default Tabs; 47 | -------------------------------------------------------------------------------- /packages/settings/src/components/tabs/tab-bar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { TabItem } from './tab-item'; 4 | 5 | type Route = import('./').Route; 6 | 7 | export interface TabBarProps { 8 | routes: Route[]; 9 | onIndexChange: (index: number) => void; 10 | onTabItemHover?: (index: number, route: Route) => void; 11 | direction?: 'horizontal' | 'vertical'; 12 | focusedIndex: number; 13 | } 14 | 15 | export const TabBar = ({ 16 | routes, 17 | onIndexChange, 18 | onTabItemHover, 19 | direction = 'horizontal', 20 | focusedIndex, 21 | }: TabBarProps) => { 22 | return ( 23 |
24 | {routes.map((route, i) => { 25 | const focused = i === focusedIndex; 26 | return ( 27 | onIndexChange(i)} 31 | onHover={() => onTabItemHover && onTabItemHover(i, route)} 32 | focused={focused} 33 | /> 34 | ); 35 | })} 36 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /packages/settings/src/components/tabs/tab-item.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import classNames from 'classnames'; 4 | 5 | export interface TabItemProps { 6 | title: string | ((props: { focused: boolean }) => React.ReactNode); 7 | onClick: () => void; 8 | onHover?: () => void; 9 | focused: boolean; 10 | } 11 | 12 | export const TabItem = ({ title, onClick, focused, onHover }: TabItemProps) => { 13 | return ( 14 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /packages/settings/src/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: React.FunctionComponent>; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /packages/settings/src/hooks/use-notices.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface NoticeProps { 4 | type?: 'error' | 'info' | 'success'; 5 | message: string; 6 | } 7 | 8 | interface NoticesContextProps { 9 | notice: NoticeProps | null; 10 | setNotice: (args: NoticeProps | null) => void; 11 | } 12 | 13 | const NoticesContext = React.createContext({ 14 | notice: null, 15 | setNotice: () => {}, 16 | }); 17 | 18 | interface NoticesProviderProps { 19 | children: React.ReactNode; 20 | } 21 | 22 | export const NoticesProvider = ({ children }: NoticesProviderProps) => { 23 | const [notice, setNotice] = React.useState(null); 24 | 25 | return ( 26 | 32 | {children} 33 | 34 | ); 35 | }; 36 | 37 | const useNotices = () => { 38 | return React.useContext(NoticesContext); 39 | }; 40 | 41 | export default useNotices; 42 | -------------------------------------------------------------------------------- /packages/settings/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* 6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 8 | */ 9 | *, 10 | ::before, 11 | ::after { 12 | /* 1 */ 13 | box-sizing: border-box; 14 | /* 2 */ 15 | border-width: 0; 16 | /* 2 */ 17 | border-style: solid; 18 | /* 2 */ 19 | border-color: #e5e7eb; 20 | } 21 | 22 | /* 23 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 24 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 25 | 3. Remove gaps between table borders by default. 26 | */ 27 | 28 | #woocommerce-pos-settings table { 29 | /* 1 */ 30 | text-indent: 0; 31 | /* 2 */ 32 | border-color: inherit; 33 | /* 3 */ 34 | border-collapse: collapse; 35 | } 36 | 37 | /** 38 | * forms.css gives input[type="checkbox"] display: inline-block 39 | * we need it to be hidden sometimes 40 | */ 41 | /* .wcpos-hidden { 42 | display: none !important; 43 | } */ 44 | 45 | /** 46 | * Give JS 10 seconds to load before showing the error 47 | * @TODO - there must be a better way to do this 48 | */ 49 | #woocommerce-pos-js-error { 50 | animation: 10s wcposError; 51 | animation-fill-mode: forwards; 52 | visibility: hidden; 53 | } 54 | 55 | @keyframes wcposError { 56 | 99% { 57 | visibility: hidden; 58 | } 59 | 60 | 100% { 61 | visibility: visible; 62 | } 63 | } -------------------------------------------------------------------------------- /packages/settings/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 4 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; 5 | import { createRoot, render } from '@wordpress/element'; 6 | import { getFragment, isValidFragment } from '@wordpress/url'; 7 | import { ErrorBoundary } from 'react-error-boundary'; 8 | 9 | import Error from './components/error'; 10 | import { SnackbarProvider } from './components/snackbar'; 11 | import { NoticesProvider } from './hooks/use-notices'; 12 | import useReadyState from './hooks/use-ready-state'; 13 | import Main, { ScreenKeys } from './screens'; 14 | 15 | import './index.css'; 16 | 17 | // Create a client 18 | const queryClient = new QueryClient({ 19 | defaultOptions: { 20 | queries: { 21 | suspense: true, 22 | staleTime: 10 * 60 * 1000, // 10 minutes 23 | }, 24 | }, 25 | }); 26 | 27 | const App = () => { 28 | const fragment = getFragment(window.location.href) || ''; 29 | const initialScreen = isValidFragment(fragment) 30 | ? (fragment.replace(/^#/, '') as ScreenKeys) 31 | : 'general'; 32 | 33 | const { isReady } = useReadyState({ initialScreen }); 34 | 35 | if (!isReady) { 36 | return null; 37 | } 38 | 39 | return ( 40 | Loading app...

}> 41 | 42 | 43 |
44 | 45 | 46 | 47 | ); 48 | }; 49 | 50 | const Root = () => { 51 | return ( 52 | 53 | 54 | 55 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | const el = document.getElementById('woocommerce-pos-settings'); 62 | 63 | if (createRoot) { 64 | createRoot(el).render(); 65 | } else { 66 | render(, el); 67 | } 68 | -------------------------------------------------------------------------------- /packages/settings/src/screens/checkout/order-status-select.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import Select from '../../components/select'; 4 | 5 | interface OrderStatusSelectProps { 6 | selectedStatus: string; 7 | mutate: (data: Record) => void; 8 | } 9 | 10 | const OrderStatusSelect = ({ selectedStatus, mutate }: OrderStatusSelectProps) => { 11 | const order_statuses = window?.wcpos?.settings?.order_statuses; 12 | 13 | const options = React.useMemo(() => { 14 | return Object.entries(order_statuses).map(([value, label]) => ({ value, label })); 15 | }, [order_statuses]); 16 | 17 | return ( 18 |