mirror of
https://github.com/lemeow125/StudE-Frontend.git
synced 2024-11-17 06:19:25 +08:00
Merge pull request #2 from lemeow125/feature/onboarding
Feature/onboarding
This commit is contained in:
commit
3f40140577
18 changed files with 559 additions and 144 deletions
33
App.tsx
33
App.tsx
|
@ -17,6 +17,7 @@ import Onboarding from "./src/routes/Onboarding/Onboarding";
|
|||
import Revalidation from "./src/routes/Revalidation/Revalidation";
|
||||
import Activation from "./src/routes/Activation/Activation";
|
||||
import { useState, useEffect } from "react";
|
||||
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
|
||||
|
||||
const Drawer = createDrawerNavigator();
|
||||
|
||||
|
@ -35,6 +36,8 @@ const linking = {
|
|||
},
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
export default function App() {
|
||||
const [initialRoute, setInitialRoute] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
|
@ -50,20 +53,22 @@ export default function App() {
|
|||
}, [initialRoute]);
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<NavigationContainer linking={linking}>
|
||||
<Drawer.Navigator
|
||||
initialRouteName="Revalidation"
|
||||
drawerContent={CustomDrawerContent}
|
||||
screenOptions={DrawerScreenSettings}
|
||||
>
|
||||
<Drawer.Screen name="Home" component={Home} />
|
||||
<Drawer.Screen name="Login" component={Login} />
|
||||
<Drawer.Screen name="Register" component={Register} />
|
||||
<Drawer.Screen name="Onboarding" component={Onboarding} />
|
||||
<Drawer.Screen name="Revalidation" component={Revalidation} />
|
||||
<Drawer.Screen name="Activation" component={Activation} />
|
||||
</Drawer.Navigator>
|
||||
</NavigationContainer>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<NavigationContainer linking={linking}>
|
||||
<Drawer.Navigator
|
||||
initialRouteName="Revalidation"
|
||||
drawerContent={CustomDrawerContent}
|
||||
screenOptions={DrawerScreenSettings}
|
||||
>
|
||||
<Drawer.Screen name="Home" component={Home} />
|
||||
<Drawer.Screen name="Login" component={Login} />
|
||||
<Drawer.Screen name="Register" component={Register} />
|
||||
<Drawer.Screen name="Onboarding" component={Onboarding} />
|
||||
<Drawer.Screen name="Revalidation" component={Revalidation} />
|
||||
<Drawer.Screen name="Activation" component={Activation} />
|
||||
</Drawer.Navigator>
|
||||
</NavigationContainer>
|
||||
</QueryClientProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
|
46
package-lock.json
generated
46
package-lock.json
generated
|
@ -13,6 +13,7 @@
|
|||
"@react-navigation/native": "^6.1.7",
|
||||
"@react-navigation/native-stack": "^6.9.13",
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"@tanstack/react-query": "^4.29.19",
|
||||
"axios": "^1.4.0",
|
||||
"expo": "~48.0.18",
|
||||
"expo-linking": "~4.0.1",
|
||||
|
@ -20,6 +21,7 @@
|
|||
"moti": "^0.25.3",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.71.8",
|
||||
"react-native-dropdown-picker": "^5.4.6",
|
||||
"react-native-gesture-handler": "~2.9.0",
|
||||
"react-native-reanimated": "~2.14.4",
|
||||
"react-native-safe-area-context": "4.5.0",
|
||||
|
@ -5201,6 +5203,41 @@
|
|||
"@sinonjs/commons": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "4.29.19",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.19.tgz",
|
||||
"integrity": "sha512-uPe1DukeIpIHpQi6UzIgBcXsjjsDaLnc7hF+zLBKnaUlh7jFE/A+P8t4cU4VzKPMFB/C970n/9SxtpO5hmIRgw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "4.29.19",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.19.tgz",
|
||||
"integrity": "sha512-XiTIOHHQ5Cw1WUlHaD4fmVUMhoWjuNJlAeJGq7eM4BraI5z7y8WkZO+NR8PSuRnQGblpuVdjClQbDFtwxTtTUw==",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "4.29.19",
|
||||
"use-sync-external-store": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-native": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hammerjs": {
|
||||
"version": "2.0.41",
|
||||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
|
||||
|
@ -12160,6 +12197,15 @@
|
|||
"nullthrows": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-dropdown-picker": {
|
||||
"version": "5.4.6",
|
||||
"resolved": "https://registry.npmjs.org/react-native-dropdown-picker/-/react-native-dropdown-picker-5.4.6.tgz",
|
||||
"integrity": "sha512-T1XBHbE++M6aRU3wFYw3MvcOuabhWZ29RK/Ivdls2r1ZkZ62iEBZknLUPeVLMX3x6iUxj4Zgr3X2DGlEGXeHsA==",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-gesture-handler": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.9.0.tgz",
|
||||
|
|
|
@ -14,20 +14,22 @@
|
|||
"@react-navigation/native": "^6.1.7",
|
||||
"@react-navigation/native-stack": "^6.9.13",
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"@tanstack/react-query": "^4.29.19",
|
||||
"axios": "^1.4.0",
|
||||
"expo": "~48.0.18",
|
||||
"expo-linking": "~4.0.1",
|
||||
"expo-status-bar": "~1.4.4",
|
||||
"moti": "^0.25.3",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.71.8",
|
||||
"react-native-dropdown-picker": "^5.4.6",
|
||||
"react-native-gesture-handler": "~2.9.0",
|
||||
"react-native-reanimated": "~2.14.4",
|
||||
"react-native-safe-area-context": "4.5.0",
|
||||
"react-native-screens": "~3.20.0",
|
||||
"react-native-svg": "13.4.0",
|
||||
"react-redux": "^8.1.1",
|
||||
"redux": "^4.2.1",
|
||||
"expo-linking": "~4.0.1"
|
||||
"redux": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
|
|
|
@ -3,6 +3,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
|
|||
import {
|
||||
ActivationParams,
|
||||
LoginParams,
|
||||
OnboardingParams,
|
||||
RegistrationParams,
|
||||
} from "../../interfaces/Interfaces";
|
||||
|
||||
|
@ -51,7 +52,10 @@ export function UserRegister(register: RegistrationParams) {
|
|||
return [true, response.status];
|
||||
})
|
||||
.catch((error) => {
|
||||
return [false, error.response.status, error.response.data];
|
||||
let error_message = "";
|
||||
if (error.response) error_message = error.response.data;
|
||||
else error_message = "Unable to reach servers";
|
||||
return [false, error_message];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -70,8 +74,10 @@ export function UserLogin(user: LoginParams) {
|
|||
return [true];
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Login Failed:" + JSON.stringify(error.response.data));
|
||||
return [false, error.response.data];
|
||||
let error_message = "";
|
||||
if (error.response) error_message = error.response.data;
|
||||
else error_message = "Unable to reach servers";
|
||||
return [false, error_message];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -88,11 +94,14 @@ export async function TokenRefresh() {
|
|||
"Token refresh success! New Access Token",
|
||||
response.data.access
|
||||
);*/
|
||||
return [true];
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Refresh Failed: " + JSON.stringify(error.response.data));
|
||||
return [false, error.response.data];
|
||||
let error_message = "";
|
||||
if (error.response) error_message = error.response.data;
|
||||
else error_message = "Unable to reach servers";
|
||||
console.log("Token Refresh error:", error_message);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
export async function UserInfo() {
|
||||
|
@ -105,11 +114,13 @@ export async function UserInfo() {
|
|||
})
|
||||
.then((response) => {
|
||||
// console.log(JSON.stringify(response.data));
|
||||
return response.data;
|
||||
return [true, response.data];
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("User Info Error", error.response.data);
|
||||
return [false, error.response.data];
|
||||
let error_message = "";
|
||||
if (error.response) error_message = error.response.data;
|
||||
else error_message = "Unable to reach servers";
|
||||
return [false, error_message];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -125,3 +136,86 @@ export function UserActivate(activation: ActivationParams) {
|
|||
}
|
||||
|
||||
// App APIs
|
||||
|
||||
export async function GetCourses() {
|
||||
const accessToken = await getAccessToken();
|
||||
return instance
|
||||
.get("/api/v1/courses/", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
// console.log(JSON.stringify(response.data));
|
||||
return 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;
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetSemesters() {
|
||||
const accessToken = await getAccessToken();
|
||||
return instance
|
||||
.get("/api/v1/semesters/", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
// console.log(JSON.stringify(response.data));
|
||||
return 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;
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetYearLevels() {
|
||||
const accessToken = await getAccessToken();
|
||||
return instance
|
||||
.get("/api/v1/year_levels/", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
// console.log(JSON.stringify(response.data));
|
||||
return 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;
|
||||
});
|
||||
}
|
||||
|
||||
export async function OnboardingUpdateStudentInfo(info: OnboardingParams) {
|
||||
const accessToken = await getAccessToken();
|
||||
const headers = {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
};
|
||||
return instance
|
||||
.patch("/api/v1/accounts/users/me/", info, { headers })
|
||||
.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 updating onboarding info", error_message);
|
||||
return [false, error_message];
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,15 +6,25 @@ export interface props {
|
|||
children: React.ReactNode;
|
||||
onPress: (event: GestureResponderEvent) => void;
|
||||
color: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function Button(props: props) {
|
||||
export default function Button({ disabled = false, ...props }: props) {
|
||||
const rgb = props.color.match(/\d+/g);
|
||||
return (
|
||||
<Pressable
|
||||
disabled={disabled}
|
||||
onPress={props.onPress}
|
||||
style={{
|
||||
...styles.button_template,
|
||||
...{ backgroundColor: props.color, width: "50%" },
|
||||
...{
|
||||
backgroundColor: disabled
|
||||
? rgb
|
||||
? `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.3)`
|
||||
: "rgba(0, 0, 0, 0)"
|
||||
: props.color,
|
||||
width: "50%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
|
|
@ -14,16 +14,40 @@ import DrawerButton from "../Button/DrawerButton";
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "../../features/redux/Store/Store";
|
||||
import LogoutIcon from "../../icons/LogoutIcon/LogoutIcon";
|
||||
import { clear } from "../../features/redux/slices/AuthSlice/AuthSlice";
|
||||
import { logout } from "../../features/redux/slices/StatusSlice/StatusSlice";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
export default function CustomDrawerContent(props: {}) {
|
||||
const navigation = useNavigation<RootDrawerParamList>();
|
||||
const logged_in = useSelector(
|
||||
(state: RootState) => state.auth.creds.logged_in
|
||||
);
|
||||
const status = useSelector((state: RootState) => state.status);
|
||||
const dispatch = useDispatch();
|
||||
if (logged_in) {
|
||||
if (status.logged_in && status.onboarding) {
|
||||
return (
|
||||
<DrawerContentScrollView {...props}>
|
||||
<View
|
||||
style={{
|
||||
...styles.flex_row,
|
||||
...{ justifyContent: "center" },
|
||||
}}
|
||||
>
|
||||
<AppIcon size={32} />
|
||||
<Text style={styles.text_white_medium}>Stud-E</Text>
|
||||
</View>
|
||||
|
||||
<DrawerButton
|
||||
color={colors.blue_2}
|
||||
onPress={async () => {
|
||||
dispatch(logout());
|
||||
await AsyncStorage.clear();
|
||||
navigation.navigate("Login");
|
||||
}}
|
||||
>
|
||||
<LogoutIcon size={32} />
|
||||
<Text style={styles.text_white_medium}>Logout</Text>
|
||||
</DrawerButton>
|
||||
</DrawerContentScrollView>
|
||||
);
|
||||
} else if (status.logged_in) {
|
||||
return (
|
||||
<DrawerContentScrollView {...props}>
|
||||
<View
|
||||
|
@ -47,7 +71,7 @@ export default function CustomDrawerContent(props: {}) {
|
|||
<DrawerButton
|
||||
color={colors.blue_2}
|
||||
onPress={async () => {
|
||||
dispatch(await clear());
|
||||
dispatch(logout());
|
||||
await AsyncStorage.clear();
|
||||
navigation.navigate("Login");
|
||||
}}
|
||||
|
@ -69,15 +93,6 @@ export default function CustomDrawerContent(props: {}) {
|
|||
<AppIcon size={32} />
|
||||
<Text style={styles.text_white_medium}>Stud-E</Text>
|
||||
</View>
|
||||
<DrawerButton
|
||||
color={colors.blue_2}
|
||||
onPress={() => {
|
||||
navigation.navigate("Home");
|
||||
}}
|
||||
>
|
||||
<HomeIcon size={32} />
|
||||
<Text style={styles.text_white_medium}>Home</Text>
|
||||
</DrawerButton>
|
||||
<DrawerButton
|
||||
color={colors.blue_2}
|
||||
onPress={() => {
|
||||
|
@ -90,7 +105,6 @@ export default function CustomDrawerContent(props: {}) {
|
|||
<DrawerButton
|
||||
color={colors.blue_2}
|
||||
onPress={() => {
|
||||
dispatch(clear());
|
||||
navigation.navigate("Register");
|
||||
}}
|
||||
>
|
||||
|
|
3
src/components/IsStringEmpty/IsStringEmpty.tsx
Normal file
3
src/components/IsStringEmpty/IsStringEmpty.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function isStringEmpty(str: string) {
|
||||
return str === "" || str === null || str === undefined;
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import AuthReducer from "../slices/AuthSlice/AuthSlice";
|
||||
import StatusReducer from "../slices/StatusSlice/StatusSlice";
|
||||
import UserReducer from "../slices/UserSlice/UserSlice";
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
auth: AuthReducer,
|
||||
status: StatusReducer,
|
||||
user: UserReducer,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
export const AuthSlice = createSlice({
|
||||
name: "Auth",
|
||||
initialState: {
|
||||
creds: {
|
||||
email: "",
|
||||
uid: "",
|
||||
username: "",
|
||||
full_name: "",
|
||||
logged_in: false,
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
login: (state) => {
|
||||
state.creds.logged_in = true;
|
||||
},
|
||||
setUser: (state, action) => {
|
||||
state.creds = {
|
||||
email: action.payload.email,
|
||||
uid: action.payload.uid,
|
||||
username: action.payload.username,
|
||||
full_name: action.payload.full_name,
|
||||
logged_in: true,
|
||||
};
|
||||
},
|
||||
clear: (state) => {
|
||||
state.creds = {
|
||||
email: "",
|
||||
uid: "",
|
||||
username: "",
|
||||
full_name: "",
|
||||
logged_in: false,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Action creators are generated for each case reducer function
|
||||
export const { login, setUser, clear } = AuthSlice.actions;
|
||||
|
||||
export default AuthSlice.reducer;
|
29
src/features/redux/slices/StatusSlice/StatusSlice.tsx
Normal file
29
src/features/redux/slices/StatusSlice/StatusSlice.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
export const StatusSlice = createSlice({
|
||||
name: "Status",
|
||||
initialState: {
|
||||
logged_in: false,
|
||||
onboarding: false,
|
||||
},
|
||||
reducers: {
|
||||
login: (state) => {
|
||||
state.logged_in = true;
|
||||
},
|
||||
logout: (state) => {
|
||||
state.logged_in = false;
|
||||
},
|
||||
setOnboarding: (state) => {
|
||||
state.onboarding = true;
|
||||
},
|
||||
unsetOnboarding: (state) => {
|
||||
state.onboarding = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Action creators are generated for each case reducer function
|
||||
export const { login, logout, setOnboarding, unsetOnboarding } =
|
||||
StatusSlice.actions;
|
||||
|
||||
export default StatusSlice.reducer;
|
51
src/features/redux/slices/UserSlice/UserSlice.tsx
Normal file
51
src/features/redux/slices/UserSlice/UserSlice.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
export const UserSlice = createSlice({
|
||||
name: "User",
|
||||
initialState: {
|
||||
user: {
|
||||
email: "",
|
||||
uid: "",
|
||||
username: "",
|
||||
first_name: "",
|
||||
last_name: "",
|
||||
full_name: "",
|
||||
year_level: "",
|
||||
semester: " ",
|
||||
course: "",
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
setUser: (state, action) => {
|
||||
state.user = {
|
||||
email: action.payload.email,
|
||||
uid: action.payload.uid,
|
||||
username: action.payload.username,
|
||||
first_name: action.payload.first_name,
|
||||
last_name: action.payload.last_name,
|
||||
full_name: action.payload.first_name + " " + action.payload.last_name,
|
||||
year_level: action.payload.year_level,
|
||||
semester: action.payload.semester,
|
||||
course: action.payload.course,
|
||||
};
|
||||
},
|
||||
clear: (state) => {
|
||||
state.user = {
|
||||
email: "",
|
||||
uid: "",
|
||||
username: "",
|
||||
first_name: "",
|
||||
last_name: "",
|
||||
full_name: "",
|
||||
year_level: "",
|
||||
semester: " ",
|
||||
course: "",
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Action creators are generated for each case reducer function
|
||||
export const { setUser, clear } = UserSlice.actions;
|
||||
|
||||
export default UserSlice.reducer;
|
|
@ -46,3 +46,27 @@ export interface ActivationParams {
|
|||
uid: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface SemesterParams {
|
||||
id: string;
|
||||
name: string;
|
||||
shortname: string;
|
||||
}
|
||||
|
||||
export interface YearLevelParams {
|
||||
id: string;
|
||||
name: string;
|
||||
shortname: string;
|
||||
}
|
||||
|
||||
export interface CourseParams {
|
||||
id: string;
|
||||
name: string;
|
||||
shortname: string;
|
||||
}
|
||||
|
||||
export interface OnboardingParams {
|
||||
year_level: string;
|
||||
course: string;
|
||||
semester: string;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { RootState } from "../../features/redux/Store/Store";
|
|||
import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
|
||||
|
||||
export default function Home() {
|
||||
const creds = useSelector((state: RootState) => state.auth.creds);
|
||||
const creds = useSelector((state: RootState) => state.user.user);
|
||||
return (
|
||||
<View style={styles.background}>
|
||||
<AnimatedContainer>
|
||||
|
|
|
@ -17,12 +17,17 @@ import { RootDrawerParamList } from "../../interfaces/Interfaces";
|
|||
import { UserInfo, UserLogin } from "../../components/Api/Api";
|
||||
import { ParseLoginError } from "../../components/ParseError/ParseError";
|
||||
import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
|
||||
import { setUser as setStateUser } from "../../features/redux/slices/AuthSlice/AuthSlice";
|
||||
import { setUser } from "../../features/redux/slices/UserSlice/UserSlice";
|
||||
import {
|
||||
login,
|
||||
setOnboarding,
|
||||
unsetOnboarding,
|
||||
} from "../../features/redux/slices/StatusSlice/StatusSlice";
|
||||
|
||||
export default function Login() {
|
||||
const navigation = useNavigation<RootDrawerParamList>();
|
||||
const dispatch = useDispatch();
|
||||
const [user, setUser] = useState({
|
||||
const [creds, setCreds] = useState({
|
||||
username: "",
|
||||
password: "",
|
||||
error: "",
|
||||
|
@ -50,11 +55,11 @@ export default function Login() {
|
|||
placeholder="Username"
|
||||
placeholderTextColor="white"
|
||||
autoCapitalize="none"
|
||||
value={user.username}
|
||||
value={creds.username}
|
||||
onChange={(
|
||||
e: NativeSyntheticEvent<TextInputChangeEventData>
|
||||
): void => {
|
||||
setUser({ ...user, username: e.nativeEvent.text });
|
||||
setCreds({ ...creds, username: e.nativeEvent.text });
|
||||
}}
|
||||
/>
|
||||
<View style={{ paddingVertical: 4 }} />
|
||||
|
@ -63,42 +68,43 @@ export default function Login() {
|
|||
placeholder="Password"
|
||||
placeholderTextColor="white"
|
||||
secureTextEntry={true}
|
||||
value={user.password}
|
||||
value={creds.password}
|
||||
onChange={(
|
||||
e: NativeSyntheticEvent<TextInputChangeEventData>
|
||||
): void => {
|
||||
setUser({ ...user, password: e.nativeEvent.text });
|
||||
setCreds({ ...creds, password: e.nativeEvent.text });
|
||||
}}
|
||||
/>
|
||||
<View style={{ paddingVertical: 2 }} />
|
||||
<Text style={styles.text_white_small}>{user.error}</Text>
|
||||
<Text style={styles.text_white_small}>{creds.error}</Text>
|
||||
<View style={{ paddingVertical: 4 }} />
|
||||
<Button
|
||||
onPress={async () => {
|
||||
await UserLogin({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
username: creds.username,
|
||||
password: creds.password,
|
||||
}).then(async (result) => {
|
||||
if (result[0]) {
|
||||
setUser({ ...user, username: "", password: "", error: "" });
|
||||
setUser({ ...creds, username: "", password: "", error: "" });
|
||||
let user_info = await UserInfo();
|
||||
dispatch(setStateUser(user_info));
|
||||
dispatch(login());
|
||||
dispatch(setUser(user_info[1]));
|
||||
// Redirect to onboarding if no year level, course, or semester specified
|
||||
if (
|
||||
!(
|
||||
user_info.year_level ||
|
||||
user_info.course ||
|
||||
user_info.semester
|
||||
)
|
||||
user_info[1].year_level == null ||
|
||||
user_info[1].course == null ||
|
||||
user_info[1].semester == null
|
||||
) {
|
||||
dispatch(setOnboarding());
|
||||
navigation.navigate("Onboarding");
|
||||
} else {
|
||||
dispatch(unsetOnboarding());
|
||||
navigation.navigate("Home");
|
||||
}
|
||||
console.log(JSON.stringify(user_info));
|
||||
} else {
|
||||
setUser({
|
||||
...user,
|
||||
...creds,
|
||||
error: ParseLoginError(JSON.stringify(result[1])),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,46 +2,99 @@ import * as React from "react";
|
|||
import styles from "../../styles";
|
||||
import { View, Text } from "react-native";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { RootDrawerParamList } from "../../interfaces/Interfaces";
|
||||
import {
|
||||
CourseParams,
|
||||
RootDrawerParamList,
|
||||
SemesterParams,
|
||||
YearLevelParams,
|
||||
} from "../../interfaces/Interfaces";
|
||||
import { colors } from "../../styles";
|
||||
import { AnimatePresence, MotiView } from "moti";
|
||||
import { useEffect, 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,
|
||||
GetSemesters,
|
||||
GetYearLevels,
|
||||
OnboardingUpdateStudentInfo,
|
||||
} from "../../components/Api/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { unsetOnboarding } from "../../features/redux/slices/StatusSlice/StatusSlice";
|
||||
import { setUser } from "../../features/redux/slices/UserSlice/UserSlice";
|
||||
export default function Onboarding() {
|
||||
const navigation = useNavigation<RootDrawerParamList>();
|
||||
// const dispatch = useDispatch();
|
||||
const dispatch = useDispatch();
|
||||
// const creds = useSelector((state: RootState) => state.auth.creds);
|
||||
const [student_info, setStudentInfo] = useState({
|
||||
year_level: "",
|
||||
course: "",
|
||||
semester: "",
|
||||
const [error, setError] = useState("");
|
||||
// Semesters
|
||||
const [selected_semester, setSelectedSemester] = useState("");
|
||||
const [semesterOpen, setSemesterOpen] = useState(false);
|
||||
const [semesters, setSemesters] = useState([
|
||||
{ label: "1st Semester", value: "1st Sem" },
|
||||
{ label: "2nd Semester", value: "2nd Sem" },
|
||||
]);
|
||||
const semester_query = useQuery({
|
||||
queryKey: ["semesters"],
|
||||
queryFn: GetSemesters,
|
||||
onSuccess: (data) => {
|
||||
let semesters = data.map((item: SemesterParams) => ({
|
||||
label: item.name,
|
||||
value: item.name,
|
||||
}));
|
||||
setSemesters(semesters);
|
||||
},
|
||||
});
|
||||
function Introduction() {
|
||||
const [shown, setShown] = useState(true);
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setShown(false);
|
||||
}, 5000);
|
||||
}, []);
|
||||
// Year Level
|
||||
const [selected_yearlevel, setSelectedYearLevel] = useState("");
|
||||
const [yearLevelOpen, setYearLevelOpen] = useState(false);
|
||||
const [year_levels, setYearLevels] = useState([
|
||||
{ label: "1st Year", value: "1st Year" },
|
||||
{ label: "2nd Year", value: "2nd Year" },
|
||||
]);
|
||||
const yearlevel_query = useQuery({
|
||||
queryKey: ["year_levels"],
|
||||
queryFn: GetYearLevels,
|
||||
onSuccess: (data) => {
|
||||
let year_levels = data.map((item: YearLevelParams) => ({
|
||||
label: item.name,
|
||||
value: item.name,
|
||||
}));
|
||||
setYearLevels(year_levels);
|
||||
},
|
||||
});
|
||||
// Course
|
||||
const [selected_course, setSelectedCourse] = useState("");
|
||||
const [courseOpen, setCourseOpen] = useState(false);
|
||||
const [courses, setCourses] = useState([
|
||||
{
|
||||
label: "Bachelor of Science in Information Technology",
|
||||
value: "BSIT",
|
||||
},
|
||||
{ label: "Bachelor of Science in Computer Science", value: "BSCS" },
|
||||
]);
|
||||
const course_query = useQuery({
|
||||
queryKey: ["courses"],
|
||||
queryFn: GetCourses,
|
||||
onSuccess: (data) => {
|
||||
let courses = data.map((item: CourseParams) => ({
|
||||
label: item.name,
|
||||
value: item.name,
|
||||
}));
|
||||
setCourses(courses);
|
||||
},
|
||||
});
|
||||
if (yearlevel_query.error || semester_query.error || course_query.error) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{shown && (
|
||||
<MotiView
|
||||
from={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
exitTransition={{ type: "timing", duration: 1200, delay: 600 }}
|
||||
transition={{ type: "timing", duration: 1200, delay: 600 }}
|
||||
>
|
||||
<Text style={styles.text_white_small}>
|
||||
We're glad to have you on board {"\n"}
|
||||
Just a few more things before we get started
|
||||
</Text>
|
||||
</MotiView>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<View style={styles.background}>
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text_white_medium}>Error loading details</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.background}>
|
||||
<View style={styles.container}>
|
||||
|
@ -63,14 +116,112 @@ export default function Onboarding() {
|
|||
}}
|
||||
/>
|
||||
<View style={{ paddingVertical: 4 }} />
|
||||
<Introduction />
|
||||
<View style={{ paddingVertical: 8 }} />
|
||||
<MotiView
|
||||
from={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ type: "timing", duration: 900, delay: 2000 }}
|
||||
transition={{ type: "timing", duration: 1200, delay: 600 }}
|
||||
>
|
||||
<Text style={styles.text_white_small}>
|
||||
We're glad to have you on board {"\n"}
|
||||
Just a few more things before we get started
|
||||
</Text>
|
||||
</MotiView>
|
||||
<View style={{ paddingVertical: 8 }} />
|
||||
|
||||
<MotiView
|
||||
from={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ type: "timing", duration: 400, delay: 1700 }}
|
||||
>
|
||||
<Text style={styles.text_white_medium}>Academic Info</Text>
|
||||
<DropDownPicker
|
||||
zIndex={3000}
|
||||
open={courseOpen}
|
||||
value={selected_course}
|
||||
items={courses}
|
||||
setOpen={(open) => {
|
||||
setCourseOpen(open);
|
||||
setSemesterOpen(false);
|
||||
setYearLevelOpen(false);
|
||||
}}
|
||||
setValue={setSelectedCourse}
|
||||
placeholder="Choose your course"
|
||||
containerStyle={{
|
||||
...styles.dropdown_template,
|
||||
...{ zIndex: 3000 },
|
||||
}}
|
||||
dropDownContainerStyle={{ backgroundColor: "white" }}
|
||||
/>
|
||||
<DropDownPicker
|
||||
zIndex={2000}
|
||||
open={semesterOpen}
|
||||
value={selected_semester}
|
||||
items={semesters}
|
||||
setOpen={(open) => {
|
||||
setSemesterOpen(open);
|
||||
setCourseOpen(false);
|
||||
setYearLevelOpen(false);
|
||||
}}
|
||||
setValue={setSelectedSemester}
|
||||
placeholder="Current semester"
|
||||
containerStyle={{
|
||||
...styles.dropdown_template,
|
||||
...{ zIndex: 2000 },
|
||||
}}
|
||||
dropDownContainerStyle={{ backgroundColor: "white" }}
|
||||
/>
|
||||
<DropDownPicker
|
||||
zIndex={1000}
|
||||
open={yearLevelOpen}
|
||||
value={selected_yearlevel}
|
||||
items={year_levels}
|
||||
setOpen={(open) => {
|
||||
setYearLevelOpen(open);
|
||||
setSemesterOpen(false);
|
||||
setCourseOpen(false);
|
||||
}}
|
||||
setValue={setSelectedYearLevel}
|
||||
placeholder="Your Year Level"
|
||||
containerStyle={{
|
||||
...styles.dropdown_template,
|
||||
...{ zIndex: 1000 },
|
||||
}}
|
||||
dropDownContainerStyle={{ backgroundColor: "white" }}
|
||||
/>
|
||||
</MotiView>
|
||||
<MotiView
|
||||
from={{ opacity: 0 }}
|
||||
animate={{ opacity: 1, zIndex: -1 }}
|
||||
transition={{ type: "timing", duration: 400, delay: 1700 }}
|
||||
style={styles.button_template}
|
||||
>
|
||||
<Text style={styles.text_white_small}>{error}</Text>
|
||||
<Button
|
||||
disabled={
|
||||
!selected_yearlevel || !selected_course || !selected_semester
|
||||
}
|
||||
onPress={async () => {
|
||||
let result = await OnboardingUpdateStudentInfo({
|
||||
semester: selected_semester,
|
||||
course: selected_course,
|
||||
year_level: selected_yearlevel,
|
||||
});
|
||||
if (result[0]) {
|
||||
dispatch(unsetOnboarding());
|
||||
setSelectedCourse("");
|
||||
setSelectedYearLevel("");
|
||||
setSelectedSemester("");
|
||||
setError("Success!");
|
||||
dispatch(setUser(result[1]));
|
||||
navigation.navigate("Home");
|
||||
} else {
|
||||
setError(result[1]);
|
||||
}
|
||||
}}
|
||||
color={colors.blue_3}
|
||||
>
|
||||
<Text style={styles.text_white_small}>Proceed</Text>
|
||||
</Button>
|
||||
</MotiView>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -159,7 +159,7 @@ export default function Register() {
|
|||
} else {
|
||||
setUser({
|
||||
...user,
|
||||
feedback: ParseError(JSON.stringify(result[2])),
|
||||
feedback: ParseError(JSON.stringify(result[1])),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -7,8 +7,13 @@ import { colors } from "../../styles";
|
|||
import { useEffect, useState } from "react";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { RootDrawerParamList } from "../../interfaces/Interfaces";
|
||||
import { setUser } from "../../features/redux/slices/AuthSlice/AuthSlice";
|
||||
import {
|
||||
login,
|
||||
unsetOnboarding,
|
||||
} from "../../features/redux/slices/StatusSlice/StatusSlice";
|
||||
import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
|
||||
import { setUser } from "../../features/redux/slices/UserSlice/UserSlice";
|
||||
import { setOnboarding } from "../../features/redux/slices/StatusSlice/StatusSlice";
|
||||
|
||||
export default function Revalidation() {
|
||||
const dispatch = useDispatch();
|
||||
|
@ -17,14 +22,23 @@ export default function Revalidation() {
|
|||
useEffect(() => {
|
||||
setState("Previous session found");
|
||||
TokenRefresh().then(async (response) => {
|
||||
if (response[0]) {
|
||||
let user_info = await UserInfo();
|
||||
await dispatch(setUser(user_info));
|
||||
if (!(user_info.year_level || user_info.course || user_info.semester)) {
|
||||
let user_info = await UserInfo();
|
||||
if (response && user_info[0]) {
|
||||
dispatch(login());
|
||||
dispatch(setUser(user_info[1]));
|
||||
if (
|
||||
!(
|
||||
user_info[1].year_level ||
|
||||
user_info[1].course ||
|
||||
user_info[1].semester
|
||||
)
|
||||
) {
|
||||
dispatch(setOnboarding());
|
||||
await setTimeout(() => {
|
||||
navigation.navigate("Onboarding");
|
||||
}, 700);
|
||||
} else {
|
||||
dispatch(unsetOnboarding());
|
||||
await setTimeout(() => {
|
||||
navigation.navigate("Home");
|
||||
}, 700);
|
||||
|
|
|
@ -11,6 +11,7 @@ export const colors = {
|
|||
text_error: "#e32d1e",
|
||||
text_success: "green",
|
||||
icon_color: "white",
|
||||
blue_disabled: "#C07624",
|
||||
};
|
||||
|
||||
export const font_sizes = {
|
||||
|
@ -98,6 +99,11 @@ const styles = StyleSheet.create({
|
|||
padding: 10,
|
||||
borderRadius: 8,
|
||||
},
|
||||
dropdown_template: {
|
||||
borderRadius: 16,
|
||||
width: "70%",
|
||||
marginVertical: 6,
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
||||
|
|
Loading…
Reference in a new issue