diff --git a/packages/api/lib/backend.js b/packages/api/lib/backend.js index 71337d33e..60251864a 100644 --- a/packages/api/lib/backend.js +++ b/packages/api/lib/backend.js @@ -1,6 +1,6 @@ const moment = require('moment') const h2m = require('h2m') -const {htmlDecode} = require('js-htmlencode') +const { htmlDecode } = require('js-htmlencode') const urls = require('./urls') const { fetchJson, fetchText, fetchRaw } = require('./fetch') @@ -16,7 +16,7 @@ const login = async (socialSecurityNumber) => { return token } -const waitForToken = async ({order}, tries = 60) => { +const waitForToken = async ({ order }, tries = 60) => { if (!tries) return Promise.reject('Timeout') const status = await fetchText(urls.checkStatus(order)) if (status === 'OK') { @@ -25,18 +25,18 @@ const waitForToken = async ({order}, tries = 60) => { } else { await pause(1000) console.log('retry', tries) - return await waitForToken({order}, tries--) + return await waitForToken({ order }, tries--) } } const getChildren = async (cookie) => { const children = await fetchJson(urls.children, cookie) return children - .map(({name, id, sdsId, status, schoolId}) => - ({name, id, sdsId, status, schoolId})) + .map(({ name, id, sdsId, status, schoolId }) => + ({ name, id, sdsId, status, schoolId })) } -const getNews = async(childId, cookie) => { +const getNews = async (childId, cookie) => { const news = await fetchJson(urls.news(childId), cookie) return news .newsItems @@ -60,7 +60,7 @@ const getNews = async(childId, cookie) => { const getCalendar = async (childId, cookie) => { const url = urls.calendar(childId) const calendar = await fetchJson(url, cookie) - + return calendar .map(({ title, @@ -84,7 +84,7 @@ const getCalendar = async (childId, cookie) => { const getNotifications = async (childId, cookie) => { const url = urls.notifications(childId) const notifications = await fetchJson(url, cookie) - + return notifications .map(({ notificationMessage: { @@ -124,7 +124,7 @@ const getSchedule = async (childId, cookie) => { const to = moment().add(7, 'days').format('YYYY-MM-DD') const url = urls.schedule(childId, from, to) const schedule = await fetchJson(url, cookie) - + return schedule .map(({ title, @@ -150,7 +150,7 @@ const getSchedule = async (childId, cookie) => { const getClassmates = async (childId, cookie) => { const url = urls.classmates(childId) const classmates = await fetchJson(url, cookie) - + return classmates .map(({ sisId, @@ -191,10 +191,10 @@ const getChildById = async (childId, cookie) => { menu, schedule ] = [ - await getNews(childId, cookie), - await getCalendar(childId, cookie), - await getNotifications(child.sdsId, cookie), - await getMenu(child.id, cookie), + await getNews(childId, cookie), + await getCalendar(childId, cookie), + await getNotifications(child.sdsId, cookie), + await getMenu(child.id, cookie), await getSchedule(child.id, cookie) ] @@ -220,4 +220,4 @@ module.exports = { getSchedule, getClassmates, download -} \ No newline at end of file +} diff --git a/packages/app/components/children.component.js b/packages/app/components/children.component.js index 4ec2852d7..e080aac4b 100644 --- a/packages/app/components/children.component.js +++ b/packages/app/components/children.component.js @@ -1,11 +1,11 @@ -import React, {useState, useMemo, useCallback, useEffect } from 'react' -import { StyleSheet, View, Image } from 'react-native' -import { SafeAreaView } from 'react-native' +import React, { useEffect } from 'react' +import { StyleSheet, View, Image, SafeAreaView } from 'react-native' + import moment from 'moment' import { Divider, Button, Icon, Layout, Text, TopNavigation, TopNavigationAction, List, Card, Avatar, Spinner } from '@ui-kitten/components' // import children from '../output.json' -import {useAsyncStorage} from 'use-async-storage' -import {api, loadChildrenDetails} from '../lib/backend' +import { useAsyncStorage } from 'use-async-storage' +import { api, fillChild } from '../lib/backend' const colors = ['primary', 'success', 'info', 'warning', 'danger'] @@ -25,38 +25,41 @@ const PeopleIcon = (style) => ( ) -export const Children = ({navigation}) => { +export const Children = ({ navigation }) => { const [children, setChildren] = useAsyncStorage('@children', []) - + const [cookie] = useAsyncStorage('@cookie') useEffect(() => { const load = async () => { try { - const childrenList = children?.length || await api.getChildren() + const childrenList = (children?.length && children) || await api.getChildren() if (!childrenList?.length) { - console.log('no children found') - return navigation.navigate('Login', {error: 'Hittar inga barn med det personnumret'}) + console.log('no children found', await api.getChildren()) + return navigation.navigate('Login', { error: 'Hittar inga barn för det personnumret' }) } - // Update the list with all details we get the most often updated info first - const fullChildren = await loadChildrenDetails(childrenList, {calendar: true, schedule: true, news: true, menu:true, notifications: true, classmates: true}) - setChildren(fullChildren) - + childrenList.forEach(async (child, i) => { + let result + let updatedChild // keep a reference to the latest updated information so we don't patch an old object + const iter = fillChild(child) + while (!result?.done) { + result = await iter.next() // get updated values for every updated property + if (result.done) break + const updated = await result.value + childrenList[i] = updatedChild = { ...updatedChild, ...updated } + await setChildren(childrenList) // update after each new information we get. Might be too much? + } + }) } catch (err) { console.log('err', err) - navigation.navigate('Login', {error: 'Fel uppstod, försök igen'}) + navigation.navigate('Login', { error: 'Fel uppstod, försök igen' }) } } - if (api.isLoggedIn) load() - }, [api.isLoggedIn]) - - return + if (cookie) load() + }, [cookie]) + return } - -export const ChildrenView = ({ navigation, children, eva }) => { - - - +export const ChildrenView = ({ navigation, childList, eva }) => { const abbrevations = { G: 'Gymnasiet', // ? i'm guessing here GR: 'Grundskolan', @@ -67,7 +70,7 @@ export const ChildrenView = ({ navigation, children, eva }) => { } const navigateChild = (child, color) => { - navigation.navigate('Child', {child, color}) + navigation.navigate('Child', { child, color }) } const BackAction = () => ( @@ -75,11 +78,11 @@ export const ChildrenView = ({ navigation, children, eva }) => { ) const Header = (props, info, i) => ( - - - + + + - + {info.item.name?.split('(')[0]} @@ -96,21 +99,24 @@ export const ChildrenView = ({ navigation, children, eva }) => { style={styles.iconButton} status='control' size='small' - accessoryLeft={NotificationIcon}> + accessoryLeft={NotificationIcon} + > {`${(info.item.news || []).length}`} nyheter @@ -118,41 +124,48 @@ export const ChildrenView = ({ navigation, children, eva }) => { const renderItem = (info) => { const color = colors[info.index % colors.length] - return Header(headerProps, info, info.index)} - footer={footerProps => Footer(footerProps, info)} - onPress={() => navigateChild(info.item, color)}> - - {([...info.item.calendar, ...info.item.schedule].filter(a => moment(a.startDate, 'YYYY-MM-DD hh:mm').isSame('day'))).map((calendarItem, i) => - - {`${calendarItem.title}`} - - )} - + return ( + Header(headerProps, info, info.index)} + footer={footerProps => Footer(footerProps, info)} + onPress={() => navigateChild(info.item, color)} + > + + {([...info.item.calendar ?? [], ...info.item.schedule ?? []].filter(a => moment(a.startDate, 'YYYY-MM-DD hh:mm').isSame('day'))).map((calendarItem, i) => + + {`${calendarItem.title}`} + + )} + + ) } return ( - - - + + + - {children?.length ? + { + childList?.length + ? + style={styles.container} + contentContainerStyle={styles.contentContainer} + data={childList} + renderItem={renderItem} + /> : - - - - Laddar... - - } + + + + Laddar... + + + } diff --git a/packages/app/components/classmates.component.js b/packages/app/components/classmates.component.js index ae6fe09e8..0fdcf31da 100644 --- a/packages/app/components/classmates.component.js +++ b/packages/app/components/classmates.component.js @@ -1,20 +1,21 @@ -import React from 'react'; -import { StyleSheet } from 'react-native'; -import { Divider, List, ListItem, Icon, Text, Button} from '@ui-kitten/components'; -import { ContactMenu } from './contactMenu.component'; +import React from 'react' +import { StyleSheet } from 'react-native' +import { Divider, List, ListItem, Icon } from '@ui-kitten/components' +import { ContactMenu } from './contactMenu.component' -export const Classmates = ({classmates}) => { - - const renderItemIcon = (props) => +export const Classmates = ({ classmates }) => { + const renderItemIcon = (props) => + const [selected, setSelected] = React.useState() const renderItem = ({ item }) => ( setSelected(item)} description={item.guardians.map(guardian => `${guardian.firstname} ${guardian.lastname}`).join(', ')} accessoryLeft={renderItemIcon} - accessoryRight={(props) => ContactMenu({...props, contact: item})} + accessoryRight={(props) => ContactMenu({ ...props, contact: item, selected: item === selected, setSelected })} /> - ); + ) return ( { ItemSeparatorComponent={Divider} renderItem={renderItem} /> - ); -}; + ) +} const styles = StyleSheet.create({ container: { - width: "100%", - }, -}); \ No newline at end of file + width: '100%' + } +}) diff --git a/packages/app/components/contactMenu.component.js b/packages/app/components/contactMenu.component.js index 8163ae650..5261879bc 100644 --- a/packages/app/components/contactMenu.component.js +++ b/packages/app/components/contactMenu.component.js @@ -1,38 +1,46 @@ -import React from 'react'; -import { StyleSheet } from 'react-native'; -import { Button, Icon, Layout, MenuItem, MenuGroup, OverflowMenu, Text, Divider } from '@ui-kitten/components'; -import {Linking} from 'react-native' +import React from 'react' +import { StyleSheet, Linking } from 'react-native' +import { Button, Icon, MenuItem, MenuGroup, OverflowMenu } from '@ui-kitten/components' +export const ContactMenu = ({ contact, selected, setSelected }) => { + const [visible, setVisible] = React.useState(selected) -export const ContactMenu = ({contact}) => { - - const [visible, setVisible] = React.useState(false); - const [selectedTitle, setSelectedTitle] = React.useState('No items selected'); - - const contactIcon = (props) => + const contactIcon = (props) => const renderToggleButton = () => ( - - ); + - - - - : - - { + + {loggedIn + ? + + + {socialSecurityNumber} + {error || 'Hurra, du är inloggad!'} + + + + + : + + { // hidden easter egg, just touch the image to login without bankId if you still have a valid token } - - - - - + + + + handleInput(text)} - placeholder="Ditt personnr (10 eller 12 siffror)"/> - - - - - } - handleInput(text)} + placeholder='Ditt personnr (10 eller 12 siffror)' + /> + + + + } + setVisible(false)}> - - {hasBankId ? Öppnar BankID. Växla tillbaka hit sen. : Väntar på BankID...} - - @@ -167,12 +176,12 @@ export const Login = ({ navigation, route }) => { const styles = StyleSheet.create({ container: { - minHeight: 192, + minHeight: 192 }, modal: { - width: "80%" + width: '80%' }, backdrop: { - backgroundColor: 'rgba(0, 0, 0, 0.5)', - }, -}) \ No newline at end of file + backgroundColor: 'rgba(0, 0, 0, 0.5)' + } +}) diff --git a/packages/app/components/newsItem.component.js b/packages/app/components/newsItem.component.js index 59fa18390..46e2227dc 100644 --- a/packages/app/components/newsItem.component.js +++ b/packages/app/components/newsItem.component.js @@ -23,10 +23,10 @@ export const NewsItem = ({ navigation, route }) => { {newsItem.header} - + ) - + const rules = { image: ( node, @@ -34,22 +34,22 @@ export const NewsItem = ({ navigation, route }) => { parent, styles, allowedImageHandlers, - defaultImageHandler, + defaultImageHandler ) => { - const {src, alt} = node.attributes; - return + const { src } = node.attributes + return } } return ( - + - - + + renderItemHeader(headerProps, newsItem)}> - + {decodeURIComponent(newsItem.body)} @@ -67,5 +67,9 @@ const styles = StyleSheet.create({ card: { flex: 1, margin: 2 + }, + image: { + width: '100%', + minHeight: 300 } }) diff --git a/packages/app/lib/backend.js b/packages/app/lib/backend.js index 14a66e55e..203c3098b 100644 --- a/packages/app/lib/backend.js +++ b/packages/app/lib/backend.js @@ -1,14 +1,22 @@ -import moment from 'moment' -import init from "@skolplattformen/embedded-api" -export const api = init(fetch) // keep a static version of this object so we can keep the session alive - -export const loadChildrenDetails = async (children, what = {news: true}) => await Promise.all(children.map(async child => ({ - ...child, - news: !what.news ? child.news : await api.getNews(child).catch(err => [{err}]), - calendar: !what.calendar ? child.calendar : await api.getCalendar(child).catch(err => [{err}]), - notifications: !what.notifications ? child.notifications : await api.getNotifications(child).catch(err => [{err}]), - schedule: !what.schedule ? child.schedule : await api.getSchedule(child, moment().startOf('day'), moment().add(7,'days').endOf('day')).catch(err => [{err}]), - classmates: !what.classmates ? child.classmates : await api.getClassmates(child).catch(err => [{err}]), - menu: !what.menu ? child.menu : await api.getMenu(child).catch(err => [{err}]), -}))) +import init from '@skolplattformen/embedded-api' +export const api = init(fetch) // keep a static version of this object so we can keep the session alive +export function * fillChild (child) { + console.log(`loading notifications for ${child.name}...`) + yield api.getNotifications(child).catch(err => Promise.resolve([{ err }])).then(notifications => ({ ...child, notifications })) + + console.log(`loading news for ${child.name}...`) + yield api.getNews(child).catch(err => Promise.resolve([{ err }])).then(news => ({ ...child, news })) + + console.log(`loading menu for ${child.name}...`) + yield api.getMenu(child).catch(err => Promise.resolve([{ err }])).then(menu => ({ ...child, menu })) + + console.log(`loading calendar for ${child.name}...`) + yield api.getCalendar(child).catch(err => Promise.resolve([{ err }])).then(calendar => ({ ...child, calendar })) + + console.log(`loading schedule for ${child.name}...`) + yield api.getSchedule(child).catch(err => Promise.resolve([{ err }])).then(schedule => ({ ...child, schedule })) + + console.log(`loading classmates for ${child.name}...`) + yield api.getClassmates(child).catch(err => Promise.resolve([{ err }])).then(classmates => ({ ...child, classmates })) +}