initial upload
This commit is contained in:
67
Client/components/CartComponent.tsx
Normal file
67
Client/components/CartComponent.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as React from 'react'
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { Product } from './types';
|
||||
import { Quantity } from './ProductsComponent';
|
||||
import { Map } from 'immutable'
|
||||
|
||||
export type CartComponentState = {
|
||||
|
||||
}
|
||||
// TODO 9: complete the implementation of the type below (1.5 pt)
|
||||
export type CartComponentProps = {
|
||||
update_user: (data: string) => void,
|
||||
user: string,
|
||||
cart: Map<Product, Quantity>,
|
||||
update_cart: (cart: Map<Product, Quantity>) => void,
|
||||
submit: () => void
|
||||
}
|
||||
|
||||
export class CartComponent extends React.Component<CartComponentProps, CartComponentState> {
|
||||
constructor(props: CartComponentProps) {
|
||||
super(props)
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return <form>
|
||||
<label>User email:</label>
|
||||
<input type="email" onChange={e => this.props.update_user(e.target.value)} value={this.props.user} required />
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<div>
|
||||
{/*
|
||||
TODO 10: complete the implementation of the type below (1.5 pt)
|
||||
NOTE: you should be able to remove an item from the cart (or decrease the amount). (hint button)
|
||||
*/}
|
||||
|
||||
{
|
||||
this.props.cart.count() == 0 ? "Your cart is empty." :
|
||||
<div>
|
||||
{
|
||||
this.props.cart.map((q, p) => {
|
||||
return <div>
|
||||
{p.Name} x {q} <button onClick={() => {
|
||||
let q1 = this.props.cart.get(p)
|
||||
if (q1 == 1) this.props.update_cart(this.props.cart.remove(p))
|
||||
else this.props.update_cart(this.props.cart.set(p, q1 - 1))
|
||||
}}>-</button>
|
||||
</div>
|
||||
}).valueSeq()
|
||||
}
|
||||
<div>-------------</div>
|
||||
<p>Total: {this.props.cart.map((q, p) => q * p.Price).reduce((a, b) => a + b, 0)}</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{(() => {
|
||||
const regex = RegExp('[a-zA-Z]+@[a-zA-Z]+');
|
||||
let is_email = regex.test(this.props.user)
|
||||
return <button style={{ cursor: is_email ? "pointer" : "not-allowed" }} disabled={!is_email} onClick={e => {
|
||||
e.preventDefault()
|
||||
this.props.submit()
|
||||
}}>Buy</button>
|
||||
})()}
|
||||
</form>
|
||||
}
|
||||
}
|
25
Client/components/Modal.tsx
Normal file
25
Client/components/Modal.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from 'react'
|
||||
|
||||
export type ModalComponentProps = {
|
||||
close: () => void,
|
||||
text: string
|
||||
}
|
||||
export type ModalComponentState = {
|
||||
}
|
||||
export class ModalComponent extends React.Component<ModalComponentProps, ModalComponentState> {
|
||||
constructor(props: ModalComponentProps) {
|
||||
super(props)
|
||||
|
||||
this.state = {}
|
||||
}
|
||||
componentWillMount() {
|
||||
}
|
||||
public render() {
|
||||
return <div id="myModal" className="modal row" style={{ display: "block" }}>
|
||||
<div className="modal-content">
|
||||
<span className="close" onClick={this.props.close}>×</span>
|
||||
<p>Order status: {this.props.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
52
Client/components/OrdersComponent.tsx
Normal file
52
Client/components/OrdersComponent.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import * as React from 'react'
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import ReactJson from 'react-json-view'
|
||||
|
||||
export type OrdersComponentState = {
|
||||
orders: { kind: "loaded", value: string } | { kind: "loading" } | { kind: "error-or-not-found" } | { kind: "none" }
|
||||
}
|
||||
export type OrdersComponentProps = {}
|
||||
|
||||
export class OrdersComponent extends React.Component<RouteComponentProps<OrdersComponentProps>, OrdersComponentState> {
|
||||
constructor(props: RouteComponentProps<OrdersComponentProps>) {
|
||||
super(props)
|
||||
this.state = {
|
||||
orders:{kind:"none"}
|
||||
}
|
||||
}
|
||||
getOrders() {
|
||||
this.setState(s => ({ ...s, orders: { kind: "loading" } }), () => {
|
||||
fetch(`/Cart/GetOrders`,
|
||||
{
|
||||
method: 'get', credentials: 'include',
|
||||
headers: { 'content-type': 'application/json' }
|
||||
}).then(async res => {
|
||||
try {
|
||||
if (!res.ok)
|
||||
this.setState(s => ({ ...s, orders: { kind: "error-or-not-found" } }))
|
||||
let res1 = await res.json()
|
||||
this.setState(s => ({ ...s, orders: { kind: "loaded", value: JSON.stringify(res1) } }))
|
||||
}
|
||||
catch {
|
||||
this.setState(s => ({ ...s, orders: { kind: "error-or-not-found" } }))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.getOrders()
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.state.orders.kind != "loaded") {
|
||||
return <p>{this.state.orders.kind}</p>
|
||||
}
|
||||
|
||||
return <div className="main-order">
|
||||
<h2>Orders</h2>
|
||||
{/* Not necessary for the exam */}
|
||||
<ReactJson src={JSON.parse(this.state.orders.value)} />
|
||||
</div>
|
||||
}
|
||||
}
|
90
Client/components/ProductComponent.tsx
Normal file
90
Client/components/ProductComponent.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import * as React from 'react'
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { Product } from './types';
|
||||
|
||||
|
||||
export type ProductRouteComponentProps = {}
|
||||
export class ProductRouteComponent extends React.Component<RouteComponentProps<ProductRouteComponentProps>, ProductComponentState> {
|
||||
render() {
|
||||
let p_id = this.props.match.params.id
|
||||
p_id == "" ? undefined : isNaN(p_id) ? undefined : +this.props.product_id
|
||||
return <ProductComponent product_id={p_id} />
|
||||
}
|
||||
}
|
||||
|
||||
export type ProductComponentProps = {
|
||||
product_id?: number
|
||||
product?: Product
|
||||
add_product?: () => void
|
||||
}
|
||||
|
||||
export type ProductComponentState = {
|
||||
product?: { kind: "loaded", product: Product } | { kind: "loading" } | { kind: "error-or-not-found" }
|
||||
}
|
||||
|
||||
export class ProductComponent extends React.Component<ProductComponentProps, ProductComponentState> {
|
||||
constructor(props: ProductComponentProps) {
|
||||
super(props)
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
getProduct() {
|
||||
{/* TODO 11: complete the implementation of the type below (1 pt) */}
|
||||
|
||||
fetch(`/Cart/GetProduct/${this.props.product_id}`, {
|
||||
method: "GET", credentials: "include"
|
||||
})
|
||||
|
||||
.then(async (response) => {
|
||||
try {
|
||||
if (!response.ok) {
|
||||
this.setState(s => ({ ...s, product: { kind: "error-or-not-found"} }));
|
||||
}
|
||||
let responseData = await response.json();
|
||||
this.setState(s => ({ ...s, product: { kind: "loaded", product: responseData }}));
|
||||
} catch (error) {
|
||||
this.setState(s => ({ ...s, product: { kind: "error-or-not-found"} }));
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
if (this.props.product != undefined) {
|
||||
this.setState(s => ({ ...s, product: { kind: "loaded", product: this.props.product } }))
|
||||
return
|
||||
}
|
||||
else if (this.props.product_id) {
|
||||
this.setState(s => ({ ...s, product: { kind: "loading" } }), () => {
|
||||
this.getProduct()
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.setState(s => ({ ...s, product: { kind: "error-or-not-found" } }))
|
||||
}
|
||||
}
|
||||
public render() {
|
||||
if (this.state.product.kind != "loaded") {
|
||||
return <p>{this.state.product.kind}</p>
|
||||
}
|
||||
|
||||
return <div className='container-fluid row'>
|
||||
{/*
|
||||
TODO 12: complete the implementation of the type below (1 pt)
|
||||
NOTE: when rendering a product you should be able to add it to the cart (hint button)
|
||||
*/}
|
||||
|
||||
<div className='col-sm-6'>
|
||||
<h3>Product</h3>
|
||||
<div>Id:<span>{this.state.product.product.Id}</span></div>
|
||||
<div>Name:<span>{this.state.product.product.Name}</span></div>
|
||||
<div>Price:<span>{this.state.product.product.Price}</span></div>
|
||||
</div>
|
||||
|
||||
<div className='col-sm-6'>
|
||||
{this.props.add_product ? <button style={{ height: "70px", marginTop: "50px" }} onClick={this.props.add_product}>+</button> : null}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
}
|
123
Client/components/ProductsComponent.tsx
Normal file
123
Client/components/ProductsComponent.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import * as React from 'react'
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { CartComponent } from './CartComponent';
|
||||
import { ProductComponent } from './ProductComponent';
|
||||
import { Product, User } from './types';
|
||||
import { Map } from 'immutable'
|
||||
import { ModalComponent } from './Modal';
|
||||
|
||||
export type Quantity = number
|
||||
export type ProductsComponentState = {
|
||||
products: { kind: "loaded", products: Product[] } | { kind: "loading" } | { kind: "error-or-not-found" } | { kind: "none" }
|
||||
user: string,
|
||||
cart: Map<Product, Quantity>
|
||||
submit_status: { kind: "processing" } | { kind: "order_completed" } | { kind: "error" } | { kind: "none" }
|
||||
}
|
||||
export type ProductsComponentProps = {}
|
||||
|
||||
export class ProductsComponent extends React.Component<RouteComponentProps<ProductsComponentProps>, ProductsComponentState> {
|
||||
constructor(props: RouteComponentProps<ProductsComponentProps>) {
|
||||
super(props)
|
||||
this.state = {
|
||||
user: "",
|
||||
cart: Map(),
|
||||
submit_status: { kind: "none" },
|
||||
products: { kind: "none" }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
getProducts() {
|
||||
this.setState(s => ({ ...s, products: { kind: "loading" } }), () => {
|
||||
fetch(`/Cart/GetProducts`,
|
||||
{
|
||||
method: 'get', credentials: 'include',
|
||||
headers: { 'content-type': 'application/json' }
|
||||
}).then(async res => {
|
||||
try {
|
||||
if (!res.ok)
|
||||
this.setState(s => ({ ...s, products: { kind: "error-or-not-found" } }))
|
||||
let res1 = await res.json()
|
||||
this.setState(s => ({ ...s, products: { kind: "loaded", products: res1 } }))
|
||||
}
|
||||
catch {
|
||||
this.setState(s => ({ ...s, products: { kind: "error-or-not-found" } }))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
placeOrder() {
|
||||
let data_to_sent = {
|
||||
user: { Name: this.state.user } as User,
|
||||
products: this.state.cart.map((q, p) => ({ product: p, quantity: q })).valueSeq().toArray()
|
||||
}
|
||||
fetch(`/Cart/PlaceOrder`,
|
||||
{
|
||||
method: 'put', credentials: 'include',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(data_to_sent)
|
||||
}).then(async res => {
|
||||
try {
|
||||
if (!res.ok)
|
||||
this.setState(s => ({ ...s, submit_status: { kind: "error" } }))
|
||||
let res1 = await res.json()
|
||||
this.setState(s => ({ ...s, submit_status: { kind: "order_completed" } }))
|
||||
}
|
||||
catch {
|
||||
this.setState(s => ({ ...s, submit_status: { kind: "error" } }))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.getProducts()
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
||||
if (this.state.products.kind != "loaded") {
|
||||
return <p>{this.state.products.kind}</p>
|
||||
}
|
||||
|
||||
|
||||
return <div className='container-fluid row'>
|
||||
<div className="row">
|
||||
<div className='col-sm-6'>
|
||||
<h2>Products</h2>
|
||||
{this.state.products.products.map(p => <ProductComponent key={p.Id} product={p} product_id={p.Id} add_product={
|
||||
() => this.setState(s => {
|
||||
let s1 = { ...s }
|
||||
|
||||
if (!s1.cart.has(p)) {
|
||||
s1.cart = s1.cart.set(p, 0)
|
||||
}
|
||||
|
||||
s1.cart = s1.cart.set(p, s1.cart.get(p) + 1)
|
||||
|
||||
return s1
|
||||
})
|
||||
} />)}
|
||||
</div>
|
||||
|
||||
<div className='col-sm-6'>
|
||||
<CartComponent user={this.state.user}
|
||||
cart={this.state.cart}
|
||||
submit={() => {
|
||||
this.setState(s => ({ ...s, submit_status: { kind: "processing" } }), () => {
|
||||
this.placeOrder()
|
||||
})
|
||||
}}
|
||||
update_cart={c => this.setState(s1 => ({ ...s1, cart: c }))}
|
||||
update_user={s => this.setState(s1 => ({ ...s1, user: s }))} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.submit_status.kind == "none" ? null : <ModalComponent
|
||||
text={this.state.submit_status.kind}
|
||||
close={() => this.setState(s => ({ ...s, submit_status: { kind: "none" } }))} />
|
||||
}
|
||||
|
||||
</div>
|
||||
}
|
||||
}
|
22
Client/components/layout.tsx
Normal file
22
Client/components/layout.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export interface LayoutProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export class Layout extends React.Component<LayoutProps, {}> {
|
||||
public render() {
|
||||
return <div>
|
||||
<div>
|
||||
<div className="sidenav">
|
||||
<Link to="/">Home</Link>
|
||||
<Link to="/orders">Orders</Link>
|
||||
</div>
|
||||
<div>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
12
Client/components/routes.tsx
Normal file
12
Client/components/routes.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { Layout } from './Layout';
|
||||
import { ProductRouteComponent } from './ProductComponent';
|
||||
import { ProductsComponent } from './ProductsComponent';
|
||||
import { OrdersComponent } from './OrdersComponent';
|
||||
|
||||
export const routes = <Layout>
|
||||
<Route exact path='/'component={ ProductsComponent } />
|
||||
<Route exact path='/products/:id' component={ ProductRouteComponent } />
|
||||
<Route exact path='/orders' component={ OrdersComponent } />
|
||||
</Layout>;
|
10
Client/components/types.ts
Normal file
10
Client/components/types.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
export type User = {
|
||||
Id: number,
|
||||
Name: string,
|
||||
}
|
||||
export type Product = {
|
||||
Id: number,
|
||||
Name: string,
|
||||
Price: number
|
||||
}
|
Reference in New Issue
Block a user