diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
new file mode 100644
index 0000000..f912847
--- /dev/null
+++ b/.vscode/c_cpp_properties.json
@@ -0,0 +1,18 @@
+{
+ "configurations": [
+ {
+ "name": "windows-gcc-x86",
+ "includePath": [
+ "${workspaceFolder}/**"
+ ],
+ "compilerPath": "C:/MinGW/bin/gcc.exe",
+ "cStandard": "${default}",
+ "cppStandard": "${default}",
+ "intelliSenseMode": "windows-gcc-x86",
+ "compilerArgs": [
+ ""
+ ]
+ }
+ ],
+ "version": 4
+}
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..a658df5
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,24 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "C/C++ Runner: Debug Session",
+ "type": "cppdbg",
+ "request": "launch",
+ "args": [],
+ "stopAtEntry": false,
+ "externalConsole": true,
+ "cwd": "c:/Users/khush/AppData/Local/Microsoft/Windows/INetCache/IE/DKU7278T",
+ "program": "c:/Users/khush/AppData/Local/Microsoft/Windows/INetCache/IE/DKU7278T/build/Debug/outDebug",
+ "MIMode": "gdb",
+ "miDebuggerPath": "gdb",
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3b66410..baf8220 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,60 @@
{
- "git.ignoreLimitWarning": true
+ "git.ignoreLimitWarning": true,
+ "C_Cpp_Runner.cCompilerPath": "gcc",
+ "C_Cpp_Runner.cppCompilerPath": "g++",
+ "C_Cpp_Runner.debuggerPath": "gdb",
+ "C_Cpp_Runner.cStandard": "",
+ "C_Cpp_Runner.cppStandard": "",
+ "C_Cpp_Runner.msvcBatchPath": "C:/Program Files/Microsoft Visual Studio/VR_NR/Community/VC/Auxiliary/Build/vcvarsall.bat",
+ "C_Cpp_Runner.useMsvc": false,
+ "C_Cpp_Runner.warnings": [
+ "-Wall",
+ "-Wextra",
+ "-Wpedantic",
+ "-Wshadow",
+ "-Wformat=2",
+ "-Wcast-align",
+ "-Wconversion",
+ "-Wsign-conversion",
+ "-Wnull-dereference"
+ ],
+ "C_Cpp_Runner.msvcWarnings": [
+ "/W4",
+ "/permissive-",
+ "/w14242",
+ "/w14287",
+ "/w14296",
+ "/w14311",
+ "/w14826",
+ "/w44062",
+ "/w44242",
+ "/w14905",
+ "/w14906",
+ "/w14263",
+ "/w44265",
+ "/w14928"
+ ],
+ "C_Cpp_Runner.enableWarnings": true,
+ "C_Cpp_Runner.warningsAsError": false,
+ "C_Cpp_Runner.compilerArgs": [],
+ "C_Cpp_Runner.linkerArgs": [],
+ "C_Cpp_Runner.includePaths": [],
+ "C_Cpp_Runner.includeSearch": [
+ "*",
+ "**/*"
+ ],
+ "C_Cpp_Runner.excludeSearch": [
+ "**/build",
+ "**/build/**",
+ "**/.*",
+ "**/.*/**",
+ "**/.vscode",
+ "**/.vscode/**"
+ ],
+ "C_Cpp_Runner.useAddressSanitizer": false,
+ "C_Cpp_Runner.useUndefinedSanitizer": false,
+ "C_Cpp_Runner.useLeakSanitizer": false,
+ "C_Cpp_Runner.showCompilationTime": false,
+ "C_Cpp_Runner.useLinkTimeOptimization": false,
+ "C_Cpp_Runner.msvcSecureNoWarnings": false
}
\ No newline at end of file
diff --git a/App.js b/App.js
index b0e9721..aab9751 100644
--- a/App.js
+++ b/App.js
@@ -5,6 +5,7 @@ import { createStackNavigator } from '@react-navigation/stack';
import { AuthProvider, useAuth } from './context/AuthContext';
import { ThemeProvider } from './context/ThemeContext'; // Make sure to import ThemeProvider
import MainTabNavigator from './navigation/MainTabNavigator';
+import WardenTabNavigator from './navigation/WardenTabNavigator';
import LoginScreen from './screens/LoginScreen';
import RegisterScreen from './screens/RegisterScreen';
import LoadingScreen from './screens/LoadingScreen';
@@ -15,6 +16,12 @@ import LibraryScreen from './screens/LibraryScreen';
import LogBook from './screens/LogBookScreen';
import { StackScreen } from 'react-native-screens';
import GuardDashboardScreen from './screens/GuardScreen';
+import WardenDashboardScreen from './screens/WardenDashboardScreen.';
+import OutpassScreen from './screens/OutpassScreen';
+// import EmergencyScreen from './screens/EmergencyScreen';
+import ProfileScreen from './screens/ProfileScreen';
+
+
const Stack = createStackNavigator();
function RootNavigator() {
@@ -23,6 +30,20 @@ function RootNavigator() {
if (loading) {
return ;
}
+
+ // if user is not logged in
+
+ if (!user) {
+ return (
+
+
+
+
+ )
+ }
+
+ // user is logged in so its safe to use user.role now otherwise error would have came
+
return (
{user ? (
@@ -34,8 +55,18 @@ function RootNavigator() {
>
- ):
- (<>
+ ) : user.role == 'warden' ? (
+ <>
+
+ {/* */}
+ {/* */}
+ {/* */}
+
+
+
+ >
+ ) : (
+ <>
diff --git a/UPDATE.md b/UPDATE.md
index bea1d5e..17252ac 100644
--- a/UPDATE.md
+++ b/UPDATE.md
@@ -42,3 +42,14 @@
- Your password will get updated and next time when you try to login you have to enter the newly setted password.
+
+*** WARDEN SIDE ACTIVE ***
+
+1. The warden is now able to register without any fail.
+2. The warden dashboard fails to load the page but it appears with 3 tabs- Dashboard, Approvals and Profile
+
+3. Profile page is not getting loaded still but.
+
+
+
+
diff --git a/components/OutpassCard.js b/components/OutpassCard.js
index 7a3c6eb..ae8dd6c 100644
--- a/components/OutpassCard.js
+++ b/components/OutpassCard.js
@@ -258,3 +258,5 @@ const styles = StyleSheet.create({
marginTop: SPACING.xs,
},
})
+
+
diff --git a/components/StudentRegisterCard.js b/components/StudentRegisterCard.js
index fd73dcc..8d35e16 100644
--- a/components/StudentRegisterCard.js
+++ b/components/StudentRegisterCard.js
@@ -22,7 +22,7 @@ export default function StudentRegisterCard({ formData, updateFormData, departme
updateFormData('studentId', value)}
diff --git a/components/WardenOutpassCard.js b/components/WardenOutpassCard.js
new file mode 100644
index 0000000..e601521
--- /dev/null
+++ b/components/WardenOutpassCard.js
@@ -0,0 +1,282 @@
+"use client"
+
+import { View, Text, StyleSheet, TouchableOpacity, Alert } from "react-native"
+import { Ionicons } from "@expo/vector-icons"
+import { commonAPI } from "../services/api"
+import { COLORS, FONTS, SIZES, SPACING, OUTPASS_STATUS } from "../utils/constants"
+
+export default function WardenOutpassCard({ outpass, onUpdate }) {
+ const getStatusColor = (status) => {
+ switch (status) {
+ case OUTPASS_STATUS.PENDING:
+ return COLORS.warning
+ case OUTPASS_STATUS.APPROVED:
+ return COLORS.success
+ case OUTPASS_STATUS.REJECTED:
+ return COLORS.error
+ case OUTPASS_STATUS.ACTIVE:
+ return COLORS.primary
+ case OUTPASS_STATUS.COMPLETED:
+ return COLORS.gray[500]
+ default:
+ return COLORS.gray[400]
+ }
+ }
+
+ const getStatusIcon = (status) => {
+ switch (status) {
+ case OUTPASS_STATUS.PENDING:
+ return "time-outline"
+ case OUTPASS_STATUS.APPROVED:
+ return "checkmark-circle-outline"
+ case OUTPASS_STATUS.REJECTED:
+ return "close-circle-outline"
+ case OUTPASS_STATUS.ACTIVE:
+ return "play-circle-outline"
+ case OUTPASS_STATUS.COMPLETED:
+ return "checkmark-done-outline"
+ default:
+ return "help-circle-outline"
+ }
+ }
+
+ const formatDate = (dateString) =>
+ new Date(dateString).toLocaleDateString("en-GB", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric",
+ })
+
+ const formatTime = (dateString) =>
+ new Date(dateString).toLocaleTimeString("en-GB", {
+ hour: "2-digit",
+ minute: "2-digit",
+ })
+
+ const updateStatus = (status) => {
+ Alert.alert(
+ "Confirm Action",
+ `Are you sure you want to ${status} this outpass?`,
+ [
+ { text: "Cancel", style: "cancel" },
+ {
+ text: "Yes",
+ onPress: async () => {
+ try {
+ const response = await commonAPI.updateOutpass(outpass._id, {
+ status,
+ })
+ onUpdate(response.data)
+ Alert.alert("Success", `Outpass ${status} successfully`)
+ } catch (error) {
+ Alert.alert("Error", "Failed to update outpass")
+ }
+ },
+ },
+ ]
+ )
+ }
+
+ const canTakeAction = outpass.status === OUTPASS_STATUS.PENDING
+
+ return (
+
+ {/* HEADER */}
+
+
+
+
+ {outpass.status.toUpperCase()}
+
+
+
+
+ {/* CONTENT */}
+
+ {/* Student Info (warden-specific) */}
+ {outpass.student?.name}
+
+ ID: {outpass.student?.studentId} | Room: {outpass.student?.roomNumber}
+
+
+ {outpass.purpose || outpass.reason}
+ {outpass.destination}
+
+
+
+
+ From
+
+ {formatDate(outpass.fromDate || outpass.outDate)} at{" "}
+ {formatTime(outpass.fromTime || outpass.outDate)}
+
+
+
+
+
+ To
+
+ {formatDate(outpass.toDate || outpass.expectedReturnDate)} at{" "}
+ {formatTime(outpass.toTime || outpass.expectedReturnDate)}
+
+
+
+
+ {outpass.remarks && (
+
+ Remarks:
+ {outpass.remarks}
+
+ )}
+
+
+ {/* ACTIONS */}
+ {canTakeAction && (
+
+ updateStatus("approved")}
+ >
+
+ Approve
+
+
+ updateStatus("rejected")}
+ >
+
+ Reject
+
+
+ )}
+
+
+ {/* FOOTER */}
+
+
+ Requested on {formatDate(outpass.createdAt)}
+
+
+
+ )
+}
+
+
+
+
+const styles = StyleSheet.create({
+ card: {
+ backgroundColor: COLORS.white,
+ borderRadius: 12,
+ padding: SPACING.lg,
+ marginBottom: SPACING.md,
+ elevation: 3,
+ },
+ cardHeader: {
+ marginBottom: SPACING.md,
+ },
+ statusContainer: {
+ flexDirection: "row",
+ alignItems: "center",
+ },
+ statusText: {
+ fontSize: SIZES.sm,
+ fontFamily: FONTS.bold,
+ marginLeft: SPACING.xs,
+ },
+ cardContent: {
+ marginBottom: SPACING.md,
+ },
+ studentName: {
+ fontSize: SIZES.md,
+ fontFamily: FONTS.bold,
+ color: COLORS.gray[800],
+ },
+ studentMeta: {
+ fontSize: SIZES.sm,
+ color: COLORS.gray[600],
+ marginBottom: SPACING.sm,
+ },
+ purpose: {
+ fontSize: SIZES.lg,
+ fontFamily: FONTS.bold,
+ color: COLORS.gray[800],
+ },
+ destination: {
+ fontSize: SIZES.md,
+ color: COLORS.gray[600],
+ marginBottom: SPACING.md,
+ },
+ timeContainer: {
+ marginBottom: SPACING.md,
+ },
+ timeItem: {
+ flexDirection: "row",
+ alignItems: "center",
+ marginBottom: SPACING.xs,
+ },
+ timeLabel: {
+ fontSize: SIZES.sm,
+ marginLeft: SPACING.xs,
+ marginRight: SPACING.xs,
+ minWidth: 30,
+ color: COLORS.gray[600],
+ },
+ timeValue: {
+ fontSize: SIZES.sm,
+ color: COLORS.gray[800],
+ },
+ remarksContainer: {
+ backgroundColor: COLORS.gray[50],
+ padding: SPACING.sm,
+ borderRadius: 8,
+ },
+ remarksLabel: {
+ fontSize: SIZES.sm,
+ fontFamily: FONTS.bold,
+ },
+ remarksText: {
+ fontSize: SIZES.sm,
+ color: COLORS.gray[600],
+ },
+ actionRow: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ marginBottom: SPACING.sm,
+ },
+ actionButton: {
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "center",
+ padding: SPACING.sm,
+ borderRadius: 8,
+ flex: 1,
+ },
+ approve: {
+ backgroundColor: COLORS.success,
+ marginRight: SPACING.xs,
+ },
+ reject: {
+ backgroundColor: COLORS.error,
+ marginLeft: SPACING.xs,
+ },
+ actionText: {
+ color: COLORS.white,
+ marginLeft: SPACING.xs,
+ fontFamily: FONTS.bold,
+ },
+ cardFooter: {
+ borderTopWidth: 1,
+ borderTopColor: COLORS.gray[200],
+ paddingTop: SPACING.sm,
+ },
+ createdAt: {
+ fontSize: SIZES.xs,
+ color: COLORS.gray[500],
+ },
+})
diff --git a/components/WardenRegisterCard.js b/components/WardenRegisterCard.js
index 6e36e28..6152c42 100644
--- a/components/WardenRegisterCard.js
+++ b/components/WardenRegisterCard.js
@@ -1,4 +1,3 @@
-
import React from 'react';
import { View, TextInput, StyleSheet } from 'react-native';
import { Picker } from '@react-native-picker/picker';
diff --git a/models/GuardUser.js b/models/GuardUser.js
index 94f9e95..834be63 100644
--- a/models/GuardUser.js
+++ b/models/GuardUser.js
@@ -39,11 +39,12 @@ const userSchema = new mongoose.Schema(
type: String,
trim: true,
},
- // deviceId: {
- // type: String,
- // required: true,
- // unique: true,
- // },
+ deviceId: {
+ type: String,
+ required: true,
+ unique: true,
+ sparse: true,
+ },
guardId: {
type: String,
sparse: true,
diff --git a/models/WardenUser.js b/models/WardenUser.js
index 8b6327d..8046196 100644
--- a/models/WardenUser.js
+++ b/models/WardenUser.js
@@ -41,11 +41,10 @@ const userSchema = new mongoose.Schema(
type: String,
trim: true,
},
- // deviceId: {
- // type: String,
- // required: true,
- // unique: true,
- // },
+ deviceId: {
+ type: String,
+ required: true,
+ },
isActive: {
type: Boolean,
default: true,
diff --git a/navigation/WardenTabNavigator.js b/navigation/WardenTabNavigator.js
new file mode 100644
index 0000000..09e53b2
--- /dev/null
+++ b/navigation/WardenTabNavigator.js
@@ -0,0 +1,44 @@
+import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"
+import { Ionicons } from "@expo/vector-icons"
+
+// Import screens
+import WardenDashboardScreen from "../screens/WardenDashboardScreen."
+import ProfileScreen from "../screens/ProfileScreen"
+import OutpassRequestsScreen from "../screens/OutpassRequestsScreen"
+// import StudentsScreen from "../screens/StudentsScreen"
+
+const Tab = createBottomTabNavigator()
+
+export default function WardenTabNavigator() {
+ return (
+ ({
+ tabBarIcon: ({ focused, color, size }) => {
+ let iconName
+
+ if (route.name === "Dashboard") {
+ iconName = focused ? "home" : "home-outline"
+ } else if (route.name === "Approvals") {
+ iconName = focused ? "checkmark-circle" : "checkmark-circle-outline";
+ } else if (route.name === "Profile") {
+ iconName = focused ? "person" : "person-outline"
+ } else if (route.name === "Students") {
+ iconName = focused ? "people" : "people-outline";
+ }
+
+
+
+ return
+ },
+ tabBarActiveTintColor: "#2563eb",
+ tabBarInactiveTintColor: "gray",
+ headerShown: false,
+ })}
+ >
+
+
+ {/* */}
+
+
+ )
+ }
\ No newline at end of file
diff --git a/routes/authRoutes.js b/routes/authRoutes.js
index 5e50dcc..ed7ea48 100644
--- a/routes/authRoutes.js
+++ b/routes/authRoutes.js
@@ -7,11 +7,12 @@ const GuardUser = require("../models/GuardUser")
const { authenticate } = require("../middleware/auth");
const router = express.Router()
+
// Register new user
router.post("/register", async (req, res) => {
try {
console.log("INCOMING REGISTRATION DATA:", req.body);
- const { name, email, password, studentId, guardId, wardenId, hostel, roomNumber, phoneNumber, securityPost, emergencyContact, gender, year, department, role} = req.body
+ const { name, email, password, studentId, guardId, wardenId, hostel, roomNumber, phoneNumber, securityPost, gender, year, department, role, deviceId} = req.body
let user;
const hashPassword = await bcrypt.hash(password, 10)
@@ -37,7 +38,6 @@ router.post("/register", async (req, res) => {
hostel,
roomNumber,
phoneNumber,
- emergencyContact,
gender,
year,
department,
@@ -48,7 +48,7 @@ router.post("/register", async (req, res) => {
else if(role == "warden") {
// Check if user already exists
const existingUser = await WardenUser.findOne({
- $or: [{ email }, { Id }],
+ $or: [{ email }],
})
if (existingUser) {
@@ -63,20 +63,20 @@ router.post("/register", async (req, res) => {
password: hashPassword,
hostel,
phoneNumber,
-
gender,
+ deviceId,
})
}
else {
// Create new user
- // Check if user already exists
+ // Check if user already exists
const existingUser = await GuardUser.findOne({
- $or: [{ email }],
+ $or: [{ email }, { guardId }],
})
if (existingUser) {
return res.status(400).json({
- message: "Guard with this ID already exists",
+ message: "Guard with this email or ID already exists",
})
}
@@ -87,9 +87,8 @@ router.post("/register", async (req, res) => {
location : securityPost,
guardId: guardId,
phoneNumber,
- emergencyContact,
-
gender,
+ deviceId,
})
}
@@ -186,7 +185,7 @@ router.post("/login", async (req, res) => {
},
});
} catch (error) {
- console.error("Login error:", error);1
+ console.error("Login error:", error);
res.status(500).json({ message: "Server error during login" });
}
})
diff --git a/routes/forgotRoute.js b/routes/forgotRoute.js
index ee3938d..5c2b4a7 100644
--- a/routes/forgotRoute.js
+++ b/routes/forgotRoute.js
@@ -31,7 +31,7 @@ router.post('/', async (req , res) => {
}
user.password = await bcrypt.hash(newPassword, 10);
- await user.save();
+
let mailOptions = {
from: process.env.GMAIL_ID,
@@ -43,11 +43,14 @@ router.post('/', async (req , res) => {
transporter.sendMail(mailOptions, function(error, info){
if (error) {
console.log(error);
+ return;
} else {
console.log('Email sent: ' + info.response);
}
});
+ await user.save();
+
return res.status(200).json({ message: 'Password reset email sent' });
}
catch(err){
diff --git a/routes/outpassRoutes.js b/routes/outpassRoutes.js
index 12d18b4..ff4cf4a 100644
--- a/routes/outpassRoutes.js
+++ b/routes/outpassRoutes.js
@@ -1,6 +1,7 @@
const express = require("express")
const Outpass = require("../models/Outpass")
const User = require("../models/User")
+const WardenUser = require("../models/WardenUser")
const Log = require("../models/Log")
const { authenticate } = require("../middleware/auth");
const adminAuth = require("../middleware/adminAuth")
@@ -379,6 +380,52 @@ router.get("/history", [authenticate, checkOutpassExpiry], async (req, res) => {
}
})
+
+// Warden: Get outpass requests for students of warden's hostel
+router.get("/warden/requests", authenticate, async (req, res) => {
+ try {
+ // Ensure only wardens can access
+ if (req.user.role !== "warden") {
+ return res.status(403).json({ message: "Access denied" })
+ }
+
+ const wardenHostel = req.user.hostel
+
+ if (!wardenHostel) {
+ return res.status(400).json({ message: "Warden hostel not assigned" })
+ }
+
+ // Expire old outpasses before fetching
+ await expireOldOutpasses()
+
+ const outpasses = await Outpass.find({
+ status: { $in: ["pending", "approved", "rejected", "expired", "cancelled"] },
+ })
+ .populate({
+ path: "userId",
+ select: "name studentId hostel roomNumber",
+ match: { hostel: wardenHostel },
+ })
+ .sort({ createdAt: -1 })
+
+ // Remove outpasses whose users didn't match hostel
+ const filteredOutpasses = outpasses
+ .filter((op) => op.userId)
+ .map((op) => ({
+ ...op.toObject(),
+ student: op.userId, // 🔑 aligns with WardenOutpassCard
+ }))
+
+ res.json({
+ outpasses: filteredOutpasses,
+ })
+ } catch (error) {
+ console.error("Warden outpass fetch error:", error)
+ res.status(500).json({ message: "Server error fetching outpass requests" })
+ }
+})
+
+
// Get current day's outpass for user
router.get("/today", [authenticate, checkOutpassExpiry], async (req, res) => {
try {
diff --git a/screens/CreateOutpassScreen.js b/screens/CreateOutpassScreen.js
index c348d97..8cd886a 100644
--- a/screens/CreateOutpassScreen.js
+++ b/screens/CreateOutpassScreen.js
@@ -19,7 +19,6 @@ import { outpass } from "../services/api"
import {useAuth} from "../context/AuthContext"
import { COLORS, FONTS, SIZES, SPACING } from "../utils/constants"
import LoadingSpinner from "../components/LoadingSpinner"
-
import styles from "../styles/CreateOutpassStyles"
export default function CreateOutpassScreen({ navigation }) {
@@ -190,7 +189,6 @@ export default function CreateOutpassScreen({ navigation }) {
placeholderTextColor={colors.subText}
value={formData.emergencyName}
onChangeText={(value) => updateFormData("emergencyName", value)}
- keyboardType="phone-pad"
/>
diff --git a/screens/GuardScreen.js b/screens/GuardScreen.js
index b3c6a0d..3c80323 100644
--- a/screens/GuardScreen.js
+++ b/screens/GuardScreen.js
@@ -31,7 +31,7 @@ export default function GuardDashboardScreen({ navigation }) {
}, [])
// Ensure a default location is selected (use profile.location if available,
- // otherwise fall back to the first entry in AllLocations)
+ // otherwise fall back to the first entry in All Locations)
useEffect(() => {
if (!loc) {
if (profile?.location) {
diff --git a/screens/LibraryScreen.js b/screens/LibraryScreen.js
index 0b1c937..5234edb 100644
--- a/screens/LibraryScreen.js
+++ b/screens/LibraryScreen.js
@@ -255,7 +255,7 @@ export default function LibraryScreen({ navigation }) {
{book.name}
{book.isDue && (
- (DUE)
+ (DUE)
)}
diff --git a/screens/LogBookScreen.js b/screens/LogBookScreen.js
index dec8422..0838ffa 100644
--- a/screens/LogBookScreen.js
+++ b/screens/LogBookScreen.js
@@ -92,10 +92,10 @@ export default function LogBook({ navigation, route }) {
]}
>
{actionLabel}
- Resident: {residentName}
- Guard: {guardOnDuty}
- Location: {item.location || "-"}
- Time: {timestamp}
+ Resident: {residentName}
+ Guard: {guardOnDuty}
+ Location: {item.location || "-"}
+ Time: {timestamp}
)
},
diff --git a/screens/OutpassRequestsScreen.js b/screens/OutpassRequestsScreen.js
new file mode 100644
index 0000000..22a0505
--- /dev/null
+++ b/screens/OutpassRequestsScreen.js
@@ -0,0 +1,147 @@
+"use client"
+
+import { View, Text, TouchableOpacity, RefreshControl, Alert, FlatList } from "react-native"
+import { useState, useEffect, useMemo } from "react"
+import { Ionicons } from "@expo/vector-icons"
+import { useTheme } from "../context/ThemeContext"
+import { useNavigation } from "@react-navigation/native"
+import { outpass } from "../services/api"
+import { COLORS } from "../utils/constants"
+
+import styles from "../styles/OutpassStyles"
+
+import LoadingSpinner from "../components/LoadingSpinner"
+import FilterTabs from "../components/FilterTabs"
+import WardenOutpassCard from "../components/WardenOutpassCard"
+
+export default function OutpassRequestsScreen() {
+ const { isDarkMode, toggleTheme, colors } = useTheme()
+ const navigation = useNavigation()
+ const [outpasses, setOutpasses] = useState([])
+ const [filteredOutpasses, setFilteredOutpasses] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [refreshing, setRefreshing] = useState(false)
+ const [activeFilter, setActiveFilter] = useState("pending")
+
+ // Count statuses
+ const statusCounts = useMemo(() => {
+ const counts = {
+ pending: 0,
+ approved: 0,
+ rejected: 0,
+ cancelled: 0,
+ expired: 0,
+ }
+
+ outpasses.forEach((item) => {
+ if (counts[item.status] !== undefined) {
+ counts[item.status] += 1
+ }
+ })
+
+ return counts
+ }, [outpasses])
+
+ const filterOptions = useMemo(
+ () => [
+ { key: "pending", label: "Pending", count: statusCounts.pending },
+ { key: "approved", label: "Approved", count: statusCounts.approved },
+ { key: "rejected", label: "Rejected", count: statusCounts.rejected },
+ { key: "cancelled", label: "Cancelled", count: statusCounts.cancelled },
+ { key: "expired", label: "Expired", count: statusCounts.expired },
+ ],
+ [statusCounts]
+ )
+
+ useEffect(() => {
+ loadOutpassRequests()
+ }, [])
+
+ useEffect(() => {
+ filterOutpasses()
+ }, [outpasses, activeFilter])
+
+ const loadOutpassRequests = async () => {
+ try {
+ const response = await outpass.getPendingRequests() // WARDEN API written inside outpassRoute.js
+ setOutpasses(response.data?.outpasses || [])
+ } catch (error) {
+ console.log("Warden outpass load error:", error)
+ Alert.alert("Error", "Failed to load outpass requests")
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const onRefresh = async () => {
+ setRefreshing(true)
+ await loadOutpassRequests()
+ setRefreshing(false)
+ }
+
+ const filterOutpasses = () => {
+ if (activeFilter === "all") {
+ setFilteredOutpasses(outpasses)
+ } else {
+ setFilteredOutpasses(outpasses.filter((o) => o.status === activeFilter))
+ }
+ }
+
+ // Update local state after approve/reject
+ const handleStatusUpdate = (updatedOutpass) => {
+ setOutpasses((prev) =>
+ prev.map((op) => (op._id === updatedOutpass._id ? updatedOutpass : op))
+ )
+ }
+
+ const renderOutpassCard = ({ item }) => (
+
+ )
+
+ const renderEmptyState = () => (
+
+
+ No Requests
+ No outpass requests in this category
+
+ )
+
+ if (loading) {
+ return
+ }
+
+ return (
+
+ {/* Header */}
+
+
+ Outpass Requests
+
+
+
+
+
+
+
+
+ item._id}
+ contentContainerStyle={styles.listContainer}
+ refreshControl={}
+ ListEmptyComponent={renderEmptyState}
+ showsVerticalScrollIndicator={false}
+ />
+
+ )
+}
diff --git a/screens/OutpassScreen.js b/screens/OutpassScreen.js
index 7076f4b..f8f724b 100644
--- a/screens/OutpassScreen.js
+++ b/screens/OutpassScreen.js
@@ -112,7 +112,9 @@ export default function OutpassScreen() {
setOutpasses((prev) => prev.map((op) => (op._id === updatedOutpass._id ? updatedOutpass : op)))
}
- const renderOutpassCard = ({ item }) =>
+ const renderOutpassCard = ({ item }) => (
+
+ )
const renderEmptyState = () => (
@@ -125,7 +127,7 @@ export default function OutpassScreen() {
? "Create your first outpass request to get started"
: `You don't have any ${activeFilter} outpasses`}
- {activeFilter === "all" && (
+ { (
Create Outpass
diff --git a/screens/ProfileScreen.js b/screens/ProfileScreen.js
index fc00ab2..ed6a7b2 100644
--- a/screens/ProfileScreen.js
+++ b/screens/ProfileScreen.js
@@ -14,11 +14,17 @@ import { Picker } from "@react-native-picker/picker"
export default function ProfileScreen() {
const { isDarkMode, toggleTheme, colors } = useTheme();
const { user, logout } = useAuth()
+
+ const isStudent = user.role === "student"
+ const isWarden = user.role === "warden"
+
const [profile, setProfile] = useState(null)
const [editing, setEditing] = useState(false)
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
+
// password update state
+
const [currentPassword, setCurrentPassword] = useState("")
const [newPassword, setNewPassword] = useState("")
const [confirmPassword, setConfirmPassword] = useState("")
@@ -69,27 +75,35 @@ export default function ProfileScreen() {
setProfile((prev) => ({ ...prev, [key]: value }))
}
- if (loading) {
- return
- }
+ if (loading) return
+
return (
+
+ {/* HEADER */}
+
+
+
- {profile?.name?.charAt(0)?.toUpperCase() || "U"}
+ {profile?.name?.charAt(0)?.toUpperCase() || "YOU"}
{profile?.name}
{profile?.role?.toUpperCase()}
+
+
+ {/* PERSONAL INFO */}
+
Personal Information
@@ -128,7 +142,8 @@ export default function ProfileScreen() {
{profile?.email}
)}
-
+
+ {isStudent && (
Student ID
{editing ? (
@@ -141,6 +156,7 @@ export default function ProfileScreen() {
{profile?.studentId}
)}
+ )}
Phone Number
@@ -157,6 +173,8 @@ export default function ProfileScreen() {
+
+ {isStudent && (
Academic Information
@@ -217,12 +235,12 @@ export default function ProfileScreen() {
))}
+
>
)
}
-
-
+
Room Number
@@ -237,8 +255,11 @@ export default function ProfileScreen() {
)}
+ )}
+
+ {/* CHANGE PASSWORD */}
+
-
Change Password
@@ -349,6 +370,10 @@ export default function ProfileScreen() {
+
+ {/* ACCOUNT STATUS */}
+
+
Account Status
@@ -372,6 +397,8 @@ export default function ProfileScreen() {
+ {/* LOGOUT */}
+
Logout
diff --git a/screens/RegisterScreen.js b/screens/RegisterScreen.js
index 1d4609d..6f7679a 100644
--- a/screens/RegisterScreen.js
+++ b/screens/RegisterScreen.js
@@ -71,9 +71,9 @@ export default function RegisterScreen({ navigation }) {
}
const validateForm = () => {
- const { name, email, password, confirmPassword, studentId, department, year, hostel, phone, gender, deviceId } = formData
+ const { name, email, password, confirmPassword, studentId, wardenId, guardId, department, year, hostel, phone, gender, role, securityPost, roomNumber } = formData
- if (!name || !email || !password || !studentId || !department || !year || !hostel || !phone || !gender) {
+ if (!name || !email || !password || !confirmPassword || !role || !phone || !gender) {
Alert.alert("Error", "Please fill in all required fields")
return false
}
@@ -88,14 +88,33 @@ export default function RegisterScreen({ navigation }) {
return false
}
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
- Alert.alert("Error", "Please enter a valid email address")
- return false
+ Alert.alert("Error", "Please enter a valid email address");
+ return false;
}
- return true
- }
+ // role based check
+
+ if(role === 'student') {
+ if(!department || !studentId || !year || !hostel || !roomNumber) {
+ Alert.alert("Error", "Please fill in all the required fields for student");
+ return false;
+ }
+ } else if(role === 'warden') {
+ if(!hostel) {
+ Alert.alert("Error", "Please fill in all the required fields for warden");
+ return false;
+ }
+ } else if (role === 'security') {
+ if (!guardId || !securityPost) {
+ Alert.alert("Error", "Please fill in all required fields for a security guard.");
+ return false;
+ }
+ }
+
+ return true;
+ };
const handleRegister = async () => {
if (!validateForm()) return
diff --git a/screens/WardenDashboardScreen..js b/screens/WardenDashboardScreen..js
new file mode 100644
index 0000000..39d380b
--- /dev/null
+++ b/screens/WardenDashboardScreen..js
@@ -0,0 +1,149 @@
+"use client"
+
+import { View, Text, StyleSheet, ScrollView, TouchableOpacity, RefreshControl, Alert } from "react-native"
+import { useState, useEffect } from "react"
+import { useTheme } from "../context/ThemeContext"
+import { Ionicons } from "@expo/vector-icons"
+import { useAuth } from "../context/AuthContext"
+import { outpass, commonAPI } from "../services/api"
+import styles from "../styles/DashboardStyles"
+
+import LoadingSpinner from "../components/LoadingSpinner"
+import PasskeyCard from "../components/PasskeyCard"
+
+export default function WardenDashboardScreen({ navigation }) {
+ const { isDarkMode, toggleTheme, colors } = useTheme();
+ const { user, logout } = useAuth()
+ const [passkey, setPasskey] = useState(null)
+ const [stats, setStats] = useState({
+ totalOutpasses: 0,
+ activeOutpasses: 0,
+ pendingOutpasses: 0,
+ })
+ const [loading, setLoading] = useState(true)
+ const [refreshing, setRefreshing] = useState(false)
+
+ useEffect(() => {
+ loadDashboardData()
+ }, [])
+
+ const loadDashboardData = async () => {
+ try {
+ const [passkeyResponse, outpassesResponse] = await Promise.all([
+ commonAPI.getDailyPasskey(),
+ outpass.getOutpasses(),
+ ])
+
+ setPasskey(passkeyResponse.data)
+
+ if (outpassesResponse.data.outpass){
+ const outpasses = outpassesResponse.data.outpass.auditTrail
+ setStats({
+ totalOutpasses: outpasses.length,
+ activeOutpasses: outpasses.filter((op) => op.status === "approved").length,
+ pendingOutpasses: outpasses.filter((op) => op.status === "pending").length,
+ })
+ }
+ } catch (error) {
+ console.log("Dashboard load error:", error)
+ Alert.alert("Error", "Failed to load dashboard data")
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const onRefresh = async () => {
+ setRefreshing(true)
+ await loadDashboardData()
+ setRefreshing(false)
+ }
+
+ const handleLogout = () => {
+ Alert.alert("Logout", "Are you sure you want to logout?", [
+ { text: "Cancel", style: "cancel" },
+ { text: "Logout", style: "destructive", onPress: logout },
+ ])
+ }
+
+ if (loading) {
+ return
+ }
+
+ return (
+ }
+ >
+
+
+
+
+
+
+ Good {getGreeting()}
+ {user?.name}
+ {user?.hostel}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Daily Passkey Card */}
+
+
+ {/* Quick Actions */}
+
+ Quick Actions
+
+ navigation.navigate("Scan")}>
+
+
+
+ Scan
+
+
+ navigation.navigate("Library")}>
+
+
+
+ Library
+
+
+ navigation.navigate("SAC")}>
+
+
+
+ SAC
+
+
+ navigation.navigate("Profile")}>
+
+
+
+ Profile
+
+
+
+
+
+ )
+}
+
+const getGreeting = () => {
+ const hour = new Date().getHours()
+ if (hour < 12) return "Morning"
+ if (hour < 17) return "Afternoon"
+ return "Evening"
+}
+
diff --git a/server.js b/server.js
index a558358..f732370 100644
--- a/server.js
+++ b/server.js
@@ -17,6 +17,8 @@ const adminRoutes = require("./routes/adminRoutes")
const securityRoutes = require("./routes/securityRoutes")
const studentRoutes = require("./routes/studentRoutes")
const forgotRoutes = require("./routes/forgotRoute")
+// const wardenRoutes = require('./routes/wardenRoutes');
+
const app = express()
const PORT = process.env.PORT || 5000
@@ -68,6 +70,9 @@ app.use("/api/admin", adminRoutes)
app.use("/api/security", securityRoutes)
app.use("/api/student", studentRoutes)
app.use("/api/forgot", forgotRoutes)
+// app.use("/api/warden", wardenRoutes);
+
+
// Health check endpoint
app.get("/api/health", (req, res) => {
diff --git a/services/api.js b/services/api.js
index 3bbd4db..f6e3487 100644
--- a/services/api.js
+++ b/services/api.js
@@ -63,6 +63,7 @@ export const outpass = {
createOutpass: (data) => api.post("/outpass/generate", data),
updateOutpass: (id, data) => api.put(`/outpass/${id}`, data),
getHistory: (params) => api.get("/outpass/history", { params }),
+ getPendingRequests: () => api.get("/outpass/warden/requests"),
}
// Emergency API endpoints
diff --git a/styles/CreateOutpassStyles.js b/styles/CreateOutpassStyles.js
index a0ad88a..52cb2d8 100644
--- a/styles/CreateOutpassStyles.js
+++ b/styles/CreateOutpassStyles.js
@@ -10,7 +10,6 @@ export default StyleSheet.create({
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
- paddingHorizontal: SPACING.lg,
paddingTop: 50,
paddingBottom: SPACING.md,
backgroundColor: COLORS.white,