skolplattformen-backup/apps/skolplattformen-app/components/login.component.tsx

362 lines
10 KiB
TypeScript

import { useApi } from '@skolplattformen/hooks'
import {
Button,
ButtonGroup,
Card,
Divider,
Input,
List,
ListItem,
Modal,
StyleService,
Text,
useStyleSheet,
} from '@ui-kitten/components'
import Personnummer from 'personnummer'
import React, { useContext, useEffect, useState } from 'react'
import {
Image,
Linking,
Platform,
TouchableWithoutFeedback,
View,
} from 'react-native'
import { schema } from '../app.json'
import { SchoolPlatformContext } from '../context/schoolPlatform/schoolPlatformContext'
import { schoolPlatforms } from '../data/schoolPlatforms'
import { useFeature } from '../hooks/useFeature'
import useSettingsStorage from '../hooks/useSettingsStorage'
import { useTranslation } from '../hooks/useTranslation'
import { Layout } from '../styles'
import {
CheckIcon,
CloseOutlineIcon,
PersonIcon,
SelectIcon,
} from './icon.component'
import AppStorage from '../services/appStorage'
const BankId = () => (
<Image
style={themedStyles.icon}
source={require('../assets/bankid_low_rgb.png')}
accessibilityIgnoresInvertColors
/>
)
interface Logins {
BANKID_SAME_DEVICE: number
BANKID_ANOTHER_DEVICE: number
TEST_USER: number
}
const LoginMethods: Logins = {
BANKID_SAME_DEVICE: 0,
BANKID_ANOTHER_DEVICE: 2,
TEST_USER: 3,
}
export const Login = () => {
const { api } = useApi()
const [cancelLoginRequest, setCancelLoginRequest] = useState<
(() => Promise<void>) | (() => null)
>(() => () => null)
const [visible, showModal] = useState(false)
const [showLoginMethod, setShowLoginMethod] = useState(false)
const [showSchoolPlatformPicker, setShowSchoolPlatformPicker] =
useState(false)
const [error, setError] = useState<string | null>(null)
const [personalIdNumber, setPersonalIdNumber] = useSettingsStorage(
'cachedPersonalIdentityNumber'
)
const [loginMethodId, setLoginMethodId] = useSettingsStorage('loginMethodId')
const loginBankIdSameDeviceWithoutId = useFeature(
'LOGIN_BANK_ID_SAME_DEVICE_WITHOUT_ID'
)
const { currentSchoolPlatform, changeSchoolPlatform } = useContext(
SchoolPlatformContext
)
const { t } = useTranslation()
const valid = Personnummer.valid(personalIdNumber)
const loginMethods = [
{ id: 'thisdevice', title: t('auth.bankid.OpenOnThisDevice') },
{ id: 'otherdevice', title: t('auth.bankid.OpenOnAnotherDevice') },
{ id: 'testuser', title: t('auth.loginAsTestUser') },
] as const
const loginHandler = async () => {
const user = await api.getUser()
await AppStorage.clearPersonalData(user)
showModal(false)
}
useEffect(() => {
api.on('login', loginHandler)
return () => {
api.off('login', loginHandler)
}
}, [api])
const getSchoolPlatformName = () => {
return schoolPlatforms.find((item) => item.id === currentSchoolPlatform)
?.displayName
}
const openBankId = (token: string) => {
try {
const redirect =
loginMethodId === 'thisdevice' ? encodeURIComponent(schema) : ''
const bankIdUrl =
Platform.OS === 'ios'
? `https://app.bankid.com/?autostarttoken=${token}&redirect=${redirect}`
: `bankid:///?autostarttoken=${token}&redirect=null`
Linking.openURL(bankIdUrl)
} catch (err) {
setError(t('auth.bankid.OpenManually'))
}
}
const isUsingPersonalIdNumber =
loginMethodId === 'otherdevice' ||
(loginMethodId === 'thisdevice' && !loginBankIdSameDeviceWithoutId)
const startLogin = async (text: string) => {
if (loginMethodId === 'thisdevice' || loginMethodId === 'otherdevice') {
showModal(true)
let ssn
if (isUsingPersonalIdNumber) {
ssn = Personnummer.parse(text).format(true)
setPersonalIdNumber(ssn)
}
const status = await api.login(ssn)
setCancelLoginRequest(() => () => status.cancel())
if (status.token !== 'fake' && loginMethodId === 'thisdevice') {
openBankId(status.token)
}
status.on('PENDING', () => console.log('BankID app not yet opened'))
status.on('USER_SIGN', () => console.log('BankID app is open'))
status.on('CANCELLED', () => {
console.log('User pressed cancel in BankID')
showModal(false)
})
status.on('ERROR', () => {
setError(t('auth.loginFailed'))
showModal(false)
})
status.on('OK', () => console.log('BankID ok'))
} else {
await api.login('201212121212')
}
}
const styles = useStyleSheet(themedStyles)
const currentLoginMethod =
loginMethods.find((method) => method.id === loginMethodId) ||
loginMethods[0]
return (
<>
<View style={styles.loginForm}>
{isUsingPersonalIdNumber && (
<Input
accessible={true}
label={t('general.socialSecurityNumber')}
autoFocus
value={personalIdNumber}
style={styles.pnrInput}
accessoryLeft={PersonIcon}
accessoryRight={(props) => (
<TouchableWithoutFeedback
accessible={true}
onPress={() => setPersonalIdNumber('')}
accessibilityHint={t(
'login.a11y_clear_social_security_input_field',
{ defaultValue: 'Rensa fältet för personnummer' }
)}
>
<CloseOutlineIcon {...props} />
</TouchableWithoutFeedback>
)}
keyboardType="numeric"
onSubmitEditing={(event) => startLogin(event.nativeEvent.text)}
caption={error || ''}
onChangeText={setPersonalIdNumber}
placeholder={t('auth.placeholder_SocialSecurityNumber')}
/>
)}
<ButtonGroup style={styles.loginButtonGroup} status="primary">
<Button
accessible={true}
onPress={() => startLogin(personalIdNumber)}
style={styles.loginButton}
appearance="ghost"
disabled={isUsingPersonalIdNumber && !valid}
status="primary"
accessoryLeft={BankId}
size="medium"
>
{currentLoginMethod.title}
</Button>
<Button
accessible={true}
onPress={() => {
setShowLoginMethod(true)
}}
style={styles.loginMethodButton}
appearance="ghost"
status="primary"
accessoryLeft={SelectIcon}
size="medium"
accessibilityHint={t('login.a11y_select_login_method', {
defaultValue: 'Välj inloggningsmetod',
})}
/>
</ButtonGroup>
<View style={styles.platformPicker}>
<Button
appearance="ghost"
status="basic"
size="small"
accessoryRight={SelectIcon}
onPress={() => {
setShowSchoolPlatformPicker(true)
}}
>
{getSchoolPlatformName()}
</Button>
</View>
</View>
<Modal
visible={showLoginMethod}
style={styles.modal}
onBackdropPress={() => setShowLoginMethod(false)}
backdropStyle={styles.backdrop}
>
<Card>
<Text category="h5" style={styles.bankIdLoading}>
{t('auth.chooseLoginMethod')}
</Text>
<List
data={loginMethods}
ItemSeparatorComponent={Divider}
renderItem={({ item, index }) => (
<ListItem
title={item.title}
accessible={true}
accessoryRight={
loginMethodId === item.id ? CheckIcon : undefined
}
onPress={() => {
setLoginMethodId(item.id)
setShowLoginMethod(false)
}}
/>
)}
/>
<Button
status="basic"
style={styles.cancelButtonStyle}
onPress={() => {
setShowLoginMethod(false)
}}
>
{t('general.cancel')}
</Button>
</Card>
</Modal>
<Modal
visible={visible}
style={styles.modal}
onBackdropPress={() => showModal(false)}
backdropStyle={styles.backdrop}
>
<Card disabled>
<Text style={styles.bankIdLoading}>{t('auth.bankid.Waiting')}</Text>
<Button
status="primary"
accessible={true}
onPress={() => {
cancelLoginRequest()
showModal(false)
}}
>
{t('general.cancel')}
</Button>
</Card>
</Modal>
<Modal
visible={showSchoolPlatformPicker}
style={styles.modal}
onBackdropPress={() => setShowSchoolPlatformPicker(false)}
backdropStyle={styles.backdrop}
>
<Card>
<Text category="h5" style={styles.bankIdLoading}>
{t('auth.chooseSchoolPlatform')}
</Text>
<List
data={schoolPlatforms}
ItemSeparatorComponent={Divider}
renderItem={({ item }) => (
<ListItem
title={item.displayName}
accessible={true}
accessoryRight={
currentSchoolPlatform === item.id ? CheckIcon : undefined
}
onPress={() => {
changeSchoolPlatform(item.id)
setShowSchoolPlatformPicker(false)
}}
/>
)}
/>
<Button
status="basic"
style={styles.cancelButtonStyle}
onPress={() => setShowSchoolPlatformPicker(false)}
>
{t('general.cancel')}
</Button>
</Card>
</Modal>
</>
)
}
const themedStyles = StyleService.create({
backdrop: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
loginForm: {
...Layout.mainAxis.flexStart,
},
pnrInput: { minHeight: 70 },
loginButtonGroup: {
minHeight: 45,
},
loginButton: { ...Layout.flex.full },
loginMethodButton: { width: 45 },
modal: {
width: '90%',
},
bankIdLoading: { margin: 10 },
cancelButtonStyle: { marginTop: 15 },
icon: {
width: 20,
height: 20,
},
platformPicker: {
width: '100%',
},
})