diff --git a/appsettings.js b/appsettings.js
index ebd662f..f2e673b 100644
--- a/appsettings.js
+++ b/appsettings.js
@@ -3,4 +3,6 @@ module.exports = {
oidcIssuer: process.env['OIDC_ISSUER'] ?? '',
oidcClientId: process.env['OIDC_CLIENT_ID'] ?? '',
oidcScope: process.env['OIDC_SCOPE'] ?? '',
+ backendApi: process.env['BACKEND_API'] ?? '',
+
};
diff --git a/components/DefautLayout.tsx b/components/DefautLayout.tsx
index 973c396..9a515a1 100644
--- a/components/DefautLayout.tsx
+++ b/components/DefautLayout.tsx
@@ -36,71 +36,12 @@ const DefaultLayout: React.FC<{
menu.push(
{
- key: '#menu-1',
- label: 'Menu 1',
+ key: '/main',
+ label: 'Main',
icon: ,
- children: [
- {
- key: '/dashboard',
- label: 'Dashboard',
- onClick: () => router.push('/dashboard')
- },
- {
- key: '/sub-menu-b',
- label: 'Sub Menu B',
- onClick: () => router.push('/')
- },
- {
- key: '/sub-menu-c',
- label: 'Sub Menu C',
- onClick: () => router.push('/')
- }
- ]
},
- {
- key: '#menu-2',
- label: 'Menu 2',
- icon: ,
- children: [
- {
- key: '/sub-menu-d',
- label: 'Sub Menu D',
- onClick: () => router.push('/')
- },
- {
- key: '/sub-menu-e',
- label: 'Sub Menu E',
- onClick: () => router.push('/')
- },
- {
- key: '/sub-menu-f',
- label: 'Sub Menu F',
- onClick: () => router.push('/')
- }
- ]
- },
- {
- key: '#menu-3',
- label: 'Menu 3',
- icon: ,
- children: [
- {
- key: '/sub-menu-g',
- label: 'Sub Menu G',
- onClick: () => router.push('/')
- },
- {
- key: '/sub-menu-h',
- label: 'Sub Menu H',
- onClick: () => router.push('/')
- },
- {
- key: '/sub-menu-i',
- label: 'Sub Menu I',
- onClick: () => router.push('/')
- }
- ]
- }
+
+
);
if (status === 'authenticated') {
@@ -124,7 +65,7 @@ const DefaultLayout: React.FC<{
icon: ,
onClick: () => {
nProgress.start();
- signIn('oidc');
+ router.push('/login')
}
});
}
diff --git a/pages/api/be/[...apiGateway].ts b/pages/api/be/[...apiGateway].ts
index 3dea920..1fbacc9 100644
--- a/pages/api/be/[...apiGateway].ts
+++ b/pages/api/be/[...apiGateway].ts
@@ -5,7 +5,7 @@ import { AppSettings } from '../../../functions/AppSettings';
// Great way to avoid using CORS and making API calls from HTTPS pages to back-end HTTP servers
// Recommendation for projects in Kubernetes cluster: set target to Service DNS name instead of public DNS name
const server = Proxy.createProxyServer({
- target: AppSettings.current.backendApiHost,
+ target: AppSettings.current.backendApi,
// changeOrigin to support name-based virtual hosting
changeOrigin: true,
xfwd: true,
@@ -23,7 +23,7 @@ server.on('proxyReq', (proxyReq, req) => {
}
proxyReq.removeHeader('cookie');
// console.log(JSON.stringify(proxyReq.getHeaders(), null, 4));
- console.log('API Proxy:', req.url, '-->', AppSettings.current.backendApiHost + urlRewrite);
+ console.log('API Proxy:', req.url, '-->', AppSettings.current.backendApi+ urlRewrite);
});
const apiGateway = async (req: NextApiRequest, res: NextApiResponse) => {
diff --git a/pages/createOrder/index.tsx b/pages/createOrder/index.tsx
new file mode 100644
index 0000000..21f5620
--- /dev/null
+++ b/pages/createOrder/index.tsx
@@ -0,0 +1,139 @@
+import React, { useState } from "react";
+import { WithDefaultLayout } from "@/components/DefautLayout";
+import { Page } from "@/types/Page";
+
+const CreateOrderPage: Page = () => {
+ const [description, setDescription] = useState("");
+ const [orderFrom, setOrderFrom] = useState("");
+ const [orderTo, setOrderTo] = useState("");
+ const [quantity, setQuantity] = useState("");
+ const [orderAt, setOrderAt] = useState("");
+ const [errorMessage, setErrorMessage] = useState("");
+
+ const handleSubmit = async (event: React.FormEvent) => {
+ event.preventDefault();
+
+ try {
+ if (!description || !orderFrom || !orderTo || !quantity || !orderAt) {
+ throw new Error("Please fill in all fields.");
+ }
+
+ if (description.length > 100) {
+ throw new Error("Description must be maximum 100 characters long.");
+ }
+
+ const parsedQuantity = parseInt(quantity);
+ if (isNaN(parsedQuantity) || parsedQuantity <= 0) {
+ throw new Error("Quantity must be a positive number.");
+ }
+
+ if (!isValidDate(orderAt)) {
+ throw new Error(
+ "Invalid date format for Order At. Please use YYYY-MM-DD format."
+ );
+ }
+
+ const response = await fetch("/api/be/api/v1/Order/CreateOrder", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ description,
+ orderFrom,
+ orderTo,
+ quantity: parsedQuantity,
+ orderAt,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to add product.");
+ }
+
+ // Reset form fields and error message on successful submission
+ setDescription("");
+ setOrderFrom("");
+ setOrderTo("");
+ setQuantity("");
+ setOrderAt("");
+ setErrorMessage("");
+ } catch (error) {
+ if (error instanceof Error) {
+ setErrorMessage(error.message);
+ } else {
+ setErrorMessage("An unknown error occurred.");
+ }
+ }
+ };
+
+ const isValidDate = (dateString: string): boolean => {
+ const regex = /^\d{4}-\d{2}-\d{2}$/;
+ return regex.test(dateString);
+ };
+
+ return (
+
+
+
Add Product
+
+ {errorMessage &&
{errorMessage}
}
+
+
+ );
+};
+CreateOrderPage.layout = WithDefaultLayout;
+export default CreateOrderPage;
diff --git a/pages/login/index.tsx b/pages/login/index.tsx
new file mode 100644
index 0000000..0d9f84e
--- /dev/null
+++ b/pages/login/index.tsx
@@ -0,0 +1,89 @@
+import React from "react";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+import { useRouter } from "next/router";
+
+const schema = z.object({
+ email: z.string().nonempty(),
+ password: z.string().min(4, "Password at least 4 characters"),
+});
+
+type FormData = z.infer;
+
+const LoginPage: React.FC = () => {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(schema),
+ });
+ const router = useRouter();
+
+ const onSubmit = async (data: FormData) => {
+ try {
+ const response = await fetch("/api/be/api/v1/Auth/Login", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ });
+
+ if (!response.ok) {
+ throw new Error("Login failed. Please check your credentials.");
+ }
+ console.log("Login successful!");
+ } catch (error: any) {
+ console.error("Login error:", error.message);
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default LoginPage;
diff --git a/pages/main.tsx b/pages/main.tsx
new file mode 100644
index 0000000..0e144f7
--- /dev/null
+++ b/pages/main.tsx
@@ -0,0 +1,423 @@
+import React, { useState, useEffect } from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { WithDefaultLayout } from "@/components/DefautLayout";
+import { Page } from "@/types/Page";
+import {
+ faFilter,
+ faEye,
+ faPenToSquare,
+ faTrash,
+ faChevronLeft,
+ faChevronRight,
+ faPlus,
+ faTimes,
+} from "@fortawesome/free-solid-svg-icons";
+import { useRouter } from "next/router";
+
+const ViewPage: Page = () => {
+ const [data, setData] = useState([]);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [itemsPerPage] = useState(5);
+ const [selectedOrder, setSelectedOrder] = useState(null);
+ const [modalVisible, setModalVisible] = useState(false);
+ const [editModalVisible, setEditModalVisible] = useState(false);
+ const [editedOrder, setEditedOrder] = useState({});
+ const [validationErrors, setValidationErrors] = useState({});
+ const [description, setDescription] = useState("");
+ const [descriptionError, setDescriptionError] = useState("");
+ const router = useRouter();
+
+ useEffect(() => {
+ fetchData();
+ }, []);
+
+ const fetchData = async () => {
+ try {
+ const fetchedData = [];
+ for (let i = 1; i <= 24; i++) {
+ const response = await fetch(`/api/be/api/v1/Order/OrderDetail/${i}`);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch data for ID ${i}`);
+ }
+ const responseData = await response.json();
+ fetchedData.push(responseData);
+ }
+ setData(fetchedData);
+ } catch (error) {
+ console.error("Error fetching data:", error);
+ }
+ };
+
+ const nextPage = () => setCurrentPage(currentPage + 1);
+ const prevPage = () => setCurrentPage(currentPage - 1);
+
+ const deleteItem = async (id) => {
+ try {
+ const response = await fetch(`/api/be/api/v1/Order/DeleteOrder/${id}`, {
+ method: "DELETE",
+ });
+ if (!response.ok) {
+ throw new Error(`Failed to delete item with ID ${id}`);
+ }
+ const newData = data.filter((item) => item.orderId !== id);
+ setData(newData);
+ // Adjust current page if it exceeds the new total number of pages
+ const totalPages = Math.ceil(newData.length / itemsPerPage);
+ if (currentPage > totalPages) {
+ setCurrentPage(totalPages);
+ }
+ } catch (error) {
+ console.error("Error deleting item:", error);
+ }
+ };
+
+ const openModal = (item) => {
+ setSelectedOrder(item);
+ setModalVisible(true);
+ };
+
+ const openEditModal = (item) => {
+ setEditedOrder({
+ orderId: item.orderId,
+ orderName: item.orderName,
+ orderFrom: item.orderFrom,
+ orderTo: item.orderTo,
+ quantity: item.quantity,
+ });
+ setEditModalVisible(true);
+ };
+
+ const closeModal = () => {
+ setSelectedOrder(null);
+ setModalVisible(false);
+ };
+
+ const closeEditModal = () => {
+ setEditedOrder({});
+ setValidationErrors({});
+ setEditModalVisible(false);
+ };
+
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setEditedOrder({ ...editedOrder, [name]: value });
+ };
+
+ const handleDescriptionChange = (e) => {
+ const { value } = e.target;
+ setDescription(value);
+ if (!value.trim()) {
+ setDescriptionError("Description cannot be empty");
+ } else if (value.length > 100) {
+ setDescriptionError("Description must be at most 100 characters");
+ } else {
+ setDescriptionError("");
+ }
+ };
+
+ const updateOrder = async () => {
+ try {
+ if (
+ !editedOrder.orderFrom ||
+ !editedOrder.orderTo ||
+ !description.trim() ||
+ description.length > 100
+ ) {
+ setValidationErrors({
+ orderFrom: !editedOrder.orderFrom
+ ? "Order From cannot be empty"
+ : null,
+ orderTo: !editedOrder.orderTo ? "Order To cannot be empty" : null,
+ description: !description.trim()
+ ? "Description cannot be empty"
+ : description.length > 100
+ ? "Description must be at most 100 characters"
+ : null,
+ });
+ return;
+ }
+ const response = await fetch("/api/be/api/v1/Order/UpdateOrder", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ ...editedOrder,
+ description,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to update order");
+ }
+ const updatedData = data.map((item) =>
+ item.orderId === selectedOrder.orderId
+ ? { ...item, ...editedOrder, description }
+ : item
+ );
+ setData(updatedData);
+ setEditModalVisible(false);
+ router.push("/main");
+ } catch (error) {
+ console.error("Error updating order:", error);
+ }
+ };
+
+ const indexOfLastItem = currentPage * itemsPerPage;
+ const indexOfFirstItem = indexOfLastItem - itemsPerPage;
+ const currentItems = data.slice(indexOfFirstItem, indexOfLastItem);
+ const totalPages = Math.ceil(data.length / itemsPerPage);
+
+ return (
+
+ {currentItems.length > 0 && (
+
+
+
+ |
+ No.
+ |
+
+ Order Name
+ |
+
+ Order From
+ |
+
+ Order To
+ |
+
+ Order At
+ |
+
+ Quantity
+ |
+ Action |
+
+
+
+ {currentItems.map((item, index) => (
+
+ | {item.orderId} |
+ Order {item.orderId} |
+ {item.orderFrom} |
+ {item.orderTo} |
+ {item.orderedAt} |
+ {item.quantity} |
+
+ openModal(item)}
+ />
+ openEditModal(item)}
+ />
+ deleteItem(item.orderId)}
+ />
+ |
+
+ ))}
+
+
+ )}
+ {modalVisible && (
+
+
+
+
+
+
+ {selectedOrder && (
+
+
Order Detail
+
+ Order ID:{" "}
+ {selectedOrder.orderId}
+
+
+ Order Name:{" "}
+ {selectedOrder.orderName}
+
+
+ Order From:{" "}
+ {selectedOrder.orderFrom}
+
+
+ Order To:{" "}
+ {selectedOrder.orderTo}
+
+
+ Ordered At:{" "}
+ {selectedOrder.orderedAt}
+
+
+ Quantity:{" "}
+ {selectedOrder.quantity}
+
+
+ )}
+
+
+
+ )}
+ {editModalVisible && (
+
+
+
+
+
+
+
Edit Order
+
+
+
+ {descriptionError && (
+
{descriptionError}
+ )}
+
+
+
+
+
+
+
+
+ {validationErrors.orderFrom && (
+
{validationErrors.orderFrom}
+ )}
+
+
+
+
+ {validationErrors.orderTo && (
+
{validationErrors.orderTo}
+ )}
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ {currentPage}
+
+
+
+
+ );
+};
+
+ViewPage.layout = WithDefaultLayout;
+export default ViewPage;
diff --git a/pages/register/index.tsx b/pages/register/index.tsx
new file mode 100644
index 0000000..ae596b5
--- /dev/null
+++ b/pages/register/index.tsx
@@ -0,0 +1,179 @@
+import { useRouter } from "next/router";
+import React, { useState } from "react";
+
+const RegisterPage: React.FC = () => {
+ const [email, setEmail] = useState("");
+ const [dob, setDob] = useState("");
+ const [gender, setGender] = useState("");
+ const [address, setAddress] = useState("");
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ const [errorMessage, setErrorMessage] = useState("");
+ const router = useRouter();
+
+ const handleSubmit = async (event: React.FormEvent) => {
+ event.preventDefault();
+
+ try {
+ if (!email || !dob || !gender || !address || !username || !password) {
+ setErrorMessage("Please fill in all fields.");
+ return;
+ }
+ if (!validateEmail(email)) {
+ setErrorMessage("Invalid email format.");
+ return;
+ }
+ const age = calculateAge(new Date(dob));
+ if (age < 14) {
+ setErrorMessage("You must be at least 14 years old to register.");
+ return;
+ }
+ if (password.length < 8 || password.length > 64) {
+ setErrorMessage("Password must be between 8 and 64 characters long.");
+ return;
+ }
+ const response = await fetch("/api/be/api/v1/Auth/Register", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ email,
+ dob,
+ gender,
+ address,
+ username,
+ password,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error("Registration failed.");
+ }
+
+ setEmail("");
+ setDob("");
+ setGender("");
+ setAddress("");
+ setUsername("");
+ setPassword("");
+ setErrorMessage("");
+
+ router.push("/login");
+ } catch (error) {
+ setErrorMessage("Registration failed. Please try again.");
+ }
+ };
+
+ const validateEmail = (email: string): boolean => {
+ const re = /\S+@\S+\.\S+/;
+ return re.test(email);
+ };
+
+ const calculateAge = (dob: Date): number => {
+ const diff = Date.now() - dob.getTime();
+ const ageDate = new Date(diff);
+ return Math.abs(ageDate.getUTCFullYear() - 1970);
+ };
+
+ return (
+
+
+
Register
+
+ {errorMessage &&
{errorMessage}
}
+
+
+ );
+};
+
+export default RegisterPage;