-
-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: allow users to change their names (#3071)
- Loading branch information
1 parent
4bbf6b0
commit 6c00463
Showing
32 changed files
with
1,273 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Community\Actions; | ||
|
||
use App\Models\User; | ||
use App\Models\UserUsername; | ||
|
||
class ApproveNewDisplayNameAction | ||
{ | ||
public function execute(User $user, UserUsername $changeRequest): void | ||
{ | ||
// Automatically mark conflicting requests as denied. | ||
UserUsername::where('username', $changeRequest->username) | ||
->where('id', '!=', $changeRequest->id) | ||
->whereNull('approved_at') | ||
->whereNull('denied_at') | ||
->update(['denied_at' => now()]); | ||
|
||
$changeRequest->update(['approved_at' => now()]); | ||
|
||
$user->display_name = $changeRequest->username; | ||
$user->save(); | ||
|
||
sendDisplayNameChangeConfirmationEmail($user, $changeRequest->username); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Community\Data; | ||
|
||
use App\Community\Requests\StoreUsernameChangeRequest; | ||
use Spatie\LaravelData\Data; | ||
|
||
class StoreUsernameChangeData extends Data | ||
{ | ||
public function __construct( | ||
public string $newDisplayName | ||
) { | ||
} | ||
|
||
public static function fromRequest(StoreUsernameChangeRequest $request): self | ||
{ | ||
return new self( | ||
newDisplayName: $request->newDisplayName, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Community\Requests; | ||
|
||
use App\Support\Rules\ValidNewUsername; | ||
use Illuminate\Foundation\Http\FormRequest; | ||
|
||
class StoreUsernameChangeRequest extends FormRequest | ||
{ | ||
public function rules(): array | ||
{ | ||
return [ | ||
'newDisplayName' => ValidNewUsername::get($this->user()), | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Filament\Resources; | ||
|
||
use App\Community\Actions\ApproveNewDisplayNameAction; | ||
use App\Filament\Extensions\Resources\Resource; | ||
use App\Filament\Resources\UserUsernameResource\Pages; | ||
use App\Models\User; | ||
use App\Models\UserUsername; | ||
use Filament\Forms\Form; | ||
use Filament\Notifications\Notification; | ||
use Filament\Tables; | ||
use Filament\Tables\Table; | ||
use Illuminate\Database\Eloquent\Builder; | ||
|
||
class UserUsernameResource extends Resource | ||
{ | ||
protected static ?string $model = UserUsername::class; | ||
|
||
protected static ?int $navigationSort = 30; | ||
|
||
protected static ?string $navigationIcon = 'heroicon-s-wrench'; | ||
|
||
protected static ?string $navigationGroup = 'Tools'; | ||
|
||
protected static ?string $navigationLabel = 'Username Change Requests'; | ||
|
||
protected static ?string $modelLabel = 'Username Change Request'; | ||
|
||
public static function getNavigationBadge(): ?string | ||
{ | ||
$count = static::getModel()::pending()->count(); | ||
|
||
return "{$count}"; | ||
} | ||
|
||
public static function getNavigationBadgeColor(): ?string | ||
{ | ||
return static::getNavigationBadge() > 0 ? 'warning' : null; | ||
} | ||
|
||
public static function form(Form $form): Form | ||
{ | ||
return $form | ||
->schema([ | ||
|
||
]); | ||
} | ||
|
||
public static function table(Table $table): Table | ||
{ | ||
return $table | ||
->modifyQueryUsing(fn (Builder $query) => $query->where(function ($query) { | ||
$query->whereNotNull('approved_at') | ||
->orWhereNotNull('denied_at') | ||
->orWhere('created_at', '>', now()->subDays(30)); | ||
})) | ||
->columns([ | ||
Tables\Columns\TextColumn::make('user.username') | ||
->label('Original Username') | ||
->url(fn (UserUsername $record) => UserResource::getUrl('view', ['record' => $record->user->display_name])) | ||
->extraAttributes(['class' => 'underline']) | ||
->openUrlInNewTab() | ||
->searchable() | ||
->sortable(), | ||
|
||
Tables\Columns\TextColumn::make('user.display_name') | ||
->label('Current Username') | ||
->url(fn (UserUsername $record) => UserResource::getUrl('view', ['record' => $record->user->display_name])) | ||
->extraAttributes(['class' => 'underline']) | ||
->openUrlInNewTab() | ||
->searchable() | ||
->sortable(), | ||
|
||
Tables\Columns\TextColumn::make('username') | ||
->label('Requested New Username') | ||
->searchable() | ||
->sortable(), | ||
|
||
Tables\Columns\TextColumn::make('created_at') | ||
->label('Requested At') | ||
->dateTime() | ||
->sortable(), | ||
|
||
Tables\Columns\TextColumn::make('status') | ||
->label('Status') | ||
->state(fn (UserUsername $record): string => match (true) { | ||
$record->is_approved => 'Approved', | ||
$record->is_denied => 'Denied', | ||
default => 'Pending', | ||
}) | ||
->icon(fn (UserUsername $record): string => match (true) { | ||
$record->is_approved => 'heroicon-o-check-circle', | ||
$record->is_denied => 'heroicon-o-x-circle', | ||
default => 'heroicon-o-clock', | ||
}) | ||
->color(fn (UserUsername $record): string => match (true) { | ||
$record->is_approved => 'success', | ||
$record->is_denied => 'danger', | ||
default => 'warning', | ||
}), | ||
]) | ||
->defaultSort('created_at', 'desc') | ||
->filters([ | ||
Tables\Filters\SelectFilter::make('status') | ||
->options([ | ||
'pending' => 'Pending', | ||
'approved' => 'Approved', | ||
'denied' => 'Denied', | ||
]) | ||
->query(function ($query, $state) { | ||
if (!isset($state['value'])) { | ||
return $query; | ||
} | ||
|
||
return match ($state['value']) { | ||
'pending' => $query->pending(), | ||
'approved' => $query->approved(), | ||
'denied' => $query->denied(), | ||
default => $query, | ||
}; | ||
}) | ||
->default('pending'), | ||
]) | ||
->actions([ | ||
Tables\Actions\Action::make('approve') | ||
->action(function (UserUsername $record) { | ||
/** @var User $user */ | ||
$user = $record->user; | ||
$originalDisplayName = $user->display_name; | ||
|
||
(new ApproveNewDisplayNameAction())->execute($user, $record); | ||
|
||
Notification::make() | ||
->success() | ||
->title('Success') | ||
->body("Approved {$originalDisplayName}'s username change request.") | ||
->send(); | ||
}) | ||
->visible(fn (UserUsername $record) => !$record->is_approved && !$record->is_denied) | ||
->requiresConfirmation() | ||
->modalDescription("Are you sure you'd like to do this? The username change will go into effect immediately.") | ||
->color('success') | ||
->icon('heroicon-o-check'), | ||
|
||
Tables\Actions\Action::make('deny') | ||
->action(function (UserUsername $record) { | ||
$record->update(['denied_at' => now()]); | ||
|
||
/** @var User $user */ | ||
$user = $record->user; | ||
|
||
sendDisplayNameChangeDeclineEmail($user, $record->username); | ||
|
||
Notification::make() | ||
->success() | ||
->title('Success') | ||
->body("Denied {$record->user->display_name}'s username change request.") | ||
->send(); | ||
}) | ||
->visible(fn (UserUsername $record) => !$record->is_approved && !$record->is_denied) | ||
->requiresConfirmation() | ||
->modalDescription('Are you sure you want to deny this username change request?') | ||
->color('danger') | ||
->icon('heroicon-o-x-mark'), | ||
]) | ||
->bulkActions([ | ||
|
||
]); | ||
} | ||
|
||
public static function getRelations(): array | ||
{ | ||
return [ | ||
|
||
]; | ||
} | ||
|
||
public static function getPages(): array | ||
{ | ||
return [ | ||
'index' => Pages\Index::route('/'), | ||
]; | ||
} | ||
} |
Oops, something went wrong.