@7365admin1/core 3.52.1 → 3.52.2-staging.235

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Fix the mailer transport defaults: an unset or unparseable port no longer resolves to `NaN`, and `MAILER_TRANSPORT_SECURE` now defaults to match the port instead of a fixed `false`. Same `Number(...) ?? n` fix applied to `PORT` and `REDIS_PORT`.
@@ -1,79 +1,166 @@
1
- <table style="height: 100%; width: 100%">
1
+ {{! Visitor invitation email.
2
+
3
+ Email-client rules this file follows -- please keep them when editing:
4
+ * layout is <table>-based; flexbox and grid do not work in Outlook
5
+ * every style is inline; <style> blocks are stripped by Gmail
6
+ * no external CSS or web fonts
7
+ * `isInviteEmail` gates the resident-invite rows. The legacy
8
+ `residentAppInvitePreRegister` sender renders this same file from a
9
+ different document shape and sets none of those keys -- Handlebars
10
+ prints a missing key as an empty string, so without the guard that
11
+ email would grow a column of blank cells.
12
+ }}
13
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color: #eef1f4; margin: 0; padding: 24px 12px;">
2
14
  <tr>
3
- <td style="vertical-align: middle; text-align: center">
4
- <div style="padding: 24px; justify-content: center;">
5
- <div style="background-color: #052439; elevation: 2;">
6
- <div style="display: flex; justify-content: center;">
7
- <img src="{{logo}}" style="width: 190px; height: 50px;" />
8
- </div>
9
- </div>
10
- <div style="margin-bottom: 16px; color: #052439; ">
11
- <p style="color: #052439; font-weight: 600; font-size: 32px; line-height: 30px; text-align: center; padding: 16px;">Hey
12
- {{name}},</p>
13
- <p style="text-align: center;">You are now ready to check in upon arrival on {{arrivalDate}}.</p>
14
- </div>
15
- <a href="{{qrLink}}" style="text-decoration: none"><div
16
- style="margin-top: 16px; padding: 5px; background-color: #4caf50; border-radius: 16px; text-align: center;"
17
- >
18
- <p style="color: white; font-weight: bold;">QR code Link (Click Here)</p>
19
- </div></a>
15
+ <td align="center" style="vertical-align: top;">
16
+
17
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" style="width: 100%; max-width: 600px; background-color: #ffffff; border-radius: 14px; overflow: hidden; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;">
20
18
 
21
- <div style="margin-top: 16px; text-align: center; color: #052439; ">
22
- <p>Please present the QR code sent to you at the pedestrian entrance for access.</p>
23
- </div>
19
+ <tr>
20
+ <td align="center" style="background-color: #052439; padding: 22px 24px;">
21
+ <img src="{{logo}}" alt="Seven365" width="190" height="50" style="width: 190px; height: 50px; border: 0; display: block; color: #ffffff; font-family: Helvetica, Arial, sans-serif; font-size: 15px; font-weight: 700;" />
22
+ </td>
23
+ </tr>
24
24
 
25
- <div style="margin-top: 16px; padding: 5px; background-color: #052439; border-radius: 16px; text-align: center;">
26
- <p style="color: white; font-weight: bold;">Visitor Information</p>
27
- </div>
25
+ <tr>
26
+ <td style="padding: 32px 28px 8px 28px; text-align: center;">
27
+ <p style="margin: 0; color: #052439; font-weight: 700; font-size: 27px; line-height: 34px;">Hey {{name}},</p>
28
+ <p style="margin: 10px 0 0 0; color: #5b6b78; font-size: 15px; line-height: 22px;">You are now ready to check in upon arrival on <span style="color: #052439; font-weight: 600;">{{arrivalDate}}</span>.</p>
29
+ </td>
30
+ </tr>
28
31
 
29
- <div style="margin-top: 16px; max-width:100%; margin-left: auto; margin-right: auto; margin-bottom: 50px">
30
- <table style="color: #052439; font-size: 16px; margin: 0 auto; border-collapse: collapse; width: 100%;">
31
- <tbody>
32
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
33
- <td style="text-align: left; font-weight: bold; padding: 8px;">Name</td>
34
- <td style="text-align: right; padding: 8px;">{{name}}</td>
32
+ <tr>
33
+ <td style="padding: 22px 28px 0 28px;">
34
+ <a href="{{qrLink}}" style="display: block; background-color: #35a745; color: #ffffff; font-size: 16px; font-weight: 700; text-decoration: none; text-align: center; padding: 15px 20px; border-radius: 10px;">View My QR Code ( Click Here )</a>
35
+ {{#if statusLabel}}
36
+ {{! Whether the QR is usable yet. Sits under the button so it is read
37
+ before the tap, not after. `statusColor` is inlined from the
38
+ context because Handlebars has no helpers registered here. }}
39
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%;">
40
+ <tr>
41
+ <td align="center" style="padding: 14px 0 0 0;">
42
+ <span style="color: #7b8a95; font-size: 12px; letter-spacing: 0.4px; text-transform: uppercase; font-weight: 700;">QR Status</span>
43
+ <span style="display: inline-block; background-color: {{statusColor}}; color: #ffffff; font-size: 13px; font-weight: 700; padding: 5px 14px; border-radius: 999px; margin-left: 8px;">{{statusLabel}}</span>
44
+ </td>
35
45
  </tr>
46
+ </table>
47
+ {{/if}}
48
+ <p style="margin: 12px 0 0 0; color: #7b8a95; font-size: 13px; line-height: 19px; text-align: center;">Please present the QR code at the pedestrian entrance for access.</p>
49
+ </td>
50
+ </tr>
51
+
52
+ <tr>
53
+ <td style="padding: 26px 28px 0 28px;">
54
+ <p style="margin: 0 0 10px 0; color: #052439; font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;">Visitor Information</p>
36
55
 
37
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
38
- <td style="text-align: left; font-weight: bold; padding: 8px;">Tenant/Resident Address</td>
39
- <td style="text-align: right; padding: 8px;">{{address}}</td>
56
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; border-collapse: collapse;">
57
+ <tr>
58
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Name</td>
59
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{name}}</td>
60
+ </tr>
61
+ {{#if isInviteEmail}}{{#if isContractor}}
62
+ <tr>
63
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Company</td>
64
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{company}}</td>
65
+ </tr>
66
+ <tr>
67
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Contractor Type</td>
68
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{contractorTypeLabel}}</td>
69
+ </tr>
70
+ {{/if}}{{/if}}
71
+ <tr>
72
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Tenant/Resident Address</td>
73
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{address}}</td>
74
+ </tr>
75
+ <tr>
76
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Contact No.</td>
77
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{contactNumber}}</td>
78
+ </tr>
79
+ <tr>
80
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Email</td>
81
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right; word-break: break-all;">{{email}}</td>
82
+ </tr>
83
+ {{#if isInviteEmail}}
84
+ <tr>
85
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Vehicle Plate No.</td>
86
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{plateNumber}}</td>
87
+ </tr>
88
+ <tr>
89
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Overnight Parking</td>
90
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{overnightParking}}</td>
40
91
  </tr>
41
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
42
- <td style="text-align: left; font-weight: bold; padding: 8px;">Contact No.</td>
43
- <td style="text-align: right; padding: 8px;">{{contactNumber}}</td>
92
+ {{/if}}
93
+ <tr>
94
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Arrival Date</td>
95
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{arrivalDate}}</td>
44
96
  </tr>
45
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
46
- <td style="text-align: left; font-weight: bold; padding: 8px;">Email</td>
47
- <td style="text-align: right; padding: 8px;">{{email}}</td>
97
+ <tr>
98
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Arrival Time</td>
99
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{arrivalTime}}</td>
48
100
  </tr>
101
+ <tr>
102
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Duration</td>
103
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{duration}}</td>
104
+ </tr>
105
+ {{#if isInviteEmail}}
106
+ <tr>
107
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Number of Passenger</td>
108
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{numberOfPassengers}}</td>
109
+ </tr>
110
+ {{/if}}
111
+ {{! Stacked rather than right-aligned: a purpose can run long, and a
112
+ wrapped right-aligned value reads as ragged noise. }}
113
+ <tr>
114
+ <td colspan="2" style="padding: 11px 0 0 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Purpose of Visit</td>
115
+ </tr>
116
+ <tr>
117
+ <td colspan="2" style="padding: 3px 0 11px 0; color: #052439; font-size: 14px; font-weight: 600; line-height: 21px;">{{purposeOfVisit}}</td>
118
+ </tr>
119
+ </table>
120
+ </td>
121
+ </tr>
122
+
123
+ {{#if hasMembers}}
124
+ <tr>
125
+ <td style="padding: 26px 28px 0 28px;">
126
+ <p style="margin: 0 0 12px 0; color: #052439; font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;">Accompanying Members</p>
127
+
128
+ {{#each members}}
129
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; background-color: #f5f8fa; border: 1px solid #e7ecf0; border-radius: 10px; margin-bottom: 10px;">
130
+ <tr>
131
+ <td style="padding: 14px 16px;">
132
+ <p style="margin: 0 0 2px 0; color: #35a745; font-size: 11px; font-weight: 700; letter-spacing: 0.8px; text-transform: uppercase;">Member {{position}}</p>
133
+ <p style="margin: 0 0 8px 0; color: #052439; font-size: 16px; font-weight: 700;">{{name}}</p>
134
+
135
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; border-collapse: collapse;">
136
+ <tr>
137
+ <td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">NRIC / Passport No.</td>
138
+ <td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{nric}}</td>
139
+ </tr>
140
+ <tr>
141
+ <td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">Contact No.</td>
142
+ <td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{contact}}</td>
143
+ </tr>
144
+ <tr>
145
+ <td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">Vehicle Plate No.</td>
146
+ <td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{plateNumber}}</td>
147
+ </tr>
148
+ </table>
149
+ </td>
150
+ </tr>
151
+ </table>
152
+ {{/each}}
153
+ </td>
154
+ </tr>
155
+ {{/if}}
156
+
157
+ <tr>
158
+ <td style="padding: 26px 28px 28px 28px;">
159
+ <p style="margin: 0; padding-top: 18px; border-top: 1px solid #e7ecf0; color: #90a0ab; font-size: 12px; line-height: 18px; text-align: center;">This invitation was sent to you by your host.<br />&copy; Seven365 Pte. Ltd.</p>
160
+ </td>
161
+ </tr>
49
162
 
50
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
51
- <td style="text-align: left; font-weight: bold; padding: 8px;">Arrival Date</td>
52
- <td style="text-align: right; padding: 8px;">{{arrivalDate}}</td>
53
- </tr>
54
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
55
- <td style="text-align: left; font-weight: bold; padding: 8px;">Arrival Time</td>
56
- <td style="text-align: right; padding: 8px;">{{arrivalTime}}</td>
57
- </tr>
58
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
59
- <td style="text-align: left; font-weight: bold; padding: 8px;">Duration</td>
60
- <td style="text-align: right; padding: 8px;">{{duration}}</td>
61
- </tr>
62
- <tr style=" padding: 20px;">
63
- <td style="text-align: left; font-weight: bold; padding: 8px;">Purpose of Visit</td>
64
- </tr>
65
- <tr style="padding: 20px;"><td style="text-align: right; padding: 8px;">{{purposeOfVisit}}</td></tr>
66
- </tbody>
67
- </table>
68
- </div>
69
- <footer height="70" style="padding: 0; bottom: 0; left: 0; right: 0; z-index: 10; background-color: #fff;">
70
- <div style="padding: 12px; max-width: 600px; margin-left: auto; margin-right: auto;">
71
- <div style="text-align: center;">
72
- <span style="font-size: 16px;">© 2024 powered by Seven365 Pte. Ltd. v.1</span>
73
- </div>
74
- </div>
75
- </footer>
76
- </div>
163
+ </table>
77
164
  </td>
78
165
  </tr>
79
- </table>
166
+ </table>
@@ -1,79 +1,166 @@
1
- <table style="height: 100%; width: 100%">
1
+ {{! Visitor invitation email.
2
+
3
+ Email-client rules this file follows -- please keep them when editing:
4
+ * layout is <table>-based; flexbox and grid do not work in Outlook
5
+ * every style is inline; <style> blocks are stripped by Gmail
6
+ * no external CSS or web fonts
7
+ * `isInviteEmail` gates the resident-invite rows. The legacy
8
+ `residentAppInvitePreRegister` sender renders this same file from a
9
+ different document shape and sets none of those keys -- Handlebars
10
+ prints a missing key as an empty string, so without the guard that
11
+ email would grow a column of blank cells.
12
+ }}
13
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color: #eef1f4; margin: 0; padding: 24px 12px;">
2
14
  <tr>
3
- <td style="vertical-align: middle; text-align: center">
4
- <div style="padding: 24px; justify-content: center;">
5
- <div style="background-color: #052439; elevation: 2;">
6
- <div style="display: flex; justify-content: center;">
7
- <img src="{{logo}}" style="width: 190px; height: 50px;" />
8
- </div>
9
- </div>
10
- <div style="margin-bottom: 16px; color: #052439; ">
11
- <p style="color: #052439; font-weight: 600; font-size: 32px; line-height: 30px; text-align: center; padding: 16px;">Hey
12
- {{name}},</p>
13
- <p style="text-align: center;">You are now ready to check in upon arrival on {{arrivalDate}}.</p>
14
- </div>
15
- <a href="{{qrLink}}" style="text-decoration: none"><div
16
- style="margin-top: 16px; padding: 5px; background-color: #4caf50; border-radius: 16px; text-align: center;"
17
- >
18
- <p style="color: white; font-weight: bold;">QR code Link (Click Here)</p>
19
- </div></a>
15
+ <td align="center" style="vertical-align: top;">
16
+
17
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" style="width: 100%; max-width: 600px; background-color: #ffffff; border-radius: 14px; overflow: hidden; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;">
20
18
 
21
- <div style="margin-top: 16px; text-align: center; color: #052439; ">
22
- <p>Please present the QR code sent to you at the pedestrian entrance for access.</p>
23
- </div>
19
+ <tr>
20
+ <td align="center" style="background-color: #052439; padding: 22px 24px;">
21
+ <img src="{{logo}}" alt="Seven365" width="190" height="50" style="width: 190px; height: 50px; border: 0; display: block; color: #ffffff; font-family: Helvetica, Arial, sans-serif; font-size: 15px; font-weight: 700;" />
22
+ </td>
23
+ </tr>
24
24
 
25
- <div style="margin-top: 16px; padding: 5px; background-color: #052439; border-radius: 16px; text-align: center;">
26
- <p style="color: white; font-weight: bold;">Visitor Information</p>
27
- </div>
25
+ <tr>
26
+ <td style="padding: 32px 28px 8px 28px; text-align: center;">
27
+ <p style="margin: 0; color: #052439; font-weight: 700; font-size: 27px; line-height: 34px;">Hey {{name}},</p>
28
+ <p style="margin: 10px 0 0 0; color: #5b6b78; font-size: 15px; line-height: 22px;">You are now ready to check in upon arrival on <span style="color: #052439; font-weight: 600;">{{arrivalDate}}</span>.</p>
29
+ </td>
30
+ </tr>
28
31
 
29
- <div style="margin-top: 16px; max-width:100%; margin-left: auto; margin-right: auto; margin-bottom: 50px">
30
- <table style="color: #052439; font-size: 16px; margin: 0 auto; border-collapse: collapse; width: 100%;">
31
- <tbody>
32
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
33
- <td style="text-align: left; font-weight: bold; padding: 8px;">Name</td>
34
- <td style="text-align: right; padding: 8px;">{{name}}</td>
32
+ <tr>
33
+ <td style="padding: 22px 28px 0 28px;">
34
+ <a href="{{qrLink}}" style="display: block; background-color: #35a745; color: #ffffff; font-size: 16px; font-weight: 700; text-decoration: none; text-align: center; padding: 15px 20px; border-radius: 10px;">View My QR Code ( Click Here )</a>
35
+ {{#if statusLabel}}
36
+ {{! Whether the QR is usable yet. Sits under the button so it is read
37
+ before the tap, not after. `statusColor` is inlined from the
38
+ context because Handlebars has no helpers registered here. }}
39
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%;">
40
+ <tr>
41
+ <td align="center" style="padding: 14px 0 0 0;">
42
+ <span style="color: #7b8a95; font-size: 12px; letter-spacing: 0.4px; text-transform: uppercase; font-weight: 700;">QR Status</span>
43
+ <span style="display: inline-block; background-color: {{statusColor}}; color: #ffffff; font-size: 13px; font-weight: 700; padding: 5px 14px; border-radius: 999px; margin-left: 8px;">{{statusLabel}}</span>
44
+ </td>
35
45
  </tr>
46
+ </table>
47
+ {{/if}}
48
+ <p style="margin: 12px 0 0 0; color: #7b8a95; font-size: 13px; line-height: 19px; text-align: center;">Please present the QR code at the pedestrian entrance for access.</p>
49
+ </td>
50
+ </tr>
51
+
52
+ <tr>
53
+ <td style="padding: 26px 28px 0 28px;">
54
+ <p style="margin: 0 0 10px 0; color: #052439; font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;">Visitor Information</p>
36
55
 
37
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
38
- <td style="text-align: left; font-weight: bold; padding: 8px;">Tenant/Resident Address</td>
39
- <td style="text-align: right; padding: 8px;">{{address}}</td>
56
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; border-collapse: collapse;">
57
+ <tr>
58
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Name</td>
59
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{name}}</td>
60
+ </tr>
61
+ {{#if isInviteEmail}}{{#if isContractor}}
62
+ <tr>
63
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Company</td>
64
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{company}}</td>
65
+ </tr>
66
+ <tr>
67
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Contractor Type</td>
68
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{contractorTypeLabel}}</td>
69
+ </tr>
70
+ {{/if}}{{/if}}
71
+ <tr>
72
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Tenant/Resident Address</td>
73
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{address}}</td>
74
+ </tr>
75
+ <tr>
76
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Contact No.</td>
77
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{contactNumber}}</td>
78
+ </tr>
79
+ <tr>
80
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Email</td>
81
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right; word-break: break-all;">{{email}}</td>
82
+ </tr>
83
+ {{#if isInviteEmail}}
84
+ <tr>
85
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Vehicle Plate No.</td>
86
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{plateNumber}}</td>
87
+ </tr>
88
+ <tr>
89
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Overnight Parking</td>
90
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{overnightParking}}</td>
40
91
  </tr>
41
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
42
- <td style="text-align: left; font-weight: bold; padding: 8px;">Contact No.</td>
43
- <td style="text-align: right; padding: 8px;">{{contactNumber}}</td>
92
+ {{/if}}
93
+ <tr>
94
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Arrival Date</td>
95
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{arrivalDate}}</td>
44
96
  </tr>
45
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
46
- <td style="text-align: left; font-weight: bold; padding: 8px;">Email</td>
47
- <td style="text-align: right; padding: 8px;">{{email}}</td>
97
+ <tr>
98
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Arrival Time</td>
99
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{arrivalTime}}</td>
48
100
  </tr>
101
+ <tr>
102
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Duration</td>
103
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{duration}}</td>
104
+ </tr>
105
+ {{#if isInviteEmail}}
106
+ <tr>
107
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Number of Passenger</td>
108
+ <td style="padding: 11px 0; border-top: 1px solid #e7ecf0; color: #052439; font-size: 14px; font-weight: 600; text-align: right;">{{numberOfPassengers}}</td>
109
+ </tr>
110
+ {{/if}}
111
+ {{! Stacked rather than right-aligned: a purpose can run long, and a
112
+ wrapped right-aligned value reads as ragged noise. }}
113
+ <tr>
114
+ <td colspan="2" style="padding: 11px 0 0 0; border-top: 1px solid #e7ecf0; color: #6b7c8a; font-size: 14px;">Purpose of Visit</td>
115
+ </tr>
116
+ <tr>
117
+ <td colspan="2" style="padding: 3px 0 11px 0; color: #052439; font-size: 14px; font-weight: 600; line-height: 21px;">{{purposeOfVisit}}</td>
118
+ </tr>
119
+ </table>
120
+ </td>
121
+ </tr>
122
+
123
+ {{#if hasMembers}}
124
+ <tr>
125
+ <td style="padding: 26px 28px 0 28px;">
126
+ <p style="margin: 0 0 12px 0; color: #052439; font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;">Accompanying Members</p>
127
+
128
+ {{#each members}}
129
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; background-color: #f5f8fa; border: 1px solid #e7ecf0; border-radius: 10px; margin-bottom: 10px;">
130
+ <tr>
131
+ <td style="padding: 14px 16px;">
132
+ <p style="margin: 0 0 2px 0; color: #35a745; font-size: 11px; font-weight: 700; letter-spacing: 0.8px; text-transform: uppercase;">Member {{position}}</p>
133
+ <p style="margin: 0 0 8px 0; color: #052439; font-size: 16px; font-weight: 700;">{{name}}</p>
134
+
135
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%; border-collapse: collapse;">
136
+ <tr>
137
+ <td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">NRIC / Passport No.</td>
138
+ <td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{nric}}</td>
139
+ </tr>
140
+ <tr>
141
+ <td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">Contact No.</td>
142
+ <td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{contact}}</td>
143
+ </tr>
144
+ <tr>
145
+ <td style="padding: 5px 0; color: #6b7c8a; font-size: 13px;">Vehicle Plate No.</td>
146
+ <td style="padding: 5px 0; color: #052439; font-size: 13px; font-weight: 600; text-align: right;">{{plateNumber}}</td>
147
+ </tr>
148
+ </table>
149
+ </td>
150
+ </tr>
151
+ </table>
152
+ {{/each}}
153
+ </td>
154
+ </tr>
155
+ {{/if}}
156
+
157
+ <tr>
158
+ <td style="padding: 26px 28px 28px 28px;">
159
+ <p style="margin: 0; padding-top: 18px; border-top: 1px solid #e7ecf0; color: #90a0ab; font-size: 12px; line-height: 18px; text-align: center;">This invitation was sent to you by your host.<br />&copy; Seven365 Pte. Ltd.</p>
160
+ </td>
161
+ </tr>
49
162
 
50
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
51
- <td style="text-align: left; font-weight: bold; padding: 8px;">Arrival Date</td>
52
- <td style="text-align: right; padding: 8px;">{{arrivalDate}}</td>
53
- </tr>
54
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
55
- <td style="text-align: left; font-weight: bold; padding: 8px;">Arrival Time</td>
56
- <td style="text-align: right; padding: 8px;">{{arrivalTime}}</td>
57
- </tr>
58
- <tr style="border-bottom: 1px solid gray; padding: 20px;">
59
- <td style="text-align: left; font-weight: bold; padding: 8px;">Duration</td>
60
- <td style="text-align: right; padding: 8px;">{{duration}}</td>
61
- </tr>
62
- <tr style=" padding: 20px;">
63
- <td style="text-align: left; font-weight: bold; padding: 8px;">Purpose of Visit</td>
64
- </tr>
65
- <tr style="padding: 20px;"><td style="text-align: right; padding: 8px;">{{purposeOfVisit}}</td></tr>
66
- </tbody>
67
- </table>
68
- </div>
69
- <footer height="70" style="padding: 0; bottom: 0; left: 0; right: 0; z-index: 10; background-color: #fff;">
70
- <div style="padding: 12px; max-width: 600px; margin-left: auto; margin-right: auto;">
71
- <div style="text-align: center;">
72
- <span style="font-size: 16px;">© 2024 powered by Seven365 Pte. Ltd. v.1</span>
73
- </div>
74
- </div>
75
- </footer>
76
- </div>
163
+ </table>
77
164
  </td>
78
165
  </tr>
79
- </table>
166
+ </table>
package/dist/index.js CHANGED
@@ -11545,11 +11545,11 @@ var dotenv = __toESM(require("dotenv"));
11545
11545
  dotenv.config();
11546
11546
  var MONGO_URI = process.env.MONGO_URI ?? "mongodb://localhost:27017";
11547
11547
  var MONGO_DB = process.env.MONGO_DB ?? "default";
11548
- var PORT = Number(process.env.PORT) ?? 3e3;
11548
+ var PORT = Number(process.env.PORT) || 3e3;
11549
11549
  var isDev = process.env.NODE_ENV !== "production";
11550
11550
  var MAILER_TRANSPORT_HOST = process.env.MAILER_TRANSPORT_HOST ?? "smtp.gmail.com";
11551
- var MAILER_TRANSPORT_PORT = Number(process.env.MAILER_TRANSPORT_PORT) ?? 465;
11552
- var MAILER_TRANSPORT_SECURE = process.env.MAILER_TRANSPORT_SECURE === "true";
11551
+ var MAILER_TRANSPORT_PORT = Number(process.env.MAILER_TRANSPORT_PORT) || 465;
11552
+ var MAILER_TRANSPORT_SECURE = process.env.MAILER_TRANSPORT_SECURE ? process.env.MAILER_TRANSPORT_SECURE === "true" : MAILER_TRANSPORT_PORT === 465;
11553
11553
  var MAILER_EMAIL = process.env.MAILER_EMAIL ?? "default@gmail.com";
11554
11554
  var MAILER_PASSWORD = process.env.MAILER_PASSWORD ?? "password";
11555
11555
  var MAILER_FROM_EMAIL = process.env.MAILER_FROM_EMAIL || void 0;
@@ -11564,7 +11564,7 @@ var APP_ACCOUNT = process.env.APP_ACCOUNT ?? "http://localhost:3000";
11564
11564
  var VERIFICATION_FORGET_PASSWORD_DURATION = process.env.VERIFICATION_FORGET_PASSWORD_DURATION ?? "10 minutes";
11565
11565
  var VERIFICATION_USER_INVITE_DURATION = process.env.VERIFICATION_USER_INVITE_DURATION ?? "3 days";
11566
11566
  var REDIS_HOST = process.env.REDIS_HOST ?? "localhost";
11567
- var REDIS_PORT = Number(process.env.REDIS_PORT) ?? 6379;
11567
+ var REDIS_PORT = Number(process.env.REDIS_PORT) || 6379;
11568
11568
  var REDIS_PASSWORD = process.env.REDIS_PASSWORD ?? "password";
11569
11569
  var DEFAULT_USER_EMAIL = process.env.DEFAULT_USER_EMAIL ?? "";
11570
11570
  var DEFAULT_USER_PASSWORD = process.env.DEFAULT_USER_PASSWORD ?? "";
@@ -19980,6 +19980,33 @@ async function sendEmail({ to, subject, text: text2, html }) {
19980
19980
  }
19981
19981
  }
19982
19982
 
19983
+ // src/utils/visitor-email.util.ts
19984
+ var maskNRIC = (value) => {
19985
+ const nric = (value ?? "").trim();
19986
+ if (!nric)
19987
+ return "";
19988
+ if (nric.length <= 4)
19989
+ return nric;
19990
+ return `${"*".repeat(nric.length - 4)}${nric.slice(-4)}`;
19991
+ };
19992
+ var formatContractorType = (value) => (value ?? "").trim().split(/[-_\s]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
19993
+ var VISITOR_STATUS_BADGE = {
19994
+ registered: { label: "Checked In", color: "#3B82F6" },
19995
+ invited: { label: "Invited", color: "#6B7280" },
19996
+ "checked-out": { label: "Checked Out", color: "#EF4444" },
19997
+ approved: { label: "Approved", color: "#10B981" },
19998
+ pending: { label: "Pending", color: "#FBBF24" }
19999
+ };
20000
+ var visitorStatusBadge = (status) => {
20001
+ const key = (status ?? "").trim().toLowerCase();
20002
+ if (!key)
20003
+ return { label: "", color: "#6B7280" };
20004
+ return VISITOR_STATUS_BADGE[key] ?? {
20005
+ label: key.charAt(0).toUpperCase() + key.slice(1),
20006
+ color: "#6B7280"
20007
+ };
20008
+ };
20009
+
19983
20010
  // src/utils/visitor-status.util.ts
19984
20011
  var PRE_CHECK_IN_STATUSES = [
19985
20012
  "approved" /* APPROVED */,
@@ -20742,21 +20769,50 @@ function useVisitorTransactionRepo() {
20742
20769
  const qrLink = `${baseUrl}/resident/invite/register/${updatedDocument?._id}`;
20743
20770
  const logo = `${baseUrl}/images/resident/dark/seven-365.svg`;
20744
20771
  const arrivalDate = updatedDocument?.expectedCheckIn ? (0, import_moment_timezone.default)(updatedDocument.expectedCheckIn).tz("Asia/Singapore").format("DD/MM/YYYY") : "No Arrival Date";
20772
+ const isLead = !updatedDocument?.leadId;
20773
+ const memberRows = isLead && Array.isArray(updatedDocument?.members) ? updatedDocument.members.map((member, index) => ({
20774
+ position: index + 1,
20775
+ name: member?.name?.trim() || "Unknown",
20776
+ nric: maskNRIC(member?.nric) || "N/A",
20777
+ contact: member?.contact || "N/A",
20778
+ plateNumber: member?.plateNumber || "N/A"
20779
+ })) : [];
20780
+ const statusBadge = visitorStatusBadge(updatedDocument?.status);
20745
20781
  let context = {
20746
20782
  logo,
20747
20783
  qrLink,
20784
+ // Whether the QR is usable yet. Same label and colour the resident
20785
+ // sees in the app, so the two never disagree.
20786
+ statusLabel: statusBadge.label,
20787
+ statusColor: statusBadge.color,
20788
+ // `isInviteEmail` gates every row added for the resident invite. The
20789
+ // legacy `residentAppInvitePreRegister` sender in the API repo renders
20790
+ // these same templates from a different document shape and has none of
20791
+ // these keys -- Handlebars prints a missing key as an empty string, so
20792
+ // without this flag its email would grow a column of blank cells.
20793
+ isInviteEmail: true,
20794
+ isContractor: updatedDocument?.type === "contractor",
20795
+ company: updatedDocument?.company || "N/A",
20796
+ contractorTypeLabel: updatedDocument?.contractorType ? formatContractorType(updatedDocument.contractorType) : "N/A",
20748
20797
  name: `${updatedDocument?.name}`,
20749
- plateNumber: updatedDocument?.plateNumber,
20798
+ plateNumber: updatedDocument?.plateNumber || "N/A",
20799
+ overnightParking: updatedDocument?.isOvernightParking ? "Yes" : "No",
20800
+ numberOfPassengers: updatedDocument?.numberOfPassengers ?? 0,
20750
20801
  nric: updatedDocument?.nric,
20751
20802
  address: `${updatedDocument?.blockName} ${updatedDocument?.levelName} ${updatedDocument?.unitName}`,
20752
20803
  contactNumber: updatedDocument?.contact ? updatedDocument?.contact : "N/A",
20753
20804
  email: updatedDocument?.email ? updatedDocument?.email : "N/A",
20754
20805
  duration: updatedDocument?.duration ? updatedDocument?.duration : "N/A",
20755
- remarks: updatedDocument?.purpose ? updatedDocument?.purpose : "N/A",
20756
20806
  arrivalDate,
20757
20807
  arrivalTime: updatedDocument?.arrivalTime ? updatedDocument?.arrivalTime : "N/A",
20758
20808
  hoursString: updatedDocument?.duration ? `Hour(s)` : "N/A",
20759
- purposeOfVisit: updatedDocument?.remarks ? updatedDocument?.remarks : "N/A"
20809
+ // The invite route accepts `purpose` and never `remarks`, so reading
20810
+ // `remarks` first (as this did) printed "N/A" for every invite. `remarks`
20811
+ // stays as the fallback because the property-management approval flow does
20812
+ // write it.
20813
+ purposeOfVisit: updatedDocument?.purpose || updatedDocument?.remarks || "N/A",
20814
+ members: memberRows,
20815
+ hasMembers: memberRows.length > 0
20760
20816
  };
20761
20817
  let handlebar = "approve-walk-in";
20762
20818
  if (updatedDocument?.isOvernightParking != null || updatedDocument?.plateNumber) {