Merge branch 'club' into le-gaulois-2024

This commit is contained in:
Jérémy Munsch 2024-07-17 16:04:05 +02:00
commit af81e6919f
No known key found for this signature in database
GPG key ID: 18361AF3BFEC0FEC
15 changed files with 559 additions and 605 deletions

View file

@ -16,6 +16,7 @@ use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Lottery;
class MessageController extends Controller
{
@ -26,6 +27,28 @@ class MessageController extends Controller
*/
public function index()
{
/** @var \App\Models\Customer */
$customer = Auth::guard('club')->user();
$unread_messages_count = Message::query()
->whereHas('toCustomer', fn (Builder $q) => $q->where('id', $customer->id))
->where('hide_to', false)
->count();
return view('club.pages.messages.index', compact(
'unread_messages_count'
));
}
/**
* Fetch conversations and block list
*
* @return \Illuminate\Http\JsonResponse
*/
public function conversations(): JsonResponse
{
/** How many message to keey in a conversation */
$messagesToKeepInConversations = 25;
/** @var \App\Models\Customer */
$customer = Auth::guard('club')->user();
$conversations = $customer->conversations();
@ -35,23 +58,19 @@ class MessageController extends Controller
->whereIn('blocked_id', $ids)->get();
// * Sort In reverse order messages
$conversations = collect($conversations)->map(function (array $conversation) {
$conversation['messages'] = collect($conversation['messages'])
->sort(fn (Message $a, Message $b) => $a->created_at->timestamp < $b->created_at->timestamp)
->values()->all();
return $conversation;
})->all();
$unread_messages_count = Message::query()
->whereHas('toCustomer', fn (Builder $q) => $q->where('id', $customer->id))
->where('hide_to', false)
->count();
return view('club.pages.messages.index', compact(
'conversations',
'block_list',
'unread_messages_count'
));
$conversations = collect($conversations)
->map(function (array $conversation) use ($messagesToKeepInConversations) {
$sortFn = fn (Message $a, Message $b) => $a->created_at->timestamp < $b->created_at->timestamp;
// * Limit messages per conversations
Lottery::odds(1, 20)
->winner(fn () => collect($conversation['messages'])
// @phpstan-ignore-next-line
->sort($sortFn)->splice($messagesToKeepInConversations)
->each(fn (Message $message) => $message->delete()))
->choose();
return $conversation;
})->all();
return response()->json(compact('conversations', 'block_list'));
}
/**
@ -75,7 +94,7 @@ class MessageController extends Controller
NotificationType::message,
route('club.messages.index', ['m' => $message->id]),
$message,
$customer
$toCustomer
));
return response()->json($message->load(['toCustomer' => function (Builder $q) {
$q->select(['id', 'username', 'created_at']);

View file

@ -29,7 +29,7 @@ class SendMessageRequest extends FormRequest
public function rules()
{
return [
'message' => 'required|string|max:255|min:10',
'message' => 'required|string|max:255|min:5',
'to' => [
'required',
Rule::exists('customers', 'username'),

View file

@ -123,7 +123,7 @@ class Customer extends Authenticatable implements MustVerifyEmail
*/
public function conversations(): array
{
$dateSort = fn (Message $a, Message $b) => $a->created_at->timestamp < $b->created_at->timestamp;
$dateSort = fn (Message $a, Message $b) => $a->created_at->timestamp > $b->created_at->timestamp;
$messageFilter = fn (Collection $item) => $item->all();
$messageWith = [
'fromCustomer' => fn (Builder $q) => $q->select(['id', 'username', 'created_at']),
@ -142,6 +142,7 @@ class Customer extends Authenticatable implements MustVerifyEmail
->groupBy('fromCustomer.username')->map($messageFilter)->toBase();
$output = $receivedM->mapWithKeys(function (array $messagesArray, string $fromUsername) use (&$sentM, $dateSort) {
// @phpstan-ignore-next-line
$responses = collect(\array_merge($sentM->pull($fromUsername) ?? [], $messagesArray))->sort($dateSort);
$last = $responses->last();
return [$fromUsername => collect([

103
package-lock.json generated
View file

@ -20,6 +20,7 @@
"@types/bootstrap": "^5.2.7",
"@types/node": "^20.8.2",
"@types/toastify-js": "^1.12.1",
"@types/ua-parser-js": "^0.7.39",
"@types/vue-select": "^3.16.5",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.36.2",
@ -44,6 +45,7 @@
"resolve-url-loader": "^5.0.0",
"rollup-plugin-copy": "^3.5.0",
"sass": "latest",
"simplebar-vue": "^2.3.5",
"slugify": "^1.6.6",
"stylelint": "^15.11.0",
"stylelint-config-idiomatic-order": "github:ream88/stylelint-config-idiomatic-order",
@ -57,6 +59,7 @@
"ts-loader": "^9.3.1",
"typescript": ">=3.3.1 <5.2.0",
"typescript-eslint": "latest",
"ua-parser-js": "^1.0.38",
"vite": "^4.5.1",
"vite-plugin-babel": "latest",
"vite-plugin-checker": "latest",
@ -1246,6 +1249,21 @@
"dev": true,
"peer": true
},
"node_modules/@types/lodash": {
"version": "4.17.7",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
"integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==",
"dev": true
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"dev": true,
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/minimatch": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
@ -1285,6 +1303,12 @@
"integrity": "sha512-9RjLlbAHMSaae/KZNHGv19VG4gcLIm3YjvacCXBtfMfYn26h76YP5oxXI8k26q4iKXCB9LNfv18lsoS0JnFPTg==",
"dev": true
},
"node_modules/@types/ua-parser-js": {
"version": "0.7.39",
"resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz",
"integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==",
"dev": true
},
"node_modules/@types/vue-select": {
"version": "3.16.8",
"resolved": "https://registry.npmjs.org/@types/vue-select/-/vue-select-3.16.8.tgz",
@ -5272,6 +5296,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"dev": true
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@ -6720,6 +6750,56 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/simplebar-core": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/simplebar-core/-/simplebar-core-1.2.6.tgz",
"integrity": "sha512-H5NYU+O+uvqOH5VXw3+lgoc1vTI6jL8LOZJsw4xgRpV7uIPjRpmLPdz0TrouxwKHBhpVLzVIlyKhaRLelIThMw==",
"dev": true,
"dependencies": {
"@types/lodash-es": "^4.17.6",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21"
}
},
"node_modules/simplebar-vue": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/simplebar-vue/-/simplebar-vue-2.3.5.tgz",
"integrity": "sha512-LCRpDfJ4nNNHoZK2h5HEhu3BXoLQkQOMzzcHngCb2GN7uVu22dwXW+Kel2f3MDhZgPR2abcTMMtRrRjK3ge0Yg==",
"dev": true,
"dependencies": {
"simplebar-core": "^1.2.6",
"vue-demi": "^0.13.11"
},
"peerDependencies": {
"vue": ">=2.5.17"
}
},
"node_modules/simplebar-vue/node_modules/vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"dev": true,
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -8220,6 +8300,29 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/ua-parser-js": {
"version": "1.0.38",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz",
"integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"engines": {
"node": "*"
}
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",

View file

@ -28,6 +28,7 @@
"@types/bootstrap": "^5.2.7",
"@types/node": "^20.8.2",
"@types/toastify-js": "^1.12.1",
"@types/ua-parser-js": "^0.7.39",
"@types/vue-select": "^3.16.5",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.36.2",
@ -52,6 +53,7 @@
"resolve-url-loader": "^5.0.0",
"rollup-plugin-copy": "^3.5.0",
"sass": "latest",
"simplebar-vue": "^2.3.5",
"slugify": "^1.6.6",
"stylelint": "^15.11.0",
"stylelint-config-idiomatic-order": "github:ream88/stylelint-config-idiomatic-order",
@ -65,6 +67,7 @@
"ts-loader": "^9.3.1",
"typescript": ">=3.3.1 <5.2.0",
"typescript-eslint": "latest",
"ua-parser-js": "^1.0.38",
"vite": "^4.5.1",
"vite-plugin-babel": "latest",
"vite-plugin-checker": "latest",

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,11 @@ import {
faCircleInfo,
faCircleNotch,
faCircleXmark,
faEllipsisVertical,
faEnvelope,
faEye,
faEyeSlash,
faLocationArrow,
faMagnifyingGlass,
faTriangleExclamation,
faXmark
@ -41,5 +43,7 @@ library.add(faSquareCheck);
library.add(faMagnifyingGlass);
library.add(faArrowRightArrowLeft);
library.add(faEnvelope);
library.add(faEllipsisVertical);
library.add(faLocationArrow);
dom.watch();

View file

@ -18,17 +18,17 @@ export default class Ajax {
toast.methods.errorNotify(trans.methods.__("La valeur saisie est incorrecte :message", {
"message": errorMessage
}));
return Promise.reject();
return;
}
if (error!.response!.status === 401) {
toast.methods.errorNotify(trans.methods.__("Vous devez vous reconnecter"));
console.warn(trans.methods.__("Vous devez vous reconnecter"));
return Promise.reject();
return;
}
if (error!.response!.status === 419) {
toast.methods.errorNotify(trans.methods.__("La page vas devoir être rechargée"));
setTimeout(() => window.location.reload(), 2000);
return Promise.reject();
return;
}
if (error!.response) {
console.error(error.response.data);
@ -39,6 +39,6 @@ export default class Ajax {
console.error("Error", error.message);
}
toast.methods.errorNotify(errorMessage);
return Promise.reject();
return;
}
}

View file

@ -81,7 +81,7 @@
<li class="nav-item">
<a class="btn btn-link text-tertiary position-relative p-1" href="{{ route('club.notifications.index') }}">
@include('club.icons.cloche')
@if ($notifications_count)
@if (!empty($notifications_count))
<span class="position-absolute start-85 translate-middle badge rounded-pill bg-danger top-15">
{{ $notifications_count }}
<span class="visually-hidden">{{ __('Nouvelles notifications') }}</span>

View file

@ -28,7 +28,7 @@
<span class="nav-icon">@include('club.icons.cloche')</span>
<a class="nav-link" href="{{ route('club.notifications.index') }}"
title="{{ __('Go to page :page', ['page' => 'Notifications']) }}">{{ __('Notifications') }}</a>
@if ($notifications_count)
@if (!empty($notifications_count))
<span class="nav-notification ms-auto">
<span class="visually-hidden">{{ __('Nouvelles notifications') }}</span>
<span>{{ $notifications_count }}</span>

View file

@ -37,11 +37,9 @@
<div class="col">
@php
$data = [
'block_list' => $block_list,
'conversations' => $conversations,
'currentUserId' => auth('club')->user()->id,
];
@endphp
@dump($data)
<div id="messages-chat" data-json='@json($data)'></div>
</div>
</div>

View file

@ -17,7 +17,7 @@
<div class="col-12 mb-3 text-center">
<span class="header-icon position-relative mb-3">
@include('club.icons.cloche')
@if ($notifications_count)
@if (!empty($notifications_count))
<span class="position-absolute start-100 translate-middle bg-danger border-light rounded-circle top-0 border px-2 py-1">
<span class="visually-hidden">{{ __('Nouvelles notifications') }}</span>
<span>{{ $notifications_count }}</span>

View file

@ -13,10 +13,12 @@
@auth('club')
<span class="d-none d-sm-inline position-relative ms-3 py-1">
<i class="fa-regular fa-user fs-4"></i>
<span class="position-absolute start-100 translate-middle badge rounded-pill text-bg-danger top-0 px-1">
{{ $notifications_count }}
<span class="visually-hidden">{{ __('Notifications') }}</span>
</span>
@if (!empty($notifications_count))
<span class="position-absolute start-100 translate-middle badge rounded-pill text-bg-danger top-0 px-1">
{{ $notifications_count }}
<span class="visually-hidden">{{ __('Notifications') }}</span>
</span>
@endif
</span>
@endauth
</span>

View file

@ -41,7 +41,10 @@
'{{ route('fo.products.search.submeat', ['model_slug' => 'MODELSLUG', 'child_slug' => 'CHILDSLUG']) }}';
window.__SYSTEM._routes['club.departaimants.save-state'] = '{{ route('club.departaimants.save-state', ['collectiontype' => 'TYPE']) }}';
window.__SYSTEM._routes['club.departaimants.search'] = '{{ route('club.departaimants.search', ['collectiontype' => 'TYPE']) }}';
window.__SYSTEM._routes['club.messages.conversations'] = '{{ route('club.messages.conversations') }}';
window.__SYSTEM._routes['club.messages.send'] = '{{ route('club.messages.send') }}';
window.__SYSTEM._routes['club.messages.delete'] = '{{ route('club.messages.delete') }}';
window.__SYSTEM._routes['club.messages.toggle-block'] = '{{ route('club.messages.toggle-block', ['status' => 'STATUS']) }}';
// FIXME: Contact report customer
window.__SYSTEM._routes['club.messages.report-customer'] = '{{ route('fo.contact.show') }}';
</script>

View file

@ -45,6 +45,7 @@ Route::name('club.')->prefix('club')->group(function () {
Route::prefix('/messages')->name('messages.')->group(function () {
Route::get('/', [MessageController::class, 'index'])->name('index');
Route::get('/conversations', [MessageController::class, 'conversations'])->name('conversations');
Route::post('send', [MessageController::class, 'send'])->name('send');
Route::delete('delete', [MessageController::class, 'delete'])->name('delete');
Route::post('block/{status}', [MessageController::class, 'toggleBlock'])->name('toggle-block');