Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/controllers/api_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ class APIController < ActionController::API
include MailController
include SmokeTestsController
include UtilController
include ParentController
end
46 changes: 46 additions & 0 deletions app/controllers/parent_controller.rb
Original file line number Diff line number Diff line change
@@ -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
87 changes: 87 additions & 0 deletions app/workers/invite_parent_worker.rb
Original file line number Diff line number Diff line change
@@ -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
<h2>Welcome to Greenlight / Bienvenido a Greenlight</h2>
<p>
Hi <%= user.first_name %>,
</p>
<p>
Every day you will receive notifications to submit symptom surveys on behalf of your children:
</p>

<p>
Login by <a href="<%= user.password_reset_url %>">resetting your password</a> or requesting a <a href="<%= user.magic_sign_in_url %>">magic sign in</a> link.
</p>
<p>
You can select which days to receive notifications or disable notifications by visiting the notification page in settings.
</p>
<p>Thanks for your help, and let us know if you have questions.</p>
<p>Stay safe out there,<br />
The Greenlight Team
</p>
<hr />

<p>
Hola <%= user.first_name %>,
</p>
<p>
<%= 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:
</p>

<p>
Inicie sesión <a href="<%= user.password_reset_url %>"> restableciendo su contraseña </a> o solicitando un enlace de <a href="<%= user.magic_sign_in_url %>"> inicio de sesión mágico </a>.
</p>
<p>
Puede seleccionar qué días recibir notificaciones o deshabilitar las notificaciones visitando la página de notificaciones en la configuración.
</p>
<p>Gracias por su ayuda y avísenos si tiene preguntas.</p>
<p>Mantente a salvo,<br />
El Equipo Greenlight
</p>
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 <a href="<%= user.password_reset_url %>">resetting your password</a> or requesting a <a href="<%= user.magic_sign_in_url %>">magic sign in</a> link.
SMS
).src
end

# <Description>
#
# @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
20 changes: 15 additions & 5 deletions client/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -232,7 +230,7 @@ export async function completeWelcomeUser(user: User): Promise<User> {
return entity
}

export async function createUserAndSignIn(user: Partial<RegisteringUser> & { password: string}): Promise<void> {
export async function createUserAndSignIn(user: Partial<RegisteringUser> & { password: string }): Promise<void> {
const signInResponse = await v1.post('/users/create-and-sign-in', user)
if (env.isCordova()) {
localStorage.setItem('token', signInResponse.data.token)
Expand Down Expand Up @@ -413,3 +411,15 @@ export function usePagedResources<T extends Model>(
): responseInterface<PagedResource<T>, any> {
return useSWR([path, page, filter], (path, page, filter) => getPagedResources<T>(path, page, filter))
}

export async function inviteAnotherParent(params: {
firstName: string
lastName: string
emailOrMobile: string
children: Array<string>
}): Promise<User> {
const response = await v1.post<RecordResponse<User>>(`/parent/invite`, transformForAPI(params))
const entity = transformRecordResponse<User>(response.data)
assertNotArray(entity)
return entity
}
24 changes: 24 additions & 0 deletions client/src/config/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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: '/',
Expand All @@ -374,6 +397,7 @@ const routeMap = {
...locationRoutes,
...surveyRoutes,
...userRoutes,
...parentRoutes,
dashboardPath: {
path: '/dashboard',
component: DashboardPage,
Expand Down
Loading