From 7bc78f4c9418582b3808394cd7158efb4db28c14 Mon Sep 17 00:00:00 2001 From: WCY-dt <834421194@qq.com> Date: Wed, 1 May 2024 23:03:37 +0800 Subject: [PATCH] UPDATE: Using RESTful API --- .editorconfig | 12 +++ package-lock.json | 32 +++++-- package.json | 6 +- src/components/clusters.tsx | 10 ++- src/components/content.tsx | 8 +- src/components/header.tsx | 4 +- src/components/linkCard.tsx | 134 ++++++++++++++++++----------- src/popups/confirm.tsx | 28 ++++++ src/popups/login.tsx | 4 +- src/routers/router.tsx | 2 +- src/services/collectionFetcher.ts | 2 +- src/services/dataFetcher.ts | 4 +- src/services/deleteCardHandler.tsx | 32 +++++++ src/services/loginHandler.ts | 3 +- src/styles/confirm.css | 0 src/styles/linkCard.css | 47 +++++++++- src/styles/notification.css | 4 +- typings.d.ts | 8 +- 18 files changed, 264 insertions(+), 76 deletions(-) create mode 100644 .editorconfig create mode 100644 src/popups/confirm.tsx create mode 100644 src/services/deleteCardHandler.tsx create mode 100644 src/styles/confirm.css diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a727df3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.yml] +indent_style = space diff --git a/package-lock.json b/package-lock.json index 7be71d4..5453934 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mindclip", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mindclip", - "version": "2.0.0", + "version": "2.0.1", "license": "GPL-3.0-only", "dependencies": { "react": "^18.2.0", @@ -19,8 +19,10 @@ "@iconify/react": "^4.1.1", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", + "@types/uuid": "^9.0.8", "cross-env": "^7.0.3", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "uuid": "^9.0.1" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -3704,6 +3706,12 @@ "version": "2.0.7", "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmmirror.com/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.10", "license": "MIT", @@ -14143,6 +14151,14 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/source-list-map": { "version": "2.0.1", "license": "MIT" @@ -15333,8 +15349,14 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "license": "MIT", + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } diff --git a/package.json b/package.json index 705548e..7299026 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mindclip", - "version": "2.0.0", + "version": "2.0.1", "private": true, "author": "ch3nyang", "license": "GPL-3.0-only", @@ -37,7 +37,9 @@ "@iconify/react": "^4.1.1", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", + "@types/uuid": "^9.0.8", "cross-env": "^7.0.3", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "uuid": "^9.0.1" } } diff --git a/src/components/clusters.tsx b/src/components/clusters.tsx index 202c146..5eb2f7e 100644 --- a/src/components/clusters.tsx +++ b/src/components/clusters.tsx @@ -9,9 +9,13 @@ interface ClustersProps { dataKey: string; searchTerm: string; setSearchTerm: (value: string) => void; + isLogedIn: boolean; + token: string; + message: string | null; + setMessage: (value: string | null) => void; } -function Clusters({ dataKey, searchTerm, setSearchTerm }: ClustersProps) { +function Clusters({ dataKey, searchTerm, setSearchTerm, isLogedIn, token, message, setMessage }: ClustersProps) { const [clusters, setClusters] = useState([]); const [selectedCategory, setSelectedCategory] = useState(null); const [isLoading, setIsLoading] = useState(true); @@ -39,6 +43,10 @@ function Clusters({ dataKey, searchTerm, setSearchTerm }: ClustersProps) { key={cluster.Title} item={cluster} setSelectedCategory={setSelectedCategory} + isLogedIn={isLogedIn} + token={token} + message={message} + setMessage={setMessage} /> )) ) : ( diff --git a/src/components/content.tsx b/src/components/content.tsx index 4f8add7..bdb7bb0 100644 --- a/src/components/content.tsx +++ b/src/components/content.tsx @@ -10,18 +10,20 @@ interface ContentProps { isLogedIn: boolean; token: string; setToken: (value: string) => void; + message: string | null; + setMessage: (value: string | null) => void; } -function Content({ routes, searchTerm, setSearchTerm, isLogedIn, token, setToken }: ContentProps) { +function Content({ routes, searchTerm, setSearchTerm, isLogedIn, token, setToken, message, setMessage }: ContentProps) { return (
} + element={} /> {Object.entries(routes).map(([path, element]) => ( } + element={} /> ))} diff --git a/src/components/header.tsx b/src/components/header.tsx index 9d6a3e3..1989576 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -73,11 +73,11 @@ function Header({ routes, searchTerm, setSearchTerm, isLogedIn, setIsLogedIn, to
{isOpen && ( -
+
    {Object.entries(routes).map(([path, element]) => (
  • {element}
  • ))} -
+ )}
{ diff --git a/src/components/linkCard.tsx b/src/components/linkCard.tsx index edf5376..4f9d74e 100644 --- a/src/components/linkCard.tsx +++ b/src/components/linkCard.tsx @@ -1,71 +1,105 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { Icon } from '@iconify/react'; +import { v4 as uuidv4 } from 'uuid'; import { getRandomColor, getContrastColor } from '../utils/randomColor'; +import Confirm from '../popups/confirm'; +import deleteCardHandler from '../services/deleteCardHandler'; import '../styles/linkCard.css'; interface LinkCardProps { item: ClusterProps; setSelectedCategory: (value: string) => void; + isLogedIn: boolean; + token: string; + message: string | null; + setMessage: (value: string | null) => void; } -function LinkCard({ item, setSelectedCategory }: LinkCardProps) { +function LinkCard({ item, setSelectedCategory, isLogedIn, token, message, setMessage }: LinkCardProps) { const backgroundColor = getRandomColor(item.Category); const textColor = getContrastColor(backgroundColor); const handleCategoryClick = (e: React.MouseEvent) => { - e.preventDefault(); - setSelectedCategory(item.Category); -}; + e.preventDefault(); + setSelectedCategory(item.Category); + }; + + const [showConfirm, setShowConfirm] = useState(false); + const confirmMessage = "Are you confirmed to delete this card?"; + + const tryDeleteCard = async () => { + const id = item.Id || 0; + const deleteCardResult = await deleteCardHandler({ id, token }); + + if (deleteCardResult === true) { + setMessage('Successfully deleted'); + } else { + setMessage('Failed to delete. Please try again.'); + } + }; return ( - -
-
- {item.Category ? item.Category.replace(/'/g, "'") : 'UNKNOWN'} -
-
- favicon -
-
- -
-
- {item.Title.replace(/'/g, "'")} -
-
- {item.Description.replace(/'/g, "'")} + <> +
+
+
+
+ {item.Category ? item.Category : 'UNKNOWN'} +
+
+ favicon +
+
+ {isLogedIn ? ( +
+ + setShowConfirm(true)}> +
+ ) : null + }
- {item.Detail ? ( -
- {item.Detail.replace(/'/g, "'")} + + +
+ {item.Title}
- ) : null} - {item.links ? ( -
- {item.links && item.links.length > 0 ? item.links.map((link) => ( - - {link.Title.replace(/'/g, "'")} - - )) : ''} +
+ {item.Description}
- ) : null} + {item.Detail ? ( +
+ {item.Detail} +
+ ) : null} + {(item.links && item.links.length > 0) ? ( +
+ {item.links.map((link) => ( + + {link.Title} + + ))} +
+ ) : null} +
- + {showConfirm ? () : null} + ); } diff --git a/src/popups/confirm.tsx b/src/popups/confirm.tsx new file mode 100644 index 0000000..4478d59 --- /dev/null +++ b/src/popups/confirm.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import '../styles/popup.css'; +import '../styles/confirm.css'; + +interface ConfirmProps { + message: string | null; + setShowConfirm: (value: boolean) => void; + actionHandler: () => void; +} + +function Confirm({ message, setShowConfirm, actionHandler }: ConfirmProps) { + return ( + <> +
+
{ message }
+
+ + +
+
+ + ); +} + +export default Confirm; \ No newline at end of file diff --git a/src/popups/login.tsx b/src/popups/login.tsx index 2f3a5f9..5d98766 100644 --- a/src/popups/login.tsx +++ b/src/popups/login.tsx @@ -19,15 +19,13 @@ function Login({ setShowLogin, setIsLogedIn, token, setToken, message, setMessag const password = (document.getElementById("password") as HTMLInputElement).value; const loginResult = await loginHandler({ username, password, setToken }); - console.log(loginResult); - if (loginResult === true) { setShowLogin(false); localStorage.setItem('token', token); setIsLogedIn(true); setMessage('Successfully logged in'); } else { - alert('Failed to login. Please try again.'); + setMessage('Login failed. Please try again.'); } }; diff --git a/src/routers/router.tsx b/src/routers/router.tsx index 6a1cd5f..913c5b6 100644 --- a/src/routers/router.tsx +++ b/src/routers/router.tsx @@ -42,7 +42,7 @@ function Router() {
- +