diff --git a/App.tsx b/App.tsx index 66975af..bf69a4b 100644 --- a/App.tsx +++ b/App.tsx @@ -16,9 +16,11 @@ import Register from "./src/routes/Register/Register"; import Onboarding from "./src/routes/Onboarding/Onboarding"; import Revalidation from "./src/routes/Revalidation/Revalidation"; import Activation from "./src/routes/Activation/Activation"; -import UserInfo from "./src/routes/UserInfo/UserInfo"; import { useState, useEffect } from "react"; import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; +import { StatusBar } from "expo-status-bar"; +import UserInfoPage from "./src/routes/UserInfoPage/UserInfoPage"; +import SubjectsPage from "./src/routes/SubjectsPage/SubjectsPage"; const Drawer = createDrawerNavigator(); @@ -53,8 +55,10 @@ export default function App() { } }, [initialRoute]); return ( - - + + + + - + + - - + + ); } diff --git a/app.json b/app.json index 4802c78..9e8bd9c 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { "expo": { - "name": "StudE_Frontend", + "name": "StudE", "scheme": "stude", "slug": "StudE_Frontend", "version": "1.0.0", @@ -10,7 +10,7 @@ "splash": { "image": "./assets/splash.png", "resizeMode": "contain", - "backgroundColor": "#ffffff" + "backgroundColor": "#1C2C3F" }, "assetBundlePatterns": [ "**/*" @@ -21,8 +21,14 @@ "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#ffffff" - } + "backgroundColor": "#FFFF" + }, + "package": "com.teamblackpink.stude", + "permissions": [ + "android.permission.ACCESS_COARSE_LOCATION", + "android.permission.ACCESS_FINE_LOCATION", + "android.permission.FOREGROUND_SERVICE" + ] }, "web": { "favicon": "./assets/favicon.png" @@ -34,6 +40,11 @@ "locationAlwaysAndWhenInUsePermission": "Allow Stud-E to use your location." } ] - ] + ], + "extra": { + "eas": { + "projectId": "614fd93f-345c-4d72-a9e7-592f1ba0c6e8" + } + } } } diff --git a/assets/adaptive-icon.png b/assets/adaptive-icon.png index 03d6f6b..ebae40b 100644 Binary files a/assets/adaptive-icon.png and b/assets/adaptive-icon.png differ diff --git a/assets/favicon.png b/assets/favicon.png index e75f697..c34a8f6 100644 Binary files a/assets/favicon.png and b/assets/favicon.png differ diff --git a/assets/icon.png b/assets/icon.png index a0b1526..af59510 100644 Binary files a/assets/icon.png and b/assets/icon.png differ diff --git a/assets/splash.png b/assets/splash.png index 0e89705..c933124 100644 Binary files a/assets/splash.png and b/assets/splash.png differ diff --git a/eas.json b/eas.json new file mode 100644 index 0000000..02060ed --- /dev/null +++ b/eas.json @@ -0,0 +1,19 @@ +{ + "build": { + "preview": { + "android": { + "buildType": "apk" + } + }, + "preview2": { + "android": { + "gradleCommand": ":app:assembleRelease" + } + }, + "preview3": { + "developmentClient": true + }, + "production": {} + } + } + \ No newline at end of file diff --git a/package.json b/package.json index 60f4b66..9dbbe39 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,10 @@ "react-native-safe-area-context": "4.5.0", "react-native-screens": "~3.20.0", "react-native-select-dropdown": "^3.3.4", - "react-native-svg": "13.4.0", "react-query": "^3.39.3", "react-redux": "^8.1.1", - "redux": "^4.2.1" + "redux": "^4.2.1", + "react-native-svg": "13.4.0" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/src/components/AnimatedContainer/AnimatedContainer.tsx b/src/components/AnimatedContainer/AnimatedContainer.tsx index 8f5960d..1a92823 100644 --- a/src/components/AnimatedContainer/AnimatedContainer.tsx +++ b/src/components/AnimatedContainer/AnimatedContainer.tsx @@ -13,21 +13,23 @@ export default function AnimatedContainer(props: props) { contentContainerStyle={styles.container} from={{ borderRadius: 0, - backgroundColor: colors.orange_2, + opacity: 0, + backgroundColor: colors.secondary_2, paddingTop: 4, paddingBottom: 4, marginHorizontal: "4%", - marginVertical: "5%", + marginVertical: "10%", }} animate={{ borderRadius: 15, - backgroundColor: colors.blue_2, + opacity: 1, + backgroundColor: colors.secondary_2, paddingTop: 16, paddingBottom: 16, marginHorizontal: "4%", marginVertical: "5%", }} - transition={{ type: "timing", duration: 300 }} + transition={{ type: "timing", duration: 700 }} > {props.children} diff --git a/src/components/AnimatedContainer/AnimatedContainerNoScroll.tsx b/src/components/AnimatedContainer/AnimatedContainerNoScroll.tsx new file mode 100644 index 0000000..a3f7c35 --- /dev/null +++ b/src/components/AnimatedContainer/AnimatedContainerNoScroll.tsx @@ -0,0 +1,37 @@ +import * as React from "react"; +import { View, Text, ScrollView } from "react-native"; +import styles from "../../styles"; +import { colors } from "../../styles"; +import { MotiView, MotiScrollView } from "moti"; +export interface props { + children: React.ReactNode; +} + +export default function AnimatedContainerNoScroll(props: props) { + return ( + + {props.children} + + ); +} diff --git a/src/components/Api/Api.tsx b/src/components/Api/Api.tsx index 5a5ddcb..58cd659 100644 --- a/src/components/Api/Api.tsx +++ b/src/components/Api/Api.tsx @@ -4,7 +4,9 @@ import { ActivationParams, LoginParams, OnboardingParams, + PatchStudentData, RegistrationParams, + StudentData, } from "../../interfaces/Interfaces"; let debug = true; @@ -43,6 +45,16 @@ export async function setRefreshToken(refresh: string) { return true; } +// Header Config Template for REST +export async function GetConfig() { + const accessToken = await getAccessToken(); + return { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }; +} + // User APIs export function UserRegister(register: RegistrationParams) { console.log(JSON.stringify(register)); @@ -77,6 +89,7 @@ export function UserLogin(user: LoginParams) { let error_message = ""; if (error.response) error_message = error.response.data; else error_message = "Unable to reach servers"; + // console.log(error_message); return [false, error_message]; }); } @@ -105,13 +118,9 @@ export async function TokenRefresh() { }); } export async function UserInfo() { - const accessToken = await getAccessToken(); + const config = await GetConfig(); return instance - .get("/api/v1/accounts/users/me/", { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) + .get("/api/v1/accounts/users/me/", config) .then((response) => { // console.log(JSON.stringify(response.data)); return [true, response.data]; @@ -124,6 +133,23 @@ export async function UserInfo() { }); } +export async function PatchUserInfo(info: PatchStudentData) { + const config = await GetConfig(); + return instance + .patch("/api/v1/accounts/users/me/", info, config) + .then((response) => { + console.log(JSON.stringify(response.data)); + return [true, response.data]; + }) + .catch((error) => { + let error_message = ""; + if (error.response) error_message = error.response.data; + else error_message = "Unable to reach servers"; + // console.log(error_message); + return [false, error_message]; + }); +} + export function UserActivate(activation: ActivationParams) { return instance .post("/api/v1/accounts/users/activation/", activation) @@ -147,14 +173,13 @@ export async function GetCourses() { }) .then((response) => { // console.log(JSON.stringify(response.data)); - return response.data; + return [true, response.data]; }) .catch((error) => { let error_message = ""; if (error.response) error_message = error.response.data; else error_message = "Unable to reach servers"; - console.log("Error getting courses", error_message); - return false; + return [false, error_message]; }); } @@ -168,45 +193,59 @@ export async function GetSemesters() { }) .then((response) => { // console.log(JSON.stringify(response.data)); - return response.data; + return [true, response.data]; }) .catch((error) => { let error_message = ""; if (error.response) error_message = error.response.data; else error_message = "Unable to reach servers"; - console.log("Error getting semesters", error_message); - return false; + return [false, error_message]; }); } export async function GetYearLevels() { - const accessToken = await getAccessToken(); + const config = await GetConfig(); return instance - .get("/api/v1/year_levels/", { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) + .get("/api/v1/year_levels/", config) .then((response) => { // console.log(JSON.stringify(response.data)); - return response.data; + return [true, response.data]; }) .catch((error) => { let error_message = ""; if (error.response) error_message = error.response.data; else error_message = "Unable to reach servers"; - console.log("Error getting year levels", error_message); - return false; + return [false, error_message]; + }); +} + +export async function GetSubjects( + course: string, + year_level: string, + semester: string +) { + const config = await GetConfig(); + return instance + .get( + "/api/v1/subjects/" + course + "/" + year_level + "/" + semester, + config + ) + .then((response) => { + // console.log(JSON.stringify(response.data)); + return [true, response.data]; + }) + .catch((error) => { + let error_message = ""; + if (error.response) error_message = error.response.data; + else error_message = "Unable to reach servers"; + return [false, error_message]; }); } export async function OnboardingUpdateStudentInfo(info: OnboardingParams) { - const accessToken = await getAccessToken(); - const headers = { - Authorization: `Bearer ${accessToken}`, - }; + const config = await GetConfig(); return instance - .patch("/api/v1/accounts/users/me/", info, { headers }) + .patch("/api/v1/accounts/users/me/", info, config) .then((response) => { console.log(JSON.stringify(response.data)); return [true, response.data]; diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 43aaf84..ee36630 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -1,30 +1,23 @@ import * as React from "react"; import { Pressable, GestureResponderEvent } from "react-native"; import styles from "../../styles"; - +import { colors } from "../../styles"; export interface props { children: React.ReactNode; onPress: (event: GestureResponderEvent) => void; - color: string; + color?: string; disabled?: boolean; } export default function Button({ disabled = false, ...props }: props) { - const rgb = props.color.match(/\d+/g); + if (!props.color) { + props.color = colors.secondary_3; + } return ( {props.children} diff --git a/src/components/Button/DrawerButton.tsx b/src/components/Button/DrawerButton.tsx index 5708d6e..8f81da3 100644 --- a/src/components/Button/DrawerButton.tsx +++ b/src/components/Button/DrawerButton.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Text, Pressable, GestureResponderEvent } from "react-native"; +import { Pressable, GestureResponderEvent } from "react-native"; import styles from "../../styles"; export interface props { diff --git a/src/components/DrawerSettings/CustomDrawerContent.tsx b/src/components/DrawerSettings/CustomDrawerContent.tsx index 845d732..49c1054 100644 --- a/src/components/DrawerSettings/CustomDrawerContent.tsx +++ b/src/components/DrawerSettings/CustomDrawerContent.tsx @@ -31,12 +31,12 @@ export default function CustomDrawerContent(props: {}) { ...{ justifyContent: "center" }, }} > - + Stud-E { dispatch(logout()); await AsyncStorage.clear(); @@ -57,11 +57,11 @@ export default function CustomDrawerContent(props: {}) { ...{ justifyContent: "center" }, }} > - + Stud-E { navigation.navigate("Home"); }} @@ -70,16 +70,25 @@ export default function CustomDrawerContent(props: {}) { Home { - navigation.navigate("UserInfo"); + navigation.navigate("User Info"); }} > - UserInfo + User Info { + navigation.navigate("Subjects"); + }} + > + + Subjects + + { dispatch(logout()); await AsyncStorage.clear(); @@ -100,11 +109,11 @@ export default function CustomDrawerContent(props: {}) { ...{ justifyContent: "center" }, }} > - + Stud-E { navigation.navigate("Login"); }} @@ -113,7 +122,7 @@ export default function CustomDrawerContent(props: {}) { Login { navigation.navigate("Register"); }} @@ -125,7 +134,7 @@ export default function CustomDrawerContent(props: {}) { {/* Debug buttons for accessing revalidation and activation page { navigation.navigate("Revalidation"); }} @@ -133,7 +142,7 @@ export default function CustomDrawerContent(props: {}) { Revalidation { navigation.navigate("Activation"); }} diff --git a/src/components/DrawerSettings/DrawerScreenSettings.tsx b/src/components/DrawerSettings/DrawerScreenSettings.tsx index 2a3ec73..3aaf1c7 100644 --- a/src/components/DrawerSettings/DrawerScreenSettings.tsx +++ b/src/components/DrawerSettings/DrawerScreenSettings.tsx @@ -10,21 +10,19 @@ const DrawerScreenSettings: DrawerNavigationOptions = { fontSize: font_sizes.medium, }, unmountOnBlur: true, - headerStyle: { backgroundColor: colors.login_color}, + headerStyle: { backgroundColor: colors.primary_1 }, headerTintColor: colors.text_default, drawerType: "slide", drawerLabelStyle: { color: colors.text_default, }, drawerStyle: { - backgroundColor: colors.login_color, + backgroundColor: colors.primary_1, width: 260, }, headerRight: () => ( - - + + ), }; diff --git a/src/icons/AppIcon/AppIcon.tsx b/src/icons/AppIcon/AppIcon.tsx index 8901270..ef45c52 100644 --- a/src/icons/AppIcon/AppIcon.tsx +++ b/src/icons/AppIcon/AppIcon.tsx @@ -1,20 +1,13 @@ import * as React from "react"; import { IconProps } from "../../interfaces/Interfaces"; - -import { Svg, Path } from "react-native-svg"; -import { colors } from "../../styles"; - +import { Image } from "react-native"; export default function AppIcon(props: IconProps) { return ( <> - - - + ); } diff --git a/src/icons/DropdownIcon/DropdownIcon.tsx b/src/icons/DropdownIcon/DropdownIcon.tsx index 224919e..27851ab 100644 --- a/src/icons/DropdownIcon/DropdownIcon.tsx +++ b/src/icons/DropdownIcon/DropdownIcon.tsx @@ -1,9 +1,7 @@ import * as React from "react"; import { IconProps } from "../../interfaces/Interfaces"; - import { Svg, Path } from "react-native-svg"; import { colors } from "../../styles"; -import finalPropsSelectorFactory from "react-redux/es/connect/selectorFactory"; export default function DropdownIcon(props: IconProps) { return ( diff --git a/src/img/app_icon_dark.png b/src/img/app_icon_dark.png new file mode 100644 index 0000000..a7e0cd0 Binary files /dev/null and b/src/img/app_icon_dark.png differ diff --git a/src/img/app_icon_light.png b/src/img/app_icon_light.png new file mode 100644 index 0000000..22e9cec Binary files /dev/null and b/src/img/app_icon_light.png differ diff --git a/src/routes/UserInfo/image/3135715.png b/src/img/user_profile_placeholder.png similarity index 100% rename from src/routes/UserInfo/image/3135715.png rename to src/img/user_profile_placeholder.png diff --git a/src/interfaces/Interfaces.tsx b/src/interfaces/Interfaces.tsx index d9671ec..2b6e526 100644 --- a/src/interfaces/Interfaces.tsx +++ b/src/interfaces/Interfaces.tsx @@ -47,26 +47,86 @@ export interface ActivationParams { token: string; } -export interface SemesterParams { +export interface OptionType { + label: string; + value: string; +} + +// Semester +export interface Semester { id: string; name: string; shortname: string; } -export interface YearLevelParams { +export type Semesters = Array; + +export type SemesterParams = [boolean, Semesters]; + +// Year Level +export interface YearLevel { id: string; name: string; shortname: string; } -export interface CourseParams { +export type YearLevels = Array; + +export type YearLevelParams = [boolean, YearLevels]; + +// Course +export interface Course { id: string; name: string; shortname: string; } +export type Courses = Array; +export type CourseParams = [boolean, Courses]; + +// Subject +export interface Subject { + name: string; + code: string; + // courses: any[]; // To-do + // year_levels: any[]; // To-do + // semesters: any[]; // To-do +} + +export type Subjects = Array; +export type SubjectParams = [boolean, Subjects]; + +// For dropdown menu export interface OnboardingParams { year_level: string; course: string; semester: string; } + +export interface PatchStudentData { + course?: string | null; + first_name?: string | null; + last_name?: string | null; + semester?: string | null; + subjects?: any[] | null; // To-do, replace 'any' with your actual type + year_level?: string | null; +} + +export interface StudentData { + first_name: string; + last_name: string; + email: string; + avatar: string; + student_id_number: string; + is_banned: boolean; + semester: string; + semester_shortname: string; + course: string; + course_shortname: string; + year_level: string; + yearlevel_shortname: string; + subjects: any[]; // To-do + username: string; +} + +export type UserInfoParams = [boolean, StudentData]; diff --git a/src/routes/Activation/Activation.tsx b/src/routes/Activation/Activation.tsx index dce7f24..10aab6a 100644 --- a/src/routes/Activation/Activation.tsx +++ b/src/routes/Activation/Activation.tsx @@ -53,7 +53,7 @@ export default function Activation() { marginBottom: 16, borderRadius: 4, width: "90%", - backgroundColor: colors.blue_1, + backgroundColor: colors.secondary_1, }} /> Activation @@ -61,7 +61,7 @@ export default function Activation() { {state} {uid + "\n" + token} diff --git a/src/routes/Home/Home.tsx b/src/routes/Home/Home.tsx index 5805287..7cc911a 100644 --- a/src/routes/Home/Home.tsx +++ b/src/routes/Home/Home.tsx @@ -1,4 +1,4 @@ -import styles from "../../styles"; +import styles, { Viewport } from "../../styles"; import { View, Text } from "react-native"; import { useSelector } from "react-redux"; import { RootState } from "../../features/redux/Store/Store"; @@ -9,7 +9,6 @@ import * as Location from "expo-location"; import GetDistance from "../../components/GetDistance/GetDistance"; import Button from "../../components/Button/Button"; import { colors } from "../../styles"; -import { startActivityAsync, ActivityAction } from "expo-intent-launcher"; type LocationType = Location.LocationObject; export default function Home() { @@ -18,47 +17,59 @@ export default function Home() { const [feedback, setFeedback] = useState( "To continue, please allow Stud-E permission to location services" ); - - async function requestLocation() { - let { status } = await Location.requestForegroundPermissionsAsync(); - if (status === "granted") { - getLocation(); - return; - } else if (status === "denied") { - setFeedback("Stud-E requires location services to function"); - setTimeout(() => { - startActivityAsync(ActivityAction.LOCATION_SOURCE_SETTINGS); - }, 3000); - console.log("Location Permission denied"); - } - } - - async function getLocation() { - let location = await Location.getCurrentPositionAsync({}); - setLocation(location); - let dist = GetDistance( - location.coords.latitude, - location.coords.longitude, - 8.4857, - 124.6565 - ); - setDist(Math.round(dist)); - } - useEffect(() => { - requestLocation(); - }, []); - const ustpCoords = { latitude: 8.4857, longitude: 124.6565, latitudeDelta: 0.000235, longitudeDelta: 0.000067, }; + + async function requestLocation() { + let { status } = await Location.requestForegroundPermissionsAsync(); + if (status !== "granted") { + setFeedback( + "Permission to access location was denied. Please allow permission" + ); + return; + } + if (status == "granted") { + let location = await Location.getCurrentPositionAsync({}); + if (location) { + setLocation(location); + getDistance(location); + } + } + } + useEffect(() => { + requestLocation(); + }, [location]); + + async function getDistance(location: LocationType) { + let dist = GetDistance( + location.coords.latitude, + location.coords.longitude, + 8.4857, // LatitudeDelta + 124.6565 // LongitudeDelta + ); + setDist(Math.round(dist)); + } + function CustomMap() { - if (dist !== null && location !== null) { + if (dist && location) { if (dist <= 1.5) { // Just switch this condition for map debugging - return ; + return ( + + ); } else { return ( @@ -68,8 +79,8 @@ export default function Home() { {feedback} - ); } } - const creds = useSelector((state: RootState) => state.user.user); return ( diff --git a/src/routes/Login/Login.tsx b/src/routes/Login/Login.tsx index a4283cd..f45d503 100644 --- a/src/routes/Login/Login.tsx +++ b/src/routes/Login/Login.tsx @@ -30,25 +30,17 @@ export default function Login() { const [creds, setCreds] = useState({ username: "", password: "", - error: "", }); + const [error, setError] = useState(""); return ( - + Student Login - + - {creds.error} + {error} diff --git a/src/routes/Onboarding/Onboarding.tsx b/src/routes/Onboarding/Onboarding.tsx index d8e5fff..fc9ab48 100644 --- a/src/routes/Onboarding/Onboarding.tsx +++ b/src/routes/Onboarding/Onboarding.tsx @@ -3,17 +3,16 @@ import styles from "../../styles"; import { View, Text } from "react-native"; import { useNavigation } from "@react-navigation/native"; import { - CourseParams, + Course, RootDrawerParamList, - SemesterParams, - YearLevelParams, + Semester, + YearLevel, } from "../../interfaces/Interfaces"; import { colors } from "../../styles"; -import { AnimatePresence, MotiView } from "moti"; -import { useEffect, useState } from "react"; +import { MotiView } from "moti"; +import { useState } from "react"; import Button from "../../components/Button/Button"; import DropDownPicker from "react-native-dropdown-picker"; -import isStringEmpty from "../../components/IsStringEmpty/IsStringEmpty"; import { useQuery } from "@tanstack/react-query"; import { GetCourses, @@ -24,6 +23,8 @@ import { import { useDispatch } from "react-redux"; import { unsetOnboarding } from "../../features/redux/slices/StatusSlice/StatusSlice"; import { setUser } from "../../features/redux/slices/UserSlice/UserSlice"; +import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer"; +import AnimatedContainerNoScroll from "../../components/AnimatedContainer/AnimatedContainerNoScroll"; export default function Onboarding() { const navigation = useNavigation(); const dispatch = useDispatch(); @@ -40,7 +41,7 @@ export default function Onboarding() { queryKey: ["semesters"], queryFn: GetSemesters, onSuccess: (data) => { - let semesters = data.map((item: SemesterParams) => ({ + let semesters = data[1].map((item: Semester) => ({ label: item.name, value: item.name, })); @@ -58,7 +59,7 @@ export default function Onboarding() { queryKey: ["year_levels"], queryFn: GetYearLevels, onSuccess: (data) => { - let year_levels = data.map((item: YearLevelParams) => ({ + let year_levels = data[1].map((item: YearLevel) => ({ label: item.name, value: item.name, })); @@ -79,7 +80,7 @@ export default function Onboarding() { queryKey: ["courses"], queryFn: GetCourses, onSuccess: (data) => { - let courses = data.map((item: CourseParams) => ({ + let courses = data[1].map((item: Course) => ({ label: item.name, value: item.name, })); @@ -97,7 +98,7 @@ export default function Onboarding() { } return ( - + @@ -144,34 +145,18 @@ export default function Onboarding() { setSemesterOpen(false); setYearLevelOpen(false); }} + style={styles.input} setValue={setSelectedCourse} placeholder="Choose your course" - containerStyle={{ - ...styles.dropdown_template, - ...{ zIndex: 3000 }, + containerStyle={{ zIndex: 3000 }} + textStyle={{ + ...styles.text_white_small_bold, + ...{ textAlign: "center" }, }} - dropDownContainerStyle={{ backgroundColor: "white" }} + dropDownContainerStyle={{ backgroundColor: colors.primary_2 }} /> { - setSemesterOpen(open); - setCourseOpen(false); - setYearLevelOpen(false); - }} - setValue={setSelectedSemester} - placeholder="Current semester" - containerStyle={{ - ...styles.dropdown_template, - ...{ zIndex: 2000 }, - }} - dropDownContainerStyle={{ backgroundColor: "white" }} - /> - + { + setSemesterOpen(open); + setCourseOpen(false); + setYearLevelOpen(false); + }} + style={styles.input} + setValue={setSelectedSemester} + placeholder="Current semester" + containerStyle={{ zIndex: 1000 }} + textStyle={{ + ...styles.text_white_small_bold, + ...{ textAlign: "center" }, + }} + dropDownContainerStyle={{ backgroundColor: colors.primary_2 }} /> + {error} - {error} - + ); } diff --git a/src/routes/Register/Register.tsx b/src/routes/Register/Register.tsx index 97c1b8c..dc3d55d 100644 --- a/src/routes/Register/Register.tsx +++ b/src/routes/Register/Register.tsx @@ -34,25 +34,16 @@ export default function Register() { return ( - + Student Signup - + - + @@ -64,7 +55,7 @@ export default function Register() { @@ -76,7 +67,7 @@ export default function Register() { @@ -93,7 +84,7 @@ export default function Register() { Register diff --git a/src/routes/Revalidation/Revalidation.tsx b/src/routes/Revalidation/Revalidation.tsx index e2f099e..ce5b1dc 100644 --- a/src/routes/Revalidation/Revalidation.tsx +++ b/src/routes/Revalidation/Revalidation.tsx @@ -58,7 +58,7 @@ export default function Revalidation() { - + {state} diff --git a/src/routes/SubjectsPage/SubjectsPage.tsx b/src/routes/SubjectsPage/SubjectsPage.tsx new file mode 100644 index 0000000..51d36cb --- /dev/null +++ b/src/routes/SubjectsPage/SubjectsPage.tsx @@ -0,0 +1,223 @@ +import * as React from "react"; +import styles from "../../styles"; +import { + View, + Text, + TextInput, + NativeSyntheticEvent, + TextInputChangeEventData, +} from "react-native"; +import { useState } from "react"; +import { + SemesterParams, + UserInfoParams, + Semester, + SubjectParams, + Subject, + YearLevel, + Course, + OptionType, + Subjects, +} from "../../interfaces/Interfaces"; +import Button from "../../components/Button/Button"; +import { Image } from "react-native"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + GetCourses, + GetSemesters, + GetSubjects, + GetYearLevels, + PatchUserInfo, + UserInfo, +} from "../../components/Api/Api"; +import { colors } from "../../styles"; +import DropDownPicker from "react-native-dropdown-picker"; +import { ValueType } from "react-native-dropdown-picker"; +import AnimatedContainerNoScroll from "../../components/AnimatedContainer/AnimatedContainerNoScroll"; + +export default function SubjectsPage() { + const queryClient = useQueryClient(); + // User Info + const [user, setUser] = useState({ + first_name: "", + last_name: "", + year_level: "", + yearlevel_shortname: "", + semester: "", + semester_shortname: "", + course: "", + course_shortname: "", + avatar: "", + student_id_number: "", + subjects: [] as Subjects, + }); + const [displayName, setDisplayName] = useState({ + first_name: "", + last_name: "", + }); + const StudentInfo = useQuery({ + queryKey: ["user"], + queryFn: UserInfo, + onSuccess: (data: UserInfoParams) => { + // console.log(data[1]); + setUser({ + ...user, + first_name: data[1].first_name, + last_name: data[1].last_name, + year_level: data[1].year_level, + semester: data[1].semester, + course: data[1].course, + avatar: data[1].avatar, + student_id_number: data[1].student_id_number, + subjects: data[1].subjects, + }); + setDisplayName({ + first_name: data[1].first_name, + last_name: data[1].last_name, + }); + setSelectedSubjects(user.subjects); + }, + }); + const mutation = useMutation({ + mutationFn: PatchUserInfo, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["user"] }); + queryClient.invalidateQueries({ queryKey: ["subjects"] }); + setSelectedSubjects([]); + }, + }); + + // Subjects + + const [selected_subjects, setSelectedSubjects] = useState([]); + const [subjectsOpen, setSubjectsOpen] = useState(false); + const [subjects, setSubjects] = useState([]); + + const Subjects = useQuery({ + enabled: StudentInfo.isFetched, + queryKey: ["subjects"], + queryFn: async () => { + let data; + if (StudentInfo.data) { + if ( + StudentInfo.data[1].course_shortname && + StudentInfo.data[1].yearlevel_shortname && + StudentInfo.data[1].semester_shortname + ) { + data = await GetSubjects( + StudentInfo.data[1].course_shortname, + StudentInfo.data[1].yearlevel_shortname, + StudentInfo.data[1].semester_shortname + ); + } + } + if (data) { + if (!data[0]) { + throw new Error("Error with query" + data[1]); + } + if (!data[1]) { + throw new Error("User has no course, year level, or semester!"); + } + // console.log("Subjects available:", data[1]); + } + return data; + }, + onSuccess: (data: SubjectParams) => { + let subjectsData = data[1].map((subject: Subject) => ({ + label: subject.name, + value: subject.name, + })); + // Update the 'subjects' state + setSelectedSubjects(user.subjects); + setSubjects(subjectsData); + }, + }); + + // Profile photo + function Avatar() { + if (user.avatar) { + return ; + } else { + return ( + + ); + } + } + + return ( + + + + + + {(displayName.first_name || "Undefined") + + " " + + (displayName.last_name || "User") + + "\n" + + user.student_id_number} + + + + + + + Subjects + + + { + setSubjectsOpen(open); + }} + setValue={setSelectedSubjects} + placeholderStyle={{ + ...styles.text_white_tiny_bold, + ...{ textAlign: "left" }, + }} + placeholder="Select subjects" + multipleText="Select subjects" + style={styles.input} + textStyle={{ + ...styles.text_white_tiny_bold, + ...{ textAlign: "left" }, + }} + dropDownContainerStyle={{ + backgroundColor: colors.primary_2, + borderWidth: 0, + zIndex: 1000, + maxHeight: 512, + }} + dropDownDirection="TOP" + /> + + + + + + + + ); +} diff --git a/src/routes/UserInfo/UserInfo.tsx b/src/routes/UserInfo/UserInfo.tsx deleted file mode 100644 index 856f817..0000000 --- a/src/routes/UserInfo/UserInfo.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import * as React from "react"; -import styles from "../../styles"; -import { - View, - Text, - TextInput, - NativeSyntheticEvent, - TextInputChangeEventData, -} from "react-native"; -import { colors } from "../../styles"; -import { useState, useEffect } from "react"; -import Button from "../../components/Button/Button"; -import { useNavigation } from "@react-navigation/native"; -import { RootDrawerParamList } from "../../interfaces/Interfaces"; -import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer"; -import { TouchableOpacity, Image } from "react-native"; -import { ScrollView } from "react-native-gesture-handler"; -import SelectDropdown from "react-native-select-dropdown"; -import DropdownIcon from "../../icons/DropdownIcon/DropdownIcon"; -import { useQuery } from "react-query"; -import { UserInfo as GetUserInfo } from "../../components/Api/Api"; -import { err } from "react-native-svg/lib/typescript/xml"; - -export default function UserInfo() { - const navigation = useNavigation(); - const [isEditable, setIsEditable] = useState(false); - const options = ["", "", "", ""]; - const [isActive, setIsActive] = useState(false); - const toggleUserActive = () => { - setIsActive(!isActive); - }; - //const dispatch = useDispatch(); - // const creds = useSelector((state: RootState) => state.auth.creds); - const [user, setUser] = useState({ - first_name: "", - last_name: "", - year_level: "", - semester: "", - course: "", - }); - const { data, isLoading, error } = useQuery("user", UserInfo, { - retry: 0, - onSuccess: (data) => console.log(data), - }); - if (!isLoading && !error) { - return ( - - - - Kurt Toledo - - - - - - Student {isActive ? "Active" : "Inactive"} - - - - - - - First Name - - - - ): void => { - setUser({ ...user, first_name: e.nativeEvent.text }); - }} - /> - - - - - Last Name - - - - ): void => { - setUser({ ...user, first_name: e.nativeEvent.text }); - }} - /> - - - - - - Year Level - - - - ): void => { - setUser({ ...user, first_name: e.nativeEvent.text }); - }} - /> - - - - - Semester - - - - ): void => { - setUser({ ...user, first_name: e.nativeEvent.text }); - }} - /> - - - - - Course - - - - ): void => { - setUser({ ...user, first_name: e.nativeEvent.text }); - }} - /> - - - - - - Subject - - - { - console.log(selectedItem, index); - }} - renderDropdownIcon={() => } - buttonTextStyle={{ - color: "white", - }} - dropdownStyle={{ - backgroundColor: "#E3963E", - }} - data={options} - buttonStyle={{ - width: "90%", - marginLeft: 10, - backgroundColor: "#E3963E", - borderRadius: 8, - }} - /> - - - setIsEditable(!isEditable)} - > - - {isEditable ? "Save" : "Edit Profile"} - - - - - ); - } -} diff --git a/src/routes/UserInfoPage/UserInfoPage.tsx b/src/routes/UserInfoPage/UserInfoPage.tsx new file mode 100644 index 0000000..5f39149 --- /dev/null +++ b/src/routes/UserInfoPage/UserInfoPage.tsx @@ -0,0 +1,339 @@ +import * as React from "react"; +import styles from "../../styles"; +import { + View, + Text, + TextInput, + NativeSyntheticEvent, + TextInputChangeEventData, +} from "react-native"; +import { useState } from "react"; +import { + SemesterParams, + UserInfoParams, + Semester, + SubjectParams, + Subject, + YearLevel, + Course, + OptionType, + Subjects, +} from "../../interfaces/Interfaces"; +import Button from "../../components/Button/Button"; +import { Image } from "react-native"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + GetCourses, + GetSemesters, + GetSubjects, + GetYearLevels, + PatchUserInfo, + UserInfo, +} from "../../components/Api/Api"; +import { colors } from "../../styles"; +import DropDownPicker from "react-native-dropdown-picker"; +import { ValueType } from "react-native-dropdown-picker"; +import AnimatedContainerNoScroll from "../../components/AnimatedContainer/AnimatedContainerNoScroll"; + +export default function UserInfoPage() { + const queryClient = useQueryClient(); + // User Info + const [user, setUser] = useState({ + first_name: "", + last_name: "", + year_level: "", + yearlevel_shortname: "", + semester: "", + semester_shortname: "", + course: "", + course_shortname: "", + avatar: "", + student_id_number: "", + }); + const [displayName, setDisplayName] = useState({ + first_name: "", + last_name: "", + }); + const StudentInfo = useQuery({ + queryKey: ["user"], + queryFn: UserInfo, + onSuccess: (data: UserInfoParams) => { + // console.log(data[1]); + setUser({ + ...user, + first_name: data[1].first_name, + last_name: data[1].last_name, + year_level: data[1].year_level, + semester: data[1].semester, + course: data[1].course, + avatar: data[1].avatar, + student_id_number: data[1].student_id_number, + }); + setDisplayName({ + first_name: data[1].first_name, + last_name: data[1].last_name, + }); + setSelectedCourse(data[1].course); + setSelectedSemester(data[1].semester); + setSelectedYearLevel(data[1].year_level); + }, + }); + const mutation = useMutation({ + mutationFn: PatchUserInfo, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["user"] }); + queryClient.invalidateQueries({ queryKey: ["subjects"] }); + }, + }); + + // Semester + const [selected_semester, setSelectedSemester] = useState(""); + const [semesterOpen, setSemesterOpen] = useState(false); + const [semesters, setSemesters] = useState([]); + const Semesters = useQuery({ + queryKey: ["semesters"], + queryFn: GetSemesters, + onSuccess: (data: SemesterParams) => { + let semestersData = data[1].map((semester: Semester) => ({ + label: semester.name, + value: semester.name, + shortname: semester.shortname, + })); + // Update the 'semesters' state + setSemesters(semestersData); + }, + }); + + // Year Level + const [selected_yearlevel, setSelectedYearLevel] = useState(""); + const [yearLevelOpen, setYearLevelOpen] = useState(false); + const [year_levels, setYearLevels] = useState([]); + const yearlevel_query = useQuery({ + queryKey: ["year_levels"], + queryFn: GetYearLevels, + onSuccess: (data) => { + let year_levels = data[1].map((yearlevel: YearLevel) => ({ + label: yearlevel.name, + value: yearlevel.name, + })); + setYearLevels(year_levels); + }, + }); + + // Course + const [selected_course, setSelectedCourse] = useState(""); + const [courseOpen, setCourseOpen] = useState(false); + const [courses, setCourses] = useState([]); + const course_query = useQuery({ + queryKey: ["courses"], + queryFn: GetCourses, + onSuccess: (data) => { + let courses = data[1].map((course: Course) => ({ + label: course.name, + value: course.name, + })); + setCourses(courses); + }, + }); + // Toggle editing of profile + const [isEditable, setIsEditable] = useState(false); + // Profile photo + function Avatar() { + if (user.avatar) { + return ; + } else { + return ( + + ); + } + } + + return ( + + + + + + {(displayName.first_name || "Undefined") + + " " + + (displayName.last_name || "User") + + "\n" + + user.student_id_number} + + + + + + + First Name + + + + ): void => { + setUser({ ...user, first_name: e.nativeEvent.text }); + }} + value={user.first_name} + /> + + + + + Last Name + + + + ): void => { + setUser({ ...user, last_name: e.nativeEvent.text }); + }} + value={user.last_name} + /> + + + + + Year Level + + + { + setYearLevelOpen(open); + setSemesterOpen(false); + setCourseOpen(false); + }} + setValue={setSelectedYearLevel} + placeholder={user.year_level} + placeholderStyle={{ + ...styles.text_white_tiny_bold, + ...{ textAlign: "left" }, + }} + style={styles.input} + textStyle={{ + ...styles.text_white_tiny_bold, + ...{ textAlign: "left" }, + }} + dropDownContainerStyle={{ + backgroundColor: colors.primary_2, + zIndex: 4000, + borderWidth: 0, + }} + dropDownDirection="TOP" + /> + + + + + Semester + + + { + setYearLevelOpen(false); + setSemesterOpen(open); + setCourseOpen(false); + }} + setValue={setSelectedSemester} + placeholder={user.semester} + placeholderStyle={{ + ...styles.text_white_tiny_bold, + ...{ textAlign: "left" }, + }} + style={styles.input} + textStyle={{ + ...styles.text_white_tiny_bold, + ...{ textAlign: "left" }, + }} + dropDownContainerStyle={{ + backgroundColor: colors.primary_2, + zIndex: 3000, + borderWidth: 0, + }} + dropDownDirection="TOP" + /> + + + + + Course + + + { + setYearLevelOpen(false); + setSemesterOpen(false); + setCourseOpen(open); + }} + setValue={setSelectedCourse} + placeholder={user.course} + placeholderStyle={{ + ...styles.text_white_tiny_bold, + ...{ textAlign: "left" }, + }} + style={styles.input} + textStyle={{ + ...styles.text_white_tiny_bold, + ...{ textAlign: "left" }, + }} + dropDownContainerStyle={{ + backgroundColor: colors.primary_2, + zIndex: 2000, + borderWidth: 0, + }} + dropDownDirection="TOP" + /> + + + + + + + + + ); +} diff --git a/src/styles.tsx b/src/styles.tsx index 88d451e..0767444 100644 --- a/src/styles.tsx +++ b/src/styles.tsx @@ -1,39 +1,39 @@ import { StyleSheet, Dimensions } from "react-native"; -const width = Dimensions.get("window").width; -const height = Dimensions.get("window").height; - -const containerWidth = width - width * 0.08; -const containerHeight = height - height * 0.01; +export const Viewport = { + width: Dimensions.get("window").width, + height: Dimensions.get("window").height, +}; export const colors = { - orange_1: "#FFDEAD", - orange_2: "#FFE2C1", - orange_3: "#C07624", - blue_1: "#E3963E", - blue_2: "#FFAC1C", - blue_3: "#FFAC1C", - text_default: "white", + primary_1: "#1C2C3F", + primary_2: "#445467", + primary_3: "#606F81", + primary_4: "#b4d0f3", + secondary_1: "#1E1F3D", + secondary_2: "#626297", + secondary_3: "#7a7abd", + secondary_4: "#FFE9CE", + secondary_5: "#FFF5E9", + text_default: "#FFFF", text_error: "#e32d1e", - text_success: "green", - icon_color: "white", - login_color: "#0047AB", - reg_color: "#0096FF", - head: "white", - blue_disabled: "#C07624", + text_success: "#2ecc71", + icon_color: "#FFFF", + head: "#FFFF", }; export const font_sizes = { tiny: 12, small: 16, medium: 24, + medium_large: 30, large: 36, xl: 48, }; const styles = StyleSheet.create({ background: { - backgroundColor: colors.orange_1, + backgroundColor: colors.secondary_1, height: "100%", width: "100%", }, @@ -75,6 +75,12 @@ const styles = StyleSheet.create({ fontWeight: "bold", textAlign: "center", }, + text_white_medium_large: { + color: colors.text_default, + fontSize: font_sizes.medium_large, + fontWeight: "bold", + textAlign: "center", + }, text_white_large: { color: colors.text_default, fontSize: font_sizes.large, @@ -87,6 +93,18 @@ const styles = StyleSheet.create({ fontWeight: "bold", textAlign: "center", }, + text_white_small_bold: { + color: colors.text_default, + fontSize: font_sizes.small, + fontWeight: "bold", + textAlign: "center", + }, + text_white_tiny_bold: { + color: colors.text_default, + fontSize: font_sizes.tiny, + fontWeight: "bold", + textAlign: "center", + }, button_template: { justifyContent: "center", alignSelf: "center", @@ -98,63 +116,47 @@ const styles = StyleSheet.create({ marginHorizontal: 8, padding: 8, borderRadius: 16, - width: width * 0.4, + width: Viewport.width * 0.4, }, text_input: { color: colors.text_default, - backgroundColor: colors.blue_1, + backgroundColor: colors.primary_2, + borderColor: colors.primary_4, + borderWidth: 1, padding: 10, borderRadius: 8, - width: width * 0.5, + width: Viewport.width * 0.5, }, dropdown_template: { - borderRadius: 16, + backgroundColor: colors.primary_2, + containerStyle: colors.primary_2, + borderRadius: 8, width: "70%", - marginVertical: 6, }, map: { - flex: 1, - height: containerHeight, - width: containerWidth, + height: Viewport.height * 0.8, + width: Viewport.width * 0.8, alignSelf: "center", }, profile: { - height: 80, - width: 80, - alignSelf: 'center', - }, + height: 64, + width: 64, + alignSelf: "center", + borderRadius: 150 / 2, + overflow: "hidden", + padding: 0, + }, input: { - height: 40, - margin: 12, - marginRight: 30, + paddingHorizontal: 8, + marginVertical: 2, borderWidth: 1, color: colors.text_default, - backgroundColor: colors.blue_1, + backgroundColor: colors.primary_2, borderRadius: 8, - borderColor: '#FFAC1C', - padding: 8, + borderColor: colors.primary_3, }, - formGroup: { - display: "flex", - flexDirection: "row", - alignItems: "center", - }, - text: { - marginLeft: 5, - color: colors.text_default, - fontSize: font_sizes.small, - fontWeight: "bold", - }, - button: { - padding: 10, - backgroundColor: colors.blue_2, - borderRadius: 5, - }, - activeText: { - color: 'green', - }, - inactiveText: { - color: 'white', + padding: { + paddingVertical: 8, }, }); export default styles;