"feat: enhance user authentication flow with loading state, improved reducer handling, and login/logout actions"
This commit is contained in:
@@ -15,13 +15,18 @@ import { endpoints } from '../../pages/MyLogin/endpoints';
|
||||
|
||||
export const loadUserData = () => async (dispatch: React.Dispatch<any>) => {
|
||||
dispatch(setLoading(true));
|
||||
|
||||
const data = await getUserData();
|
||||
dispatch(setData(data));
|
||||
|
||||
dispatch(setLoading(false));
|
||||
};
|
||||
|
||||
export const setLoading = (isLoading: boolean) =>
|
||||
({ type: 'set-user-loading', isLoading } as const);
|
||||
({
|
||||
type: 'set-user-loading',
|
||||
isLoading,
|
||||
} as const);
|
||||
|
||||
export const setData = (data: Partial<UserState>) =>
|
||||
({
|
||||
@@ -30,6 +35,7 @@ export const setData = (data: Partial<UserState>) =>
|
||||
} as const);
|
||||
|
||||
export const logoutUser = () => async (dispatch: React.Dispatch<any>) => {
|
||||
//
|
||||
await setIsLoggedInData(false);
|
||||
dispatch(setUsername());
|
||||
};
|
||||
@@ -44,6 +50,8 @@ export const setIsLoggedIn = (loggedIn: boolean) => async (dispatch: React.Dispa
|
||||
|
||||
export const setUsername = (username?: string) => async (dispatch: React.Dispatch<any>) => {
|
||||
await setUsernameData(username);
|
||||
console.log('setUsername triggered');
|
||||
|
||||
return {
|
||||
type: 'set-username',
|
||||
username,
|
||||
@@ -52,6 +60,7 @@ export const setUsername = (username?: string) => async (dispatch: React.Dispatc
|
||||
|
||||
export const setAccessToken = (token?: string) => async (dispatch: React.Dispatch<any>) => {
|
||||
await setAccessTokenData(token);
|
||||
|
||||
return {
|
||||
type: 'set-access-token',
|
||||
token,
|
||||
@@ -99,6 +108,7 @@ export const checkUserSession = () => async (dispatch: React.Dispatch<any>) => {
|
||||
|
||||
export const setHasSeenTutorial =
|
||||
(hasSeenTutorial: boolean) => async (dispatch: React.Dispatch<any>) => {
|
||||
debugger;
|
||||
await setHasSeenTutorialData(hasSeenTutorial);
|
||||
return {
|
||||
type: 'set-has-seen-tutorial',
|
||||
@@ -120,5 +130,4 @@ export type UserActions =
|
||||
| ActionType<typeof setHasSeenTutorial>
|
||||
| ActionType<typeof setDarkMode>
|
||||
| ActionType<typeof setAccessToken>
|
||||
// | ActionType<typeof setSession>
|
||||
| ActionType<typeof checkUserSession>;
|
||||
|
@@ -15,11 +15,9 @@ export function userReducer(state: UserState, action: UserActions): UserState {
|
||||
return { ...state, darkMode: action.darkMode };
|
||||
case 'set-is-loggedin':
|
||||
return { ...state, isLoggedin: action.loggedIn };
|
||||
case 'set-access-token':
|
||||
return { ...state, token: action.token };
|
||||
case 'check-user-session':
|
||||
return { ...state, isSessionValid: action.sessionValid };
|
||||
// case 'set-active-session':
|
||||
// return { ...state, session: action.session };
|
||||
// case 'set-access-token':
|
||||
// return { ...state, token: action.token };
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
export interface UserState {
|
||||
isLoggedin: boolean;
|
||||
username?: string;
|
||||
darkMode: boolean;
|
||||
hasSeenTutorial: boolean;
|
||||
loading: boolean;
|
||||
username?: string;
|
||||
hasSeenTutorial: boolean;
|
||||
darkMode: boolean;
|
||||
isLoggedin: boolean;
|
||||
isSessionValid: boolean;
|
||||
session?: any;
|
||||
token?: string;
|
||||
|
@@ -74,6 +74,7 @@ const Login: React.FC<LoginProps> = (props) => {
|
||||
setUsername: setUsernameAction,
|
||||
checkUserSession,
|
||||
isSessionValid,
|
||||
isLoggedin,
|
||||
} = props;
|
||||
|
||||
const history = useHistory();
|
||||
@@ -103,9 +104,11 @@ const Login: React.FC<LoginProps> = (props) => {
|
||||
|
||||
// ----------
|
||||
|
||||
// email:
|
||||
// password:
|
||||
const defaultValues: SignInSchemaType = {
|
||||
email: '',
|
||||
password: '',
|
||||
email: 'demo@minimals.c',
|
||||
password: '@2Minimal',
|
||||
};
|
||||
|
||||
const methods = useForm<SignInSchemaType>({
|
||||
@@ -137,10 +140,11 @@ const Login: React.FC<LoginProps> = (props) => {
|
||||
}, [isSessionValid]);
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
console.log({ data });
|
||||
// console.log({ data });
|
||||
|
||||
try {
|
||||
let token = await signInWithPassword({ email: values.email, password: values.password });
|
||||
console.log({ token });
|
||||
// console.log({ token });
|
||||
if (token) setAccessToken(token);
|
||||
|
||||
await checkUserSession();
|
||||
|
BIN
03_source/mobile/src/pages/MyProfile/NotLoggedIn/SignUp.png
Normal file
BIN
03_source/mobile/src/pages/MyProfile/NotLoggedIn/SignUp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 161 KiB |
@@ -56,6 +56,7 @@ import AboutPopover from '../../../components/AboutPopover';
|
||||
import paths from '../../../paths';
|
||||
import { getProfileById } from '../../../api/getProfileById';
|
||||
import { defaultMember, Member } from '../../MemberProfile/type';
|
||||
import SignUpPng from './SignUp.png';
|
||||
|
||||
interface OwnProps {}
|
||||
|
||||
@@ -144,29 +145,53 @@ const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '50vh',
|
||||
height: '80vh',
|
||||
//
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
//
|
||||
gap: '2rem',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
not login yet, <br />
|
||||
please login or sign up
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
height: '50vh',
|
||||
width: '95vw',
|
||||
height: '95vw',
|
||||
backgroundImage: `url(${SignUpPng})`,
|
||||
backgroundSize: 'contain',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
></div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
//
|
||||
gap: '2rem',
|
||||
}}
|
||||
>
|
||||
<IonButton disabled={disableForwardLoginButton} onClick={handleForwardLoginPage}>
|
||||
Login
|
||||
</IonButton>
|
||||
<div>
|
||||
not login yet, <br />
|
||||
please login or sign up
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<IonButton disabled={disableForwardLoginButton} onClick={handleForwardLoginPage}>
|
||||
Login
|
||||
</IonButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</IonContent>
|
||||
|
BIN
03_source/mobile/src/pages/MyProfile/SignUp.png
Normal file
BIN
03_source/mobile/src/pages/MyProfile/SignUp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 161 KiB |
@@ -44,7 +44,14 @@ import '../SpeakerList.scss';
|
||||
import { getEvents } from '../../api/getEvents';
|
||||
import { format } from 'date-fns';
|
||||
import { Event } from './types';
|
||||
import { alertOutline, chevronDownCircleOutline, createOutline, heart, menuOutline, settingsOutline } from 'ionicons/icons';
|
||||
import {
|
||||
alertOutline,
|
||||
chevronDownCircleOutline,
|
||||
createOutline,
|
||||
heart,
|
||||
menuOutline,
|
||||
settingsOutline,
|
||||
} from 'ionicons/icons';
|
||||
import AboutPopover from '../../components/AboutPopover';
|
||||
import paths from '../../paths';
|
||||
import { getProfileById } from '../../api/getProfileById';
|
||||
@@ -54,6 +61,8 @@ import NotLoggedIn from './NotLoggedIn';
|
||||
interface OwnProps {}
|
||||
|
||||
interface StateProps {
|
||||
isLoggedin: boolean;
|
||||
//
|
||||
speakers: Speaker[];
|
||||
speakerSessions: { [key: string]: Session[] };
|
||||
}
|
||||
@@ -62,7 +71,9 @@ interface DispatchProps {}
|
||||
|
||||
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps {}
|
||||
|
||||
const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) => {
|
||||
const MyProfilePage: React.FC<SpeakerListProps> = ({ speakers, speakerSessions, isLoggedin }) => {
|
||||
if (!isLoggedin) return <NotLoggedIn />;
|
||||
|
||||
const [profile, setProfile] = useState<Member>(defaultMember);
|
||||
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
@@ -94,7 +105,6 @@ const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
}, []);
|
||||
|
||||
if (!profile) return <>loading</>;
|
||||
if (profile.id == -1) return <NotLoggedIn />;
|
||||
|
||||
return (
|
||||
<IonPage id="speaker-list">
|
||||
@@ -112,7 +122,12 @@ const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
|
||||
<IonContent fullscreen={true}>
|
||||
<IonRefresher slot="fixed" onIonRefresh={handleRefresh}>
|
||||
<IonRefresherContent pullingIcon={chevronDownCircleOutline} pullingText="Pull to refresh" refreshingSpinner="circles" refreshingText="Refreshing..."></IonRefresherContent>
|
||||
<IonRefresherContent
|
||||
pullingIcon={chevronDownCircleOutline}
|
||||
pullingText="Pull to refresh"
|
||||
refreshingSpinner="circles"
|
||||
refreshingText="Refreshing..."
|
||||
></IonRefresherContent>
|
||||
</IonRefresher>
|
||||
|
||||
<IonHeader collapse="condense" className="ion-no-border">
|
||||
@@ -133,7 +148,10 @@ const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
>
|
||||
<div style={{ padding: '1rem', display: 'flex', gap: '1rem' }}>
|
||||
<IonAvatar>
|
||||
<img alt="Silhouette of a person's head" src="https://plus.unsplash.com/premium_photo-1683121126477-17ef068309bc" />
|
||||
<img
|
||||
alt="Silhouette of a person's head"
|
||||
src="https://plus.unsplash.com/premium_photo-1683121126477-17ef068309bc"
|
||||
/>
|
||||
</IonAvatar>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<div
|
||||
@@ -150,7 +168,12 @@ const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<IonButton shape="round" fill="clear" size="large" onClick={handleNotImplementedClick}>
|
||||
<IonButton
|
||||
shape="round"
|
||||
fill="clear"
|
||||
size="large"
|
||||
onClick={handleNotImplementedClick}
|
||||
>
|
||||
<IonIcon slot="icon-only" icon={createOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</div>
|
||||
@@ -179,7 +202,12 @@ const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
<div style={{ fontWeight: 'bold', fontSize: '1.1rem' }}>Membership</div>
|
||||
<div>7 of the exclusive privileges</div>
|
||||
<div>
|
||||
<IonButton expand="full" shape="round" size="large" onClick={handleNotImplementedClick}>
|
||||
<IonButton
|
||||
expand="full"
|
||||
shape="round"
|
||||
size="large"
|
||||
onClick={handleNotImplementedClick}
|
||||
>
|
||||
Unlock
|
||||
</IonButton>
|
||||
</div>
|
||||
@@ -237,7 +265,12 @@ const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
</IonContent>
|
||||
|
||||
{/* REQ0079/event-filter */}
|
||||
<IonModal ref={modal} trigger="my-profile-open-modal" initialBreakpoint={0.5} breakpoints={[0, 0.25, 0.5, 0.75]}>
|
||||
<IonModal
|
||||
ref={modal}
|
||||
trigger="my-profile-open-modal"
|
||||
initialBreakpoint={0.5}
|
||||
breakpoints={[0, 0.25, 0.5, 0.75]}
|
||||
>
|
||||
<IonContent className="ion-padding">
|
||||
<div
|
||||
style={{
|
||||
@@ -303,6 +336,7 @@ export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
speakers: selectors.getSpeakers(state),
|
||||
speakerSessions: selectors.getSpeakerSessions(state),
|
||||
isLoggedin: state.user.isLoggedin,
|
||||
}),
|
||||
component: React.memo(MyProfile),
|
||||
component: React.memo(MyProfilePage),
|
||||
});
|
||||
|
@@ -63,6 +63,7 @@ import {
|
||||
import AboutPopover from '../../components/AboutPopover';
|
||||
import { OverlayEventDetail } from '@ionic/react/dist/types/components/react-component-lib/interfaces';
|
||||
import paths from '../../paths';
|
||||
import { logoutUser, setAccessToken, setIsLoggedIn } from '../../data/user/user.actions';
|
||||
|
||||
interface OwnProps {}
|
||||
|
||||
@@ -71,11 +72,21 @@ interface StateProps {
|
||||
speakerSessions: { [key: string]: Session[] };
|
||||
}
|
||||
|
||||
interface DispatchProps {}
|
||||
interface DispatchProps {
|
||||
logoutUser: typeof logoutUser;
|
||||
setAccessToken: typeof setAccessToken;
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
}
|
||||
|
||||
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps {}
|
||||
|
||||
const EventList: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) => {
|
||||
const EventList: React.FC<SpeakerListProps> = ({
|
||||
speakers,
|
||||
speakerSessions,
|
||||
logoutUser,
|
||||
setAccessToken,
|
||||
setIsLoggedIn,
|
||||
}) => {
|
||||
const [events, setEvents] = useState<Event[] | []>([]);
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||
@@ -116,6 +127,11 @@ const EventList: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
}
|
||||
|
||||
function handleLogoutClick() {
|
||||
setAccessToken();
|
||||
setIsLoggedIn(false);
|
||||
|
||||
router.push('/tabs', 'forward', 'replace');
|
||||
|
||||
setShowLogoutConfirmModal(false);
|
||||
}
|
||||
function handleLogoutCancel() {
|
||||
@@ -190,7 +206,11 @@ const EventList: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
</IonContent>
|
||||
|
||||
{/* REQ0058/logout */}
|
||||
<IonModal isOpen={showLogoutConfirmModal} initialBreakpoint={0.5} breakpoints={[0, 0.25, 0.5, 0.75]}>
|
||||
<IonModal
|
||||
isOpen={showLogoutConfirmModal}
|
||||
initialBreakpoint={0.5}
|
||||
breakpoints={[0, 0.25, 0.5, 0.75]}
|
||||
>
|
||||
<IonContent
|
||||
className="ion-padding"
|
||||
style={{
|
||||
@@ -251,5 +271,10 @@ export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
speakers: selectors.getSpeakers(state),
|
||||
speakerSessions: selectors.getSpeakerSessions(state),
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
logoutUser,
|
||||
setAccessToken,
|
||||
setIsLoggedIn,
|
||||
},
|
||||
component: React.memo(EventList),
|
||||
});
|
||||
|
Reference in New Issue
Block a user