mirror of
https://github.com/brufdev/many-notes.git
synced 2026-01-07 19:39:56 -06:00
Improve saving users last visited URL to work with collaboration
This commit is contained in:
@@ -7,6 +7,7 @@ namespace App\Livewire\Auth;
|
||||
use App\Actions\GetAvailableOAuthProviders;
|
||||
use App\Enums\OAuthProviders;
|
||||
use App\Livewire\Forms\LoginForm;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
@@ -33,7 +34,12 @@ final class Login extends Component
|
||||
|
||||
Session::regenerate();
|
||||
|
||||
$this->redirectIntended(default: route('vaults.last', absolute: false), navigate: true);
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$redirectUrl = mb_strlen((string) $user->last_visited_url) > 0
|
||||
? $user->last_visited_url
|
||||
: route('vaults.index', absolute: false);
|
||||
$this->redirectIntended($redirectUrl, true);
|
||||
}
|
||||
|
||||
public function render(): Factory|View
|
||||
|
||||
@@ -34,6 +34,7 @@ final class OAuthLoginCallback extends Component
|
||||
}
|
||||
|
||||
$user = User::query()->where('email', $providerUser->getEmail())->first();
|
||||
|
||||
if (!$user) {
|
||||
$user = new CreateUser()->handle([
|
||||
'name' => $providerUser->getName() ?? '',
|
||||
@@ -41,7 +42,11 @@ final class OAuthLoginCallback extends Component
|
||||
'password' => Hash::make(Str::random(32)),
|
||||
]);
|
||||
}
|
||||
|
||||
Auth::login($user);
|
||||
$this->redirectIntended(route('vaults.last', absolute: false), true);
|
||||
$redirectUrl = mb_strlen((string) $user->last_visited_url) > 0
|
||||
? $user->last_visited_url
|
||||
: route('vaults.index', absolute: false);
|
||||
$this->redirectIntended($redirectUrl, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire\Dashboard;
|
||||
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
|
||||
final class Index extends Component
|
||||
{
|
||||
public function boot(): void
|
||||
{
|
||||
$this->redirect(route('vaults.last'), true);
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$redirectUrl = mb_strlen((string) $user->last_visited_url) > 0
|
||||
? $user->last_visited_url
|
||||
: route('vaults.index', absolute: false);
|
||||
$this->redirectIntended($redirectUrl, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ final class Index extends Component
|
||||
|
||||
public bool $showCreateModal = false;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->setLastVisitedUrl();
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
$this->form->create();
|
||||
@@ -78,4 +83,13 @@ final class Index extends Component
|
||||
'vaults' => $vaults,
|
||||
]);
|
||||
}
|
||||
|
||||
private function setLastVisitedUrl(): void
|
||||
{
|
||||
/** @var User $currentUser */
|
||||
$currentUser = auth()->user();
|
||||
$currentUser->update([
|
||||
'last_visited_url' => route('vaults.index', absolute: false),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire\Vault;
|
||||
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
|
||||
final class Last extends Component
|
||||
{
|
||||
public function mount(): void
|
||||
{
|
||||
/** @var User $currentUser */
|
||||
$currentUser = auth()->user();
|
||||
$lastVault = $currentUser->vaults()->whereNotNull('opened_at')->orderByDesc('opened_at')->first();
|
||||
|
||||
if (!$lastVault) {
|
||||
$this->redirect(route('vaults.index'), navigate: true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->redirect(route('vaults.show', ['vault' => $lastVault]), navigate: true);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use App\Actions\GetVaultNodeFromPath;
|
||||
use App\Actions\ResolveTwoPaths;
|
||||
use App\Actions\UpdateVault;
|
||||
use App\Livewire\Forms\VaultNodeForm;
|
||||
use App\Models\User;
|
||||
use App\Models\Vault;
|
||||
use App\Models\VaultNode;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
@@ -26,8 +27,7 @@ final class Show extends Component
|
||||
|
||||
public VaultNodeForm $nodeForm;
|
||||
|
||||
#[Locked]
|
||||
#[Url(as: 'file')]
|
||||
#[Url(as: 'file', history: true)]
|
||||
public ?int $selectedFile = null;
|
||||
|
||||
#[Locked]
|
||||
@@ -36,47 +36,37 @@ final class Show extends Component
|
||||
#[Locked]
|
||||
public ?string $selectedFileUrl = null;
|
||||
|
||||
public bool $isEditMode = true;
|
||||
|
||||
public function mount(Vault $vault): void
|
||||
{
|
||||
$this->authorize('view', $vault);
|
||||
new UpdateVault()->handle($vault, [
|
||||
'opened_at' => now(),
|
||||
]);
|
||||
$this->nodeForm->setVault($this->vault);
|
||||
|
||||
if ((int) $this->selectedFile > 0) {
|
||||
$selectedFile = $this->vault->nodes()
|
||||
->where('id', $this->selectedFile)
|
||||
->where('is_file', true)
|
||||
->first();
|
||||
|
||||
if (!$selectedFile) {
|
||||
$this->selectedFile = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->openFile($selectedFile);
|
||||
}
|
||||
$this->openFileId($this->selectedFile);
|
||||
}
|
||||
|
||||
public function openFile(VaultNode $node): void
|
||||
public function updatedSelectedFile(): void
|
||||
{
|
||||
$this->authorize('view', $node->vault);
|
||||
$this->openFileId($this->selectedFile);
|
||||
}
|
||||
|
||||
if (!$node->vault || !$node->vault->is($this->vault) || !$node->is_file) {
|
||||
public function openFileId(?int $fileId = null): void
|
||||
{
|
||||
if ($fileId === null) {
|
||||
$this->selectedFile = null;
|
||||
$this->setLastVisitedUrl();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setNode($node);
|
||||
$node = $this->vault->nodes()
|
||||
->where('id', $fileId)
|
||||
->where('is_file', true)
|
||||
->first();
|
||||
|
||||
if ($node->extension === 'md') {
|
||||
$this->dispatch('file-render-markup');
|
||||
if ($node === null) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$this->openFile($node);
|
||||
}
|
||||
|
||||
public function openFilePath(string $path): void
|
||||
@@ -99,8 +89,6 @@ final class Show extends Component
|
||||
#[On('file-refresh')]
|
||||
public function refreshFile(VaultNode $node): void
|
||||
{
|
||||
$this->authorize('view', $node->vault);
|
||||
|
||||
if ($node->id !== $this->selectedFile) {
|
||||
return;
|
||||
}
|
||||
@@ -184,4 +172,25 @@ final class Show extends Component
|
||||
$this->selectedFileUrl = new GetUrlFromVaultNode()->handle($node);
|
||||
$this->nodeForm->setNode($node);
|
||||
}
|
||||
|
||||
private function openFile(VaultNode $node): void
|
||||
{
|
||||
$this->setNode($node);
|
||||
$this->setLastVisitedUrl();
|
||||
|
||||
if ($node->extension === 'md') {
|
||||
$this->dispatch('file-render-markup');
|
||||
}
|
||||
}
|
||||
|
||||
private function setLastVisitedUrl(): void
|
||||
{
|
||||
/** @var User $currentUser */
|
||||
$currentUser = auth()->user();
|
||||
$currentUrl = route('vaults.show', ['vault' => $this->vault->id], false)
|
||||
. ($this->selectedFile !== null ? '?file=' . $this->selectedFile : '');
|
||||
$currentUser->update([
|
||||
'last_visited_url' => $currentUrl,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ final class UserFactory extends Factory
|
||||
'email_verified_at' => now(),
|
||||
'password' => self::$password ??= Hash::make('password'),
|
||||
'remember_token' => Str::random(10),
|
||||
'last_visited_url' => null,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ final class VaultFactory extends Factory
|
||||
return [
|
||||
'name' => fake()->words(3, true),
|
||||
'created_by' => User::factory(),
|
||||
'opened_at' => now(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('vaults', function (Blueprint $table): void {
|
||||
$table->dropColumn('opened_at');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table): void {
|
||||
$table->string('last_visited_url')->nullable();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -8,7 +8,7 @@
|
||||
<ul class="flex flex-col gap-2" wire:loading.class="opacity-50">
|
||||
@foreach ($nodes as $node)
|
||||
<li wire:key="{{ $node['id'] }}">
|
||||
<button type="button" wire:click="$parent.openFile({{ $node['id'] }}); modalOpen = false"
|
||||
<button type="button" wire:click="$parent.openFileId({{ $node['id'] }}); modalOpen = false"
|
||||
class="flex flex-col w-full gap-2 py-1 text-left hover:text-light-base-950 dark:hover:text-base-50">
|
||||
<span class="flex gap-2">
|
||||
<span class="overflow-hidden font-semibold whitespace-nowrap text-ellipsis"
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
Alpine.data('vault', () => ({
|
||||
isLeftPanelOpen: false,
|
||||
isRightPanelOpen: false,
|
||||
isEditMode: $wire.entangle('isEditMode'),
|
||||
isEditMode: Alpine.$persist(true),
|
||||
selectedFile: $wire.entangle('selectedFile'),
|
||||
selectedFileExtension: $wire.entangle('selectedFileExtension'),
|
||||
html: '',
|
||||
@@ -200,6 +200,10 @@
|
||||
});
|
||||
|
||||
this.isLeftPanelOpen = !this.isSmallDevice();
|
||||
|
||||
if (!this.isEditMode) {
|
||||
Alpine.nextTick(() => { this.markdownToHtml() });
|
||||
}
|
||||
},
|
||||
|
||||
isSmallDevice() {
|
||||
@@ -215,8 +219,8 @@
|
||||
this.isEditMode = !this.isEditMode;
|
||||
},
|
||||
|
||||
openFile(node) {
|
||||
$wire.openFile(node);
|
||||
openFile(nodeId) {
|
||||
$wire.openFileId(nodeId);
|
||||
|
||||
if (this.isSmallDevice()) {
|
||||
this.closePanels();
|
||||
|
||||
@@ -12,7 +12,6 @@ use App\Livewire\Auth\Register;
|
||||
use App\Livewire\Auth\ResetPassword;
|
||||
use App\Livewire\Dashboard\Index as DashboardIndex;
|
||||
use App\Livewire\Vault\Index as VaultIndex;
|
||||
use App\Livewire\Vault\Last as VaultLast;
|
||||
use App\Livewire\Vault\Show as VaultShow;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@@ -21,7 +20,6 @@ Route::middleware('auth')->group(function (): void {
|
||||
|
||||
Route::prefix('vaults')->group(function (): void {
|
||||
Route::get('/', VaultIndex::class)->name('vaults.index');
|
||||
Route::get('/last', VaultLast::class)->name('vaults.last');
|
||||
Route::get('/{vault}', VaultShow::class)->name('vaults.show');
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Livewire\Dashboard\Index;
|
||||
use App\Models\User;
|
||||
use Livewire\Livewire;
|
||||
|
||||
it('redirects guests to login page', function (): void {
|
||||
@@ -11,6 +12,9 @@ it('redirects guests to login page', function (): void {
|
||||
});
|
||||
|
||||
it('redirects users to vaults page', function (): void {
|
||||
Livewire::test(Index::class)
|
||||
->assertRedirect(route('vaults.last'));
|
||||
$user = User::factory()->hasVaults(1)->create();
|
||||
|
||||
Livewire::actingAs($user)
|
||||
->test(Index::class)
|
||||
->assertRedirect(route('vaults.index'));
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ it('successfully authenticates user', function (): void {
|
||||
->set('form.email', $user->email)
|
||||
->set('form.password', 'password')
|
||||
->call('send')
|
||||
->assertRedirect(route('vaults.last'));
|
||||
->assertRedirect(route('vaults.index'));
|
||||
});
|
||||
|
||||
it('gets rate limited', function (): void {
|
||||
|
||||
@@ -27,7 +27,7 @@ it('successfully authenticates user', function (): void {
|
||||
$availableProviders->shouldReceive('handle')->andReturn([OAuthProviders::GitHub]);
|
||||
|
||||
Livewire::test(OAuthLoginCallback::class, ['provider' => 'github'])
|
||||
->assertRedirect(route('vaults.last'));
|
||||
->assertRedirect(route('vaults.index'));
|
||||
});
|
||||
|
||||
it('fails to authenticate user', function (): void {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Livewire\Vault\Last;
|
||||
use App\Models\User;
|
||||
use Livewire\Livewire;
|
||||
|
||||
it('redirects to list of vaults', function (): void {
|
||||
$user = User::factory()->create();
|
||||
|
||||
Livewire::actingAs($user)
|
||||
->test(Last::class)
|
||||
->assertRedirect(route('vaults.index'));
|
||||
});
|
||||
|
||||
it('redirects to last opened vault', function (): void {
|
||||
$user = User::factory()->hasVaults(2)->create();
|
||||
$vault = $user->vaults()->first();
|
||||
$vault->update(['opened_at' => now()]);
|
||||
|
||||
Livewire::actingAs($user)
|
||||
->test(Last::class)
|
||||
->assertRedirect(route('vaults.show', ['vault' => $vault]));
|
||||
});
|
||||
@@ -40,7 +40,7 @@ it('does not open a non-existing file', function (): void {
|
||||
Livewire::actingAs($user)
|
||||
->withQueryParams(['file' => 500])
|
||||
->test(Show::class, ['vault' => $vault])
|
||||
->assertSet('selectedFile', null);
|
||||
->assertStatus(404);
|
||||
});
|
||||
|
||||
it('does not open a folder', function (): void {
|
||||
@@ -55,25 +55,8 @@ it('does not open a folder', function (): void {
|
||||
|
||||
Livewire::actingAs($user)
|
||||
->test(Show::class, ['vault' => $vault])
|
||||
->call('openFile', $node)
|
||||
->assertSet('selectedFile', null);
|
||||
});
|
||||
|
||||
it('resets edit mode when opening a file that is not a note', function (): void {
|
||||
$user = User::factory()->create()->first();
|
||||
$vault = new CreateVault()->handle($user, [
|
||||
'name' => fake()->words(3, true),
|
||||
]);
|
||||
$node = new CreateVaultNode()->handle($vault, [
|
||||
'is_file' => true,
|
||||
'name' => fake()->words(3, true),
|
||||
'extension' => 'jpg',
|
||||
]);
|
||||
|
||||
Livewire::actingAs($user)
|
||||
->test(Show::class, ['vault' => $vault])
|
||||
->call('openFile', $node)
|
||||
->assertSet('isEditMode', true);
|
||||
->call('openFileId', $node->id)
|
||||
->assertStatus(404);
|
||||
});
|
||||
|
||||
it('opens a file from the path', function (): void {
|
||||
@@ -131,7 +114,7 @@ it('does not open a file from a non-existent path', function (): void {
|
||||
Livewire::actingAs($user)
|
||||
->test(Show::class, ['vault' => $vault])
|
||||
->call('openFilePath', fake()->words(4, true))
|
||||
->assertSet('selectedFile', null);
|
||||
->assertStatus(404);
|
||||
});
|
||||
|
||||
it('refreshes an open file', function (): void {
|
||||
|
||||
@@ -16,6 +16,7 @@ test('to array', function (): void {
|
||||
'email_verified_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'last_visited_url',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ test('to array', function (): void {
|
||||
'id',
|
||||
'name',
|
||||
'created_by',
|
||||
'opened_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'templates_node_id',
|
||||
|
||||
Reference in New Issue
Block a user