From a76d1b72f9610380d93d04f6b85af4d2c64d78d5 Mon Sep 17 00:00:00 2001 From: Zainal Abidin Date: Sat, 23 Sep 2023 13:42:38 +0700 Subject: [PATCH 1/2] pairing typescript in project search products --- package-lock.json | 163 +++++++++++++++++++++- package.json | 7 +- src/client/App.ts | 12 -- src/client/components/ContactListItem.ts | 42 ------ src/client/components/Link.ts | 17 ++- src/client/components/Pagination.ts | 2 +- src/client/components/ProductInput.ts | 3 +- src/client/components/typings/products.ts | 21 --- src/client/index.ts | 4 +- src/client/screens/FavoritePage.ts | 41 ------ src/client/screens/HomePage.ts | 2 +- src/client/screens/InputProduct.ts | 101 -------------- src/client/screens/SeachContactPage.ts | 77 ---------- src/client/state.ts | 96 ++++++------- src/client/typings/products.ts | 5 + src/server/app.ts | 4 +- tsconfig.json | 4 +- 17 files changed, 231 insertions(+), 370 deletions(-) delete mode 100644 src/client/components/ContactListItem.ts delete mode 100644 src/client/components/typings/products.ts delete mode 100644 src/client/screens/FavoritePage.ts delete mode 100644 src/client/screens/InputProduct.ts delete mode 100644 src/client/screens/SeachContactPage.ts create mode 100644 src/client/typings/products.ts diff --git a/package-lock.json b/package-lock.json index 7ab9e4c..92ad0a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@types/express": "^4.17.17", "express": "^4.18.2", "nodemon": "^3.0.1" }, @@ -100,6 +101,23 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@types/body-parser": { + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", + "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", + "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/eslint": { "version": "8.44.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", @@ -126,23 +144,83 @@ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.36", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz", + "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true }, + "node_modules/@types/http-errors": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz", + "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==" + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, "node_modules/@types/node": { "version": "20.6.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.0.tgz", - "integrity": "sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==", - "dev": true + "integrity": "sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==" + }, + "node_modules/@types/qs": { + "version": "6.9.8", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", + "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", @@ -2947,6 +3025,23 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@types/body-parser": { + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", + "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", + "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "requires": { + "@types/node": "*" + } + }, "@types/eslint": { "version": "8.44.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", @@ -2973,23 +3068,83 @@ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, + "@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.36", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz", + "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true }, + "@types/http-errors": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz", + "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==" + }, "@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, "@types/node": { "version": "20.6.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.0.tgz", - "integrity": "sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==", - "dev": true + "integrity": "sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==" + }, + "@types/qs": { + "version": "6.9.8", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", + "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==" + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "requires": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } }, "@webassemblyjs/ast": { "version": "1.11.6", diff --git a/package.json b/package.json index 73931bf..6b9005f 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,16 @@ "description": "", "private": true, "scripts": { - "ts": "rm -rf dist/ && tsc -w", - "dev": "concurrently \"webpack --config webpack.dev.js\" \"npm start\"", + "ts": "tsc", + "dev": "concurrently \"npm run ts -- -w\" \"webpack --config webpack.dev.js\" \"npm start\"", "start": "nodemon dist/server/app.js", - "build": "webpack --config webpack.prod.js" + "build": "npm run ts && webpack --config webpack.prod.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { + "@types/express": "^4.17.17", "express": "^4.18.2", "nodemon": "^3.0.1" }, diff --git a/src/client/App.ts b/src/client/App.ts index 7783de6..6d5abf7 100644 --- a/src/client/App.ts +++ b/src/client/App.ts @@ -1,27 +1,15 @@ import HomeScreen from "./screens/HomePage"; import AboutScreen from "./screens/AboutPage"; -import InputProduct from "./screens/InputProduct"; -import FavoritePage from "./screens/FavoritePage"; // import NotFoundPage from "./screens/NotFound"; -import SearchContact from "./screens/SeachContactPage"; import { state } from "./state"; function App() { const homeScreen = HomeScreen(); const aboutScreen = AboutScreen(); - const inputProductScreen = InputProduct(); - const favoriteScreen = FavoritePage(); - const searchContactScreen = SearchContact(); // const notFoundScreen = NotFoundPage(); if (state.path == "/home") { return homeScreen; - } else if (state.path == "/input-product") { - return inputProductScreen; - } else if (state.path == "/favorite") { - return favoriteScreen; - } else if (state.path == "/search-contact") { - return searchContactScreen; } else if (state.path == "/about") { return aboutScreen; } else { diff --git a/src/client/components/ContactListItem.ts b/src/client/components/ContactListItem.ts deleted file mode 100644 index c9c7948..0000000 --- a/src/client/components/ContactListItem.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { state, setState } from "../state"; - -function ContactListItem(props) { - const name = document.createElement("p"); - name.textContent = `${props.firstName} ${props.maidenName} ${props.lastName}`; - - const email = document.createElement("p"); - email.textContent = props.email; - - const addFavoriteButton = document.createElement("button"); - addFavoriteButton.textContent = "add to favorite"; - addFavoriteButton.onclick = function () { - const favoriteContacts = state.favoriteContacts.concat({ - id: props.id, - firstName: props.firstName, - lastName: props.lastName, - maidenName: props.maidenName, - email: props.email, - }); - setState({ favoriteContacts }); - }; - - const removeFavoriteButton = document.createElement("button"); - removeFavoriteButton.textContent = "remove from favorite"; - removeFavoriteButton.onclick = function () { - const favoriteContacts = state.favoriteContacts.filter( - ({ id }) => id != props.id - ); - setState({ favoriteContacts }); - }; - - const isFavorite = state.favoriteContacts.some(({ id }) => props.id === id); - - const item = document.createElement("li"); - item.appendChild(name); - item.appendChild(email); - item.appendChild(isFavorite ? removeFavoriteButton : addFavoriteButton); - - return item; -} - -export default ContactListItem; diff --git a/src/client/components/Link.ts b/src/client/components/Link.ts index 9a79f35..44284cf 100644 --- a/src/client/components/Link.ts +++ b/src/client/components/Link.ts @@ -1,5 +1,9 @@ -import { setState, render } from "../state"; -import { LinkProps } from "./typings/products"; +import { setState } from "../state"; + +export type LinkProps = { + href: string; + label: string; +}; function Link(props: LinkProps) { const link = document.createElement("a"); @@ -8,10 +12,11 @@ function Link(props: LinkProps) { link.textContent = props.label; link.onclick = function (event) { - event.preventDefault(); - const url = new URL(event.target.href); - setState({ path: url.pathname }); - render(); + if (event.target instanceof HTMLLinkElement) { + event.preventDefault(); + const url = new URL(event.target.href); + setState({ path: url.pathname }); + } }; return link; diff --git a/src/client/components/Pagination.ts b/src/client/components/Pagination.ts index 5440d36..0cbeaea 100644 --- a/src/client/components/Pagination.ts +++ b/src/client/components/Pagination.ts @@ -39,7 +39,7 @@ function Pagination() { selectLimit.appendChild(optionLimit); }); - selectLimit.value = state.limitItem; + selectLimit.value = state.limitItem.toString(); wrapperPagination.style.marginTop = "20px"; diff --git a/src/client/components/ProductInput.ts b/src/client/components/ProductInput.ts index 64acfd3..71a5def 100644 --- a/src/client/components/ProductInput.ts +++ b/src/client/components/ProductInput.ts @@ -5,7 +5,8 @@ function ProductInput() { input.id = "input"; input.value = state.inputValue; input.oninput = function (event) { - setState({ inputValue: event.target.value }); + if (event.target instanceof HTMLInputElement) + setState({ inputValue: event.target.value }); }; input.placeholder = "Input your name"; diff --git a/src/client/components/typings/products.ts b/src/client/components/typings/products.ts deleted file mode 100644 index accf026..0000000 --- a/src/client/components/typings/products.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface DataState { - inputValue: string; - path: string; - products?: []; - limitItem?: number; - skipItem?: number; - total?: number; - isLoading?: boolean; - errorMessage?: string; - inputProducts?: []; - inputPrice?: string; - inputName?: string; - inputValueFavorite?: string; - contacts?: []; - favoriteContacts?: []; -} - -export type LinkProps = { - href: string; - label: string; -}; diff --git a/src/client/index.ts b/src/client/index.ts index add51f2..0262c99 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,3 +1,5 @@ import { render, onStateChange, state } from "./state"; render(); -onStateChange({}, state); + +// TODO: Improve this code later to remove any +onStateChange({} as any, state); diff --git a/src/client/screens/FavoritePage.ts b/src/client/screens/FavoritePage.ts deleted file mode 100644 index 2dc5e97..0000000 --- a/src/client/screens/FavoritePage.ts +++ /dev/null @@ -1,41 +0,0 @@ -import ContactListItem from "../components/ContactListItem"; -import Link from "../components/Link"; -import { state } from "../state"; - -function FavoritePage() { - const linkSearch = Link({ - href: "/search-contact", - label: "Kembali ke Search Contact", - }); - - // start - const list = document.createElement("ol"); - list.append( - ...state.favoriteContacts.map((contact) => - ContactListItem({ - id: contact.id, - firstName: contact.firstName, - maidenName: contact.maidenName, - lastName: contact.lastName, - email: contact.email, - }) - ) - ); - - const emptyText = document.createElement("p"); - emptyText.textContent = "No data found"; - - const wrapper = document.createElement("div"); - wrapper.appendChild(linkSearch); - - if (state.favoriteContacts.length === 0) { - wrapper.appendChild(emptyText); - } else { - wrapper.appendChild(list); - } - - return wrapper; - // end -} - -export default FavoritePage; diff --git a/src/client/screens/HomePage.ts b/src/client/screens/HomePage.ts index 86f0e69..4523aef 100644 --- a/src/client/screens/HomePage.ts +++ b/src/client/screens/HomePage.ts @@ -6,7 +6,7 @@ import ProductList from "../components/ProductList"; function HomeScreen() { const navbar = NavBar(); const textPreview = document.createElement("p"); - textPreview.textContent = "Cari product Anda :"; + textPreview.textContent = "Cari product Saya :"; const div = document.createElement("div"); diff --git a/src/client/screens/InputProduct.ts b/src/client/screens/InputProduct.ts deleted file mode 100644 index cef5de0..0000000 --- a/src/client/screens/InputProduct.ts +++ /dev/null @@ -1,101 +0,0 @@ -import Link from "../components/Link"; -import { setState, state } from "../state"; - -function InputProduct() { - const linkHome = Link({ - href: "/home", - label: "Kembali ke Home", - }); - - const formInput = document.createElement("form"); - formInput.style.display = "table-caption"; - formInput.style.marginTop = "20px"; - - const labelNameItem = document.createElement("label"); - labelNameItem.textContent = "Nama Barang"; - - const inputNameItem = document.createElement("input"); - inputNameItem.placeholder = "ketik nama barang"; - inputNameItem.type = "text"; - - const labelPriceItem = document.createElement("label"); - labelPriceItem.textContent = "Harga"; - - const inputPriceItem = document.createElement("input"); - inputPriceItem.placeholder = "ketik harga"; - - const submitItem = document.createElement("input"); - submitItem.value = "Submit"; - submitItem.type = "submit"; - - formInput.addEventListener("submit", (e) => { - e.preventDefault(); - - setState({ - inputName: inputNameItem.value, - inputPrice: inputPriceItem.value, - }); - - if (state.inputName && state.inputPrice !== "") { - let product = { - nameItem: state.inputName, - priceItem: state.inputPrice, - }; - - const mergedArray = state.inputProducts.concat(product); - setState({ inputProducts: mergedArray }); - } - }); - - const titleItem = document.createElement("h3"); - titleItem.textContent = "List barang"; - - const wrapperListItem = document.createElement("ul"); - wrapperListItem.style.width = "200px"; - - state.inputProducts.forEach((item, index) => { - const wrapperItem = document.createElement("li"); - wrapperItem.style.display = "flex"; - wrapperItem.style.justifyContent = "space-between"; - - const listItemName = document.createElement("p"); - listItemName.style.margin = "0"; - listItemName.textContent = item.nameItem; - - const listItemPrice = document.createElement("span"); - listItemPrice.textContent = item.priceItem; - - const deleteItem = document.createElement("button"); - deleteItem.textContent = "Hapus"; - deleteItem.onclick = () => { - const result = state.inputProducts; - const indexToRemove = index; - - if (indexToRemove >= 0 && indexToRemove < result.length) { - const newArray = result.filter((obj, index) => index !== indexToRemove); - - setState({ inputProducts: newArray }); - } - }; - - wrapperListItem.appendChild(wrapperItem); - wrapperItem.appendChild(listItemName); - wrapperItem.appendChild(listItemPrice); - wrapperItem.appendChild(deleteItem); - }); - - const div = document.createElement("div"); - div.append(linkHome); - div.append(formInput); - div.append(titleItem); - div.append(wrapperListItem); - - formInput.appendChild(labelNameItem); - formInput.appendChild(inputNameItem); - formInput.appendChild(labelPriceItem); - formInput.appendChild(inputPriceItem); - formInput.appendChild(submitItem); - return div; -} - -export default InputProduct; diff --git a/src/client/screens/SeachContactPage.ts b/src/client/screens/SeachContactPage.ts deleted file mode 100644 index 25e5075..0000000 --- a/src/client/screens/SeachContactPage.ts +++ /dev/null @@ -1,77 +0,0 @@ -import ContactListItem from "../components/ContactListItem"; -import Link from "../components/Link"; -import { state, setState } from "../state"; - -function SearchContact() { - const linkHome = Link({ - href: "/home", - label: "Kembali ke Home", - }); - - const linkFavorite = Link({ - href: "/favorite", - label: "Favorite", - }); - - // start - const list = document.createElement("ol"); - list.append( - ...state.contacts.map((contact) => - ContactListItem({ - id: contact.id, - firstName: contact.firstName, - maidenName: contact.maidenName, - lastName: contact.lastName, - email: contact.email, - }) - ) - ); - - const input = document.createElement("input"); - input.id = "search"; - input.placeholder = "Search a name"; - input.value = state.inputValueFavorite; - input.oninput = function (event: any) { - const value = event.target.value; - setState({ inputValueFavorite: value }); - }; - - const resetButton = document.createElement("button"); - resetButton.textContent = "Reset"; - resetButton.onclick = function () { - setState({ inputValueFavorite: "" }); - }; - - const emptyText = document.createElement("p"); - emptyText.textContent = "No data found"; - - const errorText = document.createElement("p"); - errorText.textContent = state.errorMessage; - - const loadingText = document.createElement("p"); - loadingText.textContent = "Loading..."; - - const wrapper = document.createElement("div"); - wrapper.appendChild(linkHome); - wrapper.appendChild(linkFavorite); - - const div = document.createElement("div"); - wrapper.append(div); - div.append(input); - div.append(resetButton); - - if (state.isLoading) { - wrapper.appendChild(loadingText); - } else if (state.errorMessage !== "") { - wrapper.appendChild(errorText); - } else if (state.contacts.length === 0) { - wrapper.appendChild(emptyText); - } else { - wrapper.appendChild(list); - } - - // end - return wrapper; -} - -export default SearchContact; diff --git a/src/client/state.ts b/src/client/state.ts index 3dd956f..a57c010 100644 --- a/src/client/state.ts +++ b/src/client/state.ts @@ -1,7 +1,18 @@ import App from "./App"; -import { DataState } from "./components/typings/products"; +import { Product } from "./typings/products"; + +export interface State { + inputValue: string; + path: string; + products: Product[]; + limitItem: number; + skipItem: number; + total: number; + isLoading: boolean; + errorMessage: string; +} -let state: DataState = { +let state: State = { inputValue: localStorage.getItem("inputValue") ?? "", path: location.pathname, products: [], @@ -10,15 +21,9 @@ let state: DataState = { total: 0, isLoading: false, errorMessage: "", - inputProducts: JSON.parse(localStorage.getItem("productItem")) || [], - inputPrice: "", - inputName: "", - inputValueFavorite: localStorage.getItem("inputValueFavorite") ?? "", - contacts: [], - favoriteContacts: JSON.parse(localStorage.getItem("favoriteContacts")) ?? [], }; -function setState(newState) { +function setState(newState: Partial) { const prevState = { ...state }; const nextState = { ...state, ...newState }; state = nextState; @@ -28,14 +33,7 @@ function setState(newState) { let timer; // Ini adalah sideEffect, dimana sebuah function yg akan dijalankan ketika state nya berubah -function onStateChange(prevState, nextState) { - if (prevState.inputProducts !== nextState.inputProducts) { - localStorage.setItem( - "productItem", - JSON.stringify(nextState.inputProducts) - ); - } - +function onStateChange(prevState: State, nextState: State) { if (prevState.inputValue !== nextState.inputValue) { setState({ skipItem: 0 }); } @@ -71,7 +69,7 @@ function onStateChange(prevState, nextState) { }) .catch((error) => { setState({ - errorMessage: "error fetching", + errorMessage: error.message, isLoading: false, products: [], }); @@ -81,52 +79,40 @@ function onStateChange(prevState, nextState) { if (prevState.path !== nextState.path) { history.pushState(null, "", nextState.path); } - - if (prevState.inputValueFavorite != nextState.inputValueFavorite) { - localStorage.setItem("inputValueFavorite", nextState.inputValueFavorite); - - if (timer) { - clearTimeout(timer); - } - - setState({ isLoading: true }); - timer = setTimeout(() => { - fetch( - `https://dummyjson.com/users/search?q=${nextState.inputValueFavorite}` - ) - .then((res) => res.json()) - .then((data) => setState({ contacts: data.users, errorMessage: "" })) - .catch((err) => setState({ contacts: [], errorMessage: err.message })) - .finally(() => setState({ isLoading: false })); - }, 600); - } - - if (prevState.favoriteContacts != nextState.favoriteContacts) { - localStorage.setItem( - "favoriteContacts", - JSON.stringify(nextState.favoriteContacts) - ); - } } function render() { - const focusedElementId = document.activeElement.id; + const focusedElementId = document.activeElement?.id; - const focusedElementSelectionStart = document.activeElement.selectionStart; + const focusedElementSelectionStart = + document.activeElement instanceof HTMLInputElement + ? document.activeElement.selectionStart + : null; - const focusedElementSelectionEnd = document.activeElement.selectionEnd; + const focusedElementSelectionEnd = + document.activeElement instanceof HTMLInputElement + ? document.activeElement.selectionEnd + : null; const root = document.getElementById("root"); const app = App(); - root.innerHTML = ""; - root.append(app); - if (focusedElementId) { - const focusedElement = document.getElementById(focusedElementId); - focusedElement.focus(); - - focusedElement.selectionStart = focusedElementSelectionStart; - focusedElement.selectionEnd = focusedElementSelectionEnd; + if (root !== null) { + root.innerHTML = ""; + root.append(app); + + if (focusedElementId) { + const focusedElement = document.getElementById(focusedElementId); + focusedElement?.focus(); + + if ( + focusedElement !== null && + focusedElement instanceof HTMLInputElement + ) { + focusedElement.selectionStart = focusedElementSelectionStart; + focusedElement.selectionEnd = focusedElementSelectionEnd; + } + } } } diff --git a/src/client/typings/products.ts b/src/client/typings/products.ts new file mode 100644 index 0000000..764c3a7 --- /dev/null +++ b/src/client/typings/products.ts @@ -0,0 +1,5 @@ +export type Product = { + id: number; + title: string; + category: string; +}; diff --git a/src/server/app.ts b/src/server/app.ts index cc38213..27e24e7 100644 --- a/src/server/app.ts +++ b/src/server/app.ts @@ -1,5 +1,5 @@ -const express = require("express"); -const path = require("path"); +import express from "express"; +import path from "path"; const app = express(); const port = 2023; diff --git a/tsconfig.json b/tsconfig.json index 6711338..81c31f3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,12 +2,12 @@ "include": ["./src/**/*"], "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ - + "moduleResolution": "NodeNext", /* Language and Environment */ "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, /* Modules */ - // "module": "commonjs" /* Specify what module code is generated. */, + "module": "commonjs" /* Specify what module code is generated. */, /* JavaScript Support */ "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, From 9196c7c9c448e74cde606a86e7a2c8e1d52f49b0 Mon Sep 17 00:00:00 2001 From: Zainal Abidin Date: Sat, 23 Sep 2023 13:58:19 +0700 Subject: [PATCH 2/2] change module tsconfig to NodeNext --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 81c31f3..60d1f9e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, /* Modules */ - "module": "commonjs" /* Specify what module code is generated. */, + "module": "NodeNext" /* Specify what module code is generated. */, /* JavaScript Support */ "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */,