diff --git a/app/Helpers/Auth/AuthenticatesUsers.php b/app/Helpers/Auth/AuthenticatesUsers.php new file mode 100644 index 0000000..98b3420 --- /dev/null +++ b/app/Helpers/Auth/AuthenticatesUsers.php @@ -0,0 +1,207 @@ +validateLogin($request); + + // If the class is using the ThrottlesLogins trait, we can automatically throttle + // the login attempts for this application. We'll key this by the username and + // the IP address of the client making these requests into this application. + if (method_exists($this, 'hasTooManyLoginAttempts') && + $this->hasTooManyLoginAttempts($request)) { + $this->fireLockoutEvent($request); + + return $this->sendLockoutResponse($request); + } + + if ($this->attemptLogin($request)) { + if ($request->hasSession()) { + $request->session()->put('auth.password_confirmed_at', time()); + } + + return $this->sendLoginResponse($request); + } + + // If the login attempt was unsuccessful we will increment the number of attempts + // to log in and redirect the user back to the login form. Of course, when this + // user surpasses their maximum number of attempts they will get locked out. + $this->incrementLoginAttempts($request); + + return $this->sendFailedLoginResponse(); + } + + /** + * Validate the user login request. + * + * @param Request $request + * @return void + * + * @throws ValidationException + */ + protected function validateLogin(Request $request): void + { + $request->validate([ + $this->username() => 'required|string', + 'password' => 'required|string', + ]); + } + + /** + * Attempt to log the user into the application. + * + * @param Request $request + * @return bool + */ + protected function attemptLogin(Request $request): bool + { + return $this->guard()->attempt( + $this->credentials($request), $request->boolean('remember') + ); + } + + /** + * Get the needed authorization credentials from the request. + * + * @param Request $request + * @return array + */ + protected function credentials(Request $request): array + { + return $request->only($this->username(), 'password'); + } + + /** + * Send the response after the user was authenticated. + * + * @param Request $request + * @return JsonResponse|RedirectResponse + */ + protected function sendLoginResponse(Request $request) + { + $request->session()->regenerate(); + + $this->clearLoginAttempts($request); + + // if ($response = $this->authenticated($request, $this->guard()->user())) { + // return $response; + // } + + return $request->wantsJson() + ? new JsonResponse([], 204) + : redirect()->intended($this->redirectPath()); + } + + /** + * The user has been authenticated. + * + * @param Request $request + * @param mixed $user + * @return void + */ + protected function authenticated(Request $request, mixed $user): void + { + // + } + + /** + * Get the failed login response instance. + * + * + * @return Response + * + * @throws ValidationException + */ + protected function sendFailedLoginResponse(): Response + { + throw ValidationException::withMessages([ + $this->username() => [trans('auth.failed')], + ]); + } + + /** + * Get the login username to be used by the controller. + * + * @return string + */ + public function username(): string + { + return 'email'; + } + + /** + * Log the user out of the application. + * + * @param Request $request + * @return RedirectResponse|JsonResponse + */ + public function logout(Request $request): JsonResponse|RedirectResponse + { + $this->guard()->logout(); + + $request->session()->invalidate(); + + $request->session()->regenerateToken(); + + // if ($response = $this->loggedOut($request)) { + // return $response; + // } + + return $request->wantsJson() + ? new JsonResponse([], 204) + : redirect('/'); + } + + /** + * The user has logged out of the application. + * + * @param Request $request + * @return void + */ + protected function loggedOut(Request $request): void + { + // + } + + /** + * Get the guard to be used during authentication. + * + * @return StatefulGuard + */ + protected function guard(): StatefulGuard + { + return Auth::guard(); + } +} diff --git a/app/Helpers/Auth/ConfirmsPasswords.php b/app/Helpers/Auth/ConfirmsPasswords.php new file mode 100644 index 0000000..c31d433 --- /dev/null +++ b/app/Helpers/Auth/ConfirmsPasswords.php @@ -0,0 +1,73 @@ +validate($this->rules(), $this->validationErrorMessages()); + + $this->resetPasswordConfirmationTimeout($request); + + return $request->wantsJson() + ? new JsonResponse([], 204) + : redirect()->intended($this->redirectPath()); + } + + /** + * Reset the password confirmation timeout. + * + * @param Request $request + * @return void + */ + protected function resetPasswordConfirmationTimeout(Request $request): void + { + $request->session()->put('auth.password_confirmed_at', time()); + } + + /** + * Get the password confirmation validation rules. + * + * @return array + */ + protected function rules(): array + { + return [ + 'password' => 'required|current_password:web', + ]; + } + + /** + * Get the password confirmation validation error messages. + * + * @return array + */ + protected function validationErrorMessages(): array + { + return []; + } +} diff --git a/app/Helpers/Auth/RedirectsUsers.php b/app/Helpers/Auth/RedirectsUsers.php new file mode 100644 index 0000000..76b79ad --- /dev/null +++ b/app/Helpers/Auth/RedirectsUsers.php @@ -0,0 +1,20 @@ +redirectTo(); + } + + return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home'; + } +} diff --git a/app/Helpers/Auth/RegistersUsers.php b/app/Helpers/Auth/RegistersUsers.php new file mode 100644 index 0000000..2129b92 --- /dev/null +++ b/app/Helpers/Auth/RegistersUsers.php @@ -0,0 +1,71 @@ +validator($request->all())->validate(); + + event(new Registered($user = $this->create($request->all()))); + + $this->guard()->login($user); + + // if ($response = $this->registered($request, $user)) { + // return $response; + // } + + return $request->wantsJson() + ? new JsonResponse([], 201) + : redirect($this->redirectPath()); + } + + /** + * Get the guard to be used during registration. + * + * @return StatefulGuard + */ + protected function guard(): StatefulGuard + { + return Auth::guard(); + } + + /** + * The user has been registered. + * + * @param Request $request + * @param mixed $user + * @return void + */ + protected function registered(Request $request, mixed $user): void + { + // + } +} diff --git a/app/Helpers/Auth/ResetsPasswords.php b/app/Helpers/Auth/ResetsPasswords.php new file mode 100644 index 0000000..946f5f4 --- /dev/null +++ b/app/Helpers/Auth/ResetsPasswords.php @@ -0,0 +1,203 @@ +route()->parameter('token'); + + return view('auth.passwords.reset')->with( + ['token' => $token, 'email' => $request->email] + ); + } + + /** + * Reset the given user's password. + * + * @param Request $request + * @return RedirectResponse|JsonResponse + */ + public function reset(Request $request): JsonResponse|RedirectResponse + { + $request->validate($this->rules(), $this->validationErrorMessages()); + + // 如果当前用户已登录,那么将 email 设置为当前用户的 email + // if (Auth::guard('web')->check()) { + // $request->merge(['email' => Auth::user()->email]); + // } + + // Here we will attempt to reset the user's password. If it is successful we + // will update the password on an actual user model and persist it to the + // database. Otherwise, we will parse the error and return the response. + $response = $this->broker()->reset( + $this->credentials($request), function ($user, $password) { + $this->resetPassword($user, $password); + } + ); + + // If the password was successfully reset, we will redirect the user back to + // the application's home authenticated view. If there is an error we can + // redirect them back to where they came from with their error message. + return $response == Password::PASSWORD_RESET + ? $this->sendResetResponse($request, $response) + : $this->sendResetFailedResponse($request, $response); + } + + /** + * Get the password reset validation rules. + * + * @return array + */ + protected function rules(): array + { + return [ + 'token' => 'required', + 'email' => 'required|email', + 'password' => ['required', 'confirmed', Rules\Password::defaults()], + ]; + } + + /** + * Get the password reset validation error messages. + * + * @return array + */ + protected function validationErrorMessages(): array + { + return []; + } + + /** + * Get the password reset credentials from the request. + * + * @param Request $request + * @return array + */ + protected function credentials(Request $request): array + { + return $request->only( + 'email', 'password', 'password_confirmation', 'token' + ); + } + + /** + * Reset the given user's password. + * + * @param CanResetPassword|User $user + * @param string $password + * @return void + */ + protected function resetPassword(CanResetPassword|User $user, string $password): void + { + $this->setUserPassword($user, $password); + + $user->setRememberToken(Str::random(60)); + + $user->save(); + + event(new PasswordReset($user)); + + $this->guard()->login($user); + } + + /** + * Set the user's password. + * + * @param CanResetPassword $user + * @param string $password + * @return void + */ + protected function setUserPassword(CanResetPassword $user, string $password): void + { + // if it has password field + if (isset($user->password)) { + $user->password = Hash::make($password); + } + } + + /** + * Get the response for a successful password reset. + * + * @param Request $request + * @param string $response + * @return RedirectResponse|JsonResponse + */ + protected function sendResetResponse(Request $request, string $response): JsonResponse|RedirectResponse + { + if ($request->wantsJson()) { + return new JsonResponse(['message' => trans($response)], 200); + } + + return redirect($this->redirectPath()) + ->with('status', trans($response)); + } + + /** + * Get the response for a failed password reset. + * + * @param Request $request + * @param string $response + * @return RedirectResponse + */ + protected function sendResetFailedResponse(Request $request, string $response): RedirectResponse + { + if ($request->wantsJson()) { + throw ValidationException::withMessages([ + 'email' => [trans($response)], + ]); + } + + return redirect()->back() + ->withInput($request->only('email')) + ->withErrors(['email' => trans($response)]); + } + + /** + * Get the broker to be used during password reset. + * + * @return PasswordBroker + */ + public function broker(): PasswordBroker + { + return Password::broker(); + } + + /** + * Get the guard to be used during password reset. + * + * @return StatefulGuard + */ + protected function guard(): StatefulGuard + { + return Auth::guard(); + } +} diff --git a/app/Helpers/Auth/SendsPasswordResetEmails.php b/app/Helpers/Auth/SendsPasswordResetEmails.php new file mode 100644 index 0000000..410448c --- /dev/null +++ b/app/Helpers/Auth/SendsPasswordResetEmails.php @@ -0,0 +1,114 @@ +validateEmail($request); + + // We will send the password reset link to this user. Once we have attempted + // to send the link, we will examine the response then see the message we + // need to show to the user. Finally, we'll send out a proper response. + $response = $this->broker()->sendResetLink( + $this->credentials($request) + ); + + return $response == Password::RESET_LINK_SENT + ? $this->sendResetLinkResponse($request, $response) + : $this->sendResetLinkFailedResponse($request, $response); + } + + /** + * Validate the email for the given request. + * + * @param Request $request + * @return void + */ + protected function validateEmail(Request $request): void + { + $request->validate(['email' => 'required|email']); + } + + /** + * Get the needed authentication credentials from the request. + * + * @param Request $request + * @return array + */ + protected function credentials(Request $request): array + { + return $request->only('email'); + } + + /** + * Get the response for a successful password reset link. + * + * @param Request $request + * @param string $response + * @return RedirectResponse|JsonResponse + */ + protected function sendResetLinkResponse(Request $request, string $response): JsonResponse|RedirectResponse + { + return $request->wantsJson() + ? new JsonResponse(['message' => trans($response)], 200) + : back()->with('status', trans($response)); + } + + /** + * Get the response for a failed password reset link. + * + * @param Request $request + * @param string $response + * @return RedirectResponse + * + * @throws ValidationException + */ + protected function sendResetLinkFailedResponse(Request $request, string $response): RedirectResponse + { + if ($request->wantsJson()) { + throw ValidationException::withMessages([ + 'email' => [trans($response)], + ]); + } + + return back() + ->withInput($request->only('email')) + ->withErrors(['email' => trans($response)]); + } + + /** + * Get the broker to be used during password reset. + * + * @return PasswordBroker + */ + public function broker(): PasswordBroker + { + return Password::broker(); + } +} diff --git a/app/Helpers/Auth/ThrottlesLogins.php b/app/Helpers/Auth/ThrottlesLogins.php new file mode 100644 index 0000000..a5110f7 --- /dev/null +++ b/app/Helpers/Auth/ThrottlesLogins.php @@ -0,0 +1,124 @@ +limiter()->tooManyAttempts( + $this->throttleKey($request), $this->maxAttempts() + ); + } + + /** + * Increment the login attempts for the user. + * + * @param Request $request + * @return void + */ + protected function incrementLoginAttempts(Request $request): void + { + $this->limiter()->hit( + $this->throttleKey($request), $this->decayMinutes() * 60 + ); + } + + /** + * Redirect the user after determining they are locked out. + * + * @param Request $request + * @return ResponseAlias + * + * @throws ValidationException + */ + protected function sendLockoutResponse(Request $request): ResponseAlias + { + $seconds = $this->limiter()->availableIn( + $this->throttleKey($request) + ); + + throw ValidationException::withMessages([ + $this->username() => [trans('auth.throttle', [ + 'seconds' => $seconds, + 'minutes' => ceil($seconds / 60), + ])], + ])->status(ResponseAlias::HTTP_TOO_MANY_REQUESTS); + } + + /** + * Clear the login locks for the given user credentials. + * + * @param Request $request + * @return void + */ + protected function clearLoginAttempts(Request $request): void + { + $this->limiter()->clear($this->throttleKey($request)); + } + + /** + * Fire an event when a lockout occurs. + * + * @param Request $request + * @return void + */ + protected function fireLockoutEvent(Request $request): void + { + event(new Lockout($request)); + } + + /** + * Get the throttle key for the given request. + * + * @param Request $request + * @return string + */ + protected function throttleKey(Request $request): string + { + return Str::transliterate(Str::lower($request->input($this->username())).'|'.$request->ip()); + } + + /** + * Get the rate limiter instance. + * + * @return RateLimiter + */ + protected function limiter(): RateLimiter + { + return app(RateLimiter::class); + } + + /** + * Get the maximum number of attempts to allow. + * + * @return int + */ + public function maxAttempts(): int + { + return property_exists($this, 'maxAttempts') ? $this->maxAttempts : 5; + } + + /** + * Get the number of minutes to throttle for. + * + * @return int + */ + public function decayMinutes(): int + { + return property_exists($this, 'decayMinutes') ? $this->decayMinutes : 1; + } +} diff --git a/app/Helpers/Auth/VerifiesEmails.php b/app/Helpers/Auth/VerifiesEmails.php new file mode 100644 index 0000000..a7e0e8d --- /dev/null +++ b/app/Helpers/Auth/VerifiesEmails.php @@ -0,0 +1,97 @@ +user()->hasVerifiedEmail() + ? redirect($this->redirectPath()) + : view('auth.verify'); + } + + /** + * Mark the authenticated user's email address as verified. + * + * @param Request $request + * @return JsonResponse|RedirectResponse + * + * @throws AuthorizationException + */ + public function verify(Request $request): JsonResponse|RedirectResponse + { + if (! hash_equals((string) $request->route('id'), (string) $request->user()->getKey())) { + throw new AuthorizationException; + } + + if (! hash_equals((string) $request->route('hash'), sha1($request->user()->getEmailForVerification()))) { + throw new AuthorizationException; + } + + if ($request->user()->hasVerifiedEmail()) { + return $request->wantsJson() + ? new JsonResponse([], 204) + : redirect($this->redirectPath()); + } + + if ($request->user()->markEmailAsVerified()) { + event(new Verified($request->user())); + } + + // if ($response = $this->verified($request)) { + // return $response; + // } + + return $request->wantsJson() + ? new JsonResponse([], 204) + : redirect($this->redirectPath())->with('verified', true); + } + + /** + * The user has been verified. + * + * @param Request $request + * @return void + */ + protected function verified(Request $request): void + { + // + } + + /** + * Resend the email verification notification. + * + * @param Request $request + * @return JsonResponse|RedirectResponse + */ + public function resend(Request $request): JsonResponse|RedirectResponse + { + if ($request->user()->hasVerifiedEmail()) { + return $request->wantsJson() + ? new JsonResponse([], 204) + : redirect($this->redirectPath()); + } + + $request->user()->sendEmailVerificationNotification(); + + return $request->wantsJson() + ? new JsonResponse([], 202) + : back()->with('resent', true); + } +} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 159346f..589fdbc 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -148,4 +148,17 @@ public function update(Request $request, User $user): RedirectResponse return back()->with('success', '已完成所有更改。'); } + + /** + * Remove the specified resource from storage. + * + * @param User $user + * @return RedirectResponse + */ + public function destroy(User $user): RedirectResponse + { + $user->delete(); + + return redirect()->route('admin.users.index')->with('success', '已删除此用户。'); + } } diff --git a/app/Http/Controllers/Web/Auth/ConfirmPasswordController.php b/app/Http/Controllers/Web/Auth/ConfirmPasswordController.php new file mode 100644 index 0000000..32db48f --- /dev/null +++ b/app/Http/Controllers/Web/Auth/ConfirmPasswordController.php @@ -0,0 +1,40 @@ +middleware('auth'); + } +} diff --git a/app/Http/Controllers/Web/Auth/ForgotPasswordController.php b/app/Http/Controllers/Web/Auth/ForgotPasswordController.php new file mode 100644 index 0000000..c1eb0b2 --- /dev/null +++ b/app/Http/Controllers/Web/Auth/ForgotPasswordController.php @@ -0,0 +1,27 @@ +middleware('throttle:2,1')->only('sendResetLinkEmail'); + } +} diff --git a/app/Http/Controllers/Web/Auth/LoginController.php b/app/Http/Controllers/Web/Auth/LoginController.php new file mode 100644 index 0000000..990a46c --- /dev/null +++ b/app/Http/Controllers/Web/Auth/LoginController.php @@ -0,0 +1,40 @@ +middleware('guest')->except('logout'); + } +} diff --git a/app/Http/Controllers/Web/Auth/RegisterController.php b/app/Http/Controllers/Web/Auth/RegisterController.php new file mode 100644 index 0000000..c02e0db --- /dev/null +++ b/app/Http/Controllers/Web/Auth/RegisterController.php @@ -0,0 +1,73 @@ +middleware('guest'); + } + + /** + * Get a validator for an incoming registration request. + * + * @param array $data + * @return \Illuminate\Contracts\Validation\Validator + */ + protected function validator(array $data) + { + return Validator::make($data, [ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], + 'password' => ['required', 'string', 'min:8', 'confirmed'], + ]); + } + + /** + * Create a new user instance after a valid registration. + * + * @param array $data + * @return \App\Models\User + */ + protected function create(array $data) + { + return User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), + ]); + } +} diff --git a/app/Http/Controllers/Web/Auth/ResetPasswordController.php b/app/Http/Controllers/Web/Auth/ResetPasswordController.php new file mode 100644 index 0000000..b5f0ff2 --- /dev/null +++ b/app/Http/Controllers/Web/Auth/ResetPasswordController.php @@ -0,0 +1,30 @@ +middleware('auth'); + $this->middleware('signed')->only('verify'); + $this->middleware('throttle:2,1')->only('resend'); + $this->middleware('throttle:6,1')->only('verify'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 799f13a..e28566b 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,18 +2,19 @@ namespace App\Models; -// use Illuminate\Contracts\Auth\MustVerifyEmail; use App\Exceptions\User\BalanceNotEnoughException; use Carbon\Exceptions\InvalidFormatException; use GeneaLabs\LaravelModelCaching\CachedBuilder; use GeneaLabs\LaravelModelCaching\Traits\Cachable; -use Illuminate\Auth\MustVerifyEmail; +use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Prunable; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Facades\Cache; @@ -21,9 +22,9 @@ use Illuminate\Support\Str; use Laravel\Sanctum\HasApiTokens; -class User extends Authenticatable +class User extends Authenticatable implements MustVerifyEmail { - use HasApiTokens, HasFactory, Notifiable, Cachable, MustVerifyEmail; + use HasApiTokens, HasFactory, Notifiable, SoftDeletes, Prunable, Cachable; public array $publics = [ 'id', @@ -114,6 +115,11 @@ protected static function boot() } } }); + + static::deleting(function (self $user) { + $user->tokens()->delete(); + $user->hosts()->update(['status' => 'suspended', 'suspended_at' => now()]); + }); } public function hosts(): HasMany @@ -167,6 +173,11 @@ public function selectPublic(): self|Builder|CachedBuilder return $this->select($this->publics); } + public function prunable() + { + return static::where('deleted_at', '<=', now()->subWeek()); + } + public function startTransfer(self $to, string $amount, string|null $description) { $description_from = "转账给 $to->name($to->email)"; diff --git a/composer.json b/composer.json index e34901e..02438f3 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,9 @@ "beyondcode/laravel-query-detector": "^1.6", "fakerphp/faker": "^1.9.1", "jetbrains/phpstorm-attributes": "^1.0", + "laravel-lang/attributes": "^2.1", + "laravel-lang/lang": "^12.14", + "laravel-lang/publisher": "^14.5", "laravel/pint": "^1.0", "laravel/sail": "^1.0.1", "laravel/telescope": "^4.9", diff --git a/composer.lock b/composer.lock index ec13b90..0b796b8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7cdf1f58081bceb75c91bcacfc8d51a8", + "content-hash": "bb7e2da0ce001f8fb09ec5a5924405bf", "packages": [ { "name": "bacon/bacon-qr-code", @@ -9034,6 +9034,58 @@ } ], "packages-dev": [ + { + "name": "archtechx/enums", + "version": "v0.3.1", + "source": { + "type": "git", + "url": "https://github.com/archtechx/enums.git", + "reference": "373a86a16e7233a56dd942d422af2cc59a2becb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/archtechx/enums/zipball/373a86a16e7233a56dd942d422af2cc59a2becb3", + "reference": "373a86a16e7233a56dd942d422af2cc59a2becb3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "nunomaduro/larastan": "^1.0|^2.0", + "orchestra/testbench": "^6.9|^7.0", + "pestphp/pest": "^1.2", + "pestphp/pest-plugin-laravel": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ArchTech\\Enums\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Samuel Štancl", + "email": "samuel@archte.ch" + } + ], + "description": "Helpers for making PHP enums more lovable.", + "support": { + "issues": "https://github.com/archtechx/enums/issues", + "source": "https://github.com/archtechx/enums/tree/v0.3.1" + }, + "time": "2022-08-24T22:27:44+00:00" + }, { "name": "beyondcode/laravel-query-detector", "version": "1.6.0", @@ -9176,6 +9228,278 @@ ], "time": "2022-12-30T00:15:36+00:00" }, + { + "name": "dragon-code/contracts", + "version": "v2.18.0", + "source": { + "type": "git", + "url": "https://github.com/TheDragonCode/contracts.git", + "reference": "27e5a9f27b3e6e5f1c184e55076b26101f3e5652" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheDragonCode/contracts/zipball/27e5a9f27b3e6e5f1c184e55076b26101f3e5652", + "reference": "27e5a9f27b3e6e5f1c184e55076b26101f3e5652", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-message": "^1.0.1", + "symfony/http-kernel": "^4.0 || ^5.0 || ^6.0", + "symfony/polyfill-php80": "^1.23" + }, + "conflict": { + "andrey-helldar/contracts": "*" + }, + "require-dev": { + "illuminate/database": "^8.0", + "phpdocumentor/reflection-docblock": "^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "DragonCode\\Contracts\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@ai-rus.com" + } + ], + "description": "A set of contracts for any project", + "keywords": [ + "contracts", + "interfaces" + ], + "support": { + "source": "https://github.com/TheDragonCode/contracts" + }, + "funding": [ + { + "url": "https://paypal.me/helldar", + "type": "custom" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "custom" + }, + { + "url": "https://opencollective.com/dragon-code", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/andrey_helldar", + "type": "patreon" + } + ], + "time": "2022-03-01T13:27:09+00:00" + }, + { + "name": "dragon-code/pretty-array", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/TheDragonCode/pretty-array.git", + "reference": "385ca81ee7e9a65fb5696692c173a1234fc2ad7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheDragonCode/pretty-array/zipball/385ca81ee7e9a65fb5696692c173a1234fc2ad7d", + "reference": "385ca81ee7e9a65fb5696692c173a1234fc2ad7d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "dragon-code/contracts": "^2.6", + "dragon-code/support": "^6.0", + "ext-dom": "*", + "ext-mbstring": "*", + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "symfony/thanks": "Give thanks (in the form of a GitHub) to your fellow PHP package maintainers" + }, + "type": "library", + "autoload": { + "psr-4": { + "DragonCode\\PrettyArray\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@ai-rus.com" + } + ], + "description": "Simple conversion of an array to a pretty view", + "keywords": [ + "andrey helldar", + "array", + "dragon", + "dragon code", + "pretty", + "pretty array" + ], + "support": { + "issues": "https://github.com/TheDragonCode/pretty-array/issues", + "source": "https://github.com/TheDragonCode/pretty-array" + }, + "funding": [ + { + "url": "https://paypal.me/helldar", + "type": "custom" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "custom" + }, + { + "url": "https://github.com/TheDragonCode", + "type": "github" + }, + { + "url": "https://opencollective.com/dragon-code", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/andrey_helldar", + "type": "patreon" + } + ], + "time": "2022-04-21T12:22:41+00:00" + }, + { + "name": "dragon-code/support", + "version": "v6.8.0", + "source": { + "type": "git", + "url": "https://github.com/TheDragonCode/support.git", + "reference": "7be34dc2ab76029b1ee8203717747ab71f0ee8bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheDragonCode/support/zipball/7be34dc2ab76029b1ee8203717747ab71f0ee8bd", + "reference": "7be34dc2ab76029b1ee8203717747ab71f0ee8bd", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "dragon-code/contracts": "^2.18", + "ext-bcmath": "*", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.0", + "psr/http-message": "^1.0.1", + "symfony/polyfill-php81": "^1.25", + "voku/portable-ascii": "^1.4.8 || ^2.0.1" + }, + "conflict": { + "andrey-helldar/support": "*" + }, + "require-dev": { + "illuminate/contracts": "^9.7", + "phpunit/phpunit": "^9.5", + "symfony/var-dumper": "^6.0" + }, + "suggest": { + "dragon-code/laravel-support": "Various helper files for the Laravel and Lumen frameworks", + "symfony/thanks": "Give thanks (in the form of a GitHub) to your fellow PHP package maintainers" + }, + "type": "library", + "extra": { + "dragon-code": { + "docs-generator": { + "preview": { + "brand": "php", + "vendor": "The Dragon Code" + } + } + } + }, + "autoload": { + "psr-4": { + "DragonCode\\Support\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://github.com/andrey-helldar" + } + ], + "description": "Support package is a collection of helpers and tools for any project.", + "keywords": [ + "dragon", + "dragon-code", + "framework", + "helper", + "helpers", + "laravel", + "php", + "support", + "symfony", + "yii", + "yii2" + ], + "support": { + "issues": "https://github.com/TheDragonCode/support/issues", + "source": "https://github.com/TheDragonCode/support" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://github.com/sponsors/TheDragonCode", + "type": "github" + }, + { + "url": "https://opencollective.com/dragon-code", + "type": "open_collective" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2022-11-16T22:35:42+00:00" + }, { "name": "fakerphp/faker", "version": "v1.21.0", @@ -9432,6 +9756,286 @@ }, "time": "2020-11-17T11:09:47+00:00" }, + { + "name": "laravel-lang/attributes", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/attributes.git", + "reference": "fd50ffcee9a472df1bf6567f4670de69d6adbc92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/attributes/zipball/fd50ffcee9a472df1bf6567f4670de69d6adbc92", + "reference": "fd50ffcee9a472df1bf6567f4670de69d6adbc92", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "conflict": { + "laravel-lang/publisher": "<14.0.0" + }, + "require-dev": { + "laravel-lang/publisher": "^14.0", + "laravel-lang/status-generator": "^1.3", + "php": "^8.1", + "phpunit/phpunit": "^9.5", + "symfony/var-dumper": "^5.0 || ^6.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Attributes\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\Attributes\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://github.com/Laravel-Lang" + } + ], + "description": "List of 78 languages for form field names", + "keywords": [ + "attributes", + "fields", + "form", + "lang", + "languages", + "laravel", + "messages", + "translations", + "validation" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/attributes/issues", + "source": "https://github.com/Laravel-Lang/attributes/tree/v2.1.1" + }, + "funding": [ + { + "url": "https://opencollective.com/laravel-lang", + "type": "open_collective" + } + ], + "time": "2023-01-26T07:41:39+00:00" + }, + { + "name": "laravel-lang/lang", + "version": "12.14.2", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/lang.git", + "reference": "7168d4bb3008d6d3c5c2802293dbfd42fcd9d9a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/lang/zipball/7168d4bb3008d6d3c5c2802293dbfd42fcd9d9a2", + "reference": "7168d4bb3008d6d3c5c2802293dbfd42fcd9d9a2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*" + }, + "conflict": { + "laravel-lang/publisher": "<14.0" + }, + "require-dev": { + "laravel-lang/publisher": "^14.0", + "laravel-lang/status-generator": "^1.11.1", + "php": "^8.1", + "phpunit/phpunit": "^9.5", + "symfony/var-dumper": "^5.0 || ^6.0" + }, + "suggest": { + "arcanedev/laravel-lang": "Translations manager and checker for Laravel", + "laravel-lang/attributes": "Translations for field names of the forms", + "laravel-lang/http-statuses": "Translations for HTTP statuses", + "laravel-lang/publisher": "Easy installation and update of translation files for your project" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Lang\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\Lang\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laravel-Lang Team", + "homepage": "https://github.com/Laravel-Lang" + } + ], + "description": "Languages for Laravel", + "keywords": [ + "lang", + "languages", + "laravel", + "lpm" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/lang/issues", + "source": "https://github.com/Laravel-Lang/lang" + }, + "funding": [ + { + "url": "https://opencollective.com/laravel-lang", + "type": "open_collective" + } + ], + "time": "2023-01-30T10:43:55+00:00" + }, + { + "name": "laravel-lang/publisher", + "version": "v14.5.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/publisher.git", + "reference": "2c1a74fe5f8f76a2d2b7802076a7c35a3f89fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/publisher/zipball/2c1a74fe5f8f76a2d2b7802076a7c35a3f89fcf4", + "reference": "2c1a74fe5f8f76a2d2b7802076a7c35a3f89fcf4", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "archtechx/enums": "^0.3", + "composer/semver": "^3.3", + "dragon-code/pretty-array": "^4.0", + "dragon-code/support": "^6.3", + "ext-json": "*", + "illuminate/console": "^8.79 || ^9.18", + "illuminate/support": "^8.79 || ^9.18", + "league/commonmark": "^2.3", + "league/config": "^1.2", + "php": "^8.1" + }, + "conflict": { + "laravel-lang/attributes": "<2.0", + "laravel-lang/http-statuses": "<3.0", + "laravel-lang/lang": "<11.0" + }, + "require-dev": { + "laravel-lang/json-fallback-hotfix": "^1.0", + "orchestra/testbench": "^6.0 || ^7.0", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.4", + "symfony/var-dumper": "^5.0 || ^6.0" + }, + "suggest": { + "laravel-lang/attributes": "List of 78 languages for form field names", + "laravel-lang/http-statuses": "List of 78 languages for HTTP statuses", + "laravel-lang/lang": "List of 78 languages for Laravel Framework, Jetstream, Fortify, Breeze, Cashier, Nova, Spark and UI." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Publisher\\ServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "helper.php" + ], + "psr-4": { + "LaravelLang\\Publisher\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://github.com/Laravel-Lang" + } + ], + "description": "Publisher lang files for the Laravel and Lumen Frameworks, Jetstream, Fortify, Cashier, Spark and Nova from Laravel-Lang/lang", + "keywords": [ + "breeze", + "cashier", + "fortify", + "framework", + "i18n", + "jetstream", + "lang", + "languages", + "laravel", + "locale", + "locales", + "localization", + "lpm", + "lumen", + "nova", + "publisher", + "spark", + "trans", + "translations", + "validations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/publisher/issues", + "source": "https://github.com/Laravel-Lang/publisher" + }, + "funding": [ + { + "url": "https://opencollective.com/laravel-lang", + "type": "open_collective" + } + ], + "time": "2023-01-10T19:26:44+00:00" + }, { "name": "laravel/pint", "version": "v1.4.0", @@ -11904,6 +12508,91 @@ ], "time": "2023-01-03T19:28:04+00:00" }, + { + "name": "symfony/polyfill-php81", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", + "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, { "name": "theseer/tokenizer", "version": "1.2.1", diff --git a/lang/zh_CN.json b/lang/zh_CN.json index 4820f96..a054d97 100644 --- a/lang/zh_CN.json +++ b/lang/zh_CN.json @@ -1,25 +1,39 @@ { "(and :count more error)": "(还有 :count 个错误)", "(and :count more errors)": "(还有 :count 个错误)", + "A fresh verification link has been sent to your email address.": "一个新的验证链接已发送到您的电子邮件地址。", "All rights reserved.": "版权所有。", + "Before proceeding, please check your email for a verification link.": "在继续之前,请检查您的电子邮件以获取验证链接。", + "click here to request another": "单击此处请求另一个", + "Confirm Password": "确认密码", + "Dashboard": "控制面板", + "Email Address": "电子邮件地址", "Forbidden": "访问被拒绝", + "Forgot Your Password?": "忘记密码?", "Go to page :page": "前往第 :page 页", "Hello!": "您好!", "If you did not create an account, no further action is required.": "如果您未注册帐号,请忽略此邮件。", + "If you did not receive the email": "如果您没有收到电子邮件", "If you did not request a password reset, no further action is required.": "如果您未申请重设密码,请忽略此邮件。", "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "如果您单击「:actionText」按钮时遇到问题,请复制下方链接到浏览器中访问:", "Login": "登录", "Logout": "登出", + "Name": "姓名", "Not Found": "页面不存在", "of": "于", "Page Expired": "页面会话已超时", "Pagination Navigation": "分页导航", + "Password": "密码", + "Payment Required": "需要付款", "Please click the button below to verify your email address.": "请点击下面按钮验证您的 E-mail:", + "Please confirm your password before continuing.": "请在继续之前确认您的密码。", "Regards": "致敬", "Register": "注册", + "Remember Me": "记住我", "Reset Password": "重置密码", "Reset Password Notification": "重置密码通知", "results": "结果", + "Send Password Reset Link": "发送重设密码链接", "Server Error": "服务器错误", "Service Unavailable": "服务不可用", "Showing": "显示中", @@ -35,6 +49,8 @@ "Too Many Requests": "请求次数过多。", "Unauthorized": "未授权", "Verify Email Address": "验证 E-mail", + "Verify Your Email Address": "验证您的邮件地址", "Whoops!": "哎呀!", + "You are logged in!": "您已登录!", "You are receiving this email because we received a password reset request for your account.": "您收到此电子邮件是因为我们收到了您帐户的密码重设请求。" -} \ No newline at end of file +} diff --git a/package.json b/package.json index a6245fd..a54324e 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,13 @@ "build": "vite build" }, "devDependencies": { - "@popperjs/core": "^2.10.2", + "@popperjs/core": "^2.11.6", "axios": "^1.1.2", "bootstrap": "5.3.0-alpha1", "laravel-vite-plugin": "^0.6.0", "lodash": "^4.17.19", "postcss": "^8.1.14", - "sass": "^1.32.11", + "sass": "^1.56.1", "vite": "^3.0.0" }, "dependencies": { diff --git a/resources/views/admin/users/edit.blade.php b/resources/views/admin/users/edit.blade.php index 4f0d41d..18c75e8 100644 --- a/resources/views/admin/users/edit.blade.php +++ b/resources/views/admin/users/edit.blade.php @@ -20,12 +20,12 @@ @endif -
- 余额: {{ $user->balance }} 元
+
+ 余额: {{ $user->balance }} 元
- 注册时间: {{ $user->created_at }}
+ 注册时间: {{ $user->created_at }}
- 邮箱: {{ $user->email }}
+ 邮箱: {{ $user->email }}
@if ($user->birthday_at) @@ -235,6 +235,17 @@ +

删除用户

+

+ 这是个非常a危险的操作,请三思而后行。 +

+
+ @csrf + @method('DELETE') + + +
+ - - - - - - - - - - diff --git a/resources/views/vendor/mail/html/message.blade.php b/resources/views/vendor/mail/html/message.blade.php deleted file mode 100644 index 4f2b40c..0000000 --- a/resources/views/vendor/mail/html/message.blade.php +++ /dev/null @@ -1,27 +0,0 @@ -@component('mail::layout') - {{-- Header --}} - @slot('header') - @component('mail::header', ['url' => config('app.url')]) - {{ config('app.display_name') }} - @endcomponent - @endslot - - {{-- Body --}} - {{ $slot }} - - {{-- Subcopy --}} - @isset($subcopy) - @slot('subcopy') - @component('mail::subcopy') - {{ $subcopy }} - @endcomponent - @endslot - @endisset - - {{-- Footer --}} - @slot('footer') - @component('mail::footer') - © {{ date('Y') }} {{ config('app.display_name') }}. @lang('All rights reserved.') - @endcomponent - @endslot -@endcomponent diff --git a/resources/views/vendor/mail/html/panel.blade.php b/resources/views/vendor/mail/html/panel.blade.php deleted file mode 100644 index ae97c13..0000000 --- a/resources/views/vendor/mail/html/panel.blade.php +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/resources/views/vendor/mail/html/subcopy.blade.php b/resources/views/vendor/mail/html/subcopy.blade.php deleted file mode 100644 index f559e8f..0000000 --- a/resources/views/vendor/mail/html/subcopy.blade.php +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/resources/views/vendor/mail/html/table.blade.php b/resources/views/vendor/mail/html/table.blade.php deleted file mode 100644 index b3bb3c8..0000000 --- a/resources/views/vendor/mail/html/table.blade.php +++ /dev/null @@ -1,3 +0,0 @@ -
- {{ Illuminate\Mail\Markdown::parse($slot) }} -
diff --git a/resources/views/vendor/mail/html/themes/default.css b/resources/views/vendor/mail/html/themes/default.css deleted file mode 100644 index 2483b11..0000000 --- a/resources/views/vendor/mail/html/themes/default.css +++ /dev/null @@ -1,290 +0,0 @@ -/* Base */ - -body, -body *:not(html):not(style):not(br):not(tr):not(code) { - box-sizing: border-box; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, - 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; - position: relative; -} - -body { - -webkit-text-size-adjust: none; - background-color: #ffffff; - color: #718096; - height: 100%; - line-height: 1.4; - margin: 0; - padding: 0; - width: 100% !important; -} - -p, -ul, -ol, -blockquote { - line-height: 1.4; - text-align: left; -} - -a { - color: #3869d4; -} - -a img { - border: none; -} - -/* Typography */ - -h1 { - color: #3d4852; - font-size: 18px; - font-weight: bold; - margin-top: 0; - text-align: left; -} - -h2 { - font-size: 16px; - font-weight: bold; - margin-top: 0; - text-align: left; -} - -h3 { - font-size: 14px; - font-weight: bold; - margin-top: 0; - text-align: left; -} - -p { - font-size: 16px; - line-height: 1.5em; - margin-top: 0; - text-align: left; -} - -p.sub { - font-size: 12px; -} - -img { - max-width: 100%; -} - -/* Layout */ - -.wrapper { - -premailer-cellpadding: 0; - -premailer-cellspacing: 0; - -premailer-width: 100%; - background-color: #edf2f7; - margin: 0; - padding: 0; - width: 100%; -} - -.content { - -premailer-cellpadding: 0; - -premailer-cellspacing: 0; - -premailer-width: 100%; - margin: 0; - padding: 0; - width: 100%; -} - -/* Header */ - -.header { - padding: 25px 0; - text-align: center; -} - -.header a { - color: #3d4852; - font-size: 19px; - font-weight: bold; - text-decoration: none; -} - -/* Logo */ - -.logo { - height: 75px; - max-height: 75px; - width: 75px; -} - -/* Body */ - -.body { - -premailer-cellpadding: 0; - -premailer-cellspacing: 0; - -premailer-width: 100%; - background-color: #edf2f7; - border-bottom: 1px solid #edf2f7; - border-top: 1px solid #edf2f7; - margin: 0; - padding: 0; - width: 100%; -} - -.inner-body { - -premailer-cellpadding: 0; - -premailer-cellspacing: 0; - -premailer-width: 570px; - background-color: #ffffff; - border-color: #e8e5ef; - border-radius: 2px; - border-width: 1px; - box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015); - margin: 0 auto; - padding: 0; - width: 570px; -} - -/* Subcopy */ - -.subcopy { - border-top: 1px solid #e8e5ef; - margin-top: 25px; - padding-top: 25px; -} - -.subcopy p { - font-size: 14px; -} - -/* Footer */ - -.footer { - -premailer-cellpadding: 0; - -premailer-cellspacing: 0; - -premailer-width: 570px; - margin: 0 auto; - padding: 0; - text-align: center; - width: 570px; -} - -.footer p { - color: #b0adc5; - font-size: 12px; - text-align: center; -} - -.footer a { - color: #b0adc5; - text-decoration: underline; -} - -/* Tables */ - -.table table { - -premailer-cellpadding: 0; - -premailer-cellspacing: 0; - -premailer-width: 100%; - margin: 30px auto; - width: 100%; -} - -.table th { - border-bottom: 1px solid #edeff2; - margin: 0; - padding-bottom: 8px; -} - -.table td { - color: #74787e; - font-size: 15px; - line-height: 18px; - margin: 0; - padding: 10px 0; -} - -.content-cell { - max-width: 100vw; - padding: 32px; -} - -/* Buttons */ - -.action { - -premailer-cellpadding: 0; - -premailer-cellspacing: 0; - -premailer-width: 100%; - margin: 30px auto; - padding: 0; - text-align: center; - width: 100%; -} - -.button { - -webkit-text-size-adjust: none; - border-radius: 4px; - color: #fff; - display: inline-block; - overflow: hidden; - text-decoration: none; -} - -.button-blue, -.button-primary { - background-color: #2d3748; - border-bottom: 8px solid #2d3748; - border-left: 18px solid #2d3748; - border-right: 18px solid #2d3748; - border-top: 8px solid #2d3748; -} - -.button-green, -.button-success { - background-color: #48bb78; - border-bottom: 8px solid #48bb78; - border-left: 18px solid #48bb78; - border-right: 18px solid #48bb78; - border-top: 8px solid #48bb78; -} - -.button-red, -.button-error { - background-color: #e53e3e; - border-bottom: 8px solid #e53e3e; - border-left: 18px solid #e53e3e; - border-right: 18px solid #e53e3e; - border-top: 8px solid #e53e3e; -} - -/* Panels */ - -.panel { - border-left: #2d3748 solid 4px; - margin: 21px 0; -} - -.panel-content { - background-color: #edf2f7; - color: #718096; - padding: 16px; -} - -.panel-content p { - color: #718096; -} - -.panel-item { - padding: 0; -} - -.panel-item p:last-of-type { - margin-bottom: 0; - padding-bottom: 0; -} - -/* Utilities */ - -.break-all { - word-break: break-all; -} diff --git a/resources/views/vendor/mail/text/button.blade.php b/resources/views/vendor/mail/text/button.blade.php deleted file mode 100644 index 97444eb..0000000 --- a/resources/views/vendor/mail/text/button.blade.php +++ /dev/null @@ -1 +0,0 @@ -{{ $slot }}: {{ $url }} diff --git a/resources/views/vendor/mail/text/footer.blade.php b/resources/views/vendor/mail/text/footer.blade.php deleted file mode 100644 index 3338f62..0000000 --- a/resources/views/vendor/mail/text/footer.blade.php +++ /dev/null @@ -1 +0,0 @@ -{{ $slot }} diff --git a/resources/views/vendor/mail/text/header.blade.php b/resources/views/vendor/mail/text/header.blade.php deleted file mode 100644 index aaa3e57..0000000 --- a/resources/views/vendor/mail/text/header.blade.php +++ /dev/null @@ -1 +0,0 @@ -[{{ $slot }}]({{ $url }}) diff --git a/resources/views/vendor/mail/text/layout.blade.php b/resources/views/vendor/mail/text/layout.blade.php deleted file mode 100644 index c59d2d8..0000000 --- a/resources/views/vendor/mail/text/layout.blade.php +++ /dev/null @@ -1,9 +0,0 @@ -{!! strip_tags($header) !!} - -{!! strip_tags($slot) !!} -@isset($subcopy) - - {!! strip_tags($subcopy) !!} -@endisset - -{!! strip_tags($footer) !!} diff --git a/resources/views/vendor/mail/text/message.blade.php b/resources/views/vendor/mail/text/message.blade.php deleted file mode 100644 index 4f2b40c..0000000 --- a/resources/views/vendor/mail/text/message.blade.php +++ /dev/null @@ -1,27 +0,0 @@ -@component('mail::layout') - {{-- Header --}} - @slot('header') - @component('mail::header', ['url' => config('app.url')]) - {{ config('app.display_name') }} - @endcomponent - @endslot - - {{-- Body --}} - {{ $slot }} - - {{-- Subcopy --}} - @isset($subcopy) - @slot('subcopy') - @component('mail::subcopy') - {{ $subcopy }} - @endcomponent - @endslot - @endisset - - {{-- Footer --}} - @slot('footer') - @component('mail::footer') - © {{ date('Y') }} {{ config('app.display_name') }}. @lang('All rights reserved.') - @endcomponent - @endslot -@endcomponent diff --git a/resources/views/vendor/mail/text/panel.blade.php b/resources/views/vendor/mail/text/panel.blade.php deleted file mode 100644 index 3338f62..0000000 --- a/resources/views/vendor/mail/text/panel.blade.php +++ /dev/null @@ -1 +0,0 @@ -{{ $slot }} diff --git a/resources/views/vendor/mail/text/subcopy.blade.php b/resources/views/vendor/mail/text/subcopy.blade.php deleted file mode 100644 index 3338f62..0000000 --- a/resources/views/vendor/mail/text/subcopy.blade.php +++ /dev/null @@ -1 +0,0 @@ -{{ $slot }} diff --git a/resources/views/vendor/mail/text/table.blade.php b/resources/views/vendor/mail/text/table.blade.php deleted file mode 100644 index 3338f62..0000000 --- a/resources/views/vendor/mail/text/table.blade.php +++ /dev/null @@ -1 +0,0 @@ -{{ $slot }} diff --git a/resources/views/vendor/notifications/email.blade.php b/resources/views/vendor/notifications/email.blade.php index ff4f83f..4b2f690 100644 --- a/resources/views/vendor/notifications/email.blade.php +++ b/resources/views/vendor/notifications/email.blade.php @@ -1,58 +1,58 @@ @component('mail::message') - {{-- Greeting --}} - @if (! empty($greeting)) - # {{ $greeting }} - @else - @if ($level === 'error') - # @lang('Whoops!') - @else - # @lang('Hello!') - @endif - @endif +{{-- Greeting --}} +@if (! empty($greeting)) +# {{ $greeting }} +@else +@if ($level === 'error') +# @lang('Whoops!') +@else +# @lang('Hello!') +@endif +@endif - {{-- Intro Lines --}} - @foreach ($introLines as $line) - {{ $line }} +{{-- Intro Lines --}} +@foreach ($introLines as $line) +{{ $line }} - @endforeach +@endforeach - {{-- Action Button --}} - @isset($actionText) - $level, - default => 'primary', - }; - ?> - @component('mail::button', ['url' => $actionUrl, 'color' => $color]) - {{ $actionText }} - @endcomponent - @endisset - - {{-- Outro Lines --}} - @foreach ($outroLines as $line) - {{ $line }} - - @endforeach - - {{-- Salutation --}} - @if (! empty($salutation)) - {{ $salutation }} - @else - @lang('Regards'),
- {{ config('app.name') }} - @endif - - {{-- Subcopy --}} - @isset($actionText) - @slot('subcopy') - @lang( - "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\n". - 'into your web browser:', - [ - 'actionText' => $actionText, - ] - ) [{{ $displayableActionUrl }}]({{ $actionUrl }}) - @endslot - @endisset +{{-- Action Button --}} +@isset($actionText) + $level, + default => 'primary', + }; +?> +@component('mail::button', ['url' => $actionUrl, 'color' => $color]) +{{ $actionText }} +@endcomponent +@endisset + +{{-- Outro Lines --}} +@foreach ($outroLines as $line) +{{ $line }} + +@endforeach + +{{-- Salutation --}} +@if (! empty($salutation)) +{{ $salutation }} +@else +@lang('Regards'),
+{{ config('app.name') }} +@endif + +{{-- Subcopy --}} +@isset($actionText) +@slot('subcopy') +@lang( + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\n". + 'into your web browser:', + [ + 'actionText' => $actionText, + ] +) [{{ $displayableActionUrl }}]({{ $actionUrl }}) +@endslot +@endisset @endcomponent diff --git a/routes/admin.php b/routes/admin.php index 2485b2f..d49f454 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -27,7 +27,7 @@ ], function () { Route::resource('admins', AdminController::class)->except('show'); - Route::resource('users', UserController::class)->only(['index', 'show', 'edit', 'update']); + Route::resource('users', UserController::class); Route::resource('modules', ModuleController::class); diff --git a/routes/web.php b/routes/web.php index e51a6ce..544290d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,11 @@ name('index')->middleware('banned'); Route::prefix('auth')->group(function () { - Route::get('redirect', [AuthController::class, 'redirect'])->name('login'); - Route::get('callback', [AuthController::class, 'callback'])->name('callback'); + // Route::get('redirect', [AuthController::class, 'redirect'])->name('login'); + // Route::get('callback', [AuthController::class, 'callback'])->name('callback'); + + Route::get('login', [LoginController::class, 'showLoginForm'])->name('login'); + Route::post('login', [LoginController::class, 'login']); + Route::post('logout', [LoginController::class, 'logout'])->name('logout'); + + Route::get('register', [RegisterController::class, 'showRegistrationForm'])->name('register'); + Route::post('register', [RegisterController::class, 'register']); + + Route::get('password/reset', [ForgotPasswordController::class, 'showLinkRequestForm'])->name('password.request'); + Route::post('password/email', [ForgotPasswordController::class, 'sendResetLinkEmail'])->name('password.email'); + Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm'])->name('password.reset'); + Route::post('password/reset', [ResetPasswordController::class, 'reset'])->name('password.update'); + + Route::get('password/confirm', [ConfirmPasswordController::class, 'showConfirmForm'])->name('password.confirm'); + Route::post('password/confirm', [ConfirmPasswordController::class, 'confirm']); + + Route::get('email/verify', [VerificationController::class, 'show'])->name('verification.notice'); + Route::get('email/verify/{id}/{hash}', [VerificationController::class, 'verify'])->name('verification.verify'); + Route::post('email/resend', [VerificationController::class, 'resend'])->name('verification.resend'); }); -Route::middleware(['auth', 'banned'])->group( +Route::middleware(['auth', 'banned', 'verified'])->group( function () { /* Start 账户区域 */ - Route::view('banned', 'banned')->name('banned')->withoutMiddleware('banned'); - Route::post('logout', [AuthController::class, 'logout'])->name('logout')->withoutMiddleware('banned'); + Route::withoutMiddleware(['banned', 'verified'])->group( + function () { + Route::view('banned', 'banned')->name('banned')->withoutMiddleware(['banned', 'verified']); + Route::post('logout', [AuthController::class, 'logout'])->name('logout')->withoutMiddleware(['banned', 'verified']); + } + ); - Route::get('confirm_redirect', [AuthController::class, 'confirm_redirect'])->name('confirm_redirect')->middleware('real_named'); - Route::post('newToken', [AuthController::class, 'newToken'])->name('newToken')->middleware('real_named'); + Route::middleware(['real_named'])->group( + function () { + Route::get('confirm_redirect', [AuthController::class, 'confirm_redirect'])->name('confirm_redirect')->middleware('real_named'); + Route::post('newToken', [AuthController::class, 'newToken'])->name('token.new')->middleware('real_named'); + } + ); - Route::delete('deleteAll', [AuthController::class, 'deleteAll'])->name('deleteAll'); + Route::delete('deleteAll', [AuthController::class, 'deleteAll'])->name('token.delete_all'); /* End 账户区域 */ /* Start 财务 */ @@ -31,7 +64,7 @@ function () { Route::resource('balances', BalanceController::class)->except('show'); Route::get('/balances/{balance:order_id}', [BalanceController::class, 'show'])->name('balances.show')->withoutMiddleware('auth'); - Route::middleware(['real_named'])->group( + Route::middleware(['real_named', 'password.confirm'])->group( function () { Route::get('transfer', [TransferController::class, 'index'])->name('transfer'); Route::post('transfer', [TransferController::class, 'transfer']); diff --git a/yarn.lock b/yarn.lock index 645716c..fca82fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,7 +12,7 @@ resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239" integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ== -"@popperjs/core@^2.10.2": +"@popperjs/core@^2.11.6": version "2.11.6" resolved "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== @@ -383,10 +383,10 @@ rollup@^2.79.1: optionalDependencies: fsevents "~2.3.2" -sass@^1.32.11: - version "1.57.1" - resolved "https://registry.npmmirror.com/sass/-/sass-1.57.1.tgz#dfafd46eb3ab94817145e8825208ecf7281119b5" - integrity sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw== +sass@^1.56.1: + version "1.58.0" + resolved "https://registry.npmmirror.com/sass/-/sass-1.58.0.tgz#ee8aea3ad5ea5c485c26b3096e2df6087d0bb1cc" + integrity sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0"