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:
+
+ 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:
+
- 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 (
+
+
+