histories and bookmark done

This commit is contained in:
heyethereum
2024-07-22 22:54:57 +08:00
parent 220fa2dfd4
commit 12873b1fa8
6 changed files with 158 additions and 77 deletions

View File

@@ -1,6 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
import { QRCode } from '../types';
export const addQRCode = createAction<QRCode>('qrCodes/addQRCode');
export const toggleBookmark = createAction<number>('qrCodes/toggleBookmark');
export const deleteQRCode = createAction<number | null>('qrCodes/deleteQRCode');

View File

@@ -8,6 +8,10 @@ const API_URL_VERIFY_URL = "/v1/qrcodetypes/verifyURL"
const API_URL_VIRUS_TOTAL_CHECK = "/v1/qrcodetypes/virusTotalCheck" const API_URL_VIRUS_TOTAL_CHECK = "/v1/qrcodetypes/virusTotalCheck"
const API_URL_CHECK_REDIRECTS = "/v1/qrcodetypes/checkRedirects" const API_URL_CHECK_REDIRECTS = "/v1/qrcodetypes/checkRedirects"
const API_URL_GET_HISTORIES = "/v1/user/getScannedHistories" 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 // Define a generic function to handle all types of requests
export const apiRequest = async (config) => { export const apiRequest = async (config) => {
@@ -64,11 +68,49 @@ export const checkRedirects = async (data) => {
data: { data } data: { data }
}); });
}; };
// GET User's Scanned Histories
export const getScannedHistories = async (userId: String) => { export const getScannedHistories = async (userId: string) => {
return apiRequest({ return apiRequest({
method: 'get', method: 'get',
url: `${API_BASE_URL}${API_URL_GET_HISTORIES}`, url: `${API_BASE_URL}${API_URL_GET_HISTORIES}`,
headers: { "X-USER-ID": userId }, 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 }
});
};

View File

@@ -1,39 +1,86 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { QRCode, UserAttributes } from '../types'; 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 = { const initialState: QRCodeState = {
qrCodes: [], qrCodes: [],
userAttributes: null, 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({ const qrCodesSlice = createSlice({
name: 'qrCodes', name: "qrCodes",
initialState, initialState,
reducers: { reducers: {
addQRCode(state, action: PayloadAction<QRCode>) { addQRCode(state, action: PayloadAction<QRCode>) {
state.qrCodes.push(action.payload); console.log("add qrcode action payload:", action.payload);
console.log('Added QR code to state:', action.payload);
}, state.qrCodes.push(action.payload);
toggleBookmark(state, action: PayloadAction<number>) { console.log("Added QR code to state:", action.payload);
const index = state.qrCodes.length - 1 - action.payload; },
if (state.qrCodes[index]) { toggleBookmarkInState(state, action: PayloadAction<QRCodeType>) {
state.qrCodes[index].bookmarked = !state.qrCodes[index].bookmarked; const qrCode = action.payload;
console.log('Toggled bookmark for QR code at index:', index);
} state.histories = state.histories!.map(b => b.data.id === qrCode.data.id ? { ...b, bookmarked: !qrCode.bookmarked } : b);
}, },
deleteQRCode(state, action: PayloadAction<number | null>) { deleteQRCodeInState(state, action: PayloadAction<string | null>) {
const index = state.qrCodes.length - 1 - (action.payload as number); state.histories = state.histories!.filter(qr => qr.data.id !== action.payload);
if (state.qrCodes[index]) { },
console.log('Deleting QR code at index:', index); setUserAttributes(state, action: PayloadAction<UserAttributes>) {
state.qrCodes.splice(index, 1); state.userAttributes = action.payload;
} console.log("(Store)Set user attributes:", action.payload);
}, },
setUserAttributes(state, action: PayloadAction<UserAttributes>) { setScannedHistories(state, action: PayloadAction<QRCodeType[]>) {
state.userAttributes = action.payload; state.histories = action.payload;
console.log('(Store)Set user attributes:', action.payload); },
}, setBookmarks(state, action: PayloadAction<QRCodeType[]>) {
}, 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; export default qrCodesSlice.reducer;

View File

@@ -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 { View, Text, StyleSheet, FlatList, TouchableOpacity, Image, BackHandler, Modal } from 'react-native';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import ScannedDataBox from '../components/ScannedDataBox'; import ScannedDataBox from '../components/ScannedDataBox';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { RootState } from '../store'; import { RootState, AppDispatch } from '../store';
import { QRCode, QRCodeType } from '../types'; import { QRCodeType } from '../types';
import { toggleBookmark, deleteQRCode } from '../actions/qrCodeActions'; import { toggleBookmark, deleteQRCode, setScannedHistories } from '../reducers/qrCodesReducer';
import useFetchUserAttributes from '../hooks/useFetchUserAttributes'; import useFetchUserAttributes from '../hooks/useFetchUserAttributes';
import useFetchScannedHistories from '../hooks/useFetchScannedHistories';
import { getScannedHistories } from '../api/qrCodeAPI'; import { getScannedHistories } from '../api/qrCodeAPI';
const HistoryScreen: React.FC = () => { const HistoryScreen: React.FC = () => {
const dispatch = useDispatch(); const dispatch = useDispatch<AppDispatch>();
const qrCodes = useSelector((state: RootState) => state.qrCodes.qrCodes); const histories = useSelector((state: RootState) => state.qrCodes.histories);
const { userAttributes } = useFetchUserAttributes(); const { userAttributes } = useFetchUserAttributes();
console.log("sub: ", userAttributes?.sub); const [showBookmarks, setShowBookmarks] = useState<boolean>(false);
const [scannedHistories, setScannedHistories] = useState<QRCodeType []| null>(null); const [qrCodeToDelete, setQrCodeToDelete] = useState<string | null>(null);
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
const [historiesLoading, setHistoriesLoading] = useState(false); const [historiesLoading, setHistoriesLoading] = useState(false);
const [historiesError, setHistoriesError] = useState<string | null>(null); const [historiesError, setHistoriesError] = useState<string | null>(null);
@@ -23,30 +24,35 @@ const HistoryScreen: React.FC = () => {
if (!userAttributes?.sub) return; if (!userAttributes?.sub) return;
try { try {
const data = await getScannedHistories(userAttributes.sub); setHistoriesLoading(true);
setScannedHistories(data as unknown as QRCodeType[]); const historiesData = await getScannedHistories(userAttributes.sub);
dispatch(setScannedHistories(historiesData));
setHistoriesLoading(false);
} catch (error: any) { } catch (error: any) {
setHistoriesError(error.message); setHistoriesError(error.message);
} finally { } finally {
setHistoriesLoading(false); setHistoriesLoading(false);
} }
}, [userAttributes?.sub]); }, [userAttributes?.sub, dispatch]);
useEffect(() => { useEffect(() => {
if (userAttributes?.sub) { if (userAttributes?.sub) {
fetchHistories(); fetchHistories();
} }
}, [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<string | null>(null); const [selectedData, setSelectedData] = useState<string | null>(null);
const [selectedScanResult, setSelectedScanResult] = useState<any | null>(null); const [selectedScanResult, setSelectedScanResult] = useState<any | null>(null);
const [selectedType, setSelectedType] = useState<string | null>(null); const [selectedType, setSelectedType] = useState<string | null>(null);
const [showBookmarks, setShowBookmarks] = useState<boolean>(false);
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
const [indexToDelete, setIndexToDelete] = useState<number | null>(null);
useEffect(() => { useEffect(() => {
const backAction = () => { const backAction = () => {
@@ -64,13 +70,7 @@ const HistoryScreen: React.FC = () => {
return () => backHandler.remove(); return () => backHandler.remove();
}, [selectedData]); }, [selectedData]);
//const filteredQrCodes = showBookmarks ? qrCodes.filter(qr => qr.bookmarked) : qrCodes.slice().reverse(); const filteredQrCodes = showBookmarks ? histories.filter(qr => qr.bookmarked) : histories;
const filteredQrCodes = showBookmarks ? scannedHistories.filter(qr => {
return qr.bookmarked
}) : scannedHistories;
console.log("filtered", filteredQrCodes);
console.log("slice", qrCodes.slice().reverse());
const handleItemPress = (item: QRCodeType) => { const handleItemPress = (item: QRCodeType) => {
// setSelectedData(item.data); // setSelectedData(item.data);
@@ -82,12 +82,6 @@ const HistoryScreen: React.FC = () => {
// console.log('Selected QR code type:', item.type); // 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 = () => { const clearSelectedData = () => {
setSelectedData(null); setSelectedData(null);
setSelectedScanResult(null); setSelectedScanResult(null);
@@ -114,8 +108,8 @@ const HistoryScreen: React.FC = () => {
{/* List of QR codes */} {/* List of QR codes */}
<FlatList <FlatList
data={filteredQrCodes} data={filteredQrCodes}
renderItem={({ item, index }) => { renderItem={({ item }) => {
console.log('Rendering QR code item:', item); // console.log('Rendering QR code item:', item);
return ( return (
<View style={styles.itemContainer}> <View style={styles.itemContainer}>
<View style={styles.itemLeft}> <View style={styles.itemLeft}>
@@ -130,10 +124,13 @@ const HistoryScreen: React.FC = () => {
</Text> </Text>
</View> </View>
<View style={styles.itemRight}> <View style={styles.itemRight}>
<TouchableOpacity onPress={() => dispatch(toggleBookmark(index))}> <TouchableOpacity onPress={() => dispatch(toggleBookmark({ userId: userAttributes.sub, qrCode: item}))}>
<Ionicons name={item.bookmarked ? "bookmark" : "bookmark-outline"} size={24} color={item.bookmarked ? "#2196F3" : "#ff69b4"} /> <Ionicons name={item.bookmarked ? "bookmark" : "bookmark-outline"} size={24} color={item.bookmarked ? "#2196F3" : "#ff69b4"} />
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => confirmDelete(index)}> <TouchableOpacity onPress={() => {
setQrCodeToDelete(item.data.id);
setIsModalVisible(true);
}}>
<Ionicons name="close-circle-outline" size={24} color="#ff69b4" /> <Ionicons name="close-circle-outline" size={24} color="#ff69b4" />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@@ -159,7 +156,7 @@ const HistoryScreen: React.FC = () => {
<Text style={styles.modalTitle}>Are you sure?</Text> <Text style={styles.modalTitle}>Are you sure?</Text>
<Text style={styles.modalText}>If bookmarked, this will be removed from both History and Bookmarks.</Text> <Text style={styles.modalText}>If bookmarked, this will be removed from both History and Bookmarks.</Text>
<View style={styles.modalButtons}> <View style={styles.modalButtons}>
<TouchableOpacity style={styles.modalButton} onPress={() => dispatch(deleteQRCode(indexToDelete))}> <TouchableOpacity style={styles.modalButton} onPress={() => handleDelete(qrCodeToDelete!)}>
<Text style={styles.modalButtonText}>Yes, Delete</Text> <Text style={styles.modalButtonText}>Yes, Delete</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={styles.modalButton} onPress={() => setIsModalVisible(false)}> <TouchableOpacity style={styles.modalButton} onPress={() => setIsModalVisible(false)}>

View File

@@ -8,7 +8,7 @@ import { useFocusEffect, useNavigation } from '@react-navigation/native';
import * as ImagePicker from 'expo-image-picker'; import * as ImagePicker from 'expo-image-picker';
import ScannedDataBox from '../components/ScannedDataBox'; import ScannedDataBox from '../components/ScannedDataBox';
import { useDispatch } from 'react-redux'; 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 import { detectQRCodeType, verifyURL, checkRedirects } from '../api/qrCodeAPI'; // Import utility functions
// Main Function // Main Function

View File

@@ -8,4 +8,5 @@ const store = configureStore({
}); });
export type RootState = ReturnType<typeof store.getState>; export type RootState = ReturnType<typeof store.getState>;
export default store; export default store;
export type AppDispatch = typeof store.dispatch;