diff --git a/App.tsx b/App.tsx index b499ef36..ff648bd0 100644 --- a/App.tsx +++ b/App.tsx @@ -10,7 +10,9 @@ import rootSaga from './src/sagas/rootSaga'; const sagaMiddleware = createSagaMiddleware(); const middleware = [sagaMiddleware]; -export const store = compose(applyMiddleware(...middleware))(createStore)(reducers); +export const store = compose(applyMiddleware(...middleware))(createStore)( + reducers, +); sagaMiddleware.run(rootSaga); const App = () => { diff --git a/package.json b/package.json index 79627c8a..0c8cac73 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@react-native-masked-view/masked-view": "^0.2.6", "@react-navigation/bottom-tabs": "^6.0.9", "@react-navigation/drawer": "^6.1.8", + "@react-navigation/material-top-tabs": "^6.6.4", "@react-navigation/native": "^6.0.8", "@react-navigation/native-stack": "^6.9.12", "@react-navigation/stack": "^6.2.0", @@ -29,6 +30,7 @@ "react-native-app-auth": "^6.4.2", "react-native-asset": "^2.1.1", "react-native-camera-kit": "^13.0.0", + "react-native-collapsible-tab-view": "^6.2.1", "react-native-date-picker": "^4.2.13", "react-native-datepicker": "^1.7.2", "react-native-device-info": "^10.8.0", @@ -38,11 +40,13 @@ "react-native-gesture-handler": "^1.10.3", "react-native-image-picker": "^4.7.1", "react-native-keychain": "^8.1.2", + "react-native-pager-view": "^6.2.1", "react-native-paper": "^4.12.3", "react-native-radio-buttons-group": "^2.2.11", "react-native-reanimated": "^2.2.4", "react-native-safe-area-context": "^3.2.0", "react-native-screens": "^3.9.0", + "react-native-tab-view": "^3.5.2", "react-native-toast-message": "^2.1.5", "react-native-webview": "^13.3.1", "react-native-walkthrough-tooltip": "^1.5.0", diff --git a/src/components/DisplayContribution.tsx b/src/components/DisplayContribution.tsx new file mode 100644 index 00000000..2a0d3221 --- /dev/null +++ b/src/components/DisplayContribution.tsx @@ -0,0 +1,175 @@ +import { View, Text, TouchableOpacity, Linking } from 'react-native'; +import React from 'react'; +import { + calculateISODateFormat, + calculateTimeDifference, + convertTimestampToReadableDate, + parseISODate, +} from '../screens/AuthScreen/Util'; +import { profileScreenStyles } from '../screens/ProfileScreen/styles'; + +const DisplayContribution = ({ tasks }) => { + return ( + + {tasks.length !== 0 ? ( + tasks.map((item, index) => ( + + Linking.openURL(item.task.featureUrl) + : null + } + > + {item.task.id ? ( + + + {item.task.title} + + <> + {item.task.purpose ? ( + + {item.task.purpose} + + ) : ( + + )} + + <> + {item.task.featureUrl ? ( + + Estimated completion:{' '} + + {calculateTimeDifference( + convertTimestampToReadableDate(item.task.startedOn), + convertTimestampToReadableDate(item.task.endsOn), + )} + + + ) : ( + + Estimated completion:{' '} + + {calculateTimeDifference( + convertTimestampToReadableDate(item.task.startedOn), + convertTimestampToReadableDate(item.task.endsOn), + )} + + + )} + + <> + {item.task.featureUrl ? ( + + Checkout this feature in action + + ) : null} + + + ) : ( + + {item.prList.length > 0 && ( + + + PR Title: {item.prList[0].title} + + + Completed in:{' '} + + {calculateTimeDifference( + parseISODate(item.prList[0].createdAt), + parseISODate(item.prList[0].updatedAt), + )} + + + + Feature live on:{' '} + {calculateISODateFormat(item.prList[0].updatedAt)} + + <> + {item.prList[0].url ? ( + + Checkout this feature in action + + ) : null} + + + )} + + )} + + + )) + ) : ( + No Tasks Yet! + )} + + ); +}; + +export default DisplayContribution; diff --git a/src/components/UserContibution/DisplayContribution.tsx b/src/components/UserContibution/DisplayContribution.tsx index 80783943..d6d00f33 100644 --- a/src/components/UserContibution/DisplayContribution.tsx +++ b/src/components/UserContibution/DisplayContribution.tsx @@ -14,7 +14,7 @@ import { const DisplayContribution = ({ tasks }) => { const [clicked, setClicked] = useState(false); - + return ( { { diff --git a/src/screens/AuthScreen/Util.ts b/src/screens/AuthScreen/Util.ts index aabba6a2..ad7ca403 100644 --- a/src/screens/AuthScreen/Util.ts +++ b/src/screens/AuthScreen/Util.ts @@ -171,6 +171,11 @@ export const formatTimeToUnix = (date) => { const unixTimestampInSeconds = newDate.getTime(); return unixTimestampInSeconds; }; + +export const convertTimestampToReadableDate = (timestamp) => { + return new Date(timestamp * 1000); +}; + export const calculateTimeDifference = (startDate, endDate) => { const timeDifference = endDate - startDate; const secondsInMillisecond = 1000; @@ -198,6 +203,34 @@ export const calculateTimeDifference = (startDate, endDate) => { } }; -export const convertTimestampToReadableDate = (timestamp) => { - return new Date(timestamp * 1000); +export const calculateISODateFormat = (isoDateString) => { + const date = new Date(isoDateString); + const formatDate = (d) => { + const months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + + const day = d.getDate(); + const monthIndex = d.getMonth(); + const year = d.getFullYear(); + + return `${day} ${months[monthIndex]}, ${year}`; + }; + const formattedDate = formatDate(date); + return formattedDate; +}; + +export const parseISODate = (isoDateString) => { + return new Date(isoDateString); }; diff --git a/src/screens/ProfileScreen/ProfileScreen.tsx b/src/screens/ProfileScreen/ProfileScreen.tsx index bd26f9f0..5ef2ac97 100644 --- a/src/screens/ProfileScreen/ProfileScreen.tsx +++ b/src/screens/ProfileScreen/ProfileScreen.tsx @@ -9,6 +9,8 @@ import { StyleSheet, SafeAreaView, ScrollView, + Button, + TouchableOpacity, } from 'react-native'; import { ScreenViewContainer } from '../../styles/GlobalStyle'; import { profileScreenStyles } from './styles'; @@ -23,6 +25,7 @@ import NoteworthyContributionsDropdown from './User Data/UserContributions/NoteW import ActiveTaskDropDown from './User Data/UserContributions/ActiveTask'; import UserData from './User Data/UserData'; import { useSelector, useDispatch } from 'react-redux'; +import { AuthViewStyle } from '../AuthScreen/styles'; import AllContributionsDropdown from './User Data/UserContributions/AllContributions'; const ProfileScreen = () => { @@ -53,10 +56,16 @@ const ProfileScreen = () => { }; const handleLogout = () => { - // please remove the token + setLoggedInUserData(null); }; return ( + + {Strings.LOGOUT} + { : dispatch({ type: 'PROD' }); }} /> - + @@ -103,14 +112,5 @@ const ProfileScreen = () => { ); }; -const styles = StyleSheet.create({ - container: { - marginBottom: 10, - paddingBottom: 30, - }, - container2: { - borderWidth: 2, - }, -}); export default withHeader(ProfileScreen); diff --git a/src/screens/ProfileScreen/ProfileScreen2.tsx b/src/screens/ProfileScreen/ProfileScreen2.tsx new file mode 100644 index 00000000..ee28b000 --- /dev/null +++ b/src/screens/ProfileScreen/ProfileScreen2.tsx @@ -0,0 +1,170 @@ +import React, { useState, useCallback, useContext } from 'react'; +import { View, Text, ScrollView, Pressable, StyleSheet } from 'react-native'; +import { ScreenViewContainer } from '../../styles/GlobalStyle'; +import { profileScreenStyles } from './styles'; +import ButtonWidget from '../../components/ButtonWidget'; +import Avatar from '../../components/Avatar'; +import UploadImageModalView from '../../components/GalleryModal'; +import { AuthContext } from '../../context/AuthContext'; +import { ImagePickerResponse } from 'react-native-image-picker'; +import Strings from '../../i18n/en'; +import UserData from './User Data/UserData'; +import { useSelector, useDispatch } from 'react-redux'; +import All from './UserDataV2/All'; +import Note from './UserDataV2/NoteWorthy'; +import { Tabs } from 'react-native-collapsible-tab-view'; +import { useFocusEffect } from '@react-navigation/native'; +import { fetchContribution } from '../AuthScreen/Util'; +import DisplayContribution from '../../components/DisplayContribution'; + +const ActiveScreen = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [activeTasks, setActiveTasks] = useState([]); + const { loggedInUserData } = useContext(AuthContext); + + useFocusEffect( + useCallback(() => { + (async () => { + const userName = loggedInUserData?.username; + const contributionResponse = await fetchContribution(userName); + setActiveTasks( + contributionResponse.all.filter( + (item) => item.task.status === 'ACTIVE', + ), + ); + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []), + ); + return ( + + + + ); +}; + +const ProfileScreen = () => { + const dispatch = useDispatch(); + const { isProdEnvironment } = useSelector((store) => store.localFeatureFlag); + const [response, setResponse] = useState({}); + const [modalVisible, setModalVisible] = useState(false); + const { loggedInUserData, setLoggedInUserData } = useContext(AuthContext); + + const openModal = useCallback(() => { + setModalVisible(true); + }, []); + + const closeModal = useCallback(() => { + setModalVisible(false); + }, []); + + const removePicture = () => { + setResponse({}); + closeModal(); + }; + + const showDefaultAvatar = () => { + if (response?.assets) { + return false; + } + return true; + }; + + const handleLogout = () => { + setLoggedInUserData(null); + }; + + return ( + + + {Strings.LOGOUT} + + + + {response?.assets && + response.assets.map(({ uri }) => ( + + ))} + {showDefaultAvatar() && ( + + )} + + + + + { + isProdEnvironment + ? dispatch({ type: 'DEV' }) + : dispatch({ type: 'PROD' }); + }} + /> + + + ); +}; + +const ProfileScreen2: React.FC = () => { + const tabNames = ['Noteworthy', 'Active ', 'All']; + + const renderTab = ({ _name, active }: { _name: string; active: boolean }) => { + return ( + // eslint-disable-next-line react/jsx-no-comment-textnodes + + {tabNames.map((name) => ( + + {name} + + ))} + + ); + }; + + return ( + + + + + + + + + + + + + + + + + + ); +}; +const styles = StyleSheet.create({ + tab: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'center', + }, + tabName: { + fontSize: 16, // You can adjust the font size and other styles + fontWeight: 'bold', + padding: 20, + color: 'black', // Change the text color as needed + }, + activeTabName: { + fontWeight: 'bold', // Add styles for the active tab + }, +}); +export default ProfileScreen2; diff --git a/src/screens/ProfileScreen/UserDataV2/All.tsx b/src/screens/ProfileScreen/UserDataV2/All.tsx new file mode 100644 index 00000000..784d1984 --- /dev/null +++ b/src/screens/ProfileScreen/UserDataV2/All.tsx @@ -0,0 +1,196 @@ +import React, { useCallback, useContext, useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + Linking, + ScrollView, +} from 'react-native'; +import { profileScreenStyles } from '../styles'; +import { + calculateISODateFormat, + calculateTimeDifference, + convertTimestampToReadableDate, + fetchContribution, + parseISODate, +} from '../../AuthScreen/Util'; +import { useFocusEffect } from '@react-navigation/native'; +import { AuthContext } from '../../../context/AuthContext'; + +const All = () => { + const [allContributionsData, setAllContributionData] = useState([]); + const { loggedInUserData } = useContext(AuthContext); + + useFocusEffect( + useCallback(() => { + (async () => { + const userName = loggedInUserData?.username; + const contributionResponse = await fetchContribution(userName); + setAllContributionData(contributionResponse.all); + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []), + ); + + return ( + + + {allContributionsData.map((item, index) => ( + + Linking.openURL(item.task.featureUrl) + : null + } + > + {item.task.id ? ( + + + {item.task.title} + + <> + {item.task.purpose ? ( + + {item.task.purpose} + + ) : ( + + )} + + <> + {item.task.featureUrl ? ( + + Estimated completion:{' '} + + {calculateTimeDifference( + convertTimestampToReadableDate(item.task.startedOn), + convertTimestampToReadableDate(item.task.endsOn), + )} + + + ) : ( + + Estimated completion:{' '} + + {calculateTimeDifference( + convertTimestampToReadableDate(item.task.startedOn), + convertTimestampToReadableDate(item.task.endsOn), + )} + + + )} + + <> + {item.task.featureUrl ? ( + + Checkout this feature in action + + ) : null} + + + ) : ( + + {item.prList.length > 0 && ( + + + PR Title: {item.prList[0].title} + + + Completed in:{' '} + + {calculateTimeDifference( + parseISODate(item.prList[0].createdAt), + parseISODate(item.prList[0].updatedAt), + )} + + + + Feature live on:{' '} + {calculateISODateFormat(item.prList[0].updatedAt)} + + <> + {item.prList[0].url ? ( + + Checkout this feature in action + + ) : null} + + + )} + + )} + + + ))} + + + ); +}; + +export default All; diff --git a/src/screens/ProfileScreen/UserDataV2/NoteWorthy.tsx b/src/screens/ProfileScreen/UserDataV2/NoteWorthy.tsx new file mode 100644 index 00000000..c3850be0 --- /dev/null +++ b/src/screens/ProfileScreen/UserDataV2/NoteWorthy.tsx @@ -0,0 +1,121 @@ +/* eslint-disable react-native/no-inline-styles */ +import React, { useCallback, useContext, useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + Linking, + ScrollView, +} from 'react-native'; +import { profileScreenStyles } from '../styles'; +import { + calculateTimeDifference, + convertTimestampToReadableDate, + fetchContribution, +} from '../../AuthScreen/Util'; +import { useFocusEffect } from '@react-navigation/native'; +import { AuthContext } from '../../../context/AuthContext'; + +const Note = () => { + const [userContributionData, setUserContributionData] = useState([]); + const { loggedInUserData } = useContext(AuthContext); + + useFocusEffect( + useCallback(() => { + (async () => { + const userName = loggedInUserData?.username; + const contributionResponse = await fetchContribution(userName); + setUserContributionData(contributionResponse.noteworthy); + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []), + ); + + return ( + + {userContributionData ? ( + + {userContributionData.map((item, index) => ( + + Linking.openURL(item.task.featureUrl) + : null + } + > + + {item.task.title} + + <> + {item.task.purpose ? ( + + {item.task.purpose} + + ) : ( + + )} + + + Estimated completion:{''} + + {calculateTimeDifference( + convertTimestampToReadableDate(item.task.startedOn), + convertTimestampToReadableDate(item.task.endsOn), + )} + + + <> + {item.task.featureUrl ? ( + + Checkout this feature in action + + ) : null} + + + + ))} + + ) : ( + + + No noteworthy task yet! + + + )} + + ); +}; + +export default Note; diff --git a/src/screens/ProfileScreen/styles.ts b/src/screens/ProfileScreen/styles.ts index 07fe65c6..070bdd10 100644 --- a/src/screens/ProfileScreen/styles.ts +++ b/src/screens/ProfileScreen/styles.ts @@ -3,13 +3,13 @@ import { StyleSheet } from 'react-native'; export const profileScreenStyles = StyleSheet.create({ mainview: { flex: 1, - paddingTop: 60, + paddingTop: 10, width: '100%', alignItems: 'center', }, titleText: { fontSize: 24, - padding: 18, + padding: 5, color: 'black', }, subTitleText: { @@ -77,32 +77,57 @@ export const profileScreenStyles = StyleSheet.create({ bottom: 0, }, logoutButton: { - backgroundColor: '#E20062', - borderRadius: 20, - padding: 10, - elevation: 2, - width: '40%', - alignItems: 'center', - position: 'relative', - // bottom: 10, - margin: 10, - alignSelf: 'center', - color: 'white', - - // paddingHorizontal: 8, - // paddingVertical: 6, + // backgroundColor: '#E20062', // borderRadius: 20, + // padding: 10, // elevation: 2, - // top: 10, - // backgroundColor: '#E20062', - // alignSelf: 'flex-end', - // margin: 5, // width: '40%', - // textAlign: 'center', + // alignItems: 'center', + // position: 'absolute', + // bottom: 10, + // color: 'white', + + paddingHorizontal: 5, + paddingVertical: 6, + borderRadius: 20, + elevation: 2, + top: 10, + backgroundColor: '#E20062', + alignSelf: 'flex-end', + margin: 5, + width: '20%', + textAlign: 'center', }, logoutText: { color: 'white', fontWeight: 'bold', textAlign: 'center', }, + //UI enhance + container: { + flex: 1, + padding: 10, + backgroundColor: '#fff', + borderRadius: 10, + // elevation: 1, + }, + DropDownElement: { + // padding: 2, + color: 'black', + width: '100%', + alignSelf: 'center', + height: 'auto', + }, + DropDownbackground: { + padding: 5, + // elevation: 1, + height: 'auto', + alignSelf: 'center', + width: '100%', + // backgroundColor: '#fff', + // borderRadius: 10, + borderBottomWidth: 1, + borderBottomColor: 'grey', + // elevation: 1, + }, }); diff --git a/yarn.lock b/yarn.lock index 1230e0ff..908a4144 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1344,6 +1344,14 @@ resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.18.tgz#d8364b40276f3efb9c229c39da3b8b465f18f0a2" integrity sha512-/0hwnJkrr415yP0Hf4PjUKgGyfshrvNUKFXN85Mrt1gY49hy9IwxZgrrxlh0THXkPeq8q4VWw44eHDfAcQf20Q== +"@react-navigation/material-top-tabs@^6.6.4": + version "6.6.4" + resolved "https://registry.yarnpkg.com/@react-navigation/material-top-tabs/-/material-top-tabs-6.6.4.tgz#6ab0b320fd5264f2d0f2d6fd0d92f42e69e53403" + integrity sha512-vl0YaUHVPk0Cmv+yBE5b811+jVgr8f0x5czZVwAyFaluedNgzBg9ybj9oo8GzjwfyOFJQwq8dpH/NV6EYi/LdQ== + dependencies: + color "^4.2.3" + warn-once "^0.1.0" + "@react-navigation/native-stack@^6.9.12": version "6.9.13" resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.9.13.tgz#f308c398ee18fcd45de8ec7c04fe0641735feb31" @@ -2893,6 +2901,11 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +dequal@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-1.0.0.tgz#41c6065e70de738541c82cdbedea5292277a017e" + integrity sha512-/Nd1EQbQbI9UbSHrMiKZjFLrXSnU328iQdZKPQf78XQI6C+gutkFUeoHpG5J08Ioa6HeRbRNFpSIclh1xyG0mw== + destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" @@ -6217,6 +6230,13 @@ react-native-codegen@^0.0.8: jscodeshift "^0.11.0" nullthrows "^1.1.1" +react-native-collapsible-tab-view@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/react-native-collapsible-tab-view/-/react-native-collapsible-tab-view-6.2.1.tgz#b48760cd98c0156bc89300516ae10ff55f7df298" + integrity sha512-pgmpZYSg6TPYmhv3Dw/czFJHk4AzcYN2df3EMlKwFqbu1ifmlA866hBUVDX3mlbRaros75U5S1wpGCwlqqXLTQ== + dependencies: + use-deep-compare "^1.1.0" + react-native-date-picker@^4.2.13: version "4.2.13" resolved "https://registry.yarnpkg.com/react-native-date-picker/-/react-native-date-picker-4.2.13.tgz#b5b894ee6c5a154a752f9094342170515e169ea6" @@ -6279,6 +6299,11 @@ react-native-keychain@^8.1.2: resolved "https://registry.yarnpkg.com/react-native-keychain/-/react-native-keychain-8.1.2.tgz#34291ae472878e5124d081211af5ede7d810e64f" integrity sha512-bhHEui+yMp3Us41NMoRGtnWEJiBE0g8tw5VFpq4mpmXAx6XJYahuM6K3WN5CsUeUl83hYysSL9oFZNKSTPSvYw== +react-native-pager-view@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.2.1.tgz#985d21bbb7d7bc012f4c142dedbb98ce4bdea104" + integrity sha512-fZ8chez5J+w831nSnbtK8hwlCiMy1DgpFv8xG6ajXLsAI8uMkGvP554QDIDNlUoCuVwMf7OFyd32Y2Nm2Iaj9Q== + react-native-paper@^4.12.3: version "4.12.5" resolved "https://registry.yarnpkg.com/react-native-paper/-/react-native-paper-4.12.5.tgz#5ea4bbe02d416d17802a199de748700358c11d3a" @@ -6320,6 +6345,13 @@ react-native-screens@^3.9.0: react-freeze "^1.0.0" warn-once "^0.1.0" +react-native-tab-view@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-3.5.2.tgz#2789b8af6148b16835869566bf13dc3b0e6c1b46" + integrity sha512-nE5WqjbeEPsWQx4mtz81QGVvgHRhujTNIIZiMCx3Bj6CBFDafbk7XZp9ocmtzXUQaZ4bhtVS43R4FIiR4LboJw== + dependencies: + use-latest-callback "^0.1.5" + react-native-toast-message@^2.1.5: version "2.1.6" resolved "https://registry.yarnpkg.com/react-native-toast-message/-/react-native-toast-message-2.1.6.tgz#322827c66901fa22cb3db614c7383cc717f5bbe2" @@ -7635,6 +7667,13 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" +use-deep-compare@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/use-deep-compare/-/use-deep-compare-1.1.0.tgz#85580dde751f68400bf6ef7e043c7f986595cef8" + integrity sha512-6yY3zmKNCJ1jjIivfZMZMReZjr8e6iC6Uqtp701jvWJ6ejC/usXD+JjmslZDPJQgX8P4B1Oi5XSLHkOLeYSJsA== + dependencies: + dequal "1.0.0" + use-latest-callback@^0.1.5: version "0.1.6" resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.6.tgz#3fa6e7babbb5f9bfa24b5094b22939e1e92ebcf6"