diff --git a/frontend/occupi-mobile4/app/_layout.tsx b/frontend/occupi-mobile4/app/_layout.tsx
index 5decefd6..0ebbed75 100644
--- a/frontend/occupi-mobile4/app/_layout.tsx
+++ b/frontend/occupi-mobile4/app/_layout.tsx
@@ -6,6 +6,7 @@ import { Stack } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import 'react-native-reanimated';
import { GluestackUIProvider } from "@gluestack-ui/themed";
+import { ThemeProvider } from '@/components/ThemeContext';
import { NavBarProvider } from '@/components/NavBarProvider';
import { config } from "@gluestack-ui/config"; // Optional if you want to use default theme
@@ -32,36 +33,38 @@ export default function RootLayout() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/components/NavBar.tsx b/frontend/occupi-mobile4/components/NavBar.tsx
index ddeef052..f67ac580 100644
--- a/frontend/occupi-mobile4/components/NavBar.tsx
+++ b/frontend/occupi-mobile4/components/NavBar.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { StyleSheet } from 'react-native';
import { Text, Button, Icon, CalendarDaysIcon, BellIcon } from '@gluestack-ui/themed';
import { Feather } from '@expo/vector-icons';
@@ -6,25 +6,38 @@ import { FontAwesome6, Ionicons } from '@expo/vector-icons';
import { router } from 'expo-router';
import { BlurView } from 'expo-blur';
import { useColorScheme } from 'react-native';
+import * as SecureStore from 'expo-secure-store';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
import { useNavBar } from './NavBarProvider';
+import { useTheme } from './ThemeContext';
const NavBar = () => {
- let colorScheme = useColorScheme();
- const styles = getStyles(colorScheme);
+ const colorscheme = useColorScheme();
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
+ const styles = getStyles(currentTheme);
+ const [accentColour, setAccentColour] = useState('greenyellow');
const { currentTab, setCurrentTab } = useNavBar();
const handleTabPress = (tabName, route) => {
setCurrentTab(tabName);
router.replace(route);
};
+
+ useEffect(() => {
+ const getSettings = async () => {
+ let accentcolour = await SecureStore.getItemAsync('accentColour');
+ setAccentColour(accentcolour);
+ };
+ getSettings();
+}, []);
// console.log(currentTab);
return (
@@ -40,13 +53,13 @@ const NavBar = () => {
Home
@@ -63,13 +76,13 @@ const NavBar = () => {
My bookings
@@ -87,13 +100,13 @@ const NavBar = () => {
as={CalendarDaysIcon}
w={hp('3%')}
h={hp('3%')}
- color={currentTab === 'Book' ? 'yellowgreen' : colorScheme === 'dark' ? 'white' : 'black'}
+ color={currentTab === 'Book' ? `${accentColour}` : currentTheme === 'dark' ? 'white' : 'black'}
/>
Book
@@ -111,14 +124,14 @@ const NavBar = () => {
as={BellIcon}
w={hp('3%')}
h={hp('3%')}
- color={currentTab === 'Notifications' ? 'yellowgreen' : colorScheme === 'dark' ? 'white' : 'black'}
+ color={currentTab === 'Notifications' ? `${accentColour}` : currentTheme === 'dark' ? 'white' : 'black'}
/>
Notifications
@@ -135,14 +148,14 @@ const NavBar = () => {
Profile
@@ -151,7 +164,7 @@ const NavBar = () => {
);
};
-const getStyles = (colorScheme) => StyleSheet.create({
+const getStyles = (currentTheme) => StyleSheet.create({
container: {
position: 'absolute',
bottom: 0,
@@ -161,10 +174,10 @@ const getStyles = (colorScheme) => StyleSheet.create({
paddingBottom: hp('3%'),
flexDirection: 'row',
justifyContent: 'space-around',
- backgroundColor: colorScheme === 'dark' ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.5)',
+ backgroundColor: currentTheme === 'dark' ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.5)',
paddingVertical: hp('1%'),
borderTopWidth: 1,
- borderTopColor: colorScheme === 'dark' ? '#444' : '#ccc',
+ borderTopColor: currentTheme === 'dark' ? '#444' : '#ccc',
borderLeftColor: '#ccc',
borderRightColor: '#ccc',
}
diff --git a/frontend/occupi-mobile4/components/ThemeContext.tsx b/frontend/occupi-mobile4/components/ThemeContext.tsx
new file mode 100644
index 00000000..acef058e
--- /dev/null
+++ b/frontend/occupi-mobile4/components/ThemeContext.tsx
@@ -0,0 +1,72 @@
+// ThemeContext.tsx
+
+import React, { createContext, useState, useEffect, ReactNode } from 'react';
+import * as SecureStore from 'expo-secure-store';
+import { View, ActivityIndicator, useColorScheme } from 'react-native';
+
+// Define the shape of the context state
+interface ThemeContextType {
+ theme: string;
+ setTheme: (theme: string) => void;
+}
+
+// Create the context
+const ThemeContext = createContext(undefined);
+
+// Create a provider component
+export const ThemeProvider = ({ children }: { children: ReactNode }) => {
+ const [theme, setTheme] = useState(null); // Start with null to indicate loading
+
+ useEffect(() => {
+ const fetchTheme = async () => {
+ try {
+ const storedTheme = await SecureStore.getItemAsync('Theme');
+ if (storedTheme) {
+ setTheme(storedTheme);
+ } else {
+ // If no theme is found, set a default theme
+ setTheme('light');
+ }
+ } catch (error) {
+ console.error('Failed to load theme from SecureStore:', error);
+ setTheme('light'); // Set a default theme in case of an error
+ }
+ };
+
+ fetchTheme();
+ }, []);
+
+ const updateTheme = async (newTheme: string) => {
+ console.log('updating theme');
+ try {
+ await SecureStore.setItemAsync('Theme', newTheme);
+ setTheme(newTheme);
+ } catch (error) {
+ console.error('Failed to save theme to SecureStore:', error);
+ }
+ };
+
+ if (theme === null) {
+ // Show a loading spinner while the theme is being fetched
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+// Custom hook to use the ThemeContext
+export const useTheme = (): ThemeContextType => {
+ const context = React.useContext(ThemeContext);
+ if (!context) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+};
diff --git a/frontend/occupi-mobile4/components/__tests__/NavBar-test.tsx b/frontend/occupi-mobile4/components/__tests__/NavBar-test.tsx
index 223c2694..e5af1344 100644
--- a/frontend/occupi-mobile4/components/__tests__/NavBar-test.tsx
+++ b/frontend/occupi-mobile4/components/__tests__/NavBar-test.tsx
@@ -1,9 +1,23 @@
import * as React from 'react';
import renderer from 'react-test-renderer';
import NavBar from '../NavBar';
+import { useNavBar } from '../NavBarProvider';
-it(`renders correctly`, () => {
- const tree = renderer.create(Snapshot test!).toJSON();
+// Mock the NavBarProvider module
+jest.mock('../NavBarProvider', () => ({
+ useNavBar: jest.fn(),
+}));
- expect(tree).toMatchSnapshot();
-});
+describe('NavBar', () => {
+ it(`renders correctly`, () => {
+ // Mock the useNavBar hook implementation
+ (useNavBar as jest.Mock).mockReturnValue({
+ currentTab: 'Home',
+ setCurrentTab: jest.fn(),
+ });
+
+ const tree = renderer.create().toJSON();
+
+ expect(tree).toMatchSnapshot();
+ });
+});
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/components/__tests__/__snapshots__/NavBar-test.tsx.snap b/frontend/occupi-mobile4/components/__tests__/__snapshots__/NavBar-test.tsx.snap
index 4687366d..5646babc 100644
--- a/frontend/occupi-mobile4/components/__tests__/__snapshots__/NavBar-test.tsx.snap
+++ b/frontend/occupi-mobile4/components/__tests__/__snapshots__/NavBar-test.tsx.snap
@@ -1,5 +1,922 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`NavBar renders correctly 1`] = `
+
+
+
+
+
+ Home
+
+
+
+
+
+ My bookings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Book
+
+
+
+
+
+
+
+
+
+
+
+
+ Notifications
+
+
+
+
+
+ Profile
+
+
+
+`;
+
exports[`renders correctly 1`] = `
{
+ await SecureStore.setItemAsync('UserData', value);
+ },
+
+ storeToken: async (value) => {
+ await SecureStore.setItemAsync('Token', value);
+ },
+
+ storeUserEmail: async (value) => {
+ await SecureStore.setItemAsync('Email', value);
+ },
+
+ getToken: async () => {
+ return await SecureStore.getItemAsync('Token');
+ }
+};
+
+export default LoginModel;
diff --git a/frontend/occupi-mobile4/models/data.ts b/frontend/occupi-mobile4/models/data.ts
new file mode 100644
index 00000000..80fc0594
--- /dev/null
+++ b/frontend/occupi-mobile4/models/data.ts
@@ -0,0 +1,52 @@
+//these models describe the room structure as well as booking information structure
+
+export interface Room {
+ description: string;
+ floorNo: string;
+ maxOccupancy: string;
+ minOccupancy:string;
+ roomId: string;
+ roomName: string;
+ roomNo: string;
+}
+
+export interface Booking {
+ checkedIn: boolean;
+ creator: string;
+ date: string;
+ emails: string[];
+ end: string;
+ floorNo: string;
+ occupiId: string;
+ roomId: string;
+ roomName: string;
+ start: string;
+}
+
+export interface User {
+ email: string;
+ name: string;
+ dob: string;
+ gender: "Male" | "Female" | "Other";
+ session_email: string;
+ employeeid: string;
+ number: string;
+ pronouns?: string;
+}
+
+export interface Notification {
+ message: string;
+ send_time: string;
+ title: string;
+ unreadEmails: string[];
+}
+
+export interface SecuritySettings {
+ mfa: "on" | "off";
+ forceLogout: "on" | "off";
+}
+
+export interface NotificationSettings {
+ invites: "on" | "off";
+ bookingReminder: "on" | "off";
+}
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/models/requests.ts b/frontend/occupi-mobile4/models/requests.ts
new file mode 100644
index 00000000..b9907cae
--- /dev/null
+++ b/frontend/occupi-mobile4/models/requests.ts
@@ -0,0 +1,127 @@
+//these models describe data to be sent to the api (POST body)
+
+/* ---Auth Requests--- */
+
+export interface LoginReq {
+ email: string;
+ password: string;
+}
+
+export interface RegisterReq {
+ email: string;
+ password: string;
+ employee_id?: string;
+ expoPushToken: string;
+}
+
+export interface VerifyOTPReq {
+ email: string;
+ otp: string;
+}
+
+export interface ResetPasswordReq {
+ email: string;
+ newPassword: string;
+ newPasswordConfirm: string;
+ otp: string;
+}
+
+/* ---API Requests--- */
+
+//Rooms & Bookings
+
+export interface BookRoomReq {
+ roomId: string;
+ roomName: string;
+ emails: string[];
+ creator: string;
+ floorNo: string; //string integer
+ date: string;
+ start: string;
+ end: string;
+}
+
+export interface ViewBookingsReq {
+ operator: string;
+ filter: {
+ email: string;
+ date?: string;
+ };
+ order_asc?: string;
+ order_desc?: string;
+ projection?: string[];
+ limit?: number;
+ page?: number;
+}
+
+export interface ViewRoomsReq {
+ operator: string;
+ filter?: {
+ floorNo: string;
+ };
+ order_asc?: string;
+ order_desc?: string;
+ projection?: string[];
+ limit?: number;
+ page?: number;
+}
+
+export interface CancelBookingReq {
+ bookingId: string;
+ roomId: string;
+ emails: string[];
+ creator: string;
+ floorNo: string; //string integer
+ date: string;
+ start: string;
+ end: string;
+ roomName: string;
+}
+
+export interface CheckInReq {
+ bookingId: string;
+ email: string;
+}
+
+
+//Users
+
+export interface UpdateDetailsReq {
+ email?: string;
+ name?: string;
+ dob?: string;
+ gender?: string;
+ session_email: string;
+ employeeid?: string;
+ number?: string;
+ pronouns?: string;
+}
+
+export interface NotificationsReq {
+ operator: string;
+ filter?: {
+ emails: string[];
+ };
+ order_asc?: string;
+ order_desc?: string;
+ projection?: string[];
+ limit?: number;
+ page?: number;
+}
+
+//Updating settings
+
+export interface SecuritySettingsReq {
+ email: string;
+ mfa?: "on" | "off";
+ forceLogout?: "on" | "off";
+ currentPassword?: string;
+ newPassword?: string;
+ newPasswordConfirm?: string;
+}
+
+export interface NotificationSettingsReq {
+ email: string;
+ invites: "on" | "off";
+ bookingReminder: "on" | "off";
+}
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/models/response.ts b/frontend/occupi-mobile4/models/response.ts
new file mode 100644
index 00000000..cf451700
--- /dev/null
+++ b/frontend/occupi-mobile4/models/response.ts
@@ -0,0 +1,28 @@
+//these models describe the structure of the responses from the api
+
+export interface Error {
+ code: string;
+ details: string;
+ message: string;
+}
+export interface Unsuccessful {
+ data: null;
+ status: 'error';
+ message: string;
+ error: Error;
+}
+
+export interface LoginSuccess {
+ data: {
+ token: string;
+ };
+ message: string;
+ status: number;
+}
+
+export interface Success {
+ status: number;
+ message: string;
+ data: any;
+}
+
diff --git a/frontend/occupi-mobile4/screens/Booking/BookRoom.tsx b/frontend/occupi-mobile4/screens/Booking/BookRoom.tsx
index f3eb6fe7..2df4c26f 100644
--- a/frontend/occupi-mobile4/screens/Booking/BookRoom.tsx
+++ b/frontend/occupi-mobile4/screens/Booking/BookRoom.tsx
@@ -13,6 +13,7 @@ import Navbar from '../../components/NavBar';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
import * as SecureStore from 'expo-secure-store';
import { Skeleton } from 'moti/skeleton';
+import { useTheme } from '@/components/ThemeContext';
const groupDataInPairs = (data) => {
if (!data) return [];
@@ -36,9 +37,11 @@ interface Room {
const BookRoom = () => {
const router = useRouter();
- const colorScheme = useColorScheme();
+ const { theme } = useTheme();
+ const colorscheme = useColorScheme();
const toast = useToast();
- const [isDarkMode, setIsDarkMode] = useState(colorScheme === 'dark');
+ const currentTheme = theme === "system" ? colorscheme : theme;
+ const isDarkMode = currentTheme === "dark";
const [layout, setLayout] = useState("row");
const [loading, setLoading] = useState(true);
const [roomData, setRoomData] = useState([]);
@@ -103,11 +106,8 @@ const BookRoom = () => {
}
};
fetchAllRooms();
- }, [toast, apiUrl, viewroomsendpoint]);
+ }, [toast]);
- useEffect(() => {
- setIsDarkMode(colorScheme === 'dark');
- }, [colorScheme]);
const backgroundColor = isDarkMode ? 'black' : 'white';
const textColor = isDarkMode ? 'white' : 'black';
diff --git a/frontend/occupi-mobile4/screens/Booking/ViewBookingDetails.tsx b/frontend/occupi-mobile4/screens/Booking/ViewBookingDetails.tsx
index da8bb457..dabed24a 100644
--- a/frontend/occupi-mobile4/screens/Booking/ViewBookingDetails.tsx
+++ b/frontend/occupi-mobile4/screens/Booking/ViewBookingDetails.tsx
@@ -24,177 +24,76 @@ import {
} from 'react-native-responsive-screen';
import PagerView from 'react-native-pager-view';
import { useRouter } from 'expo-router';
-
-interface Room {
- _id: string;
- roomName: string;
- roomId: string;
- roomNo: number;
- floorNo: number;
- minOccupancy: number;
- maxOccupancy: number;
- description: string;
- emails: string[];
- date: string;
- start: string;
- end: string;
- creator: string;
-}
-
-const ViewBookingDetails = (bookingId:string, roomName:string) => {
- const colorScheme = useColorScheme();
- const isDarkMode = colorScheme === 'dark';
- const [room, setRoom] = useState({});
+import { Booking } from '@/models/data';
+import { userCancelBooking, userCheckin } from '@/utils/bookings';
+import { useTheme } from '@/components/ThemeContext';
+
+const ViewBookingDetails = () => {
+ const colorscheme = useColorScheme();
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
+ const isDarkMode = currentTheme === 'dark';
+ const [room, setRoom] = useState();
const router = useRouter();
const [checkedIn, setCheckedIn] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const toast = useToast();
- const apiUrl = process.env.EXPO_PUBLIC_DEVELOP_API_URL;
- const checkinendpoint = process.env.EXPO_PUBLIC_CHECK_IN;
- const cancelbookingendpoint = process.env.EXPO_PUBLIC_CANCEL_BOOKING;
// console.log("HERE:" + room);
useEffect(() => {
const getCurrentRoom = async () => {
- let result : string = await SecureStore.getItemAsync('CurrentRoom');
- // console.log("CurrentRoom:",result);
- // setUserDetails(JSON.parse(result).data);
- let jsonresult = JSON.parse(result);
- console.log(jsonresult);
- setRoom(jsonresult);
- setCheckedIn(jsonresult.checkedIn);
+ let result: string = await SecureStore.getItemAsync('CurrentRoom');
+ // console.log("CurrentRoom:",result);
+ // setUserDetails(JSON.parse(result).data);
+ let jsonresult = JSON.parse(result);
+ // console.log(jsonresult);
+ setRoom(jsonresult);
+ setCheckedIn(jsonresult.checkedIn);
};
getCurrentRoom();
- }, []);
+ }, []);
+
+ // console.log("Room",room?._id);
- // console.log("Room",room._id);
-
const checkin = async () => {
- const body = {
- "bookingId": room._id,
- "creator": room.creator
- };
setIsLoading(true);
- console.log(body);
- // console.log(apiUrl+""+checkinendpoint);
- let authToken = await SecureStore.getItemAsync('Token');
- try {
- const response = await fetch(`${apiUrl}${checkinendpoint}`, {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- body: JSON.stringify(body),
- credentials: "include"
- });
- const data = await response.json();
- // console.log(data);
- // const cookies = response.headers.get('Accept');
- // console.log(cookies);
- if (response.ok) {
- setCheckedIn(true);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- setIsLoading(false);
- } else {
- setIsLoading(false);
- console.log(data);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
+ const response = await userCheckin();
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
}
- } catch (error) {
- console.error('Error:', error);
- }
+ });
+ setIsLoading(false);
};
const cancelBooking = async () => {
- const body = {
- "bookingId": room._id,
- "creator": room.creator,
- "roomId": room.roomId,
- "emails": room.emails,
- "roomName": room.roomName,
- "floorNo": room.floorNo,
- "date": room.date,
- "start": room.start,
- "end": room.end
-
- };
setIsLoading(true);
- console.log(body);
- let authToken = await SecureStore.getItemAsync('Token');
- try {
- const response = await fetch(`${apiUrl}${cancelbookingendpoint}`, {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- body: JSON.stringify(body),
- credentials: "include"
- });
- const data = await response.json();
- console.log(data);
- // const cookies = response.headers.get('Accept');
- // console.log(cookies);
- if (response.ok) {
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- setIsLoading(false);
- router.replace("/home");
- } else {
- setIsLoading(false);
- console.log(data);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
+ const response = await userCancelBooking();
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
}
- } catch (error) {
- console.error('Error:', error);
- }
+ });
+ setIsLoading(false);
};
return (
-
+
- router.back()} />
- {room.roomName}
+ router.back()} />
+ {room?.roomName}
@@ -211,25 +110,25 @@ const ViewBookingDetails = (bookingId:string, roomName:string) => {
- {room.roomName}
+ {room?.roomName}
Fast
OLED
3 - 5
- Floor: {room.floorNo === 0 ? 'G' : room.floorNo}
+ Floor: {room?.floorNo === 0 ? 'G' : room?.floorNo}
- Attendees: {room.emails?.length}
+ Attendees: {room?.emails?.length}
- {room.emails?.map((email, idx) => (
+ {room?.emails?.map((email, idx) => (
{idx + 1}. {email}
))}
- Description
- The {room.roomName} is a state-of-the-art conference space designed for modern digital connectivity, seating 3-6 comfortably. Equipped with multiple HDMI ports, a high-definition projector or large LED screen, surround sound, and wireless display options, it ensures seamless presentations and video conferencing. The room features an intuitive control panel, high-speed Wi-Fi, and ample power outlets. Additional amenities include whiteboards, flip charts, adjustable lighting, and climate control, all within a professional and comfortable interior designed for productivity.
+ Description
+ The {room?.roomName} is a state-of-the-art conference space designed for modern digital connectivity, seating 3-6 comfortably. Equipped with multiple HDMI ports, a high-definition projector or large LED screen, surround sound, and wireless display options, it ensures seamless presentations and video conferencing. The room features an intuitive control panel, high-speed Wi-Fi, and ample power outlets. Additional amenities include whiteboards, flip charts, adjustable lighting, and climate control, all within a professional and comfortable interior designed for productivity.
diff --git a/frontend/occupi-mobile4/screens/Booking/ViewBookings.tsx b/frontend/occupi-mobile4/screens/Booking/ViewBookings.tsx
index b140e8ef..caa3e01c 100644
--- a/frontend/occupi-mobile4/screens/Booking/ViewBookings.tsx
+++ b/frontend/occupi-mobile4/screens/Booking/ViewBookings.tsx
@@ -14,30 +14,20 @@ import Navbar from '../../components/NavBar';
import * as SecureStore from 'expo-secure-store';
import { useRouter } from 'expo-router';
import { Skeleton } from 'moti/skeleton';
+import { Booking } from '@/models/data';
+import { fetchUserBookings } from '@/utils/bookings';
+import { useTheme } from '@/components/ThemeContext';
+
+
const groupDataInPairs = (data) => {
const pairs = [];
- for (let i = 0; i < data.length; i += 2) {
- pairs.push(data.slice(i, i + 2));
+ for (let i = 0; i < 10; i += 2) {
+ pairs.push(data?.slice(i, i + 2));
}
return pairs;
};
-interface Room {
- _id: string;
- roomName: string;
- roomId: string;
- roomNo: number;
- floorNo: number;
- minOccupancy: number;
- maxOccupancy: number;
- description: string;
- emails: string[];
- date: string;
- start: string;
- end: string;
-}
-
function extractTimeFromDate(dateString: string): string {
const date = new Date(dateString);
date.setHours(date.getHours() - 2);
@@ -50,18 +40,33 @@ function extractDateFromDate(dateString: string): string {
}
const ViewBookings = () => {
- const colorScheme = useColorScheme();
- const [isDarkMode, setIsDarkMode] = useState(colorScheme === 'dark');
+ const colorscheme = useColorScheme();
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
+ const isDarkMode = currentTheme === "dark";
const [layout, setLayout] = useState("row");
- const toast = useToast();
- const [roomData, setRoomData] = useState([]);
+ const [roomData, setRoomData] = useState();
// const [selectedSort, setSelectedSort] = useState("newest");
- const [email, setEmail] = useState('');
const router = useRouter();
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
- const apiUrl = process.env.EXPO_PUBLIC_DEVELOP_API_URL;
- const viewbookingsendpoint = process.env.EXPO_PUBLIC_VIEW_BOOKINGS;
+ useEffect(() => {
+ const getRoomData = async () => {
+ try {
+ const roomData = await fetchUserBookings();
+ if (roomData) {
+ // console.log(roomData);
+ setRoomData(roomData);
+ } else {
+ setRoomData([]);
+ }
+ } catch (error) {
+ console.error('Error fetching bookings:', error);
+ }
+ setLoading(false);
+ };
+ getRoomData();
+ }, []);
const [accentColour, setAccentColour] = useState('greenyellow');
useEffect(() => {
@@ -74,138 +79,36 @@ const ViewBookings = () => {
const onRefresh = React.useCallback(() => {
- const fetchAllRooms = async () => {
- console.log("heree");
- let authToken = await SecureStore.getItemAsync('Token');
- console.log("Token:" + authToken);
+ const getRoomData = async () => {
try {
- const response = await fetch(`${apiUrl}${viewbookingsendpoint}?email=${email}`, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- });
- const data = await response.json();
- if (response.ok) {
- setRoomData(data.data || []); // Ensure data is an array
- setLoading(false);
+ const roomData = await fetchUserBookings();
+ if (roomData) {
+ // console.log(roomData);
+ setRoomData(roomData);
} else {
- console.log(data);
- setLoading(false);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.error.message}
-
- );
- },
- });
+ setRoomData([]); // Default value if no username is found
}
} catch (error) {
- console.error('Error:', error);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- Network Error: {error.message}
-
- );
- },
- });
+ console.error('Error fetching bookings:', error);
}
+ setLoading(false);
};
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
- fetchAllRooms();
+ getRoomData();
}, 2000);
- }, [toast, apiUrl, viewbookingsendpoint, email]);
+ }, []);
const toggleLayout = () => {
setLayout((prevLayout) => (prevLayout === "row" ? "grid" : "row"));
};
- useEffect(() => {
- setIsDarkMode(colorScheme === 'dark');
- }, [colorScheme]);
+
const backgroundColor = isDarkMode ? 'black' : 'white';
const textColor = isDarkMode ? 'white' : 'black';
const cardBackgroundColor = isDarkMode ? '#2C2C2E' : '#F3F3F3';
- // const data = [
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // { title: 'HDMI Room', description: 'Boasting sunset views, long desks, and comfy chairs', Date: '17/06/2024', Time: '07:30-09:30', available: true },
- // ];
- useEffect(() => {
- const fetchAllRooms = async () => {
- let authToken = await SecureStore.getItemAsync('Token');
- let result = await SecureStore.getItemAsync('UserData');
- // console.log(result);
- // if (result !== undefined) {
- let jsonresult = JSON.parse(result);
- setEmail(jsonresult?.data?.email);
- // }
- // console.log("Token:"+authToken);
- // console.log("heree");
- try {
- // console.log(`${apiUrl}${viewbookingsendpoint}?email=${jsonresult?.data?.email}`);
- const response = await fetch(`${apiUrl}${viewbookingsendpoint}?email=${jsonresult?.data?.email}`, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- });
- const data = await response.json();
- // console.log(data);
- if (response.ok) {
- setRoomData(data.data || []); // Ensure data is an array
- setLoading(false);
- } else {
- console.log(data);
- setLoading(false);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.error.message}
-
- );
- },
- });
- }
- } catch (error) {
- console.error('Error:', error);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- Network Error: {error.message}
-
- );
- },
- });
- }
- };
- fetchAllRooms();
- }, [toast, apiUrl, email, viewbookingsendpoint]);
+
const roomPairs = groupDataInPairs(roomData);
@@ -313,8 +216,9 @@ const ViewBookings = () => {
marginBottom: 20,
}}
>
- {pair.map((room) => (
+ {pair.map((room, idx) => (
handleRoomClick(JSON.stringify(room))}
style={{
flex: 1,
@@ -341,7 +245,7 @@ const ViewBookings = () => {
{room.roomName}
- Attendees: {room.emails.length}
+ Attendees: {room.emails?.length}
Your booking time:
@@ -366,8 +270,9 @@ const ViewBookings = () => {
}
>
- {roomData.map((room) => (
+ {roomData?.map((room, idx) => (
handleRoomClick(JSON.stringify(room))}
style={{
flex: 1,
@@ -398,7 +303,7 @@ const ViewBookings = () => {
>
{room.roomName}
- Attendees: {room.emails.length}
+ Attendees: {room.emails?.length}
Your booking time:
diff --git a/frontend/occupi-mobile4/screens/Dashboard/Dashboard.tsx b/frontend/occupi-mobile4/screens/Dashboard/Dashboard.tsx
index ff7983d5..7732ed84 100644
--- a/frontend/occupi-mobile4/screens/Dashboard/Dashboard.tsx
+++ b/frontend/occupi-mobile4/screens/Dashboard/Dashboard.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
-import { StatusBar, useColorScheme, Dimensions } from 'react-native';
+import { StatusBar, useColorScheme, Dimensions, TouchableOpacity } from 'react-native';
import Navbar from '../../components/NavBar';
import {
Text,
@@ -11,14 +11,19 @@ import {
ToastTitle,
Button,
ButtonText,
+ ScrollView,
} from '@gluestack-ui/themed';
import {
LineChart
} from "react-native-chart-kit";
-import { FontAwesome6 } from '@expo/vector-icons';
import * as SecureStore from 'expo-secure-store';
+import { FontAwesome6 } from '@expo/vector-icons';
// import { router } from 'expo-router';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
+import { fetchUsername } from '@/utils/user';
+import { Booking } from '@/models/data';
+import { fetchUserBookings } from '@/utils/bookings';
+import { useTheme } from '@/components/ThemeContext';
// import { number } from 'zod';
const getRandomNumber = () => {
@@ -27,12 +32,16 @@ const getRandomNumber = () => {
const Dashboard = () => {
const colorScheme = useColorScheme();
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorScheme : theme;
const [numbers, setNumbers] = useState(Array.from({ length: 15 }, getRandomNumber));
- const [isDarkMode, setIsDarkMode] = useState(colorScheme === 'dark');
+ const [isDarkMode, setIsDarkMode] = useState(currentTheme === 'dark');
const [checkedIn, setCheckedIn] = useState(false);
- const [name, setName] = useState("User");
- const toast = useToast()
- const [accentColour, setAccentColour] = useState('greenyellow');
+ const [roomData, setRoomData] = useState({});
+ const [username, setUsername] = useState('');
+ const toast = useToast();
+ // console.log(currentTheme);
+ // console.log(isDarkMode);
useEffect(() => {
const getAccentColour = async () => {
@@ -41,127 +50,53 @@ const Dashboard = () => {
};
getAccentColour();
}, []);
- useEffect(() => {
- const intervalId = setInterval(() => {
- setNumbers(prevNumbers => {
- const newNumbers = [getRandomNumber(), ...prevNumbers.slice(0, 14)];
- return newNumbers;
- });
- }, 3000);
- setIsDarkMode(colorScheme === 'dark');
- return () => clearInterval(intervalId);
- }, [colorScheme]);
useEffect(() => {
- const getUserDetails = async () => {
- let result = await SecureStore.getItemAsync('UserData');
- console.log(result);
- if (result !== undefined) {
- let jsonresult = JSON.parse(result);
- setName(String(jsonresult?.data?.details?.name));
- }
- };
- const getUserSettings = async () => {
+ const getUsername = async () => {
try {
- let authToken = await SecureStore.getItemAsync('Token');
- let email = await SecureStore.getItemAsync('Email');
- // console.log(authToken);
- const response = await fetch(`https://dev.occupi.tech/api/get-notification-settings?email=${email}`, {
- method: 'GET',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- credentials: "include"
- });
- const data = await response.json();
- console.log(data);
- if (response.ok) {
- const settings = {
- invites: data.data.invites,
- bookingReminder: data.data.bookingReminder
- };
- // console.log(settings);
- await SecureStore.setItemAsync('Notifications', JSON.stringify(settings));
+ const name = await fetchUsername();
+ // console.log(name);
+ if (name) {
+ setUsername(name);
} else {
- console.log(data);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.error.message}
-
- );
- },
- });
+ setUsername('Guest'); // Default value if no username is found
}
} catch (error) {
- console.error('Error:', error);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- Network Error
-
- );
- },
- });
+ console.error('Error fetching username:', error);
+ setUsername('Guest'); // Default in case of an error
}
+ };
+
+ const getRoomData = async () => {
try {
- let authToken = await SecureStore.getItemAsync('Token');
- let email = await SecureStore.getItemAsync('Email');
- // console.log(authToken);
- const response = await fetch(`https://dev.occupi.tech/api/get-security-settings?email=${email}`, {
- method: 'GET',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- credentials: "include"
- });
- const data = await response.json();
- console.log(data);
- if (response.ok) {
- const settings = {
- mfa: data.data.mfa,
- forcelogout: data.data.forceLogout
- };
- console.log(settings);
- await SecureStore.setItemAsync('Security', JSON.stringify(settings));
- } else {
- console.log(data);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.error.message}
-
- );
- },
- });
+ const roomData = await fetchUserBookings();
+ if (roomData) {
+ // console.log(roomData);
+ setRoomData(roomData[0]);
+ // console.log(roomData[0]);
}
} catch (error) {
- console.error('Error:', error);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- Network Error
-
- );
- },
- });
+ console.error('Error fetching bookings:', error);
}
- }
- getUserSettings();
- getUserDetails();
- }, [toast]);
+ };
+ getRoomData();
+ getUsername();
+ }, []);
+
+
+ const [accentColour, setAccentColour] = useState('greenyellow');
+
+
+ useEffect(() => {
+ const intervalId = setInterval(() => {
+ setNumbers(prevNumbers => {
+ const newNumbers = [getRandomNumber(), ...prevNumbers.slice(0, 14)];
+ return newNumbers;
+ });
+ }, 3000);
+ setIsDarkMode(currentTheme === 'dark');
+ return () => clearInterval(intervalId);
+ }, [currentTheme]);
const checkIn = () => {
if (checkedIn === false) {
@@ -187,125 +122,174 @@ const Dashboard = () => {
}
};
- // async function saveUserEmail(value) {
- // await SecureStore.setItemAsync('email', value);
- // }
-
-
- // saveUserEmail('kamogelomoeketse@gmail.com');
+ function extractTimeFromDate(dateString: string): string {
+ const date = new Date(dateString);
+ date.setHours(date.getHours() - 2);
+ return date.toTimeString().substring(0, 5);
+ }
+ function extractDateFromDate(dateString: string): string {
+ const date = new Date(dateString);
+ return date.toDateString();
+ }
const backgroundColor = isDarkMode ? '#1C1C1E' : 'white';
const textColor = isDarkMode ? 'white' : 'black';
const cardBackgroundColor = isDarkMode ? '#2C2C2E' : '#F3F3F3';
return (
-
-
-
-
-
- Hi {name} 👋
-
-
- Welcome to Occupi
-
+ <>
+
+
+
+
+
+ Hi {username} 👋
+
+
+ Welcome back to Occupi
+
+
+
-
-
-
-
-
- {numbers[0]}
-
- {numbers[0] / 10 + 5}%
+
+ Next booking:
+
+
+
+
+ {roomData.roomName}
+
+
+
+ {extractDateFromDate(roomData.date)}
+ {extractTimeFromDate(roomData.start)}-{extractTimeFromDate(roomData.end)}
+
+
+
-
-
-
-
- {checkedIn ? (
-
- ) : (
-
- )}
-
- {/*
+
+
+ Capacity {numbers[0] / 10 + 5}%
+ {numbers[0]}
+ Compared to
+ Yesterday
+ {/*
+ {numbers[0] / 10 + 5}%
+ */}
+
+
+
+
+ {checkedIn ? (
+
+ ) : (
+
+ )}
+
+ {/* */}
-
- Occupancy levels
-
+ Occupancy levels
+ `rgba(0, 0, 0, ${opacity})`,
+ labelColor: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
+ style: {
+ borderRadius: 20
+ },
+ propsForDots: {
+ r: "0",
+ strokeWidth: "2",
+ stroke: "green"
}
- ]
- }}
- width={Dimensions.get("window").width - 30} // from react-native
- height={220}
- // yAxisLabel=""
- // yAxisSuffix="k"
- yAxisInterval={1} // optional, defaults to 1
- chartConfig={{
- backgroundColor: "white",
- backgroundGradientFrom: "yellowgreen",
- backgroundGradientTo: "cyan",
- decimalPlaces: 0, // optional, defaults to 2dp
- color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
- labelColor: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
- style: {
- borderRadius: 20
- },
- propsForDots: {
- r: "0",
- strokeWidth: "2",
- stroke: "green"
- }
- }}
- bezier
- style={{
- marginVertical: 8,
- borderRadius: 16,
- }}
- />
-
+ }}
+ bezier
+ style={{
+ marginVertical: 8,
+ borderRadius: 16,
+ }}
+ />
+
+
-
+ >
);
};
diff --git a/frontend/occupi-mobile4/screens/Dashboard/__tests__/Dashboard-test.tsx b/frontend/occupi-mobile4/screens/Dashboard/__tests__/Dashboard-test.tsx
index ae402883..fe7814d5 100644
--- a/frontend/occupi-mobile4/screens/Dashboard/__tests__/Dashboard-test.tsx
+++ b/frontend/occupi-mobile4/screens/Dashboard/__tests__/Dashboard-test.tsx
@@ -2,9 +2,27 @@ import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { StyledProvider, Theme } from '@gluestack-ui/themed';
import Dashboard from '../Dashboard';
+import { useNavBar } from '../../../components/NavBarProvider'; // Adjust the path as needed
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); // To prevent warnings about Animated module
+jest.mock('react-native/Libraries/Settings/Settings', () => ({
+ get: jest.fn(),
+ set: jest.fn(),
+}));
+
+jest.mock('react-native', () => ({
+ ...jest.requireActual('react-native'),
+ useColorScheme: () => 'light',
+}));
+
+jest.mock('../../../components/NavBarProvider', () => ({
+ useNavBar: () => ({
+ currentTab: 'Dashboard',
+ setCurrentTab: jest.fn(),
+ }),
+}));
+
jest.mock('@gluestack-ui/themed', () => ({
...jest.requireActual('@gluestack-ui/themed'),
useToast: () => ({
@@ -47,8 +65,8 @@ describe('Dashboard component', () => {
// Mock SecureStore getItemAsync to resolve with the mocked data
require('expo-secure-store').getItemAsync.mockResolvedValueOnce(JSON.stringify(mockedData));
- it('renders text correctly', () => {
- const { getByText } = renderWithProvider();
+ it('renders text correctly', async () => {
+ const { getByText } = await renderWithProvider();
expect(getByText('Welcome to Occupi')).toBeTruthy();
});
diff --git a/frontend/occupi-mobile4/screens/Login/OtpVerification.tsx b/frontend/occupi-mobile4/screens/Login/OtpVerification.tsx
index c02b09ca..5dcf5414 100644
--- a/frontend/occupi-mobile4/screens/Login/OtpVerification.tsx
+++ b/frontend/occupi-mobile4/screens/Login/OtpVerification.tsx
@@ -2,20 +2,21 @@ import React, { useRef, useState, useEffect } from 'react';
import { View, StyleSheet, TextInput } from 'react-native';
import { VStack, Box, HStack, Image, Heading, Toast, useToast, ToastTitle, Text, } from '@gluestack-ui/themed';
// import { useForm } from 'react-hook-form';
-// import { z } from 'zod';
+import { z } from 'zod';
// import { zodResolver } from '@hookform/resolvers/zod';
import * as SecureStore from 'expo-secure-store';
-import { useRouter, useLocalSearchParams } from 'expo-router';
+import { useRouter, useLocalSearchParams } from 'expo-router';
import Logo from './assets/images/Occupi/file.png';
import StyledExpoRouterLink from '@/components/StyledExpoRouterLink';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
import { LinearGradient } from 'expo-linear-gradient';
+import { VerifyUserOtpLogin, verifyUserOtpRegister } from '@/utils/auth';
-// const OTPSchema = z.object({
-// OTP: z.string().min(6, 'OTP must be at least 6 characters in length'),
-// });
+const OTPSchema = z.object({
+ OTP: z.string().min(6, 'OTP must be at least 6 characters in length'),
+});
-// type OTPSchemaType = z.infer;
+type OTPSchemaType = z.infer;
const OTPVerification = () => {
const [email, setEmail] = useState("");
@@ -24,12 +25,9 @@ const OTPVerification = () => {
const otpSent = useState(false);
const timerRef = useRef(null);
const [loading, setLoading] = useState(false);
- const router = useRouter();
const toast = useToast();
const [otp, setOtp] = useState(['', '', '', '', '', '']);
- const apiUrl = process.env.EXPO_PUBLIC_DEVELOP_API_URL;
- const getUserDetailsUrl = process.env.EXPO_PUBLIC_GET_USER_DETAILS;
- // console.log(email);
+ const [state, setState] = useState();
useEffect(() => {
if (remainingTime > 0 && !otpSent) {
@@ -49,20 +47,13 @@ const OTPVerification = () => {
useEffect(() => {
const getUserEmail = async () => {
let email = await SecureStore.getItemAsync('Email');
- // console.log("email",email);
+ const state = await SecureStore.getItemAsync('AppState');
+ setState(state);
setEmail(email);
};
getUserEmail();
}, []);
- async function storeToken(value) {
- await SecureStore.setItemAsync('Token', value);
- }
-
- async function storeUserData(value) {
- await SecureStore.setItemAsync('UserData', value);
- }
-
// console.log("here",email);
const onSubmit = async () => {
@@ -74,99 +65,33 @@ const OTPVerification = () => {
// return;
// }
// setValidationError(null);
- console.log(pin);
setLoading(true);
- try {
- const response = await fetch('https://dev.occupi.tech/auth/verify-otp-mobile-login', {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- email: email,
- otp: pin
- }),
- credentials: "include"
+ if (state === 'verify_otp_register') {
+ const response = await verifyUserOtpRegister(email, pin);
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
+ }
+ });
+ }
+ else {
+ const response = await VerifyUserOtpLogin(email, pin);
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
+ }
});
- const data = await response.json();
- if (response.ok) {
- setLoading(false);
- // console.log(data.data.token);
- storeToken(data.data.token);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- try {
- let authToken = await SecureStore.getItemAsync('Token');
- console.log(authToken);
- const response = await fetch(`${apiUrl}${getUserDetailsUrl}?email=${email}`, {
- method: 'GET',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- credentials: "include"
- });
- const data = await response.json();
- // console.log("here");
- if (response.ok) {
- storeUserData(JSON.stringify(data));
- // console.log(`Data of ${email}: `, data);
- } else {
- console.log(data);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.error.message}
-
- );
- },
- });
- }
- } catch (error) {
- console.error('Error:', error);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- Network Error
-
- );
- },
- });
- }
- router.replace('/home');
- } else {
- setLoading(false);
- // console.log(data);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- }
- } catch (error) {
- console.error('Error:', error);
- // setResponse('An error occurred');
}
- // }, 3000);
setLoading(false);
};
@@ -185,41 +110,41 @@ const OTPVerification = () => {
);
return (
-
-
-
-
+
+
+
+
Entered OTP: {otp.join('')}
- {remainingTime} seconds remaining
- {loading ? (
-
- ) : (
- {remainingTime} seconds remaining
+ {loading ? (
+
- )}
-
-
-
-
-
+ text="Verifying OTP..."
+ />
+ ) : (
+
+ )}
+
+
+
+
+
);
};
@@ -335,7 +260,7 @@ const OTPInput = ({ otp, setOtp }) => {
keyboardType="numeric"
maxLength={1}
ref={(ref) => inputRefs.current[index] = ref}
- // autoFocus={index === inputRefs.current[index]} // Auto focus the first input on mount
+ // autoFocus={index === inputRefs.current[index]} // Auto focus the first input on mount
/>
))}
diff --git a/frontend/occupi-mobile4/screens/Login/SetDetails.tsx b/frontend/occupi-mobile4/screens/Login/SetDetails.tsx
index 53101689..007a9460 100644
--- a/frontend/occupi-mobile4/screens/Login/SetDetails.tsx
+++ b/frontend/occupi-mobile4/screens/Login/SetDetails.tsx
@@ -27,6 +27,8 @@ import { useColorScheme } from 'react-native';
import { heightPercentageToDP as hp } from 'react-native-responsive-screen';
import GradientButton from '@/components/GradientButton';
import LoadingGradientButton from '@/components/LoadingGradientButton';
+import { updateDetails } from '@/utils/user';
+import { extractDateFromTimestamp } from '@/utils/utils';
const COLORS = {
white: '#FFFFFF',
@@ -47,20 +49,14 @@ const SIZES = {
};
const SetDetails = () => {
- const [selectedGenderIndex, setSelectedGenderIndex] = useState(1);
+ const [selectedGenderIndex, setSelectedGenderIndex] = useState('');
const [name, setName] = useState('');
- const [email, setEmail] = useState('');
- const [employeeId, setEmployeeId] = useState('');
const [phoneNumber, setPhoneNumber] = useState('');
const [pronouns, setPronouns] = useState('');
const [date, setDate] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
let colorScheme = 'light';
- const apiUrl = process.env.EXPO_PUBLIC_DEVELOP_API_URL;
- const getUserDetailsUrl= process.env.EXPO_PUBLIC_GET_USER_DETAILS;
- const updateDetailsUrl = process.env.EXPO_PUBLIC_UPDATE_USER_DETAILS;
- console.log(apiUrl, getUserDetailsUrl, updateDetailsUrl);
const showDatePicker = () => {
setDatePickerVisibility(true);
@@ -70,78 +66,18 @@ const SetDetails = () => {
setDatePickerVisibility(false);
};
- const handleConfirm = (selectedDate) => {
- setDate(selectedDate);
+ const handleConfirm = (selectedDate: string) => {
+ console.log('selected',extractDateFromTimestamp(selectedDate));
+ setDate(extractDateFromTimestamp(selectedDate));
hideDatePicker();
};
const onSave = async () => {
- const body = {
- "email": email,
- "details": {
- "contactNo": phoneNumber,
- "gender": "Male",
- "name": name,
- "pronouns": pronouns
- }
- };
- // console.log(JSON.stringify(body));
setIsLoading(true);
- try {
- let authToken = await SecureStore.getItemAsync('Token');
- const response = await fetch(`${apiUrl}${updateDetailsUrl}`, {
- method: 'PUT',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- body: JSON.stringify(body),
- credentials: "include"
- });
- const data = await response.json();
- console.log(data);
- if (response.ok) {
- console.log(response);
- setIsLoading(false);
- alert('Details updated successfully');
- } else {
- console.log(data);
- setIsLoading(false);
- }
- } catch (error) {
- setIsLoading(false);
- console.error('Error:', error);
- // setResponse('An error occurred');
- }
-
- try {
- let authToken = await SecureStore.getItemAsync('Token');
- const response = await fetch(`${apiUrl}${getUserDetailsUrl}?email=${email}`, {
- method: 'GET',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- credentials: "include"
- });
- const data = await response.json();
- if (response.ok) {
- saveUserData(JSON.stringify(data));
- console.log(data);
- } else {
- console.log(data);
- }
- } catch (error) {
- console.error('Error:', error);
- }
+ const response = await updateDetails(name,date,selectedGenderIndex,phoneNumber,pronouns)
+ console.log(response);
+ setIsLoading(false);
};
- console.log(selectedGenderIndex);
-
- async function saveUserData(value) {
- await SecureStore.setItemAsync('UserData', value);
- }
return (
{
>
- router.replace('/settings')}
- />
- Account Details
+ My account
{
style={styles.icon}
/>
-
Full name
{
/>
Gender
- setSelectedGenderIndex(index)}>
+ setSelectedGenderIndex(index)}>
{
borderColor="#f2f2f2"
h={hp('5%')}
px="$4"
+
>
Male
@@ -237,7 +166,6 @@ const SetDetails = () => {
-
Cell No
{
) : (
)
}
-
diff --git a/frontend/occupi-mobile4/screens/Login/SignIn.tsx b/frontend/occupi-mobile4/screens/Login/SignIn.tsx
index 231f4803..d938634b 100644
--- a/frontend/occupi-mobile4/screens/Login/SignIn.tsx
+++ b/frontend/occupi-mobile4/screens/Login/SignIn.tsx
@@ -37,10 +37,10 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { AlertTriangle, EyeIcon, EyeOffIcon } from 'lucide-react-native';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
-import * as SecureStore from 'expo-secure-store';
import Logo from '../../screens/Login/assets/images/Occupi/file.png';
import StyledExpoRouterLink from '../../components/StyledExpoRouterLink';
import GradientButton from '@/components/GradientButton';
+import { UserLogin } from '@/utils/auth';
const signInSchema = z.object({
email: z.string().min(1, 'Email is required').email(),
@@ -67,9 +67,6 @@ const SignInForm = () => {
} = useForm({
resolver: zodResolver(signInSchema),
});
- const apiUrl = process.env.EXPO_PUBLIC_DEVELOP_API_URL;
- const loginUrl = process.env.EXPO_PUBLIC_LOGIN;
- const getUserDetailsUrl = process.env.EXPO_PUBLIC_GET_USER_DETAILS;
const isEmailFocused = useState(false);
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
@@ -87,19 +84,6 @@ const SignInForm = () => {
// console.log('Biometric hardware available:', isBiometricAvailable);
};
- async function storeUserData(value) {
- await SecureStore.setItemAsync('UserData', value);
- }
-
- async function storeToken(value) {
- await SecureStore.setItemAsync('Token', value);
- }
-
- async function storeUserEmail(value) {
- await SecureStore.setItemAsync('Email', value);
- }
-
-
const handleBiometricSignIn = async () => {
const biometricType = await LocalAuthentication.supportedAuthenticationTypesAsync();
console.log('Supported biometric types:', biometricType);
@@ -114,7 +98,7 @@ const SignInForm = () => {
});
console.log('Biometric authentication result:', result);
if (result.success) {
- router.replace('/home');
+ // router.replace('/home');
} else {
console.log('Biometric authentication failed');
toast.show({
@@ -160,104 +144,17 @@ const SignInForm = () => {
const onSubmit = async (_data: SignInSchemaType) => {
setLoading(true);
- storeUserEmail(_data.email);
- const body = {
- email: _data.email,
- password: _data.password
- };
- console.log(body);
- try {
- const response = await fetch(`${apiUrl}${loginUrl}`, {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(body),
- credentials: "include"
- });
- const data = await response.json();
- if (response.ok) {
- console.log("Data here", data);
- setLoading(false);
- if (data.data) {
- storeToken(data.data.token);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- try {
- let authToken = await SecureStore.getItemAsync('Token');
- // console.log(authToken);
- const response = await fetch(`${apiUrl}${getUserDetailsUrl}?email=${_data.email}`, {
- method: 'GET',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- credentials: "include"
- });
- const data = await response.json();
- console.log("here");
- if (response.ok) {
- storeUserData(JSON.stringify(data));
- console.log(`Data of ${_data.email}: `, data);
- } else {
- console.log(data);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.error.message}
-
- );
- },
- });
- }
- } catch (error) {
- console.error('Error:', error);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- Network Error
-
- );
- },
- });
- }
- router.replace('/home');
- }
- else {
- setLoading(false);
- router.replace('/verify-otp');
- }
- } else {
- setLoading(false);
- console.log(data);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
+ const response = await UserLogin(_data.email, _data.password);
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
}
- } catch (error) {
- console.error('Error:', error);
- }
+ });
setLoading(false);
};
diff --git a/frontend/occupi-mobile4/screens/Login/SignUp.tsx b/frontend/occupi-mobile4/screens/Login/SignUp.tsx
index 874d99ba..30e09d82 100644
--- a/frontend/occupi-mobile4/screens/Login/SignUp.tsx
+++ b/frontend/occupi-mobile4/screens/Login/SignUp.tsx
@@ -29,6 +29,7 @@ import {
FormControlLabelText,
View
} from '@gluestack-ui/themed';
+import { retrievePushToken } from '@/utils/notifications';
import GradientButton from '@/components/GradientButton';
import { Controller, useForm } from 'react-hook-form';
import { AlertTriangle, EyeIcon, EyeOffIcon } from 'lucide-react-native';
@@ -39,6 +40,7 @@ import { Keyboard } from 'react-native';
import StyledExpoRouterLink from '../../components/StyledExpoRouterLink';
import { router } from 'expo-router';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
+import { userRegister } from '@/utils/auth';
const isEmployeeIdFocused = false;
const signUpSchema = z.object({
@@ -69,6 +71,7 @@ const signUpSchema = z.object({
type SignUpSchemaType = z.infer;
+retrievePushToken();
const SignUpForm = () => {
const {
@@ -85,50 +88,18 @@ const SignUpForm = () => {
const onSubmit = async (_data: SignUpSchemaType) => {
if (_data.password === _data.confirmpassword) {
setLoading(true);
- try {
- const response = await fetch('https://dev.occupi.tech/auth/register', {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- email: _data.email,
- password: _data.password
- }),
- credentials: "include"
- });
- const data = await response.json();
- if (response.ok) {
- setLoading(false);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- router.push({pathname:'/verify-otp', params: { email: _data.email}});
- } else {
- setLoading(false);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.error.message}
-
- );
- },
- });
- }
- } catch (error) {
- console.error('Error:', error);
+ const response = await userRegister(_data.email, _data.password, _data.employeeId);
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
}
- setLoading(false)
+ });
+ setLoading(false);
} else {
toast.show({
placement: 'bottom right',
diff --git a/frontend/occupi-mobile4/screens/Login/SplashScreen.tsx b/frontend/occupi-mobile4/screens/Login/SplashScreen.tsx
index b0902ecc..3cb20b4f 100644
--- a/frontend/occupi-mobile4/screens/Login/SplashScreen.tsx
+++ b/frontend/occupi-mobile4/screens/Login/SplashScreen.tsx
@@ -93,7 +93,7 @@ export default function SplashScreen() {
useEffect(() => {
const timer = setTimeout(() => {
setSelectedIndex(1); // Assuming Onboarding1 is at index 1
- router.replace('/set-details'); // Navigate to Onboarding1 screen
+ router.replace('/welcome'); // Navigate to Onboarding1 screen
}, 5000); // 8 seconds
return () => clearTimeout(timer); // Clean up timer on component unmount
diff --git a/frontend/occupi-mobile4/screens/Login/__tests__/Welcome-test.tsx b/frontend/occupi-mobile4/screens/Login/__tests__/Welcome-test.tsx
index 704749b7..6192afb4 100644
--- a/frontend/occupi-mobile4/screens/Login/__tests__/Welcome-test.tsx
+++ b/frontend/occupi-mobile4/screens/Login/__tests__/Welcome-test.tsx
@@ -1,7 +1,8 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { StyledProvider, Theme } from '@gluestack-ui/themed';
-import Welcome from '../Welcome'; // Adjust the path to your component
+import Welcome from '../Welcome';
+import GradientButton from '@/components/GradientButton';
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); // To prevent warnings about Animated module
jest.mock('expo-router', () => ({
@@ -11,6 +12,7 @@ jest.mock('expo-router', () => ({
},
}));
+
const renderWithProvider = (component) => {
return render(
diff --git a/frontend/occupi-mobile4/screens/Notifications/Notifications.tsx b/frontend/occupi-mobile4/screens/Notifications/Notifications.tsx
index b663551c..fc21b983 100644
--- a/frontend/occupi-mobile4/screens/Notifications/Notifications.tsx
+++ b/frontend/occupi-mobile4/screens/Notifications/Notifications.tsx
@@ -14,11 +14,15 @@ import { StatusBar, useColorScheme, Dimensions } from 'react-native';
import { AntDesign, Entypo, FontAwesome6 } from '@expo/vector-icons';
import { Skeleton } from 'moti/skeleton';
import axios from 'axios';
+import { useTheme } from '@/components/ThemeContext';
+import { getUserNotifications } from '@/utils/notifications';
const Notifications = () => {
- const colorScheme = useColorScheme();
- const toast = useToast();
- const [notifications, setNotifications] = useState();
+ const colorscheme = useColorScheme();
+ const { theme } = useTheme();
+ const [accentColour, setAccentColour] = useState('greenyellow');
+ const currentTheme = theme === "system" ? colorscheme : theme;
+ const [notifications, setNotifications] = useState([]);
const [loading, setLoading] = useState(true);
const todayNotifications = [];
const yesterdayNotifications = [];
@@ -26,17 +30,25 @@ const Notifications = () => {
const apiUrl = process.env.EXPO_PUBLIC_DEVELOP_API_URL;
+ useEffect(() => {
+ const getSettings = async () => {
+ let accentcolour = await SecureStore.getItemAsync('accentColour');
+ setAccentColour(accentcolour);
+ };
+ getSettings();
+ }, []);
+
const formatNotificationDate = (sendTime) => {
const now = new Date();
// console.log(now);
const notificationDate = new Date(sendTime);
- console.log(notificationDate);
+ // console.log(notificationDate);
const differenceInHours = Math.floor((now - notificationDate) / (1000 * 60 * 60));
const differenceInDays = Math.floor(differenceInHours / 24);
if (differenceInDays === 0) {
- console.log(differenceInDays);
+ // console.log(differenceInDays);
return differenceInHours < 1 ? 'less than an hour ago' : `${differenceInHours} hours ago`;
} else if (differenceInDays === 1) {
return 'yesterday';
@@ -46,9 +58,10 @@ const Notifications = () => {
};
if (notifications) {
+ console.log('yurpp');
notifications.forEach(notification => {
const formattedDate = formatNotificationDate(notification.send_time);
-
+
if (formattedDate.includes('hours ago') || formattedDate.includes('hour ago')) {
todayNotifications.push(notification);
} else if (formattedDate === 'yesterday') {
@@ -62,67 +75,20 @@ const Notifications = () => {
useEffect(() => {
const getNotifications = async () => {
- let userEmail = await SecureStore.getItemAsync('Email');
- let authToken = await SecureStore.getItemAsync('Token');
-
- try {
- const response = await axios.get('https://dev.occupi.tech/api/get-notifications', {
- params: {
- filter: {
- emails: [{ userEmail }]
- },
- order_desc: "send_time"
- },
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- withCredentials: true
- });
- const data = response.data;
- // console.log(`Response Data: ${JSON.stringify(data.data)}`);
- console.log(data);
- if (response.status === 200) {
- setNotifications(data.data || []); // Ensure data is an array
- setLoading(false);
- } else {
- console.log(data);
- setLoading(false);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.error.message}
-
- );
- },
- });
- }
- } catch (error) {
- console.error('Error:', error);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- Network Error: {error.message}
-
- );
- },
- });
- }
+ const notifications = await getUserNotifications();
+ // console.log(notifications);
+ setNotifications(notifications);
+ setLoading(false);
};
getNotifications();
- }, [apiUrl, toast])
+ }, [])
const renderNotifications = (notificationList) => (
notificationList.map((notification, idx) => (
-
-
+
+
{notification.message} · {formatNotificationDate(notification.send_time)}
@@ -132,10 +98,10 @@ const Notifications = () => {
return (
-
+
- Notifications
-
+ Notifications
+
@@ -143,20 +109,20 @@ const Notifications = () => {
<>
{Array.from({ length: 8 }, (_, index) => (
-
+
))}
>
) : (
- Recent
+ Recent
{renderNotifications(todayNotifications)}
- Yesterday
+ Yesterday
{renderNotifications(yesterdayNotifications)}
- Older
+ Older
{renderNotifications(olderNotifications)}
diff --git a/frontend/occupi-mobile4/screens/Office/BookingDetails.tsx b/frontend/occupi-mobile4/screens/Office/BookingDetails.tsx
index e978bd23..207064d7 100644
--- a/frontend/occupi-mobile4/screens/Office/BookingDetails.tsx
+++ b/frontend/occupi-mobile4/screens/Office/BookingDetails.tsx
@@ -26,8 +26,9 @@ import { ActivityIndicator } from 'react-native';
import * as LocalAuthentication from "expo-local-authentication";
import * as SecureStore from 'expo-secure-store';
import GradientButton from '@/components/GradientButton';
-
-import { sendPushNotification } from "@/utils/utils";
+import { sendPushNotification } from "@/utils/notifications";
+import { userBookRoom } from "@/utils/bookings";
+import { useTheme } from "@/components/ThemeContext";
const BookingDetails = () => {
const navigation = useNavigation();
@@ -35,23 +36,19 @@ const BookingDetails = () => {
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const [bookingInfo, setbookingInfo] = useState();
- const colorScheme = useColorScheme();
+ const colorscheme = useColorScheme();
const toast = useToast();
const router = useRouter();
- const [creatorEmail, setCreatorEmail] = useState('');
const [startTime, setStartTime] = useState('');
const [endTime, setEndTime] = useState('');
- const isDark = colorScheme === "dark";
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
+ const isDark = colorscheme === "dark";
const [pushTokens, setPushTokens] = useState([]);
- // console.log(creatorEmail + roomId + floorNo);
- // console.log(bookingInfo?);
- // console.log(startTime);
const [attendees, setAttendees] = useState(['']);
// console.log(attendees);
const cardBackgroundColor = isDark ? '#2C2C2E' : '#F3F3F3';
const steps = ["Booking details", "Invite attendees", "Receipt"];
- const apiUrl = process.env.EXPO_PUBLIC_DEVELOP_API_URL;
- const bookroomendpoint = process.env.EXPO_PUBLIC_BOOK_ROOM;
const [accentColour, setAccentColour] = useState('greenyellow');
useEffect(() => {
@@ -64,21 +61,13 @@ const BookingDetails = () => {
useEffect(() => {
const getbookingInfo = async () => {
- let userinfo = await SecureStore.getItemAsync('UserData');
- // if (result !== undefined) {
- let jsoninfo = JSON.parse(userinfo);
- console.log("data", jsoninfo?.data.details.name);
- setCreatorEmail(jsoninfo?.data?.email);
+ let userEmail = await SecureStore.getItemAsync('Email');
let result: string = await SecureStore.getItemAsync('BookingInfo');
- console.log("CurrentRoom:", jsoninfo?.data?.email);
- // setUserDetails(JSON.parse(result).data);
let jsonresult = JSON.parse(result);
- console.log("BookingInfo", jsonresult);
setbookingInfo(jsonresult);
setStartTime(jsonresult.startTime);
setEndTime(jsonresult.endTime);
- console.log(jsoninfo?.data?.email);
- setAttendees([jsoninfo?.data?.email]);
+ setAttendees([userEmail]);
};
getbookingInfo();
}, []);
@@ -95,92 +84,23 @@ const BookingDetails = () => {
};
const onSubmit = async () => {
- const body = {
- "roomId": bookingInfo?.roomId,
- "emails": attendees,
- "roomName": bookingInfo?.roomName,
- "creator": creatorEmail,
- "floorNo": bookingInfo?.floorNo,
- "date": `${bookingInfo?.date}T00:00:00.000+00:00`,
- "start": `${bookingInfo?.date}T${startTime}:00.000+00:00`,
- "end": `${bookingInfo?.date}T${endTime}:00.000+00:00`
- };
- console.log("hereeeeee", body);
- let authToken = await SecureStore.getItemAsync('Token');
- let userinfo = await SecureStore.getItemAsync('UserData');
- let jsoninfo = JSON.parse(userinfo);
- try {
- setLoading(true);
- const response = await fetch(`${apiUrl}${bookroomendpoint}`, {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`,
- 'X-Timezone': 'Africa/Johannesburg'
- },
- body: JSON.stringify(body),
- credentials: "include"
- });
- const data = await response.json();
- console.log(data);
- console.log(attendees);
- if (response.ok) {
- try {
- const response = await fetch(`${apiUrl}/api/get-push-tokens?emails=${attendees.slice(1)}`, {
- method: 'GET',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`,
- 'X-Timezone': 'Africa/Johannesburg'
- },
- credentials: "include"
- });
- const data = await response.json();
- console.log("PUSHH TOKENSS",data);
- if (data.data) {
- let tokens = data.data.map((item) => item.expoPushToken);
- setPushTokens(tokens);
- console.log(tokens);
- sendPushNotification(tokens, "New Booking", `${jsoninfo?.data.details.name} has invited you to a booking.`);
- }
- setCurrentStep(2);
- setLoading(false);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- } catch (error) {
- setLoading(false);
- console.error('Error:', error);
- }
- } else {
- console.log(data);
- setLoading(false);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
+ setLoading(true);
+ const response = await userBookRoom(attendees, startTime, endTime);
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
}
- } catch (error) {
- setLoading(false);
- console.error('Error:', error);
- // setResponse('An error occurred');
+ });
+
+ if (response === 'Successfully booked!') {
+ setCurrentStep(2);
}
- // }, 3000);
+ setLoading(false);
};
const renderAttendee = ({ item }) => (
@@ -314,9 +234,9 @@ const BookingDetails = () => {
}}
>
navigation.goBack()}>
- router.back()} />
+ router.back()} />
- {/* */}
+ {/* */}
{
@@ -61,8 +39,10 @@ const OfficeDetails = () => {
const [startTime, setStartTime] = useState();
const [endTime, setEndTime] = useState();
const router = useRouter();
- const colorScheme = useColorScheme();
- const isDarkMode = colorScheme === 'dark';
+ const { theme } = useTheme();
+ const colorscheme = useColorScheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
+ const isDarkMode = currentTheme === 'dark';
const [room, setRoom] = useState();
const navigation = useNavigation>();
const scrollX = useRef(new Animated.Value(0)).current;
@@ -91,7 +71,7 @@ const OfficeDetails = () => {
useEffect(() => {
const getCurrentRoom = async () => {
let result : string = await SecureStore.getItemAsync('CurrentRoom');
- console.log("CurrentRoom:",result);
+ // console.log("CurrentRoom:",result);
// setUserDetails(JSON.parse(result).data);
let jsonresult = JSON.parse(result);
// console.log(jsonresult);
@@ -134,27 +114,27 @@ const OfficeDetails = () => {
maxOccupancy: room?.maxOccupancy
};
- console.log(bookingInfo);
+ // console.log(bookingInfo);
await SecureStore.setItemAsync('BookingInfo', JSON.stringify(bookingInfo));
router.replace('/booking-details');
}
-
+ console.log(theme);
// console.log(room?);
// console.log(userEmail);
return (
<>
{/* Top Section */}
-
- navigation.goBack()} />
+
+ navigation.goBack()} />
{room?.roomName}
-
+
diff --git a/frontend/occupi-mobile4/screens/Settings/Appearance.tsx b/frontend/occupi-mobile4/screens/Settings/Appearance.tsx
index 07b95a46..dc6aee5e 100644
--- a/frontend/occupi-mobile4/screens/Settings/Appearance.tsx
+++ b/frontend/occupi-mobile4/screens/Settings/Appearance.tsx
@@ -3,7 +3,8 @@ import {
StyleSheet,
Alert,
TextInput,
- TouchableOpacity
+ TouchableOpacity,
+ useColorScheme
} from 'react-native';
import { Feather } from '@expo/vector-icons';
import { MaterialCommunityIcons, FontAwesome } from '@expo/vector-icons';
@@ -15,10 +16,11 @@ import {
Box
} from '@gluestack-ui/themed';
import { router } from 'expo-router';
-import { useColorScheme, Switch } from 'react-native';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
import GradientButton from '@/components/GradientButton';
import * as SecureStore from 'expo-secure-store';
+import { storeTheme, storeAccentColour } from '@/services/securestore';
+import { useTheme } from '@/components/ThemeContext';
const FONTS = {
h3: { fontSize: 20, fontWeight: 'bold' },
@@ -32,30 +34,25 @@ const SIZES = {
};
const Appearance = () => {
- //retrieve user settings ad assign variables accordingly
- const onSave = () => {
- //integration here
- };
const [accentColour, setAccentColour] = useState('greenyellow');
- const [theme, setTheme] = useState('');
- let colorScheme = theme;
+ const { theme, setTheme } = useTheme();
+ const colorscheme = useColorScheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
+
+ const onSave = () => {
+ storeAccentColour(accentColour);
+ storeTheme(theme);
+ router.back();
+ }
useEffect(() => {
- const getAccentColour = async () => {
+ const getSettings = async () => {
let accentcolour = await SecureStore.getItemAsync('accentColour');
setAccentColour(accentcolour);
};
- getAccentColour();
+ getSettings();
}, []);
- console.log(accentColour);
-
- const setAccentcolour = async (value) => {
- setAccentColour(value);
- await SecureStore.setItemAsync('accentColour', value);
- }
- // setAccentcolour();
-
const handleBack = () => {
// if (isSaved === false) {
// Alert.alert(
@@ -76,44 +73,44 @@ const Appearance = () => {
router.back();
// }
}
- console.log(theme);
+ // console.log(theme);
return (
-
+
-
+
Appearance
- Mode
-
+ Mode
+
setTheme("light")} style={{ width: wp('25%') }}>
- Light
+ Light
setTheme("dark")} style={{ width: wp('25%') }}>
@@ -121,13 +118,13 @@ const Appearance = () => {
- Dark
+ Dark
setTheme("system")} style={{ width: wp('25%') }}>
@@ -135,90 +132,90 @@ const Appearance = () => {
- System
+ System
- Accent colour
-
+ Accent colour
+
- setAccentcolour("lightgrey")}>
+ setAccentColour("lightgrey")}>
-
+
- setAccentcolour("#FF4343")}>
+ setAccentColour("#FF4343")}>
-
+
- setAccentcolour("#FFB443")}>
+ setAccentColour("#FFB443")}>
-
+
- setAccentcolour("greenyellow")}>
+ setAccentColour("greenyellow")}>
-
+
- setAccentcolour("#43FF61")}>
+ setAccentColour("#43FF61")}>
-
+
- setAccentcolour("#43F4FF")}>
+ setAccentColour("#43F4FF")}>
-
+
- setAccentcolour("#4383FF")}>
+ setAccentColour("#4383FF")}>
-
+
- setAccentcolour("#AC43FF")}>
+ setAccentColour("#AC43FF")}>
-
+
- setAccentcolour("#FF43F7")}>
+ setAccentColour("#FF43F7")}>
-
+
- setAccentcolour("purple")}>
- setAccentcolour("#FF4343")}>
-
+ setAccentColour("purple")}>
+ setAccentColour("#FF4343")}>
+
- Or enter a custom colour
+ Or enter a custom colour
- Custom colour:
+ Custom colour:
diff --git a/frontend/occupi-mobile4/screens/Settings/ChangePassword.tsx b/frontend/occupi-mobile4/screens/Settings/ChangePassword.tsx
index 9c7a4be7..564e50a1 100644
--- a/frontend/occupi-mobile4/screens/Settings/ChangePassword.tsx
+++ b/frontend/occupi-mobile4/screens/Settings/ChangePassword.tsx
@@ -6,7 +6,8 @@ import {
Alert,
Keyboard,
KeyboardAvoidingView,
- Platform
+ Platform,
+ useColorScheme
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Feather } from '@expo/vector-icons';
@@ -29,7 +30,6 @@ import {
import { Controller, useForm } from 'react-hook-form';
import { router } from 'expo-router';
import { AlertTriangle, EyeIcon, EyeOffIcon } from 'lucide-react-native';
-import { useColorScheme, Switch } from 'react-native';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import GradientButton from '@/components/GradientButton';
@@ -37,6 +37,8 @@ import * as SecureStore from 'expo-secure-store';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
import axios from 'axios';
import { Toast, ToastTitle, useToast } from '@gluestack-ui/themed';
+import { updateSecurity } from '@/utils/user';
+import { useTheme } from '@/components/ThemeContext';
const COLORS = {
white: '#FFFFFF',
@@ -59,67 +61,29 @@ const SIZES = {
type SignUpSchemaType = z.infer;
const ChangePassword = () => {
- let colorScheme = useColorScheme();
+ const colorscheme = useColorScheme();
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
const toast = useToast();
const onSubmit = async (_data: SignUpSchemaType) => {
- //integration here
- let userEmail = await SecureStore.getItemAsync('Email');
- console.log(JSON.stringify({
- email: userEmail,
+ if (_data.password === _data.confirmpassword) {
+ const settings = {
currentPassword: _data.currentpassword,
newPassword: _data.password,
newPasswordConfirm: _data.confirmpassword
- }));
- if (_data.password === _data.confirmpassword) {
- let userEmail = await SecureStore.getItemAsync('Email');
- let authToken = await SecureStore.getItemAsync('Token');
-
- try {
- const response = await axios.post('https://dev.occupi.tech/api/update-security-settings', {
- email: userEmail,
- currentPassword: _data.currentpassword,
- newPassword: _data.password,
- newPasswordConfirm: _data.confirmpassword
- }, {
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- withCredentials: true
- });
- const data = response.data;
- // console.log(`Response Data: ${JSON.stringify(data.data)}`);
- console.log(data);
- if (response.status === 200) {
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- Password successfully changed
-
- );
- },
- });
- router.replace('/settings');
- } else {
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- console.log(data);
- }
- } catch (error) {
- console.error('Error:', error);
- }
+ };
+ const response = await updateSecurity('password', settings)
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
+ },
+ });
}
else if (_data.currentpassword === _data.password) {
Alert.alert('Error', 'New password cannot be the same as the current password');
@@ -189,7 +153,7 @@ const ChangePassword = () => {
return (
-
+
{
as={Feather}
name="chevron-left"
size="xl"
- color={colorScheme === 'dark' ? 'white' : 'black'}
+ color={currentTheme === 'dark' ? 'white' : 'black'}
onPress={() => router.back()}
/>
-
+
Change Password
@@ -218,7 +182,7 @@ const ChangePassword = () => {
- Current Password
+ Current Password
{
},
}}
render={({ field: { onChange, onBlur, value } }) => (
-
+
{
onBlur={onBlur}
onSubmitEditing={handleKeyPress}
returnKeyType="done"
- color={colorScheme === 'dark' ? 'white' : 'black'}
+ color={currentTheme === 'dark' ? 'white' : 'black'}
type={showPassword ? 'text' : 'password'}
/>
@@ -265,7 +229,7 @@ const ChangePassword = () => {
- New Password
+ New Password
{
},
}}
render={({ field: { onChange, onBlur, value } }) => (
-
+
{
onChangeText={onChange}
onBlur={onBlur}
onSubmitEditing={handleKeyPress}
- color={colorScheme === 'dark' ? 'white' : 'black'}
+ color={currentTheme === 'dark' ? 'white' : 'black'}
returnKeyType="done"
type={showPassword ? 'text' : 'password'}
/>
@@ -312,7 +276,7 @@ const ChangePassword = () => {
- Confirm Password
+ Confirm Password
{
},
}}
render={({ field: { onChange, onBlur, value } }) => (
-
+
{
onChangeText={onChange}
onBlur={onBlur}
onSubmitEditing={handleKeyPress}
- color={colorScheme === 'dark' ? 'white' : 'black'}
+ color={currentTheme === 'dark' ? 'white' : 'black'}
returnKeyType="done"
type={showConfirmPassword ? 'text' : 'password'}
/>
diff --git a/frontend/occupi-mobile4/screens/Settings/FAQPage.tsx b/frontend/occupi-mobile4/screens/Settings/FAQPage.tsx
index dbe72326..78c91732 100644
--- a/frontend/occupi-mobile4/screens/Settings/FAQPage.tsx
+++ b/frontend/occupi-mobile4/screens/Settings/FAQPage.tsx
@@ -1,10 +1,13 @@
import React from 'react';
import { ScrollView, useColorScheme } from 'react-native';
import { View, Text, Accordion, AccordionItem, AccordionHeader, AccordionTrigger, AccordionContent } from '@gluestack-ui/themed';
+import { useTheme } from '@/components/ThemeContext';
const FAQPage = () => {
- const colorScheme = useColorScheme();
- const isDarkMode = colorScheme === 'dark';
+ const colorscheme = useColorScheme();
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
+ const isDarkMode = currentTheme === 'dark';
const faqData = [
{
diff --git a/frontend/occupi-mobile4/screens/Settings/Notifications.tsx b/frontend/occupi-mobile4/screens/Settings/Notifications.tsx
index cd82dde9..4ba2ad4b 100644
--- a/frontend/occupi-mobile4/screens/Settings/Notifications.tsx
+++ b/frontend/occupi-mobile4/screens/Settings/Notifications.tsx
@@ -17,6 +17,8 @@ import GradientButton from '@/components/GradientButton';
import * as SecureStore from 'expo-secure-store';
import axios from 'axios';
import { Toast, ToastTitle, useToast } from '@gluestack-ui/themed';
+import { updateNotifications } from '@/utils/user';
+import { useTheme } from '@/components/ThemeContext';
const COLORS = {
@@ -38,7 +40,9 @@ const SIZES = {
};
const Notifications = () => {
- let colorScheme = useColorScheme();
+ const colorscheme = useColorScheme();
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
const toast = useToast();
//retrieve user settings ad assign variables accordingly
const [oldInviteVal, setOldInviteVal] = useState(false);
@@ -88,60 +92,21 @@ const Notifications = () => {
};
const onSave = async () => {
- let userEmail = await SecureStore.getItemAsync('Email');
- let authToken = await SecureStore.getItemAsync('Token');
-
- try {
- const response = await axios.get('https://dev.occupi.tech/api/update-notification-settings', {
- params: {
- email: userEmail,
- invites: newInviteVal ? "on" : "off",
- bookingReminder: newNotifyVal ? "on" : "off"
- },
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- withCredentials: true
- });
- const data = response.data;
- // console.log(`Response Data: ${JSON.stringify(data.data)}`);
- console.log(data);
- if (response.status === 200) {
- const newSettings = {
- invites: newInviteVal ? "on" : "off",
- bookingReminder: newNotifyVal ? "on" : "off",
- }
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- console.log(newSettings);
- SecureStore.setItemAsync('Notifications', JSON.stringify(newSettings));
- router.replace('/settings');
- } else {
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- console.log(data);
- }
- } catch (error) {
- console.error('Error:', error);
- }
+ const settings = {
+ invites: newInviteVal ? "on" : "off",
+ bookingReminder: newNotifyVal ? "on" : "off"
+ };
+ const response = await updateNotifications(settings)
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
+ },
+ });
};
const handleBack = () => {
@@ -166,29 +131,29 @@ const Notifications = () => {
}
return (
-
+
-
+
Notifications
-
- Notify when someone invites me
+
+ Notify when someone invites me
{
value={newInviteVal}
/>
-
- Notify 15 minutes before booking time
+
+ Notify 15 minutes before booking time
{
- const [selectedGenderIndex, setSelectedGenderIndex] = useState(1);
+ const [selectedGenderIndex, setSelectedGenderIndex] = useState('Male');
const [name, setName] = useState('');
const [email, setEmail] = useState('');
- const [employeeId, setEmployeeId] = useState('');
+ const [employeeId, setEmployeeId] = useState('OCCUPI20242417');
const [phoneNumber, setPhoneNumber] = useState('');
const [pronouns, setPronouns] = useState('');
const [date, setDate] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
- let colorScheme = useColorScheme();
- const apiUrl = process.env.EXPO_PUBLIC_DEVELOP_API_URL;
- const getUserDetailsUrl= process.env.EXPO_PUBLIC_GET_USER_DETAILS;
- const updateDetailsUrl = process.env.EXPO_PUBLIC_UPDATE_USER_DETAILS;
- console.log(apiUrl, getUserDetailsUrl, updateDetailsUrl);
+ const colorscheme = useColorScheme();
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
+ const toast = useToast();
+ // console.log(apiUrl, getUserDetailsUrl, updateDetailsUrl);
useEffect(() => {
const getUserDetails = async () => {
let result = await SecureStore.getItemAsync('UserData');
- console.log("UserData:",result);
- // setUserDetails(JSON.parse(result).data);
- let jsonresult = JSON.parse(result);
- // console.log(jsonresult.data.details.name);
- setName(String(jsonresult?.data?.details?.name));
- setEmail(String(jsonresult?.data?.email));
- setEmployeeId(String(jsonresult?.data?.occupiId));
- setPhoneNumber(String(jsonresult?.data?.details?.contactNo));
- setPronouns(String(jsonresult?.data?.details?.pronouns));
- const dateString = jsonresult?.data?.details?.dob;
- const date = new Date(dateString);
+ console.log(result);
+ const email = await SecureStore.getItemAsync('Email');
+
+ let user = JSON.parse(result);
+ setName(String(user?.name));
+ setEmail(String(email));
+ setEmployeeId(String(user?.employeeid));
+ setPhoneNumber(String(user?.number));
+ setPronouns(String(user?.pronouns));
+ setSelectedGenderIndex(String(user?.gender))
+ const dateString = user?.dob;
+ console.log('dateee',dateString);
+
+ // Manually parse the date string
+ const [datePart] = dateString.split('T');
+ const [year, month, day] = datePart.split('-').map(Number);
+
+ // Create a new Date object
+ const date = new Date(year, month, day);
+ // console.log(date.getDate());
// Get the day, month, and year
- const day = date.getDate();
- const month = date.getMonth() + 1; // Months are zero-based
- const year = date.getFullYear();
+ const formattedDay = date.getDate();
+ const formattedMonth = date.getMonth(); // Months are zero-based
+ const formattedYear = date.getFullYear();
// Format the date as MM/DD/YYYY
- const formatted = `${month}/${day}/${year}`;
+ const formatted = `${formattedYear}-${formattedMonth}-${formattedDay}`;
+ // console.log(formatted);
// Set the formatted date in the state
- setDate(formatted)
-
- // console.log(JSON.parse(result).data.details.name);
+ setDate(formatted);
};
getUserDetails();
}, []);
@@ -101,81 +114,31 @@ const Profile = () => {
setDatePickerVisibility(false);
};
- const handleConfirm = (selectedDate) => {
- setDate(selectedDate);
+ const handleConfirm = (selectedDate: string) => {
+ console.log('selected',extractDateFromTimestamp(selectedDate));
+ setDate(extractDateFromTimestamp(selectedDate));
hideDatePicker();
};
- const onSave = async () => {
- const body = {
- "email": email,
- "details": {
- "contactNo": phoneNumber,
- "gender": "Male",
- "name": name,
- "pronouns": pronouns
- }
- };
- // console.log(JSON.stringify(body));
- setIsLoading(true);
- try {
- let authToken = await SecureStore.getItemAsync('Token');
- const response = await fetch(`${apiUrl}${updateDetailsUrl}`, {
- method: 'PUT',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- body: JSON.stringify(body),
- credentials: "include"
- });
- const data = await response.json();
- console.log(data);
- if (response.ok) {
- console.log(response);
- setIsLoading(false);
- alert('Details updated successfully');
- } else {
- console.log(data);
- setIsLoading(false);
- }
- } catch (error) {
- setIsLoading(false);
- console.error('Error:', error);
- // setResponse('An error occurred');
- }
- try {
- let authToken = await SecureStore.getItemAsync('Token');
- const response = await fetch(`${apiUrl}${getUserDetailsUrl}?email=${email}`, {
- method: 'GET',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- credentials: "include"
- });
- const data = await response.json();
- if (response.ok) {
- saveUserData(JSON.stringify(data));
- console.log(data);
- } else {
- console.log(data);
- }
- } catch (error) {
- console.error('Error:', error);
- }
+ const onSave = async () => {
+ const response = await updateDetails(name,date,selectedGenderIndex,phoneNumber,pronouns)
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
+ },
+ });
};
- async function saveUserData(value) {
- await SecureStore.setItemAsync('UserData', value);
- }
return (
@@ -183,37 +146,37 @@ const Profile = () => {
as={Feather}
name={"chevron-left"}
size="xl"
- color={colorScheme === 'dark' ? 'white' : 'black'}
+ color={currentTheme === 'dark' ? 'white' : 'black'}
onPress={() => router.replace('/settings')}
/>
-
+
My account
- Full name
+ Full name
- Date of birth
+ Date of birth
-
+
{date}
-
+
{
onCancel={hideDatePicker}
/>
- Gender
- {/* setSelectedGenderIndex(index)}>
+ Gender
+ setSelectedGenderIndex(index)}>
- Male
+ Male
- Female
+ Female
- Other
+ Other
- */}
-
- Email Address
+
+ Email Address
- Occupi ID
+ Occupi ID
- Cell No
+ Cell No
- Pronouns (optional)
+ Pronouns (optional)
{
- let colorScheme = useColorScheme();
+ const colorScheme = useColorScheme();
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorScheme : theme;
const toast = useToast();
//retrieve user settings ad assign variables accordingly
@@ -60,8 +56,9 @@ const Security = () => {
useEffect(() => {
const getSecurityDetails = async () => {
let settings = await SecureStore.getItemAsync('Security');
+ // console.log(settings);
const settingsObject = JSON.parse(settings);
- console.log(settingsObject);
+ // console.log('current settings',settingsObject);
if (settingsObject.mfa === "on") {
setOldMfa(true);
@@ -119,59 +116,22 @@ const Security = () => {
const onSave = async () => {
//integration here
- let userEmail = await SecureStore.getItemAsync('Email');
- let authToken = await SecureStore.getItemAsync('Token');
-
- try {
- const response = await axios.post('https://dev.occupi.tech/api/update-security-settings', {
- email: userEmail,
- mfa: newMfa ? "on" : "off",
- forceLogout: newForceLogout ? "on" : "off"
- }, {
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- withCredentials: true
- });
- const data = response.data;
- // console.log(`Response Data: ${JSON.stringify(data.data)}`);
- // console.log(data);
- if (response.status === 200) {
- const newSettings = {
- mfa: newMfa ? "on" : "off",
- forceLogout: newForceLogout ? "on" : "off",
- }
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- // console.log(newSettings);
- SecureStore.setItemAsync('Security', JSON.stringify(newSettings));
- router.replace('/settings');
- } else {
- console.log(data);
- toast.show({
- placement: 'top',
- render: ({ id }) => {
- return (
-
- {data.message}
-
- );
- },
- });
- }
- } catch (error) {
- console.error('Error:', error);
- }
+ const settings = {
+ mfa: newMfa ? "on" : "off",
+ forceLogout: newForceLogout ? "on" : "off"
+ };
+ const response = await updateSecurity('settings', settings)
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
+ },
+ });
+ // console.log(newSettings);
};
const handleBack = () => {
@@ -195,282 +155,52 @@ const Security = () => {
}
}
return (
-
-
-
-<<<<<<< HEAD
-
-
-
-
-
- Security
-
-
-
-
-
-
-
- Use faceid/touch id to enter app
-
-
-
- Use 2fa to login
-
-
-
- Force logout on app close
-
-
-
- Use 2fa to login
-
-
-
- Force logout on app close
-
-
- handleBiometricAuth()}>
-
- Change Password
-
- Change password
-
-
- Current Password
-
- {
- try {
- await signUpSchema.parseAsync({
- password: value,
- });
- return true;
- } catch (error) {
- return error.message;
- }
- },
- }}
- render={({ field: { onChange, onBlur, value } }) => (
-
-
-
-
-
-
- )}
- />
-
-
-
- {errors?.password?.message}
-
-
-
-
-
-
- New Password
-
- {
- try {
- await signUpSchema.parseAsync({
- password: value,
- });
- return true;
- } catch (error) {
- return error.message;
- }
- },
- }}
- render={({ field: { onChange, onBlur, value } }) => (
-
-
-
-
-
-
- )}
- />
-
-
-
- {errors?.password?.message}
-
-
-
-
-
-
- Confirm Password
-
- {
- try {
- await signUpSchema.parseAsync({
- password: value,
- });
-
- return true;
- } catch (error: any) {
- return error.message;
- }
- },
- }}
- render={({ field: { onChange, onBlur, value } }) => (
-
-
-
-
-
-
-
- )}
- />
-
-
-
- {errors?.confirmpassword?.message}
-
-
-
-
-
-=======
+
-
+
Security
->>>>>>> develop
-
- Use 2fa to login
+
+ Use 2fa to login
-
- Force logout on app close
+
+ Force logout on app close
handleBiometricAuth()}>
-
- Change Password
+
+ Change Password
diff --git a/frontend/occupi-mobile4/screens/Settings/Settings.tsx b/frontend/occupi-mobile4/screens/Settings/Settings.tsx
index ced7ac53..4b5c565e 100644
--- a/frontend/occupi-mobile4/screens/Settings/Settings.tsx
+++ b/frontend/occupi-mobile4/screens/Settings/Settings.tsx
@@ -8,122 +8,111 @@ import {
Icon,
Divider,
Pressable,
+ Toast,
+ ToastTitle,
Text
} from '@gluestack-ui/themed';
-import { useNavigation } from '@react-navigation/native';
import { Feather, MaterialIcons } from '@expo/vector-icons';
import { router } from 'expo-router';
import Navbar from '../../components/NavBar';
import { useColorScheme } from 'react-native';
import { widthPercentageToDP as wp } from 'react-native-responsive-screen';
import * as SecureStore from 'expo-secure-store';
+import { useToast } from '@gluestack-ui/themed';
+import { UserLogout } from '@/utils/auth';
+import { useTheme } from '@/components/ThemeContext';
const Settings = () => {
const [name, setName] = useState('');
const [position, setPosition] = useState('');
- const navigation = useNavigation();
- let colorScheme = useColorScheme();
+ const toast = useToast();
+ const colorscheme = useColorScheme();
+ const { theme } = useTheme();
+ const currentTheme = theme === "system" ? colorscheme : theme;
useEffect(() => {
const getUserDetails = async () => {
let result = await SecureStore.getItemAsync('UserData');
let jsonresult = JSON.parse(result);
- setName(String(jsonresult.data.details.name));
- setPosition(String(jsonresult.data.position));
+ setName(String(jsonresult.name));
+ setPosition(String(jsonresult.position));
};
getUserDetails();
}, []);
const handleLogout = async () => {
- let authToken = await SecureStore.getItemAsync('Token');
- try {
- const response = await fetch('https://dev.occupi.tech/auth/logout', {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `${authToken}`
- },
- credentials: "include"
- });
- const data = await response.json();
- if (response.ok) {
- console.log(data);
- alert("logged out successfully");
- router.replace('/login');
- } else {
- console.log(data);
- alert("unable to logout");
+ const response = await UserLogout();
+ toast.show({
+ placement: 'top',
+ render: ({ id }) => {
+ return (
+
+ {response}
+
+ );
}
- } catch (error) {
- console.error('Error:', error);
- }
+ });
}
- // console.log("details"+name);
-
- const handleNavigate = (screen) => {
- navigation.navigate(screen);
- };
const data = [
{ title: 'My account', description: 'Make changes to your account', iconName: 'user', onPress: () => router.replace('/profile')},
{ title: 'Notifications', description: 'Manage your notifications', iconName: 'bell', onPress: () => router.push('set-notifications')},
{ title: 'Security', description: 'Enhance your security', iconName: 'shield', onPress: () => router.push('/set-security') },
{ title: 'Appearance', description: 'Customize your viewing experience', iconName: 'image', onPress: () => router.push('/set-appearance') },
- { title: 'FAQ', description: '', iconName: 'info', onPress: () => router.push('faqpage') },
+ { title: 'FAQ', description: "View the community's FAQ", iconName: 'info', onPress: () => router.push('faqpage') },
{ title: 'Log out', description: 'Log out from your account', iconName: 'log-out', onPress: () => handleLogout() },
];
const renderListItem = ({ item }) => (
-
-
+
+
- {item.title}
- {item.description}
+ {item.title}
+ {item.description}
- {item.accessoryRight ? item.accessoryRight() : }
+ {item.accessoryRight ? item.accessoryRight() : }
);
return (
<>
-
+
-
+
- {name}
- {/* handleNavigate('EditProfileScreen')} /> */}
+ {name}
+ {/* handleNavigate('EditProfileScreen')} /> */}
- {position}
+ {/* {position} */}
-
-
+
+
{data.map((item, index) => (
{renderListItem({ item })}
-
+
))}
- Version 0.1.0
+ Version 0.1.0
@@ -191,7 +180,7 @@ const styles = StyleSheet.create({
color: '#fff',
},
footerContainer: {
- padding: 16,
+ // padding: 16,
alignItems: 'center',
},
versionText: {
diff --git a/frontend/occupi-mobile4/services/__tests__/apiservices-test.tsx b/frontend/occupi-mobile4/services/__tests__/apiservices-test.tsx
new file mode 100644
index 00000000..d760d73a
--- /dev/null
+++ b/frontend/occupi-mobile4/services/__tests__/apiservices-test.tsx
@@ -0,0 +1,265 @@
+import axios from 'axios';
+import * as SecureStore from "expo-secure-store";
+import {
+ getUserDetails,
+ getNotificationSettings,
+ getUserBookings,
+ getSecuritySettings,
+ updateSecuritySettings,
+ updateNotificationSettings,
+} from "../apiservices";
+import { NotificationSettingsReq } from '@/models/requests';
+
+jest.mock('axios');
+jest.mock("expo-secure-store");
+
+const mockedAxios = axios as jest.Mocked;
+
+describe("User API Functions", () => {
+ const mockEmail = "test@example.com";
+ const mockAuthToken = "mockAuthToken";
+ const mockSuccessResponse = { success: true, data: {} };
+ const mockErrorResponse = {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ };
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockAuthToken);
+ mockedAxios.isAxiosError.mockImplementation((payload: any) => true);
+ });
+
+ describe("getUserDetails", () => {
+ it("should return success response when API call is successful", async () => {
+ mockedAxios.get.mockResolvedValue({ data: mockSuccessResponse });
+
+ const result = await getUserDetails(mockEmail, mockAuthToken);
+
+ expect(mockedAxios.get).toHaveBeenCalledWith(
+ "https://dev.occupi.tech/api/user-details",
+ expect.objectContaining({
+ params: { email: mockEmail },
+ headers: expect.objectContaining({ Authorization: mockAuthToken }),
+ })
+ );
+ expect(result).toEqual(mockSuccessResponse);
+ });
+
+ it("should return error response when API call fails", async () => {
+ mockedAxios.get.mockRejectedValue({
+ response: { data: mockErrorResponse },
+ });
+
+ const result = await getUserDetails(mockEmail, mockAuthToken);
+
+ expect(result).toEqual(mockErrorResponse);
+ });
+ });
+
+ describe("getNotificationSettings", () => {
+ it("should return success response when API call is successful", async () => {
+ (axios.get as jest.Mock).mockResolvedValue({ data: mockSuccessResponse });
+
+ const result = await getNotificationSettings(mockEmail);
+
+ expect(axios.get).toHaveBeenCalledWith(
+ "https://dev.occupi.tech/api/get-notification-settings",
+ expect.objectContaining({
+ params: { email: mockEmail },
+ headers: expect.objectContaining({ Authorization: mockAuthToken }),
+ })
+ );
+ expect(result).toEqual(mockSuccessResponse);
+ });
+
+ it("should return error response when API call fails", async () => {
+ (axios.get as jest.Mock).mockRejectedValue({
+ response: { data: mockErrorResponse },
+ });
+
+ const result = await getNotificationSettings(mockEmail);
+
+ expect(result).toEqual(mockErrorResponse);
+ });
+ });
+
+ describe("getUserBookings", () => {
+ it("should return success response when API call is successful", async () => {
+ const mockAuthToken = "mockAuthToken";
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockAuthToken);
+ (axios.get as jest.Mock).mockResolvedValue({
+ data: {
+ data: { bookings: [] },
+ status: 'success',
+ message: 'Bookings retrieved successfully'
+ }
+ });
+
+ const result = await getUserBookings(mockEmail);
+
+ expect(axios.get).toHaveBeenCalledWith(
+ `https://dev.occupi.tech/api/view-bookings?filter={"email":"${mockEmail}"}`,
+ expect.objectContaining({
+ headers: expect.objectContaining({ Authorization: mockAuthToken }),
+ })
+ );
+ expect(result).toEqual({
+ data: { bookings: [] },
+ status: 'success',
+ message: 'Bookings retrieved successfully'
+ });
+ });
+
+ it("should return error response when API call fails", async () => {
+ const mockAuthToken = "mockAuthToken";
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockAuthToken);
+ (axios.get as jest.Mock).mockRejectedValue({
+ response: {
+ data: {
+ data: null,
+ status: 'error',
+ message: 'Failed to retrieve bookings',
+ error: {
+ code: 'API_ERROR',
+ details: 'API call failed',
+ message: 'Failed to retrieve bookings'
+ }
+ }
+ },
+ });
+
+ const result = await getUserBookings(mockEmail);
+
+ expect(result).toEqual({
+ data: null,
+ status: 'error',
+ message: 'Failed to retrieve bookings',
+ error: {
+ code: 'API_ERROR',
+ details: 'API call failed',
+ message: 'Failed to retrieve bookings'
+ }
+ });
+ });
+
+ it("should handle case when auth token is not found", async () => {
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(null);
+
+ const result = await getUserBookings(mockEmail);
+
+ expect(result).toEqual({
+ data: null,
+ status: 'error',
+ message: 'Authentication failed',
+ error: {
+ code: 'AUTH_ERROR',
+ details: 'No authentication token found',
+ message: 'Authentication failed'
+ }
+ });
+ });
+ });
+
+ describe("getSecuritySettings", () => {
+ it("should return success response when API call is successful", async () => {
+ (axios.get as jest.Mock).mockResolvedValue({ data: mockSuccessResponse });
+
+ const result = await getSecuritySettings(mockEmail);
+
+ expect(axios.get).toHaveBeenCalledWith(
+ "https://dev.occupi.tech/api/get-security-settings",
+ expect.objectContaining({
+ params: { email: mockEmail },
+ headers: expect.objectContaining({ Authorization: mockAuthToken }),
+ })
+ );
+
+ expect(result).toEqual(mockSuccessResponse);
+ });
+
+ it("should return error response when API call fails", async () => {
+ (axios.get as jest.Mock).mockRejectedValue({
+ response: { data: mockErrorResponse },
+ });
+
+ const result = await getSecuritySettings(mockEmail);
+
+ expect(result).toEqual(mockErrorResponse);
+ });
+ });
+
+ describe("updateSecuritySettings", () => {
+ it("should return success response when API call is successful", async () => {
+ (axios.post as jest.Mock).mockResolvedValue({
+ data: mockSuccessResponse,
+ });
+ const mockReq = { email: mockEmail, newSetting: "value" };
+
+ const result = await updateSecuritySettings(mockReq);
+
+ expect(axios.post).toHaveBeenCalledWith(
+ "https://dev.occupi.tech/api/update-security-settings",
+ mockReq,
+ expect.objectContaining({
+ headers: expect.objectContaining({ Authorization: mockAuthToken }),
+ })
+ );
+ expect(result).toEqual(mockSuccessResponse);
+ });
+
+ it("should return error response when API call fails", async () => {
+ (axios.post as jest.Mock).mockRejectedValue({
+ response: { data: mockErrorResponse },
+ });
+ const mockReq = { email: mockEmail, newSetting: "value" };
+
+ const result = await updateSecuritySettings(mockReq);
+
+ expect(result).toEqual(mockErrorResponse);
+ });
+ });
+
+ describe("updateNotificationSettings", () => {
+ it("should return success response when API call is successful", async () => {
+ (axios.get as jest.Mock).mockResolvedValue({ data: mockSuccessResponse });
+ const mockReq: NotificationSettingsReq = {
+ email: mockEmail,
+ invites: "on",
+ bookingReminder: "on",
+ };
+
+ const result = await updateNotificationSettings(mockReq);
+
+ expect(axios.get).toHaveBeenCalledWith(
+ "https://dev.occupi.tech/api/update-notification-settings",
+ expect.objectContaining({
+ params: { req: mockReq },
+ headers: expect.objectContaining({ Authorization: mockAuthToken }),
+ })
+ );
+ expect(result).toEqual(mockSuccessResponse);
+ });
+
+ it("should return error response when API call fails", async () => {
+ (axios.get as jest.Mock).mockRejectedValue({
+ response: { data: mockErrorResponse },
+ });
+ const mockReq: NotificationSettingsReq = {
+ email: mockEmail,
+ invites: "on",
+ bookingReminder: "on",
+ };
+
+ const result = await updateNotificationSettings(mockReq);
+
+ expect(result).toEqual(mockErrorResponse);
+ });
+ });
+});
diff --git a/frontend/occupi-mobile4/services/__tests__/authservices-test.tsx b/frontend/occupi-mobile4/services/__tests__/authservices-test.tsx
new file mode 100644
index 00000000..31c8dc3a
--- /dev/null
+++ b/frontend/occupi-mobile4/services/__tests__/authservices-test.tsx
@@ -0,0 +1,137 @@
+import axios from 'axios';
+import * as SecureStore from 'expo-secure-store';
+import { login, logout } from '../../services/authservices';
+import { LoginReq } from '@/models/requests';
+import { LoginSuccess, Unsuccessful, Success } from '@/models/response';
+
+jest.mock('axios');
+jest.mock('expo-secure-store');
+
+const mockedAxios = axios as jest.Mocked;
+const mockedSecureStore = SecureStore as jest.Mocked;
+
+describe('authservice', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockedSecureStore.getItemAsync.mockResolvedValue('mock-token');
+ });
+
+ describe('login', () => {
+ const loginReq: LoginReq = {
+ email: 'test@example.com',
+ password: 'password123',
+ };
+
+ it('should return LoginSuccess on successful login', async () => {
+ const mockResponse: LoginSuccess = {
+ data: { token: 'mock-token' },
+ message: 'Login successful',
+ status: 200,
+ };
+
+ mockedAxios.post.mockResolvedValueOnce({ data: mockResponse });
+
+ const result = await login(loginReq);
+
+ expect(mockedAxios.post).toHaveBeenCalledWith(
+ 'https://dev.occupi.tech/auth/login-mobile',
+ loginReq,
+ expect.any(Object)
+ );
+ expect(result).toEqual(mockResponse);
+ });
+
+ it('should throw error on failed login', async () => {
+ const mockError: Unsuccessful = {
+ data: null,
+ status: 'error',
+ message: 'Invalid credentials',
+ error: {
+ code: 'AUTH_ERROR',
+ details: 'Invalid email or password',
+ message: 'Authentication failed',
+ }
+ };
+
+ mockedAxios.post.mockRejectedValueOnce({
+ isAxiosError: true,
+ response: { data: mockError }
+ });
+
+ await expect(login(loginReq)).rejects.toEqual(
+ expect.objectContaining({
+ isAxiosError: true,
+ response: { data: mockError }
+ })
+ );
+ });
+
+ it('should throw error on network failure', async () => {
+ const networkError = new Error('Network error');
+ mockedAxios.post.mockRejectedValueOnce(networkError);
+
+ await expect(login(loginReq)).rejects.toThrow('Network error');
+ });
+ });
+
+ describe('logout', () => {
+ beforeEach(() => {
+ mockedSecureStore.getItemAsync.mockResolvedValue('mock-token');
+ });
+
+ it('should return Success on successful logout', async () => {
+ const mockResponse: Success = {
+ status: 200,
+ message: 'Logout successful',
+ data: null,
+ };
+
+ mockedAxios.post.mockResolvedValueOnce({ data: mockResponse });
+
+ const result = await logout();
+
+ expect(mockedAxios.post).toHaveBeenCalledWith(
+ 'https://dev.occupi.tech/auth/logout',
+ {},
+ expect.objectContaining({
+ headers: expect.objectContaining({
+ Authorization: 'mock-token',
+ }),
+ })
+ );
+ expect(result).toEqual(mockResponse);
+ });
+
+ it('should throw error on failed logout', async () => {
+ const mockError: Unsuccessful = {
+ data: null,
+ status: 'error',
+ message: 'Logout failed',
+ error: {
+ code: 'LOGOUT_ERROR',
+ details: 'Unable to logout',
+ message: 'Logout operation failed',
+ }
+ };
+
+ mockedAxios.post.mockRejectedValueOnce({
+ isAxiosError: true,
+ response: { data: mockError }
+ });
+
+ await expect(logout()).rejects.toEqual(
+ expect.objectContaining({
+ isAxiosError: true,
+ response: { data: mockError }
+ })
+ );
+ });
+
+ it('should throw error on network failure', async () => {
+ const networkError = new Error('Network error');
+ mockedAxios.post.mockRejectedValueOnce(networkError);
+
+ await expect(logout()).rejects.toThrow('Network error');
+ });
+ });
+});
diff --git a/frontend/occupi-mobile4/services/__tests__/securestore-test.tsx b/frontend/occupi-mobile4/services/__tests__/securestore-test.tsx
new file mode 100644
index 00000000..be6a6a6e
--- /dev/null
+++ b/frontend/occupi-mobile4/services/__tests__/securestore-test.tsx
@@ -0,0 +1,83 @@
+import * as SecureStore from 'expo-secure-store';
+import {
+ storeUserData,
+ storeToken,
+ getUserData,
+ getToken,
+ deleteUserData,
+ deleteAllData,
+ } from '../securestore';
+
+ jest.mock('expo-secure-store');
+
+const mockedSecureStore = SecureStore as jest.Mocked;
+
+ describe('SecureStore', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('storeUserData', () => {
+ it('should store user data', async () => {
+ const userData = JSON.stringify({ id: 1, name: 'John Doe' });
+ await storeUserData(userData);
+ expect(mockedSecureStore.setItemAsync).toHaveBeenCalledWith('UserData', userData);
+ });
+ });
+
+ describe('storeToken', () => {
+ it('should store token', async () => {
+ const token = 'abc123';
+ await storeToken(token);
+ expect(mockedSecureStore.setItemAsync).toHaveBeenCalledWith('Token', token);
+ });
+ });
+
+ describe('getUserData', () => {
+ it('should return parsed user data when it exists', async () => {
+ const userData = { id: 1, name: 'John Doe' };
+ mockedSecureStore.getItemAsync.mockResolvedValue(JSON.stringify(userData));
+ const result = await getUserData();
+ expect(result).toEqual(userData);
+ });
+
+ it('should return null when token does not exist', async () => {
+ mockedSecureStore.getItemAsync.mockResolvedValue(null);
+ const result = await getUserData(); // Corrected this line
+ expect(result).toBeNull();
+ });
+ });
+
+ describe('getToken', () => {
+ it('should return token when it exists', async () => {
+ const token = 'abc123';
+ mockedSecureStore.getItemAsync.mockResolvedValue(token);
+ const result = await getToken();
+ expect(result).toBe(token);
+ });
+
+ it('should return undefined when token does not exist', async () => {
+ mockedSecureStore.getItemAsync.mockResolvedValue(null);
+ const result = await getToken();
+ expect(result).toBeUndefined();
+ });
+ });
+
+ describe('deleteUserData', () => {
+ it('should delete user data', async () => {
+ await deleteUserData();
+ expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('UserData');
+ });
+ });
+
+ describe('deleteAllData', () => {
+ it('should delete all data', async () => {
+ await deleteAllData();
+ expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('UserData');
+ expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('Token');
+ expect(mockedSecureStore.deleteItemAsync).toHaveBeenCalledWith('Email');
+ });
+ });
+
+
+ });
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/services/apiservices.ts b/frontend/occupi-mobile4/services/apiservices.ts
new file mode 100644
index 00000000..dea22a74
--- /dev/null
+++ b/frontend/occupi-mobile4/services/apiservices.ts
@@ -0,0 +1,376 @@
+import { Success, Unsuccessful } from "@/models/response";
+import { SecuritySettingsReq, NotificationSettingsReq, CheckInReq, CancelBookingReq, BookRoomReq, NotificationsReq, UpdateDetailsReq } from "@/models/requests";
+// import axios from 'axios';
+import * as SecureStore from 'expo-secure-store';
+import axios, { AxiosError } from 'axios';
+import { storeUserData } from "./securestore";
+
+export const getUserDetails = async (email: string, authToken: string): Promise => {
+ try {
+ const response = await axios.get("https://dev.occupi.tech/api/user-details", {
+ params: { email },
+ headers: { Authorization: authToken },
+ });
+ return response.data as Success;
+ } catch (error) {
+ console.error(`Error in getUserDetails:`, error);
+ if (axios.isAxiosError(error)) {
+ const axiosError = error as AxiosError;
+ if (axiosError.response?.data) {
+ return axiosError.response.data;
+ }
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ };
+ }
+};
+
+export async function getNotificationSettings(email: string): Promise {
+ let authToken = await SecureStore.getItemAsync('Token');
+ // console.log(authToken);
+ try {
+ const response = await axios.get(`https://dev.occupi.tech/api/get-notification-settings`, {
+ params: {
+ email: email
+ },
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': `${authToken}`
+ },
+ withCredentials: true
+ });
+ // console.log(response.data);
+ return response.data as Success;
+ } catch (error) {
+ console.error(`Error in ${Function}:`, error);
+ if (axios.isAxiosError(error) && error.response?.data) {
+ return error.response.data as Unsuccessful;
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ } as Unsuccessful;
+ }
+}
+
+export const getUserBookings = async (email: string): Promise => {
+ try {
+ const authToken = await SecureStore.getItemAsync("Token");
+ if (!authToken) {
+ return {
+ data: null,
+ status: 'error',
+ message: 'Authentication failed',
+ error: {
+ code: 'AUTH_ERROR',
+ details: 'No authentication token found',
+ message: 'Authentication failed'
+ }
+ };
+ }
+
+ const response = await axios.get(
+ `https://dev.occupi.tech/api/view-bookings?filter={"email":"${email}"}`,
+ {
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ Authorization: authToken,
+ },
+ withCredentials: true,
+ }
+ );
+ // console.log(response.data);
+ return response.data;
+ } catch (error) {
+ console.error("Error in getUserBookings:", error);
+ if (axios.isAxiosError(error) && error.response) {
+ return error.response.data;
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ } as Unsuccessful;
+ }
+};
+
+export async function getNotifications(req: NotificationsReq): Promise {
+ let authToken = await SecureStore.getItemAsync('Token');
+ try {
+ const response = await axios.get("https://dev.occupi.tech/api/get-notifications", {
+ params: req,
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': authToken
+ },
+ withCredentials: true
+ });
+ return response.data as Success;
+ } catch (error) {
+ console.error(`Error in ${Function}:`, error);
+ if (axios.isAxiosError(error) && error.response?.data) {
+ return error.response.data as Unsuccessful;
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ } as Unsuccessful;
+ }
+}
+
+export async function checkin(req: CheckInReq): Promise {
+ let authToken = await SecureStore.getItemAsync('Token');
+ console.log(req);
+ try {
+ const response = await axios.post("https://dev.occupi.tech/api/check-in", req, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': authToken
+ },
+ withCredentials: true
+ });
+ return response.data as Success;
+ } catch (error) {
+ console.error(`Error in ${Function}:`, error);
+ if (axios.isAxiosError(error) && error.response?.data) {
+ console.log(error.response.data)
+ return error.response.data as Unsuccessful;
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ } as Unsuccessful;
+ }
+}
+
+export async function updateUserDetails(req: UpdateDetailsReq): Promise {
+ let authToken = await SecureStore.getItemAsync('Token');
+ console.log(req);
+ try {
+ const response = await axios.post("https://dev.occupi.tech/api/update-user", req, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': authToken
+ },
+ withCredentials: true
+ });
+ storeUserData(JSON.stringify(req));
+ return response.data as Success;
+ } catch (error) {
+ console.error(`Error in ${Function}:`, error);
+ if (axios.isAxiosError(error) && error.response?.data) {
+ console.log(error.response.data)
+ return error.response.data as Unsuccessful;
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ } as Unsuccessful;
+ }
+}
+
+export async function bookRoom(req: BookRoomReq): Promise {
+ let authToken = await SecureStore.getItemAsync('Token');
+ console.log(req);
+ try {
+ const response = await axios.post("https://dev.occupi.tech/api/book-room", req, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': authToken
+ },
+ withCredentials: true
+ });
+ return response.data as Success;
+ } catch (error) {
+ console.error(`Error in ${Function}:`, error);
+ if (axios.isAxiosError(error) && error.response?.data) {
+ console.log(error.response.data)
+ return error.response.data as Unsuccessful;
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ } as Unsuccessful;
+ }
+}
+
+export async function cancelBooking(req: CancelBookingReq): Promise {
+ let authToken = await SecureStore.getItemAsync('Token');
+ console.log(req);
+ try {
+ const response = await axios.post("https://dev.occupi.tech/api/cancel-booking", req, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': authToken
+ },
+ withCredentials: true
+ });
+ return response.data as Success;
+ } catch (error) {
+ console.error(`Error in ${Function}:`, error);
+ if (axios.isAxiosError(error) && error.response?.data) {
+ console.log(error.response.data)
+ return error.response.data as Unsuccessful;
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ } as Unsuccessful;
+ }
+}
+
+export async function getSecuritySettings(email: string): Promise {
+ let authToken = await SecureStore.getItemAsync('Token');
+ // console.log(authToken);
+ try {
+ const response = await axios.get(`https://dev.occupi.tech/api/get-security-settings`, {
+ params: {
+ email: email
+ },
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': `${authToken}`
+ },
+ withCredentials: true
+ });
+ // console.log(response.data);
+ return response.data as Success;
+ } catch (error) {
+ console.error(`Error in ${Function}:`, error);
+ if (axios.isAxiosError(error) && error.response?.data) {
+ return error.response.data as Unsuccessful;
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ } as Unsuccessful;
+ }
+}
+
+export async function updateSecuritySettings(req: SecuritySettingsReq): Promise {
+ let authToken = await SecureStore.getItemAsync('Token');
+ try {
+ const response = await axios.post("https://dev.occupi.tech/api/update-security-settings", req, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': authToken
+ },
+ withCredentials: true
+ });
+ // console.log(response.data);
+ return response.data as Success;
+ } catch (error) {
+ console.error(`Error in ${Function}:`, error);
+ if (axios.isAxiosError(error) && error.response?.data) {
+ return error.response.data as Unsuccessful;
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ } as Unsuccessful;
+ }
+}
+
+export async function updateNotificationSettings(req: NotificationSettingsReq): Promise {
+ let authToken = await SecureStore.getItemAsync('Token');
+ try {
+ const response = await axios.get("https://dev.occupi.tech/api/update-notification-settings", {
+ params: {
+ req
+ },
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': authToken
+ },
+ withCredentials: true
+ });
+ // console.log(response.data);
+ return response.data as Success;
+ } catch (error) {
+ console.error(`Error in ${Function}:`, error);
+ if (axios.isAxiosError(error) && error.response?.data) {
+ return error.response.data as Unsuccessful;
+ }
+ return {
+ data: null,
+ status: 'error',
+ message: 'An unexpected error occurred',
+ error: {
+ code: 'UNKNOWN_ERROR',
+ details: 'An unexpected error occurred',
+ message: 'An unexpected error occurred'
+ }
+ } as Unsuccessful;
+ }
+}
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/services/authservices.ts b/frontend/occupi-mobile4/services/authservices.ts
new file mode 100644
index 00000000..06ed7ed6
--- /dev/null
+++ b/frontend/occupi-mobile4/services/authservices.ts
@@ -0,0 +1,123 @@
+import { LoginReq, RegisterReq, VerifyOTPReq } from "@/models/requests";
+import { LoginSuccess, Unsuccessful, Success } from "@/models/response";
+import axios from 'axios';
+import dotenv from 'dotenv';
+import * as SecureStore from 'expo-secure-store';
+
+// dotenv.config();
+// const devUrl = process.env.EXPO_PUBLIC_DEVELOP_API_URL;
+// console.log(devUrl);
+
+export async function login(req: LoginReq): Promise {
+ try {
+ const response = await axios.post("https://dev.occupi.tech/auth/login-mobile", req, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ withCredentials: true
+ });
+ // console.log(response.data);
+ return response.data as LoginSuccess;
+ } catch (error) {
+ if (axios.isAxiosError(error) && error.response) {
+ // console.log(error.response.data);
+ return error.response.data as Unsuccessful;
+ } else {
+ throw error;
+ }
+ }
+}
+
+export async function register(req: RegisterReq): Promise {
+ console.log(req);
+ try {
+ const response = await axios.post("https://dev.occupi.tech/auth/register", req, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ withCredentials: true
+ });
+ console.log(response.data);
+ return response.data as Success;
+ } catch (error) {
+ if (axios.isAxiosError(error) && error.response) {
+ console.log(error.response.data);
+ return error.response.data as Unsuccessful;
+ } else {
+ throw error;
+ }
+ }
+}
+
+export async function verifyOtpRegister(req: VerifyOTPReq): Promise {
+ try {
+ const response = await axios.post("https://dev.occupi.tech/auth/verify-otp", req, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ withCredentials: true
+ });
+ // console.log(response.data);
+ return response.data as LoginSuccess;
+ } catch (error) {
+ if (axios.isAxiosError(error) && error.response) {
+ // console.log(error.response.data);
+ return error.response.data as Unsuccessful;
+ } else {
+ throw error;
+ }
+ }
+}
+
+export async function verifyOtplogin(req: VerifyOTPReq): Promise {
+ try {
+ const response = await axios.post("https://dev.occupi.tech/auth/verify-otp-mobile-login", req, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ withCredentials: true
+ });
+ // console.log(response.data);
+ return response.data as LoginSuccess;
+ } catch (error) {
+ if (axios.isAxiosError(error) && error.response) {
+ // console.log(error.response.data);
+ return error.response.data as Unsuccessful;
+ } else {
+ throw error;
+ }
+ }
+}
+
+export async function logout(): Promise {
+ let authToken = await SecureStore.getItemAsync('Token');
+ // console.log('token',authToken);
+ try {
+ const response = await axios.post("https://dev.occupi.tech/auth/logout", {},{
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': `${authToken}`
+ },
+ withCredentials: true
+ });
+ // console.log(response.data);
+ return response.data as Success;
+ } catch (error) {
+ if (axios.isAxiosError(error) && error.response) {
+ // console.log(error.response.data);
+ return error.response.data as Unsuccessful;
+ } else {
+ throw error;
+ }
+ }
+}
+
+// login({
+// email: "boygenius31115@gmail.com",
+// password: "Qwert@123"
+// })
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/services/securestore.ts b/frontend/occupi-mobile4/services/securestore.ts
new file mode 100644
index 00000000..2f34575c
--- /dev/null
+++ b/frontend/occupi-mobile4/services/securestore.ts
@@ -0,0 +1,82 @@
+import * as SecureStore from 'expo-secure-store';
+import { User } from '@/models/data';
+
+export async function storeUserData(value: string) {
+ await SecureStore.setItemAsync('UserData', value);
+}
+
+export async function storeToken(value: string) {
+ await SecureStore.setItemAsync('Token', value);
+}
+
+export async function storeUserEmail(value: string) {
+ await SecureStore.setItemAsync('Email', value);
+}
+
+export async function setState(value: string) {
+ await SecureStore.setItemAsync('AppState', value);
+}
+
+export async function storeNotificationSettings(value: string) {
+ await SecureStore.setItemAsync('Notifications', value);
+}
+
+export async function storeTheme(value: string) {
+ await SecureStore.setItemAsync('Theme', value);
+}
+
+export async function storeAccentColour(value: string) {
+ await SecureStore.setItemAsync('accentColour', value);
+}
+
+export async function storeSecuritySettings(value: string) {
+ await SecureStore.setItemAsync('Security', value);
+}
+
+export async function getUserData() {
+ let result: string | null = await SecureStore.getItemAsync('UserData');
+ return result ? JSON.parse(result) : null;
+}
+
+export async function getToken() {
+ let result = await SecureStore.getItemAsync('Token');
+ const tokenVal = result;
+ // console.log('token', result);
+ return tokenVal || undefined;
+}
+
+export async function getUserEmail() {
+ let result = await SecureStore.getItemAsync('Email');
+ return result;
+}
+
+export async function getCurrentRoom() {
+ let result = await SecureStore.getItemAsync('CurrentRoom');
+ return result;
+}
+
+export async function deleteUserData() {
+ await SecureStore.deleteItemAsync('UserData');
+}
+
+export async function deleteToken() {
+ await SecureStore.deleteItemAsync('Token');
+}
+
+export async function deleteUserEmail() {
+ await SecureStore.deleteItemAsync('Email');
+}
+
+export async function deleteNotificationSettings() {
+ await SecureStore.deleteItemAsync('Notifications');
+}
+
+export async function deleteSecuritySettings() {
+ await SecureStore.deleteItemAsync('Security');
+}
+
+export async function deleteAllData() {
+ await SecureStore.deleteItemAsync('UserData');
+ await SecureStore.deleteItemAsync('Token');
+ await SecureStore.deleteItemAsync('Email');
+}
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/utils/__tests__/auth-test.tsx b/frontend/occupi-mobile4/utils/__tests__/auth-test.tsx
new file mode 100644
index 00000000..0e3721c5
--- /dev/null
+++ b/frontend/occupi-mobile4/utils/__tests__/auth-test.tsx
@@ -0,0 +1,134 @@
+import { UserLogin, UserLogout } from '../auth';
+import { login, logout } from "../../services/authservices";
+import { fetchNotificationSettings, fetchSecuritySettings, fetchUserDetails } from "../user";
+import { router } from 'expo-router';
+import { storeUserEmail, storeToken, setState, deleteToken, deleteUserData, deleteUserEmail, deleteNotificationSettings, deleteSecuritySettings } from "../../services/securestore";
+
+// Mock dependencies
+jest.mock('../../services/authservices');
+jest.mock('../user');
+jest.mock('expo-router', () => ({
+ router: {
+ replace: jest.fn(),
+ },
+}));
+jest.mock('../../services/securestore');
+
+describe('auth.ts', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('UserLogin', () => {
+ it('should login successfully and set up user data', async () => {
+ const mockEmail = 'test@example.com';
+ const mockPassword = 'password123';
+ const mockToken = 'mock-token';
+
+ (login as jest.Mock).mockResolvedValue({
+ status: 200,
+ data: { token: mockToken },
+ message: 'Login successful'
+ });
+
+ const result = await UserLogin(mockEmail, mockPassword);
+
+ expect(storeUserEmail).toHaveBeenCalledWith(mockEmail);
+ expect(login).toHaveBeenCalledWith({ email: mockEmail, password: mockPassword });
+ expect(setState).toHaveBeenCalledWith('logged_in');
+ expect(storeToken).toHaveBeenCalledWith(mockToken);
+ expect(fetchUserDetails).toHaveBeenCalledWith(mockEmail, mockToken);
+ expect(fetchNotificationSettings).toHaveBeenCalledWith(mockEmail);
+ expect(fetchSecuritySettings).toHaveBeenCalledWith(mockEmail);
+ expect(router.replace).toHaveBeenCalledWith('/home');
+ expect(result).toBe('Login successful');
+ });
+
+ it('should handle login failure', async () => {
+ const mockEmail = 'test@example.com';
+ const mockPassword = 'wrong-password';
+
+ (login as jest.Mock).mockResolvedValue({
+ status: 401,
+ message: 'Invalid credentials'
+ });
+
+ const result = await UserLogin(mockEmail, mockPassword);
+
+ expect(storeUserEmail).toHaveBeenCalledWith(mockEmail);
+ expect(login).toHaveBeenCalledWith({ email: mockEmail, password: mockPassword });
+ expect(setState).not.toHaveBeenCalled();
+ expect(storeToken).not.toHaveBeenCalled();
+ expect(fetchUserDetails).not.toHaveBeenCalled();
+ expect(fetchNotificationSettings).not.toHaveBeenCalled();
+ expect(fetchSecuritySettings).not.toHaveBeenCalled();
+ expect(router.replace).not.toHaveBeenCalled();
+ expect(result).toBe('Invalid credentials');
+ });
+
+ it('should handle errors during login', async () => {
+ const mockEmail = 'test@example.com';
+ const mockPassword = 'password123';
+
+ (login as jest.Mock).mockRejectedValue(new Error('Network error'));
+
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+
+ await UserLogin(mockEmail, mockPassword);
+
+ expect(consoleSpy).toHaveBeenCalledWith('Error:', expect.any(Error));
+ consoleSpy.mockRestore();
+ });
+ });
+
+ describe('UserLogout', () => {
+ it('should logout successfully and clear user data', async () => {
+ (logout as jest.Mock).mockResolvedValue({
+ status: 200,
+ message: 'Logout successful'
+ });
+
+ const result = await UserLogout();
+
+ expect(logout).toHaveBeenCalled();
+ expect(setState).toHaveBeenCalledWith('logged_out');
+ expect(deleteNotificationSettings).toHaveBeenCalled();
+ expect(deleteSecuritySettings).toHaveBeenCalled();
+ expect(deleteUserData).toHaveBeenCalled();
+ expect(deleteToken).toHaveBeenCalled();
+ expect(deleteUserEmail).toHaveBeenCalled();
+ expect(router.replace).toHaveBeenCalledWith('/login');
+ expect(result).toBe('Logout successful');
+ });
+
+ it('should handle logout failure', async () => {
+ (logout as jest.Mock).mockResolvedValue({
+ status: 400,
+ message: 'Logout failed'
+ });
+
+ const result = await UserLogout();
+
+ expect(logout).toHaveBeenCalled();
+ expect(setState).not.toHaveBeenCalled();
+ expect(deleteNotificationSettings).not.toHaveBeenCalled();
+ expect(deleteSecuritySettings).not.toHaveBeenCalled();
+ expect(deleteUserData).not.toHaveBeenCalled();
+ expect(deleteToken).not.toHaveBeenCalled();
+ expect(deleteUserEmail).not.toHaveBeenCalled();
+ expect(router.replace).not.toHaveBeenCalled();
+ expect(result).toBe('Logout failed');
+ });
+
+ it('should handle errors during logout', async () => {
+ (logout as jest.Mock).mockRejectedValue(new Error('Network error'));
+
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+
+ await UserLogout();
+
+ expect(consoleSpy).toHaveBeenCalledWith('Error:', expect.any(Error));
+ consoleSpy.mockRestore();
+ });
+ });
+});
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/utils/__tests__/bookings-test.tsx b/frontend/occupi-mobile4/utils/__tests__/bookings-test.tsx
new file mode 100644
index 00000000..b59f3e88
--- /dev/null
+++ b/frontend/occupi-mobile4/utils/__tests__/bookings-test.tsx
@@ -0,0 +1,125 @@
+import { fetchUserBookings, userCheckin, userCancelBooking } from '../bookings';
+import { getUserBookings, checkin, cancelBooking } from '../../services/apiservices';
+import * as SecureStore from 'expo-secure-store';
+import { router } from 'expo-router';
+
+// Mock dependencies
+jest.mock('../../services/apiservices');
+jest.mock('expo-secure-store');
+jest.mock('expo-router', () => ({
+ router: {
+ replace: jest.fn(),
+ },
+}));
+
+beforeEach(() => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+});
+
+afterEach(() => {
+ jest.restoreAllMocks();
+});
+
+describe('../bookings.ts', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('fetchUserBookings', () => {
+ it('should fetch user bookings successfully', async () => {
+ const mockEmail = 'test@example.com';
+ const mockBookings = [{ id: 1, title: 'Booking 1' }, { id: 2, title: 'Booking 2' }];
+
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(mockEmail);
+ (getUserBookings as jest.Mock).mockResolvedValue({ status: 200, data: mockBookings });
+
+ const result = await fetchUserBookings();
+
+ expect(SecureStore.getItemAsync).toHaveBeenCalledWith('Email');
+ expect(getUserBookings).toHaveBeenCalledWith(mockEmail);
+ expect(result).toEqual(mockBookings);
+ });
+
+ it('should handle errors when fetching user bookings', async () => {
+ const mockError = new Error('API Error');
+
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue('test@example.com');
+ (getUserBookings as jest.Mock).mockRejectedValue(mockError);
+
+ await expect(fetchUserBookings()).rejects.toThrow('API Error');
+ });
+ });
+
+ describe('userCheckin', () => {
+ it('should perform user check-in successfully', async () => {
+ const mockRoom = { occupiId: 'room123' };
+ const mockEmail = 'test@example.com';
+
+ (SecureStore.getItemAsync as jest.Mock)
+ .mockResolvedValueOnce(JSON.stringify(mockRoom))
+ .mockResolvedValueOnce(mockEmail);
+ (checkin as jest.Mock).mockResolvedValue({ status: 200, message: 'Check-in successful' });
+
+ const result = await userCheckin();
+
+ expect(SecureStore.getItemAsync).toHaveBeenCalledWith('CurrentRoom');
+ expect(SecureStore.getItemAsync).toHaveBeenCalledWith('Email');
+ expect(checkin).toHaveBeenCalledWith({ email: mockEmail, bookingId: 'room123' });
+ expect(result).toBe('Check-in successful');
+ });
+
+ it('should handle errors during user check-in', async () => {
+ const mockError = new Error('Check-in Error');
+
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue('{}');
+ (checkin as jest.Mock).mockRejectedValue(mockError);
+
+ await expect(userCheckin()).rejects.toThrow('Check-in Error');
+ });
+ });
+
+ describe('userCancelBooking', () => {
+ it('should cancel user booking successfully', async () => {
+ const mockRoom = {
+ occupiId: 'booking123',
+ emails: ['user1@example.com', 'user2@example.com'],
+ roomId: 'room123',
+ creator: 'user1@example.com',
+ date: '2023-07-30',
+ start: '10:00',
+ end: '11:00',
+ floorNo: 1,
+ roomName: 'Meeting Room A'
+ };
+
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(JSON.stringify(mockRoom));
+ (cancelBooking as jest.Mock).mockResolvedValue({ status: 200, message: 'Booking cancelled successfully' });
+
+ const result = await userCancelBooking();
+
+ expect(SecureStore.getItemAsync).toHaveBeenCalledWith('CurrentRoom');
+ expect(cancelBooking).toHaveBeenCalledWith(expect.objectContaining({
+ bookingId: 'booking123',
+ emails: ['user1@example.com', 'user2@example.com'],
+ roomId: 'room123',
+ creator: 'user1@example.com',
+ date: '2023-07-30',
+ start: '10:00',
+ end: '11:00',
+ floorNo: 1,
+ roomName: 'Meeting Room A'
+ }));
+ expect(router.replace).toHaveBeenCalledWith('/home');
+ expect(result).toBe('Booking cancelled successfully');
+ });
+
+ it('should handle errors during booking cancellation', async () => {
+ const mockError = new Error('Cancellation Error');
+
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue('{}');
+ (cancelBooking as jest.Mock).mockRejectedValue(mockError);
+
+ await expect(userCancelBooking()).rejects.toThrow('Cancellation Error');
+ });
+ });
+});
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/utils/__tests__/notifications-test.tsx b/frontend/occupi-mobile4/utils/__tests__/notifications-test.tsx
new file mode 100644
index 00000000..af726b8f
--- /dev/null
+++ b/frontend/occupi-mobile4/utils/__tests__/notifications-test.tsx
@@ -0,0 +1,90 @@
+import * as Notifications from 'expo-notifications';
+import { sendPushNotification } from '../notifications'; // Adjust the import path as needed
+
+jest.mock('expo-notifications');
+jest.mock('node-fetch');
+
+describe('Notification Functions', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('setNotificationHandler sets the correct handler', () => {
+ // Manually call setNotificationHandler to ensure it's executed
+ Notifications.setNotificationHandler({
+ handleNotification: async () => ({
+ shouldShowAlert: true,
+ shouldPlaySound: false,
+ shouldSetBadge: false,
+ }),
+ });
+
+ expect(Notifications.setNotificationHandler).toHaveBeenCalledWith(expect.objectContaining({
+ handleNotification: expect.any(Function),
+ }));
+ });
+
+ test('handleNotification returns correct configuration', async () => {
+ // Manually call setNotificationHandler to ensure it's executed
+ Notifications.setNotificationHandler({
+ handleNotification: async () => ({
+ shouldShowAlert: true,
+ shouldPlaySound: false,
+ shouldSetBadge: false,
+ }),
+ });
+
+ const handler = (Notifications.setNotificationHandler as jest.Mock).mock.calls[0][0];
+ const result = await handler.handleNotification();
+ expect(result).toEqual({
+ shouldShowAlert: true,
+ shouldPlaySound: false,
+ shouldSetBadge: false,
+ });
+ });
+
+ test('sendPushNotification sends notifications to all tokens', async () => {
+ const mockFetch = jest.fn().mockResolvedValue({
+ ok: true,
+ json: async () => ({ data: 'success' }),
+ });
+ global.fetch = mockFetch as any;
+
+ const expoPushTokens = ['token1', 'token2', 'token3'];
+ const title = 'Test Title';
+ const body = 'Test Body';
+
+ await sendPushNotification(expoPushTokens, title, body);
+
+ expect(mockFetch).toHaveBeenCalledTimes(3);
+
+ expoPushTokens.forEach((token, index) => {
+ expect(mockFetch).toHaveBeenNthCalledWith(index + 1, 'https://exp.host/--/api/v2/push/send', {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Accept-encoding': 'gzip, deflate',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ to: token,
+ sound: 'default',
+ title: title,
+ body: body,
+ data: { someData: 'goes here' },
+ }),
+ });
+ });
+ });
+
+ test('sendPushNotification handles errors', async () => {
+ const mockFetch = jest.fn().mockRejectedValue(new Error('Network error'));
+ global.fetch = mockFetch as any;
+
+ const expoPushTokens = ['token1'];
+ const title = 'Test Title';
+ const body = 'Test Body';
+
+ await expect(sendPushNotification(expoPushTokens, title, body)).rejects.toThrow('Network error');
+ });
+});
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/utils/__tests__/user-test.tsx b/frontend/occupi-mobile4/utils/__tests__/user-test.tsx
new file mode 100644
index 00000000..8c4d81ab
--- /dev/null
+++ b/frontend/occupi-mobile4/utils/__tests__/user-test.tsx
@@ -0,0 +1,127 @@
+import * as SecureStore from 'expo-secure-store';
+import {
+ storeUserData,
+ storeToken,
+ storeUserEmail,
+ setState,
+ storeNotificationSettings,
+ storeSecuritySettings,
+ getUserData,
+ getToken,
+ getUserEmail,
+ getCurrentRoom,
+ deleteUserData,
+ deleteToken,
+ deleteUserEmail,
+ deleteNotificationSettings,
+ deleteSecuritySettings,
+ deleteAllData
+} from '../../services/securestore';
+
+jest.mock('expo-secure-store');
+
+describe('Secure Store Functions', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('storeUserData stores user data', async () => {
+ const userData = JSON.stringify({ name: 'John Doe', email: 'john@example.com' });
+ await storeUserData(userData);
+ expect(SecureStore.setItemAsync).toHaveBeenCalledWith('UserData', userData);
+ });
+
+ test('storeToken stores token', async () => {
+ const token = 'abc123';
+ await storeToken(token);
+ expect(SecureStore.setItemAsync).toHaveBeenCalledWith('Token', token);
+ });
+
+ test('storeUserEmail stores email', async () => {
+ const email = 'john@example.com';
+ await storeUserEmail(email);
+ expect(SecureStore.setItemAsync).toHaveBeenCalledWith('Email', email);
+ });
+
+ test('setState stores app state', async () => {
+ const state = 'active';
+ await setState(state);
+ expect(SecureStore.setItemAsync).toHaveBeenCalledWith('AppState', state);
+ });
+
+ test('storeNotificationSettings stores notification settings', async () => {
+ const settings = JSON.stringify({ pushEnabled: true });
+ await storeNotificationSettings(settings);
+ expect(SecureStore.setItemAsync).toHaveBeenCalledWith('Notifications', settings);
+ });
+
+ test('storeSecuritySettings stores security settings', async () => {
+ const settings = JSON.stringify({ twoFactor: true });
+ await storeSecuritySettings(settings);
+ expect(SecureStore.setItemAsync).toHaveBeenCalledWith('Security', settings);
+ });
+
+ test('getUserData retrieves user data', async () => {
+ const userData = { name: 'John Doe', email: 'john@example.com' };
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(JSON.stringify(userData));
+ const result = await getUserData();
+ expect(result).toEqual(userData);
+ expect(SecureStore.getItemAsync).toHaveBeenCalledWith('UserData');
+ });
+
+ test('getToken retrieves token', async () => {
+ const token = 'abc123';
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(token);
+ const result = await getToken();
+ expect(result).toBe(token);
+ expect(SecureStore.getItemAsync).toHaveBeenCalledWith('Token');
+ });
+
+ test('getUserEmail retrieves email', async () => {
+ const email = 'john@example.com';
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(email);
+ const result = await getUserEmail();
+ expect(result).toBe(email);
+ expect(SecureStore.getItemAsync).toHaveBeenCalledWith('Email');
+ });
+
+ test('getCurrentRoom retrieves current room', async () => {
+ const room = 'living-room';
+ (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(room);
+ const result = await getCurrentRoom();
+ expect(result).toBe(room);
+ expect(SecureStore.getItemAsync).toHaveBeenCalledWith('CurrentRoom');
+ });
+
+ test('deleteUserData deletes user data', async () => {
+ await deleteUserData();
+ expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('UserData');
+ });
+
+ test('deleteToken deletes token', async () => {
+ await deleteToken();
+ expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Token');
+ });
+
+ test('deleteUserEmail deletes email', async () => {
+ await deleteUserEmail();
+ expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Email');
+ });
+
+ test('deleteNotificationSettings deletes notification settings', async () => {
+ await deleteNotificationSettings();
+ expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Notifications');
+ });
+
+ test('deleteSecuritySettings deletes security settings', async () => {
+ await deleteSecuritySettings();
+ expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Security');
+ });
+
+ test('deleteAllData deletes all data', async () => {
+ await deleteAllData();
+ expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('UserData');
+ expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Token');
+ expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('Email');
+ });
+});
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/utils/auth.ts b/frontend/occupi-mobile4/utils/auth.ts
new file mode 100644
index 00000000..603a04ae
--- /dev/null
+++ b/frontend/occupi-mobile4/utils/auth.ts
@@ -0,0 +1,144 @@
+//this folder contains functions that will call the service functions which make api requests for authentication
+//the purpose of this file is to refine and process the data and return these to the View
+
+import { login, logout, register, verifyOtplogin, verifyOtpRegister } from "../services/authservices";
+import { fetchNotificationSettings, fetchSecuritySettings, fetchUserDetails } from "./user";
+import { router } from 'expo-router';
+import { storeUserEmail, storeToken, setState, deleteToken, deleteUserData, deleteUserEmail, deleteNotificationSettings, deleteSecuritySettings } from "../services/securestore";
+import { retrievePushToken } from "./notifications";
+
+
+export async function UserLogin(email: string, password: string) {
+ storeUserEmail(email);
+ try {
+ const response = await login({
+ email: email,
+ password: password
+ });
+ if (response.status === 200) {
+ console.log('responseee',response);
+ if (response.data !== null) {
+ setState('logged_in');
+ storeToken(response.data.token);
+ console.log('here');
+ fetchUserDetails(email, response.data.token);
+ fetchNotificationSettings(email);
+ fetchSecuritySettings(email);
+ router.replace('/home');
+ }
+ else {
+ setState('verify_otp_login');
+ router.replace('verify-otp')
+ }
+
+ return response.message;
+ }
+ else {
+ console.log('woahhh', response)
+ return response.message;
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+export async function userRegister(email: string, password: string, employeeId: string) {
+ let expoPushToken = await retrievePushToken();
+ storeUserEmail(email);
+ try {
+ const response = await register({
+ email: email,
+ password: password,
+ // employee_id: employeeId,
+ expoPushToken: expoPushToken
+ });
+ if (response.status === 200) {
+ console.log('responseee',response);
+ setState('verify_otp_register');
+ router.replace('/verify-otp');
+ return response.message;
+ }
+ else {
+ console.log('woahhh', response)
+ return response.message;
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+export async function verifyUserOtpRegister(email: string, otp: string) {
+ try {
+ const response = await verifyOtpRegister({
+ email: email,
+ otp: otp
+ });
+ if (response.status === 200) {
+ console.log('responseee',response);
+ router.replace('/set-details');
+ router.replace('/login');
+ return response.message;
+ }
+ else {
+ console.log('woahhh', response)
+ return response.message;
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+export async function VerifyUserOtpLogin(email : string, otp : string) {
+ try {
+ const response = await verifyOtplogin({
+ email: email,
+ otp: otp
+ });
+ if (response.status === 200) {
+ console.log('responseee',response);
+ if (response.data !== null) {
+ setState('logged_in');
+ storeToken(response.data.token);
+ console.log('here');
+ fetchUserDetails(email, response.data.token);
+ fetchNotificationSettings(email);
+ fetchSecuritySettings(email);
+ router.replace('/home');
+ }
+
+ return response.message;
+ }
+ else {
+ console.log('woahhh', response)
+ return response.message;
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+export async function UserLogout() {
+ // console.log('hhhh');
+ try {
+ const response = await logout();
+ if (response.status === 200) {
+ // console.log('responseee',response);
+ setState('logged_out');
+ deleteNotificationSettings();
+ deleteSecuritySettings();
+ deleteUserData();
+ deleteToken();
+ deleteUserEmail();
+ router.replace('/login');
+ return response.message;
+ }
+ else {
+ console.log('woahhh', response)
+ return response.message;
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+// UserLogin("kamogelomoeketse@gmail.com", "Qwerty@123"); //test
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/utils/bookings.ts b/frontend/occupi-mobile4/utils/bookings.ts
new file mode 100644
index 00000000..91cfede8
--- /dev/null
+++ b/frontend/occupi-mobile4/utils/bookings.ts
@@ -0,0 +1,101 @@
+import { Booking, Room } from "@/models/data";
+import { bookRoom, cancelBooking, checkin, getUserBookings } from "../services/apiservices";
+import * as SecureStore from 'expo-secure-store';
+import { router } from 'expo-router';
+import { BookRoomReq, CancelBookingReq } from "@/models/requests";
+
+export async function fetchUserBookings(): Promise {
+ let email = await SecureStore.getItemAsync('Email');
+ try {
+ const response = await getUserBookings(email);
+ if (response.status === 200) {
+ // console.log('response', response.data);
+ return response.data;
+ // console.log(settings);
+ }
+ else {
+ console.log(response)
+ }
+ return response.data as Booking[];
+ } catch (error) {
+ console.error('Error:', error);
+ throw error; // Add a throw statement to handle the error case
+ }
+}
+
+export async function userBookRoom(attendees : string[], startTime : string, endTime : string) {
+ let roomstring = await SecureStore.getItemAsync("BookingInfo");
+ let email = await SecureStore.getItemAsync("Email");
+ const room : Booking = JSON.parse(roomstring as string);
+ const body : BookRoomReq = {
+ roomName: room.roomName,
+ creator: email,
+ date: room.date+"T00:00:00.000+00:00",
+ start: room.date+"T"+startTime+":00.000+00:00",
+ end: room.date+"T"+endTime+":00.000+00:00",
+ floorNo: room.floorNo,
+ emails: attendees,
+ roomId: room.roomId
+
+ }
+ console.log(body);
+ try {
+ const response = await bookRoom(body);
+ if (response.status === 200) {
+ return response.message;
+ }
+ return response.message;
+ } catch (error) {
+ console.error('Error:', error);
+ throw error;
+ }
+}
+
+export async function userCheckin() {
+ let roomstring = await SecureStore.getItemAsync("CurrentRoom");
+ const room = JSON.parse(roomstring as string);
+ const bookingId = room?.occupiId;
+ let email = await SecureStore.getItemAsync('Email');
+ const body = {
+ email: email as string,
+ bookingId: bookingId
+ }
+ try {
+ const response = await checkin(body);
+ if (response.status === 200) {
+ return response.message;
+ }
+ return response.message;
+ } catch (error) {
+ console.error('Error:', error);
+ throw error;
+ }
+}
+
+export async function userCancelBooking() {
+ let roomstring = await SecureStore.getItemAsync("CurrentRoom");
+ const room : Booking = JSON.parse(roomstring as string);
+ let email = await SecureStore.getItemAsync('Email');
+ const body : CancelBookingReq = {
+ bookingId: room?.occupiId,
+ emails: room?.emails,
+ roomId: room?.roomId,
+ creator: room.creator,
+ date: room?.date,
+ start: room?.start,
+ end: room?.end,
+ floorNo: room?.floorNo,
+ roomName: room?.roomName
+ }
+ try {
+ const response = await cancelBooking(body);
+ if (response.status === 200) {
+ router.replace('/home');
+ return response.message;
+ }
+ return response.message;
+ } catch (error) {
+ console.error('Error:', error);
+ throw error;
+ }
+}
diff --git a/frontend/occupi-mobile4/utils/dashboard.ts b/frontend/occupi-mobile4/utils/dashboard.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/frontend/occupi-mobile4/utils/notifications.ts b/frontend/occupi-mobile4/utils/notifications.ts
new file mode 100644
index 00000000..f0d1ff7d
--- /dev/null
+++ b/frontend/occupi-mobile4/utils/notifications.ts
@@ -0,0 +1,117 @@
+import * as Device from 'expo-device';
+import { Platform } from 'react-native';
+import * as Notifications from 'expo-notifications';
+import Constants from 'expo-constants';
+import * as SecureStore from 'expo-secure-store';
+import { NotificationsReq } from '@/models/requests';
+import { getNotifications } from '@/services/apiservices';
+
+Notifications.setNotificationHandler({
+ handleNotification: async () => ({
+ shouldShowAlert: true,
+ shouldPlaySound: false,
+ shouldSetBadge: false,
+ }),
+});
+
+export async function retrievePushToken(): Promise {
+ const token = await registerForPushNotificationsAsync();
+ // console.log(token);
+ return token as string;
+}
+
+// retrievePushToken();
+// console.log('yurp');
+
+async function registerForPushNotificationsAsync() {
+ let token;
+
+ if (Platform.OS === 'android') {
+ await Notifications.setNotificationChannelAsync('default', {
+ name: 'default',
+ importance: Notifications.AndroidImportance.MAX,
+ vibrationPattern: [0, 250, 250, 250],
+ lightColor: '#FF231F7C',
+ });
+ }
+
+ if (Device.isDevice) {
+ const { status: existingStatus } = await Notifications.getPermissionsAsync();
+ let finalStatus = existingStatus;
+ if (existingStatus !== 'granted') {
+ const { status } = await Notifications.requestPermissionsAsync();
+ finalStatus = status;
+ }
+ if (finalStatus !== 'granted') {
+ alert('Failed to get push token for push notification!');
+ return;
+ }
+ // Learn more about projectId:
+ // https://docs.expo.dev/push-notifications/push-notifications-setup/#configure-projectid
+ // EAS projectId is used here.
+ try {
+ const projectId =
+ Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId;
+ if (!projectId) {
+ throw new Error('Project ID not found');
+ }
+ token = (
+ await Notifications.getExpoPushTokenAsync({
+ projectId,
+ })
+ ).data;
+ // console.log(token);
+ } catch (e) {
+ token = `${e}`;
+ }
+ } else {
+ alert('Must use physical device for Push Notifications');
+ }
+
+ return token;
+}
+
+ export async function sendPushNotification(expoPushTokens: string[], title: string, body: string) {
+ const messages = expoPushTokens.map(token => ({
+ to: token,
+ sound: 'default',
+ title: title,
+ body: body,
+ data: { someData: 'goes here' },
+ }));
+
+ for (const message of messages) {
+ await fetch('https://exp.host/--/api/v2/push/send', {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Accept-encoding': 'gzip, deflate',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(message),
+ });
+ }
+}
+
+export async function getUserNotifications() {
+ let email = await SecureStore.getItemAsync('Email');
+
+ try {
+ const request : NotificationsReq = {
+ filter: {
+ emails: [email]
+ }
+ };
+ const response = await getNotifications(request);
+ if (response.status === 200) {
+ // console.log('notifications', response.data);
+ return response.data
+ }
+ else {
+ console.log(response)
+ return response.data;
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/utils/occupancy.ts b/frontend/occupi-mobile4/utils/occupancy.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/frontend/occupi-mobile4/utils/settings.ts b/frontend/occupi-mobile4/utils/settings.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/frontend/occupi-mobile4/utils/user.ts b/frontend/occupi-mobile4/utils/user.ts
new file mode 100644
index 00000000..8b3e70b2
--- /dev/null
+++ b/frontend/occupi-mobile4/utils/user.ts
@@ -0,0 +1,182 @@
+import { UpdateDetailsReq } from "@/models/requests";
+import { getUserDetails, getNotificationSettings, getSecuritySettings, updateSecuritySettings, updateNotificationSettings, updateUserDetails } from "../services/apiservices";
+import { storeUserData, storeNotificationSettings, getUserData, storeSecuritySettings, setState } from "../services/securestore";
+import { router } from 'expo-router';
+import * as SecureStore from 'expo-secure-store';
+
+
+export async function fetchUserDetails(email: string, token: string) {
+ try {
+ const response = await getUserDetails(email, token);
+ if (response.status === 200) {
+ storeUserData(JSON.stringify(response.data));
+ }
+ else {
+ console.log(response)
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+export async function fetchNotificationSettings(email: string) {
+ try {
+ const response = await getNotificationSettings(email);
+ if (response.status === 200) {
+ const settings = {
+ invites: response.data.invites,
+ bookingReminder: response.data.bookingReminder
+ };
+ // console.log('settings response', response.data);
+ // console.log(settings);
+ storeNotificationSettings(JSON.stringify(settings));
+ }
+ else {
+ console.log(response)
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+export async function fetchSecuritySettings(email: string) {
+ try {
+ const response = await getSecuritySettings(email);
+ if (response.status === 200) {
+ const settings = {
+ mfa: response.data.mfa,
+ forcelogout: response.data.forceLogout
+ };
+ // console.log('settings response', response.data);
+ // console.log(settings);
+ storeSecuritySettings(JSON.stringify(settings));
+ }
+ else {
+ console.log(response)
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+export async function updateSecurity(type: string, values: any) {
+ let userData = await SecureStore.getItemAsync('UserData');
+ let user = JSON.parse(userData || "");
+ let email = user.email;
+ if (type === "settings") {
+ try {
+ const request = {
+ email: email,
+ mfa: values.mfa,
+ forceLogout: values.forceLogout
+ }
+ const response = await updateSecuritySettings(request);
+ if (response.status === 200) {
+ const settings = {
+ mfa: values.mfa,
+ forceLogout: values.forceLogout
+ };
+ console.log('settings response', response);
+ console.log(settings);
+ storeSecuritySettings(JSON.stringify(settings));
+ router.replace('/settings')
+ return "Settings updated successfully"
+ }
+ else {
+ console.log(response)
+ return response.message;
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+ } else {
+ try {
+ const request = {
+ email: email,
+ currentPassword: values.currentPassword,
+ newPassword: values.newPassword,
+ newPasswordConfirm: values.newPasswordConfirm
+ }
+ const response = await updateSecuritySettings(request);
+ if (response.status === 200) {
+ router.replace('/set-security')
+ return "Successfully changed password"
+ }
+ else {
+ console.log(response);
+ return response.message;
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+ }
+}
+
+export async function updateDetails(name: string, dob: string, gender: string, cellno: string, pronouns: string) {
+ const email = await SecureStore.getItemAsync('Email');
+ const state = await SecureStore.getItemAsync('AppState');
+ try {
+ const request : UpdateDetailsReq = {
+ session_email: email,
+ name: name,
+ dob: dob + "T00:00:00.000Z",
+ gender: gender,
+ number: cellno,
+ pronouns: pronouns,
+ }
+ const response = await updateUserDetails(request);
+ if (response.status === 200) {
+ console.log(response);
+ if (state === "verify_otp_register") {
+ setState("logged_out");
+ router.replace('login');
+ }
+ router.replace('/settings')
+ return "Details updated successfully"
+ }
+ else {
+ console.log(response)
+ return response.message;
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+export async function updateNotifications(values: any) {
+ let userData = await SecureStore.getItemAsync('UserData');
+ let user = JSON.parse(userData || "");
+ let email = user.email;
+ try {
+ const request = {
+ email: email,
+ invites: values.mfa,
+ bookingReminder: values.forceLogout
+ }
+ const response = await updateNotificationSettings(request);
+ if (response.status === 200) {
+ const settings = {
+ invites: values.invites,
+ bookingReminder: values.bookingReminder
+ };
+ console.log('settings response', response);
+ console.log(settings);
+ storeNotificationSettings(JSON.stringify(settings));
+ router.replace('/settings')
+ return "Settings updated successfully"
+ }
+ else {
+ console.log(response)
+ return response.message;
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+export async function fetchUsername() {
+ let userData = await SecureStore.getItemAsync('UserData');
+ let user = JSON.parse(userData || "{}");
+ // console.log(user.name);
+ return user.name;
+}
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/utils/utils.ts b/frontend/occupi-mobile4/utils/utils.ts
index 83af1778..d6ae0fbb 100644
--- a/frontend/occupi-mobile4/utils/utils.ts
+++ b/frontend/occupi-mobile4/utils/utils.ts
@@ -1,12 +1,33 @@
import * as SecureStore from 'expo-secure-store';
+
+
export const getAccentColour = async () => {
let accentcolour = await SecureStore.getItemAsync('accentColour');
if (!accentcolour) {
return "greenyellow";
}
- else
- {
- return accentcolour;
+ else {
+ return accentcolour;
+ }
+};
+
+export const getTheme = async () => {
+ let theme = await SecureStore.getItemAsync('Theme');
+ if (!theme) {
+ return "dark";
}
- };
\ No newline at end of file
+ else {
+ return theme;
+ }
+};
+
+export const theme = getTheme();
+
+export function extractDateFromTimestamp(timestamp: string): string {
+ const date = new Date(timestamp);
+ const year = date.getUTCFullYear();
+ const month = String(date.getUTCMonth() + 1).padStart(2, '0');
+ const day = String(date.getUTCDate()+1).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+ }
\ No newline at end of file
diff --git a/frontend/occupi-mobile4/utils/viewbookings.ts b/frontend/occupi-mobile4/utils/viewbookings.ts
new file mode 100644
index 00000000..e69de29b