Club System
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
}
|
||||
}" x-init="init()">
|
||||
<div>
|
||||
@if( section_must_be( [ 'romhacks', 'homebrew' ], $section ) )
|
||||
@if( section_must_be( [ 'romhacks', 'homebrew' ], $section ) && !$isEdit )
|
||||
<label class="nsfw-label"><input id="nsfw-checkbox" type="checkbox" name="nsfw-entry" x-model="nsfw" style="transform: scale(1.5)"> NSFW</label>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<?php /** @var \App\Models\EntryGallery $galleryItem */ ?>
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('page-title', $entry->title . " - " . config('app.name') )
|
||||
@@ -19,6 +20,37 @@
|
||||
<div class="entry-info">
|
||||
<h1 class="entry-title">
|
||||
{{ $entry->title }}
|
||||
@if( $entry->state === 'pending' )
|
||||
<div style="display:inline;color:var(--rhpz-orange);">
|
||||
-
|
||||
<i data-lucide="clock" size="24"></i>
|
||||
Pending approval
|
||||
</div>
|
||||
@elseif( $entry->state === 'rejected' )
|
||||
<div style="display:inline;color:var(--error);">
|
||||
-
|
||||
<i data-lucide="x-circle" size="24"></i>
|
||||
Rejected
|
||||
</div>
|
||||
@elseif( $entry->state === 'locked' )
|
||||
<div style="display:inline;color:var(--error);">
|
||||
-
|
||||
<i data-lucide="lock" size="24"></i>
|
||||
Locked
|
||||
</div>
|
||||
@elseif( $entry->state === 'draft' )
|
||||
<div style="display:inline;color:var(--rhpz-orange);">
|
||||
-
|
||||
<i data-lucide="scissors" size="24"></i>
|
||||
Draft
|
||||
</div>
|
||||
@elseif( $entry->state === 'hidden' )
|
||||
<div style="display:inline;color:var(--rhpz-orange);">
|
||||
-
|
||||
<i data-lucide="hide" size="24"></i>
|
||||
Hidden
|
||||
</div>
|
||||
@endif
|
||||
</h1>
|
||||
<div class="entry-authors">
|
||||
@forelse( $entry->authors as $author)
|
||||
@@ -29,6 +61,26 @@
|
||||
No authors
|
||||
@endforelse
|
||||
</div>
|
||||
<div class="entry-submission-byline">
|
||||
@if($entry->user_id)
|
||||
<span>
|
||||
<i data-lucide="user" size="14"></i>
|
||||
Posted by <x-xf-username-link :user-id="$entry->user_id" />
|
||||
</span>
|
||||
@endif
|
||||
|
||||
<span>
|
||||
<i data-lucide="calendar" size="14"></i>
|
||||
{{ $entry->created_at->format('M d, Y') }}
|
||||
</span>
|
||||
|
||||
@if($entry->updated_at && $entry->updated_at->gt($entry->created_at))
|
||||
<span>
|
||||
<i data-lucide="file-edit" size="14"></i>
|
||||
Updated {{ $entry->updated_at->diffForHumans() }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="entry-meta-grid">
|
||||
@if( $entry->game )
|
||||
<x-entry-meta-item label="Game Name" value="{{ $entry->game->name }}" />
|
||||
@@ -55,18 +107,78 @@
|
||||
<x-entry-meta-item label="Type of hack" value="{{ $entry->modifications->pluck('name')->implode(', ') }}" route="none" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="hack-actions">
|
||||
<div class="hack-actions" style="display:flex;gap:10px;">
|
||||
@if($entry->state === 'pending')
|
||||
@can('approve', $entry)
|
||||
<div x-data="{ rejectOpen: false }">
|
||||
<form action="{{ route('queue.approve', $entry) }}" method="POST" style="display:inline">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<button type="submit" class="btn success" onclick="return confirm('Approve this entry?')">
|
||||
<i data-lucide="check-circle" size="14"></i>
|
||||
Approve
|
||||
</button>
|
||||
</form>
|
||||
<button type="button" class="btn danger" style="margin-right:15px;" @click="rejectOpen = !rejectOpen">
|
||||
<i data-lucide="x-circle" size="14"></i>
|
||||
Reject
|
||||
</button>
|
||||
<div
|
||||
class="modal-overlay"
|
||||
x-cloak
|
||||
x-show="rejectOpen"
|
||||
x-transition.opacity
|
||||
@click.self="rejectOpen = false"
|
||||
@keydown.escape.window="rejectOpen = false"
|
||||
@modal:opened.window="refreshIcons($el)"
|
||||
>
|
||||
<div class="modal-window" x-show="rejectOpen" x-transition>
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">Reject entry</span>
|
||||
<button type="button" class="modal-close" @click="rejectOpen = false">
|
||||
<i data-lucide="x" size="20"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<form action="{{ route('queue.reject', $entry) }}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<div class="form-group">
|
||||
<x-form-field-title name="Rejection reason" required="true" />
|
||||
<textarea
|
||||
class="form-input"
|
||||
name="reason"
|
||||
rows="4"
|
||||
placeholder="Explain why this entry is being rejected..."
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="queue-mod-actions">
|
||||
<button type="button" class="btn" @click="rejectOpen = false">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn danger">
|
||||
<i data-lucide="x-circle" size="14"></i>
|
||||
Confirm rejection
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
@endif
|
||||
<button class="btn primary" onclick="Livewire.dispatch('entryOpenFilesModal', { entryId: {{ $entry->id }} })">
|
||||
<i data-lucide="download"></i> Download
|
||||
</button>
|
||||
@can('update',$entry)
|
||||
<a href="{{ route('submit.edit', ['section' => $entry->type, 'entry' => $entry ] ) }}" class="btn primary">
|
||||
<a href="{{ route('submit.edit', ['section' => $entry->type, 'entry' => $entry ] ) }}" class="btn">
|
||||
<i data-lucide="edit"></i> Edit
|
||||
</a>
|
||||
@endcan
|
||||
<button class="btn">
|
||||
<i data-lucide="message-square"></i> Comments
|
||||
</button>
|
||||
@auth
|
||||
<a href="{{ xfRoute("romhackplaza_entry/{$entry->id}/report") }}" class="btn">
|
||||
<i data-lucide="flag"></i> Report / Claim Ownership
|
||||
@@ -95,11 +207,55 @@
|
||||
</div>
|
||||
@endif
|
||||
@if( $entry->staff_credits )
|
||||
<h2 class="entry-section-title">
|
||||
<i data-lucide="users-round"></i> Staff credits
|
||||
</h2>
|
||||
<x-entry-section-title label="Staff Credits" icon="users-round" />
|
||||
<div class="entry-description">
|
||||
{{ $entry->staff_credits }}
|
||||
<ul>
|
||||
@foreach( $entry->parseStaffCredits() as $item )
|
||||
<li>{{ $item['name'] }}: {{ $item['description'] }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
@if( $entry->gallery->isNotEmpty() )
|
||||
<div x-data="{ open: false, currentImage: ''}" x-cloak>
|
||||
<x-entry-section-title label="Gallery" icon="images" />
|
||||
<div class="entry-gallery">
|
||||
@foreach( $entry->gallery as $galleryItem )
|
||||
<div class="entry-gallery-item" @click="currentImage = '{{ Storage::url($galleryItem->image) }}'; open = true; "><img src="{{ Storage::url($galleryItem->image) }}"></div>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="gallery-modal" x-show="open" x-transition.opacity.duration.300ms @click="open = false" @keydown.escape.window="open = false">
|
||||
<span class="gallery-modal-close" @click="open = false;"><i data-lucide="x"></i></span>
|
||||
<div class="gallery-modal-content" @click.stop>
|
||||
<img :src="currentImage">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if( $entry->relevant_link )
|
||||
<x-entry-section-title label="Relevant Link" icon="link" />
|
||||
<div class="entry-description">
|
||||
<a href="{{ $entry->relevant_link }}" target="_blank">{{ $entry->relevant_link }}</a>
|
||||
</div>
|
||||
@endif
|
||||
@if( $entry->getYoutubeVideoId() )
|
||||
<div x-data="{open: false, src: ''}" x-cloak class="youtube-section">
|
||||
|
||||
<x-entry-section-title label="Youtube Video" icon="play" />
|
||||
|
||||
<div class="video-thumbnail-wrapper" @click="src = 'https://www.youtube.com/embed/{{ $entry->getYoutubeVideoId() }}?autoplay=1'; open = true">
|
||||
<img src="https://img.youtube.com/vi/{{ $entry->youtube_id }}/maxresdefault.jpg">
|
||||
<div class="play-trigger">
|
||||
<i data-lucide="play"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gallery-modal" x-show="open" x-transition.opacity.duration.300ms @click="open = false; src = ''" @keydown.escape.window="open = false; src = ''">
|
||||
<span class="gallery-modal-close" @click="open = false; src = '';"><i data-lucide="x"></i></span>
|
||||
<div class="gallery-modal-video" @click.stop>
|
||||
<iframe :src="src" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
31
resources/views/livewire/xf-user-selector.blade.php
Normal file
31
resources/views/livewire/xf-user-selector.blade.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="author-search" x-data>
|
||||
<div class="author-search-input">
|
||||
<i data-lucide="search" size="14"></i>
|
||||
<input type="text" class="form-input" wire:model.live.debounce.300ms="search" autocomplete="off">
|
||||
@if($selected)
|
||||
<span class="author-search-selected">
|
||||
<i data-lucide="check" size="13"></i>
|
||||
{{ $selectedUsername }}
|
||||
</span>
|
||||
<button type="button" class="btn" wire:click="$set('selected', null); $set('search', '')">
|
||||
Remove
|
||||
</button>
|
||||
@endif
|
||||
@if($selected)
|
||||
<input type="hidden" name="owner_user_id" value="{{ $selected }}">
|
||||
@endif
|
||||
@if(count($this->results) > 0)
|
||||
<div class="author-search-dropdown">
|
||||
@foreach($this->results as $user)
|
||||
<button
|
||||
type="button"
|
||||
class="author-search-item"
|
||||
wire:click="selectUser({{ $user->user_id }}, '{{ addslashes($user->username) }}')"
|
||||
>
|
||||
{{ $user->username }}
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
19
resources/views/queue/index.blade.php
Normal file
19
resources/views/queue/index.blade.php
Normal file
@@ -0,0 +1,19 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('page-title', "Submissions Queue - " . config('app.name') )
|
||||
|
||||
@section('content')
|
||||
<div class="page-title">
|
||||
Submissions Queue
|
||||
</div>
|
||||
@if($entries->isEmpty())
|
||||
<div class="queue-empty">
|
||||
<i data-lucide="inbox" size="48"></i>
|
||||
<p>No pending submissions.</p>
|
||||
</div>
|
||||
@else
|
||||
@foreach($entries as $entry)
|
||||
@include('queue.item', ['entry' => $entry ] )
|
||||
@endforeach
|
||||
@endif
|
||||
@endsection
|
||||
155
resources/views/queue/item.blade.php
Normal file
155
resources/views/queue/item.blade.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<div class="queue-item
|
||||
{{ $entry->state === 'rejected' ? 'queue-item--rejected' : 'queue-item--pending' }}
|
||||
"
|
||||
@if($entry->state === 'rejected')
|
||||
style="--reject-progress: {{ min(100, (now()->diffInDays($entry->rejected_at) / 7) * 100) }}%"
|
||||
@endif
|
||||
>
|
||||
<div class="queue-item-header">
|
||||
<div class="queue-item-info">
|
||||
<h3 class="queue-item-title">{{ $entry->complete_title }}</h3>
|
||||
@if($entry->state === 'rejected')
|
||||
<span class="badge badge--danger">
|
||||
<i data-lucide="x-circle" size="12"></i>
|
||||
Rejected
|
||||
@php
|
||||
$daysLeft = intval(7 - now()->diffInDays($entry->rejected_at));
|
||||
@endphp
|
||||
@if($daysLeft > 0)
|
||||
- deleted in {{ $daysLeft }} days
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
<div class="queue-item-meta">
|
||||
Submitted by <x-xf-username-link :user-id="$entry->user_id" />
|
||||
on {{ $entry->created_at->format('Y-m-d') }}
|
||||
<span class="badge {{ $entry->type }}">{{ \App\View\Components\EntryCard::ENTRY_TYPES_BADGE[$entry->type] ?? $entry->type }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@can('manageButtonsInQueue',$entry)
|
||||
<div class="queue-item-actions-header">
|
||||
<a href="{{ route('entries.show', ['section' => $entry->type, 'entry' => $entry ]) }}" class="btn" target="_blank">
|
||||
<i data-lucide="external-link" size="14"></i>
|
||||
View Entry
|
||||
</a>
|
||||
<a href="{{ route('submit.edit', ['section' => $entry->type, 'entry' =>$entry ] ) }}" class="btn" target="_blank">
|
||||
<i data-lucide="pen" size="14"></i>
|
||||
Edit
|
||||
</a>
|
||||
</div>
|
||||
@endcan
|
||||
</div>
|
||||
|
||||
<div class="timeline-container">
|
||||
<div class="timeline">
|
||||
|
||||
|
||||
<div class="timeline-step timeline-step--validated">
|
||||
<div class="timeline-dot">
|
||||
<i data-lucide="check" size="16"></i>
|
||||
</div>
|
||||
<span class="timeline-label">Submitted</span>
|
||||
</div>
|
||||
|
||||
<div class="timeline-step {{ $entry->state === 'pending' ? 'timeline-step--active' : 'timeline-step--validated' }}">
|
||||
<div class="timeline-dot">
|
||||
@if($entry->state === 'pending')
|
||||
<i data-lucide="search" size="16"></i>
|
||||
@else
|
||||
<i data-lucide="check" size="16"></i>
|
||||
@endif
|
||||
</div>
|
||||
<span class="timeline-label">Under review</span>
|
||||
</div>
|
||||
|
||||
@if($entry->state === 'rejected')
|
||||
<div class="timeline-step timeline-step--rejected">
|
||||
<div class="timeline-dot">
|
||||
<i data-lucide="x" size="16"></i>
|
||||
</div>
|
||||
<span class="timeline-label">Rejected</span>
|
||||
</div>
|
||||
@else
|
||||
<div class="timeline-step {{ $entry->state === 'published' ? 'timeline-step--validated' : '' }}">
|
||||
<div class="timeline-dot">
|
||||
@if($entry->state === 'published')
|
||||
<i data-lucide="check" size="16"></i>
|
||||
@endif
|
||||
</div>
|
||||
<span class="timeline-label">Approved</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($entry->state === 'rejected' && $entry->staff_comment)
|
||||
<div class="queue-reject-reason">
|
||||
<i data-lucide="alert-circle" size="14"></i>
|
||||
<div>
|
||||
<strong>Rejection reason :</strong>
|
||||
{{ $entry->staff_comment }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@can('approve',$entry)
|
||||
@if($entry->state === 'pending')
|
||||
<div class="queue-mod-zone">
|
||||
<form action="{{ route('queue.comment', $entry ) }}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<div class="form-group">
|
||||
<x-form-field-title name="Comment" />
|
||||
<textarea class="form-input" name="comment" rows="3">{{ $entry->staff_comment }}</textarea>
|
||||
</div>
|
||||
<div class="queue-mod-actions">
|
||||
<button type="submit" class="btn">
|
||||
<i data-lucide="save" size="14"></i>
|
||||
Save comment
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="queue-mod-separator"></div>
|
||||
|
||||
<form action="{{ route('queue.approve', $entry) }}" method="POST" style="display:inline">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<button type="submit" class="btn success" onclick="return confirm('Approve this entry?')">
|
||||
<i data-lucide="check-circle" size="14"></i>
|
||||
Approve
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div x-data="{open: false}">
|
||||
<button type="button" class="btn danger" @click="open = !open">
|
||||
<i data-lucide="x-circle" size="14"></i>
|
||||
Reject
|
||||
</button>
|
||||
|
||||
<div x-show="open" x-cloak class="queue-reject-form">
|
||||
<form action="{{ route('queue.reject', $entry) }}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<div class="form-group">
|
||||
<x-form-field-title name="Rejection reason" required="true" />
|
||||
<textarea class="form-input" name="reason" rows="3" required></textarea>
|
||||
</div>
|
||||
<div class="queue-mod-actions">
|
||||
<button type="button" class="btn" @click="open = false">Cancel</button>
|
||||
<button type="submit" class="btn btn--danger">
|
||||
<i data-lucide="x-circle" size="14"></i>
|
||||
Confirm rejection
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endif
|
||||
@endcan
|
||||
|
||||
</div>
|
||||
@@ -168,10 +168,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($isEdit)
|
||||
<x-form-group-title label="Entry Management" icon="wrench" />
|
||||
@can('moderate',$entry)
|
||||
<div class="form-group grid-c2">
|
||||
<div>
|
||||
<x-form-field-title name="Staff comment" />
|
||||
<textarea class="form-textarea" name="staff_comment" rows="3">{{ old('staff_comment', $entry->staff_comment ?? '' ) }}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<x-form-field-title name="Owner" required="true" />
|
||||
<livewire:xf-user-selector :initial-user-id="$entry->user_id" />
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
@cannot('moderate', $entry)
|
||||
|
||||
@endcannot
|
||||
@endif
|
||||
|
||||
@csrf
|
||||
|
||||
<div class="submit">
|
||||
<x-submit-entry-status :section="$section" />
|
||||
<x-submit-entry-status :section="$section" :is-edit="$isEdit" :current-state="$entry->state ?? null" :entry="$entry" />
|
||||
<button id="submit-button" type="submit" class="btn primary" style="padding:1%;">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -39,12 +39,18 @@
|
||||
<template x-if="!file.done && !file.error">
|
||||
<i data-lucide="loader-2" class="spin"></i>
|
||||
</template>
|
||||
<template x-if="file.done">
|
||||
<i data-lucide="check-circle"></i>
|
||||
</template>
|
||||
<template x-if="file.error">
|
||||
<i data-lucide="alert-circle"></i>
|
||||
</template>
|
||||
<template x-if="file.done && file.state === 'public'">
|
||||
<i data-lucide="eye" class="file-state-icon file-state-icon--public"></i>
|
||||
</template>
|
||||
<template x-if="file.done && file.state === 'private'">
|
||||
<i data-lucide="eye-off" class="file-state-icon file-state-icon--private"></i>
|
||||
</template>
|
||||
<template x-if="file.done && file.state === 'archived'">
|
||||
<i data-lucide="archive" class="file-state-icon file-state-icon--archived"></i>
|
||||
</template>
|
||||
|
||||
<div class="upload-item-info">
|
||||
<span class="upload-item-name" x-text="file.name"></span>
|
||||
@@ -55,6 +61,48 @@
|
||||
</div>
|
||||
<span class="upload-item-error" x-show="file.error" x-text="file.error"></span>
|
||||
</div>
|
||||
@if($isEdit)
|
||||
<div class="upload-item-state" x-show="file.done" x-data="{ confirmArchive: false }">
|
||||
<select class="form-select" :disabled="file.state === 'archived'" @change="
|
||||
if( $event.target.value === 'archived'){
|
||||
confirmArchive = true;
|
||||
$event.target.value = file.state;
|
||||
} else {
|
||||
$wire ? $wire.dispatch('fileStateChanged') : null;
|
||||
changeFileState(i, $event.target.value);
|
||||
}
|
||||
" :value="file.state">
|
||||
<option value="public" :selected="file.state === 'public'">Public</option>
|
||||
<option value="private" :selected="file.state === 'private'">Private</option>
|
||||
<option value="archived" :selected="file.state === 'archived'">Archived</option>
|
||||
</select>
|
||||
<div class="modal-overlay" x-cloak x-show="confirmArchive" x-transition.opacity.duration.300ms @click.self="confirmArchive = false" @keydown.escape.window="confirmArchive = false">
|
||||
<div class="modal-window" x-show="confirmArchive" x-transition>
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">
|
||||
<i data-lucide="archive" size="16"></i>
|
||||
Archive file
|
||||
</span>
|
||||
<button type="button" class="modal-close" @click="confirmArchive = false">
|
||||
<i data-lucide="x" size="20"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Archiving a file is <b>irreversible</b>.</p>
|
||||
<div class="queue-mod-actions" style="margin-top: 15px">
|
||||
<button type="button" class="btn" @click="confirmArchive = false">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" class="btn danger" @click="changeFileState(i,'archived'); confirmArchive = false;">
|
||||
<i data-lucide="archive" size="14"></i>
|
||||
Confirm archive
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="upload-item-actions">
|
||||
<button type="button" class="btn" x-show="file.error" @click="handleRetryFile(i)">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
@@ -64,6 +112,9 @@
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" name="files_uuid[]" :value="file.uuid" x-show="file.done">
|
||||
@if($isEdit)
|
||||
<input type="hidden" name="files_state[]" :value="file.state" x-show="file.done">
|
||||
@endif
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user