Various UI fixes

This commit is contained in:
Timothée Jaussoin 2023-08-03 16:07:25 +00:00
parent f8bde4345f
commit ec3f123c9a
23 changed files with 550 additions and 255 deletions

View file

@ -34,8 +34,13 @@ class AccountController extends Controller
{
public function index(Request $request)
{
$accounts = Account::orderBy('updated_at', $request->get('updated_at_order', 'desc'))
->with('externalAccount');
$request->validate([
'order_by' => 'in:username,updated_at',
'order_sort' => 'in:asc,desc',
]);
$accounts = Account::with('externalAccount', 'contactsLists')
->orderBy($request->get('order_by', 'updated_at'), $request->get('order_sort', 'desc'));
if ($request->has('search')) {
$accounts = $accounts->where('username', 'like', '%' . $request->get('search') . '%');
@ -45,17 +50,26 @@ class AccountController extends Controller
$accounts->whereDate('updated_at', $request->get('updated_date'));
}
if ($request->has('contacts_list')) {
$accounts->whereHas('contactsLists', function ($query) use ($request) {
$query->where('id', $request->get('contacts_list'));
});
}
if ($request->has('domain')) {
$accounts->where('domain', $request->get('domain'));
}
return view('admin.account.index', [
'search' => $request->get('search'),
'updated_date' => $request->get('updated_date'),
'domains' => Account::groupBy('domain')->pluck('domain'),
'contacts_lists' => ContactsList::all()->pluck('title', 'id'),
'accounts' => $accounts->paginate(20)->appends($request->query()),
'updated_at_order' => $request->get('updated_at_order') == 'desc' ? 'asc' : 'desc'
]);
}
public function search(Request $request)
{
return redirect()->route('admin.account.index', $request->except('_token'));
return redirect()->route('admin.account.index', $request->except('_token', 'query'));
}
public function create(Request $request)

View file

@ -28,11 +28,17 @@ class ContactsListController extends Controller
{
public function index(Request $request)
{
$request->validate([
'order_by' => 'in:title,updated_at,contacts_count',
'order_sort' => 'in:asc,desc',
]);
$contactsLists = ContactsList::orderBy($request->get('order_by', 'updated_at'), $request->get('order_sort', 'desc'));
return view('admin.contacts_list.index', [
'contacts_lists' => ContactsList::orderBy('updated_at', $request->get('updated_at_order', 'desc'))
'contacts_lists' => $contactsLists
->paginate(20)
->appends($request->query()),
'updated_at_order' => $request->get('updated_at_order') == 'desc' ? 'asc' : 'desc'
]);
}

View file

@ -122,14 +122,23 @@ class AccountService
public function updatePhone(Request $request): ?Account
{
$request->validate([
$request->validate($this->api ? [
'code' => 'required|digits:4'
] : [
'number_1' => 'required|digits:1',
'number_2' => 'required|digits:1',
'number_3' => 'required|digits:1',
'number_4' => 'required|digits:1'
]);
$code = $this->api ? $request->get('code')
: $request->get('number_1') . $request->get('number_2') . $request->get('number_3') . $request->get('number_4');
$account = $request->user();
$phoneChangeCode = $account->phoneChangeCode()->firstOrFail();
if ($phoneChangeCode->code == $request->get('code')) {
if ($phoneChangeCode->code == $code) {
$account->alias()->delete();
$alias = new Alias;
@ -189,14 +198,22 @@ class AccountService
public function updateEmail(Request $request): ?Account
{
$request->validate([
$request->validate($this->api ? [
'code' => 'required|digits:4'
] : [
'number_1' => 'required|digits:1',
'number_2' => 'required|digits:1',
'number_3' => 'required|digits:1',
'number_4' => 'required|digits:1'
]);
$code = $this->api ? $request->get('code')
: $request->get('number_1') . $request->get('number_2') . $request->get('number_3') . $request->get('number_4');
$account = $request->user();
$emailChangeCode = $account->emailChangeCode()->firstOrFail();
if ($emailChangeCode->validate($request->get('code'))) {
if ($emailChangeCode->validate($code)) {
$account->email = $emailChangeCode->email;
$account->save();

View file

@ -333,11 +333,28 @@ content section {
box-sizing: border-box;
}
content section.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
@media screen and (max-width: 800px) {
content section.grid {
display: block;
}
}
content section.grid header,
content section.grid .large {
grid-column: span 2;
}
content section header {
display: flex;
gap: 1rem;
align-items: center;
margin-bottom: 1rem;
margin-bottom: 3rem;
}
content section header p {
@ -354,6 +371,7 @@ content section header > *.oppose {
content nav + section {
min-width: calc(80% - 20rem);
margin-bottom: 4rem;
}
/** Sidenav **/
@ -490,6 +508,13 @@ h3 {
font-size: 1.75rem;
color: var(--second-6);
padding: 0.5rem 0;
font-weight: bold;
}
h3 i {
line-height: 2rem;
margin-right: 1rem;
vertical-align: middle;
}
h4 {
@ -499,14 +524,20 @@ h4 {
}
p + h1, p + h2, p + h3, p + h4,
ul + h1, ul + h2, ul + h3, ul + h4 {
ul + h1, ul + h2, ul + h3, ul + h4, h3 + p {
margin-top: 1rem;
}
.line {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/** Badge **/
.badge {
background-color: var(--main-5);
background-color: var(--grey-4);
color: white;
border-radius: 0.5rem;
font-weight: 700;
@ -540,17 +571,18 @@ table tr td a {
table tr td,
table tr th {
line-height: 4rem;
padding: 0 1rem;
padding: 1rem;
font-size: 1.5rem;
}
table tr th {
padding: 0 1rem;
line-height: 4rem;
}
table tr td.line,
table tr th.line {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 0;
max-width: 100%;
}
table tr th,
@ -562,7 +594,7 @@ table tr th a {
}
table tr th a i {
font-size: 3rem;
font-size: 2.5rem;
vertical-align: middle;
}
@ -697,7 +729,7 @@ select.list_toggle {
}
.card {
background-color: var(--grey-2);
background-color: var(--grey-1);
border-radius: 1rem;
padding: 1rem;
margin-bottom: 1rem;
@ -711,4 +743,32 @@ select.list_toggle {
#chart {
min-height: 80vh;
}
/** Breadcrumb **/
.breadcrumb {
margin-bottom: 2rem;
}
.breadcrumb li {
display: inline-block;
font-size: 1.5rem;
line-height: 2rem;
}
.breadcrumb li a {
color: var(--main-5);
}
.breadcrumb li.active {
color: var(--grey-5);
}
.breadcrumb li + li:before {
content: ">";
margin: 0 1rem;
font-size: 1rem;
line-height: 2rem;
display: inline-block;
}

View file

@ -13,6 +13,10 @@
white-space: nowrap;
}
p .btn {
margin: 0 1rem;
}
.btn i {
margin-right: 0.5rem;
margin-left: -0.5rem;
@ -79,7 +83,9 @@ form {
}
form.inline {
display: block;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
margin-bottom: 2rem;
}
form div {
@ -87,11 +93,34 @@ form div {
min-height: 4rem;
}
form.inline div {
form div .btn,
form div a.chip {
margin-top: 2.5rem;
}
form div.oppose {
justify-content: right;
}
form div .btn,
form div .chip {
display: inline-block;
position: relative;
margin-right: 1rem;
margin-bottom: 1rem;
align-self: flex-end;
}
form div .chip {
line-height: 4.5rem;
}
form.inline div.large {
grid-column: span 2;
align-items: end;
}
@media screen and (max-width: 800px) {
form.inline div.large {
grid-column: span 4;
}
}
form .large,
@ -105,7 +134,7 @@ form .disabled {
filter: blur(0.25rem);
}
@media screen and (max-width: 1024px) {
@media screen and (max-width: 800px) {
form div {
grid-column: 1/-1;
}
@ -119,9 +148,18 @@ form small {
margin-top: 0.25rem;
}
form small.error {
color: var(--danger-6);
}
form label {
color: var(--second-6);
font-size: 1.5rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
form input[type="radio"]~label:after,
@ -129,20 +167,15 @@ form input[required]+label:after {
content: '*';
}
form input:not([type=checkbox]) ~ label,
form textarea ~ label,
form select ~ label {
form input:not([type=checkbox])~label,
form textarea~label,
form select~label {
position: absolute;
top: 0;
left: 0;
font-weight: 700;
}
form:not(.inline) div .btn {
position: absolute;
bottom: 0;
}
form div .btn.oppose {
right: 0;
}
@ -160,6 +193,7 @@ form div select {
form div select {
appearance: none;
padding-right: 4rem;
-moz-appearance: none;
-webkit-appearance: none;
}
@ -168,11 +202,12 @@ form div.checkbox {
min-height: 2rem;
}
form div.search:after,
form div.select:after {
font-family: 'Material Icons';
content: "\e5cf";
display: block;
font-size: 3rem;
font-size: 2.5rem;
color: var(--second-6);
position: absolute;
right: 1rem;
@ -181,6 +216,14 @@ form div.select:after {
line-height: 4rem;
}
form div.search input {
padding-right: 4rem;
}
form div.search:after {
content: "\e8b6";
}
form div input[disabled],
form div textarea[disabled] {
border-color: var(--grey-4);
@ -195,6 +238,19 @@ form div textarea[readonly] {
background-color: var(--grey-2);
}
input[type=number].digit {
font-size: 3rem;
width: 4rem;
height: 5rem;
-moz-appearance: textfield;
-webkit-appearance: textfield;
border-radius: 1.5rem;
background-color: white;
padding: 0.5rem;
text-align: center;
margin-right: 1rem;
}
input[type=checkbox] {
accent-color: var(--main-5);
}
@ -203,12 +259,13 @@ form div input[type=checkbox] {
margin-right: 1rem;
}
form div input:not([type=checkbox]):not([type=radio]):not(.btn),
form div input:not([type=checkbox]):not([type=radio]):not([type="number"].digit):not(.btn),
form div textarea,
form div select {
margin-top: 2.5rem;
box-sizing: border-box;
width: 100%;
height: max-content;
}
form div input[type=radio] {
@ -218,7 +275,7 @@ form div input[type=radio] {
accent-color: var(--main-5);
}
form div input[type=radio] + p {
form div input[type=radio]+p {
display: inline-block;
}

View file

@ -108,4 +108,10 @@ var ListToggle = {
document.addEventListener("DOMContentLoaded", function(event) {
ListToggle.init();
});
});
function digitFilled(element) {
if (element.value.length == 1) {
element.nextElementSibling.focus();
}
}

View file

@ -1,93 +1,101 @@
@extends('layouts.main')
@extends('layouts.main', ['grid' => true])
@section('content')
<header>
<h1><i class="material-icons">dashboard</i> Dashboard</h1>
</header>
<p>
<i class="material-icons">email</i>
@if (!empty($account->email))
{{ $account->email }}
@else
No email yet
@endif
<a href="{{ route('account.email.change') }}">Change my current account email</a>
</p>
<p>
<i class="material-icons">call</i>
@if (!empty($account->phone))
{{ $account->phone }}
@else
No phone yet
@endif
<a href="{{ route('account.phone.change') }}">Change my current account phone</a>
</p>
@if (config('app.devices_management') == true)
<div class="card">
<h3><i class="material-icons">waving_hand</i> Welcome back</h3>
<p>
<i class="material-icons">laptop</i>
<a href="{{ route('account.device.index') }}">Manage my devices</a>
</p>
@endif
<p>
<i class="material-icons">lock</i>
<a href="{{ route('account.password') }}">
@if ($account->passwords()->count() > 0)
Change my password
<i class="material-icons">email</i>
@if (!empty($account->email))
{{ $account->email }}
@else
Set my password
No email yet
@endif
</a>
</p>
<p>
<a href="{{ route('account.email.change') }}">Change my current account email</a>
</p>
<p>
<i class="material-icons">delete</i>
<a href="{{ route('account.delete') }}">Delete my account</a>
</p>
<p>
<i class="material-icons">call</i>
@if (!empty($account->phone))
{{ $account->phone }}
@else
No phone yet
@endif
<a href="{{ route('account.phone.change') }}">Change my current account phone</a>
</p>
@if (config('app.devices_management') == true)
<p>
<i class="material-icons">laptop</i>
<a href="{{ route('account.device.index') }}">Manage my devices</a>
</p>
@endif
<p>
<i class="material-icons">lock</i>
<a href="{{ route('account.password') }}">
@if ($account->passwords()->count() > 0)
Change my password
@else
Set my password
@endif
</a>
</p>
<h2><i class="material-icons">person</i> Account information</h2>
<p><i class="material-icons">alternate_email</i> SIP address: sip:{{ $account->identifier }}</p>
<p><i class="material-icons">person</i> Username: {{ $account->username }}</p>
<p><i class="material-icons">dns</i> Domain: {{ $account->domain }}</p>
@if (!empty(config('app.proxy_registrar_address')))
<p><i class="material-icons">lan</i> Proxy/registrar address: sip:{{ config('app.proxy_registrar_address') }}</p>
@endif
@if (!empty(config('app.transport_protocol_text')))
<p><i class="material-icons">settings_ethernet</i> Transport: {{ config('app.transport_protocol_text') }} </p>
@endif
<!--<h3 class="mt-3">Automatic authentication</h3>
<p>You can automatically authenticate another device on this panel by flashing the following QR Code.
Once generated the QR Code stays valid for a few minutes.</p>
@foreach ($account->authTokens()->valid()->get() as $authToken)
<img src="{{ route('auth_tokens.qrcode', ['token' => $authToken->token]) }}">
@endforeach
{!! Form::open(['route' => 'account.auth_tokens.create']) !!}
<button type="submit" class="btn btn-primary">Generate</button>
{!! Form::close() !!}-->
<h2><i class="material-icons">key</i>API Key</h2>
<p>You can generate an API key and use it to request the different API endpoints, <a href="{{ route('api') }}">check
the related API documentation</a> to know how to use that key.</p>
{!! Form::open(['route' => 'account.api_key.generate']) !!}
<div>
<input readonly class="form-control" placeholder="No key yet, press Generate"
@if ($account->apiKey) value="{{ $account->apiKey->key }}" @endif>
<label>Key</label>
<p>
<i class="material-icons">delete</i>
<a href="{{ route('account.delete') }}">Delete my account</a>
</p>
</div>
<div>
<button type="submit" class="btn btn-primary">Generate</button>
<div class="card">
<h3><i class="material-icons">person</i> Account information</h3>
<p><i class="material-icons">alternate_email</i> SIP address: sip:{{ $account->identifier }}</p>
<p><i class="material-icons">person</i> Username: {{ $account->username }}</p>
<p><i class="material-icons">dns</i> Domain: {{ $account->domain }}</p>
@if (!empty(config('app.proxy_registrar_address')))
<p><i class="material-icons">lan</i> Proxy/registrar address: sip:{{ config('app.proxy_registrar_address') }}
</p>
@endif
@if (!empty(config('app.transport_protocol_text')))
<p><i class="material-icons">settings_ethernet</i> Transport: {{ config('app.transport_protocol_text') }} </p>
@endif
<!--<h3 class="mt-3">Automatic authentication</h3>
<p>You can automatically authenticate another device on this panel by flashing the following QR Code.
Once generated the QR Code stays valid for a few minutes.</p>
@foreach ($account->authTokens()->valid()->get() as $authToken)
<img src="{{ route('auth_tokens.qrcode', ['token' => $authToken->token]) }}">
@endforeach
{!! Form::open(['route' => 'account.auth_tokens.create']) !!}
<button type="submit" class="btn btn-primary">Generate</button>
{!! Form::close() !!}-->
</div>
<div class="large">
<h2><i class="material-icons">key</i>API Key</h2>
<p>You can generate an API key and use it to request the different API endpoints, <a href="{{ route('api') }}">check
the related API documentation</a> to know how to use that key.</p>
{!! Form::open(['route' => 'account.api_key.generate']) !!}
<div>
<input readonly class="form-control" placeholder="No key yet, press Generate"
@if ($account->apiKey) value="{{ $account->apiKey->key }}" @endif>
<label>Key</label>
</div>
<div>
<button type="submit" class="btn btn-primary">Generate</button>
</div>
{!! Form::close() !!}
</div>
{!! Form::close() !!}
@include('parts.account_variables', ['account' => $account])
@endsection

View file

@ -2,7 +2,7 @@
@section('content')
<section>
<h1><i class="material-icons">account_circle</i> Validate your email</h1>
<h1 style="margin-bottom: 4rem;"><i class="material-icons">account_circle</i> Validate your email</h1>
{!! Form::open(['route' => 'account.email.update']) !!}
@ -11,14 +11,16 @@
<p>Please enter the verification code below:</p>
</div>
<div>
{!! Form::number('code', null, ['placeholder' => '0000', 'required', 'min' => 0000, 'max' => 9999]) !!}
{!! Form::label('code', 'Code') !!}
<div class="large">
<input oninput="digitFilled(this)" onfocus="this.value = ''" autofocus class="digit" name="number_1" type="number" min="0" max="9">
<input oninput="digitFilled(this)" onfocus="this.value = ''" class="digit" name="number_2" type="number" min="0" max="9">
<input oninput="digitFilled(this)" onfocus="this.value = ''" class="digit" name="number_3" type="number" min="0" max="9">
<input oninput="digitFilled(this)" onfocus="this.value = ''" class="digit" name="number_4" type="number" min="0" max="9">
@include('parts.errors', ['name' => 'code'])
</div>
<div>
{!! Form::submit('Validate', ['class' => 'btn']) !!}
<input class="btn" type="submit" value="Validate">
</div>
{!! Form::close() !!}

View file

@ -2,7 +2,7 @@
@section('content')
<section>
<h1><i class="material-icons">waving_hand</i> Oh hi!</h1>
<h1 style="margin-bottom: 3rem;"><i class="material-icons">waving_hand</i> Welcome on {{ config('app.name') }}</h1>
@if (config('instance.intro_registration'))
@parsedown(config('instance.intro_registration'))
@ -11,25 +11,29 @@
@if (Auth::check())
@include('parts.already_auth')
@else
{!! Form::open(['route' => 'account.authenticate']) !!}
<div class="large">
@if (config('app.phone_authentication'))
{!! Form::text('username', old('username'), ['placeholder' => 'username or phone number', 'required']) !!}
{!! Form::label('username', 'Username or phone number') !!}
@else
{!! Form::text('username', old('username'), ['placeholder' => 'username', 'required']) !!}
{!! Form::label('username', 'Username') !!}
@endif
</div>
<div class="large">
{!! Form::password('password', ['placeholder' => 'myPassword', 'required']) !!}
{!! Form::label('password', 'Password') !!}
</div>
<div class="large">
{!! Form::submit('Login', ['class' => 'btn oppose']) !!}
</div>
<form style="margin-top: 3rem; margin-bottom: 3rem;" method="POST" action="{{ route('account.authenticate') }}" accept-charset="UTF-8">
@csrf
<div>
@if (config('app.phone_authentication'))
<input placeholder="username or phone number" required="" name="username" type="text"
value="{{ old('username') }}">
<label for="username">Username or phone number</label>
@else
<input placeholder="username" required="" name="username" type="text"
value="{{ old('username') }}">
<label for="username">Username</label>
@endif
</div>
<div class="on_desktop"></div>
<div>
<input placeholder="myPassword" required="" name="password" type="password" value="">
<label for="password">Password</label>
</div>
<div>
<input class="btn" type="submit" value="Login">
</div>
{!! Form::close() !!}
</form>
<br />

View file

@ -11,14 +11,16 @@
<p>Please enter the verification code below:</p>
</div>
<div>
{!! Form::number('code', null, ['placeholder' => '0000', 'required', 'min' => 0000, 'max' => 9999]) !!}
{!! Form::label('code', 'Code') !!}
<div class="large">
<input oninput="digitFilled(this)" autofocus class="digit" name="number_1" type="number" min="0" max="9">
<input oninput="digitFilled(this)" class="digit" name="number_2" type="number" min="0" max="9">
<input oninput="digitFilled(this)" class="digit" name="number_3" type="number" min="0" max="9">
<input oninput="digitFilled(this)" class="digit" name="number_4" type="number" min="0" max="9">
@include('parts.errors', ['name' => 'code'])
</div>
<div>
{!! Form::submit('Validate', ['class' => 'btn']) !!}
<input class="btn" type="submit" value="Validate">
</div>
{!! Form::close() !!}

View file

@ -2,13 +2,11 @@
@section('content')
<section>
<p class="oppose">
<h1><i class="material-icons">account_circle</i> Register</h1>
<p style="margin-bottom: 2rem;">
You already have an account?
<a class="btn btn-secondary" href="{{ route('account.login') }}">Login</a>
</p>
<h1><i class="material-icons">account_circle</i> Register</h1>
@include('parts.tabs.register')
{!! Form::open(['route' => 'account.store']) !!}
@ -20,7 +18,7 @@
</div>
<div>
<input type="text" name="username" value="{{ $domain }}" disabled>
<input type="text" name="domain" value="{{ $domain }}" disabled>
</div>
<div>

View file

@ -1,57 +1,54 @@
@extends('layouts.main', ['welcome' => true])
@section('content')
<section>
<h1><i class="material-icons">account_circle</i> Register</h1>
<p style="margin-bottom: 2rem;">
You already have an account?
<a class="btn btn-secondary" href="{{ route('account.login') }}">Login</a>
</p>
@include('parts.tabs.register')
<section>
{!! Form::open(['route' => 'account.store']) !!}
<p class="oppose">
You already have an account?
<a class="btn btn-secondary" href="{{ route('account.login') }}">Login</a>
</p>
<div>
{!! Form::text('username', old('username'), ['placeholder' => 'username', 'required']) !!}
{!! Form::label('username', 'Username') !!}
@include('parts.errors', ['name' => 'username'])
</div>
<div>
<input type="text" name="domain" value="{{ $domain }}" disabled>
</div>
<h1><i class="material-icons">account_circle</i> Register</h1>
@include('parts.tabs.register')
<div>
{!! Form::text('phone', old('phone'), ['placeholder' => '+123456789', 'required']) !!}
{!! Form::label('phone', 'Phone number') !!}
@include('parts.errors', ['name' => 'phone'])
</div>
<div></div>
{!! Form::open(['route' => 'account.store']) !!}
<div>
<input required="" name="password" type="password" value="" placeholder="Password">
<label for="password">Password</label>
@include('parts.errors', ['name' => 'password'])
</div>
<div>
<input required="" name="password_confirmation" type="password" value=""
placeholder="Password confirmation">
<label for="password_confirmation">Confirm password</label>
@include('parts.errors', ['name' => 'password_confirmation'])
</div>
<div>
{!! Form::text('username', old('username'), ['placeholder' => 'username', 'required']) !!}
{!! Form::label('username', 'Username') !!}
@include('parts.errors', ['name' => 'username'])
</div>
<div>
{!! Form::text('username', $domain, ['disabled']) !!}
</div>
@include('parts.terms')
<div>
{!! Form::text('phone', old('phone'), ['placeholder' => '+123456789', 'required']) !!}
{!! Form::label('phone', 'Phone number') !!}
@include('parts.errors', ['name' => 'phone'])
</div>
<div></div>
<div class="large">
{!! Form::submit('Register', ['class' => 'btn oppose']) !!}
</div>
<div>
<input required="" name="password" type="password" value="" placeholder="Password">
<label for="password">Password</label>
@include('parts.errors', ['name' => 'password'])
</div>
<div>
<input required="" name="password_confirmation" type="password" value="" placeholder="Password confirmation">
<label for="password_confirmation">Confirm password</label>
@include('parts.errors', ['name' => 'password_confirmation'])
</div>
{!! Form::close() !!}
@include('parts.terms')
<div class="large">
{!! Form::submit('Register', ['class' => 'btn oppose']) !!}
</div>
{!! Form::close() !!}
</section>
<section class="on_desktop">
<img src="/img/login.svg">
</section>
@endsection
</section>
<section class="on_desktop">
<img src="/img/login.svg">
</section>
@endsection

View file

@ -1,9 +1,16 @@
@extends('layouts.main')
@section('breadcrumb')
<li class="breadcrumb-item" aria-current="page">
<a href="{{ route('admin.account.index') }}">Accounts</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Edit</li>
@endsection
@section('content')
@if ($account->id)
<header>
<h1><i class="material-icons">people</i> Edit an account</h1>
<h1><i class="material-icons">people</i> {{ $account->identifier }}</h1>
<a href="{{ route('admin.account.index') }}" class="btn btn-secondary oppose">Cancel</a>
<a class="btn btn-secondary" href="{{ route('admin.account.delete', $account->id) }}">
<i class="material-icons">delete</i>

View file

@ -1,5 +1,12 @@
@extends('layouts.main')
@section('breadcrumb')
<li class="breadcrumb-item" aria-current="page">
<a href="{{ route('admin.account.index') }}">Accounts</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
@endsection
@section('content')
<h2>Delete an account</h2>

View file

@ -1,7 +1,12 @@
@extends('layouts.main')
@section('content')
@section('breadcrumb')
<li class="breadcrumb-item" aria-current="page">
Accounts
</li>
@endsection
@section('content')
<header>
<h1><i class="material-icons">people</i> Account</h1>
<a class="btn oppose" href="{{ route('admin.account.create') }}">
@ -10,18 +15,49 @@
</a>
</header>
<div>
<form class="inline" method="POST" action="{{ route('admin.account.search')}}" accept-charset="UTF-8">
<form class="inline" method="POST" action="{{ route('admin.account.search') }}" accept-charset="UTF-8">
@csrf
<div>
<input placeholder="Search by username: +1234, foo_bar…" name="search" type="text" value="{{ $search }}">
<input type="hidden" name="order_by" value="{{ request()->get('order_by', '') }}">
<input type="hidden" name="order_sort" value="{{ request()->get('order_sort', '') }}">
<div class="search large">
<input placeholder="Search by username: +1234, foo_bar…" name="search" type="text"
value="{{ request()->get('search', '') }}">
<label for="search">Search</label>
</div>
<div>
<input name="updated_date" type="date" value="{{ $updated_date }}">
<label for="updated_date">Updated Date</label>
<div class="large on_desktop"></div>
<div class="select">
<select name="domain" onchange="this.form.submit()">
<option value="">
Select a domain
</option>
@foreach ($domains as $d)
<option value="{{ $d }}" @if (request()->get('domain', '') == $d) selected="selected" @endif>
{{ $d }}
</option>
@endforeach
</select>
<label for="domain">Domain</label>
</div>
<div class="select">
<select name="contacts_list" onchange="this.form.submit()">
<option value="">
Select a contacts list
</option>
@foreach ($contacts_lists as $key => $name)
<option value="{{ $key }}" @if (request()->get('contacts_list', '') == $key) selected="selected" @endif>
{{ $name }}
</option>
@endforeach
</select>
<label for="domain">Contacts list</label>
</div>
<div>
<a href="{{ route('admin.account.index')}}" type="reset" class="btn btn-secondary">Reset</a>
<input name="updated_date" type="date" value="{{ request()->get('updated_date', '') }}"
onchange="this.form.submit()">
<label for="updated_date">Updated Date</label>
</div>
<div class="oppose">
<a href="{{ route('admin.account.index') }}" type="reset" class="btn btn-tertiary">Reset</a>
<button type="submit" class="btn">Search</button>
</div>
</form>
@ -30,18 +66,10 @@
<table class="table">
<thead>
<tr>
<th>Identifier (email)</th>
<th></th>
<th>
<a href="{{ route('admin.account.index', ['updated_at_order' => $updated_at_order]) }}">
Updated
@if ($updated_at_order == 'desc')
<i class="material-icons">expand_more</i>
@else
<i class="material-icons">expand_less</i>
@endif
</a>
</th>
@include('parts.column_sort', ['key' => 'username', 'title' => 'Identifier'])
<th>Contact lists</th>
<th>Badges</th>
@include('parts.column_sort', ['key' => 'updated_at', 'title' => 'Updated'])
</tr>
</thead>
<tbody>
@ -52,6 +80,14 @@
{{ $account->identifier }}
</a>
</td>
<td>
@if ($account->contactsLists->isNotEmpty())
{{ $account->contactsLists->first()->title }}
@if ($account->contactsLists->count() > 1)
<span class="badge">+{{ $account->contactsLists->count() - 1 }}</span>
@endif
@endif
</td>
<td>
@if ($account->externalAccount)
<span class="badge badge-secondary" title="External Account attached">EA</span>
@ -69,12 +105,11 @@
<span class="badge badge-info">SHA256</span>
@endif
</td>
<td>{{ $account->updated_at}}</td>
<td>{{ $account->updated_at }}</td>
</tr>
@endforeach
</tbody>
</table>
{{ $accounts->links('pagination::bootstrap-4') }}
@endsection
@endsection

View file

@ -1,31 +1,49 @@
@extends('layouts.main')
@section('breadcrumb')
<li class="breadcrumb-item" aria-current="page">
<a href="{{ route('admin.contacts_lists.index') }}">Contacts Lists</a>
</li>
<li class="breadcrumb-item" aria-current="page">
<a href="{{ route('admin.contacts_lists.edit', $contacts_list->id) }}">{{ $contacts_list->title }}</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Add contacts</li>
@endsection
@section('content')
<header>
<h1><i class="material-icons">account_box</i> Contacts List | Add contacts</h1>
<p class="oppose">
<span class="list_toggle" data-list-id="a{{ $contacts_list->id }}"></span> selected
</p>
<h1><i class="material-icons">account_box</i> {{ $contacts_list->title }}</h1>
<a href="{{ route('admin.contacts_lists.edit', $contacts_list->id) }}" class="btn btn-secondary oppose">Cancel</a>
<form method="POST"
action="{{ route('admin.contacts_lists.contacts.store', $contacts_list->id) }}"
name="contacts_lists_contacts_store"
accept-charset="UTF-8">
@csrf
@method('post')
<select name="contacts_ids[]" class="list_toggle" data-list-id="a{{ $contacts_list->id }}"></select>
<input type="hidden" name="contacts_list_id" value="{{ $contacts_list->id }}">
<input class="btn" type="submit" value="Add" onclick="Utils.clearStorageList('a{{ $contacts_list->id }}')">
</form>
</header>
<div>
<form class="inline" method="POST" action="{{ route('admin.contacts_lists.contacts.search', $params) }}" accept-charset="UTF-8">
@csrf
<div>
<div class="search large">
<input placeholder="Search by username: +1234, foo_bar…" name="search" type="text" value="{{ $params['search'] }}">
<label for="search">Search</label>
</div>
<div class="oppose large">
<p>
<span class="list_toggle" data-list-id="a{{ $contacts_list->id }}"></span> selected
</p>
<a class="btn" onclick="Utils.clearStorageList('a{{ $contacts_list->id }}'); document.querySelector('form[name=contacts_lists_contacts_store]').submit()">
<i class="material-icons">add_circle</i>
Add
</a>
</div>
<div>
<a href="{{ route('admin.contacts_lists.contacts.add', $contacts_list->id) }}" type="reset" class="btn btn-secondary">Reset</a>
<button type="submit" class="btn">Search</button>
@ -36,7 +54,7 @@
<table class="table">
<thead>
<tr>
<th>
<th width="1%">
<input type="checkbox" onchange="Utils.toggleAll(this)">
</th>
<th>Username</th>

View file

@ -1,9 +1,16 @@
@extends('layouts.main')
@section('breadcrumb')
<li class="breadcrumb-item" aria-current="page">
<a href="{{ route('admin.contacts_lists.index') }}">Contacts Lists</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Edit</li>
@endsection
@section('content')
<header>
@if ($contacts_list->id)
<h1><i class="material-icons">account_box</i> Edit a Contacts List</h1>
<h1><i class="material-icons">account_box</i> {{ $contacts_list->title }}</h1>
<a href="{{ route('admin.contacts_lists.index') }}" class="btn btn-secondary oppose">Cancel</a>
<a class="btn btn-secondary" href="{{ route('admin.contacts_lists.delete', $contacts_list->id) }}">
<i class="material-icons">delete</i>
@ -27,7 +34,8 @@
@csrf
@method($contacts_list->id ? 'put' : 'post')
<div>
<input placeholder="Name" required="required" name="title" type="text" value="{{ $contacts_list->title ?? old('title') }}">
<input placeholder="Name" required="required" name="title" type="text"
value="{{ $contacts_list->title ?? old('title') }}">
<label for="username">Name</label>
@include('parts.errors', ['name' => 'title'])
</div>
@ -48,14 +56,17 @@
</p>
<form method="POST" action="{{ route('admin.contacts_lists.contacts.destroy', $contacts_list->id) }}"
accept-charset="UTF-8">
name="contacts_lists_contacts_destroy" accept-charset="UTF-8">
@csrf
@method('delete')
<select name="contacts_ids[]" class="list_toggle" data-list-id="d{{ $contacts_list->id }}"></select>
<input type="hidden" name="contacts_list_id" value="{{ $contacts_list->id }}">
<input class="btn btn-tertiary" type="submit" value="Remove contacts"
onclick="Utils.clearStorageList('d{{ $contacts_list->id }}')">
<a class="btn btn-tertiary"
onclick="Utils.clearStorageList('d{{ $contacts_list->id }}'); document.querySelector('form[name=contacts_lists_contacts_destroy]').submit()">
<i class="material-icons">delete</i>
Remove contacts
</a>
</form>
<a class="btn btn-secondary" href="{{ route('admin.contacts_lists.contacts.add', $contacts_list->id) }}">
@ -66,7 +77,7 @@
<table class="large">
<thead>
<tr>
<th>
<th width="1%">
<input type="checkbox" onchange="Utils.toggleAll(this)">
</th>
<th>Username</th>

View file

@ -1,5 +1,12 @@
@extends('layouts.main')
@section('breadcrumb')
<li class="breadcrumb-item" aria-current="page">
<a href="{{ route('admin.contacts_lists.index') }}">Contacts Lists</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
@endsection
@section('content')
<h2>Delete a Contact List</h2>
@ -15,6 +22,7 @@
<input name="contacts_lists_id" type="hidden" value="{{ $contacts_list->id }}">
</div>
<div>
<a href="{{ route('admin.contacts_lists.edit', $contacts_list->id) }}" class="btn btn-secondary">Cancel</a>
<input class="btn" type="submit" value="Delete">
</div>

View file

@ -1,5 +1,11 @@
@extends('layouts.main')
@section('breadcrumb')
<li class="breadcrumb-item" aria-current="page">
Contacts Lists
</li>
@endsection
@section('content')
<header>
@ -13,19 +19,10 @@
<table class="table">
<thead>
<tr>
<th>Name</th>
@include('parts.column_sort', ['key' => 'title', 'title' => 'Name'])
<th>Description</th>
<th>Number of Contacts</th>
<th>
<a href="{{ route('admin.contacts_lists.index', ['updated_at_order' => $updated_at_order]) }}">
Updated
@if ($updated_at_order == 'desc')
<i class="material-icons">expand_more</i>
@else
<i class="material-icons">expand_less</i>
@endif
</a>
</th>
@include('parts.column_sort', ['key' => 'contacts_count', 'title' => 'Contacts'])
@include('parts.column_sort', ['key' => 'updated_at', 'title' => 'Updated'])
</tr>
</thead>
<tbody>

View file

@ -1,6 +1,14 @@
@extends('layouts.main')
@section('breadcrumb')
<li class="breadcrumb-item active" aria-current="page">Statistics</li>
@endsection
@section('content')
<header>
<h1><i class="material-icons">analytics</i> Statistics</h1>
</header>
@include('parts.tabs', [
'items' => [
route('admin.statistics.show', ['type' => 'messages']) => 'Messages',
@ -8,7 +16,7 @@
],
])
<header>
<div>
<form class="inline" method="POST" action="{{ route('admin.statistics.edit') }}" accept-charset="UTF-8">
@csrf
@method('post')
@ -36,12 +44,15 @@
class="chip @if ($by == 'year') selected @endif">Year</a>
</div>
<a class="btn btn-secondary" href="{{ route('admin.statistics.show') }}">Reset</a>
<a class="btn btn-tertiary" href="{{ route('admin.statistics.show', ['by' => $by, 'type' => $type, 'export' => true] + $request->only(['from', 'to'])) }}">
<i class="material-icons">download</i> Export
</a>
<div class="oppose">
<a class="btn btn-secondary" href="{{ route('admin.statistics.show') }}">Reset</a>
<a class="btn btn-tertiary"
href="{{ route('admin.statistics.show', ['by' => $by, 'type' => $type, 'export' => true] + $request->only(['from', 'to'])) }}">
<i class="material-icons">download</i> Export
</a>
</div>
</form>
</header>
</div>
@include('parts.graph')
@endsection

View file

@ -44,7 +44,8 @@
<a class="oppose" href="{{ route('account.logout') }}">
<i class="material-icons">logout</i>
</a>
@else
@elseif (request()->route() &&
request()->route()->getName() != 'account.login')
<a class="oppose" href="{{ route('account.login') }}">
<i class="material-icons">info</i><span class="on_desktop">Login</span>
</a>
@ -61,7 +62,16 @@
@include('parts.errors')
@if (!isset($welcome) || $welcome == false)
<section>
<section @if (isset($grid) && $grid) class="grid" @endif>
@hasSection('breadcrumb')
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('account.dashboard') }}">Home</a></li>
@yield('breadcrumb')
</ol>
</nav>
@endif
@endif
@yield('content')

View file

@ -0,0 +1,20 @@
<th class="line">
<a
href="{{ route(request()->route()->getName(), [
'order_by' => $key,
'order_sort' => request()->get('order_sort', 'desc') == 'desc' ? 'asc' : 'desc'
] + request()->except('_token', 'query')) }}">
{{ $title }}
<i class="material-icons">
@if (request()->get('order_by') == $key && request()->has('order_sort'))
@if (request()->get('order_sort') == 'asc')
expand_more
@else
expand_less
@endif
@else
sort
@endif
</i>
</a>
</th>

View file

@ -1,6 +1,6 @@
@if (isset($errors) && isset($name) && count($errors->get($name)) > 0)
@foreach ($errors->get($name) as $error)
<small>
<small class="error">
{{ $error }}
</small>
@endforeach