├── README.md ├── core ├── .gitignore ├── package.json ├── pnpm-lock.yaml ├── src │ ├── cart │ │ ├── data │ │ │ ├── CartInMemoryRepository.ts │ │ │ └── index.ts │ │ ├── domain │ │ │ ├── entities │ │ │ │ ├── Cart.ts │ │ │ │ ├── CartItem.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── repositories │ │ │ │ ├── CartRepository.ts │ │ │ │ └── index.ts │ │ │ └── usecases │ │ │ │ ├── AddProductToCartUsecase.ts │ │ │ │ ├── EditQuantityOfCartItemUsecase.ts │ │ │ │ ├── GetCartUsecase.ts │ │ │ │ ├── RemoveItemFromCartUsecase.ts │ │ │ │ └── index.ts │ │ ├── index.ts │ │ └── presentation │ │ │ ├── CartPloc.ts │ │ │ ├── CartState.ts │ │ │ └── index.ts │ ├── common │ │ ├── dependencies │ │ │ ├── DependenciesLocator.ts │ │ │ └── index.ts │ │ ├── domain │ │ │ ├── DataError.ts │ │ │ ├── Either.ts │ │ │ └── EitherAsync.ts │ │ ├── index.ts │ │ └── presentation │ │ │ ├── Ploc.ts │ │ │ └── index.ts │ ├── index.ts │ └── products │ │ ├── data │ │ ├── ProductInMemoryRepository.ts │ │ └── index.ts │ │ ├── domain │ │ ├── entities │ │ │ ├── Product.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── repositories │ │ │ ├── ProductRepository.ts │ │ │ └── index.ts │ │ └── usecases │ │ │ ├── GetProductsUsecase.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── presentation │ │ ├── ProductsPloc.ts │ │ ├── ProductsState.ts │ │ └── index.ts ├── tsconfig.json └── tsconfig.tsbuildinfo └── vue3-app ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── auto-imports.d.ts ├── components.d.ts ├── index.html ├── locales ├── README.md ├── en.yml └── es.yml ├── netlify.toml ├── package.json ├── pnpm-lock.yaml ├── public ├── _headers ├── favicon.svg ├── pwa-192x192.png ├── pwa-512x512.png ├── robots.txt └── safari-pinned-tab.svg ├── src ├── App.vue ├── cart │ ├── components │ │ └── CartItem.vue │ └── pages │ │ └── cart │ │ └── index.vue ├── common │ ├── components │ │ ├── Footer.vue │ │ ├── README.md │ │ ├── VButton.vue │ │ ├── VButtonText.vue │ │ ├── VDescription.vue │ │ ├── VInput.vue │ │ └── VTitleLg.vue │ ├── layouts │ │ ├── 404.vue │ │ ├── README.md │ │ ├── default.vue │ │ └── home.vue │ ├── logic │ │ ├── dark.ts │ │ ├── format-currency.ts │ │ └── index.ts │ ├── modules │ │ ├── README.md │ │ ├── i18n.ts │ │ ├── nprogress.ts │ │ ├── pinia.ts │ │ └── pwa.ts │ ├── pages │ │ ├── README.md │ │ ├── [...all].vue │ │ ├── about.md │ │ └── index.vue │ ├── styles │ │ ├── main.css │ │ └── markdown.css │ └── use-ploc-state.ts ├── main.ts ├── products │ ├── components │ │ └── ProductItem.vue │ └── pages │ │ └── products │ │ └── index.vue ├── shims.d.ts └── types.ts ├── tsconfig.json ├── vite.config.ts ├── windi.config.ts └── yalc.lock /README.md: -------------------------------------------------------------------------------- 1 | # Separating business logic from UI frameworks (React, Vue, Svelte, Angular, etc.) using clean architecture pattern 2 | 3 | ![Moving away from the framework](https://raw.githubusercontent.com/shamscorner/images/main/bloc-pattern.png) 4 | 5 | A Vue 3 project with Typescript to mimic the Bloc architecture pattern (Flutter way). If you are not familiar with Vue, you can still use the `core` package to whatever frontend framework you like. 6 | 7 | If you want to create a frontend application, there are many frameworks to choose from (React, Vue.js, Angular, Svelte, etc.). Let's say you select one of these frameworks and start developing your project. But on the long run if you want to change with something else, you have to change the entire codebase from start to finish. And that's why moving away from frameworks and separating the core business logics will give you the ultimate power to avoid those mistakes in the future. 8 | 9 | I have read this awesome blog post - [moving away from framework on frontend using clean architecture](http://xurxodev.com/frontend-clean_architecture/) and decided to use that approach on my own. Turned out, implementing the clean architecture pattern on (Vue 3 + Typescript) project is not that big of a deal. 10 | 11 | ## Overview 12 | 13 | In this demo project you will find 2 folders, 14 | 15 | - `core` - the core business logic 16 | - `vue3-app` - the UI framework to show the frontend views 17 | 18 | This is basically a `products - cart` project where you will see the following functionalities, 19 | 20 | - add products to cart 21 | - remove products from cart 22 | - filters products by their title 23 | - adjust the product's quantity 24 | - finally, you will see the total number of product items and their total price. 25 | 26 | ## Project Structures 27 | 28 | ### Inside the `core` folder 29 | 30 | Inside the `src` folder, you will find 3 folders. One for the `common` functionalities and the other two are responsible for `products` and `cart` management respectively. The breakdown is as follows, 31 | 32 | - **products** 33 | - data 34 | - data level repositories 35 | - in memory repositories 36 | - API level repositories 37 | - data models 38 | - domain 39 | - entities 40 | - product 41 | - product item 42 | - repositories 43 | - product repository 44 | - usecases 45 | - get products usecase 46 | - presentation 47 | - ProductPloc 48 | - ProductState 49 | - **cart** 50 | - data 51 | - data level repositories 52 | - in memory repositories 53 | - API level repositories 54 | - data models 55 | - domain 56 | - entities 57 | - cart 58 | - cart item 59 | - repositories 60 | - cart repository 61 | - usecases 62 | - add product to cart usecase 63 | - edit quantity of cart item usecase 64 | - get cart usecase 65 | - remove item from cart usecase 66 | - presentation 67 | - CartPloc 68 | - CartState 69 | - **common** 70 | - dependencies 71 | - dependencies locator 72 | - domain 73 | - DataError 74 | - Either 75 | - EitherAsync 76 | - presentation 77 | - Ploc 78 | 79 | **note:** When you use BLoC with Clean Architecture, it makes more sense to call them PLoC (Presentation Logic Component). For more details, check out this article - [moving away from framework on frontend using clean architecture](http://xurxodev.com/frontend-clean_architecture/) 80 | 81 | ### Inside the `vue3-app` folder 82 | 83 | This Vue 3 project is made with the "[Vitesse](https://github.com/antfu/vitesse)" starter template created by Anthony Fu (Vue core team member) but configured with the clean architectural pattern instead of monolithic architecture. If you want to use this modified version, check out [this repository](https://github.com/shamscorner/vitesse-stackter-clean-architect). 84 | 85 | Here you will find the following folders under `src/`, 86 | 87 | - cart 88 | - components 89 | - pages 90 | - common 91 | - components 92 | - layouts 93 | - logic 94 | - modules 95 | - pages 96 | - styles 97 | - use-ploc-state 98 | - products 99 | - components 100 | - pages 101 | 102 | As you can see, this is very similar with the base folder structure of the `core` setup mentioned earlier. 103 | 104 | ## Why move away from the framework? 105 | 106 | These are the advantages you will encounter while developing your project. 107 | 108 | - It is well structured and you will find the attributes you are looking for pretty easily. 109 | - Everything is modular design by its nature. 110 | - You can infinitely introduce new feature without breaking any changes. 111 | - UI library or framework is not tightly coupled to the business logic. So if you want to change your frontend framework from **vue** to **react** later for some reason, just replace the UI part and you are good to go. 112 | 113 | ## How to run this demo project? 114 | 115 | **step 1** - clone this repository 116 | 117 | ``` 118 | $ git clone https://github.com/shamscorner/bloc-vue-3-clean-pattern.git [your repo name] 119 | ``` 120 | 121 | **step 2** - setup `core` 122 | 123 | ``` 124 | $ cd core 125 | $ pnpm install 126 | $ pnpm build 127 | $ yalc publish 128 | ``` 129 | 130 | **note:** install [pnpm](https://pnpm.io/) and [yalc](https://www.npmjs.com/package/yalc) 131 | 132 | **step 3** - setup `vue app` 133 | 134 | ``` 135 | $ cd vue3-app 136 | $ pnpm install 137 | $ yalc add pkg-bloc-core 138 | $ pnpm install 139 | ``` 140 | 141 | Checkout [this article](https://www.viget.com/articles/how-to-use-local-unpublished-node-packages-as-project-dependencies/) if you want some more in-depth explanation. 142 | 143 | **step 4** - running the app 144 | 145 | ``` 146 | $ pnpm dev 147 | ``` 148 | 149 | ### If you want to make some changes to the `core` folder, then you have to do the following after each changes. 150 | 151 | ``` 152 | $ cd core 153 | $ pnpm build 154 | $ yalc push 155 | ``` 156 | 157 | and in the vue app, restart the server. If that doesn't work, then run this command as well. 158 | 159 | ``` 160 | $ cd vue3-app 161 | $ yalc update 162 | ``` 163 | 164 | Happy Coding :) 165 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkg-bloc-core", 3 | "version": "1.0.0", 4 | "description": "The core logic", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build": "tsc --build", 8 | "start": "tsc --watch", 9 | "test": "jest" 10 | }, 11 | "keywords": [], 12 | "author": "Shamim Hossain ", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@types/jest": "^27.0.2", 16 | "jest": "^27.3.1", 17 | "ts-jest": "^27.0.7" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/src/cart/data/CartInMemoryRepository.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from '../../common/domain/DataError'; 2 | import { Either } from '../../common/domain/Either'; 3 | import { Cart } from '../domain/entities/Cart'; 4 | import { CartRepository } from '../domain/repositories/CartRepository'; 5 | 6 | export class CartInMemoryRepository implements CartRepository { 7 | cart = new Cart([ 8 | { 9 | id: '1', 10 | image: 11 | 'https://images-na.ssl-images-amazon.com/images/I/71Y1S1m-QAL._AC_UY879_.jpg', 12 | title: 'Element Blazin LS tee Shirt, Hombre', 13 | price: 19.95, 14 | quantity: 3, 15 | }, 16 | { 17 | id: '3', 18 | image: "https://m.media-amazon.com/images/I/81ZYZ9yl1hL._AC_UL640_FMwebp_QL65_.jpg", 19 | title: 'Element Skater Backpack Mohave 15" Saison ', 20 | price: 52.45, 21 | quantity: 1 22 | }, 23 | ]); 24 | 25 | get(): Promise> { 26 | return new Promise((resolve, _reject) => { 27 | setTimeout(() => { 28 | try { 29 | resolve(Either.right(this.cart)); 30 | } catch (error: any) { 31 | resolve(Either.left({ kind: 'UnexpectedError', error })); 32 | } 33 | }, 100); 34 | }); 35 | } 36 | 37 | save(cart: Cart): Promise> { 38 | return new Promise((resolve, _reject) => { 39 | setTimeout(() => { 40 | try { 41 | this.cart = cart; 42 | resolve(Either.right(true)); 43 | } catch (error: any) { 44 | resolve(Either.left({ kind: 'UnexpectedError', error })); 45 | } 46 | }, 100); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /core/src/cart/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CartInMemoryRepository'; -------------------------------------------------------------------------------- /core/src/cart/domain/entities/Cart.ts: -------------------------------------------------------------------------------- 1 | import { CartItem } from "./CartItem"; 2 | 3 | type TotalPrice = number; 4 | type TotalItems = number; 5 | 6 | export class Cart { 7 | items: readonly CartItem[]; 8 | readonly totalPrice: TotalPrice; 9 | readonly totalItems: TotalItems; 10 | 11 | constructor(items: CartItem[]) { 12 | this.items = items; 13 | this.totalPrice = this.calculateTotalPrice(items); 14 | this.totalItems = this.calculateTotalItems(items); 15 | } 16 | 17 | static createEmpty(): Cart { 18 | return new Cart([]); 19 | } 20 | 21 | addItem(item: CartItem): Cart { 22 | const existedItem = this.items.find((i) => i.id === item.id); 23 | 24 | if (existedItem) { 25 | const newItems = this.items.map((oldItem) => { 26 | if (oldItem.id === item.id) { 27 | return { ...oldItem, quantity: oldItem.quantity + item.quantity }; 28 | } else { 29 | return oldItem; 30 | } 31 | }); 32 | 33 | return new Cart(newItems); 34 | } else { 35 | const newItems = [...this.items, item]; 36 | 37 | return new Cart(newItems); 38 | } 39 | } 40 | 41 | removeItem(itemId: string): Cart { 42 | const newItems = this.items.filter((i) => i.id !== itemId); 43 | 44 | return new Cart(newItems); 45 | } 46 | 47 | editItem(itemId: string, quantity: number): Cart { 48 | const newItems = this.items.map((oldItem) => { 49 | if (oldItem.id === itemId) { 50 | return { ...oldItem, quantity: quantity }; 51 | } else { 52 | return oldItem; 53 | } 54 | }); 55 | 56 | return new Cart(newItems); 57 | } 58 | 59 | private calculateTotalPrice(items: CartItem[]): TotalPrice { 60 | return +items 61 | .reduce( 62 | (accumulator, item) => accumulator + item.quantity * item.price, 63 | 0 64 | ) 65 | .toFixed(2); 66 | } 67 | 68 | private calculateTotalItems(items: CartItem[]): TotalItems { 69 | return +items.reduce((accumulator, item) => accumulator + item.quantity, 0); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /core/src/cart/domain/entities/CartItem.ts: -------------------------------------------------------------------------------- 1 | import { Title, Image, Price } from "../../../products/domain/entities/Product"; 2 | 3 | type CartItemId = string; 4 | type Quantity = number; 5 | 6 | export interface CartItem { 7 | readonly id: CartItemId; 8 | readonly image: Image; 9 | readonly title: Title; 10 | readonly price: Price; 11 | readonly quantity: Quantity; 12 | } -------------------------------------------------------------------------------- /core/src/cart/domain/entities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CartItem'; 2 | export * from './Cart'; -------------------------------------------------------------------------------- /core/src/cart/domain/index.ts: -------------------------------------------------------------------------------- 1 | export * from './entities'; 2 | export * from './repositories'; 3 | export * from './usecases'; -------------------------------------------------------------------------------- /core/src/cart/domain/repositories/CartRepository.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from "../../../common/domain/DataError"; 2 | import { Either } from "../../../common/domain/Either"; 3 | import { Cart } from "../entities/Cart"; 4 | 5 | export interface CartRepository { 6 | get(): Promise>; 7 | save(cart: Cart): Promise>; 8 | } 9 | -------------------------------------------------------------------------------- /core/src/cart/domain/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CartRepository'; -------------------------------------------------------------------------------- /core/src/cart/domain/usecases/AddProductToCartUsecase.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from "../../../common/domain/DataError"; 2 | import { Either } from "../../../common/domain/Either"; 3 | import { EitherAsync } from "../../../common/domain/EitherAsync"; 4 | import { Product } from "../../../products/domain/entities/Product"; 5 | import { Cart } from "../entities/Cart"; 6 | import { CartRepository } from "../repositories/CartRepository"; 7 | 8 | export class AddProductToCartUsecase { 9 | private cartRepository: CartRepository; 10 | 11 | constructor(cartRepository: CartRepository) { 12 | this.cartRepository = cartRepository; 13 | } 14 | 15 | async execute(product: Product): Promise> { 16 | const cartResult = EitherAsync.fromPromise(this.cartRepository.get()); 17 | 18 | return cartResult 19 | .flatMap(async (cart) => { 20 | const cartItem = { 21 | id: product.id, 22 | image: product.image, 23 | title: product.title, 24 | price: product.price, 25 | quantity: 1, 26 | }; 27 | 28 | const editedCart = cart.addItem(cartItem); 29 | 30 | const saveResult = await this.cartRepository.save(editedCart); 31 | 32 | return saveResult.map(() => editedCart); 33 | }) 34 | .run(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/cart/domain/usecases/EditQuantityOfCartItemUsecase.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from "../../../common/domain/DataError"; 2 | import { Either } from "../../../common/domain/Either"; 3 | import { EitherAsync } from "../../../common/domain/EitherAsync"; 4 | import { Cart } from "../entities/Cart"; 5 | import { CartRepository } from "../repositories/CartRepository"; 6 | 7 | export class EditQuantityOfCartItemUsecase { 8 | private cartRepository: CartRepository; 9 | 10 | constructor(cartRepository: CartRepository) { 11 | this.cartRepository = cartRepository; 12 | } 13 | 14 | async execute( 15 | itemId: string, 16 | quantity: number 17 | ): Promise> { 18 | const cartResult = EitherAsync.fromPromise(this.cartRepository.get()); 19 | 20 | return cartResult 21 | .flatMap(async (cart) => { 22 | const editedCart = cart.editItem(itemId, quantity); 23 | 24 | const saveResult = await this.cartRepository.save(editedCart); 25 | 26 | return saveResult.map(() => editedCart); 27 | }) 28 | .run(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/cart/domain/usecases/GetCartUsecase.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from "../../../common/domain/DataError"; 2 | import { Either } from "../../../common/domain/Either"; 3 | import { Cart } from "../entities/Cart"; 4 | import { CartRepository } from "../repositories/CartRepository"; 5 | 6 | export class GetCartUsecase { 7 | private cartRepository: CartRepository; 8 | 9 | constructor(cartRepository: CartRepository) { 10 | this.cartRepository = cartRepository; 11 | } 12 | 13 | execute(): Promise> { 14 | return this.cartRepository.get(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/src/cart/domain/usecases/RemoveItemFromCartUsecase.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from "../../../common/domain/DataError"; 2 | import { Either } from "../../../common/domain/Either"; 3 | import { EitherAsync } from "../../../common/domain/EitherAsync"; 4 | import { Cart } from "../entities/Cart"; 5 | import { CartRepository } from "../repositories/CartRepository"; 6 | 7 | export class RemoveItemFromCartUsecase { 8 | private cartRepository: CartRepository; 9 | 10 | constructor(cartRepository: CartRepository) { 11 | this.cartRepository = cartRepository; 12 | } 13 | 14 | async execute(itemId: string): Promise> { 15 | const cartResult = EitherAsync.fromPromise(this.cartRepository.get()); 16 | 17 | return cartResult 18 | .flatMap(async (cart) => { 19 | const editedCart = cart.removeItem(itemId); 20 | 21 | const saveResult = await this.cartRepository.save(editedCart); 22 | 23 | return saveResult.map(() => editedCart); 24 | }) 25 | .run(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/cart/domain/usecases/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GetCartUsecase'; 2 | export * from './AddProductToCartUsecase'; 3 | export * from './EditQuantityOfCartItemUsecase'; 4 | export * from './RemoveItemFromCartUsecase'; -------------------------------------------------------------------------------- /core/src/cart/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './domain'; 3 | export * from './presentation'; -------------------------------------------------------------------------------- /core/src/cart/presentation/CartPloc.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from '../../common/domain/DataError'; 2 | import { Ploc } from '../../common/presentation/Ploc'; 3 | import { Product } from '../../products/domain/entities/Product'; 4 | import { Cart } from '../domain/entities/Cart'; 5 | import { AddProductToCartUsecase } from '../domain/usecases/AddProductToCartUsecase'; 6 | import { EditQuantityOfCartItemUsecase } from '../domain/usecases/EditQuantityOfCartItemUsecase'; 7 | import { GetCartUsecase } from '../domain/usecases/GetCartUsecase'; 8 | import { RemoveItemFromCartUsecase } from '../domain/usecases/RemoveItemFromCartUsecase'; 9 | import { cartInitialState, CartItemState, CartState } from './CartState'; 10 | 11 | export class CartPloc extends Ploc { 12 | constructor( 13 | private getCartUsecase: GetCartUsecase, 14 | private addProductToCartUsecase: AddProductToCartUsecase, 15 | private removeItemFromCartUsecase: RemoveItemFromCartUsecase, 16 | private editQuantityOfCartItemUsecase: EditQuantityOfCartItemUsecase 17 | ) { 18 | super(cartInitialState); 19 | 20 | this.loadCart(); 21 | } 22 | 23 | closeCart() { 24 | this.changeState({ ...this.state, open: false }); 25 | } 26 | 27 | openCart() { 28 | this.changeState({ ...this.state, open: true }); 29 | } 30 | 31 | async removeCartItem(item: CartItemState) { 32 | const result = await this.removeItemFromCartUsecase.execute(item.id); 33 | 34 | result.fold( 35 | (error) => this.changeState(this.handleError(error)), 36 | (cart) => this.changeState(this.mapToUpdatedState(cart)) 37 | ); 38 | } 39 | 40 | async editQuantityCartItem(item: CartItemState, quantity: number) { 41 | const result = await this.editQuantityOfCartItemUsecase.execute( 42 | item.id, 43 | quantity 44 | ); 45 | 46 | result.fold( 47 | (error) => this.changeState(this.handleError(error)), 48 | (cart) => this.changeState(this.mapToUpdatedState(cart)) 49 | ); 50 | } 51 | 52 | async addProductToCart(product: Product) { 53 | const result = await this.addProductToCartUsecase.execute(product); 54 | 55 | result.fold( 56 | (error) => this.changeState(this.handleError(error)), 57 | (cart) => this.changeState(this.mapToUpdatedState(cart)) 58 | ); 59 | } 60 | 61 | private async loadCart() { 62 | const result = await this.getCartUsecase.execute(); 63 | 64 | result.fold( 65 | (error) => this.changeState(this.handleError(error)), 66 | (cart) => this.changeState(this.mapToUpdatedState(cart)) 67 | ); 68 | } 69 | 70 | mapToUpdatedState(cart: Cart): CartState { 71 | const formatOptions = { style: 'currency', currency: 'EUR' }; 72 | 73 | return { 74 | kind: 'UpdatedCartState', 75 | open: this.state.open, 76 | totalItems: cart.totalItems, 77 | totalPrice: cart.totalPrice.toLocaleString('es-ES', formatOptions), 78 | items: cart.items.map((cartItem) => { 79 | return { 80 | id: cartItem.id, 81 | image: cartItem.image, 82 | title: cartItem.title, 83 | price: cartItem.price.toLocaleString('es-ES', formatOptions), 84 | quantity: cartItem.quantity, 85 | }; 86 | }), 87 | }; 88 | } 89 | 90 | private handleError(error: DataError): CartState { 91 | switch (error.kind) { 92 | case 'UnexpectedError': { 93 | return { 94 | open: this.state.open, 95 | kind: 'ErrorCartState', 96 | error: 'Sorry, an error has ocurred. Please try later again', 97 | }; 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /core/src/cart/presentation/CartState.ts: -------------------------------------------------------------------------------- 1 | export interface CommonCartState { 2 | open: boolean; 3 | } 4 | 5 | export interface LoadingCartState { 6 | kind: "LoadingCartState"; 7 | } 8 | 9 | export interface UpdatedCartState { 10 | kind: "UpdatedCartState"; 11 | items: Array; 12 | totalPrice: string; 13 | totalItems: number; 14 | } 15 | 16 | export interface ErrorCartState { 17 | kind: "ErrorCartState"; 18 | error: string; 19 | } 20 | 21 | export type CartState = (LoadingCartState | UpdatedCartState | ErrorCartState) & 22 | CommonCartState; 23 | 24 | export interface CartItemState { 25 | id: string; 26 | image: string; 27 | title: string; 28 | price: string; 29 | quantity: number; 30 | } 31 | 32 | export const cartInitialState: CartState = { 33 | kind: "LoadingCartState", 34 | open: false, 35 | }; 36 | -------------------------------------------------------------------------------- /core/src/cart/presentation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CartPloc'; 2 | export * from './CartState'; -------------------------------------------------------------------------------- /core/src/common/dependencies/DependenciesLocator.ts: -------------------------------------------------------------------------------- 1 | import { CartInMemoryRepository } from "../../cart/data/CartInMemoryRepository"; 2 | import { AddProductToCartUsecase } from "../../cart/domain/usecases/AddProductToCartUsecase"; 3 | import { EditQuantityOfCartItemUsecase } from "../../cart/domain/usecases/EditQuantityOfCartItemUsecase"; 4 | import { GetCartUsecase } from "../../cart/domain/usecases/GetCartUsecase"; 5 | import { RemoveItemFromCartUsecase } from "../../cart/domain/usecases/RemoveItemFromCartUsecase"; 6 | import { CartPloc } from "../../cart/presentation/CartPloc"; 7 | import { ProductInMemoryRepository } from "../../products/data/ProductInMemoryRepository"; 8 | import { GetProductsUsecase } from "../../products/domain/usecases/GetProductsUsecase"; 9 | import { ProductsPloc } from "../../products/presentation/ProductsPloc"; 10 | 11 | function provideProductsPloc(): ProductsPloc { 12 | const productRepository = new ProductInMemoryRepository(); 13 | const getProductsUsecase = new GetProductsUsecase(productRepository); 14 | const productsPloc = new ProductsPloc(getProductsUsecase); 15 | 16 | return productsPloc; 17 | } 18 | 19 | function provideCartPloc(): CartPloc { 20 | const cartRepository = new CartInMemoryRepository(); 21 | const getCartUsecase = new GetCartUsecase(cartRepository); 22 | const addProductToCartUsecase = new AddProductToCartUsecase(cartRepository); 23 | const removeItemFromCartUsecase = new RemoveItemFromCartUsecase(cartRepository); 24 | const editQuantityOfCartItemUsecase = new EditQuantityOfCartItemUsecase(cartRepository); 25 | const cartPloc = new CartPloc( 26 | getCartUsecase, 27 | addProductToCartUsecase, 28 | removeItemFromCartUsecase, 29 | editQuantityOfCartItemUsecase 30 | ); 31 | 32 | return cartPloc; 33 | } 34 | 35 | export const dependenciesLocator = { 36 | provideProductsPloc, 37 | provideCartPloc, 38 | }; 39 | -------------------------------------------------------------------------------- /core/src/common/dependencies/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DependenciesLocator'; -------------------------------------------------------------------------------- /core/src/common/domain/DataError.ts: -------------------------------------------------------------------------------- 1 | export interface UnexpectedError { 2 | kind: 'UnexpectedError'; 3 | error: Error; 4 | } 5 | 6 | export type DataError = UnexpectedError; -------------------------------------------------------------------------------- /core/src/common/domain/Either.ts: -------------------------------------------------------------------------------- 1 | type Left = { kind: 'left'; leftValue: L }; 2 | type Right = { kind: 'right'; rightValue: R }; 3 | 4 | type EitherValue = Left | Right; 5 | 6 | export class Either { 7 | private constructor(private readonly value: EitherValue) {} 8 | 9 | isLeft(): boolean { 10 | return this.value.kind === 'left'; 11 | } 12 | isRight(): boolean { 13 | return this.value.kind === 'right'; 14 | } 15 | 16 | fold(leftFn: (left: L) => T, rightFn: (right: R) => T): T { 17 | switch (this.value.kind) { 18 | case 'left': 19 | return leftFn(this.value.leftValue); 20 | case 'right': 21 | return rightFn(this.value.rightValue); 22 | } 23 | } 24 | 25 | map(fn: (r: R) => T): Either { 26 | return this.flatMap(r => Either.right(fn(r))); 27 | } 28 | 29 | flatMap(fn: (right: R) => Either): Either { 30 | return this.fold( 31 | leftValue => Either.left(leftValue), 32 | rightValue => fn(rightValue) 33 | ); 34 | } 35 | 36 | mapLeft(fn: (l: L) => T): Either { 37 | return this.flatMapLeft(l => Either.left(fn(l))); 38 | } 39 | 40 | flatMapLeft(fn: (left: L) => Either): Either { 41 | return this.fold( 42 | leftValue => fn(leftValue), 43 | rightValue => Either.right(rightValue) 44 | ); 45 | } 46 | 47 | get(errorMessage?: string): R { 48 | return this.getOrThrow(errorMessage); 49 | } 50 | 51 | getOrThrow(errorMessage?: string): R { 52 | const throwFn = () => { 53 | throw Error( 54 | errorMessage 55 | ? errorMessage 56 | : 'An error has ocurred retrieving value: ' + JSON.stringify(this.value) 57 | ); 58 | }; 59 | 60 | return this.fold( 61 | () => throwFn(), 62 | rightValue => rightValue 63 | ); 64 | } 65 | 66 | getLeft(): L { 67 | const throwFn = () => { 68 | throw Error('The value is right: ' + JSON.stringify(this.value)); 69 | }; 70 | 71 | return this.fold( 72 | leftValue => leftValue, 73 | () => throwFn() 74 | ); 75 | } 76 | 77 | getOrElse(defaultValue: R): R { 78 | return this.fold( 79 | () => defaultValue, 80 | someValue => someValue 81 | ); 82 | } 83 | 84 | static left(value: L) { 85 | return new Either({ kind: 'left', leftValue: value }); 86 | } 87 | 88 | static right(value: R) { 89 | return new Either({ kind: 'right', rightValue: value }); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /core/src/common/domain/EitherAsync.ts: -------------------------------------------------------------------------------- 1 | import { Either } from './Either'; 2 | 3 | export class EitherAsync { 4 | private constructor(private readonly promiseValue: () => Promise>) {} 5 | 6 | map(fn: (r: R) => T): EitherAsync { 7 | return this.flatMap(async r => Either.right(fn(r))); 8 | } 9 | 10 | flatMap(fn: (right: R) => Promise>): EitherAsync { 11 | return new EitherAsync(async () => { 12 | const value = await this.promiseValue(); 13 | 14 | return value.fold( 15 | async rightValue => Either.left(rightValue), 16 | rightValue => fn(rightValue) 17 | ); 18 | }); 19 | } 20 | 21 | mapLeft(fn: (l: L) => T): EitherAsync { 22 | return this.flatMapLeft(async l => Either.left(fn(l))); 23 | } 24 | 25 | flatMapLeft(fn: (left: L) => Promise>): EitherAsync { 26 | return new EitherAsync(async () => { 27 | const value = await this.promiseValue(); 28 | 29 | return value.fold( 30 | leftValue => fn(leftValue), 31 | async rightValue => Either.right(rightValue) 32 | ); 33 | }); 34 | } 35 | 36 | run(): Promise> { 37 | return this.promiseValue(); 38 | } 39 | 40 | static fromEither(value: Either): EitherAsync { 41 | return new EitherAsync(() => Promise.resolve(value)); 42 | } 43 | 44 | static fromPromise(value: Promise>): EitherAsync { 45 | return new EitherAsync(() => value); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/src/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './presentation'; 2 | export * from './dependencies'; -------------------------------------------------------------------------------- /core/src/common/presentation/Ploc.ts: -------------------------------------------------------------------------------- 1 | type Subscription = (state: S) => void; 2 | 3 | export abstract class Ploc { 4 | private internalState: S; 5 | private listeners: Subscription[] = []; 6 | 7 | constructor(initialState: S) { 8 | this.internalState = initialState; 9 | } 10 | 11 | public get state(): S { 12 | return this.internalState; 13 | } 14 | 15 | changeState(state: S) { 16 | this.internalState = state; 17 | 18 | if (this.listeners.length > 0) { 19 | this.listeners.forEach(listener => listener(this.state)); 20 | } 21 | } 22 | 23 | subscribe(listener: Subscription) { 24 | this.listeners.push(listener); 25 | } 26 | 27 | unsubscribe(listener: Subscription) { 28 | const index = this.listeners.indexOf(listener); 29 | if (index > -1) { 30 | this.listeners.splice(index, 1); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /core/src/common/presentation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Ploc' -------------------------------------------------------------------------------- /core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cart'; 2 | export * from './common'; 3 | export * from './products'; -------------------------------------------------------------------------------- /core/src/products/data/ProductInMemoryRepository.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from "../../common/domain/DataError"; 2 | import { Either } from "../../common/domain/Either"; 3 | import { Product } from "../domain/entities/Product"; 4 | import { ProductRepository } from "../domain/repositories/ProductRepository"; 5 | 6 | const products = [ 7 | { 8 | id: "1", 9 | image: "https://images-na.ssl-images-amazon.com/images/I/71Y1S1m-QAL._AC_UY879_.jpg", 10 | title: "Element Blazin LS tee Shirt, Hombre", 11 | price: 19.95, 12 | }, 13 | { 14 | id: "3", 15 | image: "https://m.media-amazon.com/images/I/81ZYZ9yl1hL._AC_UL640_FMwebp_QL65_.jpg", 16 | title: 'Element Skater Backpack Mohave 15" Saison ', 17 | price: 52.45, 18 | }, 19 | { 20 | id: "4", 21 | image: "https://m.media-amazon.com/images/I/61-DwEh1zrL._AC_UL640_FMwebp_QL65_.jpg", 22 | title: "Element Indiana Logo N1SSA5ELP9", 23 | price: 18.9, 24 | }, 25 | { 26 | id: "5", 27 | image: "https://m.media-amazon.com/images/I/81SgdrnVNJL._AC_UL320_.jpg", 28 | title: "Basic Pocket Label Camisetas Element Hombre", 29 | price: 27.95, 30 | }, 31 | { 32 | id: "6", 33 | image: "https://m.media-amazon.com/images/I/81giLCXfxIL._AC_UL640_FMwebp_QL65_.jpg", 34 | title: "Element N2ssa2 Camiseta, Niños", 35 | price: 13.9, 36 | }, 37 | { 38 | id: "7", 39 | image: "https://m.media-amazon.com/images/I/61S2KdoGNnL._AC_UL320_.jpg", 40 | title: "Joint - Camiseta para Hombre Camiseta Element Hombre", 41 | price: 32.0, 42 | }, 43 | { 44 | id: "8", 45 | image: "https://m.media-amazon.com/images/I/7119OAEE+gL._AC_UL640_FMwebp_QL65_.jpg", 46 | title: "Element Alder Light 2 Tones", 47 | price: 68.35, 48 | }, 49 | { 50 | id: "9", 51 | image: "https://m.media-amazon.com/images/I/71dp5f24TbL._AC_UL640_FMwebp_QL65_.jpg", 52 | title: 'Element Skater Backpack Mohave 15" Season', 53 | price: 52.84, 54 | }, 55 | { 56 | id: "10", 57 | image: "https://m.media-amazon.com/images/I/71Kj-jV5v8L._AC_UL640_FMwebp_QL65_.jpg", 58 | title: "Element Vertical SS Camiseta, Niños", 59 | price: 13.9, 60 | }, 61 | { 62 | id: "11", 63 | image: "https://m.media-amazon.com/images/I/71jlppwpjmL._AC_UL640_FMwebp_QL65_.jpg", 64 | title: "Element Alder Heavy Puff TW Chaqueta, Hombre, Verde Oliva, M EU", 65 | price: 168.75, 66 | }, 67 | { 68 | id: "12", 69 | image: "https://m.media-amazon.com/images/I/71BSdq6OzDL._AC_UL640_FMwebp_QL65_.jpg", 70 | title: "Element Hombre Meridian Block Sudadera Mid Grey HTR", 71 | price: 47.5, 72 | }, 73 | { 74 | id: "13", 75 | image: "https://m.media-amazon.com/images/I/81RAeKF-8wL._AC_UL640_FMwebp_QL65_.jpg", 76 | title: "Element Sudadera - para Hombre", 77 | price: 64.94, 78 | }, 79 | { 80 | id: "14", 81 | image: "https://m.media-amazon.com/images/I/717tHbEHDnL._AC_UL640_FMwebp_QL65_.jpg", 82 | title: "Element Hombre Camiseta t-Shirt Signature", 83 | price: 29.84, 84 | }, 85 | { 86 | id: "15", 87 | image: "https://m.media-amazon.com/images/I/81rOs3LA0LL._AC_UL640_FMwebp_QL65_.jpg", 88 | title: "Element Section' Pre-Built Complete - 7.50\"", 89 | price: 99.0, 90 | }, 91 | { 92 | id: "16", 93 | image: "https://m.media-amazon.com/images/I/61-xQZORAKL._AC_UL640_FMwebp_QL65_.jpg", 94 | title: "Element Camiseta - para hombre", 95 | price: 27.06, 96 | }, 97 | { 98 | id: "17", 99 | image: "https://m.media-amazon.com/images/I/71RUdoglJML._AC_UL640_FMwebp_QL65_.jpg", 100 | title: "Element Alder Light", 101 | price: 86.52, 102 | }, 103 | { 104 | id: "18", 105 | image: "https://m.media-amazon.com/images/I/714tTmj4KvL._AC_UL640_FMwebp_QL65_.jpg", 106 | title: "Element Chaqueta Alder Puff TW Negro", 107 | price: 73.5, 108 | }, 109 | ]; 110 | 111 | export class ProductInMemoryRepository implements ProductRepository { 112 | get(filter: string): Promise> { 113 | return new Promise((resolve, _reject) => { 114 | setTimeout(() => { 115 | try { 116 | if (filter) { 117 | const filteredProducts = products.filter((p: Product) => { 118 | return p.title.toLowerCase().includes(filter.toLowerCase()); 119 | }); 120 | resolve(Either.right(filteredProducts)); 121 | } else { 122 | resolve(Either.right(products)); 123 | } 124 | } catch (error: any) { 125 | resolve(Either.left({ kind: 'UnexpectedError', error })); 126 | } 127 | }, 100); 128 | }); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /core/src/products/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ProductInMemoryRepository'; -------------------------------------------------------------------------------- /core/src/products/domain/entities/Product.ts: -------------------------------------------------------------------------------- 1 | export type ProductId = string; 2 | export type Image = string; 3 | export type Title = string; 4 | export type Price = number; 5 | 6 | export interface Product { 7 | id: ProductId; 8 | image: Image; 9 | title: Title; 10 | price: Price; 11 | } -------------------------------------------------------------------------------- /core/src/products/domain/entities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Product'; -------------------------------------------------------------------------------- /core/src/products/domain/index.ts: -------------------------------------------------------------------------------- 1 | export * from './entities'; 2 | export * from './repositories'; 3 | export * from './usecases'; -------------------------------------------------------------------------------- /core/src/products/domain/repositories/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from "../../../common/domain/DataError"; 2 | import { Either } from "../../../common/domain/Either"; 3 | import { Product } from "../entities/Product"; 4 | 5 | export interface ProductRepository { 6 | get(filter: string): Promise>; 7 | } 8 | -------------------------------------------------------------------------------- /core/src/products/domain/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ProductRepository'; -------------------------------------------------------------------------------- /core/src/products/domain/usecases/GetProductsUsecase.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from "../../../common/domain/DataError"; 2 | import { Either } from "../../../common/domain/Either"; 3 | import { Product } from "../entities/Product"; 4 | import { ProductRepository } from "../repositories/ProductRepository"; 5 | 6 | export class GetProductsUsecase { 7 | private productRepository: ProductRepository; 8 | 9 | constructor(productRepository: ProductRepository) { 10 | this.productRepository = productRepository; 11 | } 12 | 13 | execute(filter: string): Promise> { 14 | return this.productRepository.get(filter); 15 | } 16 | } -------------------------------------------------------------------------------- /core/src/products/domain/usecases/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GetProductsUsecase'; -------------------------------------------------------------------------------- /core/src/products/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data' 2 | export * from './domain' 3 | export * from './presentation' -------------------------------------------------------------------------------- /core/src/products/presentation/ProductsPloc.ts: -------------------------------------------------------------------------------- 1 | import { DataError } from "../../common/domain/DataError"; 2 | import { Ploc } from "../../common/presentation/Ploc"; 3 | import { GetProductsUsecase } from "../domain/usecases/GetProductsUsecase"; 4 | import { productsInitialState, ProductsState } from "./ProductsState"; 5 | 6 | export class ProductsPloc extends Ploc { 7 | constructor(private getProductsUsecase: GetProductsUsecase) { 8 | super(productsInitialState); 9 | } 10 | 11 | async search(searchTerm: string) { 12 | const productResult = await this.getProductsUsecase.execute(searchTerm); 13 | 14 | productResult.fold( 15 | (error) => this.changeState(this.handleError(searchTerm, error)), 16 | (products) => 17 | this.changeState({ 18 | kind: "LoadedProductsState", 19 | products, 20 | searchTerm, 21 | }) 22 | ); 23 | } 24 | 25 | private handleError(searchTerm: string, error: DataError): ProductsState { 26 | switch (error.kind) { 27 | case "UnexpectedError": { 28 | return { 29 | searchTerm, 30 | kind: "ErrorProductsState", 31 | error: "Sorry, an error has ocurred. Please try later again", 32 | }; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/products/presentation/ProductsState.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../domain/entities/Product"; 2 | 3 | export interface CommonProductsState { 4 | searchTerm: string; 5 | } 6 | 7 | export interface LoadingProductsState { 8 | kind: "LoadingProductsState"; 9 | } 10 | 11 | export interface LoadedProductsState { 12 | kind: "LoadedProductsState"; 13 | products: Array; 14 | } 15 | 16 | export interface ErrorProductsState { 17 | kind: "ErrorProductsState"; 18 | error: string; 19 | } 20 | 21 | export type ProductsState = ( 22 | | LoadingProductsState 23 | | LoadedProductsState 24 | | ErrorProductsState 25 | ) & 26 | CommonProductsState; 27 | 28 | export const productsInitialState: ProductsState = { 29 | kind: "LoadingProductsState", 30 | searchTerm: "", 31 | }; 32 | -------------------------------------------------------------------------------- /core/src/products/presentation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ProductsPloc'; 2 | export * from './ProductsState'; -------------------------------------------------------------------------------- /core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build", 4 | "rootDir": "./src", 5 | "declaration": true, 6 | "allowJs": false, 7 | "sourceMap": true, 8 | "moduleResolution": "node", 9 | "target": "es2016", 10 | "module": "ESNext", 11 | "esModuleInterop": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "strict": true, 14 | "skipLibCheck": true, 15 | "allowSyntheticDefaultImports": true, 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "incremental": false 19 | }, 20 | "include": ["./src/**/*"], 21 | "exclude": ["node_modules", "**/*.spec.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /core/tsconfig.tsbuildinfo: -------------------------------------------------------------------------------- 1 | {"program":{"fileNames":["../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es5.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2015.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2016.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2017.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2018.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2019.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2020.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.dom.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.scripthost.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2017.object.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2017.string.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2019.array.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2019.object.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2019.string.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2020.string.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../.nvm/versions/node/v14.18.0/lib/node_modules/typescript/lib/lib.esnext.intl.d.ts","./src/common/domain/DataError.ts","./src/common/domain/Either.ts","./src/products/domain/entities/Product.ts","./src/cart/domain/entities/CartItem.ts","./src/cart/domain/entities/Cart.ts","./src/cart/domain/repositories/CartRepository.ts","./src/cart/data/CartInMemoryRepository.ts","./src/cart/data/index.ts","./src/cart/domain/entities/index.ts","./src/cart/domain/repositories/index.ts","./src/cart/domain/usecases/GetCartUsecase.ts","./src/common/domain/EitherAsync.ts","./src/cart/domain/usecases/AddProductToCartUsecase.ts","./src/cart/domain/usecases/EditQuantityOfCartItemUsecase.ts","./src/cart/domain/usecases/RemoveItemFromCartUsecase.ts","./src/cart/domain/usecases/index.ts","./src/cart/domain/index.ts","./src/common/presentation/Ploc.ts","./src/cart/presentation/CartState.ts","./src/cart/presentation/CartPloc.ts","./src/cart/presentation/index.ts","./src/cart/index.ts","./src/common/presentation/index.ts","./src/products/domain/repositories/ProductRepository.ts","./src/products/data/ProductInMemoryRepository.ts","./src/products/domain/usecases/GetProductsUsecase.ts","./src/products/presentation/ProductsState.ts","./src/products/presentation/ProductsPloc.ts","./src/common/dependencies/DependenciesLocator.ts","./src/common/dependencies/index.ts","./src/common/index.ts","./src/products/data/index.ts","./src/products/domain/entities/index.ts","./src/products/domain/repositories/index.ts","./src/products/domain/usecases/index.ts","./src/products/domain/index.ts","./src/products/presentation/index.ts","./src/products/index.ts","./src/index.ts","./node_modules/.pnpm/@babel+types@7.15.6/node_modules/@babel/types/lib/index.d.ts","./node_modules/.pnpm/@types+babel__generator@7.6.3/node_modules/@types/babel__generator/index.d.ts","./node_modules/.pnpm/@babel+parser@7.15.8/node_modules/@babel/parser/typings/babel-parser.d.ts","./node_modules/.pnpm/@types+babel__template@7.4.1/node_modules/@types/babel__template/index.d.ts","./node_modules/.pnpm/@types+babel__traverse@7.14.2/node_modules/@types/babel__traverse/index.d.ts","./node_modules/.pnpm/@types+babel__core@7.1.16/node_modules/@types/babel__core/index.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/assert.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/assert/strict.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/globals.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/async_hooks.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/buffer.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/child_process.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/cluster.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/console.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/constants.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/crypto.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/dgram.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/dns.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/dns/promises.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/domain.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/events.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/fs.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/fs/promises.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/http.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/http2.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/https.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/inspector.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/module.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/net.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/os.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/path.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/perf_hooks.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/process.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/punycode.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/querystring.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/readline.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/repl.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/stream.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/stream/promises.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/stream/consumers.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/stream/web.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/string_decoder.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/timers.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/timers/promises.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/tls.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/trace_events.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/tty.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/url.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/util.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/v8.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/vm.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/wasi.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/worker_threads.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/zlib.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/globals.global.d.ts","./node_modules/.pnpm/@types+node@16.11.6/node_modules/@types/node/index.d.ts","./node_modules/.pnpm/@types+graceful-fs@4.1.5/node_modules/@types/graceful-fs/index.d.ts","./node_modules/.pnpm/@types+istanbul-lib-coverage@2.0.3/node_modules/@types/istanbul-lib-coverage/index.d.ts","./node_modules/.pnpm/@types+istanbul-lib-report@3.0.0/node_modules/@types/istanbul-lib-report/index.d.ts","./node_modules/.pnpm/@types+istanbul-reports@3.0.1/node_modules/@types/istanbul-reports/index.d.ts","./node_modules/.pnpm/jest-diff@27.3.1/node_modules/jest-diff/build/cleanupSemantic.d.ts","./node_modules/.pnpm/jest-diff@27.3.1/node_modules/jest-diff/build/types.d.ts","./node_modules/.pnpm/jest-diff@27.3.1/node_modules/jest-diff/build/diffLines.d.ts","./node_modules/.pnpm/jest-diff@27.3.1/node_modules/jest-diff/build/printDiffs.d.ts","./node_modules/.pnpm/jest-diff@27.3.1/node_modules/jest-diff/build/index.d.ts","./node_modules/.pnpm/pretty-format@27.3.1/node_modules/pretty-format/build/types.d.ts","./node_modules/.pnpm/pretty-format@27.3.1/node_modules/pretty-format/build/index.d.ts","./node_modules/.pnpm/@types+jest@27.0.2/node_modules/@types/jest/index.d.ts","./node_modules/.pnpm/@types+prettier@2.4.1/node_modules/@types/prettier/index.d.ts","./node_modules/.pnpm/@types+stack-utils@2.0.1/node_modules/@types/stack-utils/index.d.ts","./node_modules/.pnpm/@types+yargs-parser@20.2.1/node_modules/@types/yargs-parser/index.d.ts","./node_modules/.pnpm/@types+yargs@16.0.4/node_modules/@types/yargs/index.d.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"aa9fb4c70f369237c2f45f9d969c9a59e0eae9a192962eb48581fe864aa609db","affectsGlobalScope":true},"dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6","7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467","8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9","5f4e733ced4e129482ae2186aae29fde948ab7182844c3a5a51dd346182c7b06","e6b724280c694a9f588847f754198fb96c43d805f065c3a5b28bbc9594541c84","e21c071ca3e1b4a815d5f04a7475adcaeea5d64367e840dd0154096d705c3940",{"version":"e54c8715a4954cfdc66cd69489f2b725c09ebf37492dbd91cff0a1688b1159e8","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},{"version":"51b8b27c21c066bf877646e320bf6a722b80d1ade65e686923cd9d4494aef1ca","affectsGlobalScope":true},{"version":"43fb1d932e4966a39a41b464a12a81899d9ae5f2c829063f5571b6b87e6d2f9c","affectsGlobalScope":true},{"version":"cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a","affectsGlobalScope":true},{"version":"2c8c5ee58f30e7c944e04ab1fb5506fdbb4dd507c9efa6972cf4b91cec90c503","affectsGlobalScope":true},{"version":"2bb4b3927299434052b37851a47bf5c39764f2ba88a888a107b32262e9292b7c","affectsGlobalScope":true},{"version":"810627a82ac06fb5166da5ada4159c4ec11978dfbb0805fe804c86406dab8357","affectsGlobalScope":true},{"version":"62d80405c46c3f4c527ee657ae9d43fda65a0bf582292429aea1e69144a522a6","affectsGlobalScope":true},{"version":"3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93","affectsGlobalScope":true},{"version":"75ec0bdd727d887f1b79ed6619412ea72ba3c81d92d0787ccb64bab18d261f14","affectsGlobalScope":true},{"version":"3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006","affectsGlobalScope":true},{"version":"17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a","affectsGlobalScope":true},{"version":"7ce9f0bde3307ca1f944119f6365f2d776d281a393b576a18a2f2893a2d75c98","affectsGlobalScope":true},{"version":"6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577","affectsGlobalScope":true},{"version":"12a310447c5d23c7d0d5ca2af606e3bd08afda69100166730ab92c62999ebb9d","affectsGlobalScope":true},{"version":"b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e","affectsGlobalScope":true},{"version":"0eb85d6c590b0d577919a79e0084fa1744c1beba6fd0d4e951432fa1ede5510a","affectsGlobalScope":true},{"version":"da233fc1c8a377ba9e0bed690a73c290d843c2c3d23a7bd7ec5cd3d7d73ba1e0","affectsGlobalScope":true},{"version":"df9c8a72ca8b0ed62f5470b41208a0587f0f73f0a7db28e5a1272cf92537518e","affectsGlobalScope":true},{"version":"bb2d3fb05a1d2ffbca947cc7cbc95d23e1d053d6595391bd325deb265a18d36c","affectsGlobalScope":true},{"version":"c80df75850fea5caa2afe43b9949338ce4e2de086f91713e9af1a06f973872b8","affectsGlobalScope":true},{"version":"9d57b2b5d15838ed094aa9ff1299eecef40b190722eb619bac4616657a05f951","affectsGlobalScope":true},{"version":"6c51b5dd26a2c31dbf37f00cfc32b2aa6a92e19c995aefb5b97a3a64f1ac99de","affectsGlobalScope":true},{"version":"93544ca2f26a48716c1b6c5091842cad63129daac422dfa4bc52460465f22bb1","affectsGlobalScope":true},{"version":"2ad234885a4240522efccd77de6c7d99eecf9b4de0914adb9a35c0c22433f993","affectsGlobalScope":true},{"version":"1b3fe904465430e030c93239a348f05e1be80640d91f2f004c3512c2c2c89f34","affectsGlobalScope":true},{"version":"7435b75fdf3509622e79622dbe5091cf4b09688410ee2034e4fc17d0c99d0862","affectsGlobalScope":true},{"version":"e7e8e1d368290e9295ef18ca23f405cf40d5456fa9f20db6373a61ca45f75f40","affectsGlobalScope":true},{"version":"faf0221ae0465363c842ce6aa8a0cbda5d9296940a8e26c86e04cc4081eea21e","affectsGlobalScope":true},{"version":"06393d13ea207a1bfe08ec8d7be562549c5e2da8983f2ee074e00002629d1871","affectsGlobalScope":true},{"version":"9f1817f7c3f02f6d56e0f403b927e90bb133f371dcebc36fa7d6d208ef6899da","affectsGlobalScope":true},{"version":"4632665b87204bb1caa8b44d165bce0c50dfab177df5b561b345a567cabacf9a","affectsGlobalScope":true},"ab5cd6ecfec138273dc15dcdb0cc38ca130f8a6590c0447cc89d4bfa5fe84d2a","a018e38c15220f9f2270a8073be12cc405967ae0da93f38fffbee6162fa05cff","7dc0c831b4606f946fcaa3595393ec25a158b3aca0a50304b253b7e8a7a378ad","53eea00f5ae1cde3ca8ed3dd8a929c1d1bd01adfbf495f0a0802ad43d4f19f2f","2474aa99c49aecda1526051869532624ebd9b0d5c49814d0307d82977f6e824f","4bd27cca10447c82ad761b20c8909b040d5d76e392df6576d62d8ffc4a63f906","f8eb47f09d9f935de43952e650b36f380323c10d541152ec13e1fb6162854719","4ab2e1885e205df5984a5c1dbcf4796a5f260d5a64408c46f3822bbc386de56a","dc0069b7aa996369e1a79a7aabe65e2ac77b3aed3cc2e14416f69a8fe39cad74","b519069e0017cbf4b8dc5c7b78ffe517656f1204602122517cafd1452c7382d1","f00e1830759977eafc92f25a635cf15fe6f081ac50cd23f9109a93c85be5b9d2","d5533a0392ab7f1c5ab658c6ab1c5aac47f021cfd4b777541aa21ac1723847b4","0ae177b775897d1b8a8bc45f81b4ce0a0bed6a31a2af689ea44b2c029c3907e5","7edb5ce3518e12a4218bd8421eae0d8c4c4dfc092fce369c6684e276448accd2","eac29b4ee8396a6b39ddf69cd4f668544e4b9920ae820d8ac603987df917c569","f3f3a215e6e1c50b44a4c677fc8ee66bc4275ecd7c2bb8a4b4e9b70a020027e3","920acba4045f65b68f4ab46d3de7b73cefe6ef9eb66b4207cd2064ae72c3ce58","e27500de8f597f7f394b218faeb5c2cef37343f16738020fbfe84ca936a6ae99","96d5567d4ef85eb840c33753a01bebc99c94a54890fa9cc76da40c60ec1e23bc","bfdaf82c8045defd99eccfd2cb8de6a9e0381279281194f13a02c1f0dae735df","9d356c54198859ad304a97534ddaee5f1807dca2dd4a64db056cb261402d71fd","2f0805ecbb73ceebf7893f1c86fd70d3ae91eb69bef534524b31d7cfeea61576","59cf265491eb0b1fcbf1c95d570f6141b1925ad05518169660b757be9449fa1f","fef7eb0bff2a55790ed67691d56a877f049997a975c10051e86c1a7fe941f344","8c064a5ca75d602a74e1c258ad7ff04a636691d4ca16c982f236b005122b144d","0c95f888e150e742eeaeaef38caca82fedbe60dab9b6ffcb79f5cac342b7cd65","9f271af4754a1c42282cac80780580ce4de36db8c9171720441d56c8bfb1af3b","817f573b9f7406e0c2682d445e9bd27e4905d676b735f7a13ea1a5d3a90836a2","f44b113a4b981d79e078a8dffa32dd53d101faaef146ad65b899aff580855a3b","78b88df695e2925fd2df0053eb591cac6675ef58ceb913ce033fd689d0afee61","6e5ffaea118d0fa328e5d98625f1bbd6f7f45fb394e223fe01bbf9cbee5bd5a3","1c8c03989770b00b0d88b440f406f5799ad9a787ecc943272d29da275a443a97","eb9d533cc0b90f05cb26c03cbc84b3282a99f857d2acbcffeffec800a22c074b","629b1d2239ce37bd1359d098fe6450aa90ef654b661cec9fbd373b012302c2f2","a41bfb1e7cdfdfa73057128759f7e8b90b85d1581d00bfb5ecaf83cf681ade9e","920acba4045f65b68f4ab46d3de7b73cefe6ef9eb66b4207cd2064ae72c3ce58","6aff9652a235c3d24e466e615a6b68e61a3d74b6afedeb607197715c6a5719f2","e794a9a47662cc71770d80d8d74cc4ce09972f7338bf4d2d0d16768d505dc6ca","15f9e7a90a5c7a8932cd4ad4fb2821d27bbe43517217f46051b888ea190c8acc","f82348f8ac4f637d6ad76ef5d45577ccc0c59fbd25d8c44d55349a71a90e195a","8dfed5c91ad36e69e6da6b7e49be929d4e19666db2b651aa839c485170a2902c","4aaf84a5ac87bad3211f041fab85de5cc42e5954c3ed56842faf6f08167e6202","93de1c6dab503f053efe8d304cb522bb3a89feab8c98f307a674a4fae04773e9","3b043cf9a81854a72963fdb57d1884fc4da1cf5be69b5e0a4c5b751e58cb6d88","d0b0a00cf31968a33baeaadf974ce4e5e7edf58cea5288765293f41ba5e72b3a","0d5a2ee1fdfa82740e0103389b9efd6bfe145a20018a2da3c02b89666181f4d9","a69c09dbea52352f479d3e7ac949fde3d17b195abe90b045d619f747b38d6d1a",{"version":"92d63add669d18ebc349efbacd88966d6f2ccdddfb1b880b2db98ae3aa7bf7c4","affectsGlobalScope":true},"ccc94049a9841fe47abe5baef6be9a38fc6228807974ae675fb15dc22531b4be",{"version":"9acfe4d1ff027015151ce81d60797b04b52bffe97ad8310bb0ec2e8fd61e1303","affectsGlobalScope":true},"95843d5cfafced8f3f8a5ce57d2335f0bcd361b9483587d12a25e4bd403b8216","afc6e96061af46bcff47246158caee7e056f5288783f2d83d6858cd25be1c565",{"version":"34f5bcac12b36d70304b73de5f5aab3bb91bd9919f984be80579ebcad03a624e","affectsGlobalScope":true},"82408ed3e959ddc60d3e9904481b5a8dc16469928257af22a3f7d1a3bc7fd8c4","2f520601649a893e6a49a8851ebfcf4be8ce090dc1281c2a08a871cb04e8251f","f50c975ab7b50e25a69e3d8a3773894125b44e9698924105f23b812bf7488baf","2b8c764f856a1dd0a9a2bf23e5efddbff157de8138b0754010be561ae5fcaa90","ad4b60488fb1e562bf375dac9299815f7028bf667d9b5887b2d01d501b7d1ddd","246341c3a7a2638cf830d690e69de1e6085a102c6a30596435b050e6ac86c11a","6972fca26f6e9bd56197568d4379f99071a90766e06b4fcb5920a0130a9202be",{"version":"4a2628e95962c8ab756121faa3ac2ed348112ff7a87b5c286dd2cc3326546b4c","affectsGlobalScope":true},"aa8fe22e10f78a67b2ffbcc614b45fe258ff9e71d53ddb56e75fa7883c530270","a049a59a02009fc023684fcfaf0ac526fe36c35dcc5d2b7d620c1750ba11b083","a361a26932d73497a174a6d48c53cfedb55f735f20e8638fdf7b25cdeaac9ca4","b287b810b5035d5685f1df6e1e418f1ca452a3ed4f59fd5cc081dbf2045f0d9b","4b9a003b5c556c96784132945bb41c655ea11273b1917f5c8d0c154dd5fd20dd","a458dc78104cc80048ac24fdc02fe6dce254838094c2f25641b3f954d9721241",{"version":"e8b18c6385ff784228a6f369694fcf1a6b475355ba89090a88de13587a9391d5","affectsGlobalScope":true},"902cd98bf46e95caf4118a0733fb801e9e90eec3edaed6abdad77124afec9ca2","abc1c425b2ad6720433f40f1877abfa4223f0f3dd486c9c28c492179ca183cb6","cd4854d38f4eb5592afd98ab95ca17389a7dfe38013d9079e802d739bdbcc939","94eed4cc2f5f658d5e229ff1ccd38860bddf4233e347bf78edd2154dee1f2b99",{"version":"e51bee3200733b1f58818b5a9ea90fcd61c5b8afa3a0378391991f3696826a65","affectsGlobalScope":true},"9f1069b9e2c051737b1f9b4f1baf50e4a63385a6a89c32235549ae87fc3d5492","ee18f2da7a037c6ceeb112a084e485aead9ea166980bf433474559eac1b46553","29c2706fa0cc49a2bd90c83234da33d08bb9554ecec675e91c1f85087f5a5324","0acbf26bf958f9e80c1ffa587b74749d2697b75b484062d36e103c137c562bc3","d7838022c7dab596357a9604b9c6adffe37dc34085ce0779c958ce9545bd7139","1b952304137851e45bc009785de89ada562d9376177c97e37702e39e60c2f1ff",{"version":"806ef4cac3b3d9fa4a48d849c8e084d7c72fcd7b16d76e06049a9ed742ff79c0","affectsGlobalScope":true},"a279435e7813d1f061c0cab6ab77b1b9377e8d96851e5ed4a76a1ce6eb6e628f","c33a6ea7147af60d8e98f1ac127047f4b0d4e2ce28b8f08ff3de07ca7cc00637",{"version":"b42b47e17b8ece2424ae8039feb944c2e3ba4b262986aebd582e51efbdca93dc","affectsGlobalScope":true},"664d8f2d59164f2e08c543981453893bc7e003e4dfd29651ce09db13e9457980","2408611d9b4146e35d1dbd1f443ccd8e187c74614a54b80300728277529dbf11","998a3de5237518c0b3ac00a11b3b4417affb008aa20aedee52f3fdae3cb86151","ad41008ffe077206e1811fc873f4d9005b5fd7f6ab52bb6118fef600815a5cb4","d88ecca73348e7c337541c4b8b60a50aca5e87384f6b8a422fc6603c637e4c21","badae0df9a8016ac36994b0a0e7b82ba6aaa3528e175a8c3cb161e4683eec03e","c3db860bcaaaeb3bbc23f353bbda1f8ab82756c8d5e973bebb3953cb09ea68f2","235a53595bd20b0b0eeb1a29cb2887c67c48375e92f03749b2488fbd46d0b1a0","bc09393cd4cd13f69cf1366d4236fbae5359bb550f0de4e15767e9a91d63dfb1","9c266243b01545e11d2733a55ad02b4c00ecdbda99c561cd1674f96e89cdc958","c71155c05fc76ff948a4759abc1cb9feec036509f500174bc18dad4c7827a60c",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"e6ef68f677c1b63967d87568043b8af9d2dfd71d5873acd1de3abeb1db606741","3ebae8c00411116a66fca65b08228ea0cf0b72724701f9b854442100aab55aba","de18acda71730bac52f4b256ce7511bb56cc21f6f114c59c46782eff2f632857","7eb06594824ada538b1d8b48c3925a83e7db792f47a081a62cf3e5c4e23cf0ee","f5638f7c2f12a9a1a57b5c41b3c1ea7db3876c003bab68e6a57afd6bcc169af0","d8aab31ba8e618cc3eea10b0945de81cb93b7e8150a013a482332263b9305322","69da61a7b5093dac77fa3bec8be95dcf9a74c95a0e9161edb98bb24e30e439d2","561eca7a381b96d6ccac6e4061e6d2ae53f5bc44203f3fd9f5b26864c32ae6e9","62ea38627e3ebab429f7616812a9394d327c2bc271003dfba985de9b4137369f","8a8a96898906f065f296665e411f51010b51372fa260d5373bf9f64356703190","f014d6d053cb1840965952268a589c9e2a74d66c8c88286562d5699350e28e19","66851b263230decb3684072b2cb777f70ea3e52d4489b88f78f185618d4d398e",{"version":"e9f2cdc4e98e73a606ff68c470a8cb4f23cd638c47649d71b90a2d9413102080","affectsGlobalScope":true},"f58599a92d4a64416f4999a4d7241e1647aec2a6249214892722f712a6eedbe0","b0d10e46cfe3f6c476b69af02eaa38e4ccc7430221ce3109ae84bb9fb8282298","f7e133b20ee2669b6c0e5d7f0cd510868c57cd64b283e68c7f598e30ce9d76d2","6ba73232c9d3267ca36ddb83e335d474d2c0e167481e3dec416c782894e11438"],"options":{"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"esModuleInterop":true,"module":1,"outDir":"./build","rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":1},"fileIdsList":[[131],[82,131],[82,83,84,85,86,131],[82,84,131],[104,131,138],[131,140],[131,141],[131,147,149],[88,131],[91,131],[92,97,131],[93,103,104,111,120,130,131],[93,94,103,111,131],[95,131],[96,97,104,112,131],[97,120,127,131],[98,100,103,111,131],[99,131],[100,101,131],[102,103,131],[103,131],[103,104,105,120,130,131],[103,104,105,120,131],[106,111,120,130,131],[103,104,106,107,111,120,127,130,131],[106,108,120,127,130,131],[88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137],[103,109,131],[110,130,131],[100,103,111,120,131],[112,131],[113,131],[91,114,131],[115,129,131,135],[116,131],[117,131],[103,118,131],[118,119,131,133],[103,120,121,122,131],[120,122,131],[120,121,131],[123,131],[124,131],[103,125,126,131],[125,126,131],[97,111,127,131],[128,131],[111,129,131],[92,106,117,130,131],[97,131],[120,131,132],[131,133],[131,134],[92,97,103,105,114,120,130,131,133,135],[120,131,136],[131,153],[131,143,144],[131,143,144,145,146],[131,148],[43,44,47,48,131],[49,131],[46,131],[45,131],[46,47,131],[51,52,58,131],[43,44,47,131],[48,131],[43,44,45,47,48,54,131],[43,44,47,48,54,131],[53,55,56,57,131],[50,59,63,131],[43,45,47,53,55,56,57,60,61,131],[61,62,131],[49,53,55,56,57,62,67,68,70,131],[71,131],[44,131],[65,72,131],[60,131],[64,73,80,131],[43,44,45,66,131],[67,131],[75,76,77,131],[43,44,45,131],[66,131],[68,131],[74,78,79,131],[43,60,68,69,131],[69,70,131]],"referencedMap":[[1,1],[9,1],[13,1],[12,1],[3,1],[14,1],[15,1],[16,1],[17,1],[18,1],[19,1],[20,1],[21,1],[4,1],[5,1],[25,1],[22,1],[23,1],[24,1],[26,1],[27,1],[28,1],[6,1],[29,1],[30,1],[31,1],[32,1],[7,1],[33,1],[34,1],[35,1],[36,1],[8,1],[41,1],[37,1],[38,1],[39,1],[40,1],[2,1],[42,1],[11,1],[10,1],[84,2],[82,1],[87,3],[83,2],[85,4],[86,2],[139,5],[140,1],[141,6],[142,7],[150,8],[88,9],[89,9],[91,10],[92,11],[93,12],[94,13],[95,14],[96,15],[97,16],[98,17],[99,18],[100,19],[101,19],[102,20],[103,21],[104,22],[105,23],[90,1],[137,1],[106,24],[107,25],[108,26],[138,27],[109,28],[110,29],[111,30],[112,31],[113,32],[114,33],[115,34],[116,35],[117,36],[118,37],[119,38],[120,39],[122,40],[121,41],[123,42],[124,43],[125,44],[126,45],[127,46],[128,47],[129,48],[130,49],[131,50],[132,51],[133,52],[134,53],[135,54],[136,55],[151,1],[152,1],[153,1],[154,56],[143,1],[145,57],[147,58],[146,57],[144,1],[149,59],[148,1],[49,60],[50,61],[47,62],[46,63],[51,64],[59,65],[48,66],[52,67],[55,68],[56,69],[53,60],[57,69],[58,70],[64,71],[62,72],[61,1],[63,73],[71,74],[72,75],[43,1],[44,1],[54,76],[73,77],[60,1],[65,78],[81,79],[67,80],[74,81],[45,1],[75,63],[78,82],[66,83],[76,84],[68,80],[77,85],[80,86],[70,87],[69,63],[79,88]],"exportedModulesMap":[[1,1],[9,1],[13,1],[12,1],[3,1],[14,1],[15,1],[16,1],[17,1],[18,1],[19,1],[20,1],[21,1],[4,1],[5,1],[25,1],[22,1],[23,1],[24,1],[26,1],[27,1],[28,1],[6,1],[29,1],[30,1],[31,1],[32,1],[7,1],[33,1],[34,1],[35,1],[36,1],[8,1],[41,1],[37,1],[38,1],[39,1],[40,1],[2,1],[42,1],[11,1],[10,1],[84,2],[82,1],[87,3],[83,2],[85,4],[86,2],[139,5],[140,1],[141,6],[142,7],[150,8],[88,9],[89,9],[91,10],[92,11],[93,12],[94,13],[95,14],[96,15],[97,16],[98,17],[99,18],[100,19],[101,19],[102,20],[103,21],[104,22],[105,23],[90,1],[137,1],[106,24],[107,25],[108,26],[138,27],[109,28],[110,29],[111,30],[112,31],[113,32],[114,33],[115,34],[116,35],[117,36],[118,37],[119,38],[120,39],[122,40],[121,41],[123,42],[124,43],[125,44],[126,45],[127,46],[128,47],[129,48],[130,49],[131,50],[132,51],[133,52],[134,53],[135,54],[136,55],[151,1],[152,1],[153,1],[154,56],[143,1],[145,57],[147,58],[146,57],[144,1],[149,59],[148,1],[49,60],[50,61],[47,62],[46,63],[51,64],[59,65],[48,66],[52,67],[55,68],[56,69],[53,60],[57,69],[58,70],[64,71],[62,72],[61,1],[63,73],[71,74],[72,75],[43,1],[44,1],[54,76],[73,77],[60,1],[65,78],[81,79],[67,80],[74,81],[45,1],[75,63],[78,82],[66,83],[76,84],[68,80],[77,85],[80,86],[70,87],[69,63],[79,88]],"semanticDiagnosticsPerFile":[1,9,13,12,3,14,15,16,17,18,19,20,21,4,5,25,22,23,24,26,27,28,6,29,30,31,32,7,33,34,35,36,8,41,37,38,39,40,2,42,11,10,84,82,87,83,85,86,139,140,141,142,150,88,89,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,90,137,106,107,108,138,109,110,111,112,113,114,115,116,117,118,119,120,122,121,123,124,125,126,127,128,129,130,131,132,133,134,135,136,151,152,153,154,143,145,147,146,144,149,148,49,50,47,46,51,59,48,52,55,56,53,57,58,64,62,61,63,71,72,43,44,54,73,60,65,81,67,74,45,75,78,66,76,68,77,80,70,69,79]},"version":"4.4.4"} -------------------------------------------------------------------------------- /vue3-app/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | public 3 | -------------------------------------------------------------------------------- /vue3-app/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@antfu" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /vue3-app/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vite-ssg-dist 3 | .vite-ssg-temp 4 | *.local 5 | dist 6 | dist-ssr 7 | node_modules 8 | .idea/ 9 | *.log 10 | .yalc/ -------------------------------------------------------------------------------- /vue3-app/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /vue3-app/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "antfu.vite", 4 | "johnsoncodehk.volar", 5 | "lokalise.i18n-ally", 6 | "antfu.iconify", 7 | "dbaeumer.vscode-eslint", 8 | "voorjaar.windicss-intellisense", 9 | "csstools.postcss" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /vue3-app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["Vitesse"], 3 | "i18n-ally.keystyle": "nested", 4 | "i18n-ally.localesPaths": "locales", 5 | "i18n-ally.sortKeys": true, 6 | "prettier.enable": false, 7 | "typescript.tsdk": "node_modules/typescript/lib", 8 | "volar.tsPlugin": true, 9 | "volar.tsPluginStatus": false, 10 | "editor.codeActionsOnSave": { 11 | "source.fixAll.eslint": true, 12 | }, 13 | "files.associations": { 14 | "*.css": "postcss", 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /vue3-app/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2021 Shamim Hossain 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 | -------------------------------------------------------------------------------- /vue3-app/README.md: -------------------------------------------------------------------------------- 1 |

2 | Vitesse - Opinionated Vite Starter Template 3 |

4 | 5 |

6 | Mocking up web app with Vitesse(speed)
with modular design pattern. 7 |

8 | 9 |
10 | 11 |

12 | Live Demo 13 |

14 | 15 |
16 | 17 | ## Features 18 | 19 | - ⚡️ [Vue 3](https://github.com/vuejs/vue-next), [Vite 2](https://github.com/vitejs/vite), [pnpm](https://pnpm.js.org/), [ESBuild](https://github.com/evanw/esbuild) - born with fastness 20 | 21 | - 🗂 [File based routing](./src/common/pages) 22 | 23 | - 📦 [Components auto importing](./src/common/components) 24 | 25 | - 🍍 [State Management via Pinia](https://pinia.esm.dev/) 26 | 27 | - 📑 [Layout system](./src/common/layouts) 28 | 29 | - 📲 [PWA](https://github.com/antfu/vite-plugin-pwa) 30 | 31 | - 🎨 [Windi CSS](https://github.com/windicss/windicss) - next generation utility-first CSS framework 32 | 33 | - 😃 [Use icons from any icon sets, with no compromise](https://github.com/antfu/unplugin-icons) 34 | 35 | - 🌍 [I18n ready](./locales) 36 | 37 | - 🗒 [Markdown Support](https://github.com/antfu/vite-plugin-md) 38 | 39 | - 🔥 Use the [new ` 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /vue3-app/locales/README.md: -------------------------------------------------------------------------------- 1 | ## i18n 2 | 3 | This directory is to serve your locale translation files. YAML under this folder would be loaded automatically and register with their filenames as locale code. 4 | 5 | Check out [`vue-i18n`](https://github.com/intlify/vue-i18n-next) for more details. 6 | 7 | If you are using VS Code, [`i18n Ally`](https://github.com/lokalise/i18n-ally) is recommended to make the i18n experience better. 8 | -------------------------------------------------------------------------------- /vue3-app/locales/en.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: About 3 | back: Back 4 | go: GO 5 | home: Home 6 | toggle_dark: Toggle dark mode 7 | toggle_langs: Change languages 8 | choose-plans: Choose Plans 9 | intro: 10 | desc: Opinionated Vite Starter Template. Customized to support modular architecture pattern. 11 | dynamic-route: Demo of dynamic route 12 | hi: Hi, {name}! 13 | aka: Also known as 14 | whats-your-name: What's your name? 15 | whats-your-email: What's your email? 16 | fill-up-form: Fill up the form to proceed 17 | not-found: Not found 18 | order: 19 | order-summary: Order Summary 20 | description: You can now listen to millions of songs, audiobooks, and podcasts on any device anywhere you like! 21 | plan: Plan 22 | year: year 23 | month: month 24 | week: week 25 | plans: 26 | annual: Annual 27 | monthly: Monthly 28 | weekly: Weekly 29 | confirm-selected-plan: Confirm Selected Plan 30 | cancel-order: Cancel Order 31 | thanks-message: Thank you for your interest! 32 | plan-contact-info: One of our employee will contact with you soon. You can grab a cup of coffee in the meantime. 33 | no-data-saved: This demo project is not making any external API request. So, your data is not saving anywhere else. It uses Pinia (vue 3 state management) module to save your form data in your browser. 34 | -------------------------------------------------------------------------------- /vue3-app/locales/es.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: Acerca de 3 | back: Atrás 4 | go: Ir 5 | home: Inicio 6 | toggle_dark: Alternar modo oscuro 7 | toggle_langs: Cambiar idiomas 8 | choose-plans: Elija planes 9 | intro: 10 | desc: Plantilla de Inicio de Vite Dogmática. Personalizado para admitir el patrón de arquitectura modular. 11 | dynamic-route: Demo de ruta dinámica 12 | hi: ¡Hola, {name}! 13 | aka: También conocido como 14 | whats-your-name: ¿Cómo te llamas? 15 | whats-your-email: ¿Cuál es tu correo electrónico? 16 | fill-up-form: Complete el formulario para continuar 17 | not-found: No se ha encontrado 18 | order: 19 | order-summary: Resumen de la orden 20 | description: ¡Ahora puede escuchar millones de canciones, audiolibros y podcasts en cualquier dispositivo en cualquier lugar que desee! 21 | plan: Plan 22 | year: año 23 | month: mes 24 | week: semana 25 | plans: 26 | annual: Anual 27 | monthly: Mensual 28 | weekly: Semanalmente 29 | confirm-selected-plan: Confirmar plan seleccionado 30 | cancel-order: Cancelar orden 31 | thanks-message: ¡Gracias por su interés! 32 | plan-contact-info: Uno de nuestros empleados se comunicará con usted pronto. Mientras tanto, puedes tomar una taza de café. 33 | no-data-saved: Este proyecto de demostración no realiza ninguna solicitud de API externa. Por lo tanto, sus datos no se guardan en ningún otro lugar. Utiliza el módulo Pinia (gestión de estado vue 3) para guardar los datos de su formulario en su navegador. -------------------------------------------------------------------------------- /vue3-app/netlify.toml: -------------------------------------------------------------------------------- 1 | [build.environment] 2 | NPM_FLAGS = "--prefix=/dev/null" 3 | NODE_VERSION = "14" 4 | 5 | [build] 6 | publish = "dist" 7 | command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm run build" 8 | 9 | [[redirects]] 10 | from = "/*" 11 | to = "/index.html" 12 | status = 200 13 | 14 | [[headers]] 15 | for = "/manifest.webmanifest" 16 | [headers.values] 17 | Content-Type = "application/manifest+json" -------------------------------------------------------------------------------- /vue3-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "vite --port 3333 --open", 5 | "build": "cross-env NODE_ENV=production vite-ssg build", 6 | "preview": "vite preview", 7 | "preview-https": "serve dist", 8 | "analysis": "windicss-analysis" 9 | }, 10 | "dependencies": { 11 | "@vueuse/core": "^6.4.1", 12 | "@vueuse/head": "^0.6.0", 13 | "nprogress": "^0.2.0", 14 | "pinia": "^2.0.0-rc.9", 15 | "pkg-bloc-core": "file:.yalc/pkg-bloc-core", 16 | "prism-theme-vars": "^0.2.2", 17 | "vue": "^3.2.16", 18 | "vue-demi": "^0.11.4", 19 | "vue-i18n": "^9.1.7", 20 | "vue-router": "^4.0.11" 21 | }, 22 | "devDependencies": { 23 | "@antfu/eslint-config": "^0.8.2", 24 | "@iconify/json": "^1.1.405", 25 | "@intlify/vite-plugin-vue-i18n": "^2.4.0", 26 | "@types/markdown-it-link-attributes": "^3.0.1", 27 | "@types/nprogress": "^0.2.0", 28 | "@vitejs/plugin-vue": "^1.9.1", 29 | "@vue/compiler-sfc": "^3.2.16", 30 | "@vue/server-renderer": "^3.2.16", 31 | "critters": "^0.0.10", 32 | "cross-env": "^7.0.3", 33 | "eslint": "^7.32.0", 34 | "https-localhost": "^4.7.0", 35 | "markdown-it-link-attributes": "^3.0.0", 36 | "markdown-it-prism": "^2.2.1", 37 | "pnpm": "^6.15.1", 38 | "typescript": "^4.4.3", 39 | "unplugin-auto-import": "^0.4.6", 40 | "unplugin-icons": "^0.7.6", 41 | "unplugin-vue-components": "^0.15.3", 42 | "vite": "^2.5.10", 43 | "vite-plugin-inspect": "^0.3.6", 44 | "vite-plugin-md": "^0.11.1", 45 | "vite-plugin-pages": "^0.18.1", 46 | "vite-plugin-pwa": "^0.11.2", 47 | "vite-plugin-vue-layouts": "^0.4.1", 48 | "vite-plugin-windicss": "^1.4.5", 49 | "vite-ssg": "^0.15.3", 50 | "vue-tsc": "^0.3.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vue3-app/public/_headers: -------------------------------------------------------------------------------- 1 | /assets/* 2 | cache-control: max-age=31536000 3 | cache-control: immutable 4 | -------------------------------------------------------------------------------- /vue3-app/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /vue3-app/public/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shamscorner/bloc-vue-3-clean-pattern/742dc19de52b9d950e5b00f2a8b793dce1db6806/vue3-app/public/pwa-192x192.png -------------------------------------------------------------------------------- /vue3-app/public/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shamscorner/bloc-vue-3-clean-pattern/742dc19de52b9d950e5b00f2a8b793dce1db6806/vue3-app/public/pwa-512x512.png -------------------------------------------------------------------------------- /vue3-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /vue3-app/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /vue3-app/src/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /vue3-app/src/cart/components/CartItem.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 44 | -------------------------------------------------------------------------------- /vue3-app/src/cart/pages/cart/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | -------------------------------------------------------------------------------- /vue3-app/src/common/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 37 | -------------------------------------------------------------------------------- /vue3-app/src/common/components/README.md: -------------------------------------------------------------------------------- 1 | ## Components 2 | 3 | Components in this dir will be auto-registered and on-demand, powered by [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components). 4 | 5 | 6 | ### Icons 7 | 8 | You can use icons from almost any icon sets by the power of [Iconify](https://iconify.design/). 9 | 10 | It will only bundle the icons you use. Check out [`unplugin-icons`](https://github.com/antfu/unplugin-icons) for more details. -------------------------------------------------------------------------------- /vue3-app/src/common/components/VButton.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vue3-app/src/common/components/VButtonText.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vue3-app/src/common/components/VDescription.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /vue3-app/src/common/components/VInput.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 34 | -------------------------------------------------------------------------------- /vue3-app/src/common/components/VTitleLg.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /vue3-app/src/common/layouts/404.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 24 | -------------------------------------------------------------------------------- /vue3-app/src/common/layouts/README.md: -------------------------------------------------------------------------------- 1 | ## Layouts 2 | 3 | Vue components in this dir are used as layouts. 4 | 5 | By default, `default.vue` will be used unless an alternative is specified in the route meta. 6 | 7 | With [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) and [`vite-plugin-vue-layouts`](https://github.com/JohnCampionJr/vite-plugin-vue-layouts), you can specify the layout in the page's SFCs like this: 8 | 9 | ```html 10 | 11 | meta: 12 | layout: home 13 | 14 | ``` 15 | -------------------------------------------------------------------------------- /vue3-app/src/common/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /vue3-app/src/common/layouts/home.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /vue3-app/src/common/logic/dark.ts: -------------------------------------------------------------------------------- 1 | // import { useDark, useToggle } from '@vueuse/core' 2 | 3 | export const isDark = useDark() 4 | export const toggleDark = useToggle(isDark) 5 | -------------------------------------------------------------------------------- /vue3-app/src/common/logic/format-currency.ts: -------------------------------------------------------------------------------- 1 | export function formatCurrency(price: number): string { 2 | return price.toLocaleString('es-ES', { 3 | style: 'currency', 4 | currency: 'EUR', 5 | }) 6 | } 7 | -------------------------------------------------------------------------------- /vue3-app/src/common/logic/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dark' 2 | export * from './format-currency' 3 | -------------------------------------------------------------------------------- /vue3-app/src/common/modules/README.md: -------------------------------------------------------------------------------- 1 | ## Modules 2 | 3 | A custom user module system. Place a `.ts` file with the following template, it will be installed automatically. 4 | 5 | ```ts 6 | import { UserModule } from '~/types' 7 | 8 | export const install: UserModule = ({ app, router, isClient }) => { 9 | // do something 10 | } 11 | ``` 12 | -------------------------------------------------------------------------------- /vue3-app/src/common/modules/i18n.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n' 2 | import { UserModule } from '~/types' 3 | 4 | // Import i18n resources 5 | // https://vitejs.dev/guide/features.html#glob-import 6 | // 7 | // Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite 8 | const messages = Object.fromEntries( 9 | Object.entries( 10 | import.meta.globEager('../../../locales/*.y(a)?ml')) 11 | .map(([key, value]) => { 12 | const yaml = key.endsWith('.yaml') 13 | return [key.slice(17, yaml ? -5 : -4), value.default] 14 | }), 15 | ) 16 | 17 | export const install: UserModule = ({ app }) => { 18 | const i18n = createI18n({ 19 | legacy: false, 20 | locale: 'en', 21 | messages, 22 | }) 23 | 24 | app.use(i18n) 25 | } 26 | -------------------------------------------------------------------------------- /vue3-app/src/common/modules/nprogress.ts: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress' 2 | import { UserModule } from '~/types' 3 | 4 | export const install: UserModule = ({ isClient, router }) => { 5 | if (isClient) { 6 | router.beforeEach(() => { NProgress.start() }) 7 | router.afterEach(() => { NProgress.done() }) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vue3-app/src/common/modules/pinia.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | import { UserModule } from '~/types' 3 | 4 | // Setup Pinia 5 | // https://pinia.esm.dev/ 6 | export const install: UserModule = ({ isClient, initialState, app }) => { 7 | const pinia = createPinia() 8 | app.use(pinia) 9 | // Refer to 10 | // https://github.com/antfu/vite-ssg/blob/main/README.md#state-serialization 11 | // for other serialization strategies. 12 | if (isClient) 13 | pinia.state.value = (initialState.pinia) || {} 14 | 15 | else 16 | initialState.pinia = pinia.state.value 17 | } 18 | -------------------------------------------------------------------------------- /vue3-app/src/common/modules/pwa.ts: -------------------------------------------------------------------------------- 1 | import { UserModule } from '~/types' 2 | 3 | // https://github.com/antfu/vite-plugin-pwa#automatic-reload-when-new-content-available 4 | export const install: UserModule = ({ isClient, router }) => { 5 | if (!isClient) 6 | return 7 | 8 | router.isReady().then(async() => { 9 | const { registerSW } = await import('virtual:pwa-register') 10 | registerSW({ immediate: true }) 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /vue3-app/src/common/pages/README.md: -------------------------------------------------------------------------------- 1 | ## File-based Routing 2 | 3 | Routes will be auto-generated for Vue files in this dir with the same file structure. 4 | Check out [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) for more details. 5 | 6 | ### Path Aliasing 7 | 8 | `~/` is aliased to `./src/` folder. 9 | 10 | For example, instead of having 11 | 12 | ```ts 13 | import { isDark } from '../../../../logic' 14 | ``` 15 | 16 | now, you can use 17 | 18 | ```ts 19 | import { isDark } from '~/logic' 20 | ``` 21 | -------------------------------------------------------------------------------- /vue3-app/src/common/pages/[...all].vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 12 | meta: 13 | layout: 404 14 | 15 | -------------------------------------------------------------------------------- /vue3-app/src/common/pages/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About 3 | --- 4 | 5 |
6 | 7 | 8 |

About

9 |
10 | 11 | [Vitesse](https://github.com/antfu/vitesse) is an opinionated [Vite](https://github.com/vitejs/vite) starter template made by [@antfu](https://github.com/antfu) for mocking apps swiftly. With **file-based routing**, **components auto importing**, **markdown support**, I18n, PWA and uses **WindiCSS** for UI. 12 | 13 | ```js 14 | // syntax highlighting example 15 | function vitesse() { 16 | const foo = 'bar' 17 | console.log(foo) 18 | } 19 | ``` 20 | 21 | Check out the [GitHub repo](https://github.com/antfu/vitesse) for more details. 22 | -------------------------------------------------------------------------------- /vue3-app/src/common/pages/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 34 | -------------------------------------------------------------------------------- /vue3-app/src/common/styles/main.css: -------------------------------------------------------------------------------- 1 | @import './markdown.css'; 2 | 3 | html, 4 | body, 5 | #app { 6 | height: 100%; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | html.dark { 12 | background: #121212; 13 | } 14 | 15 | #nprogress { 16 | pointer-events: none; 17 | } 18 | 19 | #nprogress .bar { 20 | @apply bg-teal-600 opacity-75; 21 | 22 | position: fixed; 23 | z-index: 1031; 24 | top: 0; 25 | left: 0; 26 | 27 | width: 100%; 28 | height: 2px; 29 | } 30 | 31 | .btn { 32 | @apply px-4 py-1 rounded inline-block 33 | bg-teal-600 text-white cursor-pointer 34 | hover:bg-teal-700 35 | disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50 focus:outline-none focus:ring-2 focus:ring-teal-400 focus:ring-offset-2 dark:focus:ring-offset-dark-50; 36 | } 37 | 38 | .btn-text { 39 | @apply px-4 py-1 rounded inline-block 40 | text-gray-600 cursor-pointer 41 | hover:underline 42 | disabled:cursor-default disabled:opacity-50 focus:outline-none focus:ring-2 focus:ring-teal-400 focus:ring-offset-2 focus:underline dark:focus:ring-offset-dark-50; 43 | } 44 | 45 | .icon-btn { 46 | @apply inline-block cursor-pointer select-none 47 | opacity-75 transition duration-200 ease-in-out rounded 48 | hover:opacity-100 hover:text-teal-600 focus:outline-none focus:ring-2 focus:ring-teal-400 focus:ring-offset-2 dark:focus:ring-offset-dark-50; 49 | font-size: 0.9em; 50 | } 51 | -------------------------------------------------------------------------------- /vue3-app/src/common/styles/markdown.css: -------------------------------------------------------------------------------- 1 | /* https://github.com/antfu/prism-theme-vars */ 2 | @import 'prism-theme-vars/base.css'; 3 | 4 | .prose { 5 | --prism-font-family: 'Input Mono', monospace; 6 | } 7 | 8 | html:not(.dark) .prose { 9 | --prism-foreground: #393a34; 10 | --prism-background: #fbfbfb; 11 | --prism-comment: #a0ada0; 12 | --prism-string: #b56959; 13 | --prism-literal: #2f8a89; 14 | --prism-number: #296aa3; 15 | --prism-keyword: #1c6b48; 16 | --prism-function: #6c7834; 17 | --prism-boolean: #1c6b48; 18 | --prism-constant: #a65e2b; 19 | --prism-deleted: #a14f55; 20 | --prism-class: #2993a3; 21 | --prism-builtin: #ab5959; 22 | --prism-property: #b58451; 23 | --prism-namespace: #b05a78; 24 | --prism-punctuation: #8e8f8b; 25 | --prism-decorator: #bd8f8f; 26 | --prism-regex: #ab5e3f; 27 | --prism-json-property: #698c96; 28 | } 29 | 30 | html.dark .prose { 31 | --prism-foreground: #d4cfbf; 32 | --prism-background: #151515; 33 | --prism-comment: #758575; 34 | --prism-string: #d48372; 35 | --prism-literal: #429988; 36 | --prism-keyword: #4d9375; 37 | --prism-boolean: #1c6b48; 38 | --prism-number: #6394bf; 39 | --prism-variable: #c2b36e; 40 | --prism-function: #a1b567; 41 | --prism-deleted: #a14f55; 42 | --prism-class: #54b1bf; 43 | --prism-builtin: #e0a569; 44 | --prism-property: #dd8e6e; 45 | --prism-namespace: #db889a; 46 | --prism-punctuation: #858585; 47 | --prism-decorator: #bd8f8f; 48 | --prism-regex: #ab5e3f; 49 | --prism-json-property: #6b8b9e; 50 | --prism-line-number: #888888; 51 | --prism-line-number-gutter: #eeeeee; 52 | --prism-line-highlight-background: #444444; 53 | --prism-selection-background: #444444; 54 | } 55 | -------------------------------------------------------------------------------- /vue3-app/src/common/use-ploc-state.ts: -------------------------------------------------------------------------------- 1 | import { Ploc } from 'pkg-bloc-core' 2 | 3 | import { DeepReadonly, onMounted, onUnmounted, readonly, Ref, ref } from 'vue' 4 | 5 | export function usePlocState(ploc: Ploc): DeepReadonly> { 6 | const state = ref(ploc.state) as Ref 7 | 8 | const stateSubscription = (newState: S) => { 9 | state.value = newState 10 | } 11 | 12 | onMounted(() => { 13 | ploc.subscribe(stateSubscription) 14 | }) 15 | 16 | onUnmounted(() => { 17 | ploc.unsubscribe(stateSubscription) 18 | }) 19 | 20 | return readonly(state) 21 | } 22 | -------------------------------------------------------------------------------- /vue3-app/src/main.ts: -------------------------------------------------------------------------------- 1 | // register vue composition api globally 2 | import { ViteSSG } from 'vite-ssg' 3 | import generatedRoutes from 'virtual:generated-pages' 4 | import { setupLayouts } from 'virtual:generated-layouts' 5 | import App from './App.vue' 6 | 7 | // windicss layers 8 | import 'virtual:windi-base.css' 9 | import 'virtual:windi-components.css' 10 | // your custom styles here 11 | import './common/styles/main.css' 12 | // windicss utilities should be the last style import 13 | import 'virtual:windi-utilities.css' 14 | // windicss devtools support (dev only) 15 | import 'virtual:windi-devtools' 16 | 17 | const routes = setupLayouts(generatedRoutes) 18 | 19 | // https://github.com/antfu/vite-ssg 20 | export const createApp = ViteSSG( 21 | App, 22 | { routes }, 23 | (ctx) => { 24 | // install all modules under `**/modules/` 25 | Object.values(import.meta.globEager('./**/modules/*.ts')).map(i => i.install?.(ctx)) 26 | }, 27 | ) 28 | -------------------------------------------------------------------------------- /vue3-app/src/products/components/ProductItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 34 | -------------------------------------------------------------------------------- /vue3-app/src/products/pages/products/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 46 | -------------------------------------------------------------------------------- /vue3-app/src/shims.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-duplicates */ 2 | 3 | declare interface Window { 4 | // extend the window 5 | } 6 | 7 | // with vite-plugin-md, markdowns can be treat as Vue components 8 | declare module '*.md' { 9 | import { ComponentOptions } from 'vue' 10 | const component: ComponentOptions 11 | export default component 12 | } 13 | -------------------------------------------------------------------------------- /vue3-app/src/types.ts: -------------------------------------------------------------------------------- 1 | import { ViteSSGContext } from 'vite-ssg' 2 | 3 | export type UserModule = (ctx: ViteSSGContext) => void 4 | -------------------------------------------------------------------------------- /vue3-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "target": "es2016", 6 | "lib": ["DOM", "ESNext"], 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "incremental": false, 10 | "skipLibCheck": true, 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "noUnusedLocals": true, 14 | "strictNullChecks": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "types": [ 17 | "vite/client", 18 | "vite-plugin-pages/client", 19 | "vite-plugin-vue-layouts/client" 20 | ], 21 | "paths": { 22 | "~/*": ["src/*"] 23 | } 24 | }, 25 | "exclude": ["dist", "node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /vue3-app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { defineConfig } from 'vite' 3 | import Vue from '@vitejs/plugin-vue' 4 | import Pages from 'vite-plugin-pages' 5 | import Layouts from 'vite-plugin-vue-layouts' 6 | import Icons from 'unplugin-icons/vite' 7 | import IconsResolver from 'unplugin-icons/resolver' 8 | import Components from 'unplugin-vue-components/vite' 9 | import AutoImport from 'unplugin-auto-import/vite' 10 | import Markdown from 'vite-plugin-md' 11 | import WindiCSS from 'vite-plugin-windicss' 12 | import { VitePWA } from 'vite-plugin-pwa' 13 | import VueI18n from '@intlify/vite-plugin-vue-i18n' 14 | import Inspect from 'vite-plugin-inspect' 15 | import Prism from 'markdown-it-prism' 16 | import LinkAttributes from 'markdown-it-link-attributes' 17 | 18 | const markdownWrapperClasses = 'prose prose-sm m-auto text-left' 19 | 20 | export default defineConfig({ 21 | resolve: { 22 | alias: { 23 | '~/': `${path.resolve(__dirname, 'src')}/`, 24 | }, 25 | }, 26 | plugins: [ 27 | Vue({ 28 | include: [/\.vue$/, /\.md$/], 29 | }), 30 | 31 | // https://github.com/hannoeru/vite-plugin-pages 32 | Pages({ 33 | extensions: ['vue', 'md'], 34 | pagesDir: [ 35 | { dir: 'src/**/pages', baseRoute: '' }, 36 | ], 37 | }), 38 | 39 | // https://github.com/JohnCampionJr/vite-plugin-vue-layouts 40 | Layouts({ 41 | layoutsDir: 'src/common/layouts', 42 | }), 43 | 44 | // https://github.com/antfu/unplugin-auto-import 45 | AutoImport({ 46 | imports: [ 47 | 'vue', 48 | 'vue-router', 49 | 'vue-i18n', 50 | '@vueuse/head', 51 | '@vueuse/core', 52 | ], 53 | }), 54 | 55 | // https://github.com/antfu/unplugin-vue-components 56 | Components({ 57 | // relative paths to the directory to search for components 58 | dirs: ['src/**/components'], 59 | 60 | // allow auto load markdown components under `./src/components/` 61 | extensions: ['vue', 'md'], 62 | 63 | // search for subdirectories 64 | deep: true, 65 | 66 | dts: true, 67 | 68 | // allow auto import and register components used in markdown 69 | include: [/\.vue$/, /\.vue\?vue/, /\.md$/], 70 | 71 | // custom resolvers 72 | resolvers: [ 73 | // auto import icons 74 | // https://github.com/antfu/unplugin-icons 75 | IconsResolver({ 76 | componentPrefix: '', 77 | // enabledCollections: ['carbon'] 78 | }), 79 | ], 80 | }), 81 | 82 | // https://github.com/antfu/unplugin-icons 83 | Icons(), 84 | 85 | // https://github.com/antfu/vite-plugin-windicss 86 | WindiCSS({ 87 | safelist: markdownWrapperClasses, 88 | }), 89 | 90 | // https://github.com/antfu/vite-plugin-md 91 | // Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite 92 | Markdown({ 93 | wrapperClasses: markdownWrapperClasses, 94 | headEnabled: true, 95 | markdownItSetup(md) { 96 | // https://prismjs.com/ 97 | md.use(Prism) 98 | md.use(LinkAttributes, { 99 | pattern: /^https?:\/\//, 100 | attrs: { 101 | target: '_blank', 102 | rel: 'noopener', 103 | }, 104 | }) 105 | }, 106 | }), 107 | 108 | // https://github.com/antfu/vite-plugin-pwa 109 | VitePWA({ 110 | registerType: 'autoUpdate', 111 | includeAssets: ['favicon.svg', 'robots.txt', 'safari-pinned-tab.svg'], 112 | manifest: { 113 | name: 'Vitesse', 114 | short_name: 'Vitesse', 115 | theme_color: '#ffffff', 116 | icons: [ 117 | { 118 | src: '/pwa-192x192.png', 119 | sizes: '192x192', 120 | type: 'image/png', 121 | }, 122 | { 123 | src: '/pwa-512x512.png', 124 | sizes: '512x512', 125 | type: 'image/png', 126 | }, 127 | { 128 | src: '/pwa-512x512.png', 129 | sizes: '512x512', 130 | type: 'image/png', 131 | purpose: 'any maskable', 132 | }, 133 | ], 134 | }, 135 | }), 136 | 137 | // https://github.com/intlify/vite-plugin-vue-i18n 138 | VueI18n({ 139 | runtimeOnly: true, 140 | compositionOnly: true, 141 | include: [path.resolve(__dirname, 'locales/**')], 142 | }), 143 | 144 | // https://github.com/antfu/vite-plugin-inspect 145 | Inspect({ 146 | // change this to enable inspect for debugging 147 | enabled: false, 148 | }), 149 | ], 150 | 151 | server: { 152 | fs: { 153 | strict: true, 154 | }, 155 | }, 156 | 157 | // https://github.com/antfu/vite-ssg 158 | ssgOptions: { 159 | script: 'async', 160 | formatting: 'minify', 161 | }, 162 | 163 | optimizeDeps: { 164 | include: [ 165 | 'vue', 166 | 'vue-router', 167 | '@vueuse/core', 168 | ], 169 | exclude: [ 170 | 'vue-demi', 171 | ], 172 | }, 173 | }) 174 | -------------------------------------------------------------------------------- /vue3-app/windi.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'windicss/helpers' 2 | import colors from 'windicss/colors' 3 | import typography from 'windicss/plugin/typography' 4 | 5 | export default defineConfig({ 6 | darkMode: 'class', 7 | // https://windicss.org/posts/v30.html#attributify-mode 8 | attributify: true, 9 | 10 | plugins: [ 11 | typography(), 12 | ], 13 | theme: { 14 | extend: { 15 | typography: { 16 | DEFAULT: { 17 | css: { 18 | maxWidth: '65ch', 19 | color: 'inherit', 20 | a: { 21 | 'color': 'inherit', 22 | 'opacity': 0.75, 23 | 'fontWeight': '500', 24 | 'textDecoration': 'underline', 25 | '&:hover': { 26 | opacity: 1, 27 | color: colors.teal[600], 28 | }, 29 | }, 30 | b: { color: 'inherit' }, 31 | strong: { color: 'inherit' }, 32 | em: { color: 'inherit' }, 33 | h1: { color: 'inherit' }, 34 | h2: { color: 'inherit' }, 35 | h3: { color: 'inherit' }, 36 | h4: { color: 'inherit' }, 37 | code: { color: 'inherit' }, 38 | }, 39 | }, 40 | }, 41 | }, 42 | }, 43 | }) 44 | -------------------------------------------------------------------------------- /vue3-app/yalc.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v1", 3 | "packages": { 4 | "pkg-bloc-core": { 5 | "signature": "083d94b7dd3c1d91bc4b405ee8b51eb6", 6 | "file": true 7 | } 8 | } 9 | } --------------------------------------------------------------------------------