diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index c1de2e9..345b23e 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -19,4 +19,5 @@ class APIController < ActionController::API include MailController include SmokeTestsController include UtilController + include ParentController end diff --git a/app/controllers/parent_controller.rb b/app/controllers/parent_controller.rb new file mode 100644 index 0000000..7465e53 --- /dev/null +++ b/app/controllers/parent_controller.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true +module ParentController + extend ActiveSupport::Concern + + included do + # Invite another parent + post '/v1/parent/invite' do + # Validate if current user has correct children + request_json[:children].each do |child_id| + child = User.find(child_id) + ensure_or_forbidden! { current_user.parent_of?(child) } + end + + # Check whether the user already exists + @user = User.find_by_email_or_mobile(request_json[:email_or_mobile]) + is_new = false + + if !@user + # if user does not exist, create new one + e_or_m = EmailOrPhone.new(request_json[:email_or_mobile]) + is_new = true + + @user = User.new( + first_name: request_json[:first_name], + last_name: request_json[:last_name], + email: e_or_m.email? ? e_or_m.value : nil, + mobile_number: e_or_m.phone? ? e_or_m.value : nil, + ) + end + + @user.children = (@user.children + request_json[:children].map { |child_id| + User.find(child_id) + }).uniq + + if @user.save + if is_new + # send an email to parent so that they can have access to platform + InviteWorker.perform_async(@user.id) + end + render json: UserSerializer.new(@user) + else + error_response(@user) + end + end + end +end \ No newline at end of file diff --git a/app/workers/invite_parent_worker.rb b/app/workers/invite_parent_worker.rb new file mode 100644 index 0000000..7d4fd95 --- /dev/null +++ b/app/workers/invite_parent_worker.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true +class InviteParentWorker < ApplicationWorker + # TODO: Make invites locations specific + def html_template + # TODO: Generalize + Erubi::Engine.new(<<~HTML).src +

Welcome to Greenlight / Bienvenido a Greenlight

+

+ Hi <%= user.first_name %>, +

+

+ Every day you will receive notifications to submit symptom surveys on behalf of your children: +

+ +

+ Login by resetting your password or requesting a magic sign in link. +

+

+ You can select which days to receive notifications or disable notifications by visiting the notification page in settings. +

+

Thanks for your help, and let us know if you have questions.

+

Stay safe out there,
+ The Greenlight Team +

+
+ +

+ Hola <%= user.first_name %>, +

+

+ <%= user.first_name %> te ha agregado a Greenlight + Todos los días recibirá notificaciones para enviar encuestas de síntomas en nombre de sus hijos: +

+ +

+ Inicie sesión restableciendo su contraseña o solicitando un enlace de inicio de sesión mágico . +

+

+ Puede seleccionar qué días recibir notificaciones o deshabilitar las notificaciones visitando la página de notificaciones en la configuración. +

+

Gracias por su ayuda y avísenos si tiene preguntas.

+

Mantente a salvo,
+ El Equipo Greenlight +

+ HTML + end + + def sms_template + # TODO: Generalize + Erubi::Engine.new(<<~SMS + You have been added to Greenlight by <%= current_user.first_name %> + Every day you will receive notifications to submit symptom surveys on behalf of your children: + <%= user.children.map(%:first_name).to_sentence %> + Login by resetting your password or requesting a magic sign in link. + SMS + ).src + end + + # + # + # @param [Integer] user_id + def perform(user) + user.reset_magic_sign_in_token! + I18n.with_locale(user.locale) do + if user.email? + SendGridEmail.new( + to: user.name_with_email, + subject: user.invited_at.blank? ? "✨ Welcome to Greenlight! / Bienvenido a Greenlight" : '✨ REMINDER: Welcome to Greenlight! / Bienvenido a Greenlight', + html: eval(html_template), # rubocop:disable Security/Eval + text: eval(sms_template), # rubocop:disable Security/Eval + ).run + end + if user.mobile_number? + PlivoSMS.new( + to: user.mobile_number, + from: Greenlight::PHONE_NUMBER, + message: eval(sms_template) # rubocop:disable Security/Eval + ).run + end + end + + if user.invited_at.blank? + user.invited_at = Time.zone.now + user.save! + end + end +end diff --git a/client/src/api/index.ts b/client/src/api/index.ts index 4b1b08c..c36b7c3 100644 --- a/client/src/api/index.ts +++ b/client/src/api/index.ts @@ -1,9 +1,7 @@ import axios, { AxiosResponse } from 'axios' import { getGlobal, setGlobal } from 'reactn' -import { - assertArray, assertNotArray, assertNotNull, assertNotUndefined, transformForAPI, -} from 'src/helpers/util' +import { assertArray, assertNotArray, assertNotNull, assertNotUndefined, transformForAPI } from 'src/helpers/util' // FIXME: This shouldn't be assigned here. It should go in a provider import Honeybadger from 'honeybadger-js' @@ -182,7 +180,7 @@ export async function checkLocationRegistrationCode(locationId: string, registra return result.data.result } -export async function registerUser(locationId: string, user: RegisteringUser & { password: string}) { +export async function registerUser(locationId: string, user: RegisteringUser & { password: string }) { const userWithoutBlanks = transformForAPI(user, { removeBlanks: true }) await v1.post(`/locations/${locationId}/register`, userWithoutBlanks) const currentUser = await getCurrentUser() @@ -232,7 +230,7 @@ export async function completeWelcomeUser(user: User): Promise { return entity } -export async function createUserAndSignIn(user: Partial & { password: string}): Promise { +export async function createUserAndSignIn(user: Partial & { password: string }): Promise { const signInResponse = await v1.post('/users/create-and-sign-in', user) if (env.isCordova()) { localStorage.setItem('token', signInResponse.data.token) @@ -413,3 +411,15 @@ export function usePagedResources( ): responseInterface, any> { return useSWR([path, page, filter], (path, page, filter) => getPagedResources(path, page, filter)) } + +export async function inviteAnotherParent(params: { + firstName: string + lastName: string + emailOrMobile: string + children: Array +}): Promise { + const response = await v1.post>(`/parent/invite`, transformForAPI(params)) + const entity = transformRecordResponse(response.data) + assertNotArray(entity) + return entity +} diff --git a/client/src/config/routes.ts b/client/src/config/routes.ts index 7fc5463..a1c042f 100644 --- a/client/src/config/routes.ts +++ b/client/src/config/routes.ts @@ -59,6 +59,9 @@ import LocationLookupRegistrationCodePage from 'src/pages/locations/LocationLook import LocationCheckRegistrationCodePage from 'src/pages/locations/LocationCheckRegistrationCodePage' import MentalHealthResourcesPage from 'src/pages/resources/MentalHealthResourcesPage' import AdminEditGreenlightPassPage from 'src/pages/admin/AdminEditGreenlightPassPage' +import ParentEditPage from 'src/pages/admin/ParentEditPage' +import ParentNewPage from 'src/pages/admin/ParentNewPage' +import InviteOtherParentPage from 'src/pages/users/InviteOtherParentPage' const beforeEnter = { // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-unused-vars @@ -360,6 +363,26 @@ const userRoutes = { }, } +const parentRoutes = { + inviteOtherParent: { + path: '/users/:userId/invite-parent', + component: InviteOtherParentPage, + beforeEnter: beforeEnter.requireSignIn, + }, + + // edit Parent + newParentPath: { + path: '/users/:userId/parents/new', + component: ParentNewPage, + beforeEnter: beforeEnter.requireSignIn, + }, + editParentPath: { + path: '/users/:userId/parents/:parentId', + component: ParentEditPage, + beforeEnter: beforeEnter.requireSignIn, + }, +} + const routeMap = { rootPath: { path: '/', @@ -374,6 +397,7 @@ const routeMap = { ...locationRoutes, ...surveyRoutes, ...userRoutes, + ...parentRoutes, dashboardPath: { path: '/dashboard', component: DashboardPage, diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx index d1b10cd..588cfc2 100644 --- a/client/src/pages/DashboardPage.tsx +++ b/client/src/pages/DashboardPage.tsx @@ -10,6 +10,7 @@ import { NavTitle, NavRight, Icon, + Block, } from 'framework7-react' import { esExclaim, greeting } from 'src/helpers/util' import If from 'src/components/If' @@ -22,7 +23,7 @@ import { User } from 'src/models' import ReleaseCard from 'src/components/ReleaseCard' import { F7Props } from 'src/types' import Redirect from 'src/components/Redirect' -import { tr } from 'src/components/Tr' +import Tr, { tr } from 'src/components/Tr' import UserJDenticon from '../components/UserJDenticon' function UserList({ users }: { users: User[] }) { @@ -85,7 +86,8 @@ export default function DashboardPage(props: F7Props): JSX.Element { - {esExclaim()}{greeting()}, {currentUser.firstName}! + {esExclaim()} + {greeting()}, {currentUser.firstName}! @@ -160,47 +162,62 @@ export default function DashboardPage(props: F7Props): JSX.Element { {/* User is a worker and has children */} - {currentUser.hasLocationThatRequiresSurvey() && currentUser.isParent() - && Your Family} + {currentUser.hasLocationThatRequiresSurvey() && currentUser.isParent() && ( + Your Family + )} {/* User is only a parent */} - {!currentUser.hasLocationThatRequiresSurvey() && currentUser.isParent() && Your Children} + {!currentUser.hasLocationThatRequiresSurvey() && currentUser.isParent() && ( + Your Children + )} {/* User is not a parent */} {!currentUser.isParent() && Your Status} + + + + + + + {currentUser.isParent() && ( + + )} + Resources For You - { - currentUser.isAdminSomewhere() - && ( - - - - - { - currentUser.adminLocations().map((location) => ( - - )) - } - - - - ) - } + {currentUser.isAdminSomewhere() && ( + + + + + {currentUser.adminLocations().map((location) => ( + + ))} + + + + )} @@ -209,8 +226,10 @@ export default function DashboardPage(props: F7Props): JSX.Element { link={paths.chwRequestPath} title={tr({ en: 'Connect to Services', es: 'Conectarse a los servicios' })} footer={tr({ - en: 'Send a request to a community health worker for help with healthcare, housing, legal services, COVID-19 supplies and more.', - es: 'Envíe una solicitud a un trabajador de salud de la comunidad para que le ayude con la atención médica, la vivienda, los servicios legales, los suministros de COVID-19 y más.', + en: + 'Send a request to a community health worker for help with healthcare, housing, legal services, COVID-19 supplies and more.', + es: + 'Envíe una solicitud a un trabajador de salud de la comunidad para que le ayude con la atención médica, la vivienda, los servicios legales, los suministros de COVID-19 y más.', })} > @@ -226,15 +245,11 @@ export default function DashboardPage(props: F7Props): JSX.Element { @@ -250,23 +265,24 @@ export default function DashboardPage(props: F7Props): JSX.Element { {!currentUser.isInBrevard__HACK() && ( )} - { - currentUser.isInBrevard__HACK() && ( - - - - ) - } + {currentUser.isInBrevard__HACK() && ( + + + + )} {/* https://ncchildcare.ncdhhs.gov/Portals/0/documents/pdf/P/Parent_and_Families_School_Age_Child_Care.pdf?ver=2020-08-26-122445-963 */} @@ -315,7 +334,6 @@ export default function DashboardPage(props: F7Props): JSX.Element { > */} - ) diff --git a/client/src/pages/admin/AdminUserPage.tsx b/client/src/pages/admin/AdminUserPage.tsx index 7979e73..86a49c0 100644 --- a/client/src/pages/admin/AdminUserPage.tsx +++ b/client/src/pages/admin/AdminUserPage.tsx @@ -1,12 +1,8 @@ import React from 'react' -import { - Page, Block, Navbar, List, ListItem, BlockTitle, -} from 'framework7-react' +import { Page, Block, Navbar, List, ListItem, BlockTitle, AccordionContent } from 'framework7-react' import { F7Props } from 'src/types' -import { - assertNotNull, assertNotUndefined, copyTextToClipboard, formatPhone, -} from 'src/helpers/util' +import { assertNotNull, assertNotUndefined, copyTextToClipboard, formatPhone } from 'src/helpers/util' import { User } from 'src/models' import { getUserParents } from 'src/api' @@ -15,6 +11,7 @@ import LoadingUserContent from 'src/components/LoadingUserContent' import LoadingLocationContent from 'src/components/LoadingLocationContent' import LoadingBundleContent from 'src/components/LoadingBundleContent' import FakeF7ListItem from 'src/components/FakeF7ListItem' +import Tr, { tr } from 'src/components/Tr' export default function AdminUserPage(props: F7Props): JSX.Element { const { locationId, userId } = props.f7route.params @@ -48,30 +45,23 @@ export default function AdminUserPage(props: F7Props): JSX.Element {

- - Actions - + Actions - { - user.hasNotSubmittedOwnSurvey() ? ( - - ) : ( - - - {/* + ) : ( + + + {/* */} - - ) - } + + )} {/* */} - { - !locationAccount.isStudent() && ( + {!locationAccount.isStudent() && ( - ) - } + )} - - Contact - + Contact {user.mobileNumber && ( @@ -119,40 +108,37 @@ export default function AdminUserPage(props: F7Props): JSX.Element { footer={user.email} /> )} - {locationAccount.isStudent() && ( - - - showAsPage - action={() => getUserParents(userId)} - - content={(state) => { - const parents = state.bundle || [] - return parents.map((parent) => ( - <> - {parent.mobileNumber && ( - - )} - {parent.email && ( - - )} - - )) - }} - /> - - )} + + + + + + + + showAsPage + action={() => getUserParents(userId)} + content={(state) => { + const parents = state.bundle || [] + return ( + + {parents.map((parent) => ( + + ))} + + + ) + }} + /> + ) }} diff --git a/client/src/pages/admin/ParentEditPage.tsx b/client/src/pages/admin/ParentEditPage.tsx new file mode 100644 index 0000000..5a3daf8 --- /dev/null +++ b/client/src/pages/admin/ParentEditPage.tsx @@ -0,0 +1,66 @@ +import { t, Trans } from '@lingui/macro' +import { Block, BlockTitle, Button, f7, List, ListInput, ListItem, Navbar, Page } from 'framework7-react' +import React, { useGlobal, useRef } from 'reactn' +import { store } from 'src/api' +import SubmitHandler from 'src/helpers/SubmitHandler' +import { User } from 'src/models' +import { dynamicPaths, paths } from 'src/config/routes' +import { assertNotNull, formatPhone } from 'src/helpers/util' +import { F7Props, FunctionComponent } from '../../types' +import { tr } from 'src/components/Tr' + +const ParentEditPage: FunctionComponent = ({ f7route, f7router }) => { + const [currentUser] = useGlobal('currentUser') + assertNotNull(currentUser) + const submissionHandler = new SubmitHandler(f7) + + const { userId, parentId } = f7route.params + + const user = store.findEntity(`user-${userId}`) + const parent = store.findEntity(`user-${parentId}`) + + if (!user || !parent) { + f7router.navigate(paths.notFoundPath) + return <> + } + + return ( + + + + + Contact + + + {parent.mobileNumber && ( + + )} + {parent.email && ( + + )} + + + + + Actions + + + + + + + + ) +} + +export default ParentEditPage diff --git a/client/src/pages/admin/ParentNewPage.tsx b/client/src/pages/admin/ParentNewPage.tsx new file mode 100644 index 0000000..e4dda99 --- /dev/null +++ b/client/src/pages/admin/ParentNewPage.tsx @@ -0,0 +1,102 @@ +import { t, Trans } from '@lingui/macro' +import { FormikProvider, useFormik } from 'formik' +import { Block, BlockTitle, Button, f7, List, ListInput, ListItem, Navbar, Page } from 'framework7-react' +import React, { useGlobal, useRef } from 'reactn' +import { store } from 'src/api' +import SubmitHandler from 'src/helpers/SubmitHandler' +import { User } from 'src/models' +import { paths } from 'src/config/routes' +import { assertNotNull, formatPhone } from 'src/helpers/util' +import * as Yup from 'yup' +import { F7Props, FunctionComponent } from '../../types' +import EmailOrPhoneListInput from 'src/components/EmailOrPhoneListInput' +import FakeF7ListItem from 'src/components/FakeF7ListItem' +import { tr } from 'src/components/Tr' + +interface EditUserInput { + firstName: string + lastName: string + emailOrMobile: string +} + +const schema = Yup.object().shape({ + firstName: Yup.string().required(), + lastName: Yup.string().required(), + zipCode: Yup.string().matches(/^\d{5}$/, { + excludeEmptyString: true, + message: t({ id: 'EditUserPage.invalid_zip_code', message: 'Zip code should be 5 digits' }), + }), +}) + +const ParentNewPage: FunctionComponent = ({ f7route, f7router }) => { + const [currentUser] = useGlobal('currentUser') + assertNotNull(currentUser) + const submissionHandler = new SubmitHandler(f7) + const emailOrMobileRef = useRef(null) + + const { userId } = f7route.params + + const user = store.findEntity(`user-${userId}`) + + if (!user) { + f7router.navigate(paths.notFoundPath) + return <> + } + + const formik = useFormik({ + validationSchema: schema, + validateOnChange: true, + initialValues: { + firstName: '', + lastName: '', + emailOrMobile: '', + }, + onSubmit: (values) => { + console.log('onSubmit', values) + }, + }) + + return ( + + + + + + + + { + formik.setFieldValue('emailOrMobile', e.target.value) + }} + /> + + + + + + + + ) +} + +export default ParentNewPage diff --git a/client/src/pages/register-user/RegisterChildrenPage.tsx b/client/src/pages/register-user/RegisterChildrenPage.tsx index 8569a69..b1f879f 100644 --- a/client/src/pages/register-user/RegisterChildrenPage.tsx +++ b/client/src/pages/register-user/RegisterChildrenPage.tsx @@ -146,7 +146,6 @@ function ChildrenList({ link="#" key={index} title={`${child.firstName} ${child.lastName}`} - after="Edit" onClick={(e) => { e.preventDefault() setSelectedUser(index) diff --git a/client/src/pages/sessions/SignInPage.tsx b/client/src/pages/sessions/SignInPage.tsx index d29d87e..ad50205 100644 --- a/client/src/pages/sessions/SignInPage.tsx +++ b/client/src/pages/sessions/SignInPage.tsx @@ -1,17 +1,6 @@ import React, { setGlobal, useState } from 'reactn' -import { - Page, - List, - ListInput, - Navbar, - Link, - Block, - Button, - BlockFooter, - ListItem, - f7, -} from 'framework7-react' +import { Page, List, ListInput, Navbar, Link, Block, Button, BlockFooter, ListItem, f7 } from 'framework7-react' import EmailOrPhoneListInput from 'src/components/EmailOrPhoneListInput' import './SessionsPage.css' @@ -43,13 +32,15 @@ export default function SignInPage(props: F7Props): JSX.Element { return passwordValid && emailOrMobileValid } - async function submit() { + async function submit(event: any) { + if (event) { + event.preventDefault() + } + if (!validate()) { return } - f7.dialog.preloader( - t({ id: 'SignInPage.signing_you_in', message: 'Signing you in...' }), - ) + f7.dialog.preloader(t({ id: 'SignInPage.signing_you_in', message: 'Signing you in...' })) try { await createSession(state.emailOrMobile, state.password, state.rememberMe) @@ -84,9 +75,7 @@ export default function SignInPage(props: F7Props): JSX.Element { return ( - + with Magic ✨ @@ -97,7 +86,7 @@ export default function SignInPage(props: F7Props): JSX.Element { Greenlight - submit()}> + { setState({ ...state, rememberMe: e.target.checked as boolean }) }} /> - Forgot your password? - {' '} - Request a reset or - {' '} + Forgot your password? Request a reset or{' '} a magic sign in link. - ¿Olvidó su contraseña? Solicitar - {' '} - un restablecimiento de contraseña or - {' '} + ¿Olvidó su contraseña? Solicitar{' '} + un restablecimiento de contraseña or{' '} un enlace de inicio de sesión mágico. diff --git a/client/src/pages/users/InviteOtherParentPage.tsx b/client/src/pages/users/InviteOtherParentPage.tsx new file mode 100644 index 0000000..9c7a12a --- /dev/null +++ b/client/src/pages/users/InviteOtherParentPage.tsx @@ -0,0 +1,195 @@ +import { useRef, useCallback } from 'react' +import { FormikProvider, useFormik, yupToFormErrors } from 'formik' +import { Block, BlockTitle, Button, f7, List, ListItem, Navbar, Page } from 'framework7-react' +import { getEmailOrMobileTaken, inviteAnotherParent, store } from 'src/api' +import EmailOrPhoneListInput from 'src/components/EmailOrPhoneListInput' +import FormikInput from 'src/components/FormikInput' +import LoadingUserContent from 'src/components/LoadingUserContent' +import Tr, { tr } from 'src/components/Tr' +import { assertNotNull, assertNotUndefined } from 'src/helpers/util' +import { F7Props } from 'src/types' +import * as Yup from 'yup' +import { values } from 'lodash' +import SubmitHandler from 'src/helpers/SubmitHandler' + +export default function InviteOtherParentPage(props: F7Props) { + const { userId } = props.f7route.params + assertNotUndefined(userId) + + const emailOrMobileRef = useRef(null) + const submitHandler = new SubmitHandler(f7) + + const handleSubmit = useCallback((values: UserForm) => { + submitHandler.submit(() => { + return inviteAnotherParent(values).then(() => { + f7.dialog.alert( + tr({ + en: `Thank you! An account has been created for ${values.firstName}. They can access their account by resetting their password or requesting a magic sign-in link. They should receive an email with these instructions shortly too.`, + es: `¡Gracias! Se ha creado una cuenta para ${values.firstName}. Pueden acceder a su cuenta restableciendo su contraseña o solicitando un enlace mágico de inicio de sesión. También deberían recibir un correo electrónico con estas instrucciones en breve.`, + reviewTrans: true, + }), + ) + }) + }) + }, []) + + const formik = useFormik({ + validationSchema: Yup.object().shape({ + firstName: Yup.string() + .required() + .label(tr({ en: 'First Name', es: 'Primero' })), + lastName: Yup.string() + .required() + .label(tr({ en: 'Last Name', es: 'Apellido' })), + emailOrMobile: Yup.string().required(), + }), + initialValues: new UserForm(), + onSubmit: async (values) => { + if (!emailOrMobileRef.current?.validate(values.emailOrMobile)) { + return + } + + let emailOrMobileTaken: boolean = false + + await submitHandler.submit(async () => { + emailOrMobileTaken = await getEmailOrMobileTaken(values.emailOrMobile) + }) + + if (emailOrMobileTaken) { + let message: string + if (values.emailOrMobile.includes('@')) { + message = tr({ + en: + 'The email you provided is already in use. By continuing, you will allow the person with this account to access your children', + es: + 'El correo electrónico que proporcionó ya está en uso. Al continuar, permitirá que la persona con esta cuenta acceda a sus hijos.', + reviewTrans: true, + }) + } else { + message = tr({ + en: + 'The mobile number you provided is already in use. By continuing, you will allow the person with this account to access your children', + es: + 'El número de móvil que proporcionó ya está en uso. Al continuar, permitirá que la persona con esta cuenta acceda a sus hijos.', + reviewTrans: true, + }) + } + + f7.dialog.confirm( + message, + tr({ en: 'Account already exists', es: 'la cuenta ya existe', reviewTrans: true }), + () => { + handleSubmit(values) + }, + () => {}, + ) + } else { + handleSubmit(values) + } + }, + }) + + return ( + + + { + const { user } = state + assertNotNull(user) + + return ( + + + + + + + { + e.preventDefault() + formik.submitForm() + }} + > + + + + { + formik.setFieldValue('emailOrMobile', e.target.value) + }} + /> + + + + + + {user.children.map((child, index) => ( + { + e.preventDefault() + + if (formik.values.children.includes(child.id)) { + formik.setFieldValue( + 'children', + formik.values.children.filter((id) => id !== child.id), + ) + } else { + formik.setFieldValue('children', [...formik.values.children, child.id]) + } + }} + /> + ))} + + + + + + + ) + }} + /> + + ) +} + +class UserForm { + firstName: string = '' + + lastName: string = '' + + emailOrMobile: string = '' + + children: Array = [] +} diff --git a/client/src/pages/users/SettingsPage.tsx b/client/src/pages/users/SettingsPage.tsx index 63872bb..ed2fce8 100644 --- a/client/src/pages/users/SettingsPage.tsx +++ b/client/src/pages/users/SettingsPage.tsx @@ -1,13 +1,12 @@ import React, { useGlobal } from 'reactn' -import { - Page, Navbar, List, ListItem, AccordionContent, -} from 'framework7-react' +import { Page, Navbar, List, ListItem, AccordionContent } from 'framework7-react' import { toggleLocale, signOut } from 'src/helpers/global' import { t } from '@lingui/macro' import { assertNotNull } from 'src/helpers/util' import { dynamicPaths, paths } from 'src/config/routes' import NavbarHomeLink from 'src/components/NavbarHomeLink' +import { tr } from 'src/components/Tr' export default function SettingsPage() { const [currentUser] = useGlobal('currentUser') @@ -15,10 +14,7 @@ export default function SettingsPage() { return ( - + @@ -28,10 +24,7 @@ export default function SettingsPage() { title={t({ id: 'SettingsPage.profile', message: 'Profile' })} /> {currentUser.locations__HACK().length > 0 && ( - + {currentUser.locations__HACK().map((location) => ( @@ -46,10 +39,7 @@ export default function SettingsPage() { )} {currentUser.hasChildren() && ( - + {currentUser.sortedChildren().map((child) => ( @@ -76,31 +66,18 @@ export default function SettingsPage() { title={t({ id: 'Common.toggle_locale', message: 'En Español' })} /> - + - + - + - signOut()} - title={t({ id: 'Common.sign_out', message: 'Sign Out' })} - /> + signOut()} title={t({ id: 'Common.sign_out', message: 'Sign Out' })} /> )