Skip to content

Commit 4cfad5b

Browse files
committed
wip
1 parent 419a373 commit 4cfad5b

20 files changed

+996
-20
lines changed

app/Models/User.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function initials(): string
5656
return Str::of($this->name)
5757
->explode(' ')
5858
->take(2)
59-
->map(fn($word) => Str::substr($word, 0, 1))
59+
->map(fn ($word) => Str::substr($word, 0, 1))
6060
->implode('');
6161
}
6262
}

app/Providers/FortifyServiceProvider.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,11 @@
22

33
namespace App\Providers;
44

5-
use App\Actions\Fortify\CreateNewUser;
6-
use App\Actions\Fortify\ResetUserPassword;
7-
use App\Actions\Fortify\UpdateUserPassword;
8-
use App\Actions\Fortify\UpdateUserProfileInformation;
95
use Illuminate\Cache\RateLimiting\Limit;
106
use Illuminate\Http\Request;
117
use Illuminate\Support\Facades\RateLimiter;
128
use Illuminate\Support\ServiceProvider;
139
use Illuminate\Support\Str;
14-
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable;
1510
use Laravel\Fortify\Fortify;
1611

1712
class FortifyServiceProvider extends ServiceProvider
@@ -29,6 +24,8 @@ public function register(): void
2924
*/
3025
public function boot(): void
3126
{
27+
Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge'));
28+
3229
RateLimiter::for('two-factor', function (Request $request) {
3330
return Limit::perMinute(5)->by($request->session()->get('login.id'));
3431
});

config/fortify.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
|
7474
*/
7575

76-
'home' => '/home',
76+
'home' => '/dashbaord',
7777

7878
/*
7979
|--------------------------------------------------------------------------
@@ -144,11 +144,11 @@
144144
*/
145145

146146
'features' => [
147-
Features::registration(),
148-
Features::resetPasswords(),
149-
// Features::emailVerification(),
150-
Features::updateProfileInformation(),
151-
Features::updatePasswords(),
147+
// Features::registration(),
148+
// Features::resetPasswords(),
149+
// // Features::emailVerification(),
150+
// Features::updateProfileInformation(),
151+
// Features::updatePasswords(),
152152
Features::twoFactorAuthentication([
153153
'confirm' => true,
154154
'confirmPassword' => true,
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
@props([
2+
'digits' => 6,
3+
'eventCallback' => null,
4+
'name' => 'code',
5+
])
6+
7+
<div x-data="{
8+
totalInputDigits: @js($digits),
9+
callbackEventName: @js($eventCallback),
10+
11+
init() {
12+
this.$nextTick(() => {
13+
this.focusFirstInputField();
14+
this.updateHiddenInputValue();
15+
});
16+
},
17+
18+
focusFirstInputField() {
19+
this.getInputElementByIndex(1)?.focus();
20+
},
21+
22+
getInputElementByIndex(inputIndex) {
23+
return this.$refs[`input${inputIndex}`];
24+
},
25+
26+
handleKeydown(currentInputIndex, keyboardEvent) {
27+
const pressedKey = keyboardEvent.key;
28+
29+
if (this.isValidDigitKey(pressedKey)) {
30+
this.processDigitInput(currentInputIndex, keyboardEvent);
31+
} else if (pressedKey === 'Backspace') {
32+
this.processBackspaceInput(currentInputIndex, keyboardEvent);
33+
}
34+
},
35+
36+
isValidDigitKey(keyValue) {
37+
const parsedNumber = parseInt(keyValue);
38+
return !isNaN(parsedNumber) && parsedNumber >= 0 && parsedNumber <= 9;
39+
},
40+
41+
processDigitInput(currentInputIndex, keyboardEvent) {
42+
if (currentInputIndex === this.totalInputDigits) return;
43+
44+
keyboardEvent.preventDefault();
45+
keyboardEvent.stopPropagation();
46+
47+
const currentInputElement = this.getInputElementByIndex(currentInputIndex);
48+
const nextInputElement = this.getInputElementByIndex(currentInputIndex + 1);
49+
50+
currentInputElement.value = keyboardEvent.key;
51+
nextInputElement?.focus();
52+
53+
this.scheduleInputUpdate(currentInputIndex);
54+
},
55+
56+
processBackspaceInput(currentInputIndex, keyboardEvent) {
57+
keyboardEvent.preventDefault();
58+
keyboardEvent.stopPropagation();
59+
60+
const currentInputElement = this.getInputElementByIndex(currentInputIndex);
61+
const previousInputElement = this.getInputElementByIndex(currentInputIndex - 1);
62+
63+
if (currentInputElement.value !== '') {
64+
currentInputElement.value = '';
65+
} else if (currentInputIndex > 1) {
66+
previousInputElement.value = '';
67+
previousInputElement.focus();
68+
}
69+
70+
this.scheduleInputUpdate(currentInputIndex);
71+
},
72+
73+
handlePaste(pasteEvent) {
74+
pasteEvent.preventDefault();
75+
76+
const clipboardData = (pasteEvent.clipboardData || window.clipboardData).getData('text');
77+
const extractedDigits = clipboardData.split('').slice(0, this.totalInputDigits);
78+
79+
extractedDigits.forEach((digitValue, digitIndex) => {
80+
const inputElement = this.getInputElementByIndex(digitIndex + 1);
81+
if (inputElement && this.isValidDigitKey(digitValue)) {
82+
inputElement.value = digitValue;
83+
}
84+
});
85+
86+
const nextFocusIndex = Math.min(extractedDigits.length, this.totalInputDigits);
87+
this.getInputElementByIndex(nextFocusIndex)?.focus();
88+
89+
this.updateHiddenInputValue();
90+
},
91+
92+
sanitizeInput(inputElement) {
93+
inputElement.value = inputElement.value.replace(/[^0-9]/g, '').slice(0, 1);
94+
},
95+
96+
scheduleInputUpdate(currentInputIndex) {
97+
setTimeout(() => {
98+
this.updateHiddenInputValue();
99+
}, 100);
100+
},
101+
102+
generateCompleteCode() {
103+
return Array.from({ length: this.totalInputDigits }, (_, digitIndex) =>
104+
this.getInputElementByIndex(digitIndex + 1)?.value || ''
105+
).join('');
106+
},
107+
108+
updateHiddenInputValue() {
109+
const completeCode = this.generateCompleteCode();
110+
const hiddenInputElement = this.$refs.code;
111+
112+
if (hiddenInputElement) {
113+
hiddenInputElement.value = completeCode;
114+
hiddenInputElement.dispatchEvent(new Event('input', { bubbles: true }));
115+
}
116+
},
117+
118+
clearAllInputs() {
119+
for (let i = 1; i <= this.totalInputDigits; i++) {
120+
const input = this.getInputElementByIndex(i);
121+
if (input) input.value = '';
122+
}
123+
this.updateHiddenInputValue();
124+
this.focusFirstInputField();
125+
},
126+
}"
127+
x-init="init()"
128+
x-intersect.once="setTimeout(() => focusFirstInputField(), 100)"
129+
@focus-auth-2fa-auth-code.window="focusFirstInputField()"
130+
@clear-auth-2fa-auth-code.window="clearAllInputs()"
131+
class="relative">
132+
133+
<div class="flex items-center">
134+
@for ($x = 1; $x <= $digits; $x++)
135+
<input x-ref="input{{ $x }}"
136+
type="text"
137+
inputmode="numeric"
138+
pattern="[0-9]"
139+
maxlength="1"
140+
autocomplete="off"
141+
@paste="handlePaste"
142+
@keydown="handleKeydown({{ $x }}, $event)"
143+
@focus="$el.select()"
144+
@input="sanitizeInput($el)"
145+
class="flex h-10 w-10 items-center justify-center border border-zinc-300 bg-accent-foreground text-center text-sm font-medium text-accent-content transition-colors placeholder:text-zinc-500 focus:border-accent focus:border-2 focus:outline-none focus:relative focus:z-10 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-700 dark:focus:border-accent @if($x == 1) rounded-l-md @endif @if($x == $digits) rounded-r-md @endif @if($x > 1) -ml-px @endif" />
146+
@endfor
147+
</div>
148+
149+
<input {{ $attributes->except(['eventCallback', 'digits', 'name']) }}
150+
type="hidden"
151+
class="hidden"
152+
x-ref="code"
153+
name="{{ $name }}"
154+
minlength="{{ $digits }}"
155+
maxlength="{{ $digits }}" />
156+
</div>

resources/views/components/settings/layout.blade.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<flux:navlist>
44
<flux:navlist.item :href="route('settings.profile')" wire:navigate>{{ __('Profile') }}</flux:navlist.item>
55
<flux:navlist.item :href="route('settings.password')" wire:navigate>{{ __('Password') }}</flux:navlist.item>
6+
<flux:navlist.item :href="route('settings.two-factor')" wire:navigate>{{ __('Two-factor Authentication') }}</flux:navlist.item>
67
<flux:navlist.item :href="route('settings.appearance')" wire:navigate>{{ __('Appearance') }}</flux:navlist.item>
78
</flux:navlist>
89
</div>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{{-- Credit: Lucide (https://lucide.dev) --}}
2+
3+
@props([
4+
'variant' => 'outline',
5+
])
6+
7+
@php
8+
if ($variant === 'solid') {
9+
throw new \Exception('The "solid" variant is not supported in Lucide.');
10+
}
11+
12+
$classes = Flux::classes('shrink-0')
13+
->add(match($variant) {
14+
'outline' => '[:where(&)]:size-6',
15+
'solid' => '[:where(&)]:size-6',
16+
'mini' => '[:where(&)]:size-5',
17+
'micro' => '[:where(&)]:size-4',
18+
});
19+
20+
$strokeWidth = match ($variant) {
21+
'outline' => 2,
22+
'mini' => 2.25,
23+
'micro' => 2.5,
24+
};
25+
@endphp
26+
27+
<svg
28+
{{ $attributes->class($classes) }}
29+
data-flux-icon
30+
xmlns="http://www.w3.org/2000/svg"
31+
viewBox="0 0 24 24"
32+
fill="none"
33+
stroke="currentColor"
34+
stroke-width="{{ $strokeWidth }}"
35+
stroke-linecap="round"
36+
stroke-linejoin="round"
37+
aria-hidden="true"
38+
data-slot="icon"
39+
>
40+
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
41+
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
42+
</svg>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{{-- Credit: Lucide (https://lucide.dev) --}}
2+
3+
@props([
4+
'variant' => 'outline',
5+
])
6+
7+
@php
8+
if ($variant === 'solid') {
9+
throw new \Exception('The "solid" variant is not supported in Lucide.');
10+
}
11+
12+
$classes = Flux::classes('shrink-0')
13+
->add(match($variant) {
14+
'outline' => '[:where(&)]:size-6',
15+
'solid' => '[:where(&)]:size-6',
16+
'mini' => '[:where(&)]:size-5',
17+
'micro' => '[:where(&)]:size-4',
18+
});
19+
20+
$strokeWidth = match ($variant) {
21+
'outline' => 2,
22+
'mini' => 2.25,
23+
'micro' => 2.5,
24+
};
25+
@endphp
26+
27+
<svg
28+
{{ $attributes->class($classes) }}
29+
data-flux-icon
30+
xmlns="http://www.w3.org/2000/svg"
31+
viewBox="0 0 24 24"
32+
fill="none"
33+
stroke="currentColor"
34+
stroke-width="{{ $strokeWidth }}"
35+
stroke-linecap="round"
36+
stroke-linejoin="round"
37+
aria-hidden="true"
38+
data-slot="icon"
39+
>
40+
<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" />
41+
<path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" />
42+
<path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" />
43+
<path d="m2 2 20 20" />
44+
</svg>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{{-- Credit: Lucide (htt}ps://lucide.dev) --}}
2+
3+
@props([
4+
'variant' => 'outline',
5+
])
6+
7+
@php
8+
if ($variant === 'solid') {
9+
throw new \Exception('The "solid" variant is not supported in Lucide.');
10+
}
11+
12+
$classes = Flux::classes('shrink-0')
13+
->add(match($variant) {
14+
'outline' => '[:where(&)]:size-6',
15+
'solid' => '[:where(&)]:size-6',
16+
'mini' => '[:where(&)]:size-5',
17+
'micro' => '[:where(&)]:size-4',
18+
});
19+
20+
$strokeWidth = match ($variant) {
21+
'outline' => 2,
22+
'mini' => 2.25,
23+
'micro' => 2.5,
24+
};
25+
@endphp
26+
27+
<svg
28+
{{ $attributes->class($classes) }}
29+
data-flux-icon
30+
xmlns="http://www.w3.org/2000/svg"
31+
viewBox="0 0 24 24"
32+
fill="none"
33+
stroke="currentColor"
34+
stroke-width="{{ $strokeWidth }}"
35+
stroke-linecap="round"
36+
stroke-linejoin="round"
37+
aria-hidden="true"
38+
data-slot="icon"
39+
>
40+
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
41+
</svg>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{{-- Credit: Lucide (https://lucide.dev) --}}
2+
3+
@props([
4+
'variant' => 'outline',
5+
])
6+
7+
@php
8+
if ($variant === 'solid') {
9+
throw new \Exception('The "solid" variant is not supported in Lucide.');
10+
}
11+
12+
$classes = Flux::classes('shrink-0')->add(
13+
match ($variant) {
14+
'outline' => '[:where(&)]:size-6',
15+
'solid' => '[:where(&)]:size-6',
16+
'mini' => '[:where(&)]:size-5',
17+
'micro' => '[:where(&)]:size-4',
18+
},
19+
);
20+
21+
$strokeWidth = match ($variant) {
22+
'outline' => 2,
23+
'mini' => 2.25,
24+
'micro' => 2.5,
25+
};
26+
@endphp
27+
28+
<svg
29+
{{ $attributes->class($classes) }}
30+
data-flux-icon
31+
xmlns="http://www.w3.org/2000/svg"
32+
viewBox="0 0 24 24"
33+
fill="none"
34+
stroke="currentColor"
35+
stroke-width="{{ $strokeWidth }}"
36+
stroke-linecap="round"
37+
stroke-linejoin="round"
38+
aria-hidden="true"
39+
data-slot="icon"
40+
>
41+
<path d="m7 15 5 5 5-5" />
42+
<path d="m7 9 5-5 5 5" />
43+
</svg>

0 commit comments

Comments
 (0)