From 12873b1fa8bd9494e3c67de9c8895d867a102ef2 Mon Sep 17 00:00:00 2001 From: heyethereum Date: Mon, 22 Jul 2024 22:54:57 +0800 Subject: [PATCH] histories and bookmark done --- actions/qrCodeActions.ts | 6 -- api/qrCodeAPI.tsx | 46 ++++++++++++++- reducers/qrCodesReducer.ts | 109 ++++++++++++++++++++++++++---------- screens/HistoryScreen.tsx | 69 +++++++++++------------ screens/QRScannerScreen.tsx | 2 +- store.ts | 3 +- 6 files changed, 158 insertions(+), 77 deletions(-) delete mode 100644 actions/qrCodeActions.ts diff --git a/actions/qrCodeActions.ts b/actions/qrCodeActions.ts deleted file mode 100644 index ef6a525..0000000 --- a/actions/qrCodeActions.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createAction } from '@reduxjs/toolkit'; -import { QRCode } from '../types'; - -export const addQRCode = createAction('qrCodes/addQRCode'); -export const toggleBookmark = createAction('qrCodes/toggleBookmark'); -export const deleteQRCode = createAction('qrCodes/deleteQRCode'); diff --git a/api/qrCodeAPI.tsx b/api/qrCodeAPI.tsx index 5cb7aa1..550730e 100644 --- a/api/qrCodeAPI.tsx +++ b/api/qrCodeAPI.tsx @@ -8,6 +8,10 @@ const API_URL_VERIFY_URL = "/v1/qrcodetypes/verifyURL" const API_URL_VIRUS_TOTAL_CHECK = "/v1/qrcodetypes/virusTotalCheck" const API_URL_CHECK_REDIRECTS = "/v1/qrcodetypes/checkRedirects" const API_URL_GET_HISTORIES = "/v1/user/getScannedHistories" +const API_URL_DELETE_SCANNED_HISTORY = "/v1/user/deleteScannedHistories" +const API_URL_GET_BOOKMARKS = "/v1/user/getBookmarks" +const API_URL_SET_BOOKMARK = "/v1/user/setBookmark" +const API_URL_DELETE_BOOKMARK = "/v1/user/deleteBookmark" // Define a generic function to handle all types of requests export const apiRequest = async (config) => { @@ -64,11 +68,49 @@ export const checkRedirects = async (data) => { data: { data } }); }; - -export const getScannedHistories = async (userId: String) => { +// GET User's Scanned Histories +export const getScannedHistories = async (userId: string) => { return apiRequest({ method: 'get', url: `${API_BASE_URL}${API_URL_GET_HISTORIES}`, headers: { "X-USER-ID": userId }, }); }; +// GET All User's Bookmark +export const getAllUserBookmarks = async (userId: string) => { + return apiRequest({ + method: 'get', + url: `${API_BASE_URL}${API_URL_GET_BOOKMARKS}`, + headers: { "X-USER-ID": userId }, + }); +}; + +// Create Bookmark on QR Code +export const setBookmark = async (userId: string, qrCodeId: string) => { + return apiRequest({ + method: 'post', + url: `${API_BASE_URL}${API_URL_SET_BOOKMARK}`, + headers: { "X-USER-ID": userId}, + data: { "qrCodeId": qrCodeId } + }); +}; + +// Delete single bookmark +export const deleteBookmark = async (userId: string, qrCodeId: string) => { + return apiRequest({ + method: 'put', + url: `${API_BASE_URL}${API_URL_DELETE_BOOKMARK}`, + headers: { "X-USER-ID": userId}, + data: { "qrCodeId": qrCodeId } + }); +}; + +// Delete Single Scanned History +export const deleteScannedHistory = async (userId: string, qrCodeId: string) => { + return apiRequest({ + method: 'put', + url: `${API_BASE_URL}${API_URL_DELETE_SCANNED_HISTORY}`, + headers: { "X-USER-ID": userId}, + data: { "qrCodeId": qrCodeId } + }); +}; \ No newline at end of file diff --git a/reducers/qrCodesReducer.ts b/reducers/qrCodesReducer.ts index 3c56225..7b6434e 100644 --- a/reducers/qrCodesReducer.ts +++ b/reducers/qrCodesReducer.ts @@ -1,39 +1,86 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { QRCode, UserAttributes } from '../types'; +import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { QRCode, QRCodeType, UserAttributes } from "../types"; +import { deleteBookmark, deleteScannedHistory, setBookmark } from "../api/qrCodeAPI"; +import { RootState } from '../store'; +interface QRCodeState { + qrCodes: QRCode[]; + histories: QRCodeType[] | null; + bookmarks: QRCodeType[] | null; + userAttributes: UserAttributes; +} const initialState: QRCodeState = { - qrCodes: [], - userAttributes: null, + qrCodes: [], + histories: [], + bookmarks: [], + userAttributes: null, }; +export const toggleBookmark = createAsyncThunk( + 'qrCodes/toggleBookmark', + async ({ userId, qrCode }: { userId: string, qrCode: QRCodeType }, { dispatch, rejectWithValue }) => { + try { + await (qrCode.bookmarked ? deleteBookmark(userId, qrCode.data.id) : setBookmark(userId, qrCode.data.id)); + // Dispatch the action to update local state + dispatch(toggleBookmarkInState(qrCode)); + return qrCode; + + } catch (error) { + return rejectWithValue((error as Error).message); + } + } +); + +export const deleteQRCode = createAsyncThunk( + 'qrCodes/deleteQRCode', + async ({ userId, qrCodeId }: { userId: string, qrCodeId: string }, { dispatch, rejectWithValue }) => { + try { + await deleteScannedHistory(userId, qrCodeId); + dispatch(deleteQRCodeInState(qrCodeId)); + return qrCodeId; + } catch (error) { + return rejectWithValue((error as Error).message); + } + } +); + const qrCodesSlice = createSlice({ - name: 'qrCodes', - initialState, - reducers: { - addQRCode(state, action: PayloadAction) { - state.qrCodes.push(action.payload); - console.log('Added QR code to state:', action.payload); - }, - toggleBookmark(state, action: PayloadAction) { - const index = state.qrCodes.length - 1 - action.payload; - if (state.qrCodes[index]) { - state.qrCodes[index].bookmarked = !state.qrCodes[index].bookmarked; - console.log('Toggled bookmark for QR code at index:', index); - } - }, - deleteQRCode(state, action: PayloadAction) { - const index = state.qrCodes.length - 1 - (action.payload as number); - if (state.qrCodes[index]) { - console.log('Deleting QR code at index:', index); - state.qrCodes.splice(index, 1); - } - }, - setUserAttributes(state, action: PayloadAction) { - state.userAttributes = action.payload; - console.log('(Store)Set user attributes:', action.payload); - }, - }, + name: "qrCodes", + initialState, + reducers: { + addQRCode(state, action: PayloadAction) { + console.log("add qrcode action payload:", action.payload); + + state.qrCodes.push(action.payload); + console.log("Added QR code to state:", action.payload); + }, + toggleBookmarkInState(state, action: PayloadAction) { + const qrCode = action.payload; + + state.histories = state.histories!.map(b => b.data.id === qrCode.data.id ? { ...b, bookmarked: !qrCode.bookmarked } : b); + }, + deleteQRCodeInState(state, action: PayloadAction) { + state.histories = state.histories!.filter(qr => qr.data.id !== action.payload); + }, + setUserAttributes(state, action: PayloadAction) { + state.userAttributes = action.payload; + console.log("(Store)Set user attributes:", action.payload); + }, + setScannedHistories(state, action: PayloadAction) { + state.histories = action.payload; + }, + setBookmarks(state, action: PayloadAction) { + state.bookmarks = action.payload; + }, + } }); -export const { addQRCode, toggleBookmark, deleteQRCode, setUserAttributes } = qrCodesSlice.actions; +export const { + addQRCode, + toggleBookmarkInState, + deleteQRCodeInState, + setUserAttributes, + setScannedHistories, +} = qrCodesSlice.actions; + export default qrCodesSlice.reducer; diff --git a/screens/HistoryScreen.tsx b/screens/HistoryScreen.tsx index 7ad14d5..bf894e5 100644 --- a/screens/HistoryScreen.tsx +++ b/screens/HistoryScreen.tsx @@ -1,21 +1,22 @@ -import React, { useCallback, useState, useEffect } from 'react'; +import React, { useCallback, useState, useEffect, useRef } from 'react'; import { View, Text, StyleSheet, FlatList, TouchableOpacity, Image, BackHandler, Modal } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; import ScannedDataBox from '../components/ScannedDataBox'; import { Ionicons } from '@expo/vector-icons'; -import { RootState } from '../store'; -import { QRCode, QRCodeType } from '../types'; -import { toggleBookmark, deleteQRCode } from '../actions/qrCodeActions'; +import { RootState, AppDispatch } from '../store'; +import { QRCodeType } from '../types'; +import { toggleBookmark, deleteQRCode, setScannedHistories } from '../reducers/qrCodesReducer'; + import useFetchUserAttributes from '../hooks/useFetchUserAttributes'; -import useFetchScannedHistories from '../hooks/useFetchScannedHistories'; import { getScannedHistories } from '../api/qrCodeAPI'; const HistoryScreen: React.FC = () => { - const dispatch = useDispatch(); - const qrCodes = useSelector((state: RootState) => state.qrCodes.qrCodes); + const dispatch = useDispatch(); + const histories = useSelector((state: RootState) => state.qrCodes.histories); const { userAttributes } = useFetchUserAttributes(); - console.log("sub: ", userAttributes?.sub); - const [scannedHistories, setScannedHistories] = useState(null); + const [showBookmarks, setShowBookmarks] = useState(false); + const [qrCodeToDelete, setQrCodeToDelete] = useState(null); + const [isModalVisible, setIsModalVisible] = useState(false); const [historiesLoading, setHistoriesLoading] = useState(false); const [historiesError, setHistoriesError] = useState(null); @@ -23,30 +24,35 @@ const HistoryScreen: React.FC = () => { if (!userAttributes?.sub) return; try { - const data = await getScannedHistories(userAttributes.sub); - setScannedHistories(data as unknown as QRCodeType[]); + setHistoriesLoading(true); + const historiesData = await getScannedHistories(userAttributes.sub); + dispatch(setScannedHistories(historiesData)); + + setHistoriesLoading(false); } catch (error: any) { setHistoriesError(error.message); } finally { setHistoriesLoading(false); } - }, [userAttributes?.sub]); + }, [userAttributes?.sub, dispatch]); useEffect(() => { if (userAttributes?.sub) { fetchHistories(); } }, [userAttributes?.sub, fetchHistories]); - console.log("scanned history: ", scannedHistories); - - + + const handleDelete = useCallback((qrCodeId: string) => { + if (userAttributes?.sub) { + dispatch(deleteQRCode({ userId: userAttributes.sub, qrCodeId })); + setIsModalVisible(false); + } + }, [dispatch, userAttributes]); + const [selectedData, setSelectedData] = useState(null); const [selectedScanResult, setSelectedScanResult] = useState(null); const [selectedType, setSelectedType] = useState(null); - const [showBookmarks, setShowBookmarks] = useState(false); - const [isModalVisible, setIsModalVisible] = useState(false); - const [indexToDelete, setIndexToDelete] = useState(null); useEffect(() => { const backAction = () => { @@ -64,13 +70,7 @@ const HistoryScreen: React.FC = () => { return () => backHandler.remove(); }, [selectedData]); - //const filteredQrCodes = showBookmarks ? qrCodes.filter(qr => qr.bookmarked) : qrCodes.slice().reverse(); - const filteredQrCodes = showBookmarks ? scannedHistories.filter(qr => { - return qr.bookmarked - }) : scannedHistories; - - console.log("filtered", filteredQrCodes); - console.log("slice", qrCodes.slice().reverse()); + const filteredQrCodes = showBookmarks ? histories.filter(qr => qr.bookmarked) : histories; const handleItemPress = (item: QRCodeType) => { // setSelectedData(item.data); @@ -82,12 +82,6 @@ const HistoryScreen: React.FC = () => { // console.log('Selected QR code type:', item.type); }; - const confirmDelete = (index: number) => { - setIndexToDelete(index); - setIsModalVisible(true); - console.log('Confirm delete for QR code at index:', index); - }; - const clearSelectedData = () => { setSelectedData(null); setSelectedScanResult(null); @@ -114,8 +108,8 @@ const HistoryScreen: React.FC = () => { {/* List of QR codes */} { - console.log('Rendering QR code item:', item); + renderItem={({ item }) => { + // console.log('Rendering QR code item:', item); return ( @@ -130,10 +124,13 @@ const HistoryScreen: React.FC = () => { - dispatch(toggleBookmark(index))}> + dispatch(toggleBookmark({ userId: userAttributes.sub, qrCode: item}))}> - confirmDelete(index)}> + { + setQrCodeToDelete(item.data.id); + setIsModalVisible(true); + }}> @@ -159,7 +156,7 @@ const HistoryScreen: React.FC = () => { Are you sure? If bookmarked, this will be removed from both History and Bookmarks. - dispatch(deleteQRCode(indexToDelete))}> + handleDelete(qrCodeToDelete!)}> Yes, Delete setIsModalVisible(false)}> diff --git a/screens/QRScannerScreen.tsx b/screens/QRScannerScreen.tsx index 5e9d527..d82113c 100644 --- a/screens/QRScannerScreen.tsx +++ b/screens/QRScannerScreen.tsx @@ -8,7 +8,7 @@ import { useFocusEffect, useNavigation } from '@react-navigation/native'; import * as ImagePicker from 'expo-image-picker'; import ScannedDataBox from '../components/ScannedDataBox'; import { useDispatch } from 'react-redux'; -import { addQRCode } from '../actions/qrCodeActions'; // Assuming you have actions defined for Redux +import { addQRCode } from '../reducers/qrCodesReducer'; // Assuming you have actions defined for Redux import { detectQRCodeType, verifyURL, checkRedirects } from '../api/qrCodeAPI'; // Import utility functions // Main Function diff --git a/store.ts b/store.ts index 3ae4b7b..67c59f0 100644 --- a/store.ts +++ b/store.ts @@ -8,4 +8,5 @@ const store = configureStore({ }); export type RootState = ReturnType; -export default store; \ No newline at end of file +export default store; +export type AppDispatch = typeof store.dispatch; \ No newline at end of file