From e71f6dc01b98c261120e4c86d200800f84ee2779 Mon Sep 17 00:00:00 2001 From: Min Ji Choi Date: Tue, 7 Jan 2025 15:26:40 -0800 Subject: [PATCH 1/5] DBC22-3244: HTML emails for route notifications and verification email --- src/backend/apps/event/tasks.py | 36 ++-- .../apps/event/templates/email/advisory.html | 148 +++++++++++++++ .../templates/email/email-verification.html | 83 +++++++++ .../templates/email/environment-canada.html | 148 +++++++++++++++ .../event/templates/email/event_updated.html | 173 ++++++++++++++++-- .../event/templates/email/major-delay.html | 172 +++++++++++++++++ .../event/templates/email/road-condition.html | 172 +++++++++++++++++ 7 files changed, 904 insertions(+), 28 deletions(-) create mode 100644 src/backend/apps/event/templates/email/advisory.html create mode 100644 src/backend/apps/event/templates/email/email-verification.html create mode 100644 src/backend/apps/event/templates/email/environment-canada.html create mode 100644 src/backend/apps/event/templates/email/major-delay.html create mode 100644 src/backend/apps/event/templates/email/road-condition.html diff --git a/src/backend/apps/event/tasks.py b/src/backend/apps/event/tasks.py index 03d133acc..2f0c2989f 100644 --- a/src/backend/apps/event/tasks.py +++ b/src/backend/apps/event/tasks.py @@ -180,19 +180,23 @@ def send_event_notifications(updated_event_ids): updated_interecting_events = Event.objects.filter(id__in=updated_event_ids, location__intersects=saved_route.route) if updated_interecting_events.count() > 0: - context = { - 'events': updated_interecting_events, - 'route': saved_route, - } - - text = render_to_string('email/event_updated.txt', context) - html = render_to_string('email/event_updated.html', context) - - msg = EmailMultiAlternatives( - f'DriveBC route update: {saved_route.label}', - text, - env("DRIVEBC_FEEDBACK_EMAIL_DEFAULT"), - [saved_route.user.email] - ) - msg.attach_alternative(html, 'text/html') - msg.send() + for event in updated_interecting_events: + + print(f"Event: {event}") + + context = { + 'event': event, + 'route': saved_route, + } + + text = render_to_string('email/event_updated.txt', context) + html = render_to_string('email/event_updated.html', context) + + msg = EmailMultiAlternatives( + f'DriveBC route update: {saved_route.label}', + text, + env("DRIVEBC_FEEDBACK_EMAIL_DEFAULT"), + [saved_route.user.email] + ) + msg.attach_alternative(html, 'text/html') + msg.send() diff --git a/src/backend/apps/event/templates/email/advisory.html b/src/backend/apps/event/templates/email/advisory.html new file mode 100644 index 000000000..cb955e901 --- /dev/null +++ b/src/backend/apps/event/templates/email/advisory.html @@ -0,0 +1,148 @@ + + + + + + + DriveBC Saved Route Update + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + DriveBC Logo + +
+ + + + + + + +
+ Hello {name}, +
+ This notification is sent based on your + + + route notification settings + + + on DriveBC for the following route: +
+
+ + + + + + + + + + +
+ {route name} +
+ {route from and to} +
+ + + + + + + + + + + + + + + + + + + +
+ Advisory + Advisory +
+ {Highway name} +
+ {direction} +
+ + + + + + + + + +
+ Description + + {description} +
+ Last updated + + {last updated timestamp} +
+
+ +
+ View on DriveBC +
+
+
+
+
+ + + + + + + + + + +
+ Your DriveBC team. +
+ Based on your settings, you are being notified for new and updated advisories, closures, major delays, and road conditions from 9:00 am to 10:30 am, every Monday, Tuesday, Wednesday, Thursday, Friday. +
+ Add notifications@drivebc.ca to your address book to ensure that you receive these notifications to your inbox. If you do not want to receive emails related to this route, you can turn off or modify these notifications at any time. +
+
+
+ + diff --git a/src/backend/apps/event/templates/email/email-verification.html b/src/backend/apps/event/templates/email/email-verification.html new file mode 100644 index 000000000..8471a1349 --- /dev/null +++ b/src/backend/apps/event/templates/email/email-verification.html @@ -0,0 +1,83 @@ + + + + + + + Email Verification + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + DriveBC Logo + +
+ + + + + + + +
+ Almost there! +
+ To finish creating your account, we need you to verify your email. +Once verified, you’ll be able to save cameras and routes that matter to you, and setup personalized notifications for delays that could affect a trip. +
+ + + + +
+ +
+ Verify email +
+
+
+ + + + +
+ If you didn’t initiate this request, please disregard this email. +
+
+ + + + + + + +
+ Your DriveBC team. +
+ Add notifications@drivebc.ca to your address book to ensure that you receive these notifications to your inbox. +
+
+
+ + diff --git a/src/backend/apps/event/templates/email/environment-canada.html b/src/backend/apps/event/templates/email/environment-canada.html new file mode 100644 index 000000000..90ca773fa --- /dev/null +++ b/src/backend/apps/event/templates/email/environment-canada.html @@ -0,0 +1,148 @@ + + + + + + + DriveBC Saved Route Update + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + DriveBC Logo + +
+ + + + + + + +
+ Hello {name}, +
+ This notification is sent based on your + + + route notification settings + + + on DriveBC for the following route: +
+
+ + + + + + + + + + +
+ {route name} +
+ {route from and to} +
+ + + + + + + + + + + + + + + + + + + +
+ Environment Canada weather alert + Environment Canada weather alert +
+ {Highway name} +
+ {direction} +
+ + + + + + + + + +
+ Description + + {description} +
+ Last updated + + {last updated timestamp} +
+
+ +
+ View on DriveBC +
+
+
+
+
+ + + + + + + + + + +
+ Your DriveBC team. +
+ Based on your settings, you are being notified for new and updated advisories, closures, major delays, and road conditions from 9:00 am to 10:30 am, every Monday, Tuesday, Wednesday, Thursday, Friday. +
+ Add notifications@drivebc.ca to your address book to ensure that you receive these notifications to your inbox. If you do not want to receive emails related to this route, you can turn off or modify these notifications at any time. +
+
+
+ + diff --git a/src/backend/apps/event/templates/email/event_updated.html b/src/backend/apps/event/templates/email/event_updated.html index 689813d4a..8a04b3b75 100644 --- a/src/backend/apps/event/templates/email/event_updated.html +++ b/src/backend/apps/event/templates/email/event_updated.html @@ -1,14 +1,163 @@ -

- Hello,\n - This notification is sent based on your route notification settings on DriveBC for the following route:\n + + + + + +
+ + + + + + + + + + + + + + + + + + - {% for event in events %} - {{ event.event_type }}\n - {{ event.description }}\n - Last updated: {{ event.last_updated }}\n - {% endfor %} -

+ +
+ + DriveBC Logo + +
+ + + + + + + +
+ Hello {name}, +
+ This notification is sent based on your + + + route notification settings + + + on DriveBC for the following route: +
+
+ + + + + + + + + + +
+ {{ route.label }} +
+ {{ route.start }} to {{ route.end }} +
+ + + + + + + + + + + + + + + + + + - {{ route.label }}\n - from: {{ route.start }}\n - to: {{ route.end }}\n +
+ Major current event + {{ event.event_type }} +
+ {{ event.highway_segment_names }} +
+ {{ event.direction }} +
+ + + + + + + + + + + + + + + + + + + + + +
+ Location + + {{ event.location }} +
+ Closest landmark + + {{ event.closest_landmark }} +
+ Description + + {{ event.location_description }} +
+ Last updated + + {{ event.last_updated }} +
+ Next update + + {{ event.next_update }} +
+
+ +
+ View on DriveBC +
+
+
+
+
+ + + + + + + + + + +
+ Your DriveBC team. +
+ Based on your settings, you are being notified for new and updated advisories, closures, major delays, and road conditions from 9:00 am to 10:30 am, every Monday, Tuesday, Wednesday, Thursday, Friday. +
+ Add notifications@drivebc.ca to your address book to ensure that you receive these notifications to your inbox. If you do not want to receive emails related to this route, you can turn off or modify these notifications at any time. +
+
+
+ \ No newline at end of file diff --git a/src/backend/apps/event/templates/email/major-delay.html b/src/backend/apps/event/templates/email/major-delay.html new file mode 100644 index 000000000..0ccac6e60 --- /dev/null +++ b/src/backend/apps/event/templates/email/major-delay.html @@ -0,0 +1,172 @@ + + + + + + + DriveBC Saved Route Update + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + DriveBC Logo + +
+ + + + + + + +
+ Hello {name}, +
+ This notification is sent based on your + + + route notification settings + + + on DriveBC for the following route: +
+
+ + + + + + + + + + +
+ {route name} +
+ {route from and to} +
+ + + + + + + + + + + + + + + + + + + +
+ Major current event + Major current event +
+ {Highway name} +
+ {direction} +
+ + + + + + + + + + + + + + + + + + + + + +
+ Location + + {location} +
+ Closest landmark + + {landmark} +
+ Description + + {description} +
+ Last updated + + {last updated timestamp} +
+ Next update + + {next update timestamp} +
+
+ +
+ View on DriveBC +
+
+
+
+
+ + + + + + + + + + +
+ Your DriveBC team. +
+ Based on your settings, you are being notified for new and updated advisories, closures, major delays, and road conditions from 9:00 am to 10:30 am, every Monday, Tuesday, Wednesday, Thursday, Friday. +
+ Add notifications@drivebc.ca to your address book to ensure that you receive these notifications to your inbox. If you do not want to receive emails related to this route, you can turn off or modify these notifications at any time. +
+
+
+ + diff --git a/src/backend/apps/event/templates/email/road-condition.html b/src/backend/apps/event/templates/email/road-condition.html new file mode 100644 index 000000000..8fd76bf0c --- /dev/null +++ b/src/backend/apps/event/templates/email/road-condition.html @@ -0,0 +1,172 @@ + + + + + + + DriveBC Saved Route Update + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + DriveBC Logo + +
+ + + + + + + +
+ Hello {name}, +
+ This notification is sent based on your + + + route notification settings + + + on DriveBC for the following route: +
+
+ + + + + + + + + + +
+ {route name} +
+ {route from and to} +
+ + + + + + + + + + + + + + + + + + + +
+ Road condition + Road condition +
+ {Highway name} +
+ {direction} +
+ + + + + + + + + + + + + + + + + + + + + +
+ Location + + {location} +
+ Closest landmark + + {landmark} +
+ Description + + {description} +
+ Last updated + + {last updated timestamp} +
+ Next update + + {next update timestamp} +
+
+ +
+ View on DriveBC +
+
+
+
+
+ + + + + + + + + + +
+ Your DriveBC team. +
+ Based on your settings, you are being notified for new and updated advisories, closures, major delays, and road conditions from 9:00 am to 10:30 am, every Monday, Tuesday, Wednesday, Thursday, Friday. +
+ Add notifications@drivebc.ca to your address book to ensure that you receive these notifications to your inbox. If you do not want to receive emails related to this route, you can turn off or modify these notifications at any time. +
+
+
+ + From 002dd46eabd003b20651be5414eb50678bc2eb02 Mon Sep 17 00:00:00 2001 From: ray Date: Tue, 7 Jan 2025 17:03:10 -0800 Subject: [PATCH 2/5] DBC22-3244: fixed buffer filter and email template --- src/backend/apps/event/tasks.py | 7 +++++-- .../apps/event/templates/email/event_updated.html | 10 ++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/backend/apps/event/tasks.py b/src/backend/apps/event/tasks.py index 2f0c2989f..a355543ef 100644 --- a/src/backend/apps/event/tasks.py +++ b/src/backend/apps/event/tasks.py @@ -177,16 +177,19 @@ def populate_all_event_data(): def send_event_notifications(updated_event_ids): for saved_route in SavedRoutes.objects.filter(user__verified=True, notification=True): - updated_interecting_events = Event.objects.filter(id__in=updated_event_ids, location__intersects=saved_route.route) + # Apply a 150m buffer to the route geometry + buffered_route = saved_route.route.buffer(150) + updated_interecting_events = Event.objects.filter(id__in=updated_event_ids, location__intersects=buffered_route) if updated_interecting_events.count() > 0: for event in updated_interecting_events: - + print(f"Event: {event}") context = { 'event': event, 'route': saved_route, + 'user': saved_route.user } text = render_to_string('email/event_updated.txt', context) diff --git a/src/backend/apps/event/templates/email/event_updated.html b/src/backend/apps/event/templates/email/event_updated.html index 8a04b3b75..6388e29e2 100644 --- a/src/backend/apps/event/templates/email/event_updated.html +++ b/src/backend/apps/event/templates/email/event_updated.html @@ -18,12 +18,12 @@ @@ -154,10 +154,8 @@
- Hello {name}, + Hello {% if user.first_name %}{{ user.first_name }}{% else %}{{ user.email }}{% endif %},
- This notification is sent based on your + This notification is sent based on your route notification settings @@ -143,7 +143,7 @@
- Based on your settings, you are being notified for new and updated advisories, closures, major delays, and road conditions from 9:00 am to 10:30 am, every Monday, Tuesday, Wednesday, Thursday, Friday. + Based on your settings, you are being notified for new and updated advisories, closures, major delays, and road conditions from 9:00 am to 10:30 am, every Monday, Tuesday, Wednesday, Thursday, Friday.
- - - \ No newline at end of file + From b4491766b413da987cc367ca2adc8e1bffd475a5 Mon Sep 17 00:00:00 2001 From: ray Date: Tue, 7 Jan 2025 18:09:41 -0800 Subject: [PATCH 3/5] DBC22-3244: working image attachment --- src/backend/apps/event/tasks.py | 12 ++++++++++++ .../event/templates/email/event_updated.html | 2 +- src/backend/static/images/drivebclogo.png | Bin 0 -> 7167 bytes 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/backend/static/images/drivebclogo.png diff --git a/src/backend/apps/event/tasks.py b/src/backend/apps/event/tasks.py index a355543ef..c17829d9e 100644 --- a/src/backend/apps/event/tasks.py +++ b/src/backend/apps/event/tasks.py @@ -1,5 +1,7 @@ import datetime import logging +import os +from email.mime.image import MIMEImage from pathlib import Path from zoneinfo import ZoneInfo @@ -201,5 +203,15 @@ def send_event_notifications(updated_event_ids): env("DRIVEBC_FEEDBACK_EMAIL_DEFAULT"), [saved_route.user.email] ) + + # Attach image with Content-ID + image_path = os.path.join(BASE_DIR, 'src', 'backend', 'static', 'images', 'drivebclogo.png') + with open(image_path, 'rb') as image_file: + img = MIMEImage(image_file.read(), _subtype="svg+xml") + img.add_header('Content-ID', '') + img.add_header('X-Attachment-Id', 'drivebclogo.png') + img.add_header('Content-Disposition', 'inline', filename='drivebclogo.png') + msg.attach(img) + msg.attach_alternative(html, 'text/html') msg.send() diff --git a/src/backend/apps/event/templates/email/event_updated.html b/src/backend/apps/event/templates/email/event_updated.html index 6388e29e2..c1f3c8fb5 100644 --- a/src/backend/apps/event/templates/email/event_updated.html +++ b/src/backend/apps/event/templates/email/event_updated.html @@ -8,7 +8,7 @@ - DriveBC Logo + DriveBC Logo diff --git a/src/backend/static/images/drivebclogo.png b/src/backend/static/images/drivebclogo.png new file mode 100644 index 0000000000000000000000000000000000000000..3f8440b7680fc47a69f185cc30a2d27be5bef469 GIT binary patch literal 7167 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91rJw@<1ONa40RR91GXMYp0LA2}#Q*>sX-PyuRCodHT?c$rRn|ZEO-rwY z0HFm0REi*-RjEswsJMV@U33*zbXhe)P~6>EP)hQtuq&2TP%NviC}0{*st80y5ySve zVrc1YGMUW0`~B~IGm}ZeqyZ{8C%>7w@Ah}z|DAU4dtr0|iD!2UCgYepxZNDfYsz~u zTS*(P^Vbo#=@~QHIgt&+S29D`ac&6Su8{vS)_39e7jR0Ac5TZHdsQD>G_BD}-ogvc z0sZ+NA@>9F?a*z?-{6$e6*-w`DvIHM$W z$Cz^R@3DbV*yF6rDP!F%ofA9(bq2fq>FvLrOX59KpyQm0 z@!b>WscLCHFWNSVn>zWE0G=U~DCjD!b3+(_=)k`YWx4=jrbwci60O|Wu67av%%%>+ z1Flqf^@)CJ?rU4suWtLC?@LA}UivL?;G96ozZlg~-9G$BwP?%Zkc1lQtfv5~_5fut zkscCIko62vU|%h*_|%rE7A;QzRFFQHSjaF9(Dq8G_dP^v4deM)!_~r-CvE#@-=$Ub zeqYKQXf-HncHS7Hn)43xikz;JK|(SLD*Z~TVrFVbtj#!Nz@)UBiTt|BVyUAA2%VA> z_9Yn|LNItBae$HO1Bj{tWI@aT$?8v3m1ksm%a_}~nYLW!d%rGO4zvoCwp}+()!B+y zQOIEES_cW0ZFERH^C2Q35q03(CNL03f8Rpnxlgp+4tu%1d@? zd4atFYDi5%#&(RVPyR{$c1DJl;r+f8InZKI+Oj9##%x8eaNjN_GK9dJzE0zd09MFg zq#+rLcifqHRP`cSs)b z{7jS^h(+D09oqyLdqTD2*RLz`09p_W>;s!KpQz1P029CypQ0_AT^WG-0+a&4?Iyxw z?wk$xD}e2E`j0?_&si@ZUCQhL_TFi=ggW?Kqy3P&a!lCt_F(dkd6wB;qc z;CaJ42jE$o@lrx^YXQ-zm9>E8oT&xtU*+FFfPuYqsc@iKptNQGF@cxtxJI6|0Aax8 z+WD7e6VHQW6HX2!CF}hngrlB#?lNM%o&vzu5UQ8 zm1n&6rFKOwiSK5CQn8o3Oujv!i$V4ZO%+GpB-Jfd>?58701CAoIs(81u!K!0ylnxl z(yxg@Eh~dRLBJdmwbMPQ%HU=9B^D6_)qeviSEgd|2Of9D55)SsMEuY^34GS?bpSA& zxWL@8r5GFl7JVX*s*cI&h#NwwIWvgw`U*K@$;6MnqB%ywj0lJ4kJTcsS`}_S`WiaB!quTSanW)>lYWg% z-@faHLjXs2GDg6z^MSN$N4$I=?8(nH&lpRIEczkhhf^e2*?<+0m=Ib}vqB}ehOny$ zk{6pczMW2#p9K~E2#yVqpwFT^sB96265%1C%GDZe9u<34C7}Nmtp97U7g|T&T|%wn z+xczc;{qD6`PN?;k_=kLv(X93mDr!d1^x z;$U7ewGZ>B0AHi0Dl(tj5fXya#3II1&QC=YhW)`|g)hT1Uqy$EcTnuhMmlmDAtjKn zQBUvOTA+ppM|T|=b5E|!Y~hzzeYcQd7pQupj?U=!#zk$(f!#*6fE4qnw%KXzo}XM( zdT{AR((A_2wAjW0DMCRnton)`e{&mkjtHdx&c2=obcdJjfrL$36*QWjT)vf3S7wu< zFq$;H2Sta^qWP2<@w^HoE!E5G8S9w@EmB-b0t(?2CW{9zi*6@rLfNX6M?B@h2Q!Ty}ov@EUHAC2bXT3 zQRvGBNN1xaeovQF_H3exxA<4i0gsZhqRIyMw&k?rz)2b~^KE)7F_umho9T%a+bORQ z0rw^d5)M*v9j)GZnD!nmaM_AYG;2^?S#1kykm=31s5syely=TTTYDz?WmOdS&yQQx zeD%&BTh-3l$Wm>?I;fW}W)65%-wc75i}A#)HoM_soVRz*wH)vWN~2y^TwCqpN%@)# zh$DIFV&;HnP#TmI7vqWfoAk9_*2Q=N-Z_&;o6^tMu*JzAgm`#lh@UOg-&Acf>a7Na z*)33$a4Jlviz`i~mDL`>?d;>{`V9^6J@Rvne!A5i zcpLsIIB>y01E7XhRo2{kro^0(TTnS*|M7yzqH@axk*M9;1y_&m*XfP_%pCuKxOrO3 zS?@AqYdLp*+g&hFo^6`5Uf(>vpseba6UCJy4xcEFK76vw$O&%{Y6W#{6IgRqbejX0 zMTNfj;(epB8~EJG3(9ln;rZyHRe(4j1B*W9zPTZ)%%V&zE~^@qUuN!8SY91bQc>k! zVX^5|tdr{%#)JG!)*yeAIXu88KP13-B-G#MOB}D7KlR!#&(}Ph{dhs?8vATGnGD#XPDZlRVPaSzV#*aTA&X@i56Y6z(M-J2!2Sbq zT{!abP!2a!<}N(Sh(2N$^+UhiaZ^O1ESJ(|rBT9y(Wu)I&q0_e{n3>u=QM79XEN!o zBULy#_RW+r+f$&x{oEU*o29CSQE;)&+#uxENnNp2^IZ?-|VO z0Oquv^2^uLm&tg>0v>^3cZ2z3l--FfQV;W@BkIAGRGBTB(`iFM0)L>V?`ei znof8up>Zlt4~Xzn4EJ!xCZN9{JQ*$4lEp^YV5}St5MyCm<6bashsA7N(6l$q-8NIE z&&D$fbevROZ`$#E73Ed}6l^hN>|85BtI}|aABtVLpbd`~;=39bY9?jQ+JViGz3@tw zk0>MYeY6<~P+5`xUt%gA_)&t$Qoa_G!?U?H+aw_477POzZ%3VvDZK(}m1&zb(`Ii1 zgXWV>K|8QU(E2&YF-|``OLk>z0V(@w&X*X=YMX@%X2*Q-un-%Bjh!>8C~pX*C-$TC zr2b^L+>A>IKCocY(RsF$Cub!N8X4Vv716C@UJw?dm_k^=7T6{_@7vw-`-?SQuBRce0T>2i$<1 z9I{m483@q6WGi(^1u!7h1S#qUNU>Cz7GIo~09d8+I4!BxUeoS6ty=ml@g^%Fu{kcB zg^pRZ`b<9%<2v4$m8?=4&D9#99!H-XT zKqBP&QnAGRfjujH34Yr`Kpqjp^qxXiyy$83z7J?@ub}X40Onv5`Qo~oCx~))I`aVq zgawCx3JhEWDR`$ouJ}Foa`&_$rf8ixE9^QG=X;oEO z^X6wGou`ptIUkW_jOi|m3Kq7ti|kpUe*qxJ;6^(1QCZOou7V~K)i!!dWwfv93n?C~ zQQR-MZ6kmZPJVtNt})V*z9cg}2vt3c#yk_{%0HKcu)i{vR8v}F3hc9MFy;buS1Vyv z`!3jzU${P;w@f9T%iLPicHm6Ft&99{mWRYe|a=zc8?&Z`Dro-Xo z%9GkW)&ESH0ynprj*2dskUdkT5AAlZ~ufcz!o|HSEP_5K{RT^+TzKPnPEwv+!EDGGSK#RIDX8-(KD#J3bu2<~xN!k+25lz)_CXK1O`93=Ph=A@nNYwHRxJIddH34%GlEoxSr(#ev9 zMdJV-y!;4Wc6ws-RdGV98C@W@$^S=bNt2}-H|Wyf1c~=*=K@GuanXeq5KiS1m@f~L zxDoZrDwKudwwr;lnt0n6!IKBD3_H*k;WmChY)&m4CkycvpOTKg+EQi3DB6@<;fOQi zl_LO^h&v0CHwD{|)GW-!N1-k`88dDnY3f0W@

KAvKA*`cDA^CgX(u3rY1#3;Xj{ zZ<+(zV!yV0Rx@Q-G4b(XW~DxO~NNm z7h8CFjm_E%cGTzalyAeoj?1pe2#ay6ubEiLn0aMrpj&>klX|R%1Y_l8E!pfzl$E&E zt>tn6^IxuGaaY>)cz2(IxFxkAJA&L|xiKyouk!VeMLoP7RXqw7@tJHRS9FZ=s0*oJ z-zrsyC`ed@cln9C&$a_5utIwMP_o&!qc7LpCfkd8Di5b>>jrqMmtkD78e~N*)QWrW z1XWdT0kdtmkw)Oc;LfE=DpfOR`|NVrUVO!+4uDko+I9R(Cj<|%B&JP+A9YPfhx5|t zuq!VkSqnM2G8~~N5wv%wD)R^A7x+AQ8b@)@Mo`A%+LE1*|4rci7nBZXP*7x#iTDN| z;bg_9T#p+-Xi1oF+z)^k>Am206@D8KRB|0@=aW#L0||Kj6T`ySqL;CFNyJ+x)=+eZ z31o&uJt+b*g8cmyp+QRJK?}YVGU`Nxes8@1u+{~)J}DFNi-wYFc#5y{|%fL3XmjNgh^_$}ub}81f>kw9dICDFB zH5=gZp_utsxvD89#JKoIFxveNEr-*iO6h1(7r+-K`kHNpYQ?NXFHexgap5j!}DbHYK<4u&{5b040NiDqm z^G&Xh5O=8KCI}bqG`m#T(n7V@q~kr}Eg*Yl=xhweMGUZq$99aw z&YhsMmUB8ItU@x_G$W#ZB^4@;JO5#hTOn57T}Q%3J)ML29UDbs=SAY*ktAq4ja)Tq z6Dr$HrM8c7w#zW?yE0T#TUwiv0ASq^OT!Kn+6^;KlOVM<&ON0%FBCRiVDe3N7_5ID z^KJ`XE{D2Y(PaG=S70y6CJOx>vzbI^D&MKo1soE`@gq|E)hYCF08uAP7=@gzMs<4F zl{j_SrLgc7v8bXEHd!pjnoFiQQgm!ZX=YHEqo~oJnv9T9uwN%4#>Eg5Q~rcyL(zF% z5^A8vwhwi}VH|gtJ&nGnHTy!;>8P;->}(Rm zu!0e@5Gzg|NO-~VDgQ>jKcmLKD1GO>4Qsk?+=kb{SsBZDT2goRI_c%LJzNK7iT z!B#od_9n{VX(Rp_+4F7EB)7A7m^&$n&_X?HGmsJj2?RfANS%BfBqsxJ3VTlGbN*Ox zY*~?89bQycG#L^Jb7Ii}OuxBmb#Sy>d4r@SJ-9n!pl`!sSffzd7tiDF{SF@7>oOqj ztUf9^fDFn4sO?<#Nc(MY22dw=yZj+J9{E{KG@le zd;{-;*PS7cMU0G#z%IyAjqnqjNb1qm@>slI8VJ?W_!j#zh1V}BS=>zB0k&=OP^^RN z#EOoCIEBdodRB6gT!tjt5AU;8_Kma4+Fy(g@l79!)q`X#L7!T z>Rj><#D5pS_BPcX177{L!3`&(ADn}ig7_dv|L6t+LR-4kfT8d)C_jtjN><@|ESKHX zz`?6ea*wGBjUj^R@FPNWM*zYV2>Yerrr8Xw(iP#T9MLR)6$K(jISt7GaSX*a-JTit zkTor&zcn-XYFlRbT~7=T6Ct6Y=xr(_l3l%Jq-tFZSlTW;DA zYuoMoZzUCVRqK0{_1GbKvBj_0Ow9B`3Ko#f@&@UQoiV3-adKD*5@U!P0IMTn+nW|x zo{9^y7ymix9eALzI0xL7_7A1kFZUnn{($)fV4MZ6kcm5I9BF%I_=AYU)w9LwRg6oS zgubw44SPk!Ryp~P&7s~H4*|dYF)!gBRA8>yRG+LOJP4!lvf3j`K@ftyzLH2gscTZok?_?IiFJ zz5*Of!ipK^OWk9a0MGwnn@K3=oLqm~coAqR$;*<|+}0|9=WM-+Q8iLAd|`002ovPDHLkV1f}a Bj;H_t literal 0 HcmV?d00001 From 779548f73714404bf0f3c0895ef0552eb4b35a63 Mon Sep 17 00:00:00 2001 From: ray Date: Wed, 8 Jan 2025 19:03:17 -0800 Subject: [PATCH 4/5] DBC22-2879: verification redirection and success message --- src/backend/apps/authentication/views.py | 13 +++- src/frontend/src/Components/data/user.js | 4 +- .../src/Components/routing/RouteDetails.js | 2 +- src/frontend/src/pages/AccountPage.js | 77 ++++++++++++++----- src/frontend/src/pages/AccountPage.scss | 25 ++++++ src/frontend/src/pages/SavedRoutesPage.js | 12 ++- src/frontend/src/pages/SavedRoutesPage.scss | 7 +- src/frontend/src/pages/VerifyEmailPage.js | 4 +- src/frontend/src/styles/variables.scss | 2 + 9 files changed, 119 insertions(+), 27 deletions(-) diff --git a/src/backend/apps/authentication/views.py b/src/backend/apps/authentication/views.py index d9f17ada0..539b53fa5 100644 --- a/src/backend/apps/authentication/views.py +++ b/src/backend/apps/authentication/views.py @@ -129,7 +129,12 @@ def get(self, request, uidb64, token): if user is not None and EmailVerificationTokenGenerator().check_token(user, token): user.verified = True user.save() - return HttpResponseRedirect(env("FRONTEND_BASE_URL") + 'account') # Redirect to the account page + + my_routes = request.GET.get('my_routes') + redirect_url = env("FRONTEND_BASE_URL") + 'my-routes?verified=true' if my_routes == 'True'\ + else env("FRONTEND_BASE_URL") + 'account?verified=true' + + return HttpResponseRedirect(redirect_url) # Redirect to the account page else: return Response({'error': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST) @@ -145,7 +150,11 @@ def post(self, request): uid = urlsafe_base64_encode(force_bytes(user.pk)) token = EmailVerificationTokenGenerator().make_token(user) - verification_url = request.build_absolute_uri(reverse('verify-email', kwargs={'uidb64': uid, 'token': token})) + + my_routes = request.data.get('my_routes', 'false') + verification_url = request.build_absolute_uri( + reverse('verify-email', kwargs={'uidb64': uid, 'token': token}) + f'?my_routes={my_routes}' + ) context = { 'email': request.user.email, diff --git a/src/frontend/src/Components/data/user.js b/src/frontend/src/Components/data/user.js index d626c7a20..96d13137f 100644 --- a/src/frontend/src/Components/data/user.js +++ b/src/frontend/src/Components/data/user.js @@ -1,6 +1,6 @@ import { getCookie } from "../../util"; -export const sendVerificationEmail = async () => { +export const sendVerificationEmail = async ({ my_routes = false } = {}) => { const url = `${window.API_HOST}/api/users/send-verification-email/`; try { @@ -10,7 +10,7 @@ export const sendVerificationEmail = async () => { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ }), + body: JSON.stringify({ my_routes }), credentials: 'include' }); diff --git a/src/frontend/src/Components/routing/RouteDetails.js b/src/frontend/src/Components/routing/RouteDetails.js index b480219dc..b24c7169e 100644 --- a/src/frontend/src/Components/routing/RouteDetails.js +++ b/src/frontend/src/Components/routing/RouteDetails.js @@ -336,7 +336,7 @@ export default function RouteDetails(props) { const toggleHandler = async (e) => { if (!authContext.verified) { e.preventDefault(); - navigate('/verify-email'); + navigate('/verify-email?my_route=true'); return; } diff --git a/src/frontend/src/pages/AccountPage.js b/src/frontend/src/pages/AccountPage.js index 79d6f309e..842772c30 100644 --- a/src/frontend/src/pages/AccountPage.js +++ b/src/frontend/src/pages/AccountPage.js @@ -1,6 +1,4 @@ -import React, { useContext, useEffect } from 'react'; - -import { useNavigate } from 'react-router-dom'; +import React, {useContext, useEffect, useRef, useState} from 'react'; // External imports import Container from 'react-bootstrap/Container'; @@ -14,16 +12,41 @@ import PageHeader from '../PageHeader'; import './AccountPage.scss'; export default function AccountPage() { - const { authContext } = useContext(AuthContext); + // Navigation + const params = new URLSearchParams(window.location.search); + + // Context + const { authContext, setAuthContext } = useContext(AuthContext); + + // Refs + const showedModal = useRef(false); - const navigate = useNavigate(); + // States + const [verified, setVerified] = useState(params.get('verified')); useEffect(() => { - if (authContext.loginStateKnown && !authContext.username) { - navigate('/'); + if (!authContext.loginStateKnown) { + return; + } + + // Show login modal once if user is not logged in + if (!authContext.username && !showedModal.current) { + toggleAuthModal("Sign In"); + showedModal.current = true; } }, [authContext]); + /* Handlers */ + const toggleAuthModal = (action) => { + setAuthContext((prior) => { + if (!prior.showingModal) { + return { ...prior, showingModal: true, action }; + } + return prior; + }) + }; + + /* Rendering */ return (

+ {authContext.loginStateKnown && authContext.username && authContext.verified && verified && +
+ You have successfully verified your email address. +
+ } + -
-

Email Address

-

{authContext.email}

- - {!authContext.verified && -

- This email address has not been verified. Email notifications for - saved routes will be disabled until verification - is complete. + {authContext.loginStateKnown && authContext.username && +

+

Email Address

+

{authContext.email}

+ + {!authContext.verified && +

+ This email address has not been verified. Email notifications for + saved routes will be disabled until verification + is complete. +

+ } +
+ } + + {authContext.loginStateKnown && !authContext.username && +
+

Login required

+ +

+ You must be toggleAuthModal("Sign In")}>logged in to view this page.

- } -
+
+ }
diff --git a/src/frontend/src/pages/AccountPage.scss b/src/frontend/src/pages/AccountPage.scss index 5b044b004..601ef3d8a 100644 --- a/src/frontend/src/pages/AccountPage.scss +++ b/src/frontend/src/pages/AccountPage.scss @@ -1,6 +1,31 @@ @import "../styles/variables.scss"; .account-page { + .verified { + background-color: $MyAccountSuccess; + color: $MyAccountSuccessText; + padding: 1rem 4rem; + font-size: 0.875rem; + + @media (max-width: 992px) { + padding: 1rem 0.75rem; + } + + .verify-link { + text-decoration: underline; + cursor: pointer; + + @media (min-width: 992px) { + display: inline; + margin-left: 1rem; + } + + @media (max-width: 992px) { + margin-top: 1rem; + } + } + } + .email-address { .email, .header { font-size: 1.1rem; diff --git a/src/frontend/src/pages/SavedRoutesPage.js b/src/frontend/src/pages/SavedRoutesPage.js index 8963c6784..50558904c 100644 --- a/src/frontend/src/pages/SavedRoutesPage.js +++ b/src/frontend/src/pages/SavedRoutesPage.js @@ -29,6 +29,7 @@ export default function SavedRoutesPage() { // Navigation const navigate = useNavigate(); + const params = new URLSearchParams(window.location.search); // Context const { authContext, setAuthContext } = useContext(AuthContext); @@ -44,6 +45,7 @@ export default function SavedRoutesPage() { // States const [routeLabel, setRouteLabel] = useState(); const [routeFavCams, setRouteFavCams] = useState(false); + const [verified, setVerified] = useState(params.get('verified')); // Effects useEffect(() => { @@ -85,14 +87,20 @@ export default function SavedRoutesPage() {
navigate('/verify-email')} - onKeyPress={() => navigate('/verify-email')}> + onClick={() => navigate('/verify-email?my_routes=true')} + onKeyPress={() => navigate('/verify-email?my_routes=true')}> Verify email address
} + {authContext.loginStateKnown && authContext.username && authContext.verified && verified && +
+ You have successfully verified your email address. +
+ } + {authContext.loginStateKnown && authContext.username && {!(favRoutes && favRoutes.length) && diff --git a/src/frontend/src/pages/SavedRoutesPage.scss b/src/frontend/src/pages/SavedRoutesPage.scss index c794c2d67..a11d7f525 100644 --- a/src/frontend/src/pages/SavedRoutesPage.scss +++ b/src/frontend/src/pages/SavedRoutesPage.scss @@ -3,7 +3,7 @@ .saved-routes-page { position: relative; - .not-verified { + .not-verified, .verified { background-color: $MyAccountWarning; color: $MyAccountWarningText; padding: 1rem 4rem; @@ -28,6 +28,11 @@ } } + .verified { + background-color: $MyAccountSuccess; + color: $MyAccountSuccessText; + } + .container { @media (min-width: 576px) { max-width: 1828px; diff --git a/src/frontend/src/pages/VerifyEmailPage.js b/src/frontend/src/pages/VerifyEmailPage.js index 4aad21527..43c5690ef 100644 --- a/src/frontend/src/pages/VerifyEmailPage.js +++ b/src/frontend/src/pages/VerifyEmailPage.js @@ -18,7 +18,9 @@ export default function VerifyEmailPage() { useEffect(() => { if (authContext.loginStateKnown && !authContext.verified) { - sendVerificationEmail(); + const params = new URLSearchParams(window.location.search); + const myRoutes = params.get('my_routes') === 'true'; + sendVerificationEmail({ my_routes: myRoutes }); } }, [authContext]); diff --git a/src/frontend/src/styles/variables.scss b/src/frontend/src/styles/variables.scss index fbd3db7c5..8c01a2fa3 100644 --- a/src/frontend/src/styles/variables.scss +++ b/src/frontend/src/styles/variables.scss @@ -103,5 +103,7 @@ $GreyOnBlue: #5C6B78; // My Account $MyAccountHeader: #323130; +$MyAccountSuccess: #E5F5E7; +$MyAccountSuccessText: #2E8540; $MyAccountWarning: #FEF8E8; $MyAccountWarningText: #584215; From 45fa6f8c225c59c7cbae2469a24a13d259c13703 Mon Sep 17 00:00:00 2001 From: ray Date: Wed, 8 Jan 2025 19:35:33 -0800 Subject: [PATCH 5/5] DBC22-2879: polishing --- .../templates/email/email_verification.html | 87 ++++++++++++++++++- src/backend/apps/authentication/views.py | 12 +++ src/backend/apps/event/tasks.py | 4 +- .../templates/email/email-verification.html | 83 ------------------ .../event/templates/email/event_updated.html | 10 +-- src/frontend/src/pages/AccountPage.js | 18 ++++ src/frontend/src/pages/AccountPage.scss | 4 + src/frontend/src/pages/SavedRoutesPage.js | 7 +- src/frontend/src/pages/SavedRoutesPage.scss | 6 +- 9 files changed, 127 insertions(+), 104 deletions(-) delete mode 100644 src/backend/apps/event/templates/email/email-verification.html diff --git a/src/backend/apps/authentication/templates/email/email_verification.html b/src/backend/apps/authentication/templates/email/email_verification.html index 9ef21e679..e0b2e539b 100644 --- a/src/backend/apps/authentication/templates/email/email_verification.html +++ b/src/backend/apps/authentication/templates/email/email_verification.html @@ -1,4 +1,83 @@ -

- Please click the link below to verify your email address: \n - {{verification_url}} -

+ + + + + + + Email Verification + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + DriveBC Logo + +
+ + + + + + + +
+ Almost there! +
+ To finish creating your account, we need you to verify your email. +Once verified, you’ll be able to save cameras and routes that matter to you, and setup personalized notifications for delays that could affect a trip. +
+ + + + +
+ +
+ Verify email +
+
+
+ + + + +
+ If you didn’t initiate this request, please disregard this email. +
+
+ + + + + + + +
+ Your DriveBC team. +
+ Add notifications@drivebc.ca to your address book to ensure that you receive these notifications to your inbox. +
+
+
+ + diff --git a/src/backend/apps/authentication/views.py b/src/backend/apps/authentication/views.py index 539b53fa5..e2b059073 100644 --- a/src/backend/apps/authentication/views.py +++ b/src/backend/apps/authentication/views.py @@ -1,3 +1,5 @@ +import os +from email.mime.image import MIMEImage from pathlib import Path import environ @@ -170,6 +172,16 @@ def post(self, request): env("DRIVEBC_FEEDBACK_EMAIL_DEFAULT"), [request.user.email] ) + + # Attach image with Content-ID + image_path = os.path.join(BASE_DIR, 'src', 'backend', 'static', 'images', 'drivebclogo.png') + with open(image_path, 'rb') as image_file: + img = MIMEImage(image_file.read(), _subtype="png") + img.add_header('Content-ID', '') + img.add_header('X-Attachment-Id', 'drivebclogo.png') + img.add_header('Content-Disposition', 'inline', filename='drivebclogo.png') + msg.attach(img) + msg.attach_alternative(html, 'text/html') msg.send() diff --git a/src/backend/apps/event/tasks.py b/src/backend/apps/event/tasks.py index c17829d9e..ae19f39e5 100644 --- a/src/backend/apps/event/tasks.py +++ b/src/backend/apps/event/tasks.py @@ -198,7 +198,7 @@ def send_event_notifications(updated_event_ids): html = render_to_string('email/event_updated.html', context) msg = EmailMultiAlternatives( - f'DriveBC route update: {saved_route.label}', + f'DriveBC route update: {saved_route.label}' if saved_route.label else 'DriveBC route update', text, env("DRIVEBC_FEEDBACK_EMAIL_DEFAULT"), [saved_route.user.email] @@ -207,7 +207,7 @@ def send_event_notifications(updated_event_ids): # Attach image with Content-ID image_path = os.path.join(BASE_DIR, 'src', 'backend', 'static', 'images', 'drivebclogo.png') with open(image_path, 'rb') as image_file: - img = MIMEImage(image_file.read(), _subtype="svg+xml") + img = MIMEImage(image_file.read(), _subtype="png") img.add_header('Content-ID', '') img.add_header('X-Attachment-Id', 'drivebclogo.png') img.add_header('Content-Disposition', 'inline', filename='drivebclogo.png') diff --git a/src/backend/apps/event/templates/email/email-verification.html b/src/backend/apps/event/templates/email/email-verification.html deleted file mode 100644 index 8471a1349..000000000 --- a/src/backend/apps/event/templates/email/email-verification.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - Email Verification - - - - - - -
- - - - - - - - - - - - - - - - -
- - DriveBC Logo - -
- - - - - - - -
- Almost there! -
- To finish creating your account, we need you to verify your email. -Once verified, you’ll be able to save cameras and routes that matter to you, and setup personalized notifications for delays that could affect a trip. -
- - - - -
- -
- Verify email -
-
-
- - - - -
- If you didn’t initiate this request, please disregard this email. -
-
- - - - - - - -
- Your DriveBC team. -
- Add notifications@drivebc.ca to your address book to ensure that you receive these notifications to your inbox. -
-
-
- - diff --git a/src/backend/apps/event/templates/email/event_updated.html b/src/backend/apps/event/templates/email/event_updated.html index c1f3c8fb5..a7713f8c5 100644 --- a/src/backend/apps/event/templates/email/event_updated.html +++ b/src/backend/apps/event/templates/email/event_updated.html @@ -55,7 +55,6 @@ @@ -64,11 +63,6 @@ {{ event.highway_segment_names }} - - -
- Major current event {{ event.event_type }}
- {{ event.direction }} -
@@ -77,7 +71,7 @@ Location @@ -93,7 +87,7 @@ Description diff --git a/src/frontend/src/pages/AccountPage.js b/src/frontend/src/pages/AccountPage.js index 842772c30..927f04a28 100644 --- a/src/frontend/src/pages/AccountPage.js +++ b/src/frontend/src/pages/AccountPage.js @@ -1,6 +1,10 @@ import React, {useContext, useEffect, useRef, useState} from 'react'; +// Navigation +import { useNavigate } from 'react-router-dom'; + // External imports +import Button from "react-bootstrap/Button"; import Container from 'react-bootstrap/Container'; // Internal imports @@ -12,7 +16,9 @@ import PageHeader from '../PageHeader'; import './AccountPage.scss'; export default function AccountPage() { + /* Setup */ // Navigation + const navigate = useNavigate(); const params = new URLSearchParams(window.location.search); // Context @@ -24,6 +30,7 @@ export default function AccountPage() { // States const [verified, setVerified] = useState(params.get('verified')); + // Effects useEffect(() => { if (!authContext.loginStateKnown) { return; @@ -71,6 +78,17 @@ export default function AccountPage() { This email address has not been verified. Email notifications for saved routes will be disabled until verification is complete. + +
+ +

} diff --git a/src/frontend/src/pages/AccountPage.scss b/src/frontend/src/pages/AccountPage.scss index 601ef3d8a..39a7338b6 100644 --- a/src/frontend/src/pages/AccountPage.scss +++ b/src/frontend/src/pages/AccountPage.scss @@ -42,6 +42,10 @@ color: $MyAccountWarningText; padding: 0.5rem 1rem; font-size: 0.875rem; + + .btn { + margin-top: 1rem; + } } } } diff --git a/src/frontend/src/pages/SavedRoutesPage.js b/src/frontend/src/pages/SavedRoutesPage.js index 50558904c..1e588bd29 100644 --- a/src/frontend/src/pages/SavedRoutesPage.js +++ b/src/frontend/src/pages/SavedRoutesPage.js @@ -11,6 +11,7 @@ import { memoize } from 'proxy-memoize'; // External imports import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faStar as faStarOutline, faXmark } from '@fortawesome/pro-regular-svg-icons'; +import Button from "react-bootstrap/Button"; // Internal imports import { AuthContext } from '../App'; @@ -84,14 +85,14 @@ export default function SavedRoutesPage() { {authContext.email} has not been verified. Email notifications for saved routes will be disabled until verification is complete. -
navigate('/verify-email?my_routes=true')} onKeyPress={() => navigate('/verify-email?my_routes=true')}> Verify email address -
+ } diff --git a/src/frontend/src/pages/SavedRoutesPage.scss b/src/frontend/src/pages/SavedRoutesPage.scss index a11d7f525..5efc20800 100644 --- a/src/frontend/src/pages/SavedRoutesPage.scss +++ b/src/frontend/src/pages/SavedRoutesPage.scss @@ -6,7 +6,7 @@ .not-verified, .verified { background-color: $MyAccountWarning; color: $MyAccountWarningText; - padding: 1rem 4rem; + padding: 0.5rem 4rem; font-size: 0.875rem; @media (max-width: 992px) { @@ -14,11 +14,9 @@ } .verify-link { - text-decoration: underline; - cursor: pointer; + margin-bottom: 0; @media (min-width: 992px) { - display: inline; margin-left: 1rem; }
- {{ event.location }} + {{ event.location_description }}
- {{ event.location_description }} + {{ event.description }}