mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-06-15 13:32:43 +01:00
Added newsletter consent and term/privacy checkbox to registration
This commit is contained in:
committed by
Constantin Graf
parent
11af9fab7e
commit
0448ebc180
@@ -6,6 +6,7 @@ namespace App\Actions\Fortify;
|
|||||||
|
|
||||||
use App\Enums\Role;
|
use App\Enums\Role;
|
||||||
use App\Enums\Weekday;
|
use App\Enums\Weekday;
|
||||||
|
use App\Events\NewsletterRegistered;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Service\TimezoneService;
|
use App\Service\TimezoneService;
|
||||||
@@ -49,6 +50,9 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
],
|
],
|
||||||
'password' => $this->passwordRules(),
|
'password' => $this->passwordRules(),
|
||||||
'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
|
'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
|
||||||
|
'newsletter_consent' => [
|
||||||
|
'boolean',
|
||||||
|
],
|
||||||
])->validate();
|
])->validate();
|
||||||
|
|
||||||
$timezone = 'UTC';
|
$timezone = 'UTC';
|
||||||
@@ -56,7 +60,7 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
$timezone = $input['timezone'];
|
$timezone = $input['timezone'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return DB::transaction(function () use ($input, $timezone) {
|
$user = DB::transaction(function () use ($input, $timezone) {
|
||||||
return tap(User::create([
|
return tap(User::create([
|
||||||
'name' => $input['name'],
|
'name' => $input['name'],
|
||||||
'email' => $input['email'],
|
'email' => $input['email'],
|
||||||
@@ -67,6 +71,13 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
$this->createTeam($user);
|
$this->createTeam($user);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$newsletterConsent = isset($input['newsletter_consent']) && (bool) $input['newsletter_consent'];
|
||||||
|
if ($newsletterConsent) {
|
||||||
|
NewsletterRegistered::dispatch($input['name'], $input['email'], $user->getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
28
app/Events/NewsletterRegistered.php
Normal file
28
app/Events/NewsletterRegistered.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
|
||||||
|
class NewsletterRegistered
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
|
||||||
|
public string $name;
|
||||||
|
|
||||||
|
public string $email;
|
||||||
|
|
||||||
|
public string $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct(string $name, string $email, string $id)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->email = $email;
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ use Brick\Money\Currency;
|
|||||||
use Brick\Money\ISOCurrencyProvider;
|
use Brick\Money\ISOCurrencyProvider;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Laravel\Fortify\Fortify;
|
||||||
use Laravel\Jetstream\Actions\UpdateTeamMemberRole;
|
use Laravel\Jetstream\Actions\UpdateTeamMemberRole;
|
||||||
use Laravel\Jetstream\Jetstream;
|
use Laravel\Jetstream\Jetstream;
|
||||||
|
|
||||||
@@ -52,6 +54,13 @@ class JetstreamServiceProvider extends ServiceProvider
|
|||||||
Jetstream::useTeamModel(Organization::class);
|
Jetstream::useTeamModel(Organization::class);
|
||||||
Jetstream::useTeamInvitationModel(OrganizationInvitation::class);
|
Jetstream::useTeamInvitationModel(OrganizationInvitation::class);
|
||||||
app()->singleton(UpdateTeamMemberRole::class, UpdateMemberRole::class);
|
app()->singleton(UpdateTeamMemberRole::class, UpdateMemberRole::class);
|
||||||
|
Fortify::registerView(function () {
|
||||||
|
return Inertia::render('Auth/Register', [
|
||||||
|
'terms_url' => config('auth.terms_url'),
|
||||||
|
'privacy_policy_url' => config('auth.privacy_policy_url'),
|
||||||
|
'newsletter_consent' => config('auth.newsletter_consent'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -117,4 +117,10 @@ return [
|
|||||||
|
|
||||||
'super_admins' => ! is_string(env('SUPER_ADMINS', null)) ? [] : explode(',', env('SUPER_ADMINS')),
|
'super_admins' => ! is_string(env('SUPER_ADMINS', null)) ? [] : explode(',', env('SUPER_ADMINS')),
|
||||||
|
|
||||||
|
'terms_url' => env('TERMS_URL'),
|
||||||
|
|
||||||
|
'privacy_policy_url' => env('PRIVACY_POLICY_URL'),
|
||||||
|
|
||||||
|
'newsletter_consent' => env('NEWSLETTER_CONSENT', false),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -60,9 +60,8 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'features' => [
|
'features' => [
|
||||||
// Features::termsAndPrivacyPolicy(),
|
Features::termsAndPrivacyPolicy(),
|
||||||
Features::profilePhotos(),
|
Features::profilePhotos(),
|
||||||
// Features::api(),
|
|
||||||
Features::teams(['invitations' => true]),
|
Features::teams(['invitations' => true]),
|
||||||
Features::accountDeletion(),
|
Features::accountDeletion(),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Third Party Services
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This file is for storing the credentials for third party services such
|
|
||||||
| as Mailgun, Postmark, AWS and more. This file provides the de facto
|
|
||||||
| location for this type of information, allowing packages to have
|
|
||||||
| a conventional file to locate the various service credentials.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'mailgun' => [
|
|
||||||
'domain' => env('MAILGUN_DOMAIN'),
|
|
||||||
'secret' => env('MAILGUN_SECRET'),
|
|
||||||
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
|
|
||||||
'scheme' => 'https',
|
|
||||||
],
|
|
||||||
|
|
||||||
'postmark' => [
|
|
||||||
'token' => env('POSTMARK_TOKEN'),
|
|
||||||
],
|
|
||||||
|
|
||||||
'ses' => [
|
|
||||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
|
||||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
|
||||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -15,15 +15,21 @@ const form = useForm({
|
|||||||
password_confirmation: '',
|
password_confirmation: '',
|
||||||
terms: false,
|
terms: false,
|
||||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone ?? null,
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone ?? null,
|
||||||
|
newsletter_consent: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
form.post(route('register'), {
|
form.post(route('register'), {
|
||||||
onFinish: () => form.reset('password', 'password_confirmation'),
|
onSuccess: () => {
|
||||||
|
form.reset('password', 'password_confirmation');
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const page = usePage<{
|
const page = usePage<{
|
||||||
|
terms_url: string | null;
|
||||||
|
privacy_policy_url: string | null;
|
||||||
|
newsletter_consent: boolean;
|
||||||
jetstream: {
|
jetstream: {
|
||||||
hasTermsAndPrivacyPolicyFeature: boolean;
|
hasTermsAndPrivacyPolicyFeature: boolean;
|
||||||
};
|
};
|
||||||
@@ -111,29 +117,32 @@ const page = usePage<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="page.props.jetstream.hasTermsAndPrivacyPolicyFeature"
|
v-if="
|
||||||
|
page.props.jetstream.hasTermsAndPrivacyPolicyFeature &&
|
||||||
|
page.props.terms_url !== null &&
|
||||||
|
page.props.privacy_policy_url !== null
|
||||||
|
"
|
||||||
class="mt-4">
|
class="mt-4">
|
||||||
<InputLabel for="terms">
|
<InputLabel for="terms">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="terms"
|
id="terms"
|
||||||
v-model:checked="form.terms"
|
v-model:checked="form.terms"
|
||||||
name="terms"
|
name="terms" />
|
||||||
required />
|
|
||||||
|
|
||||||
<div class="ms-2">
|
<div class="ms-2">
|
||||||
I agree to the
|
I agree to the
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
:href="route('terms.show')"
|
:href="page.props.terms_url"
|
||||||
class="underline text-sm text-muted hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
class="underline text-sm text-muted hover:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||||
>Terms of Service</a
|
>Terms of Service</a
|
||||||
>
|
>
|
||||||
and
|
and
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
:href="route('policy.show')"
|
:href="page.props.privacy_policy_url"
|
||||||
class="underline text-sm text-muted hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
class="underline text-sm text-muted hover:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||||
>Privacy Policy</a
|
>Privacy Policy</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,6 +151,25 @@ const page = usePage<{
|
|||||||
</InputLabel>
|
</InputLabel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4" v-if="page.props.newsletter_consent">
|
||||||
|
<InputLabel for="newsletter_consent">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="newsletter_consent"
|
||||||
|
v-model:checked="form.newsletter_consent"
|
||||||
|
name="newsletter_consent" />
|
||||||
|
|
||||||
|
<div class="ms-2">
|
||||||
|
I agree to receive emails about product related
|
||||||
|
updates
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<InputError
|
||||||
|
class="mt-2"
|
||||||
|
:message="form.errors.newsletter_consent" />
|
||||||
|
</InputLabel>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-end mt-4">
|
<div class="flex items-center justify-end mt-4">
|
||||||
<Link
|
<Link
|
||||||
:href="route('login')"
|
:href="route('login')"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const verificationLinkSent = computed(
|
|||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
:href="route('profile.show')"
|
:href="route('profile.show')"
|
||||||
class="underline text-sm text-muted hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
class="underline text-sm text-muted hover:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||||
Edit Profile</Link
|
Edit Profile</Link
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ declare(strict_types=1);
|
|||||||
namespace Tests\Feature;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Enums\Role;
|
use App\Enums\Role;
|
||||||
|
use App\Events\NewsletterRegistered;
|
||||||
use App\Models\Membership;
|
use App\Models\Membership;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Providers\RouteServiceProvider;
|
use App\Providers\RouteServiceProvider;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
use Laravel\Fortify\Features;
|
use Laravel\Fortify\Features;
|
||||||
use Laravel\Jetstream\Jetstream;
|
use Laravel\Jetstream\Jetstream;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
@@ -41,6 +43,11 @@ class RegistrationTest extends TestCase
|
|||||||
|
|
||||||
public function test_new_users_can_register(): void
|
public function test_new_users_can_register(): void
|
||||||
{
|
{
|
||||||
|
// Arrange
|
||||||
|
Event::fake([
|
||||||
|
NewsletterRegistered::class,
|
||||||
|
]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$response = $this->post('/register', [
|
$response = $this->post('/register', [
|
||||||
'name' => 'Test User',
|
'name' => 'Test User',
|
||||||
@@ -51,6 +58,7 @@ class RegistrationTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
$response->assertValid();
|
||||||
$this->assertAuthenticated();
|
$this->assertAuthenticated();
|
||||||
$response->assertRedirect(RouteServiceProvider::HOME);
|
$response->assertRedirect(RouteServiceProvider::HOME);
|
||||||
$user = User::where('email', 'test@example.com')->firstOrFail();
|
$user = User::where('email', 'test@example.com')->firstOrFail();
|
||||||
@@ -60,6 +68,34 @@ class RegistrationTest extends TestCase
|
|||||||
$this->assertSame(true, $organization->personal_team);
|
$this->assertSame(true, $organization->personal_team);
|
||||||
$member = Membership::query()->whereBelongsTo($user, 'user')->whereBelongsTo($organization, 'organization')->firstOrFail();
|
$member = Membership::query()->whereBelongsTo($user, 'user')->whereBelongsTo($organization, 'organization')->firstOrFail();
|
||||||
$this->assertSame(Role::Owner->value, $member->role);
|
$this->assertSame(Role::Owner->value, $member->role);
|
||||||
|
Event::assertNotDispatched(NewsletterRegistered::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_new_users_can_consent_to_newsletter_during_registration(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Event::fake([
|
||||||
|
NewsletterRegistered::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->post('/register', [
|
||||||
|
'name' => 'Test User',
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
'password' => 'password',
|
||||||
|
'password_confirmation' => 'password',
|
||||||
|
'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(),
|
||||||
|
'newsletter_consent' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertValid();
|
||||||
|
$this->assertAuthenticated();
|
||||||
|
$response->assertRedirect(RouteServiceProvider::HOME);
|
||||||
|
$user = User::where('email', 'test@example.com')->firstOrFail();
|
||||||
|
$this->assertSame('Test User', $user->name);
|
||||||
|
$this->assertSame('UTC', $user->timezone);
|
||||||
|
Event::assertDispatched(NewsletterRegistered::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_new_users_can_register_and_frontend_can_send_timezone_for_user(): void
|
public function test_new_users_can_register_and_frontend_can_send_timezone_for_user(): void
|
||||||
|
|||||||
Reference in New Issue
Block a user