diff --git a/.env.development b/.env.development
index 58ed8bd..5fdce69 100644
--- a/.env.development
+++ b/.env.development
@@ -1,6 +1,6 @@
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=e01b7895a403fa7364061b2f01a650fc
-BACKEND_API_HOST=https://demo.duendesoftware.com
+BACKEND_API_HOST=https://new-dev.accelist.com:1234
OIDC_ISSUER=https://demo.duendesoftware.com
OIDC_CLIENT_ID=interactive.public.short
OIDC_SCOPE=openid profile email api offline_access
diff --git a/components/DefautLayout.tsx b/components/DefautLayout.tsx
index 973c396..aaade45 100644
--- a/components/DefautLayout.tsx
+++ b/components/DefautLayout.tsx
@@ -1,7 +1,7 @@
import React, { useState } from "react";
import Head from 'next/head';
import { Avatar, Button, ConfigProvider, Drawer, Layout, Menu, MenuProps } from "antd";
-import { faBars, faSignOut, faSignIn, faHome, faCubes, faUser, faUsers, faFlaskVial } from '@fortawesome/free-solid-svg-icons'
+import { faBars, faSignOut, faSignIn, faHome, faCubes, faUser, faUsers, faFlaskVial, faPlus } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/router";
import { useSession, signIn, signOut } from "next-auth/react";
@@ -102,36 +102,46 @@ const DefaultLayout: React.FC<{
]
}
);
-
+
if (status === 'authenticated') {
menu.push({
- key: '/sign-out',
- label: 'Sign out',
- icon: ,
- onClick: () => {
- nProgress.start();
- signOut();
- // HINT: use this method call if need to end SSO server authentication session:
- // signOut({
- // callbackUrl: '/api/end-session'
- // });
- }
+ key: '/post-order',
+ label: 'Post Order',
+ icon: ,
+ onClick: () => {
+ nProgress.start();
+ router.push('/be-orders/orderpostpage');
+ },
});
- } else {
+
menu.push({
- key: '/sign-in',
- label: 'Sign in',
- icon: ,
- onClick: () => {
- nProgress.start();
- signIn('oidc');
- }
+ key: '/sign-out',
+ label: 'Sign out',
+ icon: ,
+ onClick: () => {
+ nProgress.start();
+ signOut();
+ // HINT: use this method call if need to end SSO server authentication session:
+ // signOut({
+ // callbackUrl: '/api/end-session'
+ // });
+ },
});
+ } else {
+ menu.push({
+ key: '/sign-in',
+ label: 'Sign in',
+ icon: ,
+ onClick: () => {
+ nProgress.start();
+ signIn('oidc');
+ },
+ });
+ }
+
+ return menu;
}
- return menu;
- }
-
const displayUserName = session?.user?.name;
function renderAvatar() {
diff --git a/package-lock.json b/package-lock.json
index 9344003..4cb792d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,12 +14,13 @@
"@fortawesome/react-fontawesome": "0.2.0",
"@hookform/error-message": "2.0.1",
"@hookform/resolvers": "3.0.1",
+ "@tanstack/react-query": "^5.32.0",
"antd": "5.4.0",
"dayjs": "1.11.7",
"http-proxy": "1.18.1",
"jotai": "2.0.3",
"next": "13.3.0",
- "next-auth": "4.22.0",
+ "next-auth": "^4.22.0",
"nprogress": "0.2.0",
"openid-client": "5.4.0",
"react": "18.2.0",
@@ -671,6 +672,30 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.32.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.32.0.tgz",
+ "integrity": "sha512-Z3flEgCat55DRXU5UMwYU1U+DgFZKA3iufyOKs+II7iRAo0uXkeU7PH5e6sOH1CGEag0IpKmZxlUFpCg6roSKw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.32.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.32.0.tgz",
+ "integrity": "sha512-+E3UudQtarnx9A6xhpgMZapyF+aJfNBGFMgI459FnduEZqT/9KhOWnMOneZahLRt52yzskSA0AuOyLkXHK0yBA==",
+ "dependencies": {
+ "@tanstack/query-core": "5.32.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0"
+ }
+ },
"node_modules/@types/http-proxy": {
"version": "1.17.10",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.10.tgz",
diff --git a/package.json b/package.json
index 3d75a9a..6ab4a40 100644
--- a/package.json
+++ b/package.json
@@ -44,12 +44,13 @@
"@fortawesome/react-fontawesome": "0.2.0",
"@hookform/error-message": "2.0.1",
"@hookform/resolvers": "3.0.1",
+ "@tanstack/react-query": "^5.32.0",
"antd": "5.4.0",
"dayjs": "1.11.7",
"http-proxy": "1.18.1",
"jotai": "2.0.3",
"next": "13.3.0",
- "next-auth": "4.22.0",
+ "next-auth": "^4.22.0",
"nprogress": "0.2.0",
"openid-client": "5.4.0",
"react": "18.2.0",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 57444b8..f52162a 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -6,6 +6,7 @@ import Router from 'next/router';
import NProgress from 'nprogress';
import { SessionProvider } from 'next-auth/react';
import { SessionErrorHandler } from '../components/SessionErrorHandler';
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; // Add this line
// https://fontawesome.com/v5/docs/web/use-with/react#next-js
import { config } from '@fortawesome/fontawesome-svg-core';
@@ -26,16 +27,21 @@ function CustomApp({
Component,
pageProps: { session, ...pageProps }
}: AppPropsWithLayout): JSX.Element {
+ const queryClient = new QueryClient();
+
// https://nextjs.org/docs/basic-features/layouts#per-page-layouts
const withLayout = Component.layout ?? (page => page);
return (
- // https://next-auth.js.org/getting-started/client#sessionprovider
-
-
- {withLayout()}
-
-
+ // Wrap your application in the QueryClientProvider
+
+ {/* https://next-auth.js.org/getting-started/client#sessionprovider */}
+
+
+ {withLayout()}
+
+
+
);
}
@@ -48,3 +54,4 @@ Router.events.on('routeChangeComplete', NProgress.done);
Router.events.on('routeChangeError', NProgress.done);
export default CustomApp;
+
diff --git a/pages/be-orders/orderdeletepage.tsx b/pages/be-orders/orderdeletepage.tsx
new file mode 100644
index 0000000..01d5eda
--- /dev/null
+++ b/pages/be-orders/orderdeletepage.tsx
@@ -0,0 +1,38 @@
+import { useRouter } from 'next/router';
+import { WithDefaultLayout } from '@/components/DefautLayout';
+
+export default function DeleteOrderPage() {
+ const router = useRouter();
+ const { id } = router.query; //You get this from the URL, which you get from the MainMenu.
+
+ const handleDelete = async () => {
+ const confirmDelete = window.confirm('Are you sure you want to delete this order?');
+ if (!confirmDelete) {
+ return;
+ }
+
+ // Make a DELETE request to your API
+ const response = await fetch(`api/be/api/v1/Order/DeleteOrder/${id}`, {
+ method: 'DELETE',
+ });
+
+ if (response.ok) {
+ // If the order was successfully deleted, redirect to the main menu
+ router.push('/MainMenu');
+ } else {
+ // Handle error
+ alert('Failed to delete order');
+ }
+ };
+
+ return (
+
+
Delete Order
+
+
+ );
+}
+
+DeleteOrderPage.layout = WithDefaultLayout;
diff --git a/pages/be-orders/orderdetailpage.tsx b/pages/be-orders/orderdetailpage.tsx
new file mode 100644
index 0000000..ca0bf7b
--- /dev/null
+++ b/pages/be-orders/orderdetailpage.tsx
@@ -0,0 +1,58 @@
+import { useState, useEffect } from 'react';
+import { useRouter } from 'next/router';
+import { WithDefaultLayout } from '@/components/DefautLayout';
+
+export default function OrderDetailPage() {
+ const router = useRouter();
+ const { id } = router.query;
+ const [order, setOrder] = useState(null);
+
+ interface Order {
+ orderId: number;
+ description: string;
+ orderFrom: string;
+ orderTo: string;
+ total: number;
+ quantity: number;
+ orderedAt: string;
+ }
+
+ useEffect(() => {
+ const fetchOrder = async () => {
+ const response = await fetch(`api/be/api/v1/Order/OrderDetail/${id}`);
+ const data = await response.json();
+ setOrder(data);
+ };
+
+ if (id) {
+ fetchOrder();
+ }
+ }, [id]);
+
+ if (!order) {
+ return null;
+ }
+
+ return (
+
+
Order Detail
+
+
Order ID:
+
{order.orderId}
+
Description:
+
{order.description}
+
Order From:
+
{order.orderFrom}
+
Order To:
+
{order.orderTo}
+
Ordered At:
+
{order.orderedAt}
+
Quantity:
+
{order.quantity}
+
+
+ );
+}
+
+OrderDetailPage.layout = WithDefaultLayout;
+
diff --git a/pages/be-orders/orderpostpage.tsx b/pages/be-orders/orderpostpage.tsx
new file mode 100644
index 0000000..9bc4621
--- /dev/null
+++ b/pages/be-orders/orderpostpage.tsx
@@ -0,0 +1,110 @@
+import { useState, FormEvent } from 'react';
+import { useRouter } from 'next/router';
+import { WithDefaultLayout } from '@/components/DefautLayout';
+
+export default function PostOrderPage() {
+ const [orderFrom, setOrderFrom] = useState('');
+ const [orderTo, setOrderTo] = useState('');
+ const [total, setTotal] = useState('');
+ const [quantity, setQuantity] = useState(1);
+ const [orderedAt, setOrderedAt] = useState('');
+ const router = useRouter();
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+
+ // Add your validation logic here
+ if (!orderFrom || orderFrom.length < 1) {
+ alert('Invalid order from');
+ return;
+ }
+ if (!orderTo || orderTo.length < 1) {
+ alert('Invalid order to');
+ return;
+ }
+ if (!total || isNaN(Number(total))) {
+ alert('Invalid total');
+ return;
+ }
+ if (!quantity || quantity < 1 || quantity > 99) {
+ alert('Invalid quantity');
+ return;
+ }
+ if (!orderedAt || new Date(orderedAt) < new Date()) {
+ alert('Invalid ordered at date');
+ return;
+ }
+
+ // Make a POST request to your API
+ const response = await fetch('api/be/api/v1/Order/CreateOrder', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ orderFrom: orderFrom,
+ orderTo: orderTo,
+ total: total,
+ quantity: quantity,
+ orderedAt: orderedAt,
+ }),
+ });
+
+ if (response.ok) {
+ // If the order was successfully posted, redirect to the main menu
+ router.push('/MainMenu');
+ } else {
+ // Handle error
+ alert('Failed to post order');
+ }
+ };
+
+ return (
+
+);
+}
+
+PostOrderPage.layout = WithDefaultLayout;
+
+
diff --git a/pages/be-orders/orderupdatepage.tsx b/pages/be-orders/orderupdatepage.tsx
new file mode 100644
index 0000000..cba91e2
--- /dev/null
+++ b/pages/be-orders/orderupdatepage.tsx
@@ -0,0 +1,94 @@
+import { useState, FormEvent } from 'react';
+import { useRouter } from 'next/router';
+import { WithDefaultLayout } from '@/components/DefautLayout';
+
+export default function UpdateOrderPage() {
+ const [description, setDescription] = useState('');
+ const [orderFrom, setOrderFrom] = useState('');
+ const [orderTo, setOrderTo] = useState('');
+ const [quantity, setQuantity] = useState(1);
+ const router = useRouter();
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+
+ // Add your validation logic here
+ if (!description || description.length > 100) {
+ alert('Invalid description');
+ return;
+ }
+ if (!orderFrom || orderFrom.length < 1) {
+ alert('Invalid order from');
+ return;
+ }
+ if (!orderTo || orderTo.length < 1) {
+ alert('Invalid order to');
+ return;
+ }
+ if (!quantity || quantity < 1 || quantity > 99) {
+ alert('Invalid quantity');
+ return;
+ }
+
+ // Make a PUT request to your API
+ const response = await fetch(`api/be/api/v1/Order/UpdateOrder`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ description: description,
+ orderFrom: orderFrom,
+ orderTo: orderTo,
+ quantity: quantity,
+ }),
+ });
+
+ if (response.ok) {
+ // If the order was successfully updated, redirect to the main menu
+ router.push('/MainMenu');
+ } else {
+ // Handle error
+ alert('Failed to update order');
+ }
+ };
+
+ return (
+
+ );
+}
+
+UpdateOrderPage.layout = WithDefaultLayout;
diff --git a/pages/index.tsx b/pages/index.tsx
index 6c0943a..74c7293 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -1,15 +1,126 @@
+import { useRouter } from 'next/router';
import { WithDefaultLayout } from '../components/DefautLayout';
-import { Title } from '../components/Title';
-import { Page } from '../types/Page';
-
-const IndexPage: Page = () => {
- return (
-
-
Home
- Hello World!
-
- );
+import { useEffect, useState } from 'react';
+
+interface Order {
+ id: number;
+ name: string;
+ from: string;
+ to: string;
+ at: string;
+ quantity: number;
}
-IndexPage.layout = WithDefaultLayout;
-export default IndexPage;
+export default function MainMenu() {
+// const [orders, setOrders] = useState([]);
+
+// useEffect(() => {
+// const fetchOrders = async () => {
+// const orderIds = [1, 2, 3, 4, 5];
+
+// const fetchOrderDetails = async (id: number) => {
+// const response = await fetch(`api/be/api/v1/Order/OrderDetail/${id}`);
+// if (!response.ok) {
+// console.error(`Failed to fetch order ${id}: ${response.statusText}`);
+// return null;
+// }
+// const data = await response.json();
+// return data;
+// };
+
+// const orders = await Promise.all(orderIds.map(fetchOrderDetails));
+// setOrders(orders.filter(order => order !== null));
+// };
+
+// fetchOrders();
+// }, []);
+
+// const router = useRouter();
+
+const [orders, setOrders] = useState([]);
+
+useEffect(() => {
+ // Replace fetchOrders with dummy data
+ const dummyOrders: Order[] = [
+ { id: 1, name: 'Order 1', from: 'Location 1', to: 'Location 2', at: '2022-01-01', quantity: 10 },
+ { id: 2, name: 'Order 2', from: 'Location 2', to: 'Location 3', at: '2022-02-01', quantity: 20 },
+ { id: 3, name: 'Order 3', from: 'Location 3', to: 'Location 4', at: '2022-03-01', quantity: 30 },
+ { id: 4, name: 'Order 4', from: 'Location 4', to: 'Location 5', at: '2022-04-01', quantity: 40 },
+ { id: 5, name: 'Order 5', from: 'Location 5', to: 'Location 6', at: '2022-05-01', quantity: 50 },
+ ];
+ setOrders(dummyOrders);
+}, []);
+
+const router = useRouter();
+
+ const handleView = (order: Order) => {
+ // Navigate to the Order Detail page for the clicked order
+ router.push(`/be-orders/orderdetailpage/${order.id}`);
+ };
+
+ const handleUpdate = (order: Order) => {
+ // Handle update action here...
+ router.push(`/be-orders/orderupdatepage/${order.id}`);
+ };
+
+ const handleDelete = async (order: Order) => {
+ const confirmDelete = window.confirm('Are you sure you want to delete this order?');
+ if (!confirmDelete) {
+ return;
+ }
+
+ // Make a DELETE request to your API
+ const response = await fetch(`api/be/api/v1/Order/DeleteOrder/${order.id}`, {
+ method: 'DELETE',
+ });
+
+ if (response.ok) {
+ // If the order was successfully deleted, remove it from the orders state
+ setOrders(orders.filter(o => o.id !== order.id));
+ } else {
+ // Handle error
+ alert('Failed to delete order');
+ }
+ };
+
+ return (
+
+
Main Menu
+
+
+
+
+ Order |
+ Name |
+ From |
+ To |
+ At |
+ Quantity |
+ Action |
+
+
+
+ {orders.map((order) => (
+
+ {order.id} |
+ {order.name} |
+ {order.from} |
+ {order.to} |
+ {order.at} |
+ {order.quantity} |
+
+ handleView(order)}>View
+ handleUpdate(order)}>Update
+ handleDelete(order)}>Delete
+ |
+
+ ))}
+
+
+
+
+ );
+}
+
+MainMenu.layout = WithDefaultLayout;
+
diff --git a/pages/user/login.tsx b/pages/user/login.tsx
new file mode 100644
index 0000000..f600293
--- /dev/null
+++ b/pages/user/login.tsx
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { useRouter } from 'next/router';
+import { WithDefaultLayout } from '@/components/DefautLayout';
+
+export default function LoginPage() {
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const router = useRouter();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ // Make a POST request to your API
+ const response = await fetch('api/be/api/v1/auth/login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ username: username,
+ password: password,
+ }),
+ });
+
+ if (response.ok) {
+ // If the login was successful, redirect to the main menu
+ router.push('/MainMenu');
+ } else {
+ // Handle error
+ alert('Failed to log in');
+ }
+ };
+
+ return (
+
+
+
+ Login Page
+
+
+
+
+ );
+}
+
+
+LoginPage.layout = WithDefaultLayout;
\ No newline at end of file
diff --git a/pages/user/register.tsx b/pages/user/register.tsx
new file mode 100644
index 0000000..687c496
--- /dev/null
+++ b/pages/user/register.tsx
@@ -0,0 +1,214 @@
+import { useState } from 'react';
+import { useRouter } from 'next/router';
+import { WithDefaultLayout } from '@/components/DefautLayout';
+
+export default function RegisterPage() {
+ 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 router = useRouter();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ // Add your validation logic here
+ if (!email || !email.includes('@')) {
+ alert('Invalid email');
+ return;
+ }
+
+ const age = new Date().getFullYear() - new Date(dob).getFullYear();
+ if (age < 14) {
+ alert('You must be at least 14 years old');
+ return;
+ }
+
+ if (!['M', 'P', 'Other'].includes(gender)) {
+ alert('Invalid gender');
+ return;
+ }
+
+ if (!address || address.length > 255) {
+ alert('Invalid address');
+ return;
+ }
+
+ if (!username || username.length > 20) {
+ alert('Invalid username');
+ return;
+ }
+
+ if (!password || password.length < 8 || password.length > 64) {
+ alert('Invalid password');
+ return;
+ }
+
+ // Make a POST request to your API
+ const response = await fetch('api/be/api/v1/auth/register', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ email: email,
+ dob: dob,
+ gender: gender,
+ address: address,
+ username: username,
+ password: password,
+ }),
+ });
+
+ if (response.ok) {
+ // If the registration was successful, redirect to the login page
+ router.push('/LoginPage');
+ } else {
+ // Handle error
+ alert('Failed to register');
+ }
+ };
+
+ return (
+
+
+
+ Register Page
+
+
+
+
+ );
+ }
+
+
+RegisterPage.layout = WithDefaultLayout;