diff --git a/.env.development b/.env.development index 58ed8bd..f60f11d 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,7 @@ NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=e01b7895a403fa7364061b2f01a650fc BACKEND_API_HOST=https://demo.duendesoftware.com +BACKEND_CUSTOM_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/appsettings.js b/appsettings.js index ebd662f..b17bf9c 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'] ?? '', + backendCustomApiHost: process.env['BACKEND_CUSTOM_API_HOST'] ?? '', + // BACKEND_CUSTOM_API_HOST }; diff --git a/components/DefautLayout.tsx b/components/DefautLayout.tsx index 973c396..7f1f6c1 100644 --- a/components/DefautLayout.tsx +++ b/components/DefautLayout.tsx @@ -1,10 +1,10 @@ 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, faUser, faCircleUp } 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"; +import { useSession, signOut } from "next-auth/react"; import nProgress from "nprogress"; const { Content, Sider } = Layout; @@ -36,71 +36,55 @@ const DefaultLayout: React.FC<{ menu.push( { - key: '#menu-1', - label: 'Menu 1', - 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: 'Post', + label: 'Post', + icon: , + onClick: () => router.push('/create') }, - { - 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('/') - } - ] - } + // { + // 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') { @@ -122,10 +106,7 @@ const DefaultLayout: React.FC<{ key: '/sign-in', label: 'Sign in', icon: , - onClick: () => { - nProgress.start(); - signIn('oidc'); - } + onClick: () => router.push('/login') }); } diff --git a/functions/BackendApiClient.ts b/functions/BackendApiClient.ts new file mode 100644 index 0000000..e756c2a --- /dev/null +++ b/functions/BackendApiClient.ts @@ -0,0 +1,365 @@ +//---------------------- +// +// Generated using the NSwag toolchain v14.0.7.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) +// +//---------------------- + +/* tslint:disable */ +/* eslint-disable */ +// ReSharper disable InconsistentNaming + +export class AuthClient { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? ""; + } + + /** + * @param body (optional) + * @return Success + */ + login(body: LoginModel | undefined): Promise { + let url_ = this.baseUrl + "/api/v1/Auth/Login"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(body); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processLogin(_response); + }); + } + + protected processLogin(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + return; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * @param body (optional) + * @return Success + */ + register(body: RegisterModel | undefined): Promise { + let url_ = this.baseUrl + "/api/v1/Auth/Register"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(body); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processRegister(_response); + }); + } + + protected processRegister(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + return; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class OrderClient { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? ""; + } + + /** + * @param body (optional) + * @return Success + */ + orderGrid(body: OrderFilter | undefined): Promise { + let url_ = this.baseUrl + "/api/v1/Order/OrderGrid"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(body); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processOrderGrid(_response); + }); + } + + protected processOrderGrid(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + return; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * @return Success + */ + orderDetail(id: number): Promise { + let url_ = this.baseUrl + "/api/v1/Order/OrderDetail/{id}"; + if (id === undefined || id === null) + throw new Error("The parameter 'id' must be defined."); + url_ = url_.replace("{id}", encodeURIComponent("" + id)); + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processOrderDetail(_response); + }); + } + + protected processOrderDetail(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + return; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * @param body (optional) + * @return Success + */ + createOrder(body: CreateOrderModel | undefined): Promise { + let url_ = this.baseUrl + "/api/v1/Order/CreateOrder"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(body); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processCreateOrder(_response); + }); + } + + protected processCreateOrder(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + return; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * @param body (optional) + * @return Success + */ + updateOrder(body: UpdateOrderModel | undefined): Promise { + let url_ = this.baseUrl + "/api/v1/Order/UpdateOrder"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(body); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processUpdateOrder(_response); + }); + } + + protected processUpdateOrder(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + return; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * @return Success + */ + deleteOrder(id: number): Promise { + let url_ = this.baseUrl + "/api/v1/Order/DeleteOrder/{id}"; + if (id === undefined || id === null) + throw new Error("The parameter 'id' must be defined."); + url_ = url_.replace("{id}", encodeURIComponent("" + id)); + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "DELETE", + headers: { + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processDeleteOrder(_response); + }); + } + + protected processDeleteOrder(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + return; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export interface CreateOrderModel { + description?: string | undefined; + orderFrom?: string | undefined; + orderTo?: string | undefined; + quantity?: number; + orderedAt?: Date; +} + +export interface LoginModel { + email?: string | undefined; + password: string; + phoneNumber?: string | undefined; +} + +export interface OrderFilter { + orderId?: number | undefined; + orderFrom?: string | undefined; + orderTo?: string | undefined; + total?: number; + quantity?: number | undefined; + orderedAt?: Date | undefined; + currentPage?: number; + pageSize?: number; +} + +export interface RegisterModel { + email?: string | undefined; + dateOfBirth?: Date; + gender?: string | undefined; + address?: string | undefined; + username?: string | undefined; + password?: string | undefined; +} + +export interface UpdateOrderModel { + description?: string | undefined; + orderFrom?: string | undefined; + orderTo?: string | undefined; + quantity?: number; +} + +export class ApiException extends Error { + override message: string; + status: number; + response: string; + headers: { [key: string]: any; }; + result: any; + + constructor(message: string, status: number, response: string, headers: { [key: string]: any; }, result: any) { + super(); + + this.message = message; + this.status = status; + this.response = response; + this.headers = headers; + this.result = result; + } + + protected isApiException = true; + + static isApiException(obj: any): obj is ApiException { + return obj.isApiException === true; + } +} + +function throwException(message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any { + if (result !== null && result !== undefined) + throw result; + else + throw new ApiException(message, status, response, headers, null); +} \ No newline at end of file diff --git a/nswag-config.nswag b/nswag-config.nswag new file mode 100644 index 0000000..5107ae9 --- /dev/null +++ b/nswag-config.nswag @@ -0,0 +1,74 @@ +{ + "runtime": "Net80", + "defaultVariables": null, + "documentGenerator": { + "fromDocument": { + "json": "{\r\n \"openapi\": \"3.0.1\",\r\n \"info\": {\r\n \"title\": \"FEExamAPI\",\r\n \"version\": \"1.0\"\r\n },\r\n \"paths\": {\r\n \"/api/v1/Auth/Login\": {\r\n \"post\": {\r\n \"tags\": [\r\n \"Auth\"\r\n ],\r\n \"requestBody\": {\r\n \"content\": {\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/LoginModel\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/LoginModel\"\r\n }\r\n },\r\n \"application/*+json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/LoginModel\"\r\n }\r\n }\r\n }\r\n },\r\n \"responses\": {\r\n \"200\": {\r\n \"description\": \"Success\"\r\n }\r\n }\r\n }\r\n },\r\n \"/api/v1/Auth/Register\": {\r\n \"post\": {\r\n \"tags\": [\r\n \"Auth\"\r\n ],\r\n \"requestBody\": {\r\n \"content\": {\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/RegisterModel\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/RegisterModel\"\r\n }\r\n },\r\n \"application/*+json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/RegisterModel\"\r\n }\r\n }\r\n }\r\n },\r\n \"responses\": {\r\n \"200\": {\r\n \"description\": \"Success\"\r\n }\r\n }\r\n }\r\n },\r\n \"/api/v1/Order/OrderGrid\": {\r\n \"post\": {\r\n \"tags\": [\r\n \"Order\"\r\n ],\r\n \"requestBody\": {\r\n \"content\": {\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/OrderFilter\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/OrderFilter\"\r\n }\r\n },\r\n \"application/*+json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/OrderFilter\"\r\n }\r\n }\r\n }\r\n },\r\n \"responses\": {\r\n \"200\": {\r\n \"description\": \"Success\"\r\n }\r\n }\r\n }\r\n },\r\n \"/api/v1/Order/OrderDetail/{id}\": {\r\n \"get\": {\r\n \"tags\": [\r\n \"Order\"\r\n ],\r\n \"parameters\": [\r\n {\r\n \"name\": \"id\",\r\n \"in\": \"path\",\r\n \"required\": true,\r\n \"schema\": {\r\n \"type\": \"integer\",\r\n \"format\": \"int32\"\r\n }\r\n }\r\n ],\r\n \"responses\": {\r\n \"200\": {\r\n \"description\": \"Success\"\r\n }\r\n }\r\n }\r\n },\r\n \"/api/v1/Order/CreateOrder\": {\r\n \"post\": {\r\n \"tags\": [\r\n \"Order\"\r\n ],\r\n \"requestBody\": {\r\n \"content\": {\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/CreateOrderModel\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/CreateOrderModel\"\r\n }\r\n },\r\n \"application/*+json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/CreateOrderModel\"\r\n }\r\n }\r\n }\r\n },\r\n \"responses\": {\r\n \"200\": {\r\n \"description\": \"Success\"\r\n }\r\n }\r\n }\r\n },\r\n \"/api/v1/Order/UpdateOrder\": {\r\n \"post\": {\r\n \"tags\": [\r\n \"Order\"\r\n ],\r\n \"requestBody\": {\r\n \"content\": {\r\n \"application/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/UpdateOrderModel\"\r\n }\r\n },\r\n \"text/json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/UpdateOrderModel\"\r\n }\r\n },\r\n \"application/*+json\": {\r\n \"schema\": {\r\n \"$ref\": \"#/components/schemas/UpdateOrderModel\"\r\n }\r\n }\r\n }\r\n },\r\n \"responses\": {\r\n \"200\": {\r\n \"description\": \"Success\"\r\n }\r\n }\r\n }\r\n },\r\n \"/api/v1/Order/DeleteOrder/{id}\": {\r\n \"delete\": {\r\n \"tags\": [\r\n \"Order\"\r\n ],\r\n \"parameters\": [\r\n {\r\n \"name\": \"id\",\r\n \"in\": \"path\",\r\n \"required\": true,\r\n \"schema\": {\r\n \"type\": \"integer\",\r\n \"format\": \"int32\"\r\n }\r\n }\r\n ],\r\n \"responses\": {\r\n \"200\": {\r\n \"description\": \"Success\"\r\n }\r\n }\r\n }\r\n }\r\n },\r\n \"components\": {\r\n \"schemas\": {\r\n \"CreateOrderModel\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"description\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"orderFrom\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"orderTo\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"quantity\": {\r\n \"type\": \"integer\",\r\n \"format\": \"int32\"\r\n },\r\n \"orderedAt\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\"\r\n }\r\n },\r\n \"additionalProperties\": false\r\n },\r\n \"LoginModel\": {\r\n \"required\": [\r\n \"password\"\r\n ],\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"email\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"password\": {\r\n \"minLength\": 1,\r\n \"type\": \"string\"\r\n },\r\n \"phoneNumber\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n }\r\n },\r\n \"additionalProperties\": false\r\n },\r\n \"OrderFilter\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"orderId\": {\r\n \"type\": \"integer\",\r\n \"format\": \"int32\",\r\n \"nullable\": true\r\n },\r\n \"orderFrom\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"orderTo\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"total\": {\r\n \"type\": \"number\",\r\n \"format\": \"double\"\r\n },\r\n \"quantity\": {\r\n \"type\": \"integer\",\r\n \"format\": \"int32\",\r\n \"nullable\": true\r\n },\r\n \"orderedAt\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\",\r\n \"nullable\": true\r\n },\r\n \"currentPage\": {\r\n \"type\": \"integer\",\r\n \"format\": \"int32\"\r\n },\r\n \"pageSize\": {\r\n \"type\": \"integer\",\r\n \"format\": \"int32\"\r\n }\r\n },\r\n \"additionalProperties\": false\r\n },\r\n \"RegisterModel\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"email\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"dateOfBirth\": {\r\n \"type\": \"string\",\r\n \"format\": \"date-time\"\r\n },\r\n \"gender\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"address\": {\r\n \"maxLength\": 255,\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"username\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"password\": {\r\n \"maxLength\": 64,\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n }\r\n },\r\n \"additionalProperties\": false\r\n },\r\n \"UpdateOrderModel\": {\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"description\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"orderFrom\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"orderTo\": {\r\n \"type\": \"string\",\r\n \"nullable\": true\r\n },\r\n \"quantity\": {\r\n \"type\": \"integer\",\r\n \"format\": \"int32\"\r\n }\r\n },\r\n \"additionalProperties\": false\r\n }\r\n }\r\n }\r\n}", + "url": "https://new-dev.accelist.com:1234/swagger/v1/swagger.json", + "output": null, + "newLineBehavior": "Auto" + } + }, + "codeGenerators": { + "openApiToTypeScriptClient": { + "className": "{controller}Client", + "moduleName": "", + "namespace": "", + "typeScriptVersion": 4.3, + "template": "Fetch", + "promiseType": "Promise", + "httpClass": "HttpClient", + "withCredentials": false, + "useSingletonProvider": false, + "injectionTokenType": "OpaqueToken", + "rxJsVersion": 6.0, + "dateTimeType": "Date", + "nullValue": "Undefined", + "generateClientClasses": true, + "generateClientInterfaces": false, + "generateOptionalParameters": false, + "exportTypes": true, + "wrapDtoExceptions": false, + "exceptionClass": "ApiException", + "clientBaseClass": null, + "wrapResponses": false, + "wrapResponseMethods": [], + "generateResponseClasses": true, + "responseClass": "SwaggerResponse", + "protectedMethods": [], + "configurationClass": null, + "useTransformOptionsMethod": false, + "useTransformResultMethod": false, + "generateDtoTypes": true, + "operationGenerationMode": "MultipleClientsFromFirstTagAndOperationId", + "markOptionalProperties": true, + "generateCloneMethod": false, + "typeStyle": "Interface", + "enumStyle": "Enum", + "useLeafType": false, + "classTypes": [], + "extendedClasses": [], + "extensionCode": null, + "generateDefaultValues": true, + "excludedTypeNames": [], + "excludedParameterNames": [], + "handleReferences": false, + "generateTypeCheckFunctions": false, + "generateConstructorInterface": true, + "convertConstructorInterfaceData": false, + "importRequiredTypes": true, + "useGetBaseUrlMethod": false, + "baseUrlTokenName": "API_BASE_URL", + "queryNullValue": "", + "useAbortSignal": false, + "inlineNamedDictionaries": false, + "inlineNamedAny": false, + "includeHttpContext": false, + "templateDirectory": null, + "serviceHost": null, + "serviceSchemes": null, + "output": "functions/BackendApiUrl.ts", + "newLineBehavior": "Auto" + } + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9344003..86e77f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@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", @@ -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..724b3eb 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@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", diff --git a/pages/api/be/[...apiGateway].ts b/pages/api/be/[...apiGateway].ts index 3dea920..f72f420 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.backendCustomApiHost, // 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.backendCustomApiHost + urlRewrite); }); const apiGateway = async (req: NextApiRequest, res: NextApiResponse) => { diff --git a/pages/create.tsx b/pages/create.tsx new file mode 100644 index 0000000..9c9a53d --- /dev/null +++ b/pages/create.tsx @@ -0,0 +1,79 @@ +import { WithDefaultLayout } from '@/components/DefautLayout'; +import { Form, Input, DatePicker, Button, InputNumber } from 'antd'; +import router from 'next/router'; + + +const CreateOrderPage = () => { + const [form] = Form.useForm(); + + const handleSubmit = async (values) => { + try { + const response = await fetch('/api/be/api/v1/Order/CreateOrder', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + }); + + if (!response.ok) { + throw new Error('Failed to create order'); + } + + router.push('/'); + } catch (error) { + console.error('Error creating order:', error); + } + }; + + return ( +
+
+
+

Post Order

+
+ + + + + + + + + + + + + + + + + + + + + {/* Button to submit form */} + + + +
+
+
+
+ ); +}; + +CreateOrderPage.layout = WithDefaultLayout; +export default CreateOrderPage; diff --git a/pages/index.tsx b/pages/index.tsx index 6c0943a..b602020 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,15 +1,200 @@ -import { WithDefaultLayout } from '../components/DefautLayout'; -import { Title } from '../components/Title'; -import { Page } from '../types/Page'; +import { WithDefaultLayout } from '@/components/DefautLayout'; +import { OrderClient } from '@/functions/BackendApiClient'; +import { Button, Space, Table, TablePaginationConfig, Modal } from 'antd'; +import { ColumnsType } from 'antd/es/table'; +import router from 'next/router'; +// import Link from 'next/link'; +import { useEffect, useState } from 'react'; + +interface OrderData { + key: number; + name: string; + from: string; + to: string; + orderedAt: string; + quantity: number; +} + + +const MainMenu = () => { + const [data, setData] = useState([]); + const [pagination, setPagination] = useState({ pageSize: 5, current: 1, total: 0 }); + const [loading, setLoading] = useState(false); + + const [deleteVisible, setDeleteVisible] = useState(false); + const [recordToDelete, setRecordToDelete] = useState(null); + + + + const columns: ColumnsType = [ + { + title: 'No.', + dataIndex: 'key', + render: (_, __, index) => (pagination.current - 1) * pagination.pageSize + index + 1, + }, + { + title: 'Order Name', + dataIndex: 'name', + }, + { + title: 'From', + dataIndex: 'from', + }, + { + title: 'To', + dataIndex: 'to', + }, + { + title: 'Ordered At', + dataIndex: 'orderedAt', + }, + { + title: 'Quantity', + dataIndex: 'quantity', + }, + { + title: 'Action', + key: 'action', + render: (_, record: OrderData) => ( + + + + + + ), + }, + ]; + + //buat hilangkan warning + const performDelete = () => { + if (recordToDelete) { + setRecordToDelete(null); + } + }; + + const handleDelete = (record: OrderData) => { + performDelete(); + setRecordToDelete(record); + setDeleteVisible(true); + }; + + const handleView = (record: OrderData) => { + router.push(`/view/${record.key}`); + } + + const handleUpdate = (record: OrderData) => { + router.push(`/update/${record.key}`); + } + + const handleDeleteConfirm = async (orderId) => { + const productClient = new OrderClient('http://localhost:3000/api/be'); + + try { + await productClient.deleteOrder(orderId); + } catch (error) { + console.error(error); + } + + fetchData(pagination) + .then(dataFromServer => { + setData(dataFromServer.data.map((item, index) => ({ ...item, key: item.key || index }))); + setPagination(prev => ({ ...prev, total: dataFromServer.total })); + setLoading(false); + }) + .catch(error => { + console.error('Failed to fetch data:', error); + setLoading(false); + }); + + setDeleteVisible(false); + }; + + const fetchData = async (pagination: TablePaginationConfig): Promise<{ data: OrderData[], total: number }> => { + const response = await fetch('/api/be/api/v1/Order/OrderGrid', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + currentPage: pagination.current, + pageSize: 25, + }), + }); + + if (!response.ok) { + throw new Error('Network response was not ok'); + } + + const responseData = await response.json(); + + // Map keys to match the expected structure + const mappedData = responseData.map((item) => ({ + key: item.orderId, + name: item.orderFrom, + from: item.orderFrom, + to: item.orderTo, + orderedAt: item.orderedAt, + quantity: item.quantity, + })); + + return { + data: mappedData, + total: responseData.length, + }; + }; + + + useEffect(() => { + setLoading(true); + fetchData(pagination) + .then(dataFromServer => { + setData(dataFromServer.data.map((item, index) => ({ ...item, key: item.key || index }))); + setPagination(prev => ({ + ...prev, + total: dataFromServer.total + })); + setLoading(false); + }) + .catch(error => { + console.error('Failed to fetch data:', error); + setLoading(false); + }); + }, [pagination.current, pagination.pageSize]); + + const handleTableChange = (newPagination: TablePaginationConfig) => { + setPagination(prev => ({ + ...prev, + current: newPagination.current || 1, + pageSize: newPagination.pageSize || 5 + })); + }; + -const IndexPage: Page = () => { return ( -
- Home - Hello World! -
+ <> +

MyOrder

+ + + setDeleteVisible(false)} + okType="danger" + > +

Are you sure you want to delete this order?

+
+ + + ); -} +}; -IndexPage.layout = WithDefaultLayout; -export default IndexPage; +MainMenu.layout = WithDefaultLayout; +export default MainMenu; diff --git a/pages/login.tsx b/pages/login.tsx new file mode 100644 index 0000000..d629bfa --- /dev/null +++ b/pages/login.tsx @@ -0,0 +1,107 @@ +import { Page } from '@/types/Page'; +import { Button, Form, Input, notification } from 'antd'; +import { useRouter } from 'next/router'; + +const LoginPage: Page = () => { + + const [form] = Form.useForm(); + const router = useRouter(); + + const onFinish = async (values) => { + try { + const response = await fetch('/api/be/api/v1/Auth/Login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: values.usernameOrEmail, + password: values.password + }), + }); + + if (response.ok) { + const text = await response.text(); + if (text === "Login success") { + notification.success({ + message: 'Login Successful', + description: 'You have been successfully logged in.', + }); + router.push('/'); + } else { + notification.error({ + message: 'Login Failed', + description: 'Received unexpected response from the server.', + }); + } + } else { + const errorText = await response.text(); + throw new Error(errorText || `Server responded with status: ${response.status}`); + } + } catch (error) { + console.error('Login error:', error); + notification.error({ + message: 'Login Error', + description: (error instanceof Error) ? error.message : 'There was an issue completing your request.', + }); + } + }; + + const onFinishFailed = (errorInfo) => { + console.log('Failed:', errorInfo); + notification.error({ + message: 'Login Failed', + description: 'Please check your username or password.', + }); + }; + + const handleRegisterClick = () => { + router.push('/register'); + }; + + return ( +
+
+

Login

+
+ + + + + + + + + + + + + + + + +
+
+ ); +} + +export default LoginPage; \ No newline at end of file diff --git a/pages/register.tsx b/pages/register.tsx new file mode 100644 index 0000000..6a0e360 --- /dev/null +++ b/pages/register.tsx @@ -0,0 +1,128 @@ +// import { useState } from 'react'; +import { useRouter } from 'next/router'; +import { Input, Button, Form, DatePicker, Select, notification } from 'antd'; +import { Page } from '@/types/Page'; + + +const { Option } = Select; +const { TextArea } = Input; + +interface FormData { + email: string; + birthdate: Date | null; + gender: string; + address: string; + username: string; + password: string; +} + +const RegistrationPage: Page = () => { + + const [form] = Form.useForm(); + const router = useRouter(); + + const handleSubmit = async (values: FormData) => { + try { + const response = await fetch('/api/be/api/v1/Auth/Register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values) + }); + if (response.ok) { + notification.success({ + message: 'Registration Successful', + description: 'You will be redirected to the Login page.' + }); + router.push('/login'); + } else { + throw new Error('Failed to register'); + } + } catch (error) { + notification.error({ + message: 'Registration Failed', + description: 'An error occurred during registration.' + }); + } + }; + + + const validateAge = (_, value: Date | null) => { + if (!value) { + return Promise.reject(new Error('Please select your birthdate!')); + } + + const today = new Date(); + const birthdate = new Date(value); + const ageDiffMs = today.getTime() - birthdate.getTime(); + const ageDate = new Date(ageDiffMs); + const age = Math.abs(ageDate.getUTCFullYear() - 1970); + + if (age >= 14) { + return Promise.resolve(); + } + return Promise.reject(new Error('You must be at least 14 years old!')); + }; + + + return ( +
+
+
+

Register

+
+ + + + + + + + + + + + + +