From bb35486722527ca7857c24f95a30f0863f8ac03e Mon Sep 17 00:00:00 2001
From: Caleb Ukle
Date: Thu, 15 Jun 2023 15:38:50 -0500
Subject: [PATCH] kind of working
---
apps/cart-e2e/project.json | 3 +-
apps/cart/.env.serve | 1 +
apps/cart/functions/api/api.ts | 47 +++++++
apps/cart/project.json | 18 ++-
apps/cart/public/index.html | 6 +
apps/cart/src/_redirects | 5 +-
apps/cart/src/app/app.spec.tsx | 2 +
apps/cart/src/app/app.tsx | 6 +-
.../cart/src/environments/environment.prod.ts | 1 +
apps/cart/src/environments/environment.ts | 1 +
apps/products/.env.serve | 1 +
apps/products/functions/api/api.ts | 4 +-
apps/products/netlify.toml | 15 ---
apps/products/project.json | 36 ++----
apps/products/src/_redirects | 5 +-
apps/products/src/app/app.module.ts | 3 +-
.../src/environments/environment.prod.ts | 1 +
apps/products/src/environments/environment.ts | 1 +
.../cart-cart-page/cart-cart-page.spec.tsx | 30 ++++-
.../src/lib/cart-cart-page/cart-cart-page.tsx | 117 ++++++++++--------
.../src/lib/cart-cart-page/cart-page-hooks.ts | 36 ++++++
.../lib/home-page/home-page.component.spec.ts | 17 ++-
.../src/lib/home-page/home-page.component.ts | 1 -
.../product-detail-page.component.html | 3 +-
.../product-detail-page.component.scss | 7 ++
.../product-detail-page.component.spec.ts | 15 ++-
.../product-detail-page.component.ts | 9 +-
.../products-product-detail-page.module.ts | 1 -
.../cart/state/src/lib/+state/cart.actions.ts | 17 ++-
.../cart/state/src/lib/+state/cart.reducer.ts | 10 ++
libs/shared/product/state/src/index.ts | 1 +
.../state/src/lib/+state/products.effects.ts | 43 +++++--
.../src/lib/+state/products.reducer.spec.ts | 4 +-
.../state/src/lib/+state/products.reducer.ts | 7 +-
libs/shared/product/state/src/react.ts | 1 +
netlify.toml | 2 -
tools/scripts/deploy.ts | 33 -----
37 files changed, 338 insertions(+), 172 deletions(-)
create mode 100644 apps/cart/.env.serve
create mode 100644 apps/cart/functions/api/api.ts
create mode 100644 apps/cart/public/index.html
create mode 100644 apps/products/.env.serve
delete mode 100644 apps/products/netlify.toml
create mode 100644 libs/cart/cart-page/src/lib/cart-cart-page/cart-page-hooks.ts
delete mode 100644 netlify.toml
delete mode 100644 tools/scripts/deploy.ts
diff --git a/apps/cart-e2e/project.json b/apps/cart-e2e/project.json
index bf57fddf..b32731cc 100644
--- a/apps/cart-e2e/project.json
+++ b/apps/cart-e2e/project.json
@@ -9,7 +9,8 @@
"options": {
"cypressConfig": "apps/cart-e2e/cypress.config.ts",
"devServerTarget": "cart:serve",
- "testingType": "e2e"
+ "testingType": "e2e",
+ "baseUrl": "http://localhost:4200"
},
"configurations": {
"production": {
diff --git a/apps/cart/.env.serve b/apps/cart/.env.serve
new file mode 100644
index 00000000..7ded5764
--- /dev/null
+++ b/apps/cart/.env.serve
@@ -0,0 +1 @@
+BASE_API_PATH=/.netlify/functions
diff --git a/apps/cart/functions/api/api.ts b/apps/cart/functions/api/api.ts
new file mode 100644
index 00000000..6cd40cef
--- /dev/null
+++ b/apps/cart/functions/api/api.ts
@@ -0,0 +1,47 @@
+import { Handler } from '@netlify/functions';
+import fastify, { FastifyInstance, FastifyRequest } from 'fastify';
+import awsLambdaFastify from '@fastify/aws-lambda';
+import sensible from '@fastify/sensible';
+import { products } from '@nx-example/shared/product/data';
+import cors from '@fastify/cors';
+import { randomUUID } from 'crypto';
+
+async function routes(fastify: FastifyInstance) {
+ fastify.get('/products', async () => {
+ return products;
+ });
+ fastify.post(
+ '/checkout',
+ async (
+ request: FastifyRequest<{
+ Body: { productId: string; quanntity: number }[];
+ }>
+ ) => {
+ const items = request.body;
+ console.log(request.body);
+ const price = items.reduce((acc, item) => {
+ const product = products.find((p) => p.id === item.productId);
+ return acc + product.price * item.quanntity;
+ }, 0);
+
+ // gotta think real hard
+ await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
+
+ return { success: true, orderId: randomUUID(), total: price };
+ }
+ );
+}
+
+function init() {
+ const app = fastify();
+ app.register(sensible);
+ app.register(cors);
+ // set the prefix for the netlify functions url
+ app.register(routes, {
+ prefix: `${process.env.BASE_API_PATH || ''}/api`,
+ });
+ return app;
+}
+
+// Note: Netlify deploys this function at the endpoint /.netlify/functions/api
+export const handler: Handler = awsLambdaFastify(init());
diff --git a/apps/cart/project.json b/apps/cart/project.json
index bac4ef89..e6edf0d3 100644
--- a/apps/cart/project.json
+++ b/apps/cart/project.json
@@ -70,7 +70,7 @@
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production"
},
- "serve": {
+ "serve-app": {
"executor": "@nx/webpack:dev-server",
"options": {
"buildTarget": "cart:build"
@@ -88,7 +88,10 @@
"lint": {
"executor": "@nx/linter:eslint",
"options": {
- "lintFilePatterns": ["apps/cart/**/*.{ts,tsx,js,jsx}"]
+ "lintFilePatterns": [
+ "apps/cart/**/*.{ts,tsx,js,jsx}",
+ "./functions/**/*.ts"
+ ]
},
"outputs": ["{options.outputFile}"]
},
@@ -100,15 +103,18 @@
},
"outputs": ["{workspaceRoot}/coverage/apps/cart"]
},
- "deploy": {
+ "serve": {
"executor": "nx:run-commands",
"options": {
"commands": [
- {
- "command": "npx ts-node --project tools/tsconfig.tools.json tools/scripts/deploy --siteName nrwl-nx-examples-cart --outputPath dist/apps/cart"
- }
+ "BROWSER=false netlify dev --functions=apps/cart/functions",
+ "nx serve-app cart"
]
}
+ },
+ "deploy": {
+ "dependsOn": ["build"],
+ "command": "netlify deploy --site=nx-examples-cart-test --dir dist/apps/cart --functions apps/cart/functions --prod"
}
},
"tags": ["type:app", "scope:cart"],
diff --git a/apps/cart/public/index.html b/apps/cart/public/index.html
new file mode 100644
index 00000000..29e67ca1
--- /dev/null
+++ b/apps/cart/public/index.html
@@ -0,0 +1,6 @@
+Netlify Functions
+
+ The sample function is available at
+ /.netlify/functions/hello
.
+
diff --git a/apps/cart/src/_redirects b/apps/cart/src/_redirects
index 50d93f23..6b14ac3a 100644
--- a/apps/cart/src/_redirects
+++ b/apps/cart/src/_redirects
@@ -1,3 +1,4 @@
/cart/* /index.html 200
-/ https://nrwl-nx-examples-products.netlify.com/ 301
-* https://nrwl-nx-examples-products.netlify.com/:splat 301
+/api/* https://nx-examples-cart-test.netlify.app/.netlify/functions/api/:splat 200
+/ https://nx-examples-products-test.netlify.com/cart 301
+* https://nx-examples-products-test.netlify.com/:splat 301
diff --git a/apps/cart/src/app/app.spec.tsx b/apps/cart/src/app/app.spec.tsx
index be06392e..cf8f4988 100644
--- a/apps/cart/src/app/app.spec.tsx
+++ b/apps/cart/src/app/app.spec.tsx
@@ -2,6 +2,8 @@ import { MemoryRouter } from 'react-router-dom';
import { cleanup, render } from '@testing-library/react';
+jest.doMock('@nx-example/cart/cart-page');
+// eslint-disable-next-line
import App from './app';
describe('App', () => {
diff --git a/apps/cart/src/app/app.tsx b/apps/cart/src/app/app.tsx
index 4206822d..3700fc69 100644
--- a/apps/cart/src/app/app.tsx
+++ b/apps/cart/src/app/app.tsx
@@ -3,13 +3,17 @@ import { Route, Routes } from 'react-router-dom';
import '@nx-example/shared/header';
import { CartCartPage } from '@nx-example/cart/cart-page';
+import { environment } from '../environments/environment';
export const App = () => {
return (
<>
- } />
+ }
+ />
>
);
diff --git a/apps/cart/src/environments/environment.prod.ts b/apps/cart/src/environments/environment.prod.ts
index c9669790..f1c78864 100644
--- a/apps/cart/src/environments/environment.prod.ts
+++ b/apps/cart/src/environments/environment.prod.ts
@@ -1,3 +1,4 @@
export const environment = {
production: true,
+ baseApiPath: '',
};
diff --git a/apps/cart/src/environments/environment.ts b/apps/cart/src/environments/environment.ts
index 7ed83767..704bc470 100644
--- a/apps/cart/src/environments/environment.ts
+++ b/apps/cart/src/environments/environment.ts
@@ -3,4 +3,5 @@
export const environment = {
production: false,
+ baseApiPath: 'http://localhost:8888/.netlify/functions',
};
diff --git a/apps/products/.env.serve b/apps/products/.env.serve
new file mode 100644
index 00000000..7ded5764
--- /dev/null
+++ b/apps/products/.env.serve
@@ -0,0 +1 @@
+BASE_API_PATH=/.netlify/functions
diff --git a/apps/products/functions/api/api.ts b/apps/products/functions/api/api.ts
index c4559c6a..e2460d55 100644
--- a/apps/products/functions/api/api.ts
+++ b/apps/products/functions/api/api.ts
@@ -1,5 +1,5 @@
import { Handler } from '@netlify/functions';
-import fastify, { FastifyInstance, FastifyRequest } from 'fastify';
+import fastify, { FastifyInstance } from 'fastify';
import awsLambdaFastify from '@fastify/aws-lambda';
import sensible from '@fastify/sensible';
import { products } from '@nx-example/shared/product/data';
@@ -16,7 +16,7 @@ function init() {
app.register(sensible);
app.register(cors);
// set the prefix for the netlify functions url
- app.register(routes, { prefix: '/.netlify/functions/api' });
+ app.register(routes, { prefix: `${process.env.BASE_API_PATH || ''}/api` });
return app;
}
diff --git a/apps/products/netlify.toml b/apps/products/netlify.toml
deleted file mode 100644
index 01a8bbe4..00000000
--- a/apps/products/netlify.toml
+++ /dev/null
@@ -1,15 +0,0 @@
-[build]
-# Static files from this folder will be served at the root of the site.
-publish = "public"
-[functions]
-# Directory with serverless functions, including background
-# functions, to deploy. This is relative to the base directory
-# if one has been set, or the root directory if
-# a base hasn’t been set.
-directory = "functions/"
-
-# Specifies \`esbuild\` for functions bundling, esbuild is the default for TS
-# node_bundler = "esbuild"
-
-[functions."hello*"]
-# Apply settings to any functions with a name beginning with "hello"
\ No newline at end of file
diff --git a/apps/products/project.json b/apps/products/project.json
index 302f2d54..10f752b4 100644
--- a/apps/products/project.json
+++ b/apps/products/project.json
@@ -50,14 +50,14 @@
"with": "apps/products/src/environments/environment.prod.ts"
}
],
- "optimization": true,
+ "optimization": false,
"outputHashing": "all",
- "sourceMap": false,
+ "sourceMap": true,
"namedChunks": false,
- "aot": true,
+ "aot": false,
"extractLicenses": true,
"vendorChunk": false,
- "buildOptimizer": true,
+ "buildOptimizer": false,
"budgets": [
{
"type": "initial",
@@ -73,7 +73,7 @@
},
"outputs": ["{options.outputPath}"]
},
- "serve": {
+ "serve-app": {
"executor": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "products:build"
@@ -109,31 +109,19 @@
},
"outputs": ["{workspaceRoot}/coverage/apps/products"]
},
- "deploy": {
+
+ "serve": {
"executor": "nx:run-commands",
"options": {
"commands": [
- {
- "command": "npx ts-node --project tools/tsconfig.tools.json tools/scripts/deploy --siteName nrwl-nx-examples-products --outputPath dist/apps/products"
- }
+ "BROWSER=false netlify dev --functions=apps/products/functions",
+ "nx serve-app products"
]
}
},
- "serve-functions": {
- "command": "npx netlify dev"
- },
- "deploy-functions": {
- "dependsOn": ["lint"],
- "command": "npx netlify deploy",
- "options": {
- "cwd": "apps/products"
- },
- "configurations": {
- "production": {
- "command": "npx netlify deploy --prod",
- "cwd": "apps/products"
- }
- }
+ "deploy": {
+ "dependsOn": ["build"],
+ "command": "netlify deploy --site=nx-examples-products-test --dir dist/apps/products --functions apps/products/functions --prod"
}
},
"tags": ["type:app", "scope:products"],
diff --git a/apps/products/src/_redirects b/apps/products/src/_redirects
index 7cbf76be..3cfa59e9 100644
--- a/apps/products/src/_redirects
+++ b/apps/products/src/_redirects
@@ -1,2 +1,3 @@
-/cart https://nrwl-nx-examples-cart.netlify.com/cart 301
-/* /index.html 200
+/api/* https://nx-examples-products-test.netlify.app/.netlify/functions/api/:splat 200
+/cart https://nx-examples-cart-test.netlify.com/cart 301
+/* /index.html 200
diff --git a/apps/products/src/app/app.module.ts b/apps/products/src/app/app.module.ts
index 854b3446..db81db7f 100644
--- a/apps/products/src/app/app.module.ts
+++ b/apps/products/src/app/app.module.ts
@@ -5,6 +5,7 @@ import { RouterModule } from '@angular/router';
import { StoreModule } from '@ngrx/store';
import { AppComponent } from './app.component';
import { EffectsModule } from '@ngrx/effects';
+import { environment } from '../environments/environment';
@NgModule({
declarations: [AppComponent],
@@ -33,7 +34,7 @@ import { EffectsModule } from '@ngrx/effects';
StoreModule.forRoot({}),
EffectsModule.forRoot(),
],
- providers: [],
+ providers: [{ provide: 'BASE_API_PATH', useValue: environment.baseApiPath }],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
diff --git a/apps/products/src/environments/environment.prod.ts b/apps/products/src/environments/environment.prod.ts
index c9669790..f1c78864 100644
--- a/apps/products/src/environments/environment.prod.ts
+++ b/apps/products/src/environments/environment.prod.ts
@@ -1,3 +1,4 @@
export const environment = {
production: true,
+ baseApiPath: '',
};
diff --git a/apps/products/src/environments/environment.ts b/apps/products/src/environments/environment.ts
index 99c3763c..16f9a302 100644
--- a/apps/products/src/environments/environment.ts
+++ b/apps/products/src/environments/environment.ts
@@ -4,6 +4,7 @@
export const environment = {
production: false,
+ baseApiPath: 'http://localhost:8888/.netlify/functions',
};
/*
diff --git a/libs/cart/cart-page/src/lib/cart-cart-page/cart-cart-page.spec.tsx b/libs/cart/cart-page/src/lib/cart-cart-page/cart-cart-page.spec.tsx
index 496df785..019d76ad 100644
--- a/libs/cart/cart-page/src/lib/cart-cart-page/cart-cart-page.spec.tsx
+++ b/libs/cart/cart-page/src/lib/cart-cart-page/cart-cart-page.spec.tsx
@@ -1,7 +1,31 @@
+import { useReducer } from 'react';
+jest.doMock('./cart-page-hooks', () => {
+ return {
+ useProducts: (): ReturnType => {
+ const [cartState, dispatchCart] = useReducer(cartReducer, {
+ items: products.map((p) => ({ productId: p.id, quantity: 1 })),
+ });
+ const [productsState, dispatchProducts] = useReducer(productsReducer, {
+ products,
+ });
+
+ return [
+ { cart: cartState, products: productsState },
+ { cart: dispatchCart, products: dispatchProducts },
+ ];
+ },
+ };
+});
+
import { cleanup, fireEvent, render } from '@testing-library/react';
+import { products } from '@nx-example/shared/product/data';
+
+import { cartReducer } from '@nx-example/shared/cart/state/react';
+import { productsReducer } from '@nx-example/shared/product/state/react';
import CartCartPage from './cart-cart-page';
+import { useProducts } from './cart-page-hooks';
describe(' CartCartPage', () => {
afterEach(cleanup);
@@ -11,9 +35,9 @@ describe(' CartCartPage', () => {
});
it('should render products', () => {
- expect(
- render().baseElement.querySelectorAll('li figure').length
- ).toEqual(5);
+ const { baseElement } = render();
+
+ expect(baseElement.querySelectorAll('li figure').length).toEqual(5);
});
it('should render a total', () => {
diff --git a/libs/cart/cart-page/src/lib/cart-cart-page/cart-cart-page.tsx b/libs/cart/cart-page/src/lib/cart-cart-page/cart-cart-page.tsx
index 384ca9a1..1e5b2891 100644
--- a/libs/cart/cart-page/src/lib/cart-cart-page/cart-cart-page.tsx
+++ b/libs/cart/cart-page/src/lib/cart-cart-page/cart-cart-page.tsx
@@ -1,21 +1,16 @@
-import { useReducer } from 'react';
-
import styled from '@emotion/styled';
import '@nx-example/shared/product/ui';
import {
CartItem,
- cartReducer,
getItemCost,
getTotalCost,
SetQuantity,
+ CheckoutSuccess,
} from '@nx-example/shared/cart/state/react';
-import {
- getProduct,
- initialState,
- productsReducer,
-} from '@nx-example/shared/product/state/react';
+import { getProduct } from '@nx-example/shared/product/state/react';
+import { useProducts } from './cart-page-hooks';
const StyledUl = styled.ul`
display: flex;
@@ -80,60 +75,78 @@ const StyledTotalLi = styled.li`
const optionsArray = new Array(5).fill(null);
-export const CartCartPage = () => {
- const [productsState] = useReducer(productsReducer, initialState);
- const { products } = productsState;
- const [cartState, dispatch] = useReducer(cartReducer, {
- items: products.map((product) => ({
- productId: product.id,
- quantity: 1,
- })),
- });
+export const CartCartPage = (props) => {
+ const [state, dispatch] = useProducts(props.baseUrl);
+
+ const handleCheckout = () => {
+ fetch('/api/checkout', {
+ method: 'POST',
+ body: JSON.stringify(state.cart.items),
+ headers: {
+ 'content-type': 'application/json',
+ },
+ })
+ .then((r) => r.json())
+ .then((r) => {
+ dispatch.cart(new CheckoutSuccess(r.orderId));
+ });
+ };
return (
- {cartState.items.map((item: CartItem) => (
-
-
-
-
-
- {getProduct(productsState, item.productId).name}
-
-
-
-
-
-
-
-
-
- ))}
+ {state.products.products.length > 0 &&
+ state.cart.items.length > 0 &&
+ state.cart.items.map((item: CartItem) => (
+
+
+
+
+
+ {getProduct(state.products, item.productId)?.name}
+
+
+
+
+
+
+
+
+
+ ))}
Total
+
+ {state.cart.orderId ? (
+ Checkout Success! Order ID: {state.cart.orderId}
+ ) : (
+
+ )}
+
);
};
diff --git a/libs/cart/cart-page/src/lib/cart-cart-page/cart-page-hooks.ts b/libs/cart/cart-page/src/lib/cart-cart-page/cart-page-hooks.ts
new file mode 100644
index 00000000..bbdf4775
--- /dev/null
+++ b/libs/cart/cart-page/src/lib/cart-cart-page/cart-page-hooks.ts
@@ -0,0 +1,36 @@
+import { Product } from '@nx-example/shared/product/types';
+import { useEffect, useReducer } from 'react';
+import { cartReducer, SetItems } from '@nx-example/shared/cart/state/react';
+import {
+ initialState,
+ productsReducer,
+ LoadProductsSuccess,
+} from '@nx-example/shared/product/state/react';
+
+export const useProducts = (baseUrl: string) => {
+ const [cartState, dispatchCart] = useReducer(cartReducer, {
+ items: [],
+ });
+ const [productsState, dispatchProducts] = useReducer(
+ productsReducer,
+ initialState
+ );
+
+ const cb = (products: Product[]): void => {
+ dispatchProducts(new LoadProductsSuccess(products));
+ dispatchCart(
+ new SetItems(products.map((p) => ({ productId: p.id, quantity: 1 })))
+ );
+ };
+
+ useEffect(() => {
+ fetch(baseUrl + '/api/products')
+ .then((r) => r.json())
+ .then(cb);
+ }, []);
+
+ return [
+ { cart: cartState, products: productsState },
+ { cart: dispatchCart, products: dispatchProducts },
+ ] as const;
+};
diff --git a/libs/products/home-page/src/lib/home-page/home-page.component.spec.ts b/libs/products/home-page/src/lib/home-page/home-page.component.spec.ts
index 87d1216a..1435d7b0 100644
--- a/libs/products/home-page/src/lib/home-page/home-page.component.spec.ts
+++ b/libs/products/home-page/src/lib/home-page/home-page.component.spec.ts
@@ -2,11 +2,15 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
-import { StoreModule } from '@ngrx/store';
-
-import { SharedProductStateModule } from '@nx-example/shared/product/state';
+import {
+ createMockProductService,
+ SharedProductStateModule,
+} from '@nx-example/shared/product/state';
+import { products } from '@nx-example/shared/product/data';
import { HomePageComponent } from './home-page.component';
+import { StoreModule } from '@ngrx/store';
+import { EffectsModule } from '@ngrx/effects';
describe('HomePageComponent', () => {
let component: HomePageComponent;
@@ -15,12 +19,17 @@ describe('HomePageComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
- StoreModule.forRoot({}),
RouterTestingModule,
+ StoreModule.forRoot({}),
+ EffectsModule.forRoot(),
SharedProductStateModule,
],
declarations: [HomePageComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ providers: [
+ createMockProductService(products),
+ { provide: 'BASE_API_PATH', useValue: '' },
+ ],
}).compileComponents();
}));
diff --git a/libs/products/home-page/src/lib/home-page/home-page.component.ts b/libs/products/home-page/src/lib/home-page/home-page.component.ts
index d57240eb..34ab565d 100644
--- a/libs/products/home-page/src/lib/home-page/home-page.component.ts
+++ b/libs/products/home-page/src/lib/home-page/home-page.component.ts
@@ -26,7 +26,6 @@ export class HomePageComponent implements OnInit {
constructor(private store: Store) {}
ngOnInit() {
- console.log('cmp init');
this.store.dispatch(new LoadProducts());
}
}
diff --git a/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.html b/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.html
index ecf457f9..5199e886 100644
--- a/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.html
+++ b/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.html
@@ -12,8 +12,7 @@ {{ product.name }}
>
-
-
+ Buy
diff --git a/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.scss b/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.scss
index 98fbca89..d287aa12 100644
--- a/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.scss
+++ b/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.scss
@@ -25,3 +25,10 @@ img {
max-width: 100%;
max-height: 100%;
}
+
+a.buy-link {
+ padding: 0.5rem 1rem;
+ border-radius: 4px;
+ background-color: #143055;
+ color: #eee;
+}
diff --git a/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.spec.ts b/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.spec.ts
index 0de2f97b..cca8a372 100644
--- a/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.spec.ts
+++ b/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.spec.ts
@@ -5,9 +5,14 @@ import { ActivatedRoute } from '@angular/router';
import { StoreModule } from '@ngrx/store';
import { of } from 'rxjs';
-import { SharedProductStateModule } from '@nx-example/shared/product/state';
+import {
+ createMockProductService,
+ SharedProductStateModule,
+} from '@nx-example/shared/product/state';
+import { products } from '@nx-example/shared/product/data';
import { ProductDetailPageComponent } from './product-detail-page.component';
+import { EffectsModule } from '@ngrx/effects';
class MockActivatedRoute {
paramMap = of(new Map([['productId', '1']]));
@@ -19,12 +24,18 @@ describe('ProductDetailPageComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
- imports: [StoreModule.forRoot({}), SharedProductStateModule],
+ imports: [
+ StoreModule.forRoot(),
+ EffectsModule.forRoot(),
+ SharedProductStateModule,
+ ],
providers: [
{
provide: ActivatedRoute,
useClass: MockActivatedRoute,
},
+ createMockProductService(products),
+ { provide: 'BASE_API_PATH', useValue: '' },
],
declarations: [ProductDetailPageComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
diff --git a/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.ts b/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.ts
index c20bd27b..77fd92e5 100644
--- a/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.ts
+++ b/libs/products/product-detail-page/src/lib/product-detail-page/product-detail-page.component.ts
@@ -1,4 +1,4 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { select, Store } from '@ngrx/store';
@@ -7,6 +7,7 @@ import { concatMap, map } from 'rxjs/operators';
import {
getProduct,
getProductsState,
+ LoadProducts,
ProductsPartialState,
} from '@nx-example/shared/product/state';
import '@nx-example/shared/product/ui';
@@ -16,7 +17,7 @@ import '@nx-example/shared/product/ui';
templateUrl: './product-detail-page.component.html',
styleUrls: ['./product-detail-page.component.scss'],
})
-export class ProductDetailPageComponent {
+export class ProductDetailPageComponent implements OnInit {
product = this.route.paramMap.pipe(
map((paramMap) => paramMap.get('productId')),
concatMap((productId) =>
@@ -27,4 +28,8 @@ export class ProductDetailPageComponent {
private store: Store,
private route: ActivatedRoute
) {}
+
+ ngOnInit(): void {
+ this.store.dispatch(new LoadProducts());
+ }
}
diff --git a/libs/products/product-detail-page/src/lib/products-product-detail-page.module.ts b/libs/products/product-detail-page/src/lib/products-product-detail-page.module.ts
index 3d77c3b1..833a1b9f 100644
--- a/libs/products/product-detail-page/src/lib/products-product-detail-page.module.ts
+++ b/libs/products/product-detail-page/src/lib/products-product-detail-page.module.ts
@@ -10,7 +10,6 @@ import { ProductDetailPageComponent } from './product-detail-page/product-detail
imports: [
CommonModule,
SharedProductStateModule,
-
RouterModule.forChild([
{ path: ':productId', component: ProductDetailPageComponent },
]),
diff --git a/libs/shared/cart/state/src/lib/+state/cart.actions.ts b/libs/shared/cart/state/src/lib/+state/cart.actions.ts
index 3cff41aa..bc824c50 100644
--- a/libs/shared/cart/state/src/lib/+state/cart.actions.ts
+++ b/libs/shared/cart/state/src/lib/+state/cart.actions.ts
@@ -1,8 +1,15 @@
import type { Action } from '@ngrx/store';
export enum CartActionTypes {
- /* eslint-disable @typescript-eslint/no-shadow */
SetQuantity = '[Cart] Set Quantity',
+ SetItems = '[Cart] Set Items',
+ Checkout = '[Cart] Checkout Success',
+}
+
+export class SetItems implements Action {
+ readonly type = CartActionTypes.SetItems;
+
+ constructor(public items: { productId: string; quantity: number }[]) {}
}
export class SetQuantity implements Action {
@@ -11,4 +18,10 @@ export class SetQuantity implements Action {
constructor(public productId: string, public quantity: number) {}
}
-export type CartAction = SetQuantity;
+export class CheckoutSuccess implements Action {
+ readonly type = CartActionTypes.Checkout;
+
+ constructor(public orderId: string) {}
+}
+
+export type CartAction = SetQuantity | SetItems | CheckoutSuccess;
diff --git a/libs/shared/cart/state/src/lib/+state/cart.reducer.ts b/libs/shared/cart/state/src/lib/+state/cart.reducer.ts
index 0583a02d..0e82554b 100644
--- a/libs/shared/cart/state/src/lib/+state/cart.reducer.ts
+++ b/libs/shared/cart/state/src/lib/+state/cart.reducer.ts
@@ -9,6 +9,7 @@ export interface CartItem {
export interface CartState {
items: CartItem[];
+ orderId?: string;
}
export interface CartPartialState {
@@ -21,6 +22,15 @@ export const initialState: CartState = {
export const cartReducer = (state: CartState, action: CartAction) => {
switch (action.type) {
+ case CartActionTypes.Checkout: {
+ return {
+ ...state,
+ orderId: action.orderId,
+ };
+ }
+ case CartActionTypes.SetItems: {
+ return { items: action.items };
+ }
case CartActionTypes.SetQuantity: {
return {
...state,
diff --git a/libs/shared/product/state/src/index.ts b/libs/shared/product/state/src/index.ts
index bd3bb6ec..3cbbb86d 100644
--- a/libs/shared/product/state/src/index.ts
+++ b/libs/shared/product/state/src/index.ts
@@ -6,3 +6,4 @@ export * from './lib/+state/products.selectors';
export * from './lib/+state/products.actions';
export const getProductsState = createFeatureSelector(PRODUCTS_FEATURE_KEY);
export * from './lib/shared-product-state.module';
+export * from './lib/+state/products.effects';
diff --git a/libs/shared/product/state/src/lib/+state/products.effects.ts b/libs/shared/product/state/src/lib/+state/products.effects.ts
index e9462894..b2eb206e 100644
--- a/libs/shared/product/state/src/lib/+state/products.effects.ts
+++ b/libs/shared/product/state/src/lib/+state/products.effects.ts
@@ -1,7 +1,21 @@
-import { Injectable } from '@angular/core';
+import { Inject, Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { LoadProductsSuccess, ProductsActionTypes } from './products.actions';
-import { exhaustMap } from 'rxjs';
+import { exhaustMap, map, of } from 'rxjs';
+import { Product } from '@nx-example/shared/product/types';
+
+@Injectable({ providedIn: 'root' })
+export class ProductsService {
+ constructor(
+ private http: HttpClient,
+ @Inject('BASE_API_PATH') private baseUrl: string
+ ) {}
+
+ getProducts() {
+ return this.http.get(`${this.baseUrl}/products`);
+ }
+}
@Injectable({ providedIn: 'root' })
export class ProductsEffects {
@@ -9,14 +23,27 @@ export class ProductsEffects {
this.actions$.pipe(
ofType(ProductsActionTypes.LoadProducts),
exhaustMap(() =>
- fetch('http://localhost:8888/.netlify/functions/api/products')
- .then((r) => r.json())
- .then((p) => new LoadProductsSuccess(p))
+ this.productsService
+ .getProducts()
+ .pipe(map((p) => new LoadProductsSuccess(p)))
)
)
);
- constructor(private actions$: Actions) {
- console.log('ProductsEffects created');
- }
+ constructor(
+ private actions$: Actions,
+ private productsService: ProductsService
+ ) {}
}
+
+export const createMockProductService = (products: Product[]) => {
+ class MockProductsService {
+ getProducts() {
+ return of(products);
+ }
+ }
+ return {
+ provide: ProductsService,
+ useClass: MockProductsService,
+ };
+};
diff --git a/libs/shared/product/state/src/lib/+state/products.reducer.spec.ts b/libs/shared/product/state/src/lib/+state/products.reducer.spec.ts
index b2821f72..6e08d278 100644
--- a/libs/shared/product/state/src/lib/+state/products.reducer.spec.ts
+++ b/libs/shared/product/state/src/lib/+state/products.reducer.spec.ts
@@ -1,7 +1,7 @@
-import { Action } from '@ngrx/store';
import { mockProducts } from '@nx-example/shared/product/data/testing';
import { productsReducer, ProductsState } from './products.reducer';
+import { ProductsAction } from './products.actions';
describe('Products Reducer', () => {
let productsState: ProductsState;
@@ -14,7 +14,7 @@ describe('Products Reducer', () => {
describe('unknown action', () => {
it('should return the initial state', () => {
- const action = {} as Action;
+ const action = {} as ProductsAction;
const result = productsReducer(productsState, action);
expect(result).toBe(productsState);
diff --git a/libs/shared/product/state/src/lib/+state/products.reducer.ts b/libs/shared/product/state/src/lib/+state/products.reducer.ts
index 520c69c0..4dd3606b 100644
--- a/libs/shared/product/state/src/lib/+state/products.reducer.ts
+++ b/libs/shared/product/state/src/lib/+state/products.reducer.ts
@@ -1,6 +1,6 @@
import { Product } from '@nx-example/shared/product/types';
-import { ProductsAction } from './products.actions';
+import { ProductsAction, ProductsActionTypes } from './products.actions';
export const PRODUCTS_FEATURE_KEY = 'products';
@@ -20,11 +20,10 @@ export function productsReducer(
state: ProductsState = initialState,
action: ProductsAction
): ProductsState {
- console.log('productsReducer', action.type);
switch (action.type) {
- case '[Products] Load Products Success':
+ case ProductsActionTypes.LoadProductsSuccess:
return { products: action.products };
- case '[Products] Load Products':
+ case ProductsActionTypes.LoadProducts:
return { products: [] };
default: {
return state;
diff --git a/libs/shared/product/state/src/react.ts b/libs/shared/product/state/src/react.ts
index fa1cb475..90a1f7f2 100644
--- a/libs/shared/product/state/src/react.ts
+++ b/libs/shared/product/state/src/react.ts
@@ -1,2 +1,3 @@
export * from './lib/+state/products.reducer';
export * from './lib/+state/products.selectors';
+export * from './lib/+state/products.actions';
diff --git a/netlify.toml b/netlify.toml
deleted file mode 100644
index acfd3e7d..00000000
--- a/netlify.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-[build]
-base = "apps/products"
diff --git a/tools/scripts/deploy.ts b/tools/scripts/deploy.ts
deleted file mode 100644
index 4e8d2ff3..00000000
--- a/tools/scripts/deploy.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { existsSync } from 'fs';
-import * as minimist from 'minimist';
-import * as NetlifyClient from 'netlify';
-import { join } from 'path';
-
-const token = process.env.NETLIFY_AUTH_TOKEN;
-
-const argv = minimist(process.argv.slice(2));
-
-const netlifyClient = new NetlifyClient(token);
-const root = join(__dirname, '../..');
-const outDir = join(root, argv.outputPath);
-
-if (!existsSync(outDir)) {
- throw new Error(`${outDir} does not exist`);
-}
-
-(async () => {
- try {
- const sites = await netlifyClient.listSites();
- const site = sites.find((s) => argv.siteName === s.name);
- if (!site) {
- throw Error(`Could not find site ${argv.siteName}`);
- }
- console.log(`Deploying ${argv.siteName} to Netlify...`);
- const deployResult = await netlifyClient.deploy(site.id, outDir);
- console.log(
- `\n🚀 New version of ${argv.siteName} is running at ${deployResult.deploy.ssl_url}!\n`
- );
- } catch (e) {
- console.error('Authentication Failure: Invalid Token');
- }
-})();