Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions front/src/components/notifications/NotificationItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { FC } from 'react';
import { StyleSheet, View } from 'react-native';
import { Text } from 'react-native-paper';

import { Notification } from '@/models/entities/notification';

/**
* The styles for the NotificationItem component.
*/
const styles = StyleSheet.create({
container: {
gap: 16,
},

verticalContainer: {
flexDirection: 'column',
gap: 12,
},
});

/**
* The props for the NotificationItem component.
*/
type NotificationItemProps = {
/**
* The notification to display.
*/
notification: Notification;
};

/**
* Displays a notification.
* @constructor
*/
const NotificationItem: FC<NotificationItemProps> = ({ notification }) => {
return (
<View>
<View style={[styles.container, styles.verticalContainer]}>
<Text variant='labelLarge'>{`${notification.title}`}</Text>
<Text>{`${notification.content}`}</Text>
</View>
</View>
);
};

export default NotificationItem;
40 changes: 40 additions & 0 deletions front/src/components/notifications/NotificationList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { FC } from 'react';
import { StyleSheet, View } from 'react-native';
import { Divider } from 'react-native-paper';

import { Notification } from '@/models/entities/notification';

import NotificationItem from './NotificationItem';

/**
* The styles for the NotificationList component.
*/
const styles = StyleSheet.create({
divider: {
height: 1,
marginVertical: 8,
width: '100%',
},
});

type NotificationListProps = {
/**
* The list of notifications to display.
*/
notifications: Notification[];
};

const NotificationList: FC<NotificationListProps> = ({ notifications }) => {
return (
<View>
{notifications?.map((notification) => (
<View key={notification.id}>
<NotificationItem notification={notification} />
<Divider style={styles.divider} />
</View>
))}
</View>
);
};

export default NotificationList;
25 changes: 25 additions & 0 deletions front/src/models/entities/notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Notification.
*/
export type Notification = {
/**
* The id of the notification.
*/
id: string;

/**
* The title of the notification.
*/
title: string;

/**
* The content of the notification.
*/
content: string;

/**
* The date of the notification.
* Formatted as an ISO 8601 string.
*/
sentAt: string;
};
67 changes: 67 additions & 0 deletions front/src/pages/messaging/MessageTabBarNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import { StyleSheet } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';

import NotificationListPage from '../notifications/NotificationlListPage';
import MessageChannelListPage from './MessageChannelListPage';

/**
* The styles for the MessageTabBarNav component.
*/
const styles = StyleSheet.create({
container: {
gap: 10,
},
});

/**
* The parameter list for the MessageTabBarNav navigator.
*/
export type MessageTabBarNavParamList = {
MessagesTabBar: undefined;
NotificationsTabBar: undefined;
};

const MessagingTabBar =
createMaterialTopTabNavigator<MessageTabBarNavParamList>();

/**
* The top tabbed navigator for testing it.
* @constructor
*/
const MessageTabBarNav = () => {
return (
<MessagingTabBar.Navigator style={styles.container}>
<MessagingTabBar.Screen
name='MessagesTabBar'
component={MessageChannelListPage}
options={{
tabBarLabel: 'Messages',
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name='message-text-outline'
color={color}
size={24}
/>
),
}}
/>
<MessagingTabBar.Screen
name='NotificationsTabBar'
component={NotificationListPage}
options={{
tabBarLabel: 'Notifications',
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name='bell-outline'
color={color}
size={24}
/>
),
}}
/>
</MessagingTabBar.Navigator>
);
};

export default MessageTabBarNav;
19 changes: 11 additions & 8 deletions front/src/pages/messaging/MessagingNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
import PaperNavigationBar from '@/components/utils/PaperNavigationBar';
import i18n from '@/utils/i18n';

import MessagingListPage from './MessageChannelListPage';
import MessageChannelPage, {
MessageChannelPageParams,
} from './MessageChannelPage';
import MessageTabBarNav from './MessageTabBarNav';

/**
* The parameter list for the MessagingNav navigator.
*/
export type MessagingStackParamList = {
MessageChannelList: undefined;
MessageChannel: MessageChannelPageParams;
MessageTabBar: undefined;
};

const MessagingStack = createNativeStackNavigator<MessagingStackParamList>();
Expand All @@ -25,19 +26,21 @@ const MessagingStack = createNativeStackNavigator<MessagingStackParamList>();
const MessagingNav = () => {
return (
<MessagingStack.Navigator
initialRouteName='MessageChannelList'
screenOptions={{ header: (props) => <PaperNavigationBar {...props} /> }}
initialRouteName='MessageTabBar'
screenOptions={{
header: (props) => <PaperNavigationBar {...props} />,
headerShown: false,
}}
>
<MessagingStack.Screen
name='MessageChannelList'
component={MessagingListPage}
options={{ headerTitle: `${i18n.t('messaging.info.messageChannel')}` }}
/>
<MessagingStack.Screen
name='MessageChannel'
component={MessageChannelPage}
options={{ headerTitle: `${i18n.t('messaging.info.messageChannel')}` }}
/>
<MessagingStack.Screen
name='MessageTabBar'
component={MessageTabBarNav}
/>
</MessagingStack.Navigator>
);
};
Expand Down
52 changes: 52 additions & 0 deletions front/src/pages/notifications/NotificationlListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useFocusEffect } from '@react-navigation/native';
import { FC, useCallback } from 'react';
import { ScrollView, StyleSheet } from 'react-native';

import NotificationList from '@/components/notifications/NotificationList';
import { useGetNotificationsQuery } from '@/store/api/notificationApiSlice';

/**
* The styles for the NotificationListPage component.
*/
const styles = StyleSheet.create({
container: {
flex: 1,
},
contentContainer: {
gap: 8,
paddingBottom: 8,
paddingHorizontal: 16,
},
});

/**
* Displays the page of notifications for the current user.
* @constructor
*/
const NotificationListPage: FC = () => {
// API calls
const { data: notifications, refetch: refetchNotifications } =
useGetNotificationsQuery();

// Fetch data from the API when the page is focused
useFocusEffect(
useCallback(() => {
refetchNotifications();
}, [refetchNotifications]),
);

if (notifications === undefined) {
return null;
}

return (
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}
>
<NotificationList notifications={notifications} />
</ScrollView>
);
};

export default NotificationListPage;
1 change: 1 addition & 0 deletions front/src/store/api/apiSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ export const apiSlice = createApi({
'JobOffer',
'MessageChannel',
'Message',
'Notification',
],
});
25 changes: 25 additions & 0 deletions front/src/store/api/notificationApiSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Notification } from '@/models/entities/notification';
import { apiSlice } from '@/store/api/apiSlice';

/**
* The extended API slice for notifications.
*/
export const extendedApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getNotifications: builder.query<Notification[], void>({
query: () => 'notifications/',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({
type: 'Notification' as const,
id,
})),
{ type: 'Notification', id: 'LIST' },
]
: [{ type: 'Notification', id: 'LIST' }],
}),
}),
});

export const { useGetNotificationsQuery } = extendedApiSlice;
6 changes: 6 additions & 0 deletions mock/config/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,12 @@
"title": "Candidature refusée",
"content": "Votre candidature à l'offre 'Serveur à l'entrecôte' à été refusée par l'employeur.",
"sentAt": "2023-10-23T09:34:19Z"
},
{
"id": "12ef4ac1-ab39-4d8d-8286-ccf16f4b3311",
"title": "Candidature acceptée",
"content": "Votre candidature à l'offre 'Femme de ménage' à été acceptée par l'employeur.",
"sentAt": "2023-09-23T09:34:19Z"
}
],
"companies": [
Expand Down