forked from vergnet/application-amicale
Replace Authenticated screen by RequestScreen
This commit is contained in:
parent
9b4caade00
commit
742643b9e2
11 changed files with 251 additions and 526 deletions
|
@ -1,245 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
|
||||||
*
|
|
||||||
* This file is part of Campus INSAT.
|
|
||||||
*
|
|
||||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Campus INSAT is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
|
||||||
import ConnectionManager from '../../managers/ConnectionManager';
|
|
||||||
import { ERROR_TYPE } from '../../utils/WebData';
|
|
||||||
import ErrorView from '../Screens/ErrorView';
|
|
||||||
import BasicLoadingScreen from '../Screens/BasicLoadingScreen';
|
|
||||||
import i18n from 'i18n-js';
|
|
||||||
|
|
||||||
type PropsType<T> = {
|
|
||||||
navigation: StackNavigationProp<any>;
|
|
||||||
requests: Array<{
|
|
||||||
link: string;
|
|
||||||
params: object;
|
|
||||||
mandatory: boolean;
|
|
||||||
}>;
|
|
||||||
renderFunction: (data: Array<T | null>) => React.ReactNode;
|
|
||||||
errorViewOverride?: Array<{
|
|
||||||
errorCode: number;
|
|
||||||
message: string;
|
|
||||||
icon: string;
|
|
||||||
showRetryButton: boolean;
|
|
||||||
}> | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StateType = {
|
|
||||||
loading: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
|
|
||||||
static defaultProps = {
|
|
||||||
errorViewOverride: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
currentUserToken: string | null;
|
|
||||||
|
|
||||||
connectionManager: ConnectionManager;
|
|
||||||
|
|
||||||
errors: Array<number>;
|
|
||||||
|
|
||||||
fetchedData: Array<T | null>;
|
|
||||||
|
|
||||||
constructor(props: PropsType<T>) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
loading: true,
|
|
||||||
};
|
|
||||||
this.currentUserToken = null;
|
|
||||||
this.connectionManager = ConnectionManager.getInstance();
|
|
||||||
props.navigation.addListener('focus', this.onScreenFocus);
|
|
||||||
this.fetchedData = new Array(props.requests.length);
|
|
||||||
this.errors = new Array(props.requests.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refreshes screen if user changed
|
|
||||||
*/
|
|
||||||
onScreenFocus = () => {
|
|
||||||
if (this.currentUserToken !== this.connectionManager.getToken()) {
|
|
||||||
this.currentUserToken = this.connectionManager.getToken();
|
|
||||||
this.fetchData();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback used when a request finishes, successfully or not.
|
|
||||||
* Saves data and error code.
|
|
||||||
* If the token is invalid, logout the user and open the login screen.
|
|
||||||
* If the last request was received, stop the loading screen.
|
|
||||||
*
|
|
||||||
* @param data The data fetched from the server
|
|
||||||
* @param index The index for the data
|
|
||||||
* @param error The error code received
|
|
||||||
*/
|
|
||||||
onRequestFinished(data: T | null, index: number, error?: number) {
|
|
||||||
const { props } = this;
|
|
||||||
if (index >= 0 && index < props.requests.length) {
|
|
||||||
this.fetchedData[index] = data;
|
|
||||||
this.errors[index] = error != null ? error : ERROR_TYPE.SUCCESS;
|
|
||||||
}
|
|
||||||
// Token expired, logout user
|
|
||||||
if (error === ERROR_TYPE.BAD_TOKEN) {
|
|
||||||
this.connectionManager.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.allRequestsFinished()) {
|
|
||||||
this.setState({ loading: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the error to render.
|
|
||||||
* Non-mandatory requests are ignored.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return {number} The error code or ERROR_TYPE.SUCCESS if no error was found
|
|
||||||
*/
|
|
||||||
getError(): number {
|
|
||||||
const { props } = this;
|
|
||||||
for (let i = 0; i < this.errors.length; i += 1) {
|
|
||||||
if (
|
|
||||||
this.errors[i] !== ERROR_TYPE.SUCCESS &&
|
|
||||||
props.requests[i].mandatory
|
|
||||||
) {
|
|
||||||
return this.errors[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ERROR_TYPE.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the error view to display in case of error
|
|
||||||
*
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
getErrorRender() {
|
|
||||||
const { props } = this;
|
|
||||||
const errorCode = this.getError();
|
|
||||||
let shouldOverride = false;
|
|
||||||
let override = null;
|
|
||||||
const overrideList = props.errorViewOverride;
|
|
||||||
if (overrideList != null) {
|
|
||||||
for (let i = 0; i < overrideList.length; i += 1) {
|
|
||||||
if (overrideList[i].errorCode === errorCode) {
|
|
||||||
shouldOverride = true;
|
|
||||||
override = overrideList[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldOverride && override != null) {
|
|
||||||
return (
|
|
||||||
<ErrorView
|
|
||||||
icon={override.icon}
|
|
||||||
message={override.message}
|
|
||||||
button={
|
|
||||||
override.showRetryButton
|
|
||||||
? {
|
|
||||||
icon: 'refresh',
|
|
||||||
text: i18n.t('general.retry'),
|
|
||||||
onPress: this.fetchData,
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ErrorView
|
|
||||||
status={errorCode}
|
|
||||||
button={{
|
|
||||||
icon: 'refresh',
|
|
||||||
text: i18n.t('general.retry'),
|
|
||||||
onPress: this.fetchData,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the data from the server.
|
|
||||||
*
|
|
||||||
* If the user is not logged in errorCode is set to BAD_TOKEN and all requests fail.
|
|
||||||
*
|
|
||||||
* If the user is logged in, send all requests.
|
|
||||||
*/
|
|
||||||
fetchData = () => {
|
|
||||||
const { state, props } = this;
|
|
||||||
if (!state.loading) {
|
|
||||||
this.setState({ loading: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.connectionManager.isLoggedIn()) {
|
|
||||||
for (let i = 0; i < props.requests.length; i += 1) {
|
|
||||||
this.connectionManager
|
|
||||||
.authenticatedRequest<T>(
|
|
||||||
props.requests[i].link,
|
|
||||||
props.requests[i].params
|
|
||||||
)
|
|
||||||
.then((response: T): void => this.onRequestFinished(response, i))
|
|
||||||
.catch((error: number): void =>
|
|
||||||
this.onRequestFinished(null, i, error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < props.requests.length; i += 1) {
|
|
||||||
this.onRequestFinished(null, i, ERROR_TYPE.BAD_TOKEN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if all requests finished processing
|
|
||||||
*
|
|
||||||
* @return {boolean} True if all finished
|
|
||||||
*/
|
|
||||||
allRequestsFinished(): boolean {
|
|
||||||
let finished = true;
|
|
||||||
this.errors.forEach((error: number | null) => {
|
|
||||||
if (error == null) {
|
|
||||||
finished = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloads the data, to be called using ref by parent components
|
|
||||||
*/
|
|
||||||
reload() {
|
|
||||||
this.fetchData();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { state, props } = this;
|
|
||||||
if (state.loading) {
|
|
||||||
return <BasicLoadingScreen />;
|
|
||||||
}
|
|
||||||
if (this.getError() === ERROR_TYPE.SUCCESS) {
|
|
||||||
return props.renderFunction(this.fetchedData);
|
|
||||||
}
|
|
||||||
return this.getErrorRender();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AuthenticatedScreen;
|
|
|
@ -39,7 +39,7 @@ function ErrorDialog(props: PropsType) {
|
||||||
visible={props.visible}
|
visible={props.visible}
|
||||||
onDismiss={props.onDismiss}
|
onDismiss={props.onDismiss}
|
||||||
title={i18n.t('errors.title')}
|
title={i18n.t('errors.title')}
|
||||||
message={getErrorMessage(props)}
|
message={getErrorMessage(props).message}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ const APP_IMAGES_ENDPOINT = STUDENT_SERVER + '~amicale_app/images/';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
amicale: {
|
amicale: {
|
||||||
api: APP_ENDPOINT,
|
api: AMICALE_ENDPOINT,
|
||||||
resetPassword: AMICALE_SERVER + 'password/reset',
|
resetPassword: AMICALE_SERVER + 'password/reset',
|
||||||
events: AMICALE_ENDPOINT + 'event/list',
|
events: AMICALE_ENDPOINT + 'event/list',
|
||||||
},
|
},
|
||||||
|
|
|
@ -163,7 +163,9 @@ export default class ConnectionManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch((err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -177,7 +179,7 @@ export default class ConnectionManager {
|
||||||
*/
|
*/
|
||||||
async authenticatedRequest<T>(
|
async authenticatedRequest<T>(
|
||||||
path: string,
|
path: string,
|
||||||
params: { [key: string]: any }
|
params?: { [key: string]: any }
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return new Promise(
|
return new Promise(
|
||||||
(
|
(
|
||||||
|
|
|
@ -29,13 +29,13 @@ import {
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
|
|
||||||
import CustomHTML from '../../../components/Overrides/CustomHTML';
|
import CustomHTML from '../../../components/Overrides/CustomHTML';
|
||||||
import { TAB_BAR_HEIGHT } from '../../../components/Tabbar/CustomTabBar';
|
import { TAB_BAR_HEIGHT } from '../../../components/Tabbar/CustomTabBar';
|
||||||
import type { ClubCategoryType, ClubType } from './ClubListScreen';
|
import type { ClubCategoryType, ClubType } from './ClubListScreen';
|
||||||
import { ERROR_TYPE } from '../../../utils/WebData';
|
|
||||||
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
||||||
import ImageGalleryButton from '../../../components/Media/ImageGalleryButton';
|
import ImageGalleryButton from '../../../components/Media/ImageGalleryButton';
|
||||||
|
import RequestScreen from '../../../components/Screens/RequestScreen';
|
||||||
|
import ConnectionManager from '../../../managers/ConnectionManager';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp<any>;
|
navigation: StackNavigationProp<any>;
|
||||||
|
@ -49,6 +49,8 @@ type PropsType = {
|
||||||
theme: ReactNativePaper.Theme;
|
theme: ReactNativePaper.Theme;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ResponseType = ClubType;
|
||||||
|
|
||||||
const AMICALE_MAIL = 'clubs@amicale-insat.fr';
|
const AMICALE_MAIL = 'clubs@amicale-insat.fr';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -88,7 +90,7 @@ const styles = StyleSheet.create({
|
||||||
* If called with clubId parameter, will fetch the information on the server
|
* If called with clubId parameter, will fetch the information on the server
|
||||||
*/
|
*/
|
||||||
class ClubDisplayScreen extends React.Component<PropsType> {
|
class ClubDisplayScreen extends React.Component<PropsType> {
|
||||||
displayData: ClubType | null;
|
displayData: ClubType | undefined;
|
||||||
|
|
||||||
categories: Array<ClubCategoryType> | null;
|
categories: Array<ClubCategoryType> | null;
|
||||||
|
|
||||||
|
@ -98,7 +100,7 @@ class ClubDisplayScreen extends React.Component<PropsType> {
|
||||||
|
|
||||||
constructor(props: PropsType) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.displayData = null;
|
this.displayData = undefined;
|
||||||
this.categories = null;
|
this.categories = null;
|
||||||
this.clubId = props.route.params?.clubId ? props.route.params.clubId : 0;
|
this.clubId = props.route.params?.clubId ? props.route.params.clubId : 0;
|
||||||
this.shouldFetchData = true;
|
this.shouldFetchData = true;
|
||||||
|
@ -236,9 +238,8 @@ class ClubDisplayScreen extends React.Component<PropsType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getScreen = (response: Array<ClubType | null>) => {
|
getScreen = (data: ResponseType | undefined) => {
|
||||||
let data: ClubType | null = response[0];
|
if (data) {
|
||||||
if (data != null) {
|
|
||||||
this.updateHeaderTitle(data);
|
this.updateHeaderTitle(data);
|
||||||
return (
|
return (
|
||||||
<CollapsibleScrollView style={styles.scroll} hasTab>
|
<CollapsibleScrollView style={styles.scroll} hasTab>
|
||||||
|
@ -264,7 +265,7 @@ class ClubDisplayScreen extends React.Component<PropsType> {
|
||||||
</CollapsibleScrollView>
|
</CollapsibleScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return <View />;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -278,31 +279,20 @@ class ClubDisplayScreen extends React.Component<PropsType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { props } = this;
|
|
||||||
if (this.shouldFetchData) {
|
if (this.shouldFetchData) {
|
||||||
return (
|
return (
|
||||||
<AuthenticatedScreen
|
<RequestScreen
|
||||||
navigation={props.navigation}
|
request={() =>
|
||||||
requests={[
|
ConnectionManager.getInstance().authenticatedRequest<ResponseType>(
|
||||||
{
|
'clubs/info',
|
||||||
link: 'clubs/info',
|
{ id: this.clubId }
|
||||||
params: { id: this.clubId },
|
)
|
||||||
mandatory: true,
|
}
|
||||||
},
|
render={this.getScreen}
|
||||||
]}
|
|
||||||
renderFunction={this.getScreen}
|
|
||||||
errorViewOverride={[
|
|
||||||
{
|
|
||||||
errorCode: ERROR_TYPE.BAD_INPUT,
|
|
||||||
message: i18n.t('screens.clubs.invalidClub'),
|
|
||||||
icon: 'account-question',
|
|
||||||
showRetryButton: false,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.getScreen([this.displayData]);
|
return this.getScreen(this.displayData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ import { Platform } from 'react-native';
|
||||||
import { Searchbar } from 'react-native-paper';
|
import { Searchbar } from 'react-native-paper';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
|
|
||||||
import ClubListItem from '../../../components/Lists/Clubs/ClubListItem';
|
import ClubListItem from '../../../components/Lists/Clubs/ClubListItem';
|
||||||
import {
|
import {
|
||||||
isItemInCategoryFilter,
|
isItemInCategoryFilter,
|
||||||
|
@ -32,7 +31,8 @@ import ClubListHeader from '../../../components/Lists/Clubs/ClubListHeader';
|
||||||
import MaterialHeaderButtons, {
|
import MaterialHeaderButtons, {
|
||||||
Item,
|
Item,
|
||||||
} from '../../../components/Overrides/CustomHeaderButton';
|
} from '../../../components/Overrides/CustomHeaderButton';
|
||||||
import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
|
import ConnectionManager from '../../../managers/ConnectionManager';
|
||||||
|
import WebSectionList from '../../../components/Screens/WebSectionList';
|
||||||
|
|
||||||
export type ClubCategoryType = {
|
export type ClubCategoryType = {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -58,6 +58,11 @@ type StateType = {
|
||||||
currentSearchString: string;
|
currentSearchString: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ResponseType = {
|
||||||
|
categories: Array<ClubCategoryType>;
|
||||||
|
clubs: Array<ClubType>;
|
||||||
|
};
|
||||||
|
|
||||||
const LIST_ITEM_HEIGHT = 96;
|
const LIST_ITEM_HEIGHT = 96;
|
||||||
|
|
||||||
class ClubListScreen extends React.Component<PropsType, StateType> {
|
class ClubListScreen extends React.Component<PropsType, StateType> {
|
||||||
|
@ -146,30 +151,13 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
getScreen = (
|
createDataset = (data: ResponseType | undefined) => {
|
||||||
data: Array<{
|
if (data) {
|
||||||
categories: Array<ClubCategoryType>;
|
this.categories = data?.categories;
|
||||||
clubs: Array<ClubType>;
|
return [{ title: '', data: data.clubs }];
|
||||||
} | null>
|
} else {
|
||||||
) => {
|
return [{ title: '', data: [] }];
|
||||||
let categoryList: Array<ClubCategoryType> = [];
|
|
||||||
let clubList: Array<ClubType> = [];
|
|
||||||
if (data[0] != null) {
|
|
||||||
categoryList = data[0].categories;
|
|
||||||
clubList = data[0].clubs;
|
|
||||||
}
|
}
|
||||||
this.categories = categoryList;
|
|
||||||
return (
|
|
||||||
<CollapsibleFlatList
|
|
||||||
data={clubList}
|
|
||||||
keyExtractor={this.keyExtractor}
|
|
||||||
renderItem={this.getRenderItem}
|
|
||||||
ListHeaderComponent={this.getListHeader()}
|
|
||||||
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
|
|
||||||
removeClippedSubviews
|
|
||||||
getItemLayout={this.itemLayout}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -177,8 +165,9 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
|
||||||
*
|
*
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
getListHeader() {
|
getListHeader(data: ResponseType | undefined) {
|
||||||
const { state } = this;
|
const { state } = this;
|
||||||
|
if (data) {
|
||||||
return (
|
return (
|
||||||
<ClubListHeader
|
<ClubListHeader
|
||||||
categories={this.categories}
|
categories={this.categories}
|
||||||
|
@ -186,6 +175,9 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
|
||||||
onChipSelect={this.onChipSelect}
|
onChipSelect={this.onChipSelect}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -223,15 +215,6 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
keyExtractor = (item: ClubType): string => item.id.toString();
|
keyExtractor = (item: ClubType): string => item.id.toString();
|
||||||
|
|
||||||
itemLayout = (
|
|
||||||
_data: Array<ClubType> | null | undefined,
|
|
||||||
index: number
|
|
||||||
): { length: number; offset: number; index: number } => ({
|
|
||||||
length: LIST_ITEM_HEIGHT,
|
|
||||||
offset: LIST_ITEM_HEIGHT * index,
|
|
||||||
index,
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the search string and category filter, saving them to the State.
|
* Updates the search string and category filter, saving them to the State.
|
||||||
*
|
*
|
||||||
|
@ -282,18 +265,20 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { props } = this;
|
|
||||||
return (
|
return (
|
||||||
<AuthenticatedScreen
|
<WebSectionList
|
||||||
navigation={props.navigation}
|
request={() =>
|
||||||
requests={[
|
ConnectionManager.getInstance().authenticatedRequest<ResponseType>(
|
||||||
{
|
'clubs/list'
|
||||||
link: 'clubs/list',
|
)
|
||||||
params: {},
|
}
|
||||||
mandatory: true,
|
createDataset={this.createDataset}
|
||||||
},
|
keyExtractor={this.keyExtractor}
|
||||||
]}
|
renderItem={this.getRenderItem}
|
||||||
renderFunction={this.getScreen}
|
renderListHeaderComponent={(data) => this.getListHeader(data)}
|
||||||
|
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
|
||||||
|
removeClippedSubviews={true}
|
||||||
|
itemHeight={LIST_ITEM_HEIGHT}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,14 @@ import { StyleSheet, View } from 'react-native';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
|
|
||||||
import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem';
|
import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem';
|
||||||
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
||||||
import { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
|
import { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
|
||||||
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
||||||
import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
|
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
import GENERAL_STYLES from '../../../constants/Styles';
|
||||||
|
import ConnectionManager from '../../../managers/ConnectionManager';
|
||||||
|
import { ApiRejectType } from '../../../utils/WebData';
|
||||||
|
import WebSectionList from '../../../components/Screens/WebSectionList';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp<any>;
|
navigation: StackNavigationProp<any>;
|
||||||
|
@ -52,6 +53,11 @@ export type RentedDeviceType = {
|
||||||
end: string;
|
end: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ResponseType = {
|
||||||
|
devices: Array<DeviceType>;
|
||||||
|
locations?: Array<RentedDeviceType>;
|
||||||
|
};
|
||||||
|
|
||||||
const LIST_ITEM_HEIGHT = 64;
|
const LIST_ITEM_HEIGHT = 64;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -65,10 +71,6 @@ const styles = StyleSheet.create({
|
||||||
class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
||||||
userRents: null | Array<RentedDeviceType>;
|
userRents: null | Array<RentedDeviceType>;
|
||||||
|
|
||||||
authRef: { current: null | AuthenticatedScreen<any> };
|
|
||||||
|
|
||||||
canRefresh: boolean;
|
|
||||||
|
|
||||||
constructor(props: PropsType) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.userRents = null;
|
this.userRents = null;
|
||||||
|
@ -77,22 +79,8 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
||||||
AsyncStorageManager.PREFERENCES.equipmentShowMascot.key
|
AsyncStorageManager.PREFERENCES.equipmentShowMascot.key
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
this.canRefresh = false;
|
|
||||||
this.authRef = React.createRef();
|
|
||||||
props.navigation.addListener('focus', this.onScreenFocus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onScreenFocus = () => {
|
|
||||||
if (
|
|
||||||
this.canRefresh &&
|
|
||||||
this.authRef.current &&
|
|
||||||
this.authRef.current.reload
|
|
||||||
) {
|
|
||||||
this.authRef.current.reload();
|
|
||||||
}
|
|
||||||
this.canRefresh = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
getRenderItem = ({ item }: { item: DeviceType }) => {
|
getRenderItem = ({ item }: { item: DeviceType }) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
return (
|
return (
|
||||||
|
@ -139,37 +127,17 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
keyExtractor = (item: DeviceType): string => item.id.toString();
|
keyExtractor = (item: DeviceType): string => item.id.toString();
|
||||||
|
|
||||||
/**
|
createDataset = (data: ResponseType | undefined) => {
|
||||||
* Gets the main screen component with the fetched data
|
if (data) {
|
||||||
*
|
const userRents = data.locations;
|
||||||
* @param data The data fetched from the server
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
getScreen = (
|
|
||||||
data: Array<
|
|
||||||
| { devices: Array<DeviceType> }
|
|
||||||
| { locations: Array<RentedDeviceType> }
|
|
||||||
| null
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
const [allDevices, userRents] = data;
|
|
||||||
if (userRents) {
|
if (userRents) {
|
||||||
this.userRents = (userRents as {
|
this.userRents = userRents;
|
||||||
locations: Array<RentedDeviceType>;
|
|
||||||
}).locations;
|
|
||||||
}
|
}
|
||||||
return (
|
return [{ title: '', data: data.devices }];
|
||||||
<CollapsibleFlatList
|
} else {
|
||||||
keyExtractor={this.keyExtractor}
|
return [{ title: '', data: [] }];
|
||||||
renderItem={this.getRenderItem}
|
|
||||||
ListHeaderComponent={this.getListHeader()}
|
|
||||||
data={
|
|
||||||
allDevices
|
|
||||||
? (allDevices as { devices: Array<DeviceType> }).devices
|
|
||||||
: null
|
|
||||||
}
|
}
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
showMascotDialog = () => {
|
showMascotDialog = () => {
|
||||||
|
@ -184,26 +152,46 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
||||||
this.setState({ mascotDialogVisible: false });
|
this.setState({ mascotDialogVisible: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
request = () => {
|
||||||
|
return new Promise(
|
||||||
|
(
|
||||||
|
resolve: (data: ResponseType) => void,
|
||||||
|
reject: (error: ApiRejectType) => void
|
||||||
|
) => {
|
||||||
|
ConnectionManager.getInstance()
|
||||||
|
.authenticatedRequest<{ devices: Array<DeviceType> }>('location/all')
|
||||||
|
.then((devicesData) => {
|
||||||
|
ConnectionManager.getInstance()
|
||||||
|
.authenticatedRequest<{
|
||||||
|
locations: Array<RentedDeviceType>;
|
||||||
|
}>('location/my')
|
||||||
|
.then((rentsData) => {
|
||||||
|
resolve({
|
||||||
|
devices: devicesData.devices,
|
||||||
|
locations: rentsData.locations,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() =>
|
||||||
|
resolve({
|
||||||
|
devices: devicesData.devices,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { props, state } = this;
|
const { state } = this;
|
||||||
return (
|
return (
|
||||||
<View style={GENERAL_STYLES.flex}>
|
<View style={GENERAL_STYLES.flex}>
|
||||||
<AuthenticatedScreen
|
<WebSectionList
|
||||||
navigation={props.navigation}
|
request={this.request}
|
||||||
ref={this.authRef}
|
createDataset={this.createDataset}
|
||||||
requests={[
|
keyExtractor={this.keyExtractor}
|
||||||
{
|
renderItem={this.getRenderItem}
|
||||||
link: 'location/all',
|
renderListHeaderComponent={() => this.getListHeader()}
|
||||||
params: {},
|
|
||||||
mandatory: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
link: 'location/my',
|
|
||||||
params: {},
|
|
||||||
mandatory: false,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
renderFunction={this.getScreen}
|
|
||||||
/>
|
/>
|
||||||
<MascotPopup
|
<MascotPopup
|
||||||
visible={state.mascotDialogVisible}
|
visible={state.mascotDialogVisible}
|
||||||
|
|
|
@ -55,7 +55,7 @@ type StateType = {
|
||||||
isPasswordValidated: boolean;
|
isPasswordValidated: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
dialogVisible: boolean;
|
dialogVisible: boolean;
|
||||||
dialogError: REQUEST_STATUS;
|
dialogError: ApiRejectType;
|
||||||
mascotDialogVisible: boolean;
|
mascotDialogVisible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,14 +110,15 @@ class LoginScreen extends React.Component<Props, StateType> {
|
||||||
this.onInputChange(false, value);
|
this.onInputChange(false, value);
|
||||||
};
|
};
|
||||||
props.navigation.addListener('focus', this.onScreenFocus);
|
props.navigation.addListener('focus', this.onScreenFocus);
|
||||||
|
// TODO remove
|
||||||
this.state = {
|
this.state = {
|
||||||
email: '',
|
email: 'vergnet@etud.insa-toulouse.fr',
|
||||||
password: '',
|
password: 'IGtt25ùj',
|
||||||
isEmailValidated: false,
|
isEmailValidated: false,
|
||||||
isPasswordValidated: false,
|
isPasswordValidated: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
dialogError: REQUEST_STATUS.SUCCESS,
|
dialogError: { status: REQUEST_STATUS.SUCCESS },
|
||||||
mascotDialogVisible: AsyncStorageManager.getBool(
|
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||||
AsyncStorageManager.PREFERENCES.loginShowMascot.key
|
AsyncStorageManager.PREFERENCES.loginShowMascot.key
|
||||||
),
|
),
|
||||||
|
@ -338,9 +339,11 @@ class LoginScreen extends React.Component<Props, StateType> {
|
||||||
* @param error The error given by the login request
|
* @param error The error given by the login request
|
||||||
*/
|
*/
|
||||||
showErrorDialog = (error: ApiRejectType) => {
|
showErrorDialog = (error: ApiRejectType) => {
|
||||||
|
console.log(error);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
dialogVisible: true,
|
dialogVisible: true,
|
||||||
dialogError: error.status,
|
dialogError: error,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -460,7 +463,8 @@ class LoginScreen extends React.Component<Props, StateType> {
|
||||||
<ErrorDialog
|
<ErrorDialog
|
||||||
visible={dialogVisible}
|
visible={dialogVisible}
|
||||||
onDismiss={this.hideErrorDialog}
|
onDismiss={this.hideErrorDialog}
|
||||||
status={dialogError}
|
status={dialogError.status}
|
||||||
|
code={dialogError.code}
|
||||||
/>
|
/>
|
||||||
</CollapsibleScrollView>
|
</CollapsibleScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
|
|
|
@ -30,7 +30,6 @@ import {
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import AuthenticatedScreen from '../../components/Amicale/AuthenticatedScreen';
|
|
||||||
import LogoutDialog from '../../components/Amicale/LogoutDialog';
|
import LogoutDialog from '../../components/Amicale/LogoutDialog';
|
||||||
import MaterialHeaderButtons, {
|
import MaterialHeaderButtons, {
|
||||||
Item,
|
Item,
|
||||||
|
@ -42,6 +41,8 @@ import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatLis
|
||||||
import type { ServiceItemType } from '../../managers/ServicesManager';
|
import type { ServiceItemType } from '../../managers/ServicesManager';
|
||||||
import GENERAL_STYLES from '../../constants/Styles';
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
import Urls from '../../constants/Urls';
|
import Urls from '../../constants/Urls';
|
||||||
|
import RequestScreen from '../../components/Screens/RequestScreen';
|
||||||
|
import ConnectionManager from '../../managers/ConnectionManager';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp<any>;
|
navigation: StackNavigationProp<any>;
|
||||||
|
@ -89,7 +90,7 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
class ProfileScreen extends React.Component<PropsType, StateType> {
|
class ProfileScreen extends React.Component<PropsType, StateType> {
|
||||||
data: ProfileDataType | null;
|
data: ProfileDataType | undefined;
|
||||||
|
|
||||||
flatListData: Array<{ id: string }>;
|
flatListData: Array<{ id: string }>;
|
||||||
|
|
||||||
|
@ -97,7 +98,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
constructor(props: PropsType) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.data = null;
|
this.data = undefined;
|
||||||
this.flatListData = [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }];
|
this.flatListData = [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }];
|
||||||
const services = new ServicesManager(props.navigation);
|
const services = new ServicesManager(props.navigation);
|
||||||
this.amicaleDataset = services.getAmicaleServices([SERVICES_KEY.PROFILE]);
|
this.amicaleDataset = services.getAmicaleServices([SERVICES_KEY.PROFILE]);
|
||||||
|
@ -134,9 +135,10 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
|
||||||
* @param data The data fetched from the server
|
* @param data The data fetched from the server
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
getScreen = (data: Array<ProfileDataType | null>) => {
|
getScreen = (data: ProfileDataType | undefined) => {
|
||||||
const { dialogVisible } = this.state;
|
const { dialogVisible } = this.state;
|
||||||
this.data = data[0];
|
if (data) {
|
||||||
|
this.data = data;
|
||||||
return (
|
return (
|
||||||
<View style={GENERAL_STYLES.flex}>
|
<View style={GENERAL_STYLES.flex}>
|
||||||
<CollapsibleFlatList
|
<CollapsibleFlatList
|
||||||
|
@ -149,6 +151,9 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return <View />;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getRenderItem = ({ item }: { item: { id: string } }) => {
|
getRenderItem = ({ item }: { item: { id: string } }) => {
|
||||||
|
@ -482,18 +487,12 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { navigation } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<AuthenticatedScreen
|
<RequestScreen<ProfileDataType>
|
||||||
navigation={navigation}
|
request={() =>
|
||||||
requests={[
|
ConnectionManager.getInstance().authenticatedRequest('user/profile')
|
||||||
{
|
}
|
||||||
link: 'user/profile',
|
render={this.getScreen}
|
||||||
params: {},
|
|
||||||
mandatory: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
renderFunction={this.getScreen}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { RefreshControl, StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import AuthenticatedScreen from '../../components/Amicale/AuthenticatedScreen';
|
|
||||||
import { getTimeOnlyString, stringToDate } from '../../utils/Planning';
|
import { getTimeOnlyString, stringToDate } from '../../utils/Planning';
|
||||||
import VoteTease from '../../components/Amicale/Vote/VoteTease';
|
import VoteTease from '../../components/Amicale/Vote/VoteTease';
|
||||||
import VoteSelect from '../../components/Amicale/Vote/VoteSelect';
|
import VoteSelect from '../../components/Amicale/Vote/VoteSelect';
|
||||||
|
@ -32,8 +30,11 @@ import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable';
|
import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable';
|
||||||
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
|
||||||
import GENERAL_STYLES from '../../constants/Styles';
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
|
import ConnectionManager from '../../managers/ConnectionManager';
|
||||||
|
import WebSectionList, {
|
||||||
|
SectionListDataType,
|
||||||
|
} from '../../components/Screens/WebSectionList';
|
||||||
|
|
||||||
export type VoteTeamType = {
|
export type VoteTeamType = {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -60,6 +61,11 @@ type VoteDatesObjectType = {
|
||||||
date_result_end: Date;
|
date_result_end: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ResponseType = {
|
||||||
|
teams?: TeamResponseType;
|
||||||
|
dates?: VoteDatesStringType;
|
||||||
|
};
|
||||||
|
|
||||||
// const FAKE_DATE = {
|
// const FAKE_DATE = {
|
||||||
// "date_begin": "2020-08-19 15:50",
|
// "date_begin": "2020-08-19 15:50",
|
||||||
// "date_end": "2020-08-19 15:50",
|
// "date_end": "2020-08-19 15:50",
|
||||||
|
@ -108,11 +114,7 @@ type VoteDatesObjectType = {
|
||||||
// ],
|
// ],
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const MIN_REFRESH_TIME = 5 * 1000;
|
type PropsType = {};
|
||||||
|
|
||||||
type PropsType = {
|
|
||||||
navigation: StackNavigationProp<any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StateType = {
|
type StateType = {
|
||||||
hasVoted: boolean;
|
hasVoted: boolean;
|
||||||
|
@ -135,23 +137,21 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
hasVoted: boolean;
|
hasVoted: boolean;
|
||||||
|
|
||||||
datesString: null | VoteDatesStringType;
|
datesString: undefined | VoteDatesStringType;
|
||||||
|
|
||||||
dates: null | VoteDatesObjectType;
|
dates: undefined | VoteDatesObjectType;
|
||||||
|
|
||||||
today: Date;
|
today: Date;
|
||||||
|
|
||||||
mainFlatListData: Array<{ key: string }>;
|
mainFlatListData: SectionListDataType<{ key: string }>;
|
||||||
|
|
||||||
lastRefresh: Date | null;
|
refreshData: () => void;
|
||||||
|
|
||||||
authRef: { current: null | AuthenticatedScreen<any> };
|
|
||||||
|
|
||||||
constructor(props: PropsType) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.teams = [];
|
this.teams = [];
|
||||||
this.datesString = null;
|
this.datesString = undefined;
|
||||||
this.dates = null;
|
this.dates = undefined;
|
||||||
this.state = {
|
this.state = {
|
||||||
hasVoted: false,
|
hasVoted: false,
|
||||||
mascotDialogVisible: AsyncStorageManager.getBool(
|
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||||
|
@ -160,9 +160,10 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
||||||
};
|
};
|
||||||
this.hasVoted = false;
|
this.hasVoted = false;
|
||||||
this.today = new Date();
|
this.today = new Date();
|
||||||
this.authRef = React.createRef();
|
this.refreshData = () => undefined;
|
||||||
this.lastRefresh = null;
|
this.mainFlatListData = [
|
||||||
this.mainFlatListData = [{ key: 'main' }, { key: 'info' }];
|
{ title: '', data: [{ key: 'main' }, { key: 'info' }] },
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -201,37 +202,32 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
||||||
return this.getContent();
|
return this.getContent();
|
||||||
};
|
};
|
||||||
|
|
||||||
getScreen = (data: Array<TeamResponseType | VoteDatesStringType | null>) => {
|
createDataset = (
|
||||||
const { state } = this;
|
data: ResponseType | undefined,
|
||||||
|
_loading: boolean,
|
||||||
|
_lastRefreshDate: Date | undefined,
|
||||||
|
refreshData: () => void
|
||||||
|
) => {
|
||||||
// data[0] = FAKE_TEAMS2;
|
// data[0] = FAKE_TEAMS2;
|
||||||
// data[1] = FAKE_DATE;
|
// data[1] = FAKE_DATE;
|
||||||
this.lastRefresh = new Date();
|
this.refreshData = refreshData;
|
||||||
|
if (data) {
|
||||||
|
const { teams, dates } = data;
|
||||||
|
|
||||||
const teams = data[0] as TeamResponseType | null;
|
if (dates && dates.date_begin == null) {
|
||||||
const dateStrings = data[1] as VoteDatesStringType | null;
|
this.datesString = undefined;
|
||||||
|
|
||||||
if (dateStrings != null && dateStrings.date_begin == null) {
|
|
||||||
this.datesString = null;
|
|
||||||
} else {
|
} else {
|
||||||
this.datesString = dateStrings;
|
this.datesString = dates;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (teams != null) {
|
if (teams) {
|
||||||
this.teams = teams.teams;
|
this.teams = teams.teams;
|
||||||
this.hasVoted = teams.has_voted;
|
this.hasVoted = teams.has_voted;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.generateDateObject();
|
this.generateDateObject();
|
||||||
return (
|
|
||||||
<CollapsibleFlatList
|
|
||||||
data={this.mainFlatListData}
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={false} onRefresh={this.reloadData} />
|
|
||||||
}
|
}
|
||||||
extraData={state.hasVoted.toString()}
|
return this.mainFlatListData;
|
||||||
renderItem={this.getMainRenderItem}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getContent() {
|
getContent() {
|
||||||
|
@ -261,7 +257,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
||||||
<VoteSelect
|
<VoteSelect
|
||||||
teams={this.teams}
|
teams={this.teams}
|
||||||
onVoteSuccess={this.onVoteSuccess}
|
onVoteSuccess={this.onVoteSuccess}
|
||||||
onVoteError={this.reloadData}
|
onVoteError={this.refreshData}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -327,23 +323,6 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloads vote data if last refresh delta is smaller than the minimum refresh time
|
|
||||||
*/
|
|
||||||
reloadData = () => {
|
|
||||||
let canRefresh;
|
|
||||||
const { lastRefresh } = this;
|
|
||||||
if (lastRefresh != null) {
|
|
||||||
canRefresh =
|
|
||||||
new Date().getTime() - lastRefresh.getTime() > MIN_REFRESH_TIME;
|
|
||||||
} else {
|
|
||||||
canRefresh = true;
|
|
||||||
}
|
|
||||||
if (canRefresh && this.authRef.current != null) {
|
|
||||||
this.authRef.current.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
showMascotDialog = () => {
|
showMascotDialog = () => {
|
||||||
this.setState({ mascotDialogVisible: true });
|
this.setState({ mascotDialogVisible: true });
|
||||||
};
|
};
|
||||||
|
@ -403,13 +382,36 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
||||||
date_result_end: dateResultEnd,
|
date_result_end: dateResultEnd,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this.dates = null;
|
this.dates = undefined;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.dates = null;
|
this.dates = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request = () => {
|
||||||
|
return new Promise((resolve: (data: ResponseType) => void) => {
|
||||||
|
ConnectionManager.getInstance()
|
||||||
|
.authenticatedRequest<VoteDatesStringType>('elections/dates')
|
||||||
|
.then((datesData) => {
|
||||||
|
ConnectionManager.getInstance()
|
||||||
|
.authenticatedRequest<TeamResponseType>('elections/teams')
|
||||||
|
.then((teamsData) => {
|
||||||
|
resolve({
|
||||||
|
dates: datesData,
|
||||||
|
teams: teamsData,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() =>
|
||||||
|
resolve({
|
||||||
|
dates: datesData,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(() => resolve({}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the authenticated screen.
|
* Renders the authenticated screen.
|
||||||
*
|
*
|
||||||
|
@ -418,25 +420,14 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { props, state } = this;
|
const { state } = this;
|
||||||
return (
|
return (
|
||||||
<View style={GENERAL_STYLES.flex}>
|
<View style={GENERAL_STYLES.flex}>
|
||||||
<AuthenticatedScreen<TeamResponseType | VoteDatesStringType>
|
<WebSectionList
|
||||||
navigation={props.navigation}
|
request={this.request}
|
||||||
ref={this.authRef}
|
createDataset={this.createDataset}
|
||||||
requests={[
|
extraData={state.hasVoted.toString()}
|
||||||
{
|
renderItem={this.getMainRenderItem}
|
||||||
link: 'elections/teams',
|
|
||||||
params: {},
|
|
||||||
mandatory: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
link: 'elections/dates',
|
|
||||||
params: {},
|
|
||||||
mandatory: false,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
renderFunction={this.getScreen}
|
|
||||||
/>
|
/>
|
||||||
<MascotPopup
|
<MascotPopup
|
||||||
visible={state.mascotDialogVisible}
|
visible={state.mascotDialogVisible}
|
||||||
|
|
|
@ -39,8 +39,9 @@ export type ApiDataLoginType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type ApiResponseType<T> = {
|
type ApiResponseType<T> = {
|
||||||
error: number;
|
status: REQUEST_STATUS;
|
||||||
data: T;
|
error?: API_REQUEST_CODES;
|
||||||
|
data?: T;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiRejectType = {
|
export type ApiRejectType = {
|
||||||
|
@ -87,6 +88,8 @@ export async function apiRequest<T>(
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
requestParams = { ...params };
|
requestParams = { ...params };
|
||||||
}
|
}
|
||||||
|
console.log(Urls.amicale.api + path);
|
||||||
|
|
||||||
fetch(Urls.amicale.api + path, {
|
fetch(Urls.amicale.api + path, {
|
||||||
method,
|
method,
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
|
@ -95,12 +98,20 @@ export async function apiRequest<T>(
|
||||||
}),
|
}),
|
||||||
body: JSON.stringify(requestParams),
|
body: JSON.stringify(requestParams),
|
||||||
})
|
})
|
||||||
.then(
|
.then((response: Response) => {
|
||||||
async (response: Response): Promise<ApiResponseType<T>> =>
|
const status = response.status;
|
||||||
response.json()
|
if (status === REQUEST_STATUS.SUCCESS) {
|
||||||
)
|
return response.json().then(
|
||||||
|
(data): ApiResponseType<T> => {
|
||||||
|
return { status: status, error: data.error, data: data.data };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return { status: status };
|
||||||
|
}
|
||||||
|
})
|
||||||
.then((response: ApiResponseType<T>) => {
|
.then((response: ApiResponseType<T>) => {
|
||||||
if (isApiResponseValid(response)) {
|
if (isApiResponseValid(response) && response.data) {
|
||||||
if (response.error === API_REQUEST_CODES.SUCCESS) {
|
if (response.error === API_REQUEST_CODES.SUCCESS) {
|
||||||
resolve(response.data);
|
resolve(response.data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -111,15 +122,15 @@ export async function apiRequest<T>(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reject({
|
reject({
|
||||||
status: REQUEST_STATUS.SERVER_ERROR,
|
status: response.status,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() => {
|
||||||
reject({
|
reject({
|
||||||
status: REQUEST_STATUS.SERVER_ERROR,
|
status: REQUEST_STATUS.CONNECTION_ERROR,
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue