Skip to content

Commit 9ae95ce

Browse files
committed
feat: welcome email style update
Signed-off-by: romanetar <roman_ag@hotmail.com>
1 parent 08712ec commit 9ae95ce

4 files changed

Lines changed: 174 additions & 30 deletions

File tree

.github/workflows/pull_request_unit_tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ jobs:
3737
PHP_VERSION: 8.3
3838
OTEL_SDK_DISABLED: true
3939
OTEL_SERVICE_ENABLED: false
40+
MAIL_FROM_EMAIL: noreply@tipit.net
41+
MAIL_FROM_NAME: noreply@tipit.net
4042
services:
4143
mysql:
4244
image: mysql:8.0

.github/workflows/push.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ jobs:
3333
PHP_VERSION: 8.3
3434
OTEL_SDK_DISABLED: true
3535
OTEL_SERVICE_ENABLED: false
36+
MAIL_FROM_EMAIL: noreply@tipit.net
37+
MAIL_FROM_NAME: noreply@tipit.net
3638
services:
3739
mysql:
3840
image: mysql:8.0
Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,71 @@
11
@extends('emails.email_layout')
22

33
@section('content')
4-
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;" width="100%">
4+
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
55
<tbody>
6-
<tr>
7-
<td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;">
8-
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">Your {!! Config::get('app.app_name') !!} is: <a href="#" style="text-decoration:none !important;color:black !important; cursor:default !important">{!!$user_email!!}</a></div>
9-
</td>
10-
</tr>
11-
@if(!empty($reset_password_link))
12-
<tr>
13-
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
14-
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">If you did not set a password you can <a href="{!! $reset_password_link !!}" target="_blank">do so now</a> (this link expires in {!! $reset_password_link_lifetime !!} min).</div>
15-
</td>
16-
</tr>
17-
@endif
18-
@if(!$user_is_complete)
19-
<tr>
20-
<td align="center" style="font-size:0px;padding:10px 25px;padding-right:16px;padding-left:25px;word-break:break-word;">
21-
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">If you have not entered your first name, last name, company, and country please <a href="{!! URL::action("UserController@getLogin") !!}" target="_blank">do so in your profile now</a>. You may also update your photo, add a bio, and provide other information you wish to share.</div>
22-
</td>
23-
</tr>
24-
@endif
25-
<tr>
26-
<td align="center" style="font-size:0px;padding:10px 25px;padding-right:16px;padding-left:25px;word-break:break-word;">
27-
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">Your {!! Config::get('app.app_name') !!} can be used to access all {!! Config::get('app.tenant_name') !!} apps necessary to interact with the event. In order for the apps to operate, each one will ask for permission to access your information and/or request the authority to use your {!! Config::get('app.app_name') !!}.</div>
28-
</td>
29-
</tr>
30-
<tr>
31-
<td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;">
32-
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">Should you have any questions, please email <a href="mailto:{!! Config::get('app.help_email') !!}">{!! Config::get('app.help_email') !!}</a> for assistance.</div>
33-
</td>
34-
</tr>
6+
<tr>
7+
<td style="padding: 24px 40px 8px;">
8+
<h1 style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 22px; font-weight: 500; margin: 0 0 8px; color: #0A0A0A; letter-spacing: -0.01em;">Your FNid is ready.</h1>
9+
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 15px; color: #5F5E5A; margin: 0 0 24px; line-height: 1.6;">One identity, every FNTECH app &mdash; for this event and the next.</p>
10+
</td>
11+
</tr>
12+
<tr>
13+
<td style="padding: 0 40px 32px;">
14+
<div style="background: #F5F4EF; border-radius: 6px; padding: 14px 16px;">
15+
<span style="font-family: 'Courier New', monospace; font-size: 14px; color: #0A0A0A; word-break: break-all;">{{ $user_email }}</span>
16+
</div>
17+
</td>
18+
</tr>
19+
@if(!empty($reset_password_link))
20+
<tr>
21+
<td style="padding: 0 40px 32px;">
22+
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
23+
<tbody>
24+
<tr>
25+
<td style="vertical-align: top; width: 32px; padding-right: 10px;">
26+
<div style="width: 22px; height: 22px; border-radius: 50%; background: #E6F1FB; color: #0C64C8; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 12px; font-weight: 500; text-align: center; line-height: 22px;">1</div>
27+
</td>
28+
<td>
29+
<div style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 15px; font-weight: 500; color: #0A0A0A; margin-bottom: 4px;">Set your password</div>
30+
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; color: #5F5E5A; margin: 0 0 12px; line-height: 1.6;">Link expires in {!! $reset_password_link_lifetime !!} minutes.</p>
31+
<a href="{!! $reset_password_link !!}" target="_blank" style="display: inline-block; background: #0A0A0A; color: #FFFFFF; text-decoration: none; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 500; padding: 9px 18px; border-radius: 6px;">Set password &rarr;</a>
32+
</td>
33+
</tr>
34+
</tbody>
35+
</table>
36+
</td>
37+
</tr>
38+
@endif
39+
@if(!$user_is_complete)
40+
<tr>
41+
<td style="padding: 0 40px 32px;">
42+
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
43+
<tbody>
44+
<tr>
45+
<td style="vertical-align: top; width: 32px; padding-right: 10px;">
46+
<div style="width: 22px; height: 22px; border-radius: 50%; background: #E6F1FB; color: #0C64C8; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 12px; font-weight: 500; text-align: center; line-height: 22px;">{{ !empty($reset_password_link) ? '2' : '1' }}</div>
47+
</td>
48+
<td>
49+
<div style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 15px; font-weight: 500; color: #0A0A0A; margin-bottom: 4px;">Complete your profile</div>
50+
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; color: #5F5E5A; margin: 0 0 12px; line-height: 1.6;">Add your name, company, and country. Photo and bio optional.</p>
51+
<a href="{!! $bio_link !!}" target="_blank" style="display: inline-block; background: transparent; color: #0A0A0A; text-decoration: none; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 500; padding: 8px 17px; border-radius: 6px; border: 1px solid #B4B2A9;">Open profile &rarr;</a>
52+
</td>
53+
</tr>
54+
</tbody>
55+
</table>
56+
</td>
57+
</tr>
58+
@endif
59+
<tr>
60+
<td style="padding: 0 40px 24px; border-top: 1px solid #E5E4DE;">
61+
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 13px; color: #5F5E5A; margin: 20px 0 0; line-height: 1.6;">You stay in control. Each {!! Config::get('app.tenant_name') !!} app will ask before accessing your information or acting on your behalf.</p>
62+
</td>
63+
</tr>
64+
<tr>
65+
<td style="background: #F5F4EF; padding: 20px 40px; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 13px; color: #5F5E5A; text-align: center;">
66+
Questions? <a href="mailto:{!! Config::get('app.help_email') !!}" style="color: #0C64C8; text-decoration: none;">{!! Config::get('app.help_email') !!}</a>
67+
</td>
68+
</tr>
3569
</tbody>
3670
</table>
3771
@stop
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php namespace Tests\unit;
2+
3+
/**
4+
* Copyright 2026 OpenStack Foundation
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
**/
15+
16+
use App\Mail\WelcomeNewUserEmail;
17+
use Auth\User;
18+
use Illuminate\Support\Facades\Config;
19+
use Illuminate\Support\Facades\Mail;
20+
use Illuminate\Support\Facades\Queue;
21+
use Tests\BrowserKitTestCase;
22+
23+
/**
24+
* Class WelcomeNewUserEmailFnViewTest
25+
*
26+
* Renders and delivers the FNTECH welcome email view to the configured mailer
27+
* (Mailtrap) for visual preview, covering both the full case (password link +
28+
* incomplete profile) and the minimal case (no password link, complete profile).
29+
*
30+
* @package Tests\unit
31+
*/
32+
class WelcomeNewUserEmailFnViewTest extends BrowserKitTestCase
33+
{
34+
protected function prepareForTests(): void
35+
{
36+
$_SERVER['HTTP_CLIENT_IP'] = '127.0.0.1';
37+
Queue::fake();
38+
Mail::fake();
39+
Config::set('app.tenant_name', 'FNTECH');
40+
}
41+
42+
private function makeUser(string $email, bool $complete): User
43+
{
44+
$user = $this->createMock(User::class);
45+
$user->method('getEmail')->willReturn($email);
46+
$user->method('getFullName')->willReturn('Preview User');
47+
$user->method('getFirstName')->willReturn($complete ? 'Preview' : null);
48+
$user->method('getLastName')->willReturn($complete ? 'User' : null);
49+
$user->method('getCompany')->willReturn($complete ? 'ACME Inc.' : null);
50+
$user->method('getCountry')->willReturn($complete ? 'US' : null);
51+
$user->method('createdByOTP')->willReturn(false);
52+
return $user;
53+
}
54+
55+
public function testWelcomeEmailWithPasswordLinkAndIncompleteProfile(): void
56+
{
57+
$email = 'preview+welcome@example.com';
58+
$resetLink = 'https://id.fntech.com/reset/abc123deadbeef';
59+
60+
$mailable = new WelcomeNewUserEmail($this->makeUser($email, false), $resetLink);
61+
62+
$mailable->assertHasSubject(
63+
sprintf('[%1$s] You now have an %1$s', Config::get('app.app_name'))
64+
);
65+
$mailable->assertTo($email);
66+
67+
$html = $mailable->render();
68+
69+
$this->assertStringContainsString('Your FNid is ready.', $html);
70+
$this->assertStringContainsString($email, $html);
71+
// Step 1 — password
72+
$this->assertStringContainsString('Set your password', $html);
73+
$this->assertStringContainsString($resetLink, $html);
74+
// Step 2 — profile
75+
$this->assertStringContainsString('Complete your profile', $html);
76+
$this->assertStringContainsString('Open profile', $html);
77+
// Disclaimer and footer
78+
$this->assertStringContainsString('You stay in control', $html);
79+
$this->assertStringContainsString(Config::get('app.help_email'), $html);
80+
81+
}
82+
83+
public function testWelcomeEmailWithoutPasswordLinkAndCompleteProfile(): void
84+
{
85+
$email = 'preview+welcome-complete@example.com';
86+
87+
$mailable = new WelcomeNewUserEmail($this->makeUser($email, true), null);
88+
89+
$mailable->assertHasSubject(
90+
sprintf('[%1$s] You now have an %1$s', Config::get('app.app_name'))
91+
);
92+
$mailable->assertTo($email);
93+
94+
$html = $mailable->render();
95+
96+
$this->assertStringContainsString('Your FNid is ready.', $html);
97+
$this->assertStringContainsString($email, $html);
98+
// Neither step should appear
99+
$this->assertStringNotContainsString('Set your password', $html);
100+
$this->assertStringNotContainsString('Complete your profile', $html);
101+
// Disclaimer and footer always present
102+
$this->assertStringContainsString('You stay in control', $html);
103+
$this->assertStringContainsString(Config::get('app.help_email'), $html);
104+
105+
}
106+
}

0 commit comments

Comments
 (0)