updated so many things, my bad

This commit is contained in:
Brian 2022-06-22 10:47:22 -06:00
parent 938e1f8c96
commit 6cf7a5e3d1
Signed by: brian
GPG Key ID: DE1A5390A3B84CD8
22 changed files with 1513 additions and 663 deletions

View File

@ -37,7 +37,7 @@ class ResetPassword extends Command
*
* @return int
*/
public function handle()
public function handle(): int
{
$id = $this->option('id');
$email = $this->option('email');

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('user_id', 64);
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable()->index();
$table->text('payload');
$table->integer('last_activity')->index();
$table->timestamp('vr_last_activity_at')->virtualAs('FROM_UNIXTIME(last_activity)');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('sessions');
}
};

View File

@ -9,6 +9,7 @@
@import 'components/buttons.css';
@import 'components/cards.css';
@import 'components/forms.css';
@import 'components/tables.css';
body {
@ -17,6 +18,19 @@ body {
scrollbar-gutter: stable both-edges;
}
html, body {
transition-duration: 0.05s;
transition-timing-function: ease-in-out;
}
html, body {
transition-property: background, color;
}
button, a {
transition-property: all;
}
@media (max-width: 1023px) {
.grid-container {
@apply grid-cols-1;

View File

@ -0,0 +1,31 @@
.form-block {
@apply grid auto-rows-max gap-y-1;
}
.form-label {
@apply uppercase font-semibold text-xs text-zinc-600 dark:text-zinc-300;
}
input.form-input[type="date"],
input.form-input[type="datetime-local"],
input.form-input[type="email"],
input.form-input[type="month"],
input.form-input[type="number"],
input.form-input[type="password"],
input.form-input[type="search"],
input.form-input[type="tel"],
input.form-input[type="text"],
input.form-input[type="textarea"],
input.form-input[type="url"],
input.form-input[type="week"],
textarea.form-input {
@apply relative px-3 py-1 border rounded focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 bg-white dark:bg-indigo-900 text-zinc-900 dark:text-zinc-100 border-stone-300 dark:border-stone-600 shadow dark:shadow-white;
}
.form-checkbox-label {
@apply grid grid-flow-col auto-cols-max gap-x-2 items-center;
}
.form-checkbox-label .form-checkbox {
/*rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50*/
}

View File

@ -1,7 +1,26 @@
.table thead tr {
@apply border-b-2 border-gray-700 uppercase text-left;
.table {
@apply bg-white dark:bg-neutral-800 rounded w-full;
}
.table thead th,
.table tbody td {
@apply px-4 py-3;
}
.table thead tr {
@apply border-b border-neutral-900 dark:border-neutral-300;
}
.table thead th {
@apply text-zinc-700 dark:text-zinc-200 text-sm text-left font-semibold uppercase;
}
.table tbody tr:not(:first-child) {
@apply border-t border-neutral-700 dark:border-neutral-400;
}
.table tbody td {}
.table-fuzzy-hover tbody:hover td {
color: transparent;
text-shadow: 0 0 2px #999;

View File

@ -0,0 +1,241 @@
.text-shadow-slate-50 { text-shadow: 1px 1px 2px #f8fafc; }
.text-shadow-slate-100 { text-shadow: 1px 1px 2px #f1f5f9; }
.text-shadow-slate-200 { text-shadow: 1px 1px 2px #e2e8f0; }
.text-shadow-slate-300 { text-shadow: 1px 1px 2px #cbd5e1; }
.text-shadow-slate-400 { text-shadow: 1px 1px 2px #94a3b8; }
.text-shadow-slate-500 { text-shadow: 1px 1px 2px #64748b; }
.text-shadow-slate-600 { text-shadow: 1px 1px 2px #475569; }
.text-shadow-slate-700 { text-shadow: 1px 1px 2px #334155; }
.text-shadow-slate-800 { text-shadow: 1px 1px 2px #1e293b; }
.text-shadow-slate-900 { text-shadow: 1px 1px 2px #0f172a; }
.text-shadow-gray-50 { text-shadow: 1px 1px 2px #f9fafb; }
.text-shadow-gray-100 { text-shadow: 1px 1px 2px #f3f4f6; }
.text-shadow-gray-200 { text-shadow: 1px 1px 2px #e5e7eb; }
.text-shadow-gray-300 { text-shadow: 1px 1px 2px #d1d5db; }
.text-shadow-gray-400 { text-shadow: 1px 1px 2px #9ca3af; }
.text-shadow-gray-500 { text-shadow: 1px 1px 2px #6b7280; }
.text-shadow-gray-600 { text-shadow: 1px 1px 2px #4b5563; }
.text-shadow-gray-700 { text-shadow: 1px 1px 2px #374151; }
.text-shadow-gray-800 { text-shadow: 1px 1px 2px #1f2937; }
.text-shadow-gray-900 { text-shadow: 1px 1px 2px #111827; }
.text-shadow-zinc-50 { text-shadow: 1px 1px 2px #fafafa; }
.text-shadow-zinc-100 { text-shadow: 1px 1px 2px #f4f4f5; }
.text-shadow-zinc-200 { text-shadow: 1px 1px 2px #e4e4e7; }
.text-shadow-zinc-300 { text-shadow: 1px 1px 2px #d4d4d8; }
.text-shadow-zinc-400 { text-shadow: 1px 1px 2px #a1a1aa; }
.text-shadow-zinc-500 { text-shadow: 1px 1px 2px #71717a; }
.text-shadow-zinc-600 { text-shadow: 1px 1px 2px #52525b; }
.text-shadow-zinc-700 { text-shadow: 1px 1px 2px #3f3f46; }
.text-shadow-zinc-800 { text-shadow: 1px 1px 2px #27272a; }
.text-shadow-zinc-900 { text-shadow: 1px 1px 2px #18181b; }
.text-shadow-neutral-50 { text-shadow: 1px 1px 2px #fafafa; }
.text-shadow-neutral-100 { text-shadow: 1px 1px 2px #f5f5f5; }
.text-shadow-neutral-200 { text-shadow: 1px 1px 2px #e5e5e5; }
.text-shadow-neutral-300 { text-shadow: 1px 1px 2px #d4d4d4; }
.text-shadow-neutral-400 { text-shadow: 1px 1px 2px #a3a3a3; }
.text-shadow-neutral-500 { text-shadow: 1px 1px 2px #737373; }
.text-shadow-neutral-600 { text-shadow: 1px 1px 2px #525252; }
.text-shadow-neutral-700 { text-shadow: 1px 1px 2px #404040; }
.text-shadow-neutral-800 { text-shadow: 1px 1px 2px #262626; }
.text-shadow-neutral-900 { text-shadow: 1px 1px 2px #171717; }
.text-shadow-stone-50 { text-shadow: 1px 1px 2px #fafaf9; }
.text-shadow-stone-100 { text-shadow: 1px 1px 2px #f5f5f4; }
.text-shadow-stone-200 { text-shadow: 1px 1px 2px #e7e5e4; }
.text-shadow-stone-300 { text-shadow: 1px 1px 2px #d6d3d1; }
.text-shadow-stone-400 { text-shadow: 1px 1px 2px #a8a29e; }
.text-shadow-stone-500 { text-shadow: 1px 1px 2px #78716c; }
.text-shadow-stone-600 { text-shadow: 1px 1px 2px #57534e; }
.text-shadow-stone-700 { text-shadow: 1px 1px 2px #44403c; }
.text-shadow-stone-800 { text-shadow: 1px 1px 2px #292524; }
.text-shadow-stone-900 { text-shadow: 1px 1px 2px #1c1917; }
.text-shadow-red-50 { text-shadow: 1px 1px 2px #fef2f2; }
.text-shadow-red-100 { text-shadow: 1px 1px 2px #fee2e2; }
.text-shadow-red-200 { text-shadow: 1px 1px 2px #fecaca; }
.text-shadow-red-300 { text-shadow: 1px 1px 2px #fca5a5; }
.text-shadow-red-400 { text-shadow: 1px 1px 2px #f87171; }
.text-shadow-red-500 { text-shadow: 1px 1px 2px #ef4444; }
.text-shadow-red-600 { text-shadow: 1px 1px 2px #dc2626; }
.text-shadow-red-700 { text-shadow: 1px 1px 2px #b91c1c; }
.text-shadow-red-800 { text-shadow: 1px 1px 2px #991b1b; }
.text-shadow-red-900 { text-shadow: 1px 1px 2px #7f1d1d; }
.text-shadow-orange-50 { text-shadow: 1px 1px 2px #fff7ed; }
.text-shadow-orange-100 { text-shadow: 1px 1px 2px #ffedd5; }
.text-shadow-orange-200 { text-shadow: 1px 1px 2px #fed7aa; }
.text-shadow-orange-300 { text-shadow: 1px 1px 2px #fdba74; }
.text-shadow-orange-400 { text-shadow: 1px 1px 2px #fb923c; }
.text-shadow-orange-500 { text-shadow: 1px 1px 2px #f97316; }
.text-shadow-orange-600 { text-shadow: 1px 1px 2px #ea580c; }
.text-shadow-orange-700 { text-shadow: 1px 1px 2px #c2410c; }
.text-shadow-orange-800 { text-shadow: 1px 1px 2px #9a3412; }
.text-shadow-orange-900 { text-shadow: 1px 1px 2px #7c2d12; }
.text-shadow-amber-50 { text-shadow: 1px 1px 2px #fffbeb; }
.text-shadow-amber-100 { text-shadow: 1px 1px 2px #fef3c7; }
.text-shadow-amber-200 { text-shadow: 1px 1px 2px #fde68a; }
.text-shadow-amber-300 { text-shadow: 1px 1px 2px #fcd34d; }
.text-shadow-amber-400 { text-shadow: 1px 1px 2px #fbbf24; }
.text-shadow-amber-500 { text-shadow: 1px 1px 2px #f59e0b; }
.text-shadow-amber-600 { text-shadow: 1px 1px 2px #d97706; }
.text-shadow-amber-700 { text-shadow: 1px 1px 2px #b45309; }
.text-shadow-amber-800 { text-shadow: 1px 1px 2px #92400e; }
.text-shadow-amber-900 { text-shadow: 1px 1px 2px #78350f; }
.text-shadow-yellow-50 { text-shadow: 1px 1px 2px #fefce8; }
.text-shadow-yellow-100 { text-shadow: 1px 1px 2px #fef9c3; }
.text-shadow-yellow-200 { text-shadow: 1px 1px 2px #fef08a; }
.text-shadow-yellow-300 { text-shadow: 1px 1px 2px #fde047; }
.text-shadow-yellow-400 { text-shadow: 1px 1px 2px #facc15; }
.text-shadow-yellow-500 { text-shadow: 1px 1px 2px #eab308; }
.text-shadow-yellow-600 { text-shadow: 1px 1px 2px #ca8a04; }
.text-shadow-yellow-700 { text-shadow: 1px 1px 2px #a16207; }
.text-shadow-yellow-800 { text-shadow: 1px 1px 2px #854d0e; }
.text-shadow-yellow-900 { text-shadow: 1px 1px 2px #713f12; }
.text-shadow-lime-50 { text-shadow: 1px 1px 2px #f7fee7; }
.text-shadow-lime-100 { text-shadow: 1px 1px 2px #ecfccb; }
.text-shadow-lime-200 { text-shadow: 1px 1px 2px #d9f99d; }
.text-shadow-lime-300 { text-shadow: 1px 1px 2px #bef264; }
.text-shadow-lime-400 { text-shadow: 1px 1px 2px #a3e635; }
.text-shadow-lime-500 { text-shadow: 1px 1px 2px #84cc16; }
.text-shadow-lime-600 { text-shadow: 1px 1px 2px #65a30d; }
.text-shadow-lime-700 { text-shadow: 1px 1px 2px #4d7c0f; }
.text-shadow-lime-800 { text-shadow: 1px 1px 2px #3f6212; }
.text-shadow-lime-900 { text-shadow: 1px 1px 2px #365314; }
.text-shadow-green-50 { text-shadow: 1px 1px 2px #f0fdf4; }
.text-shadow-green-100 { text-shadow: 1px 1px 2px #dcfce7; }
.text-shadow-green-200 { text-shadow: 1px 1px 2px #bbf7d0; }
.text-shadow-green-300 { text-shadow: 1px 1px 2px #86efac; }
.text-shadow-green-400 { text-shadow: 1px 1px 2px #4ade80; }
.text-shadow-green-500 { text-shadow: 1px 1px 2px #22c55e; }
.text-shadow-green-600 { text-shadow: 1px 1px 2px #16a34a; }
.text-shadow-green-700 { text-shadow: 1px 1px 2px #15803d; }
.text-shadow-green-800 { text-shadow: 1px 1px 2px #166534; }
.text-shadow-green-900 { text-shadow: 1px 1px 2px #14532d; }
.text-shadow-emerald-50 { text-shadow: 1px 1px 2px #ecfdf5; }
.text-shadow-emerald-100 { text-shadow: 1px 1px 2px #d1fae5; }
.text-shadow-emerald-200 { text-shadow: 1px 1px 2px #a7f3d0; }
.text-shadow-emerald-300 { text-shadow: 1px 1px 2px #6ee7b7; }
.text-shadow-emerald-400 { text-shadow: 1px 1px 2px #34d399; }
.text-shadow-emerald-500 { text-shadow: 1px 1px 2px #10b981; }
.text-shadow-emerald-600 { text-shadow: 1px 1px 2px #059669; }
.text-shadow-emerald-700 { text-shadow: 1px 1px 2px #047857; }
.text-shadow-emerald-800 { text-shadow: 1px 1px 2px #065f46; }
.text-shadow-emerald-900 { text-shadow: 1px 1px 2px #064e3b; }
.text-shadow-teal-50 { text-shadow: 1px 1px 2px #f0fdfa; }
.text-shadow-teal-100 { text-shadow: 1px 1px 2px #ccfbf1; }
.text-shadow-teal-200 { text-shadow: 1px 1px 2px #99f6e4; }
.text-shadow-teal-300 { text-shadow: 1px 1px 2px #5eead4; }
.text-shadow-teal-400 { text-shadow: 1px 1px 2px #2dd4bf; }
.text-shadow-teal-500 { text-shadow: 1px 1px 2px #14b8a6; }
.text-shadow-teal-600 { text-shadow: 1px 1px 2px #0d9488; }
.text-shadow-teal-700 { text-shadow: 1px 1px 2px #0f766e; }
.text-shadow-teal-800 { text-shadow: 1px 1px 2px #115e59; }
.text-shadow-teal-900 { text-shadow: 1px 1px 2px #134e4a; }
.text-shadow-cyan-50 { text-shadow: 1px 1px 2px #ecfeff; }
.text-shadow-cyan-100 { text-shadow: 1px 1px 2px #cffafe; }
.text-shadow-cyan-200 { text-shadow: 1px 1px 2px #a5f3fc; }
.text-shadow-cyan-300 { text-shadow: 1px 1px 2px #67e8f9; }
.text-shadow-cyan-400 { text-shadow: 1px 1px 2px #22d3ee; }
.text-shadow-cyan-500 { text-shadow: 1px 1px 2px #06b6d4; }
.text-shadow-cyan-600 { text-shadow: 1px 1px 2px #0891b2; }
.text-shadow-cyan-700 { text-shadow: 1px 1px 2px #0e7490; }
.text-shadow-cyan-800 { text-shadow: 1px 1px 2px #155e75; }
.text-shadow-cyan-900 { text-shadow: 1px 1px 2px #164e63; }
.text-shadow-sky-50 { text-shadow: 1px 1px 2px #f0f9ff; }
.text-shadow-sky-100 { text-shadow: 1px 1px 2px #e0f2fe; }
.text-shadow-sky-200 { text-shadow: 1px 1px 2px #bae6fd; }
.text-shadow-sky-300 { text-shadow: 1px 1px 2px #7dd3fc; }
.text-shadow-sky-400 { text-shadow: 1px 1px 2px #38bdf8; }
.text-shadow-sky-500 { text-shadow: 1px 1px 2px #0ea5e9; }
.text-shadow-sky-600 { text-shadow: 1px 1px 2px #0284c7; }
.text-shadow-sky-700 { text-shadow: 1px 1px 2px #0369a1; }
.text-shadow-sky-800 { text-shadow: 1px 1px 2px #075985; }
.text-shadow-sky-900 { text-shadow: 1px 1px 2px #0c4a6e; }
.text-shadow-blue-50 { text-shadow: 1px 1px 2px #eff6ff; }
.text-shadow-blue-100 { text-shadow: 1px 1px 2px #dbeafe; }
.text-shadow-blue-200 { text-shadow: 1px 1px 2px #bfdbfe; }
.text-shadow-blue-300 { text-shadow: 1px 1px 2px #93c5fd; }
.text-shadow-blue-400 { text-shadow: 1px 1px 2px #60a5fa; }
.text-shadow-blue-500 { text-shadow: 1px 1px 2px #3b82f6; }
.text-shadow-blue-600 { text-shadow: 1px 1px 2px #2563eb; }
.text-shadow-blue-700 { text-shadow: 1px 1px 2px #1d4ed8; }
.text-shadow-blue-800 { text-shadow: 1px 1px 2px #1e40af; }
.text-shadow-blue-900 { text-shadow: 1px 1px 2px #1e3a8a; }
.text-shadow-indigo-50 { text-shadow: 1px 1px 2px #eef2ff; }
.text-shadow-indigo-100 { text-shadow: 1px 1px 2px #e0e7ff; }
.text-shadow-indigo-200 { text-shadow: 1px 1px 2px #c7d2fe; }
.text-shadow-indigo-300 { text-shadow: 1px 1px 2px #a5b4fc; }
.text-shadow-indigo-400 { text-shadow: 1px 1px 2px #818cf8; }
.text-shadow-indigo-500 { text-shadow: 1px 1px 2px #6366f1; }
.text-shadow-indigo-600 { text-shadow: 1px 1px 2px #4f46e5; }
.text-shadow-indigo-700 { text-shadow: 1px 1px 2px #4338ca; }
.text-shadow-indigo-800 { text-shadow: 1px 1px 2px #3730a3; }
.text-shadow-indigo-900 { text-shadow: 1px 1px 2px #312e81; }
.text-shadow-violet-50 { text-shadow: 1px 1px 2px #f5f3ff; }
.text-shadow-violet-100 { text-shadow: 1px 1px 2px #ede9fe; }
.text-shadow-violet-200 { text-shadow: 1px 1px 2px #ddd6fe; }
.text-shadow-violet-300 { text-shadow: 1px 1px 2px #c4b5fd; }
.text-shadow-violet-400 { text-shadow: 1px 1px 2px #a78bfa; }
.text-shadow-violet-500 { text-shadow: 1px 1px 2px #8b5cf6; }
.text-shadow-violet-600 { text-shadow: 1px 1px 2px #7c3aed; }
.text-shadow-violet-700 { text-shadow: 1px 1px 2px #6d28d9; }
.text-shadow-violet-800 { text-shadow: 1px 1px 2px #5b21b6; }
.text-shadow-violet-900 { text-shadow: 1px 1px 2px #4c1d95; }
.text-shadow-purple-50 { text-shadow: 1px 1px 2px #faf5ff; }
.text-shadow-purple-100 { text-shadow: 1px 1px 2px #f3e8ff; }
.text-shadow-purple-200 { text-shadow: 1px 1px 2px #e9d5ff; }
.text-shadow-purple-300 { text-shadow: 1px 1px 2px #d8b4fe; }
.text-shadow-purple-400 { text-shadow: 1px 1px 2px #c084fc; }
.text-shadow-purple-500 { text-shadow: 1px 1px 2px #a855f7; }
.text-shadow-purple-600 { text-shadow: 1px 1px 2px #9333ea; }
.text-shadow-purple-700 { text-shadow: 1px 1px 2px #7e22ce; }
.text-shadow-purple-800 { text-shadow: 1px 1px 2px #6b21a8; }
.text-shadow-purple-900 { text-shadow: 1px 1px 2px #581c87; }
.text-shadow-fuchsia-50 { text-shadow: 1px 1px 2px #fdf4ff; }
.text-shadow-fuchsia-100 { text-shadow: 1px 1px 2px #fae8ff; }
.text-shadow-fuchsia-200 { text-shadow: 1px 1px 2px #f5d0fe; }
.text-shadow-fuchsia-300 { text-shadow: 1px 1px 2px #f0abfc; }
.text-shadow-fuchsia-400 { text-shadow: 1px 1px 2px #e879f9; }
.text-shadow-fuchsia-500 { text-shadow: 1px 1px 2px #d946ef; }
.text-shadow-fuchsia-600 { text-shadow: 1px 1px 2px #c026d3; }
.text-shadow-fuchsia-700 { text-shadow: 1px 1px 2px #a21caf; }
.text-shadow-fuchsia-800 { text-shadow: 1px 1px 2px #86198f; }
.text-shadow-fuchsia-900 { text-shadow: 1px 1px 2px #701a75; }
.text-shadow-pink-50 { text-shadow: 1px 1px 2px #fdf2f8; }
.text-shadow-pink-100 { text-shadow: 1px 1px 2px #fce7f3; }
.text-shadow-pink-200 { text-shadow: 1px 1px 2px #fbcfe8; }
.text-shadow-pink-300 { text-shadow: 1px 1px 2px #f9a8d4; }
.text-shadow-pink-400 { text-shadow: 1px 1px 2px #f472b6; }
.text-shadow-pink-500 { text-shadow: 1px 1px 2px #ec4899; }
.text-shadow-pink-600 { text-shadow: 1px 1px 2px #db2777; }
.text-shadow-pink-700 { text-shadow: 1px 1px 2px #be185d; }
.text-shadow-pink-800 { text-shadow: 1px 1px 2px #9d174d; }
.text-shadow-pink-900 { text-shadow: 1px 1px 2px #831843; }
.text-shadow-rose-50 { text-shadow: 1px 1px 2px #fff1f2; }
.text-shadow-rose-100 { text-shadow: 1px 1px 2px #ffe4e6; }
.text-shadow-rose-200 { text-shadow: 1px 1px 2px #fecdd3; }
.text-shadow-rose-300 { text-shadow: 1px 1px 2px #fda4af; }
.text-shadow-rose-400 { text-shadow: 1px 1px 2px #fb7185; }
.text-shadow-rose-500 { text-shadow: 1px 1px 2px #f43f5e; }
.text-shadow-rose-600 { text-shadow: 1px 1px 2px #e11d48; }
.text-shadow-rose-700 { text-shadow: 1px 1px 2px #be123c; }
.text-shadow-rose-800 { text-shadow: 1px 1px 2px #9f1239; }
.text-shadow-rose-900 { text-shadow: 1px 1px 2px #881337; }

View File

@ -0,0 +1,107 @@
<template>
<jet-action-section>
<template #title>
Delete Account
</template>
<template #description>
Permanently delete your account.
</template>
<template #content>
<div class="max-w-xl text-sm text-gray-600">
Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.
</div>
<div class="mt-5">
<jet-danger-button @click="confirmUserDeletion">
Delete Account
</jet-danger-button>
</div>
<!-- Delete Account Confirmation Modal -->
<jet-dialog-modal :show="confirmingUserDeletion" @close="closeModal">
<template #title>
Delete Account
</template>
<template #content>
Are you sure you want to delete your account? Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.
<div class="mt-4">
<jet-input type="password" class="mt-1 block w-3/4" placeholder="Password"
ref="password"
v-model="form.password"
@keyup.enter="deleteUser" />
<jet-input-error :message="form.errors.password" class="mt-2" />
</div>
</template>
<template #footer>
<jet-secondary-button @click="closeModal">
Cancel
</jet-secondary-button>
<jet-danger-button class="ml-2" @click="deleteUser" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Delete Account
</jet-danger-button>
</template>
</jet-dialog-modal>
</template>
</jet-action-section>
</template>
<script>
import { defineComponent } from 'vue'
import JetActionSection from '@/Jetstream/ActionSection.vue'
import JetDialogModal from '@/Jetstream/DialogModal.vue'
import JetDangerButton from '@/Jetstream/DangerButton.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
export default defineComponent({
components: {
JetActionSection,
JetDangerButton,
JetDialogModal,
JetInput,
JetInputError,
JetSecondaryButton,
},
data() {
return {
confirmingUserDeletion: false,
form: this.$inertia.form({
password: '',
})
}
},
methods: {
confirmUserDeletion() {
this.confirmingUserDeletion = true;
setTimeout(() => this.$refs.password.focus(), 250)
},
deleteUser() {
this.form.delete(route('current-user.destroy'), {
preserveScroll: true,
onSuccess: () => this.closeModal(),
onError: () => this.$refs.password.focus(),
onFinish: () => this.form.reset(),
})
},
closeModal() {
this.confirmingUserDeletion = false
this.form.reset()
},
},
})
</script>

View File

@ -0,0 +1,145 @@
<template>
<jet-action-section>
<template #title>
Browser Sessions
</template>
<template #description>
Manage and log out your active sessions on other browsers and devices.
</template>
<template #content>
<div class="max-w-xl text-sm text-gray-600">
If necessary, you may log out of all of your other browser sessions across all of your devices. Some of your recent sessions are listed below; however, this list may not be exhaustive. If you feel your account has been compromised, you should also update your password.
</div>
<!-- Other Browser Sessions -->
<div class="mt-5 space-y-6" v-if="sessions.length > 0">
<div class="flex items-center" v-for="(session, i) in sessions" :key="i">
<div>
<svg fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor" class="w-8 h-8 text-gray-500" v-if="session.agent.is_desktop">
<path d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" class="w-8 h-8 text-gray-500" v-else>
<path d="M0 0h24v24H0z" stroke="none"></path><rect x="7" y="4" width="10" height="16" rx="1"></rect><path d="M11 5h2M12 17v.01"></path>
</svg>
</div>
<div class="ml-3">
<div class="text-sm text-gray-600">
{{ session.agent.platform }} - {{ session.agent.browser }}
</div>
<div>
<div class="text-xs text-gray-500">
{{ session.ip_address }},
<span class="text-green-500 font-semibold" v-if="session.is_current_device">This device</span>
<span v-else>Last active {{ session.last_active }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="flex items-center mt-5">
<jet-button @click="confirmLogout">
Log Out Other Browser Sessions
</jet-button>
<jet-action-message :on="form.recentlySuccessful" class="ml-3">
Done.
</jet-action-message>
</div>
<!-- Log Out Other Devices Confirmation Modal -->
<jet-dialog-modal :show="confirmingLogout" @close="closeModal">
<template #title>
Log Out Other Browser Sessions
</template>
<template #content>
Please enter your password to confirm you would like to log out of your other browser sessions across all of your devices.
<div class="mt-4">
<jet-input type="password" class="mt-1 block w-3/4" placeholder="Password"
ref="password"
v-model="form.password"
@keyup.enter="logoutOtherBrowserSessions" />
<jet-input-error :message="form.errors.password" class="mt-2" />
</div>
</template>
<template #footer>
<jet-secondary-button @click="closeModal">
Cancel
</jet-secondary-button>
<jet-button class="ml-2" @click="logoutOtherBrowserSessions" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Log Out Other Browser Sessions
</jet-button>
</template>
</jet-dialog-modal>
</template>
</jet-action-section>
</template>
<script>
import { defineComponent } from 'vue'
import JetActionMessage from '@/Jetstream/ActionMessage.vue'
import JetActionSection from '@/Jetstream/ActionSection.vue'
import JetButton from '@/Jetstream/Button.vue'
import JetDialogModal from '@/Jetstream/DialogModal.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
export default defineComponent({
props: ['sessions'],
components: {
JetActionMessage,
JetActionSection,
JetButton,
JetDialogModal,
JetInput,
JetInputError,
JetSecondaryButton,
},
data() {
return {
confirmingLogout: false,
form: this.$inertia.form({
password: '',
})
}
},
methods: {
confirmLogout() {
this.confirmingLogout = true
setTimeout(() => this.$refs.password.focus(), 250)
},
logoutOtherBrowserSessions() {
this.form.delete(route('other-browser-sessions.destroy'), {
preserveScroll: true,
onSuccess: () => this.closeModal(),
onError: () => this.$refs.password.focus(),
onFinish: () => this.form.reset(),
})
},
closeModal() {
this.confirmingLogout = false
this.form.reset()
},
},
})
</script>

View File

@ -0,0 +1,167 @@
<template>
<jet-action-section>
<template #title>
Two Factor Authentication
</template>
<template #description>
Add additional security to your account using two factor authentication.
</template>
<template #content>
<h3 class="text-lg font-medium text-gray-900" v-if="twoFactorEnabled">
You have enabled two factor authentication.
</h3>
<h3 class="text-lg font-medium text-gray-900" v-else>
You have not enabled two factor authentication.
</h3>
<div class="mt-3 max-w-xl text-sm text-gray-600">
<p>
When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve this token from your phone's Google Authenticator application.
</p>
</div>
<div v-if="twoFactorEnabled">
<div v-if="qrCode">
<div class="mt-4 max-w-xl text-sm text-gray-600">
<p class="font-semibold">
Two factor authentication is now enabled. Scan the following QR code using your phone's authenticator application.
</p>
</div>
<div class="mt-4" v-html="qrCode">
</div>
</div>
<div v-if="recoveryCodes.length > 0">
<div class="mt-4 max-w-xl text-sm text-gray-600">
<p class="font-semibold">
Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.
</p>
</div>
<div class="grid gap-1 max-w-xl mt-4 px-4 py-4 font-mono text-sm bg-gray-100 rounded-lg">
<div v-for="code in recoveryCodes" :key="code">
{{ code }}
</div>
</div>
</div>
</div>
<div class="mt-5">
<div v-if="! twoFactorEnabled">
<jet-confirms-password @confirmed="enableTwoFactorAuthentication">
<jet-button type="button" :class="{ 'opacity-25': enabling }" :disabled="enabling">
Enable
</jet-button>
</jet-confirms-password>
</div>
<div v-else>
<jet-confirms-password @confirmed="regenerateRecoveryCodes">
<jet-secondary-button class="mr-3"
v-if="recoveryCodes.length > 0">
Regenerate Recovery Codes
</jet-secondary-button>
</jet-confirms-password>
<jet-confirms-password @confirmed="showRecoveryCodes">
<jet-secondary-button class="mr-3" v-if="recoveryCodes.length === 0">
Show Recovery Codes
</jet-secondary-button>
</jet-confirms-password>
<jet-confirms-password @confirmed="disableTwoFactorAuthentication">
<jet-danger-button
:class="{ 'opacity-25': disabling }"
:disabled="disabling">
Disable
</jet-danger-button>
</jet-confirms-password>
</div>
</div>
</template>
</jet-action-section>
</template>
<script>
import { defineComponent } from 'vue'
import JetActionSection from '@/Jetstream/ActionSection.vue'
import JetButton from '@/Jetstream/Button.vue'
import JetConfirmsPassword from '@/Jetstream/ConfirmsPassword.vue'
import JetDangerButton from '@/Jetstream/DangerButton.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
export default defineComponent({
components: {
JetActionSection,
JetButton,
JetConfirmsPassword,
JetDangerButton,
JetSecondaryButton,
},
data() {
return {
enabling: false,
disabling: false,
qrCode: null,
recoveryCodes: [],
}
},
methods: {
enableTwoFactorAuthentication() {
this.enabling = true
this.$inertia.post('/user/two-factor-authentication', {}, {
preserveScroll: true,
onSuccess: () => Promise.all([
this.showQrCode(),
this.showRecoveryCodes(),
]),
onFinish: () => (this.enabling = false),
})
},
showQrCode() {
return axios.get('/user/two-factor-qr-code')
.then(response => {
this.qrCode = response.data.svg
})
},
showRecoveryCodes() {
return axios.get('/user/two-factor-recovery-codes')
.then(response => {
this.recoveryCodes = response.data
})
},
regenerateRecoveryCodes() {
axios.post('/user/two-factor-recovery-codes')
.then(response => {
this.showRecoveryCodes()
})
},
disableTwoFactorAuthentication() {
this.disabling = true
this.$inertia.delete('/user/two-factor-authentication', {
preserveScroll: true,
onSuccess: () => (this.disabling = false),
})
},
},
computed: {
twoFactorEnabled() {
return ! this.enabling && this.$page.props.user.two_factor_enabled
}
}
})
</script>

View File

@ -0,0 +1,93 @@
<template>
<jet-form-section @submitted="updatePassword">
<template #title>
Update Password
</template>
<template #description>
Ensure your account is using a long, random password to stay secure.
</template>
<template #form>
<div class="col-span-6 sm:col-span-4">
<jet-label for="current_password" value="Current Password" />
<jet-input id="current_password" type="password" class="mt-1 block w-full" v-model="form.current_password" ref="current_password" autocomplete="current-password" />
<jet-input-error :message="form.errors.current_password" class="mt-2" />
</div>
<div class="col-span-6 sm:col-span-4">
<jet-label for="password" value="New Password" />
<jet-input id="password" type="password" class="mt-1 block w-full" v-model="form.password" ref="password" autocomplete="new-password" />
<jet-input-error :message="form.errors.password" class="mt-2" />
</div>
<div class="col-span-6 sm:col-span-4">
<jet-label for="password_confirmation" value="Confirm Password" />
<jet-input id="password_confirmation" type="password" class="mt-1 block w-full" v-model="form.password_confirmation" autocomplete="new-password" />
<jet-input-error :message="form.errors.password_confirmation" class="mt-2" />
</div>
</template>
<template #actions>
<jet-action-message :on="form.recentlySuccessful" class="mr-3">
Saved.
</jet-action-message>
<jet-button :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Save
</jet-button>
</template>
</jet-form-section>
</template>
<script>
import { defineComponent } from 'vue'
import JetActionMessage from '@/Jetstream/ActionMessage.vue'
import JetButton from '@/Jetstream/Button.vue'
import JetFormSection from '@/Jetstream/FormSection.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetLabel from '@/Jetstream/Label.vue'
export default defineComponent({
components: {
JetActionMessage,
JetButton,
JetFormSection,
JetInput,
JetInputError,
JetLabel,
},
data() {
return {
form: this.$inertia.form({
current_password: '',
password: '',
password_confirmation: '',
}),
}
},
methods: {
updatePassword() {
this.form.put(route('user-password.update'), {
errorBag: 'updatePassword',
preserveScroll: true,
onSuccess: () => this.form.reset(),
onError: () => {
if (this.form.errors.password) {
this.form.reset('password', 'password_confirmation')
this.$refs.password.focus()
}
if (this.form.errors.current_password) {
this.form.reset('current_password')
this.$refs.current_password.focus()
}
}
})
},
},
})
</script>

View File

@ -0,0 +1,174 @@
<template>
<jet-form-section @submitted="updateProfileInformation">
<template #title>
Profile Information
</template>
<template #description>
Update your account's profile information and email address.
</template>
<template #form>
<!-- Profile Photo -->
<div class="col-span-6 sm:col-span-4" v-if="$page.props.jetstream.managesProfilePhotos">
<!-- Profile Photo File Input -->
<input type="file" class="hidden"
ref="photo"
@change="updatePhotoPreview">
<jet-label for="photo" value="Photo" />
<!-- Current Profile Photo -->
<div class="mt-2" v-show="! photoPreview">
<img :src="user.profile_photo_url" :alt="user.name" class="rounded-full h-20 w-20 object-cover">
</div>
<!-- New Profile Photo Preview -->
<div class="mt-2" v-show="photoPreview">
<span class="block rounded-full w-20 h-20 bg-cover bg-no-repeat bg-center"
:style="'background-image: url(\'' + photoPreview + '\');'">
</span>
</div>
<jet-secondary-button class="mt-2 mr-2" type="button" @click.prevent="selectNewPhoto">
Select A New Photo
</jet-secondary-button>
<jet-secondary-button type="button" class="mt-2" @click.prevent="deletePhoto" v-if="user.profile_photo_path">
Remove Photo
</jet-secondary-button>
<jet-input-error :message="form.errors.photo" class="mt-2" />
</div>
<!-- Name -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="name" value="Name" />
<jet-input id="name" type="text" class="mt-1 block w-full" v-model="form.name" autocomplete="given-name" />
<jet-input-error :message="form.errors.name" class="mt-2" />
</div>
<!-- Surname -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="surname" value="Surname" />
<jet-input id="surname" type="text" class="mt-1 block w-full" v-model="form.surname" autocomplete="family-name" />
<jet-input-error :message="form.errors.surname" class="mt-2" />
</div>
<!-- Email -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="email" value="Email" />
<jet-input id="email" type="email" class="mt-1 block w-full" v-model="form.email" />
<jet-input-error :message="form.errors.email" class="mt-2" />
</div>
<!-- Timezone -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="timezone_name" value="Timezone" />
<jet-input id="timezone_name" type="text" list="" class="mt-1 block w-full" v-model="form.timezone_name" />
<jet-input-error :message="form.errors.timezone_name" class="mt-2" />
<datalist id="timezones">
<option v-for="timezone in timezones" :key="timezone" :value="timezone"></option>
</datalist>
</div>
</template>
<template #actions>
<jet-action-message :on="form.recentlySuccessful" class="mr-3">
Saved.
</jet-action-message>
<jet-button :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Save
</jet-button>
</template>
</jet-form-section>
</template>
<script>
import { defineComponent } from 'vue'
import JetButton from '@/Jetstream/Button.vue'
import JetFormSection from '@/Jetstream/FormSection.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetLabel from '@/Jetstream/Label.vue'
import JetActionMessage from '@/Jetstream/ActionMessage.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
export default defineComponent({
components: {
JetActionMessage,
JetButton,
JetFormSection,
JetInput,
JetInputError,
JetLabel,
JetSecondaryButton,
},
props: ['user', 'timezones'],
data() {
return {
form: this.$inertia.form({
_method: 'PUT',
name: this.user.name,
surname: this.user.surname,
email: this.user.email,
timezone_name: this.user.timezone_name,
photo: null,
}),
photoPreview: null,
}
},
methods: {
updateProfileInformation() {
if (this.$refs.photo) {
this.form.photo = this.$refs.photo.files[0]
}
this.form.post(route('user-profile-information.update'), {
errorBag: 'updateProfileInformation',
preserveScroll: true,
onSuccess: () => (this.clearPhotoFileInput()),
});
},
selectNewPhoto() {
this.$refs.photo.click();
},
updatePhotoPreview() {
const photo = this.$refs.photo.files[0];
if (! photo) return;
const reader = new FileReader();
reader.onload = (e) => {
this.photoPreview = e.target.result;
};
reader.readAsDataURL(photo);
},
deletePhoto() {
this.$inertia.delete(route('current-user-photo.destroy'), {
preserveScroll: true,
onSuccess: () => {
this.photoPreview = null;
this.clearPhotoFileInput();
},
});
},
clearPhotoFileInput() {
if (this.$refs.photo?.value) {
this.$refs.photo.value = null;
}
},
},
})
</script>

View File

@ -0,0 +1,68 @@
<template>
<app-layout title="Profile">
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Profile
</h2>
</template>
<div>
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<div v-if="$page.props.jetstream.canUpdateProfileInformation">
<update-profile-information-form :user="$page.props.user" />
<jet-section-border />
</div>
<div v-if="$page.props.jetstream.canUpdatePassword">
<update-password-form class="mt-10 sm:mt-0" />
<jet-section-border />
</div>
<div v-if="$page.props.jetstream.canManageTwoFactorAuthentication">
<two-factor-authentication-form class="mt-10 sm:mt-0" />
<jet-section-border />
</div>
<logout-other-browser-sessions-form :sessions="sessions" class="mt-10 sm:mt-0" />
<template v-if="$page.props.jetstream.hasAccountDeletionFeatures">
<jet-section-border />
<delete-user-form class="mt-10 sm:mt-0" />
</template>
</div>
</div>
</app-layout>
</template>
<script>
import { defineComponent } from 'vue'
import AppLayout from '@/Layouts/AppLayout.vue'
import DeleteUserForm from '@/Pages/Profile/Partials/DeleteUserForm.vue'
import JetSectionBorder from '@/Jetstream/SectionBorder.vue'
import LogoutOtherBrowserSessionsForm from '@/Pages/Profile/Partials/LogoutOtherBrowserSessionsForm.vue'
import TwoFactorAuthenticationForm from '@/Pages/Profile/Partials/TwoFactorAuthenticationForm.vue'
import UpdatePasswordForm from '@/Pages/Profile/Partials/UpdatePasswordForm.vue'
import UpdateProfileInformationForm from '@/Pages/Profile/Partials/UpdateProfileInformationForm.vue'
export default defineComponent({
props: {
sessions: Array,
timezones: Array,
allTimes: Array,
},
components: {
AppLayout,
DeleteUserForm,
JetSectionBorder,
LogoutOtherBrowserSessionsForm,
TwoFactorAuthenticationForm,
UpdatePasswordForm,
UpdateProfileInformationForm,
},
})
</script>

View File

@ -1,107 +1,82 @@
<template>
<jet-action-section>
<template #title>
Delete Account
</template>
<script setup>
import { ref } from 'vue'
import { useForm } from '@inertiajs/inertia-vue3'
import JetActionSection from '@/Jetstream/ActionSection.vue'
import JetDialogModal from '@/Jetstream/DialogModal.vue'
import JetDangerButton from '@/Jetstream/DangerButton.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
<template #description>
Permanently delete your account.
</template>
const confirmingUserDeletion = ref(false)
const passwordInput = ref(null)
const form = useForm({
password: '',
})
// computed properties
// watchers
// lifecycle hooks
// methods
const confirmUserDeletion = () => {
confirmingUserDeletion.value = true
setTimeout(() => passwordInput.value.focus(), 250)
}
const closeModal = () => {
confirmingUserDeletion.value = false
form.reset()
}
const deleteUser = () => {
form.delete(route('current-user.destroy'), {
preserveScroll: true,
onSuccess: () => closeModal(),
onError: () => passwordInput.value.focus(),
onFinish: () => form.reset(),
})
}
</script>
<template>
<JetActionSection>
<template #title>Delete Account</template>
<template #description>Permanently delete your account.</template>
<template #content>
<div class="max-w-xl text-sm text-gray-600">
Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.
<div class="max-w-xl">
<p class="text-sm text-gray-600">Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.</p>
</div>
<div class="mt-5">
<jet-danger-button @click="confirmUserDeletion">
Delete Account
</jet-danger-button>
<JetDangerButton @click="confirmUserDeletion">Delete Account</JetDangerButton>
</div>
<!-- Delete Account Confirmation Modal -->
<jet-dialog-modal :show="confirmingUserDeletion" @close="closeModal">
<template #title>
Delete Account
</template>
<JetDialogModal :show="confirmingUserDeletion" @close="closeModal">
<template #title>Delete Account</template>
<template #content>
Are you sure you want to delete your account? Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.
<p>Are you sure you want to delete your account? Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.</p>
<div class="mt-4">
<jet-input type="password" class="mt-1 block w-3/4" placeholder="Password"
ref="password"
v-model="form.password"
@keyup.enter="deleteUser" />
<jet-input-error :message="form.errors.password" class="mt-2" />
<JetInput type="password" ref="passwordInput" placeholder="Password" v-model="form.password" class="mt-1 block w-3/4" @keyup.enter="deleteUser" />
<JetInputError :message="form.errors.password" class="mt-2" />
</div>
</template>
<template #footer>
<jet-secondary-button @click="closeModal">
Cancel
</jet-secondary-button>
<JetSecondaryButton @click="closeModal">Cancel</JetSecondaryButton>
<jet-danger-button class="ml-2" @click="deleteUser" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Delete Account
</jet-danger-button>
<JetDangerButton class="ml-3" :class="{ 'opacity-25': form.processing }" :disabled="form.processing" @click="deleteUser"
>Delete Account</JetDangerButton>
</template>
</jet-dialog-modal>
</JetDialogModal>
</template>
</jet-action-section>
</JetActionSection>
</template>
<script>
import { defineComponent } from 'vue'
import JetActionSection from '@/Jetstream/ActionSection.vue'
import JetDialogModal from '@/Jetstream/DialogModal.vue'
import JetDangerButton from '@/Jetstream/DangerButton.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
export default defineComponent({
components: {
JetActionSection,
JetDangerButton,
JetDialogModal,
JetInput,
JetInputError,
JetSecondaryButton,
},
data() {
return {
confirmingUserDeletion: false,
form: this.$inertia.form({
password: '',
})
}
},
methods: {
confirmUserDeletion() {
this.confirmingUserDeletion = true;
setTimeout(() => this.$refs.password.focus(), 250)
},
deleteUser() {
this.form.delete(route('current-user.destroy'), {
preserveScroll: true,
onSuccess: () => this.closeModal(),
onError: () => this.$refs.password.focus(),
onFinish: () => this.form.reset(),
})
},
closeModal() {
this.confirmingUserDeletion = false
this.form.reset()
},
},
})
</script>

View File

@ -1,5 +1,54 @@
<script setup>
import { ref } from 'vue'
import { useForm } from '@inertiajs/inertia-vue3'
import JetActionMessage from '@/Jetstream/ActionMessage.vue'
import JetActionSection from '@/Jetstream/ActionSection.vue'
import JetButton from '@/Jetstream/Button.vue'
import JetDialogModal from '@/Jetstream/DialogModal.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
const props = defineProps({
sessions: Array,
})
const confirmingLogout = ref(false)
const passwordInput = ref(null)
const form = useForm({
password: '',
})
// computed properties
// watchers
// lifecycle hooks
// methods
const confirmLogout = () => {
confirmingLogout.value = true
setTimeout(() => passwordInput.value.focus(), 250)
};
const logoutOtherBrowserSessions = () => {
form.delete(route('other-browser-sessions.destroy'), {
preserveScroll: true,
onSuccess: () => closeModal(),
onError: () => passwordInput.value.focus(),
onFinish: () => form.reset(),
})
}
const closeModal = () => {
confirmingLogout.value = false
form.reset()
}
</script>
<template>
<jet-action-section>
<JetActionSection>
<template #title>
Browser Sessions
</template>
@ -14,28 +63,53 @@
</div>
<!-- Other Browser Sessions -->
<div class="mt-5 space-y-6" v-if="sessions.length > 0">
<div class="flex items-center" v-for="(session, i) in sessions" :key="i">
<div v-if="sessions.length > 0" class="mt-5 space-y-6">
<div v-for="(session, i) in sessions" :key="i" class="flex items-center">
<div>
<svg fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor" class="w-8 h-8 text-gray-500" v-if="session.agent.is_desktop">
<path d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
<svg
v-if="session.agent.is_desktop"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
stroke="currentColor"
class="w-8 h-8 text-gray-500"
>
<path d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" class="w-8 h-8 text-gray-500" v-else>
<path d="M0 0h24v24H0z" stroke="none"></path><rect x="7" y="4" width="10" height="16" rx="1"></rect><path d="M11 5h2M12 17v.01"></path>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
class="w-8 h-8 text-gray-500"
>
<path d="M0 0h24v24H0z" stroke="none" /><rect
x="7"
y="4"
width="10"
height="16"
rx="1"
/><path d="M11 5h2M12 17v.01" />
</svg>
</div>
<div class="ml-3">
<div class="text-sm text-gray-600">
{{ session.agent.platform }} - {{ session.agent.browser }}
{{ session.agent.platform ? session.agent.platform : 'Unknown' }} - {{ session.agent.browser ? session.agent.browser : 'Unknown' }}
</div>
<div>
<div class="text-xs text-gray-500">
{{ session.ip_address }},
<span class="text-green-500 font-semibold" v-if="session.is_current_device">This device</span>
<span v-if="session.is_current_device" class="text-green-500 font-semibold">This device</span>
<span v-else>Last active {{ session.last_active }}</span>
</div>
</div>
@ -44,17 +118,17 @@
</div>
<div class="flex items-center mt-5">
<jet-button @click="confirmLogout">
<JetButton @click="confirmLogout">
Log Out Other Browser Sessions
</jet-button>
</JetButton>
<jet-action-message :on="form.recentlySuccessful" class="ml-3">
<JetActionMessage :on="form.recentlySuccessful" class="ml-3">
Done.
</jet-action-message>
</JetActionMessage>
</div>
<!-- Log Out Other Devices Confirmation Modal -->
<jet-dialog-modal :show="confirmingLogout" @close="closeModal">
<JetDialogModal :show="confirmingLogout" @close="closeModal">
<template #title>
Log Out Other Browser Sessions
</template>
@ -63,83 +137,34 @@
Please enter your password to confirm you would like to log out of your other browser sessions across all of your devices.
<div class="mt-4">
<jet-input type="password" class="mt-1 block w-3/4" placeholder="Password"
ref="password"
v-model="form.password"
@keyup.enter="logoutOtherBrowserSessions" />
<JetInput
ref="passwordInput"
v-model="form.password"
type="password"
class="mt-1 block w-3/4"
placeholder="Password"
@keyup.enter="logoutOtherBrowserSessions"
/>
<jet-input-error :message="form.errors.password" class="mt-2" />
<JetInputError :message="form.errors.password" class="mt-2" />
</div>
</template>
<template #footer>
<jet-secondary-button @click="closeModal">
<JetSecondaryButton @click="closeModal">
Cancel
</jet-secondary-button>
</JetSecondaryButton>
<jet-button class="ml-2" @click="logoutOtherBrowserSessions" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
<JetButton
class="ml-3"
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
@click="logoutOtherBrowserSessions"
>
Log Out Other Browser Sessions
</jet-button>
</JetButton>
</template>
</jet-dialog-modal>
</JetDialogModal>
</template>
</jet-action-section>
</JetActionSection>
</template>
<script>
import { defineComponent } from 'vue'
import JetActionMessage from '@/Jetstream/ActionMessage.vue'
import JetActionSection from '@/Jetstream/ActionSection.vue'
import JetButton from '@/Jetstream/Button.vue'
import JetDialogModal from '@/Jetstream/DialogModal.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
export default defineComponent({
props: ['sessions'],
components: {
JetActionMessage,
JetActionSection,
JetButton,
JetDialogModal,
JetInput,
JetInputError,
JetSecondaryButton,
},
data() {
return {
confirmingLogout: false,
form: this.$inertia.form({
password: '',
})
}
},
methods: {
confirmLogout() {
this.confirmingLogout = true
setTimeout(() => this.$refs.password.focus(), 250)
},
logoutOtherBrowserSessions() {
this.form.delete(route('other-browser-sessions.destroy'), {
preserveScroll: true,
onSuccess: () => this.closeModal(),
onError: () => this.$refs.password.focus(),
onFinish: () => this.form.reset(),
})
},
closeModal() {
this.confirmingLogout = false
this.form.reset()
},
},
})
</script>

View File

@ -1,167 +1,117 @@
<template>
<jet-action-section>
<template #title>
Two Factor Authentication
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { Inertia } from '@inertiajs/inertia'
import { useForm, usePage } from '@inertiajs/inertia-vue3'
import JetActionSection from '@/Jetstream/ActionSection.vue'
import JetButton from '@/Jetstream/Button.vue'
import JetConfirmsPassword from '@/Jetstream/ConfirmsPassword.vue'
import JetDangerButton from '@/Jetstream/DangerButton.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetLabel from '@/Jetstream/Label.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
<template #description>
Add additional security to your account using two factor authentication.
</template>
const props = defineProps({
requiresConfirmation: Boolean,
})
<template #content>
<h3 class="text-lg font-medium text-gray-900" v-if="twoFactorEnabled">
You have enabled two factor authentication.
</h3>
const enabling = ref(false)
const confirming = ref(false)
const disabling = ref(false)
const qrCode = ref(null)
const setupKey = ref(null)
const recoveryCodes = ref([])
<h3 class="text-lg font-medium text-gray-900" v-else>
You have not enabled two factor authentication.
</h3>
const confirmationForm = useForm({
code: '',
})
<div class="mt-3 max-w-xl text-sm text-gray-600">
<p>
When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve this token from your phone's Google Authenticator application.
</p>
</div>
// computed properties
const twoFactorEnabled = computed(
() => ! enabling.value && usePage().props.value.user?.two_factor_enabled,
)
<div v-if="twoFactorEnabled">
<div v-if="qrCode">
<div class="mt-4 max-w-xl text-sm text-gray-600">
<p class="font-semibold">
Two factor authentication is now enabled. Scan the following QR code using your phone's authenticator application.
</p>
</div>
// watchers
watch(twoFactorEnabled, () => {
if (! twoFactorEnabled.value) {
confirmationForm.reset()
confirmationForm.clearErrors()
}
})
<div class="mt-4" v-html="qrCode">
</div>
</div>
// lifecycle hooks
<div v-if="recoveryCodes.length > 0">
<div class="mt-4 max-w-xl text-sm text-gray-600">
<p class="font-semibold">
Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.
</p>
</div>
// methods
const enableTwoFactorAuthentication = () => {
enabling.value = true
<div class="grid gap-1 max-w-xl mt-4 px-4 py-4 font-mono text-sm bg-gray-100 rounded-lg">
<div v-for="code in recoveryCodes" :key="code">
{{ code }}
</div>
</div>
</div>
</div>
Inertia.post('/user/two-factor-authentication', {}, {
preserveScroll: true,
onSuccess: () => Promise.all([
showQrCode(),
showSetupKey(),
showRecoveryCodes(),
]),
onFinish: () => {
enabling.value = false;
confirming.value = props.requiresConfirmation
},
})
}
<div class="mt-5">
<div v-if="! twoFactorEnabled">
<jet-confirms-password @confirmed="enableTwoFactorAuthentication">
<jet-button type="button" :class="{ 'opacity-25': enabling }" :disabled="enabling">
Enable
</jet-button>
</jet-confirms-password>
</div>
<div v-else>
<jet-confirms-password @confirmed="regenerateRecoveryCodes">
<jet-secondary-button class="mr-3"
v-if="recoveryCodes.length > 0">
Regenerate Recovery Codes
</jet-secondary-button>
</jet-confirms-password>
<jet-confirms-password @confirmed="showRecoveryCodes">
<jet-secondary-button class="mr-3" v-if="recoveryCodes.length === 0">
Show Recovery Codes
</jet-secondary-button>
</jet-confirms-password>
<jet-confirms-password @confirmed="disableTwoFactorAuthentication">
<jet-danger-button
:class="{ 'opacity-25': disabling }"
:disabled="disabling">
Disable
</jet-danger-button>
</jet-confirms-password>
</div>
</div>
</template>
</jet-action-section>
</template>
<script>
import { defineComponent } from 'vue'
import JetActionSection from '@/Jetstream/ActionSection.vue'
import JetButton from '@/Jetstream/Button.vue'
import JetConfirmsPassword from '@/Jetstream/ConfirmsPassword.vue'
import JetDangerButton from '@/Jetstream/DangerButton.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
export default defineComponent({
components: {
JetActionSection,
JetButton,
JetConfirmsPassword,
JetDangerButton,
JetSecondaryButton,
},
data() {
return {
enabling: false,
disabling: false,
qrCode: null,
recoveryCodes: [],
}
},
methods: {
enableTwoFactorAuthentication() {
this.enabling = true
this.$inertia.post('/user/two-factor-authentication', {}, {
preserveScroll: true,
onSuccess: () => Promise.all([
this.showQrCode(),
this.showRecoveryCodes(),
]),
onFinish: () => (this.enabling = false),
})
},
showQrCode() {
return axios.get('/user/two-factor-qr-code')
.then(response => {
this.qrCode = response.data.svg
})
},
showRecoveryCodes() {
return axios.get('/user/two-factor-recovery-codes')
.then(response => {
this.recoveryCodes = response.data
})
},
regenerateRecoveryCodes() {
axios.post('/user/two-factor-recovery-codes')
.then(response => {
this.showRecoveryCodes()
})
},
disableTwoFactorAuthentication() {
this.disabling = true
this.$inertia.delete('/user/two-factor-authentication', {
preserveScroll: true,
onSuccess: () => (this.disabling = false),
})
},
},
computed: {
twoFactorEnabled() {
return ! this.enabling && this.$page.props.user.two_factor_enabled
}
}
const showQrCode = () => {
return axios.get('/user/two-factor-qr-code')
.then(response => {
qrCode.value = response.data.svg
})
}
const showSetupKey = () => {
return axios.get('/user/two-factor-secret-key')
.then(response => {
setupKey.value = response.data.secretKey
})
}
const showRecoveryCodes = () => {
return axios.get('/user/two-factor-recovery-codes')
.then(response => {
recoveryCodes.value = response.data
})
}
const confirmTwoFactorAuthentication = () => {
confirmationForm.post('/user/confirmed-two-factor-authentication', {
errorBag: "confirmTwoFactorAuthentication",
preserveScroll: true,
preserveState: true,
onSuccess: () => {
confirming.value = false
qrCode.value = null
setupKey.value = null
},
})
}
const regenerateRecoveryCodes = () => {
axios
.post('/user/two-factor-recovery-codes')
.then(() => showRecoveryCodes())
}
const disableTwoFactorAuthentication = () => {
disabling.value = true
Inertia.delete('/user/two-factor-authentication', {
preserveScroll: true,
onSuccess: () => {
disabling.value = false
confirming.value = false
},
})
}
</script>
<template>
<div></div>
</template>

View File

@ -1,93 +1,19 @@
<template>
<jet-form-section @submitted="updatePassword">
<template #title>
Update Password
</template>
<script setup>
import { reactive, ref, computed, watch, onBeforeMount, onMounted } from 'vue'
import { useForm } from '@inertiajs/inertia-vue3'
<template #description>
Ensure your account is using a long, random password to stay secure.
</template>
const props = defineProps({})
<template #form>
<div class="col-span-6 sm:col-span-4">
<jet-label for="current_password" value="Current Password" />
<jet-input id="current_password" type="password" class="mt-1 block w-full" v-model="form.current_password" ref="current_password" autocomplete="current-password" />
<jet-input-error :message="form.errors.current_password" class="mt-2" />
</div>
// computed properties
<div class="col-span-6 sm:col-span-4">
<jet-label for="password" value="New Password" />
<jet-input id="password" type="password" class="mt-1 block w-full" v-model="form.password" ref="password" autocomplete="new-password" />
<jet-input-error :message="form.errors.password" class="mt-2" />
</div>
// watchers
<div class="col-span-6 sm:col-span-4">
<jet-label for="password_confirmation" value="Confirm Password" />
<jet-input id="password_confirmation" type="password" class="mt-1 block w-full" v-model="form.password_confirmation" autocomplete="new-password" />
<jet-input-error :message="form.errors.password_confirmation" class="mt-2" />
</div>
</template>
// lifecycle hooks
<template #actions>
<jet-action-message :on="form.recentlySuccessful" class="mr-3">
Saved.
</jet-action-message>
// methods
<jet-button :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Save
</jet-button>
</template>
</jet-form-section>
</template>
<script>
import { defineComponent } from 'vue'
import JetActionMessage from '@/Jetstream/ActionMessage.vue'
import JetButton from '@/Jetstream/Button.vue'
import JetFormSection from '@/Jetstream/FormSection.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetLabel from '@/Jetstream/Label.vue'
export default defineComponent({
components: {
JetActionMessage,
JetButton,
JetFormSection,
JetInput,
JetInputError,
JetLabel,
},
data() {
return {
form: this.$inertia.form({
current_password: '',
password: '',
password_confirmation: '',
}),
}
},
methods: {
updatePassword() {
this.form.put(route('user-password.update'), {
errorBag: 'updatePassword',
preserveScroll: true,
onSuccess: () => this.form.reset(),
onError: () => {
if (this.form.errors.password) {
this.form.reset('password', 'password_confirmation')
this.$refs.password.focus()
}
if (this.form.errors.current_password) {
this.form.reset('current_password')
this.$refs.current_password.focus()
}
}
})
},
},
})
</script>
<template>
<div></div>
</template>

View File

@ -1,174 +1,19 @@
<template>
<jet-form-section @submitted="updateProfileInformation">
<template #title>
Profile Information
</template>
<script setup>
import { reactive, ref, computed, watch, onBeforeMount, onMounted } from 'vue'
import { useForm } from '@inertiajs/inertia-vue3'
<template #description>
Update your account's profile information and email address.
</template>
const props = defineProps({})
<template #form>
<!-- Profile Photo -->
<div class="col-span-6 sm:col-span-4" v-if="$page.props.jetstream.managesProfilePhotos">
<!-- Profile Photo File Input -->
<input type="file" class="hidden"
ref="photo"
@change="updatePhotoPreview">
// computed properties
<jet-label for="photo" value="Photo" />
// watchers
<!-- Current Profile Photo -->
<div class="mt-2" v-show="! photoPreview">
<img :src="user.profile_photo_url" :alt="user.name" class="rounded-full h-20 w-20 object-cover">
</div>
// lifecycle hooks
<!-- New Profile Photo Preview -->
<div class="mt-2" v-show="photoPreview">
<span class="block rounded-full w-20 h-20 bg-cover bg-no-repeat bg-center"
:style="'background-image: url(\'' + photoPreview + '\');'">
</span>
</div>
// methods
<jet-secondary-button class="mt-2 mr-2" type="button" @click.prevent="selectNewPhoto">
Select A New Photo
</jet-secondary-button>
<jet-secondary-button type="button" class="mt-2" @click.prevent="deletePhoto" v-if="user.profile_photo_path">
Remove Photo
</jet-secondary-button>
<jet-input-error :message="form.errors.photo" class="mt-2" />
</div>
<!-- Name -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="name" value="Name" />
<jet-input id="name" type="text" class="mt-1 block w-full" v-model="form.name" autocomplete="given-name" />
<jet-input-error :message="form.errors.name" class="mt-2" />
</div>
<!-- Surname -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="surname" value="Surname" />
<jet-input id="surname" type="text" class="mt-1 block w-full" v-model="form.surname" autocomplete="family-name" />
<jet-input-error :message="form.errors.surname" class="mt-2" />
</div>
<!-- Email -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="email" value="Email" />
<jet-input id="email" type="email" class="mt-1 block w-full" v-model="form.email" />
<jet-input-error :message="form.errors.email" class="mt-2" />
</div>
<!-- Timezone -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="timezone_name" value="Timezone" />
<jet-input id="timezone_name" type="text" list="" class="mt-1 block w-full" v-model="form.timezone_name" />
<jet-input-error :message="form.errors.timezone_name" class="mt-2" />
<datalist id="timezones">
<option v-for="timezone in timezones" :key="timezone" :value="timezone"></option>
</datalist>
</div>
</template>
<template #actions>
<jet-action-message :on="form.recentlySuccessful" class="mr-3">
Saved.
</jet-action-message>
<jet-button :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Save
</jet-button>
</template>
</jet-form-section>
</template>
<script>
import { defineComponent } from 'vue'
import JetButton from '@/Jetstream/Button.vue'
import JetFormSection from '@/Jetstream/FormSection.vue'
import JetInput from '@/Jetstream/Input.vue'
import JetInputError from '@/Jetstream/InputError.vue'
import JetLabel from '@/Jetstream/Label.vue'
import JetActionMessage from '@/Jetstream/ActionMessage.vue'
import JetSecondaryButton from '@/Jetstream/SecondaryButton.vue'
export default defineComponent({
components: {
JetActionMessage,
JetButton,
JetFormSection,
JetInput,
JetInputError,
JetLabel,
JetSecondaryButton,
},
props: ['user', 'timezones'],
data() {
return {
form: this.$inertia.form({
_method: 'PUT',
name: this.user.name,
surname: this.user.surname,
email: this.user.email,
timezone_name: this.user.timezone_name,
photo: null,
}),
photoPreview: null,
}
},
methods: {
updateProfileInformation() {
if (this.$refs.photo) {
this.form.photo = this.$refs.photo.files[0]
}
this.form.post(route('user-profile-information.update'), {
errorBag: 'updateProfileInformation',
preserveScroll: true,
onSuccess: () => (this.clearPhotoFileInput()),
});
},
selectNewPhoto() {
this.$refs.photo.click();
},
updatePhotoPreview() {
const photo = this.$refs.photo.files[0];
if (! photo) return;
const reader = new FileReader();
reader.onload = (e) => {
this.photoPreview = e.target.result;
};
reader.readAsDataURL(photo);
},
deletePhoto() {
this.$inertia.delete(route('current-user-photo.destroy'), {
preserveScroll: true,
onSuccess: () => {
this.photoPreview = null;
this.clearPhotoFileInput();
},
});
},
clearPhotoFileInput() {
if (this.$refs.photo?.value) {
this.$refs.photo.value = null;
}
},
},
})
</script>
<template>
<div></div>
</template>

View File

@ -1,68 +1,53 @@
<template>
<app-layout title="Profile">
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Profile
</h2>
</template>
<div>
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<div v-if="$page.props.jetstream.canUpdateProfileInformation">
<update-profile-information-form :user="$page.props.user" />
<jet-section-border />
</div>
<div v-if="$page.props.jetstream.canUpdatePassword">
<update-password-form class="mt-10 sm:mt-0" />
<jet-section-border />
</div>
<div v-if="$page.props.jetstream.canManageTwoFactorAuthentication">
<two-factor-authentication-form class="mt-10 sm:mt-0" />
<jet-section-border />
</div>
<logout-other-browser-sessions-form :sessions="sessions" class="mt-10 sm:mt-0" />
<template v-if="$page.props.jetstream.hasAccountDeletionFeatures">
<jet-section-border />
<delete-user-form class="mt-10 sm:mt-0" />
</template>
</div>
</div>
</app-layout>
</template>
<script>
import { defineComponent } from 'vue'
<script setup>
import { reactive, ref, computed, watch, onBeforeMount, onMounted } from 'vue'
import AppLayout from '@/Layouts/AppLayout.vue'
import DeleteUserForm from '@/Pages/Profile/Partials/DeleteUserForm.vue'
import JetSectionBorder from '@/Jetstream/SectionBorder.vue'
import LogoutOtherBrowserSessionsForm from '@/Pages/Profile/Partials/LogoutOtherBrowserSessionsForm.vue'
import TwoFactorAuthenticationForm from '@/Pages/Profile/Partials/TwoFactorAuthenticationForm.vue'
import UpdatePasswordForm from '@/Pages/Profile/Partials/UpdatePasswordForm.vue'
import UpdateProfileInformationForm from '@/Pages/Profile/Partials/UpdateProfileInformationForm.vue'
export default defineComponent({
props: {
sessions: Array,
timezones: Array,
allTimes: Array,
},
components: {
AppLayout,
DeleteUserForm,
JetSectionBorder,
LogoutOtherBrowserSessionsForm,
TwoFactorAuthenticationForm,
UpdatePasswordForm,
UpdateProfileInformationForm,
},
const props = defineProps({
confirmsTwoFactorAuthentication: Boolean,
sessions: Array,
timezones: Array,
})
// computed properties
// watchers
// lifecycle hooks
// methods
</script>
<template>
<app-layout title="Profile">
<template #header>Profile</template>
<div class="col-span-6">
<div v-if="$page.props.jetstream.canUpdateProfileInformation">
<UpdateProfileInformationForm :user="$page.props.user" />
</div>
<div v-if="$page.props.jetstream.canUpdatePassword">
<UpdatePasswordForm class="mt-10 sm:mt-0" />
</div>
<div v-if="$page.props.jetstream.canManageTwoFactorAuthentication">
<TwoFactorAuthenticationForm
:requires-confirmation="confirmsTwoFactorAuthentication"
class="mt-10 sm:mt-0"
/>
</div>
<LogoutOtherBrowserSessionsForm :sessions="sessions" class="mt-10 sm:mt-0" />
<template v-if="$page.props.jetstream.hasAccountDeletionFeatures">
<DeleteUserForm class="mt-10 sm:mt-0" />
</template>
</div>
</app-layout>
</template>

View File

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Set the base URL for all relative URLs within the document -->
<base href="https://example.com/">
<base href="{{ url('/') }}">
<meta name="robots" content="index,follow">
<meta name="googlebot" content="index,follow">
@ -17,32 +17,32 @@
<!-- Prefetching, preloading, prebrowsing -->
<meta http-equiv="x-dns-prefetch-control" content="off">
<link rel="dns-prefetch" href="//example.com/">
<link rel="preconnect" href="https://www.example.com/">
<link rel="prefetch" href="https://www.example.com/">
<link rel="prerender" href="https://example.com/">
<link rel="dns-prefetch" href="{{ config('app.domain', 'localhost') }}">
<link rel="preconnect" href="{{ url('/') }}">
<link rel="prefetch" href="{{ url('/') }}">
<link rel="prerender" href="{{ url('/') }}">
<link rel="preload" href="image.png" as="image">
<meta name="rating" content="General">
<meta name="url" content="{{ url('/') }}">
<meta name="subject" content="your website's subject">
<meta name="description" content="A description of the page">
<meta name="subject" content="{{ $pageSubject ?? "your website's subject" }}">
<meta name="description" content="{{ $pageDescription ?? "A description of the page" }}">
<title inertia>{{ config('app.name', 'Laravel') }}</title>
<!-- Name of web application (only should be used if the website is used as an app) -->
<meta name="application-name" content="Application Name">
<!-- <meta name="application-name" content="Application Name"> -->
<!-- Privacy -->
<meta name="twitter:dnt" content="on">
<meta name="pinterest" content="nopin" description="Sorry, you can't save from my website!">
<meta name="pinterest" content="nopin" description="No pinning allowed.">
<!-- analytics -->
<!-- -->
<!-- Helps prevent duplicate content issues -->
<link href="{{ url()->full() }}" rel="canonical">
<link href="{{ url()->current() }}" rel="canonical">
<!-- Gives a reference to a location in your document that may be in another language -->
{{-- <link href="https://de.example.com/2010/06/title-of-my-article" rel="alternate" hreflang="de"> --}}
@ -59,8 +59,8 @@
{{-- <link href="{{ url('/feed.atom') }}" type="application/atom+xml" title="Atom 0.3" rel="alternate"> --}}
<!-- oEmbed links -->
<link rel="alternate" type="application/json+oembed" href="https://example.com/services/oembed?url=http%3A%2F%2Fexample.com%2Ffoo%2F&amp;format=json" title="oEmbed Profile: JSON">
<link rel="alternate" type="text/xml+oembed" href="https://example.com/services/oembed?url=http%3A%2F%2Fexample.com%2Ffoo%2F&amp;format=xml" title="oEmbed Profile: XML">
{{-- <link rel="alternate" type="application/json+oembed" href="https://example.com/services/oembed?url={{ urlencode(url()->current()) }}&amp;format=json" title="oEmbed Profile: JSON"> --}}
{{-- <link rel="alternate" type="text/xml+oembed" href="https://example.com/services/oembed?url={{ urlencode(url()->current()) }}&amp;format=xml" title="oEmbed Profile: XML"> --}}
<!-- Favicon -->
{{-- <link href="{{ asset('/favicon.ico') }}" rel="icon" sizes="16x16" type="image/icon" integrity="{{ env('INTEGRITY_HASH_FAVICON_ICO') }}"> --}}
@ -68,7 +68,12 @@
{{-- <link href="{{ asset('/favicon.png') }}" rel="icon" sizes="192x192" integrity="{{ env('INTEGRITY_HASH_FAVICON_PNG') }}"> --}}
<!-- Font preloads (should be done for each font file) -->
<link href="{{ asset('/fonts/Nunito/Nunito-Regular.woff2') }}" rel="preload" as="font" type="font/woff2" integrity="{{ env('INTEGRITY_HASH_NUNITO_REGULAR_WOFF2_FONT') }}" crossorigin="anonymous">
<link href="{{ asset('/fonts/Nunito/Nunito-Bold.woff2') }}" rel="preload" as="font" type="font/woff2" crossorigin="anonymous">
<link href="{{ asset('/fonts/Nunito/Nunito-SemiBold.woff2') }}" rel="preload" as="font" type="font/woff2" crossorigin="anonymous">
<link href="{{ asset('/fonts/Nunito/Nunito-Regular.woff2') }}" rel="preload" as="font" type="font/woff2" crossorigin="anonymous">
<link href="{{ asset('/fonts/OpenSans/OpenSans-Bold.woff2') }}" rel="preload" as="font" type="font/woff2" crossorigin="anonymous">
<link href="{{ asset('/fonts/OpenSans/OpenSans-SemiBold.woff2') }}" rel="preload" as="font" type="font/woff2" crossorigin="anonymous">
<link href="{{ asset('/fonts/OpenSans/OpenSans-Regular.woff2') }}" rel="preload" as="font" type="font/woff2" crossorigin="anonymous">
<!-- CSS -->
<link href="{{ mix('/css/app.css') }}" rel="stylesheet" integrity="{{ env('INTEGRITY_HASH_APP_CSS') }}" media="screen">
@ -86,7 +91,7 @@
<script src="{{ mix('/js/app.js') }}" integrity="{{ env('INTEGRITY_HASH_APP_JS') }}" defer></script>
</head>
<body class="bg-gray-100 font-sans antialiased">
<x-country-flags></x-country-flags>
<x-flags></x-flags>
@inertia

View File

@ -1,10 +1 @@
<meta property="fb:app_id" content="123456789">
<meta property="og:url" content="https://example.com/page.html">
<meta property="og:type" content="website">
<meta property="og:title" content="Content Title">
<meta property="og:image" content="https://example.com/image.jpg">
<meta property="og:image:alt" content="A description of what is in the image (not a caption)">
<meta property="og:description" content="Description Here">
<meta property="og:site_name" content="Site Name">
<meta property="og:locale" content="en_US">
<meta property="article:author" content="">

View File

@ -0,0 +1,26 @@
<!-- https://ogp.me/ -->
<meta property="og:site_name" content="Site Name">
<meta property="og:type" content="website">
<meta property="og:url" content="https://example.com/page.html">
<meta property="og:locale" content="en_US">
<meta property="og:title" content="Content Title">
<meta property="og:description" content="Description Here">
<meta property="og:image" content="https://example.com/image.jpg">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="640">
<meta property="og:image:height" content="480">
<meta property="og:image:alt" content="A description of what is in the image (not a caption)">
<meta property="og:video" content="https://example.com/video.m4v">
<meta property="og:video:type" content="video/mp4">
<meta property="og:video:width" content="640">
<meta property="og:video:height" content="480">
<meta property="og:audio" content="https://example.com/sound.mp3">
<meta property="og:audio:type" content="audio/mpeg">
<meta property="article:author" content="">
<meta property="article:published_time" content="YYYY-MM-DDTHH:MM:SS+00:00">
<meta property="article:modified_time" content="YYYY-MM-DDTHH:MM:SS+00:00">
<meta property="article:expiration_time" content="YYYY-MM-DDTHH:MM:SS+00:00">

View File

@ -61,6 +61,19 @@ module.exports = {
900: '#8d0f11',
},
'totem-pole': {
50: '#fff0f0',
100: '#ffdddd',
200: '#ffc0c0',
300: '#ff9494',
400: '#ff5757',
500: '#ff2323',
600: '#ff0303',
700: '#d70000',
800: '#b10303',
900: '#8f0a0a',
},
// oranges
@ -134,6 +147,20 @@ module.exports = {
900: '#1a4d31',
},
'japanese-laurel': {
50: '#ebffe5',
100: '#d0ffc7',
200: '#a4ff95',
300: '#6bfe58',
400: '#3cf526',
500: '#18dc06',
600: '#0cad00',
700: '#0c8506',
800: '#0f690b',
900: '#10580f',
},
// teals
'aquamarine': {
50: '#eafff7',