├── .nuxtrc ├── .eslintignore ├── playground ├── app.vue ├── package.json ├── pages │ ├── settings.vue │ ├── products │ │ └── [slug].vue │ └── index.vue └── nuxt.config.ts ├── tsconfig.json ├── .gitattributes ├── .eslintrc ├── src ├── runtime │ ├── composables │ │ ├── useSwell.ts │ │ ├── useSwellCategories.ts │ │ ├── useSwellProduct.ts │ │ └── useSwellProducts.ts │ └── plugin.ts ├── types │ ├── swell-extended.ts │ └── swell.ts └── module.ts ├── .editorconfig ├── .gitignore ├── .github └── workflows │ └── npm-publish.yml ├── package.json └── README.md /.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=false 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "my-module-playground" 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs/eslint-config-typescript" 4 | ], 5 | "rules": { 6 | "@typescript-eslint/no-unused-vars": [ 7 | "off" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/runtime/composables/useSwell.ts: -------------------------------------------------------------------------------- 1 | import { useNuxtApp } from '#app' 2 | import { Swell } from '../../types/swell' 3 | 4 | export default function (): Swell { 5 | const nuxtApp = useNuxtApp() 6 | return nuxtApp.$swell 7 | } 8 | -------------------------------------------------------------------------------- /playground/pages/settings.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config' 2 | import Swell from '..' 3 | 4 | export default defineNuxtConfig({ 5 | modules: [ 6 | Swell 7 | ], 8 | swell: { 9 | storeId: process.env.STORE_ID, 10 | apiKey: process.env.SWELL_PUBLIC_KEY 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /src/runtime/plugin.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtPlugin, useRuntimeConfig } from '#app' 2 | import swell from 'swell-js' 3 | 4 | export default defineNuxtPlugin((nuxtApp) => { 5 | const { storeId, apiKey, options } = useRuntimeConfig().public.swell 6 | swell.init(storeId, apiKey, options) 7 | nuxtApp.provide('swell', swell) 8 | }) 9 | -------------------------------------------------------------------------------- /playground/pages/products/[slug].vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .vercel_build_output 23 | .build-* 24 | .env 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode 38 | 39 | # Intellij idea 40 | *.iml 41 | .idea 42 | 43 | # OSX 44 | .DS_Store 45 | .AppleDouble 46 | .LSOverride 47 | .AppleDB 48 | .AppleDesktop 49 | Network Trash Folder 50 | Temporary Items 51 | .apdisk 52 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | workflow_dispatch: 8 | release: 9 | types: [created] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: '16.x' 19 | registry-url: 'https://registry.npmjs.org' 20 | - run: yarn 21 | - run: yarn publish 22 | env: 23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}} 24 | -------------------------------------------------------------------------------- /src/runtime/composables/useSwellCategories.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '#app' 2 | import { computed, ComputedRef } from 'vue' 3 | import { Category } from 'swell-js/types/category' 4 | import useSwell from './useSwell' 5 | 6 | export type UseCategoriesReturnType = { 7 | categories: ComputedRef, 8 | fetchCategories: () => Promise 9 | } 10 | 11 | export default function (): UseCategoriesReturnType { 12 | const result = useState('categories', () => []) 13 | 14 | const fetch = async (): Promise => { 15 | const response = await useSwell().categories.get() 16 | result.value = response.results 17 | return result.value 18 | } 19 | 20 | return { 21 | categories: computed(() => result.value), 22 | fetchCategories: fetch 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/runtime/composables/useSwellProduct.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '#app' 2 | import { computed, ComputedRef } from 'vue' 3 | import { Product } from 'swell-js/types/product' 4 | import { Query } from '../../types/swell-extended' 5 | import useSwell from './useSwell' 6 | 7 | export type UseProductReturnType = { 8 | product: ComputedRef, 9 | fetch: (options: Query) => Promise 10 | } 11 | export default function (productId: string): UseProductReturnType { 12 | const result = useState(productId, () => null) 13 | 14 | const fetch = async (options: Query = {}): Promise => { 15 | result.value = await useSwell().products.get(productId, options) 16 | return result.value 17 | } 18 | 19 | return { 20 | product: computed(() => result.value), 21 | fetch 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/runtime/composables/useSwellProducts.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '#app' 2 | import type { ComputedRef } from 'vue' 3 | import { computed } from 'vue' 4 | import { Product } from 'swell-js/types/product' 5 | import { Query, SearchQuery, ResultsResponse } from '../../types/swell-extended' 6 | import useSwell from './useSwell' 7 | 8 | export type UseProductsReturnType = { 9 | list: ComputedRef>, 10 | fetch: (options: Query | SearchQuery) => Promise> 11 | } 12 | 13 | export default function (key: string): UseProductsReturnType { 14 | const result = useState>(key, () => ({ 15 | count: 0, 16 | results: [], 17 | page: -1 18 | })) 19 | 20 | const fetch = async (options: Query | SearchQuery = {}) => { 21 | result.value = await useSwell().products.list(options) 22 | return result.value 23 | } 24 | return { 25 | list: computed(() => result.value), 26 | fetch 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /playground/pages/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-swell", 3 | "version": "0.2.0", 4 | "license": "MIT", 5 | "type": "module", 6 | "repository": { 7 | "url": "https://github.com/markus-gx/nuxt-swell" 8 | }, 9 | "keywords": [ 10 | "nuxt", 11 | "vue", 12 | "swell", 13 | "ecommerce" 14 | ], 15 | "exports": { 16 | ".": { 17 | "import": "./dist/module.mjs", 18 | "require": "./dist/module.cjs" 19 | } 20 | }, 21 | "main": "./dist/module.cjs", 22 | "types": "./dist/types.d.ts", 23 | "files": [ 24 | "dist" 25 | ], 26 | "scripts": { 27 | "prepack": "nuxt-module-build", 28 | "dev": "nuxi dev playground", 29 | "dev:build": "nuxi build playground", 30 | "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground" 31 | }, 32 | "dependencies": { 33 | "@nuxt/kit": "^3.6.2", 34 | "swell-js": "^3.22.1" 35 | }, 36 | "devDependencies": { 37 | "@nuxt/module-builder": "^0.4.0", 38 | "@nuxt/schema": "^3.6.2", 39 | "@nuxtjs/eslint-config-typescript": "^12.0.0", 40 | "eslint": "^8.44.0", 41 | "nuxt": "^3.6.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/types/swell-extended.ts: -------------------------------------------------------------------------------- 1 | import { CardCamel } from 'swell-js/types/card/camel' 2 | import { Order } from 'swell-js/types/order' 3 | import { Subscription } from 'swell-js/types/subscription' 4 | import { AccountCamel } from 'swell-js/types/account/camel' 5 | import { AddressSnake } from 'swell-js/types/account/snake' 6 | 7 | export interface BaseModel { 8 | date_created?: string; 9 | date_updated?: string; 10 | id?: string; 11 | } 12 | 13 | export interface InitOptions { 14 | currency?: string; 15 | key?: string; 16 | locale?: string; 17 | previewContent?: boolean; 18 | session?: string; 19 | store?: string; 20 | timeout?: number; 21 | useCamelCase?: boolean; 22 | URL?: string; 23 | vaultUrl?: string; 24 | } 25 | 26 | export interface Query { 27 | limit?: number; 28 | page?: number; 29 | expand?: string[]; 30 | } 31 | 32 | export interface ProductQuery extends Query { 33 | category?: string; 34 | categories?: string[]; 35 | $filters?: unknown; 36 | } 37 | 38 | export interface SearchQuery extends Query { 39 | search: string; 40 | } 41 | 42 | export interface ResultsResponse { 43 | count: number; 44 | page: number; 45 | pages?: { 46 | [index: number]: { 47 | start: number; 48 | end: number; 49 | }; 50 | }; 51 | results: Array; 52 | } 53 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { fileURLToPath } from 'url' 3 | import { defineNuxtModule, addPlugin, addImportsDir, extendViteConfig } from '@nuxt/kit' 4 | import { InitOptions } from './types/swell-extended' 5 | 6 | export interface ModuleOptions { 7 | storeId?: string, 8 | apiKey?: string 9 | options?: InitOptions 10 | } 11 | 12 | export default defineNuxtModule({ 13 | meta: { 14 | name: 'nuxt-swell', 15 | configKey: 'swell' 16 | }, 17 | defaults: { 18 | storeId: undefined, 19 | apiKey: undefined 20 | }, 21 | setup (moduleOptions: ModuleOptions, nuxt) { 22 | if (!moduleOptions.apiKey) { 23 | throw new Error('[@nuxtjs/swell] Please provide a valid API Key.') 24 | } 25 | if (!moduleOptions.storeId) { 26 | throw new Error('[@nuxtjs/swell] Please provide a valid store id.') 27 | } 28 | const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) 29 | nuxt.options.build.transpile.push(runtimeDir) 30 | addPlugin(resolve(runtimeDir, 'plugin')) 31 | nuxt.options.runtimeConfig.public.swell = { 32 | storeId: moduleOptions.storeId, 33 | apiKey: moduleOptions.apiKey, 34 | options: { ...moduleOptions.options } 35 | } 36 | addImportsDir(resolve(runtimeDir, 'composables')) 37 | extendViteConfig((config) => { 38 | config.optimizeDeps = { 39 | include: ['lodash/camelCase', 'lodash/cloneDeep', 'deepmerge', 'lodash/find', 'lodash/findIndex', 'lodash/get', 40 | 'lodash/isEqual', 'lodash/round', 'lodash/set', 'lodash/snakeCase', 'qs', 'lodash/uniq', 41 | 'lodash/isEmpty', 'lodash/map', 'lodash/reduce', 'lodash/toLower', 'lodash/toNumber'] 42 | } 43 | }) 44 | console.info('[🚀]nuxt-swell launched successfully.') 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nuxt-swell 2 | 3 | Among a few other [features](#features) this module wraps the [Universal JavaScript client for Swell's Frontend API](https://github.com/swellstores/swell-js) , 4 | providing client-safe access to store and customer data. 5 | 6 | [Swell](https://www.swell.is) is a customizable, API-first platform for powering modern B2C/B2B shopping experiences and marketplaces. Build and connect anything using your favorite technologies, and provide admins with an easy to use dashboard. 7 | 8 | ## Features 9 | - Nuxt 3 Ready 10 | - Useful Composables to fetch data from swell e-commerce 11 | - TypeScript Support 12 | 13 | ## Setup 14 | ```shell 15 | yarn add nuxt-swell # yarn 16 | npm i nuxt-swell # npm 17 | ``` 18 | 19 | ## Usage 20 | 21 | Inside your `nuxt.config.ts` you just need to add the following piece of code and enter the credentials of your store to make everything work out of the box. 22 | 23 | ```ts 24 | export default defineNuxtConfig({ 25 | modules: [ 26 | "nuxt-swell" 27 | ], 28 | swell: { 29 | storeId: "YOUR_STORE_ID", 30 | apiKey: "YOUR_PUBLIC_ACCESS_TOKEN", 31 | options: { // optional 32 | useCamelCase: true // Default is false change it to true to switch to camelCase responses 33 | } 34 | } 35 | }) 36 | ``` 37 | 38 | ### Composables 39 | This modules consits of the following composables to make your life easier 😉 40 | 41 | #### Use the Swell.JS SDK 42 | Inside your setup function you can just call the composable `useSwell()` and next to first class typescript support, 43 | you are able to call allavailable functions from the Swell.js SDK. 44 | 45 | 📖 [View Swell.js Documentation for basic usage](https://developers.swell.is/frontend-api/) 46 | 47 | ```vue 48 | 51 | ``` 52 | 53 | #### Fetch A Single Product 54 | 55 | To retrieve a certain product by `id` or `slug` and fetch it async with Nuxt 3 `useAsyncData` you can just call 56 | 57 | ```vue 58 | 62 | ``` 63 | Everything else is handled by the composable, it just returns you the result as `ComputedRef` and a fetch method to actually fetch the product. 64 | 65 | #### Fetch A Product List 66 | Same goes for fetching multiple Products at once. 67 | Enter your desired `key` as parameter to make the result available across your Nuxt 3 Application with ease and 68 | pass optional `options` like a SearchQuery as `fetch()` parameter. 69 | 70 | You can destructure this composable as well and receive a `list` object and a `fetch` method. Where you can access your products 71 | after fetching by just using `list.results`. 72 | 73 | ```vue 74 | 82 | 83 | 91 | ``` 92 | 93 | #### Fetch All Categories 94 | To fetch all categories from swell just destructure the composable and use the result or `categories` as `ComputedRef`. 95 | 96 | ```vue 97 |
    98 |
  • 99 | {{ c.name }} 100 |
  • 101 |
102 | 103 | 107 | ``` 108 | 109 | 110 | **And more to come soon!** 111 | 112 | ## Development 113 | * Run `npm run dev:prepare` to generate type stubs 114 | * Use `npm run dev` to start playground in development mode. 115 | 116 | ## Credits 117 | - [Gus](https://github.com/gusfune) for kicking of the DefinitelyTyped swell-js package 118 | - [Jakub](https://github.com/Baroshem) for reviewing and giving me hints for my first nuxt module 😉 119 | -------------------------------------------------------------------------------- /src/types/swell.ts: -------------------------------------------------------------------------------- 1 | import { Account, Address, PasswordTokenInput } from 'swell-js/types/account' 2 | import { Card, InputCreateToken, TokenResponse } from 'swell-js/types/card' 3 | import { Order } from 'swell-js/types/order' 4 | import { Attribute } from 'swell-js/types/attribute' 5 | import { Cart, CartItem } from 'swell-js/types/cart' 6 | import { Settings } from 'swell-js/types/settings' 7 | import { Category } from 'swell-js/types/category' 8 | import { EnabledCurrency, FormatInput, SelectCurrencyReturn } from 'swell-js/types/currency' 9 | import { Locale } from 'swell-js/types/locale' 10 | import { 11 | InputPaymentElementCard, 12 | InputPaymentElementIdeal, 13 | InputPaymentElementPaypal, InputPaymentRedirect, 14 | Payment 15 | } from 'swell-js/types/payment' 16 | import { FlexibleProductInput, PriceRange, Product } from 'swell-js/types/product' 17 | import { Subscription } from 'swell-js/types/subscription' 18 | import { InitOptions, ResultsResponse } from './swell-extended' 19 | 20 | export interface Swell { 21 | init (storeId: string, publicKey: string, options?: InitOptions): void 22 | 23 | get (url: string, query: object): Promise 24 | 25 | put (url: string, query: object): Promise 26 | 27 | post (url: string, query: object): Promise 28 | 29 | account: { 30 | create (input: Account): Promise; 31 | createAddress (input: Address): Promise
; 32 | createCard (input: Card): Promise; 33 | deleteAddress (id: string): Promise
; 34 | deleteCard (id: string): Promise; 35 | get (): Promise; 36 | getAddresses (input: object): Promise
; 37 | getCards (input: object): Promise; 38 | getOrder (id: string): Promise; 39 | getOrders (): ResultsResponse>; 40 | listAddresses (): Promise; 41 | listCards (): Promise; 42 | listOrders (input?: object): ResultsResponse>; 43 | login ( 44 | user: string, 45 | password: string | PasswordTokenInput, 46 | ): Promise; 47 | logout (): Promise; 48 | recover (input: object): Promise; 49 | update (input: Account): Promise; 50 | updateAddress (id: string, input: Address): Promise
; 51 | } 52 | attributes: { 53 | get (input: string): Promise; 54 | list (input?: object): Promise>; 55 | get (): Promise>; 56 | } 57 | card: { 58 | createToken (input: InputCreateToken): Promise; 59 | validateCVC (input: string): boolean; 60 | validateExpiry (input: string): boolean; 61 | validateNumber (input: string): boolean; 62 | } 63 | cart: { 64 | addItem (input: CartItem): Promise; 65 | get (input?: string): Promise; 66 | getSettings (): Promise; 67 | getShippingRates (): Promise; // TODO: add shipping Rate object 68 | removeItem (input: string): Promise; 69 | recover (input: string): Promise; 70 | setItems (input: [CartItem]): Promise; 71 | updateItem (id: string, input: CartItem): Promise; 72 | update (input: object): Promise; 73 | } 74 | categories: { 75 | get (input: string): Promise; 76 | list (input?: object): Promise>; 77 | get (): Promise>; 78 | } 79 | currency: { 80 | format (input: number, format: FormatInput): string; 81 | list (): Promise>; 82 | select (input: string): Promise; 83 | selected (): Promise; 84 | } 85 | locale: { 86 | get (): Promise; 87 | list (): Promise>; 88 | selected (): Promise; 89 | select (locale: string): Promise<{ locale: string }>; 90 | set (code: string): Promise; 91 | } 92 | payment: { 93 | authenticate (id: string): Promise; 94 | authorizeGateway (input: object): Promise; 95 | createElements (input: { 96 | card?: InputPaymentElementCard; 97 | paypal?: InputPaymentElementPaypal; 98 | ideal?: InputPaymentElementIdeal; 99 | }): Promise; 100 | createIntent (input: { 101 | gateway: string; 102 | intent: object; 103 | }): Promise; 104 | get (input: string): Promise; 105 | handleRedirect (input: { 106 | card?: InputPaymentRedirect; 107 | paysafecard?: InputPaymentRedirect; 108 | klarna?: InputPaymentRedirect; 109 | }): Promise; 110 | tokenize (input: { 111 | card?: object; 112 | ideal?: object; 113 | klarna?: object; 114 | bancontact?: object; 115 | paysafecard?: object; 116 | }): Promise; 117 | updateIntent (input: { 118 | gateway: string; 119 | intent: object; 120 | }): Promise; 121 | } 122 | products: { 123 | categories ( 124 | products: FlexibleProductInput, 125 | ): Promise>; 126 | filters (products: FlexibleProductInput): Promise; 127 | filterableAttributeFilters ( 128 | products: Array, 129 | options?: object, 130 | ): Array; 131 | get (id: string, input?: object): Promise; 132 | list (input?: object): Promise>; 133 | priceRange (product: FlexibleProductInput): PriceRange; 134 | variation (product: Product, options: object): Promise; 135 | } 136 | settings: { 137 | get ( 138 | id?: string, 139 | def?: string | number | Settings, 140 | ): Promise; 141 | getCurrentLocale (): Promise; 142 | getStoreLocale (): Promise; 143 | getStoreLocales (): Promise>; 144 | load (): Promise | null; 145 | menus (input?: string): Promise; 146 | payments ( 147 | id?: string, 148 | def?: string | number | Settings, 149 | ): Promise; 150 | subscriptions ( 151 | id?: string, 152 | def?: string | number | Settings, 153 | ): Promise; 154 | session ( 155 | id?: string, 156 | def?: string | number | Settings, 157 | ): Promise; 158 | } 159 | subscriptions: { 160 | addItem (id: string, input: object): Promise; 161 | create (input: object): Promise; 162 | get (id: string): Promise; 163 | list (): Promise>; 164 | removeItem (id: string, itemId: string): Promise; 165 | update (id: string, input: object): Promise; 166 | updateItem ( 167 | id: string, 168 | itemId: string, 169 | input: any, 170 | ): Promise; 171 | } 172 | } 173 | --------------------------------------------------------------------------------