Skip to content

Commit 5914e0e

Browse files
committed
add shopping cart #63
1 parent 576610b commit 5914e0e

File tree

15 files changed

+375
-124
lines changed

15 files changed

+375
-124
lines changed

src/dotnet/api-gateway/Middleware/XsrfMiddleware.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@ await ctx.Response.WriteAsJsonAsync(new
6565
await next(ctx);
6666
});
6767
}
68-
}
68+
}

src/dotnet/shopping-cart/Core/Dtos/CartItemDto.cs

-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,4 @@ public class CartItemDto
1212
public string ProductImagePath { get; set; } = default!;
1313
public Guid InventoryId { get; set; }
1414
public string InventoryLocation { get; set; } = default!;
15-
public string InventoryWebsite { get; set; } = default!;
16-
public string InventoryDescription { get; set; } = default!;
1715
}

src/dotnet/shopping-cart/Core/Dtos/ProductDto.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ public class ProductDto
77
public double Price { get; set; }
88
public string ImageUrl { get; set; } = default!;
99
public string Description { get; set; } = default!;
10-
public CategoryDto Category { get; set; } = default!;
11-
public InventoryDto Inventory { get; set; } = default!;
10+
11+
public string InventoryId { get; set; } = default!;
12+
public string InventoryLocation { get; set; } = default!;
13+
public string CategoryId { get; set; } = default!;
14+
public string CategoryName { get; set; } = default!;
1215
}

src/dotnet/shopping-cart/Infrastructure/Extensions/CartDtoExtensions.cs

+2-4
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@ public static async Task<CartDto> InsertItemToCartAsync(this CartDto cart,
1818
item.ProductPrice = product.Price;
1919
item.ProductImagePath = product.ImageUrl;
2020
item.ProductDescription = product.Description;
21-
item.InventoryId = product.Inventory.Id;
22-
item.InventoryLocation = product.Inventory.Location;
23-
item.InventoryWebsite = product.Inventory.Website;
24-
item.InventoryDescription = product.Inventory.Description;
21+
item.InventoryId = product.InventoryId.ConvertTo<Guid>();
22+
item.InventoryLocation = product.InventoryLocation;
2523
}
2624

2725
cart.Items.Add(item);

src/dotnet/shopping-cart/UseCases/CreateShoppingCartWithProduct/CreateShoppingCartWithProductHandler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public async Task<CartDto> Handle(CreateShoppingCartWithProductCommand request,
3030
{
3131
var currentUserId = _securityContextAccessor.UserId;
3232

33-
var cart = new CartDto {UserId = currentUserId};
33+
var cart = new CartDto {Id = NewGuid(), UserId = currentUserId};
3434

3535
await cart.InsertItemToCartAsync(request.Quantity, request.ProductId, _productCatalogGateway);
3636
await cart.CalculateCartAsync(_productCatalogGateway, _shippingGateway, _promoGateway);

src/web/app/components/AppFooter.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { ReactElement } from "react";
22

33
function FooterContent(): ReactElement {
44
return (
5-
<div className="remix-app__footer-content">
6-
<p className="text-center">&copy; Saigon Devs Group</p>
5+
<div className="remix-app__footer-content h-10">
6+
<p className="text-red-500 font-bold text-center">&copy; Saigon Devs Group</p>
77
</div>
88
);
99
}

src/web/app/components/AppHeader.tsx

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PropsWithChildren, ReactElement } from "react";
22
import { ShoppingCartIcon } from "@heroicons/react/outline";
3+
import { Link } from "@remix-run/react";
34

45
type LoaderData = { userInfo: any };
56

@@ -9,26 +10,26 @@ function AppHeader({ userInfo }: PropsWithChildren<LoaderData>): ReactElement {
910
<div className="container mx-auto">
1011
<div className="flex items-center justify-between border-b-2 border-gray-100 py-6 md:justify-start md:space-x-10">
1112
<div className="flex justify-start lg:w-0 lg:flex-1">
12-
<a href="/">
13+
<Link to="/" prefetch="intent">
1314
<span className="sr-only">CoolStore</span>
1415
<img
1516
className="h-8 w-auto sm:h-10"
1617
src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg"
1718
alt=""
1819
/>
19-
</a>
20+
</Link>
2021
</div>
2122

2223
<div className="hidden items-center justify-end md:flex md:flex-1 lg:w-0">
23-
<span>{userInfo?.name}</span>
24-
<li className="mt-4 block align-middle font-sans text-black hover:text-gray-700 lg:mt-0 lg:ml-6 lg:inline-block">
25-
<a href="#" role="button" className="relative flex">
24+
<span className="font-bold text-red-500">{userInfo?.name}</span>
25+
<span className="mt-4 block align-middle font-sans text-black hover:text-gray-700 lg:mt-0 lg:ml-6 lg:inline-block">
26+
<Link to="/cart" role="button" className="relative flex">
2627
<ShoppingCartIcon className="h-8 w-8 flex-1 fill-current"></ShoppingCartIcon>
2728
<span className="top right absolute right-0 top-0 m-0 h-4 w-4 rounded-full bg-red-600 p-0 text-center font-mono text-sm leading-tight text-white">
2829
0
2930
</span>
30-
</a>
31-
</li>
31+
</Link>
32+
</span>
3233
{userInfo == null && (
3334
<a
3435
href="https://localhost:5000/login"

src/web/app/lib/auth.ts

+97-6
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,36 @@ export type ProductSearchResult = {
3636
page: number
3737
}
3838

39+
export type CartModel = {
40+
id: string
41+
userId: string
42+
cartItemTotal: number
43+
cartItemPromoSavings: number
44+
shippingTotal: number
45+
shippingPromoSavings: number
46+
cartTotal: number
47+
isCheckOut: boolean
48+
items: CartItem[]
49+
}
50+
51+
export type CartItem = {
52+
quantity: number
53+
price: number
54+
productId: string
55+
productName: string
56+
productPrice: number
57+
productDescription: string
58+
productImagePath: string
59+
inventoryId: string
60+
inventoryLocation: string
61+
inventoryDescription: string
62+
inventoryWebsite: string
63+
}
64+
3965
const API_URL = "https://localhost:5000";
4066
const PRODUCT_URL = `${API_URL}/api-gw/product-catalog/api/products`;
4167
const PRODUCT_SEARCH_URL = `${API_URL}/api-gw/product-catalog/api/products/search`;
68+
const CART_URL = `${API_URL}/api-gw/shopping-cart/api/carts`;
4269

4370
const axios = Axios.create({
4471
httpsAgent: new https.Agent({
@@ -75,14 +102,16 @@ export const getUserInfo = async (request: Request) => {
75102
}
76103

77104
export async function searchProduct(request: Request, query: string, price: number, page: number, pageSize: number = 10) {
78-
const { data } = await axios.get<any>(
105+
const response = await axios.get<any>(
79106
`${PRODUCT_SEARCH_URL}/${price}/${page}/${pageSize}`
80107
, {
81108
headers: {
82109
cookie: request.headers.get("Cookie")?.toString()
83110
} as any
84111
});
85112

113+
const { data } = response;
114+
86115
const result = {
87116
products: data.results,
88117
categoryTags: data.categoryTags,
@@ -91,21 +120,17 @@ export async function searchProduct(request: Request, query: string, price: numb
91120
totalItem: data.total
92121
} as ProductSearchResult;
93122

94-
//console.log(result)
95-
return result;
123+
return { productSearchResult: result };
96124
}
97125

98126
export async function getProductById(request: Request, id: string) {
99-
console.log("product_id", id);
100127
const { data } = await axios.get<any>(
101128
`${PRODUCT_URL}/${id}`
102129
, {
103130
headers: {
104131
cookie: request.headers.get("Cookie")?.toString()
105132
} as any
106133
});
107-
108-
// console.log(data as ProductDetailModel);
109134
return data as ProductDetailModel;
110135
}
111136

@@ -118,3 +143,69 @@ export async function createUserSession(userId: string, redirectTo: string) {
118143
},
119144
});
120145
}
146+
147+
export async function getCartForCurrentUser(request: Request) {
148+
const cookie = request.headers.get("Cookie")?.toString()!;
149+
const response = await axios.get<any>(
150+
CART_URL
151+
, {
152+
headers: {
153+
cookie: cookie
154+
} as any
155+
});
156+
157+
const { data } = response;
158+
const xsrfToken = convertCookie(cookie)['XSRF-TOKEN'];
159+
160+
console.log(data as CartModel);
161+
return { cartData: data as CartModel, csrf: xsrfToken };
162+
}
163+
164+
export async function updateCartForCurrentUser(request: Request, productId: string) {
165+
const userData = await getUserInfo(request);
166+
const { cartData, csrf } = await getCartForCurrentUser(request);
167+
const cookie = request.headers.get("Cookie")?.toString();
168+
169+
if (cartData.id === null) {
170+
// create new cart
171+
const { data } = await axios.post<any>(
172+
CART_URL,
173+
{
174+
productId: productId,
175+
userId: userData.userId,
176+
quantity: 1,
177+
},
178+
{
179+
headers: {
180+
cookie: cookie,
181+
"X-XSRF-TOKEN": csrf,
182+
} as any
183+
});
184+
return data as CartModel;
185+
} else {
186+
// update cart
187+
const { data } = await axios.put<any>(
188+
CART_URL,
189+
{
190+
productId: productId,
191+
quantity: 1,
192+
},
193+
{
194+
headers: {
195+
cookie: cookie,
196+
"X-XSRF-TOKEN": csrf,
197+
} as any
198+
});
199+
return data as CartModel;
200+
}
201+
}
202+
203+
function convertCookie(cookie: string) {
204+
const str: string[] | any = cookie?.toString().split('; ');
205+
const result: any = {};
206+
for (let i in str) {
207+
const cur = str[i].split('=');
208+
result[cur[0]] = cur[1];
209+
}
210+
return result;
211+
}

src/web/app/root.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export let loader: LoaderFunction = async ({ request }) => {
3232
const userInfo = await getUserInfo(request);
3333
if (userInfo != null) {
3434
const session = await createUserSession(userInfo.sub, "/");
35-
console.info("root-cookie", session.headers.get("set-cookie"));
35+
// console.info("root-cookie", session.headers.get("set-cookie"));
3636
}
3737
return null;
3838
};
@@ -126,7 +126,7 @@ export function ErrorBoundary({ error }: { error: Error }) {
126126
<hr />
127127
<p>
128128
Hey, developer, you should replace this with what you want your
129-
users to see. <a href="https://localhost:5000/login">Log In</a> to the system please!
129+
users to see. <a href="https://localhost:5000/login" className="text-red-500">Log In</a> to the system please!
130130
</p>
131131
</div>
132132
</Layout>

src/web/app/routes/cart/index.tsx

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { LoaderFunction } from "@remix-run/node";
2+
import { useLoaderData } from "@remix-run/react";
3+
import SiteLayout from "~/components/SiteLayout";
4+
import { getUserInfo, getCartForCurrentUser } from "~/lib/auth";
5+
6+
type LoaderData = { userInfo: any };
7+
8+
export const loader: LoaderFunction = async ({ request }) => {
9+
const userInfo = await getUserInfo(request);
10+
const data = await getCartForCurrentUser(request);
11+
return { userInfo };
12+
};
13+
14+
export default function Index() {
15+
const data = useLoaderData<LoaderData>();
16+
return (
17+
<SiteLayout userInfo={data.userInfo}>
18+
<h1>Cart</h1>
19+
</SiteLayout>
20+
);
21+
}

0 commit comments

Comments
 (0)