Compare commits
No commits in common. "main" and "feature/push-notification-deals" have entirely different histories.
main
...
feature/pu
14
.env.example
14
.env.example
@ -75,16 +75,4 @@ VAPID_PUBLIC_KEY=
|
||||
VAPID_PRIVATE_KEY=
|
||||
|
||||
# Same as the VAPID_PUBLIC_KEY
|
||||
VITE_VAPID_PUBLIC_KEY="${VAPID_PUBLIC_KEY}"
|
||||
|
||||
REVERB_APP_ID=
|
||||
REVERB_APP_KEY=
|
||||
REVERB_APP_SECRET=
|
||||
REVERB_HOST=
|
||||
REVERB_PORT=
|
||||
REVERB_SCHEME=
|
||||
|
||||
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
|
||||
VITE_REVERB_HOST="${REVERB_HOST}"
|
||||
VITE_REVERB_PORT="${REVERB_PORT}"
|
||||
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
|
||||
VITE_VAPID_PUBLIC_KEY=BOBjjU2E-h8pDCV13yPwvMDR_WZwEhFmQY90gr16oJ5L1mpJ5qc7-0WzXcD1Z9D0Ozz0cLZxTe0_7nnDK3VFMP4
|
||||
|
||||
11
README.md
11
README.md
@ -79,22 +79,11 @@ ## ⚙️ Installation
|
||||
```bash
|
||||
git clone https://git.sentientgeeks.us/joydip.manna/dealhub.git
|
||||
cd dealhub
|
||||
# Install dependencies
|
||||
composer install
|
||||
npm install
|
||||
|
||||
# Set VAPID for webpush
|
||||
php artisan webpush:vapid
|
||||
|
||||
# Install Reverb
|
||||
php artisan install:broadcasting
|
||||
|
||||
npm run dev
|
||||
php artisan migrate
|
||||
php artisan serve
|
||||
|
||||
# Start Reverb Server ( Another terminal )
|
||||
php artisan reverb:start
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
4
TODO.md
4
TODO.md
@ -1,4 +0,0 @@
|
||||
- [ ] Live Support.
|
||||
- [ ] Add an Audit Log table to save all actions.
|
||||
- [ ] Link foreign keys properly with related tables in the database.
|
||||
- [ ] Add debouncing to the search feature and create proper database indexes to improve search performance.
|
||||
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Models\Inbox;
|
||||
use App\Models\User;
|
||||
use DB;
|
||||
|
||||
final readonly class CreateOrGetInboxAction
|
||||
{
|
||||
/**
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function execute(User $recipient, User $sender): Inbox
|
||||
{
|
||||
$existingInbox = Inbox::whereHas('users', fn ($q) => $q->where('users.id', $sender->id))
|
||||
->whereHas('users', fn ($q) => $q->where('users.id', $recipient->id))
|
||||
->first();
|
||||
|
||||
if ($existingInbox) {
|
||||
return $existingInbox;
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($sender, $recipient) {
|
||||
$inbox = Inbox::create();
|
||||
$inbox->users()->attach([$sender->id, $recipient->id]);
|
||||
|
||||
return $inbox;
|
||||
}, 2);
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Events\MessageSent;
|
||||
use App\Exceptions\MessageNotSendException;
|
||||
use App\Models\User;
|
||||
use DB;
|
||||
|
||||
final readonly class SendMessageAction
|
||||
{
|
||||
public function __construct(private CreateOrGetInboxAction $inboxAction) {}
|
||||
|
||||
/**
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function execute(User $sender, User $recipient, array $data): void
|
||||
{
|
||||
try {
|
||||
|
||||
// find the inbox between the two users
|
||||
DB::beginTransaction();
|
||||
$inbox = $this->inboxAction->execute($recipient, $sender);
|
||||
|
||||
// update the inbox with the last message and last user as current user
|
||||
$inbox->last_message = $data['message'];
|
||||
$inbox->last_user_id = $sender->id;
|
||||
$inbox->save();
|
||||
|
||||
// create a new message in the inbox
|
||||
$message = $inbox->messages()->create([
|
||||
'message' => $data['message'],
|
||||
'user_id' => $sender->id,
|
||||
]);
|
||||
|
||||
// Send the message to all other users in the inbox
|
||||
broadcast(new MessageSent($message))->toOthers();
|
||||
|
||||
DB::commit();
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
throw new MessageNotSendException('Message not sent.', previous: $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Message;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MessageSent implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Message $message)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
$users = $this->message->inbox->users->pluck('id')->toArray();
|
||||
sort($users);
|
||||
|
||||
return [
|
||||
new PrivateChannel("chat.{$users[0]}.{$users[1]}"),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class MessageNotSendException extends Exception {}
|
||||
@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\CreateOrGetInboxAction;
|
||||
use App\Actions\SendMessageAction;
|
||||
use App\Exceptions\MessageNotSendException;
|
||||
use App\Http\Requests\SendMessageRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Container\Attributes\CurrentUser;
|
||||
use Log;
|
||||
use Throwable;
|
||||
|
||||
class ChatController extends Controller
|
||||
{
|
||||
//
|
||||
public function index(#[CurrentUser] User $user)
|
||||
{
|
||||
return view('dashboards.user.chat')
|
||||
->with('inboxes', $user->inboxes);
|
||||
}
|
||||
|
||||
public function show(#[CurrentUser] User $sender, User $recipient, CreateOrGetInboxAction $action)
|
||||
{
|
||||
try {
|
||||
$inbox = $action->execute($recipient, $sender);
|
||||
} catch (Throwable $e) {
|
||||
Log::error('Inbox instantiation Failed: ', [$e->getMessage()]);
|
||||
abort(500);
|
||||
}
|
||||
|
||||
return view('dashboards.user.chat')
|
||||
->with('recipient', $recipient)
|
||||
->with('inboxes', $sender->inboxes)
|
||||
->with('messages', $inbox->messages()->latest()->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function store(
|
||||
#[CurrentUser] User $sender,
|
||||
User $recipient,
|
||||
SendMessageRequest $request,
|
||||
SendMessageAction $action
|
||||
) {
|
||||
try {
|
||||
$action->execute($sender, $recipient, $request->validated());
|
||||
|
||||
return response()->json(['message' => 'Message sent successfully.']);
|
||||
|
||||
} catch (MessageNotSendException $e) {
|
||||
|
||||
Log::error('Message send failed', [$e->getMessage()]);
|
||||
|
||||
return response()->json(['message' => 'Message sent failed.'], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureUserFollowedBroker
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$sender = \Auth::user();
|
||||
if ($sender->isBroker()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$recipientUser = $request->route('recipient');
|
||||
if ($recipientUser->isBroker()) {
|
||||
$recipient = $recipientUser->type;
|
||||
|
||||
$isFollowing = $recipient->followers->contains($sender->id);
|
||||
|
||||
if ($isFollowing) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
abort(403, 'You are not following this broker.');
|
||||
}
|
||||
|
||||
abort('404', 'Broker not found.');
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SendMessageRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'message' => 'required|string',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -14,9 +14,6 @@
|
||||
* @property bool $verified
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Follow|null $pivot
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Customer> $followers
|
||||
* @property-read int|null $followers_count
|
||||
* @property-read \App\Models\User|null $user
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker newModelQuery()
|
||||
|
||||
@ -5,28 +5,6 @@
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $text
|
||||
* @property int $deal_id
|
||||
* @property int $user_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Deal|null $deal
|
||||
* @property-read \App\Models\User|null $user
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereDealId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereText($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereUserId($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Comment extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
|
||||
@ -13,8 +13,6 @@
|
||||
* @property string|null $phone
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Follow> $followings
|
||||
* @property-read int|null $followings_count
|
||||
* @property-read \App\Models\User|null $user
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Customer newModelQuery()
|
||||
|
||||
@ -25,8 +25,6 @@
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\User|null $broker
|
||||
* @property-read \App\Models\DealCategory|null $category
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Comment> $comments
|
||||
* @property-read int|null $comments_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Interaction> $interactions
|
||||
* @property-read int|null $interactions_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Report> $reports
|
||||
@ -52,7 +50,6 @@
|
||||
* @method static Builder<static>|Deal whereTitle($value)
|
||||
* @method static Builder<static>|Deal whereUpdatedAt($value)
|
||||
* @method static Builder<static>|Deal whereUserId($value)
|
||||
* @method static Builder<static>|Deal withIsFollowedByCurrentUser()
|
||||
* @method static Builder<static>|Deal withViewPerDeal()
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
|
||||
@ -5,26 +5,6 @@
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $customer_id
|
||||
* @property int $broker_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Broker|null $broker
|
||||
* @property-read \App\Models\Customer|null $customer
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow whereBrokerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow whereCustomerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow whereUpdatedAt($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Follow extends Pivot
|
||||
{
|
||||
protected $table = 'follows';
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string|null $last_message
|
||||
* @property int|null $last_user_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\User|null $lastUser
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
|
||||
* @property-read int|null $users_count
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox whereLastMessage($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox whereLastUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox whereUpdatedAt($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Inbox extends Model
|
||||
{
|
||||
protected $fillable = ['last_user_id', 'last_message'];
|
||||
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
}
|
||||
|
||||
public function lastUser(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'id', 'last_user_id');
|
||||
}
|
||||
|
||||
public function messages(): HasMany
|
||||
{
|
||||
return $this->hasMany(Message::class, 'inbox_id');
|
||||
}
|
||||
|
||||
public function getRecipientAttribute(): User
|
||||
{
|
||||
// first user in the relationship that is NOT the authenticated user
|
||||
return $this->users->where('id', '!=', auth()->id())->first();
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Eloquent;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property int $inbox_id
|
||||
* @property string $message
|
||||
* @property string|null $read_at
|
||||
* @property string|null $delivered_at
|
||||
* @property string|null $deleted_at
|
||||
* @property string|null $failed_at
|
||||
* @property Carbon $created_at
|
||||
* @property-read Inbox|null $inbox
|
||||
* @property-read User|null $user
|
||||
*
|
||||
* @method static Builder<static>|Message newModelQuery()
|
||||
* @method static Builder<static>|Message newQuery()
|
||||
* @method static Builder<static>|Message query()
|
||||
* @method static Builder<static>|Message whereCreatedAt($value)
|
||||
* @method static Builder<static>|Message whereDeletedAt($value)
|
||||
* @method static Builder<static>|Message whereDeliveredAt($value)
|
||||
* @method static Builder<static>|Message whereFailedAt($value)
|
||||
* @method static Builder<static>|Message whereId($value)
|
||||
* @method static Builder<static>|Message whereInboxId($value)
|
||||
* @method static Builder<static>|Message whereMessage($value)
|
||||
* @method static Builder<static>|Message whereReadAt($value)
|
||||
* @method static Builder<static>|Message whereUserId($value)
|
||||
*
|
||||
* @mixin Eloquent
|
||||
*/
|
||||
class Message extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'inbox_id', 'user_id', 'message',
|
||||
'read_at', 'deleted_at', 'failed_at',
|
||||
'created_at', 'delivered_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function inbox(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Inbox::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@ -6,20 +6,11 @@
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int|null $user_id
|
||||
* @property string $page
|
||||
* @property UserTypes|null $user_type
|
||||
* @property string $created_at
|
||||
* @property UserTypes $user_type
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit wherePage($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit whereUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit whereUserType($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
@ -26,24 +25,16 @@
|
||||
* @property string $role
|
||||
* @property string|null $role_type
|
||||
* @property int|null $role_id
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Comment> $comments
|
||||
* @property-read int|null $comments_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Deal> $deals
|
||||
* @property-read int|null $deals_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Interaction> $dealsInteractions
|
||||
* @property-read int|null $deals_interactions_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Inbox> $inboxes
|
||||
* @property-read int|null $inboxes_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Deal> $interactedDeals
|
||||
* @property-read int|null $interacted_deals_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, User> $interactions
|
||||
* @property-read int|null $interactions_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Message> $messages
|
||||
* @property-read int|null $messages_count
|
||||
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
|
||||
* @property-read int|null $notifications_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \NotificationChannels\WebPush\PushSubscription> $pushSubscriptions
|
||||
* @property-read int|null $push_subscriptions_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecentSearch> $recentSearches
|
||||
* @property-read int|null $recent_searches_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Report> $reports
|
||||
@ -167,14 +158,4 @@ public function comments(): HasMany
|
||||
{
|
||||
return $this->hasMany(Comment::class);
|
||||
}
|
||||
|
||||
public function inboxes(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Inbox::class);
|
||||
}
|
||||
|
||||
public function messages(): HasMany
|
||||
{
|
||||
return $this->hasMany(Message::class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
channels: __DIR__.'/../routes/channels.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
|
||||
15
bootstrap/cache/packages.php
vendored
Executable file → Normal file
15
bootstrap/cache/packages.php
vendored
Executable file → Normal file
@ -20,13 +20,6 @@
|
||||
0 => 'BladeUI\\Icons\\BladeIconsServiceProvider',
|
||||
),
|
||||
),
|
||||
'laradumps/laradumps' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'LaraDumps\\LaraDumps\\LaraDumpsServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel-notification-channels/webpush' =>
|
||||
array (
|
||||
'providers' =>
|
||||
@ -41,14 +34,6 @@
|
||||
0 => 'Laravel\\Pail\\PailServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/reverb' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Laravel\\Reverb\\ApplicationManagerServiceProvider',
|
||||
1 => 'Laravel\\Reverb\\ReverbServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/sail' =>
|
||||
array (
|
||||
'providers' =>
|
||||
|
||||
46
bootstrap/cache/services.php
vendored
Executable file → Normal file
46
bootstrap/cache/services.php
vendored
Executable file → Normal file
@ -27,19 +27,16 @@
|
||||
23 => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
24 => 'BladeUI\\Heroicons\\BladeHeroiconsServiceProvider',
|
||||
25 => 'BladeUI\\Icons\\BladeIconsServiceProvider',
|
||||
26 => 'LaraDumps\\LaraDumps\\LaraDumpsServiceProvider',
|
||||
27 => 'NotificationChannels\\WebPush\\WebPushServiceProvider',
|
||||
28 => 'Laravel\\Pail\\PailServiceProvider',
|
||||
29 => 'Laravel\\Reverb\\ApplicationManagerServiceProvider',
|
||||
30 => 'Laravel\\Reverb\\ReverbServiceProvider',
|
||||
31 => 'Laravel\\Sail\\SailServiceProvider',
|
||||
32 => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
33 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
34 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
35 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
36 => 'Opcodes\\LogViewer\\LogViewerServiceProvider',
|
||||
37 => 'Pest\\Laravel\\PestServiceProvider',
|
||||
38 => 'App\\Providers\\AppServiceProvider',
|
||||
26 => 'NotificationChannels\\WebPush\\WebPushServiceProvider',
|
||||
27 => 'Laravel\\Pail\\PailServiceProvider',
|
||||
28 => 'Laravel\\Sail\\SailServiceProvider',
|
||||
29 => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
30 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
31 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
32 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
33 => 'Opcodes\\LogViewer\\LogViewerServiceProvider',
|
||||
34 => 'Pest\\Laravel\\PestServiceProvider',
|
||||
35 => 'App\\Providers\\AppServiceProvider',
|
||||
),
|
||||
'eager' =>
|
||||
array (
|
||||
@ -55,16 +52,14 @@
|
||||
9 => 'Illuminate\\View\\ViewServiceProvider',
|
||||
10 => 'BladeUI\\Heroicons\\BladeHeroiconsServiceProvider',
|
||||
11 => 'BladeUI\\Icons\\BladeIconsServiceProvider',
|
||||
12 => 'LaraDumps\\LaraDumps\\LaraDumpsServiceProvider',
|
||||
13 => 'NotificationChannels\\WebPush\\WebPushServiceProvider',
|
||||
14 => 'Laravel\\Pail\\PailServiceProvider',
|
||||
15 => 'Laravel\\Reverb\\ReverbServiceProvider',
|
||||
16 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
17 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
18 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
19 => 'Opcodes\\LogViewer\\LogViewerServiceProvider',
|
||||
20 => 'Pest\\Laravel\\PestServiceProvider',
|
||||
21 => 'App\\Providers\\AppServiceProvider',
|
||||
12 => 'NotificationChannels\\WebPush\\WebPushServiceProvider',
|
||||
13 => 'Laravel\\Pail\\PailServiceProvider',
|
||||
14 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
15 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
16 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
17 => 'Opcodes\\LogViewer\\LogViewerServiceProvider',
|
||||
18 => 'Pest\\Laravel\\PestServiceProvider',
|
||||
19 => 'App\\Providers\\AppServiceProvider',
|
||||
),
|
||||
'deferred' =>
|
||||
array (
|
||||
@ -226,8 +221,6 @@
|
||||
'Barryvdh\\LaravelIdeHelper\\Console\\ModelsCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
'Barryvdh\\LaravelIdeHelper\\Console\\MetaCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
'Barryvdh\\LaravelIdeHelper\\Console\\EloquentCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
'Laravel\\Reverb\\ApplicationManager' => 'Laravel\\Reverb\\ApplicationManagerServiceProvider',
|
||||
'Laravel\\Reverb\\Contracts\\ApplicationProvider' => 'Laravel\\Reverb\\ApplicationManagerServiceProvider',
|
||||
'Laravel\\Sail\\Console\\InstallCommand' => 'Laravel\\Sail\\SailServiceProvider',
|
||||
'Laravel\\Sail\\Console\\PublishCommand' => 'Laravel\\Sail\\SailServiceProvider',
|
||||
'command.tinker' => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
@ -276,9 +269,6 @@
|
||||
'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Laravel\\Reverb\\ApplicationManagerServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Laravel\\Sail\\SailServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
|
||||
@ -13,14 +13,12 @@
|
||||
"blade-ui-kit/blade-heroicons": "^2.6",
|
||||
"laravel-notification-channels/webpush": "^10.4",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/reverb": "^1.0",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"twilio/sdk": "^8.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^3.6",
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laradumps/laradumps": "^5.0",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.24",
|
||||
"laravel/sail": "^1.41",
|
||||
|
||||
1296
composer.lock
generated
1296
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,82 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Broadcaster
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default broadcaster that will be used by the
|
||||
| framework when an event needs to be broadcast. You may set this to
|
||||
| any of the connections defined in the "connections" array below.
|
||||
|
|
||||
| Supported: "reverb", "pusher", "ably", "redis", "log", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('BROADCAST_CONNECTION', 'null'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Broadcast Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the broadcast connections that will be used
|
||||
| to broadcast events to other systems or over WebSockets. Samples of
|
||||
| each available type of connection are provided inside this array.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'reverb' => [
|
||||
'driver' => 'reverb',
|
||||
'key' => env('REVERB_APP_KEY'),
|
||||
'secret' => env('REVERB_APP_SECRET'),
|
||||
'app_id' => env('REVERB_APP_ID'),
|
||||
'options' => [
|
||||
'host' => env('REVERB_HOST'),
|
||||
'port' => env('REVERB_PORT', 443),
|
||||
'scheme' => env('REVERB_SCHEME', 'https'),
|
||||
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
|
||||
],
|
||||
'client_options' => [
|
||||
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||
],
|
||||
],
|
||||
|
||||
'pusher' => [
|
||||
'driver' => 'pusher',
|
||||
'key' => env('PUSHER_APP_KEY'),
|
||||
'secret' => env('PUSHER_APP_SECRET'),
|
||||
'app_id' => env('PUSHER_APP_ID'),
|
||||
'options' => [
|
||||
'cluster' => env('PUSHER_APP_CLUSTER'),
|
||||
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
|
||||
'port' => env('PUSHER_PORT', 443),
|
||||
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||
'encrypted' => true,
|
||||
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
|
||||
],
|
||||
'client_options' => [
|
||||
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||
],
|
||||
],
|
||||
|
||||
'ably' => [
|
||||
'driver' => 'ably',
|
||||
'key' => env('ABLY_KEY'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'driver' => 'log',
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'null',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
@ -1,95 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Reverb Server
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default server used by Reverb to handle
|
||||
| incoming messages as well as broadcasting message to all your
|
||||
| connected clients. At this time only "reverb" is supported.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('REVERB_SERVER', 'reverb'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Reverb Servers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define details for each of the supported Reverb servers.
|
||||
| Each server has its own configuration options that are defined in
|
||||
| the array below. You should ensure all the options are present.
|
||||
|
|
||||
*/
|
||||
|
||||
'servers' => [
|
||||
|
||||
'reverb' => [
|
||||
'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
|
||||
'port' => env('REVERB_SERVER_PORT', 8080),
|
||||
'path' => env('REVERB_SERVER_PATH', ''),
|
||||
'hostname' => env('REVERB_HOST'),
|
||||
'options' => [
|
||||
'tls' => [],
|
||||
],
|
||||
'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000),
|
||||
'scaling' => [
|
||||
'enabled' => env('REVERB_SCALING_ENABLED', false),
|
||||
'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
|
||||
'server' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'database' => env('REDIS_DB', '0'),
|
||||
'timeout' => env('REDIS_TIMEOUT', 60),
|
||||
],
|
||||
],
|
||||
'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
|
||||
'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Reverb Applications
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define how Reverb applications are managed. If you choose
|
||||
| to use the "config" provider, you may define an array of apps which
|
||||
| your server will support, including their connection credentials.
|
||||
|
|
||||
*/
|
||||
|
||||
'apps' => [
|
||||
|
||||
'provider' => 'config',
|
||||
|
||||
'apps' => [
|
||||
[
|
||||
'key' => env('REVERB_APP_KEY'),
|
||||
'secret' => env('REVERB_APP_SECRET'),
|
||||
'app_id' => env('REVERB_APP_ID'),
|
||||
'options' => [
|
||||
'host' => env('REVERB_HOST'),
|
||||
'port' => env('REVERB_PORT', 443),
|
||||
'scheme' => env('REVERB_SCHEME', 'https'),
|
||||
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
|
||||
],
|
||||
'allowed_origins' => ['*'],
|
||||
'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
|
||||
'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30),
|
||||
'max_connections' => env('REVERB_APP_MAX_CONNECTIONS'),
|
||||
'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('inboxes', function (Blueprint $table) {
|
||||
$table->id()->index();
|
||||
$table->text('last_message')->nullable();
|
||||
$table->foreignIdFor(User::class, 'last_user_id')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['last_user_id', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('inboxes');
|
||||
}
|
||||
};
|
||||
@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Inbox;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('messages', function (Blueprint $table) {
|
||||
$table->id()->index();
|
||||
$table->foreignIdFor(User::class)->index();
|
||||
$table->foreignIdFor(Inbox::class)->index();
|
||||
$table->text('message');
|
||||
$table->time('read_at')->nullable();
|
||||
$table->time('delivered_at')->nullable();
|
||||
$table->time('deleted_at')->nullable();
|
||||
$table->time('failed_at')->nullable();
|
||||
$table->timestamp('created_at')->useCurrent()->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('messages');
|
||||
}
|
||||
};
|
||||
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Inbox;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('inbox_user', function (Blueprint $table) {
|
||||
$table->id()->index();
|
||||
$table->foreignIdFor(Inbox::class)->index();
|
||||
$table->foreignIdFor(User::class)->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('inbox_user');
|
||||
}
|
||||
};
|
||||
159
package-lock.json
generated
159
package-lock.json
generated
@ -11,9 +11,7 @@
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"axios": "^1.11.0",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-echo": "^2.3.0",
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"pusher-js": "^8.4.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"vite": "^7.0.7"
|
||||
}
|
||||
@ -866,14 +864,6 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
|
||||
@ -1327,25 +1317,6 @@
|
||||
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@ -1388,32 +1359,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
|
||||
"integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.4.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.18.3",
|
||||
"xmlhttprequest-ssl": "~2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.4",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz",
|
||||
@ -1751,20 +1696,6 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-echo": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-2.3.0.tgz",
|
||||
"integrity": "sha512-wgHPnnBvfHmu2I58xJ4asZH37Nu6P0472ku6zuoGRLc3zEWwIbpovDLYTiOshDH1SM7rA6AjZTKuu+jYoM1tpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pusher-js": "*",
|
||||
"socket.io-client": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-vite-plugin": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz",
|
||||
@ -2089,14 +2020,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
@ -2172,16 +2095,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pusher-js": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0.tgz",
|
||||
"integrity": "sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@ -2260,38 +2173,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
|
||||
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.4.1",
|
||||
"engine.io-client": "~6.6.1",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz",
|
||||
"integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@ -2401,13 +2282,6 @@
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
||||
"dev": true,
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||
@ -2525,39 +2399,6 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
||||
@ -10,9 +10,7 @@
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"axios": "^1.11.0",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-echo": "^2.3.0",
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"pusher-js": "^8.4.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"vite": "^7.0.7"
|
||||
},
|
||||
|
||||
@ -15,7 +15,6 @@ import {deleteRecentSearch} from "./deleteRecentSearch.js";
|
||||
import {initNavMenu} from "./nav-menu.js";
|
||||
import {toggleShimmer} from "./shimmer.js";
|
||||
import {follow} from "./interaction.js";
|
||||
import {addMessageToChat, sendMessage} from "./message.js";
|
||||
|
||||
document.deleteSearch = deleteRecentSearch;
|
||||
document.like = like;
|
||||
@ -24,8 +23,7 @@ document.redirect = redirect;
|
||||
document.showReportModal = showReportModal;
|
||||
window.toggleShimmer = toggleShimmer;
|
||||
window.follow = follow;
|
||||
window.sendMessage = sendMessage;
|
||||
window.addMessageToChat = addMessageToChat;
|
||||
|
||||
window.addEventListener('load', async () => {
|
||||
const preloader = document.getElementById('preloader');
|
||||
const content = document.getElementById('content');
|
||||
|
||||
8
resources/js/bootstrap.js
vendored
8
resources/js/bootstrap.js
vendored
@ -9,11 +9,3 @@ window.Chart = Chart;
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
window.axios.defaults.headers.common['Accept'] = 'application/json';
|
||||
window.axios.defaults.headers.common['Content-Type'] = 'application/json';
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allow your team to quickly build robust real-time web applications.
|
||||
*/
|
||||
|
||||
import './echo';
|
||||
|
||||
@ -97,7 +97,7 @@ function setDealDetails(dealDetails) {
|
||||
async function setComments(dealId, dealModal) {
|
||||
const commentsContainer = dealModal.querySelector('.comments-container');
|
||||
toggleShimmer(false, commentsContainer);
|
||||
commentsContainer.outerHTML = await getComments(dealId);
|
||||
commentsContainer.innerHTML = await getComments(dealId);
|
||||
toggleShimmer(true, commentsContainer);
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import Echo from 'laravel-echo';
|
||||
|
||||
import Pusher from 'pusher-js';
|
||||
window.Pusher = Pusher;
|
||||
|
||||
window.Echo = new Echo({
|
||||
broadcaster: 'reverb',
|
||||
key: import.meta.env.VITE_REVERB_APP_KEY,
|
||||
wsHost: import.meta.env.VITE_REVERB_HOST,
|
||||
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
|
||||
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
|
||||
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
|
||||
enabledTransports: ['ws', 'wss'],
|
||||
});
|
||||
@ -1,51 +0,0 @@
|
||||
import {showToast} from "./toast.js";
|
||||
|
||||
export const sendMessage = async (element, event) => {
|
||||
event.preventDefault();
|
||||
const messageInput = element.querySelector('[name="message"]');
|
||||
const message = messageInput.value;
|
||||
if (!message) return;
|
||||
|
||||
const recipentId = element.dataset.recipientId;
|
||||
messageInput.value = '';
|
||||
|
||||
// Capture the ID of the UI element we just added
|
||||
const tempMessageId = addMessageToChat({message: message}, true);
|
||||
|
||||
try {
|
||||
await axios.post(`/api/chat/${recipentId}/message`, {message: message});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast('Message could not be sent.');
|
||||
|
||||
const failedMessage = document.getElementById(tempMessageId);
|
||||
if (failedMessage) {
|
||||
failedMessage.remove();
|
||||
}
|
||||
|
||||
messageInput.value = message;
|
||||
}
|
||||
}
|
||||
|
||||
export const addMessageToChat = (message, right = false) => {
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
if (!chatContainer) return;
|
||||
|
||||
const tempId = 'msg-' + Date.now();
|
||||
|
||||
const placeholder = chatContainer.querySelector('#no-messages-placeholder');
|
||||
if (placeholder) placeholder.remove();
|
||||
|
||||
const messagePlaceholder = `
|
||||
<div id="${tempId}" class="grid px-4 my-1 w-full ${right ? 'place-items-end' : 'place-items-start'}">
|
||||
<div class="max-w-[40vw] py-2 px-4 rounded-full ${right ? 'rounded-br-none' : 'rounded-tl-none'} bg-gray-200">
|
||||
${message.message}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
chatContainer.insertAdjacentHTML('afterbegin', messagePlaceholder);
|
||||
chatContainer.scrollTop = 0;
|
||||
|
||||
// Return the ID so the caller can find this specific message later
|
||||
return tempId;
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
<x-layout>
|
||||
<div class="flex">
|
||||
<x-dashboard.broker.sidebar.layout>
|
||||
<div class="">
|
||||
<div class="flex space-x-4 items-center border-b border-b-gray-300 pb-6 mb-4">
|
||||
<x-logo/>
|
||||
<p class="font-bold text-2xl whitespace-nowrap group-[.w-20]:hidden">Messages</p>
|
||||
</div>
|
||||
{{$sidebarItems ?? ''}}
|
||||
</div>
|
||||
</x-dashboard.broker.sidebar.layout>
|
||||
<section
|
||||
class="flex relative flex-col space-y-4 md:space-y-8 bg-[#F9FAFB] overflow-y-auto overflow-x-hidden h-screen w-full">
|
||||
{{$slot}}
|
||||
</section>
|
||||
</div>
|
||||
</x-layout>
|
||||
@ -1,58 +0,0 @@
|
||||
@props(['recipient', 'messages' => []])
|
||||
@php
|
||||
$groupedMessages = $messages->groupBy(function($msg) {
|
||||
return $msg->created_at->format('M j, Y');
|
||||
});
|
||||
@endphp
|
||||
|
||||
<x-dashboard.page-heading class="m-0 mb-0.5" :title="$recipient->name" description="offline"/>
|
||||
<div class="bg-gray-50 h-full overflow-hidden flex-shrink-0">
|
||||
<div id="chat-container" data-auth-id="{{ auth()->id() }}" data-partner-id="{{ $recipient->id }}"
|
||||
class="text-sm flex flex-col-reverse overflow-y-scroll h-full max-h-screen pb-50 scroll-snap-y-container">
|
||||
@forelse($groupedMessages as $date => $dayMessages)
|
||||
@foreach($dayMessages as $message)
|
||||
<x-chat.message :right="$message->user_id === auth()->id()">
|
||||
<p>{{ $message->message }}</p>
|
||||
<span class="text-[10px] opacity-70 block text-right mt-1">
|
||||
{{ $message->created_at->format('g:i A') }}
|
||||
</span>
|
||||
</x-chat.message>
|
||||
@endforeach
|
||||
|
||||
{{-- The Date Divider --}}
|
||||
<div class="flex justify-center my-4 top-0">
|
||||
<span class="bg-gray-200 text-gray-600 text-xs px-3 py-1 rounded-full shadow-sm">
|
||||
{{ $date }}
|
||||
</span>
|
||||
</div>
|
||||
@empty
|
||||
<div id="no-messages-placeholder">No Messages Found !</div>
|
||||
@endforelse
|
||||
</div>
|
||||
<x-chat.message-input :recipient_id="$recipient->id"/>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const container = document.getElementById('chat-container');
|
||||
|
||||
if (container) {
|
||||
const authId = parseInt(container.dataset.authId);
|
||||
const partnerId = parseInt(container.dataset.partnerId);
|
||||
|
||||
// Sort IDs for consistent channel naming
|
||||
const user1 = Math.min(authId, partnerId);
|
||||
const user2 = Math.max(authId, partnerId);
|
||||
|
||||
window.Echo.private(`chat.${user1}.${user2}`)
|
||||
.listen('MessageSent', (e) => {
|
||||
const message = e.message;
|
||||
if (!message) return;
|
||||
// Check if user is the recipient of the message
|
||||
if (message.user_id === partnerId) {
|
||||
addMessageToChat({message: e.message.message}, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -1,10 +0,0 @@
|
||||
@props(['recipient_id'])
|
||||
<div class="absolute bottom-5 w-8/12 rounded-xl left-50 p-2 bg-white border border-gray-300 shadow-xl">
|
||||
<form id="messageBox" data-recipient-id="{{$recipient_id}}" onsubmit="sendMessage(this, event)" action=""
|
||||
class="flex space-x-4 items-center">
|
||||
<x-ui.textarea class="flex-1" rows="1" name="message" placeholder="Enter your message..."></x-ui.textarea>
|
||||
<x-ui.button variant="neutral" icon="">
|
||||
<x-heroicon-o-paper-airplane class="w-5 h-5"/>
|
||||
</x-ui.button>
|
||||
</form>
|
||||
</div>
|
||||
@ -1,11 +0,0 @@
|
||||
@props(['right' => false])
|
||||
<div class="grid px-4 my-1 w-full @if($right) place-items-end @else place-items-start @endif">
|
||||
<div class="max-w-[70vw] md:max-w-[40vw] py-2 px-4 bg-gray-200 rounded-3xl
|
||||
@if($right)
|
||||
rounded-br-none
|
||||
@else
|
||||
rounded-tl-none
|
||||
@endif">
|
||||
{{$slot}}
|
||||
</div>
|
||||
</div>
|
||||
@ -1,8 +0,0 @@
|
||||
@props(['avatar', 'name', 'status' => 'inactive', 'message' => '', 'link' => ''])
|
||||
<x-dashboard.broker.sidebar.item :link="$link" class="">
|
||||
<x-ui.avatar class="w-12! h-12! text-xl!">{{$avatar}}</x-ui.avatar>
|
||||
<div class="flex flex-col text-left max-w-24">
|
||||
<p class="sidebar-text text-sm transition-opacity duration-300 ease-in-out font-bold">{{$name}}</p>
|
||||
<p class="sidebar-text text-sm text-accent-600 transition-opacity duration-300 ease-in-out truncate">{{$message}}</p>
|
||||
</div>
|
||||
</x-dashboard.broker.sidebar.item>
|
||||
@ -6,7 +6,7 @@
|
||||
<div id="sidebar"
|
||||
class="flex flex-col p-4 pt-6 justify-between font-medium h-full w-full transition-all duration-300 ease-in-out">
|
||||
<div>
|
||||
<div class="flex space-x-4 border-b border-b-gray-300 pb-6 ">
|
||||
<div class="flex space-x-4 border-b border-b-gray-300 pb-6 n">
|
||||
<x-logo/>
|
||||
<a href="{{route('home')}}" class="whitespace-nowrap group-[.w-20]:hidden">
|
||||
<p class="text-2xl font-bold">DealHub</p>
|
||||
@ -64,7 +64,7 @@ class="py-3 pl-3 border border-white hover:bg-red-50 hover:border-red-200 rounde
|
||||
|
||||
{{-- Toggle Button --}}
|
||||
<div
|
||||
class="text-gray-500 cursor-pointer hover:text-gray-900 rounded-full p-1.5 bg-white border border-gray-300 absolute -right-3.5 top-21 z-20">
|
||||
class="text-gray-500 cursor-pointer hover:text-gray-900 rounded-full p-1.5 bg-white border border-gray-300 absolute -right-3.5 top-21">
|
||||
<x-heroicon-c-chevron-left id="closeSidebarBtn" class="w-4"/>
|
||||
<x-heroicon-c-chevron-right id="openSidebarBtn" class="w-4 hidden"/>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
<x-dashboard.broker.sidebar.layout>
|
||||
@props(['activeClass' => 'bg-gray-100 text-gray-900'])
|
||||
|
||||
<div id="sidebarWrapper" {{$attributes->merge([ 'class' => 'border-r border-r-gray-300 transition-all duration-300 ease-in-out w-64 relative'])}}>
|
||||
<div class="hidden md:flex h-screen items-center">
|
||||
<div id="sidebar" class="flex flex-col p-4 pt-6 justify-between font-medium h-full w-full overflow-hidden transition-all duration-300 ease-in-out">
|
||||
<div class="">
|
||||
<div class="flex space-x-4 border-b border-b-gray-300 pb-6">
|
||||
<x-logo/>
|
||||
@ -35,8 +39,7 @@
|
||||
<form method="post" action="{{route('logout')}}">
|
||||
@csrf
|
||||
@method('delete')
|
||||
<button
|
||||
class="py-3 pl-3 border border-white hover:bg-red-50 hover:border-red-200 rounded-xl w-full mt-4 transition-all">
|
||||
<button class="py-3 pl-3 border border-white hover:bg-red-50 hover:border-red-200 rounded-xl w-full mt-4 transition-all">
|
||||
<div class="flex space-x-3 items-center text-red-500">
|
||||
<x-heroicon-o-arrow-right-start-on-rectangle class="w-5 min-w-5"/>
|
||||
<p class="sidebar-text transition-opacity duration-300 ease-in-out">Logout</p>
|
||||
@ -44,4 +47,15 @@ class="py-3 pl-3 border border-white hover:bg-red-50 hover:border-red-200 rounde
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</x-dashboard.broker.sidebar.layout>
|
||||
</div>
|
||||
|
||||
{{-- Toggle Button --}}
|
||||
<div class="text-gray-500 cursor-pointer hover:text-gray-900 rounded-full p-1.5 bg-white border border-gray-300 absolute -right-3.5 top-21">
|
||||
<x-heroicon-c-chevron-left id="closeSidebarBtn" class="w-4"/>
|
||||
<x-heroicon-c-chevron-right id="openSidebarBtn" class="w-4 hidden"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@vite(['resources/js/sidebar.js'])
|
||||
|
||||
|
||||
|
||||
@ -6,15 +6,9 @@
|
||||
@endphp
|
||||
@aware(['activeClass' => 'bg-gray-100 text-gray-900'])
|
||||
<div class="relative group/item w-full hover:z-10">
|
||||
@if(filled($link))
|
||||
<a href="{{$link}}" {{$attributes->class(["flex space-x-3 items-center pl-3 py-3 rounded-xl hover:bg-gray-100 border border-transparent ease-in-out transition-all duration-300 active:scale-80 hover:border-gray-300", $activeClass => $active])}} >
|
||||
{{$slot}}
|
||||
</a>
|
||||
@else
|
||||
<button {{$attributes->class(["flex space-x-3 items-center pl-3 py-3 rounded-xl hover:bg-gray-100 border border-transparent ease-in-out transition-all duration-300 active:scale-80 hover:border-gray-300", $activeClass => $active])}} >
|
||||
{{$slot}}
|
||||
</button>
|
||||
@endif
|
||||
@if($tooltip !== '')
|
||||
<span
|
||||
class="absolute z-10 top-[30%] hidden group-[.w-20]:group-hover/item:block left-[110%] py-1 px-2 rounded-lg bg-gray-900 text-xs whitespace-nowrap text-white">
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
@props(['activeClass' => 'bg-gray-100 text-gray-900'])
|
||||
<div id="sidebarWrapper" {{$attributes->merge([ 'class' => 'border-r border-r-gray-300 transition-all group duration-300 ease-in-out w-64 relative z-10'])}}>
|
||||
<div class="hidden md:flex w-max h-screen items-center">
|
||||
<div id="sidebar" class="flex flex-col p-4 pt-6 justify-between font-medium h-full w-full overflow-hidden transition-all duration-300 ease-in-out">
|
||||
{{$slot}}
|
||||
</div>
|
||||
|
||||
{{-- Toggle Button --}}
|
||||
<div class="text-gray-500 cursor-pointer hover:text-gray-900 rounded-full p-1.5 bg-white border border-gray-300 absolute -right-3.5 top-21 z-30">
|
||||
<x-heroicon-c-chevron-left id="closeSidebarBtn" class="w-4"/>
|
||||
<x-heroicon-c-chevron-right id="openSidebarBtn" class="w-4 hidden"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@push('scripts')
|
||||
@vite(['resources/js/sidebar.js'])
|
||||
@endpush
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
@props(['title' => '', 'description' => '', 'backLink' => ''])
|
||||
<section {{$attributes->merge(['class' => 'flex space-x-6 items-center justify-between wrapper py-6 shadow w-full'])}}>
|
||||
<section class="flex space-x-6 items-center justify-between wrapper py-6 shadow w-full">
|
||||
<div class="flex space-x-6 items-center">
|
||||
@if($backLink !== '')
|
||||
<div class="">
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
@props(['broker', 'is_followed' => false])
|
||||
@props(['broker' => '', 'is_followed' => false])
|
||||
<div {{$attributes->merge(['class' => "p-4 text-sm bg-gray-100 border-gray-200 border rounded-xl"])}}>
|
||||
<div class="flex space-x-2 items-center mb-2">
|
||||
<p class="font-bold">Broker Contact</p>
|
||||
<x-ui.button-sm data-is-loading="false" data-followed="{{$is_followed ? 'true' : 'false'}}"
|
||||
onclick="follow(this, {{$broker->role_id ?? 0}})" class="followBtn group p-0! mt-0.5">
|
||||
<div class="flex space-x-4 items-baseline">
|
||||
<p class="font-bold mb-2">Broker Contact</p>
|
||||
<x-ui.button-sm data-is-loading="false" data-followed="{{$is_followed ? 'true' : 'false'}}" onclick="follow(this, {{$broker->role_id ?? ''}})" class="followBtn group p-0! mt-0.5">
|
||||
<span class="group-data-[followed=true]:hidden text-blue-600">Follow</span>
|
||||
<span class="group-data-[followed=false]:hidden text-accent-600">Unfollow</span>
|
||||
</x-ui.button-sm>
|
||||
<x-ui.button-sm :link="route('chat.show', ['recipient' => $broker->id ?? 0])">
|
||||
<x-heroicon-o-chat-bubble-oval-left class="w-4 text-accent-600"/>
|
||||
</x-ui.button-sm>
|
||||
</div>
|
||||
<div class="text-accent-600 space-y-1">
|
||||
<p data-is-loading="false" class="broker-name">{{$broker->name ?? ''}}</p>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
@props(['comments' => []])
|
||||
<div data-is-loading="true" class="comments-container mt-2 space-y-2 max-h-40 overflow-y-auto data-[is-loading=true]:h-10">
|
||||
<div data-is-loading="true" class="comments-container mt-2 space-y-2 max-h-40 overflow-y-scroll data-[is-loading=true]:h-10">
|
||||
@forelse($comments as $comment)
|
||||
<x-dashboard.user.deal-comment.item :comment="$comment"/>
|
||||
@empty
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<x-ui.modal id="deal-modal" class="deal-identifier w-11/12 md:w-10/12 overflow-y-auto">
|
||||
<x-ui.modal id="deal-modal" class="deal-identifier w-11/12 md:w-10/12 overflow-scroll">
|
||||
<form class="flex justify-between items-start mb-4" method="dialog">
|
||||
<p class="text-xl font-bold">Deal Details</p>
|
||||
<button type="submit" class="">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
@props(['deal'])
|
||||
@props(['deal' => '', 'broker' => ''])
|
||||
<x-ui.image-card class="deal-identifier deal-card shadow-lg cursor-pointer" :image="asset('storage/'.$deal->image)"
|
||||
data-deal-id="{{$deal->id}}">
|
||||
<div class="bg-white pt-8 p-4 h-full space-y-2 flex flex-col justify-between">
|
||||
@ -24,7 +24,7 @@
|
||||
</a>
|
||||
@endguest
|
||||
@auth
|
||||
<x-dashboard.user.broker-contact :broker="$deal->broker" :is_followed="$deal->is_followed"/>
|
||||
<x-dashboard.user.broker-contact :broker="$broker" :is_followed="$deal->is_followed"/>
|
||||
@endauth
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
@props(['deals' => [], 'isInteractive'])
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
@forelse($deals as $deal)
|
||||
<x-dashboard.user.listing-card :deal="$deal" />
|
||||
<x-dashboard.user.listing-card :deal="$deal" :broker="$deal->broker"/>
|
||||
@empty
|
||||
<p class="col-span-2 text-sm text-center text-accent-600 mt-12">No Deals found !</p>
|
||||
@endforelse
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
@php use App\Enums\UserTypes; @endphp
|
||||
@props(['profileLink' => ''])
|
||||
<div class="flex items-center">
|
||||
@auth
|
||||
@ -14,7 +13,7 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@if(auth()->check() && auth()->user()->role === UserTypes::Broker->value)
|
||||
@if(auth()->check() && auth()->user()->role === \App\Enums\UserTypes::Broker->value)
|
||||
<li class="py-2 px-4 hover:bg-gray-100 hover:text-gray-900 hover:cursor-pointer hover:font-bold">
|
||||
<a href="{{route('broker.dashboard')}}" class="flex space-x-4">
|
||||
<div class="p-1 bg-gray-200 rounded-xl text-gray-900">
|
||||
@ -25,14 +24,6 @@
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li class="py-2 px-4 hover:bg-gray-100 hover:text-gray-900 hover:cursor-pointer hover:font-bold">
|
||||
<a href="{{route('chat')}}" class="flex space-x-4">
|
||||
<div class="p-1 bg-gray-200 rounded-xl text-gray-900">
|
||||
<x-heroicon-o-chat-bubble-left-right class="w-4"/>
|
||||
</div>
|
||||
<p>Chat</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@endauth
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
<section
|
||||
{{$attributes->merge(['class' => 'w-25 h-25 rounded-xl bg-linear-150 from-[#305afc] to-[#941dfb] text-5xl text-white flex justify-center items-center'])}}>
|
||||
{{$slot}}
|
||||
</section>
|
||||
@ -1,6 +1,7 @@
|
||||
@props(['label' => '', 'name' => '', 'placeholder' => '', 'required' => false, 'value' => '', 'description', 'rows'=>1])
|
||||
<div {{$attributes->merge(['class' => 'flex flex-col space-y-2'])}}>
|
||||
@if($label !== '')
|
||||
@props(['label' => '', 'name' => '', 'placeholder' => '', 'required' => false, 'value' => '', 'description'])
|
||||
@if($label !== '')
|
||||
<div class="flex flex-col space-y-2">
|
||||
|
||||
<label class="text-sm font-bold" for="{{$name}}">
|
||||
{{$label}}
|
||||
|
||||
@ -8,10 +9,9 @@
|
||||
*
|
||||
@endif
|
||||
</label>
|
||||
@endif
|
||||
|
||||
<textarea
|
||||
class="bg-[#F3F3F5] py-2 px-4 rounded-lg"
|
||||
rows="{{$rows}}"
|
||||
class="bg-[#F3F3F5] py-2 px-4 rounded-lg h-40"
|
||||
name="{{$name}}" placeholder="{{$placeholder}}"
|
||||
required="{{$required?'required':''}}"
|
||||
>{{old($name, $value)}}</textarea>
|
||||
@ -21,4 +21,5 @@ class="bg-[#F3F3F5] py-2 px-4 rounded-lg"
|
||||
@endisset
|
||||
|
||||
<x-ui.inline-error :name="$name"/>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@ -23,7 +23,10 @@ class="border border-accent-600/40">
|
||||
|
||||
<div
|
||||
class="col-span-8 place-self-start md:col-span-2 lg:col-span-1 flex items-center justify-center w-full">
|
||||
<x-ui.avatar>{{$initials}}</x-ui.avatar>
|
||||
<div
|
||||
class="w-25 h-25 rounded-xl bg-linear-150 from-[#305afc] to-[#941dfb] text-5xl text-white flex justify-center items-center">
|
||||
{{$initials}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-8 md:col-span-6 lg:col-span-7 flex flex-col space-y-6">
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
@php use App\Services\ProfileInitialsService; @endphp
|
||||
<x-chat.layout>
|
||||
<x-slot:sidebarItems>
|
||||
@forelse($inboxes as $inbox)
|
||||
<x-chat.sidebar-item
|
||||
:name="$inbox->recipient->name"
|
||||
:link="route('chat.show', $inbox->recipient->id)"
|
||||
:avatar="(new ProfileInitialsService)->create($inbox->recipient->name)"
|
||||
:message="$inbox->last_message"/>
|
||||
@empty
|
||||
No chats found !
|
||||
@endforelse
|
||||
</x-slot:sidebarItems>
|
||||
<div class="overflow-y-hidden">
|
||||
|
||||
@if(isset($recipient))
|
||||
<x-chat.message-box :recipient="$recipient" :messages="$messages"/>
|
||||
@else
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<p class="font-bold text-5xl text-gray-400">Start a chat ! </p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<x-ui.toast/>
|
||||
</x-chat.layout>
|
||||
@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\ChatController;
|
||||
use App\Http\Controllers\RecentSearchController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@ -13,6 +12,4 @@
|
||||
include __DIR__.'/push-notification.php';
|
||||
|
||||
Route::delete('/recent-search/{recentSearch}', RecentSearchController::class)->name('recent-search.destroy');
|
||||
|
||||
Route::post('/chat/{recipient}/message', [ChatController::class, 'store'])->name('chat.message');
|
||||
});
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
|
||||
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
|
||||
return (int) $user->id === (int) $id;
|
||||
});
|
||||
|
||||
Broadcast::channel('chat.{user1}.{user2}', function (User $user, int $user1, int $user2) {
|
||||
// Only allow the user if their ID matches one of the two in the channel name
|
||||
return $user->id === $user1 || $user->id === $user2;
|
||||
});
|
||||
@ -10,14 +10,26 @@
|
||||
require __DIR__.'/web/interaction.php';
|
||||
require __DIR__.'/web/customer.php';
|
||||
require __DIR__.'/web/admin.php';
|
||||
require __DIR__.'/web/chat.php';
|
||||
|
||||
Route::get('/', HomeController::class)->name('home');
|
||||
Route::get('/explore', ExplorePageController::class)->name('explore');
|
||||
Route::post('/contact', ContactController::class)->name('contact');
|
||||
|
||||
Route::get('/test-openssl', function () {
|
||||
$res = openssl_pkey_new([
|
||||
'curve_name' => 'prime256v1',
|
||||
'private_key_type' => OPENSSL_KEYTYPE_EC,
|
||||
]);
|
||||
|
||||
if ($res === false) {
|
||||
return 'ERROR: '.openssl_error_string(); // likely "error:02001003:system library:fopen:No such process"
|
||||
}
|
||||
|
||||
return 'SUCCESS: OpenSSL is configured correctly.';
|
||||
});
|
||||
|
||||
/**
|
||||
* This route is accessed by JS XHR requests, and is loaded here cause
|
||||
* This routes are accessed by JS XHR requests, and is loaded here cause
|
||||
* we do not want to use sanctum for web requests
|
||||
*/
|
||||
// ------------- API Routes ------------
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\ChatController;
|
||||
use App\Http\Middleware\EnsureUserFollowedBroker;
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::get('/chat', [ChatController::class, 'index'])->name('chat');
|
||||
|
||||
Route::get('/chat/{recipient}', [ChatController::class, 'show'])
|
||||
->middleware(EnsureUserFollowedBroker::class)
|
||||
->name('chat.show');
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user