13 | The SCAYLE Panel Demo Add-on is showcasing how to write an Add-on in combination with the SCAYLE Component Library. 14 |
15 | 16 | 19 | 20 | 21 | ## Getting Started 22 | 23 | Visit the [Add-on Developer Guide](https://scayle.dev/en/dev/add-on/introduction) to learn more on how to use the Panel icons. 24 | 25 | Visit the [Docs](https://scayle.dev) to learn more about our system requirements. 26 | 27 | ## What is Scayle ? 28 | 29 | [SCAYLE](https://scayle.com) is a full-featured e-commerce software solution that comes with flexible APIs. Within SCAYLE, you can manage all aspects of your shop, such as products, stocks, customers, and transactions. 30 | 31 | Learn more about [Scayles’s architecture](https://scayle.dev/en/developer-guide/introduction/infrastructure) and [commerce modules](https://scayle.dev/en/getting-started) in the Docs. 32 | 33 | 34 | ## Installation 35 | 36 | Before running the application, you need to configure your hosts file with the add-on's hostname. You'll also need to create an SSL key pair for this domain. 37 | 38 | It's also recommended to install [Volta](https://volta.sh/) in order to use the correct versions of node.js and npm. 39 | 40 | ```shell 41 | # Add 42 | 127.0.0.1 {hostname from .env}` (replace everything including the {}) to `/etc/hosts` 43 | 44 | # Execute 45 | npm ci 46 | 47 | # !! Make sure you have openssl installed on your computer for this step. !! 48 | # Generate a ssl certificates with the following command. The domain name (aka hostname) is retrieved from `CONFIG_SERVER_HOST` in `.env`. 49 | npm run generate:ssl 50 | ``` 51 | 52 | ## Scripts 53 | 54 | Build the production app: 55 | `npm run build` 56 | 57 | Build the hot-reloading app: 58 | `npm run dev` 59 | 60 | Run unit tests: `npm run unit` 61 | 62 | Check types: `npm run typecheck` 63 | 64 | ## Built With 65 | 66 | [Tailwindcss](https://tailwindcss.com/) 67 | [Vue 3](https://vuejs.org/) 68 | [Vite](https://vitejs.dev/) 69 | [Vitest](https://vitest.dev/) 70 | [TypeScript](https://www.typescriptlang.org/) 71 | 72 | ## Shadow DOM 73 | 74 | To inject addon in it's own isolated container we can use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM). To enable it you must set in .env `PANEL_USE_SHADOW_DOM=true`. 75 | 76 | ## Other channels 77 | 78 | - [LinkedIn](https://www.linkedin.com/company/scaylecommerce/) 79 | - [Jobs](https://www.scayle.com/company/career/) 80 | - [SCAYLE](https://scayle.com) 81 | 82 | ## License 83 | Licensed under the [MIT](https://opensource.org/license/mit/) 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-add-on-vite", 3 | "description": "The SCAYLE Panel Demo Add-On is showcasing how to write an Add-On in combination with the SCAYLE Component Library.", 4 | "homepage": "https://www.scayle.com/", 5 | "private": true, 6 | "version": "1.0.0", 7 | "type": "module", 8 | "scripts": { 9 | "dev": "vite", 10 | "unit": "vitest run", 11 | "coverage": "vitest run --coverage", 12 | "typecheck": "vue-tsc --noEmit", 13 | "preview": "vite preview", 14 | "build": "vue-tsc --noEmit && vite build", 15 | "build:watch": "vite build --watch --mode development", 16 | "watch": "run-p build:watch preview", 17 | "generate:ssl": "bash -c ./server-ssl/generate-ssl.sh", 18 | "prepare": "bash -c ./scripts/prepare.sh", 19 | "code-lint": "eslint --fix 'src/**/*.{ts,tsx,vue,js}'", 20 | "lint-staged": "lint-staged" 21 | }, 22 | "keywords": [ 23 | "scayle", 24 | "scayle panel", 25 | "panel", 26 | "scayle panel components", 27 | "panel components", 28 | "scayle panel add-on", 29 | "panel-add-on", 30 | "add-on" 31 | ], 32 | "author": "SCAYLE", 33 | "license": "MIT", 34 | "dependencies": { 35 | "@scayle/add-on-utils": "^2.0.0", 36 | "@scayle/components": "^3.0.0", 37 | "@scayle/panel-icons": "^2.2.0", 38 | "@scayle/single-spa-vue": "^2.6.2", 39 | "@scayle/tailwind-base": "^1.1.1", 40 | "@tanstack/vue-query": "^4.14.3", 41 | "@vueuse/core": "^9.4.0", 42 | "element-plus": "^2.3.4", 43 | "vue": "^3.2.41", 44 | "vue-router": "^4.1.6" 45 | }, 46 | "devDependencies": { 47 | "@types/node": "^18.11.9", 48 | "@typescript-eslint/parser": "^6.0.0", 49 | "@vitejs/plugin-vue": "^3.2.0", 50 | "@vitest/coverage-c8": "^0.24.5", 51 | "@vue/test-utils": "^2.2.1", 52 | "autoprefixer": "^10.4.13", 53 | "dom-storage": "^2.1.0", 54 | "eslint": "^8.27.0", 55 | "eslint-import-resolver-typescript": "^3.5.2", 56 | "eslint-plugin-import": "^2.26.0", 57 | "eslint-plugin-unicorn": "^48.0.0", 58 | "eslint-plugin-vue": "^9.7.0", 59 | "husky": "^8.0.1", 60 | "jsdom": "^20.0.2", 61 | "lint-staged": "^13.0.3", 62 | "magic-string": "^0.26.7", 63 | "nanoid": "^4.0.0", 64 | "npm-run-all": "^4.1.5", 65 | "postcss": "^8.4.18", 66 | "postcss-dir-pseudo-class": "^6.0.5", 67 | "postcss-logical": "^5.0.4", 68 | "tailwindcss": "3.2.2", 69 | "tailwindcss-logical": "3.0.0", 70 | "typescript": "^4.8.4", 71 | "unplugin-auto-import": "^0.11.4", 72 | "unplugin-icons": "^0.14.15", 73 | "unplugin-vue-components": "^0.24.1", 74 | "vite": "^3.2.2", 75 | "vite-tsconfig-paths": "^3.5.2", 76 | "vitest": "^0.24.5", 77 | "vue-tsc": "^0.40.13" 78 | }, 79 | "volta": { 80 | "node": "18.12.1", 81 | "npm": "9.1.1" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /vite-plugins/aboutYouGlobalWindowEventLoader.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite'; 2 | import MagicString from 'magic-string'; 3 | 4 | export const generateWindowKeyFromAddon = (addOnId: string) => { 5 | let normalizedAddOnId = addOnId 6 | .replace('-', '_') 7 | .replace(' ', '_') 8 | 9 | return `__VITE_ADDON_EVENT_${normalizedAddOnId}__`; 10 | } 11 | 12 | const windowGlobalEventHandlerCode = (addOnId: string) => { 13 | const windowKey = generateWindowKeyFromAddon(addOnId); 14 | const windowObj = `window['${windowKey}']`; 15 | 16 | return ` 17 | const initWindowGlobalHandler = () => { 18 | if (${windowObj}) { 19 | return; 20 | } 21 | 22 | ${windowObj} = { 23 | listeners: [], 24 | listen(callback) { 25 | this.listeners.push(callback); 26 | }, 27 | async trigger() { 28 | try { 29 | for (const callback of this.listeners) { 30 | callback(); 31 | } 32 | } catch (e) { 33 | // do nothing if an error happens 34 | } 35 | } 36 | }; 37 | }; 38 | initWindowGlobalHandler(); 39 | 40 | onBeforeMount(() => ${windowObj}?.trigger()); 41 | `; 42 | }; 43 | 44 | const getSetupFunctionIndex = (code: string) => { 45 | const regex = /(setup\(.*\)\s*\{)/; 46 | const found = regex.exec(code); 47 | 48 | if (!found?.[0]) { 49 | return; 50 | } 51 | 52 | return found.index + found?.[0].length; 53 | } 54 | 55 | const getBeforeMountImportIndex = (code: string) => { 56 | const regex = /import\s*(\{).*from\s*'vue';/; 57 | const found = regex.exec(code); 58 | 59 | if (!found?.[0]) { 60 | return; 61 | } 62 | 63 | if (found?.[0].includes('onBeforeMount')) { 64 | return; 65 | } 66 | 67 | return found.index + found[0].search('{') + 1; 68 | } 69 | 70 | export default function aboutYouAddonLoader({ 71 | addOnId 72 | }: { addOnId: string }): Plugin { 73 | return { 74 | enforce: 'pre', 75 | 76 | name: 'about-you-global-window-event-loader', 77 | 78 | transform(code, id) { 79 | if (!/App\.vue($|\?)/.test(id)) { 80 | return; 81 | } 82 | 83 | if (id.includes('type=style') || id.includes('type=script')) { 84 | return; 85 | } 86 | 87 | const setupFunctionStartIndex = getSetupFunctionIndex(code); 88 | const onBeforeMountImportIndex = getBeforeMountImportIndex(code); 89 | 90 | if (!setupFunctionStartIndex) { 91 | throw new Error('App.vue does not have a setup method! Please add a setup method.'); 92 | } 93 | 94 | const newCode = new MagicString(code) 95 | 96 | newCode.appendLeft(setupFunctionStartIndex, windowGlobalEventHandlerCode(addOnId)); 97 | 98 | if (onBeforeMountImportIndex) { 99 | newCode.appendLeft(onBeforeMountImportIndex, 'onBeforeMount, ') 100 | } 101 | 102 | return { code: newCode.toString()}; 103 | } 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /src/composables/useFetchProductsQuery.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies. 3 | */ 4 | import { Ref } from 'vue'; 5 | import { useQuery } from '@tanstack/vue-query'; 6 | 7 | /** 8 | * Internal dependencies. 9 | */ 10 | import { startTimeout, wrapInRef } from '@/utils'; 11 | import { Product, ProductStatus } from '@/types/Product'; 12 | import products from '@/mocks/products'; 13 | 14 | export type ProductsFilters = { 15 | search?: string, 16 | name?: string; 17 | status?: ProductStatus[]; 18 | }; 19 | 20 | export type ProductsPaginationData = { 21 | total: number; 22 | lastPage: number; 23 | currentPage: number; 24 | hasNextPage: boolean; 25 | products: Product[]; 26 | }; 27 | 28 | export type UseFetchProductsQueryOptions = { 29 | page?: Ref