Skip to content

Commit a88f2c0

Browse files
authored
Merge pull request #168 from GTBitsOfGood/nathan/162-unsubscribe-emails
Unsubscribe emails
2 parents dd4f47c + 988993f commit a88f2c0

File tree

13 files changed

+185
-17
lines changed

13 files changed

+185
-17
lines changed

backend/server/mongodb/actions/User.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export async function createUser(
2626
email: string,
2727
firebaseUid: string,
2828
roles: Array<Role>,
29+
unsubscribeEmail: boolean,
2930
birthday?: Date,
3031
firstName?: string,
3132
lastName?: string,
@@ -42,6 +43,7 @@ export async function createUser(
4243
email: email,
4344
firebaseUid: firebaseUid,
4445
roles: roles,
46+
unsubscribeEmail: unsubscribeEmail,
4547
birthday: birthday,
4648
firstName: firstName,
4749
lastName: lastName,
@@ -66,7 +68,8 @@ export async function updateUser(
6668
address?: string,
6769
annualPetVisitDay?: Date,
6870
profileImage?: string,
69-
nextPrescriptionReminder?: Date
71+
nextPrescriptionReminder?: Date,
72+
unsubscribeEmail?: boolean,
7073
) {
7174
await dbConnect();
7275
const user = UserModel.findByIdAndUpdate(userId, {
@@ -79,6 +82,7 @@ export async function updateUser(
7982
annualPetVisitDay: annualPetVisitDay,
8083
profileImage: profileImage,
8184
nextPrescriptionReminder: nextPrescriptionReminder,
85+
unsubscribeEmail: unsubscribeEmail,
8286
});
8387
return user;
8488
}

backend/server/mongodb/models/User.ts

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ const UserSchema = new mongoose.Schema<User>({
6464
required: true,
6565
default: false,
6666
},
67+
unsubscribeEmail: {
68+
type: Boolean,
69+
required: true,
70+
default: false,
71+
}
6772
});
6873

6974
const UserModel =

backend/server/utils/seed.ts

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ async function generateUsers(): Promise<User[]> {
7676
email,
7777
firebaseUser.uid,
7878
roles,
79+
false,
7980
birthday,
8081
firstName,
8182
lastName,

backend/src/pages/api/user/user.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export default APIWrapper({
4747
const profileImage: string = req.body.profileImage as string;
4848
const address: string = req.body.address as string;
4949
const annualPetVisitDay: Date = req.body.annualPetVisitDay as Date;
50+
const unsubscribeEmail: boolean = req.body.unsubscribeEmail as boolean;
5051

5152
const dbUser = await findUserByFirebaseUid(firebaseUid);
5253
if (dbUser) {
@@ -71,6 +72,7 @@ export default APIWrapper({
7172
email,
7273
firebaseUid,
7374
roles,
75+
unsubscribeEmail,
7476
birthday,
7577
firstName,
7678
lastName,
@@ -106,7 +108,7 @@ export default APIWrapper({
106108
let nextPrescriptionReminder: Date = req.body
107109
.nextPrescriptionReminder as Date;
108110
const userCreation: boolean = req.body.userCreation as boolean;
109-
111+
const unsubscribeEmail: boolean = req.body.unsubscribeEmail as boolean;
110112
const user = await getUser(accessToken);
111113

112114
if (!user) {
@@ -131,7 +133,8 @@ export default APIWrapper({
131133
address,
132134
annualPetVisitDay,
133135
profileImage,
134-
nextPrescriptionReminder
136+
nextPrescriptionReminder,
137+
unsubscribeEmail
135138
);
136139

137140
if (!updatedUser?.modifiedPaths) {

backend/src/utils/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface User {
2929
profileImage?: string;
3030
verifiedByAdmin: boolean;
3131
emailVerified: boolean;
32+
unsubscribeEmail: boolean;
3233
}
3334

3435
export interface ServiceAnimal {

mobile/App.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import SignUpScreen from "./screens/Onboarding/SignUpScreen";
77
import HandlerInformationScreen from "./screens/Onboarding/HandlerInformationScreen";
88
import AnimalInformationScreen from "./screens/Onboarding/AnimalInformationScreen";
99
import UserDashboardScreen from "./screens/User/UserDashboardScreen";
10+
import UserSettings from "./screens/User/UserSettings";
1011
import AdminDashboardScreen from "./screens/Admin/AdminDashboardScreen";
1112
import LandingScreen from "./screens/Onboarding/LandingScreen";
1213
import DevelopmentScreen from "./screens/Development/DevelopmentScreen";
@@ -122,6 +123,10 @@ export default function App() {
122123
name={Screens.USER_DASHBOARD_SCREEN}
123124
component={UserDashboardScreen}
124125
/>
126+
<Stack.Screen
127+
name={Screens.USER_SETTINGS}
128+
component={UserSettings}
129+
/>
125130
<Stack.Screen
126131
name={Screens.ADMIN_DASHBOARD_SCREEN}
127132
component={AdminDashboardScreen}

mobile/actions/User.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ export const userGetUserInfo = async () => {
1515
export const userCreateUser = async (
1616
email: string,
1717
firebaseUid: string,
18+
unsubscribeEmail: boolean,
1819
address?: string,
1920
birthday?: Date,
2021
firstName?: string,
2122
lastName?: string,
2223
handlerType?: HandlerType,
2324
profileImage?: string,
24-
annualPetVisitDay?: Date
25+
annualPetVisitDay?: Date,
2526
) => {
2627
return internalRequest<User>({
2728
url: userUserUrl,
@@ -36,6 +37,7 @@ export const userCreateUser = async (
3637
profileImage,
3738
address,
3839
annualPetVisitDay,
40+
unsubscribeEmail,
3941
},
4042
});
4143
};
@@ -50,9 +52,10 @@ export const userUpdateUser = async (
5052
annualPetVisitDay?: Date,
5153
profileImage?: string,
5254
nextPrescriptionReminder?: Date,
53-
userCreation?: boolean
55+
userCreation?: boolean,
56+
unsubscribeEmail?: boolean,
5457
) => {
55-
return internalRequest<User>({
58+
const user = internalRequest<User>({
5659
url: userUserUrl,
5760
method: HttpMethod.PATCH,
5861
authRequired: true,
@@ -67,6 +70,8 @@ export const userUpdateUser = async (
6770
profileImage,
6871
nextPrescriptionReminder,
6972
userCreation,
73+
unsubscribeEmail,
7074
},
7175
});
76+
return user;
7277
};

mobile/components/DashboardHeader.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ const DashboardHeader = ({
2121
<Text style={styles.profileName}>
2222
{userInfo?.firstName} {userInfo?.lastName}
2323
</Text>
24+
<IconButton icon={
25+
<MaterialCommunityIcons name="account-settings" size={26} color="#3F3BED" />
26+
}
27+
callbackFunction={() => {
28+
navigationProp.navigate(Screens.USER_SETTINGS);
29+
}}
30+
/>
2431
<IconButton
2532
callbackFunction={async () => {
2633
await auth.signOut().then().catch();

mobile/screens/Onboarding/SignUpScreen.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export default function SignUpScreen(props: any) {
6767
const createdUser = await ErrorWrapper({
6868
functionToExecute: userCreateUser,
6969
errorHandler: setErrorMessage,
70-
parameters: [email, firebaseUid, "N/A"],
70+
parameters: [email, firebaseUid, false, "N/A"],
7171
});
7272

7373
await ErrorWrapper({

mobile/screens/User/UserDashboardScreen.tsx

+10-8
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export default function UserDashboardScreen(props: any) {
111111
);
112112
return;
113113
}
114-
114+
115115
if (today >= new Date(userInformation?.nextPrescriptionReminder)) {
116116
addToModalQueue(
117117
"prescription",
@@ -194,6 +194,7 @@ export default function UserDashboardScreen(props: any) {
194194
animalInformation?.profileImage
195195
);
196196

197+
// Change content for deploy preview
197198
const emailData: { [key: string]: string } = {
198199
firstName: userInformation.firstName,
199200
lastName: userInformation.lastName,
@@ -204,13 +205,14 @@ export default function UserDashboardScreen(props: any) {
204205
day: "numeric",
205206
}) as string,
206207
};
207-
208-
await userSendEmail(
209-
userInformation.email,
210-
"Rabies Shot Reminder for Your Service Animal",
211-
"shot-reminder",
212-
emailData
213-
);
208+
if (userInformation.unsubscribeEmail === false) {
209+
await userSendEmail(
210+
userInformation.email,
211+
"Rabies Shot Reminder for Your Service Animal",
212+
"shot-reminder",
213+
emailData
214+
);
215+
}
214216

215217
if (newAnimal) {
216218
setAnimalInfo(newAnimal);

mobile/screens/User/UserSettings.tsx

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import React from 'react';
2+
import { View, Text } from 'react-native';
3+
import { StyleSheet, Switch } from 'react-native';
4+
import { useEffect, useState } from 'react';
5+
import { User } from "../../utils/types";
6+
import { userGetUserInfo } from "../../actions/User";
7+
import { ErrorWrapper, endOfExecutionHandler } from "../../utils/error";
8+
import BaseOverlay from "../../components/Overlays/BaseOverlay";
9+
import ErrorBox from "../../components/ErrorBox";
10+
import IconButton from "../../components/IconButton";
11+
import { MaterialCommunityIcons } from '@expo/vector-icons';
12+
import { Screens } from '../../utils/types';
13+
import { userUpdateUser } from '../../actions/User';
14+
15+
export default function UserSettings(props: any) {
16+
const [error, setError] = useState<string>("");
17+
const [userInfo, setUserInfo] = useState<User | null>(null);
18+
19+
useEffect(() => {
20+
async function getUserInfo() {
21+
try {
22+
const user: User = (await ErrorWrapper({
23+
functionToExecute: userGetUserInfo,
24+
errorHandler: setError,
25+
})) as User;
26+
if (user.unsubscribeEmail === undefined) {
27+
user.unsubscribeEmail = false;
28+
await userUpdateUser(
29+
user.roles,
30+
user.birthday,
31+
user.firstName,
32+
user.lastName,
33+
user.handlerType,
34+
user.address,
35+
user.annualPetVisitDay,
36+
user.profileImage,
37+
user.nextPrescriptionReminder,
38+
false,
39+
user.unsubscribeEmail,
40+
);
41+
}
42+
setUserInfo(user);
43+
} catch (error) {
44+
endOfExecutionHandler(error as Error);
45+
}
46+
}
47+
getUserInfo().catch();
48+
}, []);
49+
50+
51+
const toggleSwitch = async () => {
52+
// Change userInfo on this page and change in database
53+
const updatedUserInfo = {
54+
...userInfo,
55+
unsubscribeEmail: !userInfo?.unsubscribeEmail
56+
}
57+
setUserInfo(updatedUserInfo as User);
58+
await userUpdateUser(
59+
updatedUserInfo.roles,
60+
updatedUserInfo.birthday,
61+
updatedUserInfo.firstName,
62+
updatedUserInfo.lastName,
63+
updatedUserInfo.handlerType,
64+
updatedUserInfo.address,
65+
updatedUserInfo.annualPetVisitDay,
66+
updatedUserInfo.profileImage,
67+
updatedUserInfo.nextPrescriptionReminder,
68+
false,
69+
updatedUserInfo.unsubscribeEmail,
70+
);
71+
}
72+
73+
return (
74+
<BaseOverlay
75+
header={
76+
<View style={styles.header}>
77+
<Text>{/* This is just to align the text */}</Text>
78+
<Text style={styles.headerText}>User Settings</Text>
79+
<IconButton
80+
callbackFunction={() => {
81+
props.navigation.navigate(Screens.USER_DASHBOARD_SCREEN);
82+
}}
83+
icon={
84+
<MaterialCommunityIcons name="home" size={26} color="#3F3BED" />
85+
}
86+
/>
87+
</View>
88+
}
89+
90+
body={
91+
<View style={styles.container}>
92+
<View style={styles.setting}>
93+
<Text style={styles.settingText}>Unsubscribe from Emails?</Text>
94+
<Switch
95+
trackColor={{ false: "#EBEBE4", true: "#32CD32" }}
96+
thumbColor={"#f4f3f4"}
97+
ios_backgroundColor="#3e3e3e"
98+
onValueChange={toggleSwitch}
99+
value={userInfo?.unsubscribeEmail}
100+
/>
101+
</View>
102+
</View>
103+
}
104+
105+
footer={<ErrorBox errorMessage={error} />}
106+
/>
107+
);
108+
}
109+
110+
const styles = StyleSheet.create({
111+
header: {
112+
flexDirection: 'row',
113+
justifyContent: 'space-between',
114+
alignItems: 'center',
115+
marginBottom: 30,
116+
},
117+
headerText: {
118+
fontSize: 24,
119+
fontWeight: 'bold',
120+
},
121+
container: {
122+
flex: 1,
123+
justifyContent: 'center',
124+
alignItems: 'center',
125+
},
126+
setting: {
127+
flexDirection: 'row',
128+
justifyContent: 'space-between',
129+
alignItems: 'center',
130+
marginBottom: 20,
131+
},
132+
settingText: {
133+
paddingRight: 15,
134+
}
135+
});

mobile/utils/requests.ts

-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export async function internalRequest<T>({
1919
: "",
2020
},
2121
};
22-
2322
if (body) {
2423
requestInfo.body = JSON.stringify(body);
2524
}
@@ -34,7 +33,6 @@ export async function internalRequest<T>({
3433
}
3534
const response = await fetch(url, requestInfo);
3635
const responseBody = (await response.json()) as InternalResponseData<T>;
37-
3836
if (!responseBody) {
3937
throw new Error("Unable to connect to API.");
4038
} else if (!responseBody.success) {

mobile/utils/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export interface User {
5151
profileImage?: string;
5252
verifiedByAdmin: boolean;
5353
emailVerified: boolean;
54+
unsubscribeEmail: boolean;
5455
}
5556

5657
export interface ServiceAnimal {
@@ -159,6 +160,7 @@ export enum Screens {
159160
ANALYTICS_DASHBOARD_SCREEN = "Analytics Dashboard",
160161
ANALYTICS_USER_LIST = "Analytics User List",
161162
PARTNERSHIPS_SCREEN = "Partnerships",
163+
USER_SETTINGS = "User Settings",
162164
}
163165

164166
export enum UserFilter {

0 commit comments

Comments
 (0)