Skip to content

Commit 8d1482a

Browse files
committed
feat: verification confirmation email style update
Signed-off-by: romanetar <roman_ag@hotmail.com>
1 parent 08712ec commit 8d1482a

4 files changed

Lines changed: 166 additions & 23 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: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,77 @@
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') !!} <b><a href="#" style="text-decoration:none !important;color:black !important; cursor:default !important">{!!$user_email!!}</a></b> has been successfully verified.</div>
9-
</td>
10-
</tr>
11-
12-
@if(!empty($reset_password_link))
136
<tr>
14-
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
15-
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">Remember, if you need to set a password, you may do so <a href="{!! $reset_password_link !!}" target="_blank">here</a> (link expires in {!! $reset_password_link_lifetime !!} minutes).</div>
7+
<td style="padding: 24px 40px 8px;">
8+
<table border="0" cellpadding="0" cellspacing="0" role="presentation">
9+
<tbody>
10+
<tr>
11+
<td style="vertical-align: middle; width: 38px; padding-right: 10px;">
12+
<div style="width: 28px; height: 28px; border-radius: 50%; background: #1D9E75; text-align: center; line-height: 28px; font-size: 16px; color: #FFFFFF; font-weight: 700;">&#10003;</div>
13+
</td>
14+
<td style="vertical-align: middle;">
15+
<h1 style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 22px; font-weight: 500; margin: 0; color: #0A0A0A; letter-spacing: -0.01em;">You're verified.</h1>
16+
</td>
17+
</tr>
18+
</tbody>
19+
</table>
20+
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 15px; color: #5F5E5A; margin: 12px 0 24px; line-height: 1.6;"><span style="font-family: 'Courier New', monospace; font-size: 14px; color: #0A0A0A;">{{ $user_email }}</span> is now your {!! Config::get('app.app_name') !!}.</p>
21+
</td>
22+
</tr>
23+
@if(!empty($reset_password_link) || !$user_is_complete)
24+
<tr>
25+
<td style="padding: 0 40px 20px;">
26+
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; color: #5F5E5A; margin: 0; line-height: 1.6;">Two quick things to finish setup:</p>
27+
</td>
28+
</tr>
29+
@endif
30+
@if(!empty($reset_password_link))
31+
<tr>
32+
<td style="padding: 0 40px 32px;">
33+
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
34+
<tbody>
35+
<tr>
36+
<td style="vertical-align: top; width: 32px; padding-right: 10px;">
37+
<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>
38+
</td>
39+
<td>
40+
<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>
41+
<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>
42+
<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>
43+
</td>
44+
</tr>
45+
</tbody>
46+
</table>
47+
</td>
48+
</tr>
49+
@endif
50+
@if(!$user_is_complete)
51+
<tr>
52+
<td style="padding: 0 40px 32px;">
53+
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
54+
<tbody>
55+
<tr>
56+
<td style="vertical-align: top; width: 32px; padding-right: 10px;">
57+
<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>
58+
</td>
59+
<td>
60+
<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>
61+
<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>
62+
<a href="{!! URL::action("UserController@getLogin") !!}" 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>
63+
</td>
64+
</tr>
65+
</tbody>
66+
</table>
67+
</td>
68+
</tr>
69+
@endif
70+
<tr>
71+
<td style="background: #F5F4EF; padding: 20px 40px; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 13px; color: #5F5E5A; text-align: center;">
72+
Questions? <a href="mailto:{!! Config::get('app.help_email') !!}" style="color: #0C64C8; text-decoration: none;">{!! Config::get('app.help_email') !!}</a>
1673
</td>
1774
</tr>
18-
@endif
19-
@if(!$user_is_complete)
20-
<tr>
21-
<td align="center" style="font-size:0px;padding:10px 25px;padding-right:16px;padding-left:25px;word-break:break-word;">
22-
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">You may enter your profile details <a href="{!! URL::action("UserController@getLogin") !!}" target="_blank">here</a>.</div>
23-
</td>
24-
</tr>
25-
@endif
26-
<tr>
27-
<td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;">
28-
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">If you need further assistance please email <a href="mailto:{!! Config::get('app.help_email') !!}">{!! Config::get('app.help_email') !!}</a>.</div>
29-
</td>
30-
</tr>
3175
</tbody>
3276
</table>
3377
@stop
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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\UserEmailVerificationSuccess;
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+
class EmailVerificationSuccessViewTest extends BrowserKitTestCase
24+
{
25+
protected function prepareForTests(): void
26+
{
27+
$_SERVER['HTTP_CLIENT_IP'] = '127.0.0.1';
28+
Queue::fake();
29+
Mail::fake();
30+
}
31+
32+
private function makeUser(string $email, bool $complete): User
33+
{
34+
$user = $this->createMock(User::class);
35+
$user->method('getEmail')->willReturn($email);
36+
$user->method('getFullName')->willReturn('Preview User');
37+
$user->method('getFirstName')->willReturn($complete ? 'Preview' : null);
38+
$user->method('getLastName')->willReturn($complete ? 'User' : null);
39+
$user->method('getCompany')->willReturn($complete ? 'ACME Inc.' : null);
40+
$user->method('getCountry')->willReturn($complete ? 'US' : null);
41+
return $user;
42+
}
43+
44+
public function testVerificationSuccessWithPasswordLinkAndIncompleteProfile(): void
45+
{
46+
$email = 'preview+verified@example.com';
47+
$resetLink = 'https://id.fntech.com/reset/abc123deadbeef';
48+
49+
$mailable = new UserEmailVerificationSuccess($this->makeUser($email, false), $resetLink);
50+
51+
$mailable->assertHasSubject(
52+
sprintf('[%1$s] %1$s Verified', Config::get('app.app_name'))
53+
);
54+
$mailable->assertTo($email);
55+
56+
$html = $mailable->render();
57+
58+
$this->assertStringContainsString("You're verified.", $html);
59+
$this->assertStringContainsString($email, $html);
60+
$this->assertStringContainsString(Config::get('app.app_name'), $html);
61+
// Both steps present
62+
$this->assertStringContainsString('Two quick things to finish setup', $html);
63+
$this->assertStringContainsString('Set your password', $html);
64+
$this->assertStringContainsString($resetLink, $html);
65+
$this->assertStringContainsString('Complete your profile', $html);
66+
$this->assertStringContainsString('Open profile', $html);
67+
// Footer
68+
$this->assertStringContainsString(Config::get('app.help_email'), $html);
69+
70+
}
71+
72+
public function testVerificationSuccessWithoutPasswordLinkAndCompleteProfile(): void
73+
{
74+
$email = 'preview+verified-complete@example.com';
75+
76+
$mailable = new UserEmailVerificationSuccess($this->makeUser($email, true), null);
77+
78+
$mailable->assertHasSubject(
79+
sprintf('[%1$s] %1$s Verified', Config::get('app.app_name'))
80+
);
81+
$mailable->assertTo($email);
82+
83+
$html = $mailable->render();
84+
85+
$this->assertStringContainsString("You're verified.", $html);
86+
$this->assertStringContainsString($email, $html);
87+
// No steps — intro paragraph should be absent
88+
$this->assertStringNotContainsString('Two quick things to finish setup', $html);
89+
$this->assertStringNotContainsString('Set your password', $html);
90+
$this->assertStringNotContainsString('Complete your profile', $html);
91+
// Footer always present
92+
$this->assertStringContainsString(Config::get('app.help_email'), $html);
93+
94+
}
95+
}

0 commit comments

Comments
 (0)