Skip to content

10 Frontend React

Node.js

  • Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine.
  • Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
  • Node.js package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
  • https://nodejs.org (Download LTS Version)

NPM

  • npm is the package manager for JavaScript and the world's largest software registry.
  • discover packages of reusable code -- and assemble them in powerful new ways.

https://docs.npmjs.com/cli/install

React

https://github.com/facebook/create-react-app

npx create-react-app frontend

cd frontend

npm start

Screenshot 2023-12-14 at 18.44.47

Screenshot 2023-12-14 at 18.45.27

  • Install component library

npm add antd at command line

Screenshot 2023-12-14 at 18.47.15

On the top of src/index.css file, add this line

@import '~antd/dist/antd.css';

  • Setup proxy

In the package.json file (there should be only one file with this name under your project folder), add "proxy": "http://localhost:8080"

proxy 是代理的意思,这里的意思是将请求代理到 http://localhost:8080 上,这样就可以解决跨域的问题了。

Create a file at this path, /src/utils.js

utils.js

export const login = (credential) => {
    // login 是一个函数,接受一个参数 credential
    // credential 是一个对象,包含 username 和 password 两个属性
    // 例如:{ username: 'Jerry', password: '123' }
    const loginUrl = `/login?username=${credential.username}&password=${credential.password}`;
    // loginUrl 是一个字符串,例如:'/login?username=Jerry&password=123'
    // 请注意,这里的 username 和 password 都是从 credential 对象中取出的

    return fetch(loginUrl, {
        // fetch 是浏览器提供的一个 API,用于发起 HTTP 请求
        method: 'POST', // HTTP 请求的方法是 POST
        headers: { // HTTP 请求的头部信息
            'Content-Type': 'application/json', // HTTP 请求的内容类型是 JSON
        },
        credentials: 'include', // fetch 默认不会发送 Cookie,需要设置 credentials 为 'include'
        // 请注意,这里的 body 是一个对象,需要转换成 JSON 字符串
    }).then((response) => {
        // fetch 的返回值是一个 Promise 对象
        if (response.status < 200 || response.status >= 300) {
            // 如果 HTTP 响应的状态码不是 2xx,就表示请求失败
            throw Error("Fail to log in");
        }
    });
};

export const signup = (data) => {
    // signup 是一个函数,接受一个参数 data
    // data 是一个对象,包含 username 和 password 两个属性
    const signupUrl = '/signup';
    // signupUrl 是一个字符串,表示注册的 API 接口
    // 例如:'/signup'

    return fetch(signupUrl, { // 使用 fetch 发起 HTTP 请求
        method: 'POST', 
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
    }).then((response) => {
        if (response.status < 200 || response.status >= 300) {
            throw Error("Fail to sign up");
        }
    });
};

export const getMenus = (restId) => {
    return fetch(`/restaurant/${restId}/menu`).then((response) => {
        if (response.status < 200 || response.status >= 300) {
            throw Error("Fail to get menus");
        }
        return response.json();
    });
};

export const getRestaurants = () => {
    return fetch("/restaurants").then((response) => {
        if (response.status < 200 || response.status >= 300) {
            throw Error("Fail to get restaurants");
        }
        return response.json();
    });
};

export const getCart = () => {
    return fetch("/cart").then((response) => {
        if (response.status < 200 || response.status >= 300) {
            throw Error("Fail to get shopping cart data");
        }
        return response.json();
    });
};

export const checkout = () => {
    return fetch("/checkout").then((response) => {
        if (response.status < 200 || response.status >= 300) {
            throw Error("Fail to checkout");
        }
    });
};

export const addItemToCart = (itemId) => {
    return fetch(`/cart/${itemId}`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        credentials: 'include',
    }).then((response) =>{
        if (response.status < 200 || response.status >= 300) {
            throw Error("Fail to add item to cart");
        }
    });
};

Build the skeleton/layout of the app

find what we need here https://ant.design/components/layout/#header

  • Delete the whole src/App.css file, won't need it

  • Remove everything but the first line in src/index.css file, so it only has

@import '~antd/dist/reset.css';

Use what we found on Ant Design's doc on src/App.js

import logo from './logo.svg';
import './App.css';
import {Content, Header} from "antd/es/layout/layout"
import {Layout} from "antd"

function App() {
  return (
      <Layout style={{ minHeight: '100vh' }}>
        <Header>header</Header>
        <Content
            style={{
              padding: '50px',
              maxHeight: "calc(100vh - 64px)",
                overflow: "auto",
            }}
            >
          content
        </Content>
      </Layout>
  );
}

export default App;

Build login form

need a form to collect user input and execute a login request

src/components/LoginForm.js

import {login} from "../utils";
import {Button, Form, Input, message} from "antd";
import {UserOutlined} from "@ant-design/icons";
import React from "react";


class LoginForm extends React.Component{
    // React.Component 是 React 的基类,用于定义组件类
    state = {
        // state 是组件内部的状态管理机制,用于存储组件内部的状态
        // 通过 this.state 访问
        loading: false,
        // loading 用于标识当前是否正在登录中
        // 通过 this.state.loading 访问
        // false 表示当前没有正在登录中的请求
    };

    onFinish = (data) => {
        // onFinish 是 Form 组件的回调函数,当用户点击登录按钮时,会调用 onFinish 函数
        // onFinish 函数的参数 data 是一个对象,包含了用户输入的用户名和密码
        this.setState({
            // setState 是 React 组件的内置方法,用于更新组件的 state
            // 通过 this.setState 更新 state
            loading: true, // 设置 loading 为 true,表示当前正在登录中
        });
        // 调用 login 函数,向服务器发送登录请求
        login(data)
            .then((res) => { // 登录成功
                message.success(`Login Successful`);
                this.props.onSuccess(); // 调用父组件传递过来的 onSuccess 函数
                // onSuccess 函数用于更新 App 组件的 state,将 isLogin 设置为 true
                // 从而触发 App 组件的 componentDidUpdate 函数
                // props 是组件的属性,通过 this.props 访问
            })
            .catch((err) => { // 登录失败
                message.error(`err.message`);
                // message 是 antd 组件库的消息提示组件
            })
            .finally(() => {
                // 无论登录成功还是失败,loading 都应该设置为 false
                this.setState({
                    // setState 是 React 组件的内置方法,用于更新组件的 state
                    // 通过 this.setState 更新 state
                    loading: false, // 设置 loading 为 false,表示当前没有正在登录中
                });
            });
    };

    render() { // render 函数用于渲染组件的内容
        return(
            <Form // Form 是 antd 组件库的表单组件
                name="normal_login" // 表单的名称
                onFinish={this.onFinish} // 表单提交的回调函数
                style={{ // 表单的样式
                    width: 300,
                    margin: "auto", // 居中显示
                }}
            >
                <Form.Item
                    name="username"
                    rules={[{
                        required: true,
                        message: "Please input your Username!",
                    }]}// 表单验证规则 rules
                    // rules 是一个数组,数组中包含了多个表单验证规则
                    // required 表示该表单项是必填项
                >
                    <Input prefix={<UserOutlined />} placeholder="Username" />
                    {/*// Input 是 antd 组件库的输入框组件*/}
                    {/*// prefix 表示输入框前面的图标*/}
                    {/*// placeholder 表示输入框的提示文字*/}
                    {/*// <UserOutlined /> 是 antd 组件库的图标组件*/}

                </Form.Item>

                <Form.Item
                    name="password"
                    rules={[{ required: true,
                        message: "Please input your Password!"
                    }]}
                >
                    <Input prefix={<UserOutlined />} type="password" placeholder="Password" />
                </Form.Item>

                <Form.Item>
                    <Button type="primary" htmlType="submit"
                        loading={this.state.loading}
                    >
                        Login
                    </Button>
                </Form.Item>
            </Form>
        );
    };
}

export default LoginForm;

Build signup form

similarly we need a UI to support sign up

src/components/SignupForm.js

import {signup} from "../utils"
import {Button, Form, Input, message} from "antd"

class SignupForm extends React.Component{
    state = {
        displayModel: false,
    };

    handleCancle = () => {
        this.setState({
            displayModel: false,
        });
    };

    signupOnClick = () => {
        this.setState({
            displayModel: true,
        });
    }

    onFinish = (data) => {
        signup(data)
            .then(() => {
                this.setState({
                    displayModel: false,
                });
                message.success(`Successfully signed up`);
            })
            .catch((err) => {
                message.error(err.message);
            });
    };


    render() { // render 函数用于渲染组件的内容
        return(
            <>
                <Button shape = "round" type="primary" onClick={this.signupOnClick}>
                    Register
                </Button>
                <Model
                    title="Register"
                    visible={this.state.displayModel}
                    onCancel={this.handleCancle}
                    footer={null}
                    destroyOnClose={true}
                >
                    <Form
                        name="normal_register"
                        initialValues={{remember: true}}
                        onFinish={this.onFinish}
                        preserve={false}
                    >
                        <Form.Item
                            name="email"
                            rules={[{ required: true, message: 'Please input your email!' }]}
                            >
                            <Input prefix={<UserOutlined />} placeholder="Email" />
                        </Form.Item>
                        <Form.Item
                            name="password"
                            rules={[{
                                required: true,
                                message: 'Please input your password!',
                            }]}
                        >
                            <Input prefix={<LockOutlined />} type="password" placeholder="Password" />
                        </Form.Item>
                        <Form.Item
                            name="firstName"
                            rules={[{
                                required: true,
                                message: 'Please input your first name!' ,
                            }]}
                        >
                            <Input placeholder="first Name" />
                        </Form.Item>
                        <Form.Item
                            name="lastName"
                            rules={[
                                {  required: true,
                                    message: 'Please input your last name!' ,
                                }
                                ]}
                        >
                            <Input placeholder="last Name" />
                        </Form.Item>

                        <Form.Item>
                            <Button type="primary" htmlType="submit">
                                Register
                            </Button>
                        </Form.Item>
                    </Form>
                </Model>
            </>
        );
    };
}

export default SignupForm;

src/App.js

import {Layout, Typography} from "antd"
import {useState} from "react"
import LoginForm from "./components/LoginForm"

const { Header, Content } = Layout;
//  const 是 ES6 的语法,用于定义变量
// Header, Content 是 antd 组件库的组件
// Layout 是 antd 组件库的布局组件
// 通过 import 语句引入 antd 组件库的组件
const { Title } = Typography;
// Typography 是 antd 组件库的排版组件
// Title 是 antd 组件库的标题组件

function App() {
    const [authed, setAuthed] = useState(false);
    // useState 是 React 的内置 Hook 函数,用于定义组件的 state
    // authed 是 state 的名称,用于存储用户的登录状态
    // setAuthed 是用于更新 authed 的函数
    // useState(false) 表示 authed 的初始值为 false
    return (
        <Layout style={{ minHeight: '100vh' }}>
            <Header>
                <div className="header">
                    <Title level={2}
                           style={{color: "white", lineHeight: "inherit", marginBottom: 0}}
                    >
                        Eve Food
                    </Title>
                </div>
            </Header>
            <Content
                style={{
                    padding: '50px',
                    maxHeight: "calc(100vh - 64px)",
                    overflow: "auto",
                }}
            >
                {
                    authed ?
                        (<div>content placeholder</div>)
                        :
                        (<LoginForm onSuccess={() => setAuthed(true)}/> )
                }
            </Content>
      </Layout>
    );
}

export default App;

Build FoodList

src/components/FoodList.js

import {Button, Card, List, message, Select, Tooltip} from "antd"
import {addItemToCart, getMenus, getRestaurants} from "../utils"
import {useEffect, useState} from "react"

const { Option } = Select;

const AddToCartButton = ({ itemId }) => {
    const [loading, setLoading] = useState(false);

    const AddToCart = () => {
        setLoading(true);
        addItemToCart(itemId)
            .then(() => message.success("Successfully add item"))
            .catch((err) => message.error(err.message))
            .finally(() => {
                setLoading(false);
            });
    };

    return (
        <Tooltip title="Add to Cart">
            <Button
                loading={loading}
                type="primary"
                icon={<PlusOutlined />}
                onClick={AddToCart}
            />
            </Tooltip>
    );
};

const FoodList = () => {
    const [foodData, setFoodData] = useState([]);
    const [curRest, setCurRest] = useState(null);
    const [restaurants, setRestaurants] = useState([]);
    const [loading, setLoadings] = useState(false);
    const [loadingRest, setLoadingRest] = useState(false);

    useEffect(() => {
        setLoadingRest(true);
        getRestaurants()
            .then((data) => {
                setRestaurants(data);
            })
            .catch((err) => {
                message.error(err.message);
            })
            .finally(() => {
                setLoadingRest(false);
            });
    }, []);

    useEffect(() => {
        if (curRest) {
            setLoadings(true);
            getMenus(curRest)
                .then((data) => {
                    setFoodData(data);
                })
                .catch((err) => {
                    message.error(err.message);
                })
                .finally(() => {
                    setLoadings(false);
                });
        }
    }, [curRest]);

    return (
        <>
            <Select
                value={curRest}
                onSelect={(value) => setCurRest(value)}
                placeholder="Select a restaurant"
                loading={loadingRest}
                style={{ width: 300 }}
                onChange={() => {}}
            >
                {restaurants.map((item) => {
                    return (
                        <Option key={item.id} value={item.id}>
                            {item.name}
                        </Option>
                    );
                })}
            </Select>
            {curRest && (
                <List
                    style={{ marginTop: 20 }}
                    loading={loading}
                    grid={{
                        gutter: 16,
                        xs: 1,
                        sm: 2,
                        md: 4,
                        lg: 4,
                        xl: 3,
                        xxl: 3,
                    }}
                    dataSource={foodData}
                    renderItem={(item) => (
                        <List.Item>
                            <Card
                                title={item.name}
                                extra={<AddToCartButton itemId={item.id} />}
                            >
                                <img
                                    src={item.imageUrl}
                                    alt={item.name}
                                    style={{ height: 340, width: "100%", display: "block" }}
                                />
                                {`Price: ${item.price}`}
                            </Card>
                        </List.Item>
                    )}
                />
            )}
        </>
    );
}

export default FoodList;

Build MyCart

src/components/MyCart.js

import {Button, Drawer, List, message, Typography} from "antd";
import React, {useEffect, useState} from "react";
import {checkout, getCart} from "../utils";

const { Text } = Typography;

const MyCart = () => {
    const [cartVisible, setCartVisible] = useState(false);
    const [cartData, setCartData] = useState();
    const [loading, setLoading] = useState(false);
    const [checking, setChecking] = useState(false);

    useEffect(() => {
        if (!cartVisible) {
            return;
        }

        setLoading(true);
        getCart()
            .then((data) => {
                setCartData(data);
            })
            .catch((err) => {
                message.error(err.message);
            })
            .finally(() => {
                setLoading(false);
            });
    }, [cartVisible]);

    const onCheckout = () => {
        setChecking(true);
        checkout()
            .then(() => {
                message.success("Successfully checked out");
                setCartVisible(false);
            })
            .catch((err) => {
                message.error(err.message);
            })
            .finally(() => {
                setChecking(false);
            });
    };

    const onCloseDrawer = () => {
        setCartVisible(false);
    };

    const onOpenDrawer = () => {
        setCartVisible(true);
    };

    return (
        <>
            <Button type="primary" shape="round" onClick={onOpenDrawer}>
                Cart
            </Button>
            <Drawer
                title="My Cart"
                onClose={onCloseDrawer}
                visible={cartVisible}
                width={520}
                footer={
                    <div
                        style={{
                            display: "flex",
                            justifyContent: "space-between",
                        }}
                    >
                        <Text strong={true}>{`Total price:
                        $${cartData?.totalPrice}`}
                        </Text>
                        <div>
                            <Button onClick={onCloseDrawer} style={{ marginRight: 8 }}>
                                Cancel
                            </Button>
                            <Button
                                onClick={onCheckout}
                                type="primary"
                                loading={checking}
                                disabled={loading || cartData?.orderItemList.length === 0}
                            >
                                Checkout
                            </Button>
                        </div>
                    </div>
                }
                >
                <List
                    loading={loading}
                    itemLayout="horizontal"
                    dataSource={cartData?.orderItemList}
                    renderItem={(item) => (
                        <List.Item>
                            <List.Item.Meta
                                title={item.menuItem.name}
                                description={`$${item.price}`}
                            />
                        </List.Item>
                    )}
                />
            </Drawer>
        </>
    );
};

export default MyCart;

Integrate everything

src/app.js

import {Layout, Typography} from "antd"
import {useState} from "react"
import LoginForm from "./components/LoginForm"
import FoodList from "./components/FoodList"
import MyCart from "./components/MyCart"
import SignupForm from "./components/SignupForm"


const { Header, Content } = Layout;
//  const 是 ES6 的语法,用于定义变量
// Header, Content 是 antd 组件库的组件
// Layout 是 antd 组件库的布局组件
// 通过 import 语句引入 antd 组件库的组件
const { Title } = Typography;
// Typography 是 antd 组件库的排版组件
// Title 是 antd 组件库的标题组件

function App() {
    const [authed, setAuthed] = useState(false);
    // useState 是 React 的内置 Hook 函数,用于定义组件的 state
    // authed 是 state 的名称,用于存储用户的登录状态
    // setAuthed 是用于更新 authed 的函数
    // useState(false) 表示 authed 的初始值为 false
    return (
        <Layout style={{ minHeight: '100vh', backgroundColor: 'black'}}>
            <Header style={{ backgroundColor: 'black'}}>
                <div className="header">
                    <Title level={2}
                           style={{color: "white", lineHeight: "inherit", marginBottom: 0}}
                    >
                        Eve Restaurant Order
                    </Title>
                    <div>{authed ? <MyCart /> : <SignupForm />}</div>
                </div>
            </Header>
            <Content
                style={{
                    padding: '50px',
                    maxHeight: "calc(100% - 64px)",
                    overflowY: "auto",
                }}
            >
                {
                    authed ?
                        (<FoodList />)
                        :
                        (<LoginForm onSuccess={() => setAuthed(true)}/> )
                }
            </Content>
      </Layout>
    );
}

export default App;

Now need to build our front end code and move them to server project, then all code will be hosted on the same server.

Open the terminal, at fronted , run npm run build

Screenshot 2023-12-15 at 17.14.39

Copy all the generated file (front end) into the webapp folder under you backend project.

And int the file onlineOrder-servlet.xml , add this some thing.

Screenshot 2023-12-15 at 17.17.43

<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

       <mvc:resources mapping="/**" location="/" />
       <context:component-scan base-package="com.eve.onlineOrder" />
</beans>

Run the project http://localhost:8080