mirror of
https://github.com/openclassify/openclassify.git
synced 2026-04-14 11:12:09 -05:00
1362 lines
51 KiB
PHP
1362 lines
51 KiB
PHP
@php
|
||
$maxPhotoCount = (int) config('quick-listing.max_photo_count', 20);
|
||
$visiblePhotoSlotCount = min($maxPhotoCount, 8);
|
||
$maxVideoCount = (int) config('video.max_listing_videos', 5);
|
||
$currency = \Modules\Listing\Support\ListingPanelHelper::defaultCurrency();
|
||
$displayPrice = is_numeric($price) ? number_format((float) $price, 0, ',', '.') : $price;
|
||
@endphp
|
||
|
||
<div class="mx-auto w-full max-w-[920px] px-4 py-6 sm:py-10">
|
||
<style>
|
||
.qc-shell {
|
||
--qc-surface: rgba(255, 255, 255, 0.9);
|
||
--qc-surface-soft: #f5f5f7;
|
||
--qc-surface-subtle: #fbfbfd;
|
||
--qc-border: rgba(15, 23, 42, 0.08);
|
||
--qc-border-strong: rgba(15, 23, 42, 0.12);
|
||
--qc-text: #1d1d1f;
|
||
--qc-muted: #6e6e73;
|
||
--qc-primary: #0071e3;
|
||
--qc-primary-strong: #0066cc;
|
||
--qc-primary-soft: #e8f3ff;
|
||
--qc-danger: #dc2626;
|
||
color: var(--qc-text);
|
||
font-family: "SF Pro Text", "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
|
||
}
|
||
|
||
.qc-header {
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
justify-items: center;
|
||
text-align: center;
|
||
margin-bottom: 1.9rem;
|
||
}
|
||
|
||
.qc-step-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 2rem;
|
||
padding: 0 0.9rem;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--qc-border);
|
||
background: rgba(255, 255, 255, 0.85);
|
||
color: var(--qc-muted);
|
||
font-size: 0.76rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.14em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.qc-title {
|
||
margin: 0;
|
||
font-size: clamp(2.2rem, 5vw, 4.5rem);
|
||
font-weight: 700;
|
||
line-height: 0.98;
|
||
letter-spacing: -0.06em;
|
||
}
|
||
|
||
.qc-progress {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||
gap: 0.45rem;
|
||
width: min(280px, 72vw);
|
||
}
|
||
|
||
.qc-progress > span {
|
||
height: 0.28rem;
|
||
border-radius: 999px;
|
||
background: rgba(15, 23, 42, 0.1);
|
||
}
|
||
|
||
.qc-progress > span.is-on {
|
||
background: linear-gradient(90deg, var(--qc-primary), #4aa8ff);
|
||
}
|
||
|
||
.qc-card {
|
||
border: 1px solid var(--qc-border);
|
||
border-radius: 2.25rem;
|
||
background: var(--qc-surface);
|
||
box-shadow: 0 30px 80px rgba(15, 23, 42, 0.07);
|
||
overflow: hidden;
|
||
backdrop-filter: saturate(180%) blur(20px);
|
||
}
|
||
|
||
.qc-body {
|
||
padding: 1.4rem;
|
||
}
|
||
|
||
.qc-stack {
|
||
display: grid;
|
||
gap: 0.9rem;
|
||
}
|
||
|
||
.qc-panel,
|
||
.qc-upload-zone,
|
||
.qc-summary-card,
|
||
.qc-notice,
|
||
.qc-empty,
|
||
.qc-photo-strip {
|
||
border: 1px solid var(--qc-border);
|
||
border-radius: 1.5rem;
|
||
background: var(--qc-surface-subtle);
|
||
}
|
||
|
||
.qc-upload-zone {
|
||
display: grid;
|
||
place-items: center;
|
||
text-align: center;
|
||
gap: 0.8rem;
|
||
min-height: 360px;
|
||
padding: 2.5rem 1.5rem;
|
||
cursor: pointer;
|
||
border-style: dashed;
|
||
border-color: rgba(0, 113, 227, 0.16);
|
||
background:
|
||
radial-gradient(circle at top, rgba(0, 113, 227, 0.08), transparent 34%),
|
||
#fbfbfd;
|
||
}
|
||
|
||
.qc-upload-zone:hover {
|
||
border-color: rgba(0, 113, 227, 0.26);
|
||
background:
|
||
radial-gradient(circle at top, rgba(0, 113, 227, 0.1), transparent 34%),
|
||
#ffffff;
|
||
}
|
||
|
||
.qc-upload-icon {
|
||
width: 4.25rem;
|
||
height: 4.25rem;
|
||
border-radius: 1.35rem;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #fff;
|
||
color: var(--qc-text);
|
||
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.06);
|
||
}
|
||
|
||
.qc-upload-title {
|
||
font-size: 2rem;
|
||
line-height: 1.04;
|
||
letter-spacing: -0.04em;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.qc-copy {
|
||
color: var(--qc-muted);
|
||
font-size: 0.94rem;
|
||
line-height: 1.55;
|
||
max-width: 28rem;
|
||
margin: 0;
|
||
}
|
||
|
||
.qc-primary-pill,
|
||
.qc-secondary-pill,
|
||
.qc-button,
|
||
.qc-button-secondary,
|
||
.qc-chip,
|
||
.qc-icon-button {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 999px;
|
||
font-weight: 700;
|
||
transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
|
||
}
|
||
|
||
.qc-primary-pill,
|
||
.qc-button {
|
||
min-height: 3.25rem;
|
||
padding: 0 1.4rem;
|
||
border: 1px solid transparent;
|
||
background: linear-gradient(180deg, #2997ff, var(--qc-primary));
|
||
color: #fff;
|
||
box-shadow: 0 14px 28px rgba(0, 113, 227, 0.18);
|
||
}
|
||
|
||
.qc-primary-pill:hover,
|
||
.qc-button:hover {
|
||
transform: translateY(-1px);
|
||
background: linear-gradient(180deg, #1587ff, var(--qc-primary-strong));
|
||
}
|
||
|
||
.qc-secondary-pill,
|
||
.qc-button-secondary,
|
||
.qc-chip,
|
||
.qc-icon-button {
|
||
min-height: 3rem;
|
||
padding: 0 1rem;
|
||
border: 1px solid var(--qc-border);
|
||
background: #fff;
|
||
color: var(--qc-text);
|
||
}
|
||
|
||
.qc-secondary-pill:hover,
|
||
.qc-button-secondary:hover,
|
||
.qc-chip:hover,
|
||
.qc-icon-button:hover {
|
||
transform: translateY(-1px);
|
||
border-color: var(--qc-border-strong);
|
||
background: #fff;
|
||
}
|
||
|
||
.qc-panel {
|
||
padding: 1rem 1.05rem;
|
||
}
|
||
|
||
.qc-panel-head,
|
||
.qc-panel-row,
|
||
.qc-summary-card,
|
||
.qc-review-meta,
|
||
.qc-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.qc-panel-head h2,
|
||
.qc-panel-row h2 {
|
||
margin: 0;
|
||
font-size: 1.05rem;
|
||
font-weight: 700;
|
||
letter-spacing: -0.02em;
|
||
}
|
||
|
||
.qc-panel-head p,
|
||
.qc-panel-row p,
|
||
.qc-summary-copy,
|
||
.qc-meta-copy,
|
||
.qc-seller-copy {
|
||
margin: 0.2rem 0 0;
|
||
color: var(--qc-muted);
|
||
font-size: 0.9rem;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.qc-count {
|
||
flex-shrink: 0;
|
||
color: var(--qc-muted);
|
||
font-size: 0.82rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.qc-photo-grid,
|
||
.qc-photo-strip {
|
||
display: grid;
|
||
gap: 0.8rem;
|
||
}
|
||
|
||
.qc-photo-grid {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.qc-photo-strip {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
padding: 0.9rem;
|
||
background: #fff;
|
||
}
|
||
|
||
.qc-photo-slot,
|
||
.qc-review-thumb,
|
||
.qc-gallery-main {
|
||
position: relative;
|
||
border-radius: 1.15rem;
|
||
overflow: hidden;
|
||
border: 1px solid var(--qc-border);
|
||
background: #eef2f7;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.qc-photo-slot {
|
||
aspect-ratio: 1;
|
||
min-height: 120px;
|
||
}
|
||
|
||
.qc-photo-slot img,
|
||
.qc-review-thumb img,
|
||
.qc-gallery-main img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.qc-remove {
|
||
position: absolute;
|
||
top: 0.5rem;
|
||
right: 0.5rem;
|
||
width: 1.9rem;
|
||
height: 1.9rem;
|
||
border-radius: 999px;
|
||
border: 0;
|
||
background: rgba(15, 23, 42, 0.88);
|
||
color: #fff;
|
||
font-size: 0.9rem;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.qc-cover {
|
||
position: absolute;
|
||
left: 0.55rem;
|
||
bottom: 0.55rem;
|
||
min-height: 1.8rem;
|
||
padding: 0 0.7rem;
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.96);
|
||
color: var(--qc-text);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.qc-empty {
|
||
padding: 1.15rem 1.2rem;
|
||
text-align: center;
|
||
color: var(--qc-muted);
|
||
font-size: 0.93rem;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.qc-video-list {
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.qc-video-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0.8rem;
|
||
padding: 0.95rem 1rem;
|
||
border: 1px solid var(--qc-border);
|
||
border-radius: 1.1rem;
|
||
background: #fff;
|
||
}
|
||
|
||
.qc-video-meta {
|
||
min-width: 0;
|
||
}
|
||
|
||
.qc-video-name {
|
||
color: var(--qc-text);
|
||
font-size: 0.93rem;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.qc-video-size {
|
||
margin-top: 0.2rem;
|
||
color: var(--qc-muted);
|
||
font-size: 0.84rem;
|
||
}
|
||
|
||
.qc-notice {
|
||
padding: 0.9rem 1rem;
|
||
color: var(--qc-text);
|
||
font-size: 0.9rem;
|
||
line-height: 1.55;
|
||
}
|
||
|
||
.qc-chip-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.6rem;
|
||
}
|
||
|
||
.qc-category-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 0.9rem;
|
||
}
|
||
|
||
.qc-category-card {
|
||
border: 1px solid var(--qc-border);
|
||
border-radius: 1.4rem;
|
||
background: #fff;
|
||
padding: 1.1rem 1rem;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease, background 0.18s ease;
|
||
}
|
||
|
||
.qc-category-card:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.06);
|
||
}
|
||
|
||
.qc-category-card.is-selected {
|
||
border-color: rgba(0, 113, 227, 0.24);
|
||
background: var(--qc-primary-soft);
|
||
}
|
||
|
||
.qc-category-icon {
|
||
width: 4rem;
|
||
height: 4rem;
|
||
margin: 0 auto 0.8rem;
|
||
border-radius: 1.2rem;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--qc-surface-soft);
|
||
color: var(--qc-text);
|
||
}
|
||
|
||
.qc-category-name {
|
||
font-size: 0.95rem;
|
||
font-weight: 700;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.qc-search-wrap {
|
||
display: grid;
|
||
gap: 0.8rem;
|
||
}
|
||
|
||
.qc-input,
|
||
.qc-select,
|
||
.qc-textarea {
|
||
width: 100%;
|
||
min-height: 3.25rem;
|
||
padding: 0 1rem;
|
||
border: 1px solid var(--qc-border);
|
||
border-radius: 1rem;
|
||
background: #fff;
|
||
color: var(--qc-text);
|
||
font-size: 0.96rem;
|
||
transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
|
||
}
|
||
|
||
.qc-textarea {
|
||
min-height: 10rem;
|
||
padding-top: 0.9rem;
|
||
padding-bottom: 0.9rem;
|
||
resize: vertical;
|
||
}
|
||
|
||
.qc-input:focus,
|
||
.qc-select:focus,
|
||
.qc-textarea:focus {
|
||
outline: none;
|
||
border-color: rgba(0, 113, 227, 0.28);
|
||
box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.12);
|
||
}
|
||
|
||
.qc-category-list {
|
||
display: grid;
|
||
gap: 0.6rem;
|
||
}
|
||
|
||
.qc-category-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto auto;
|
||
gap: 0.6rem;
|
||
align-items: center;
|
||
padding: 0.7rem;
|
||
border: 1px solid var(--qc-border);
|
||
border-radius: 1rem;
|
||
background: #fff;
|
||
}
|
||
|
||
.qc-category-main,
|
||
.qc-category-next,
|
||
.qc-back-link,
|
||
.qc-text-link {
|
||
border: 0;
|
||
background: transparent;
|
||
color: var(--qc-text);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.qc-category-main {
|
||
text-align: left;
|
||
font-size: 0.96rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.qc-category-main.is-selected {
|
||
color: var(--qc-primary);
|
||
}
|
||
|
||
.qc-category-check {
|
||
color: var(--qc-primary);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.qc-back-link,
|
||
.qc-text-link {
|
||
color: var(--qc-primary);
|
||
font-size: 0.92rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.qc-summary-card {
|
||
padding: 0.95rem 1rem;
|
||
background: #fff;
|
||
}
|
||
|
||
.qc-summary-label {
|
||
display: block;
|
||
color: var(--qc-muted);
|
||
font-size: 0.78rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.qc-summary-value {
|
||
display: block;
|
||
margin-top: 0.3rem;
|
||
color: var(--qc-text);
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.qc-fields {
|
||
display: grid;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.qc-fields.two-col {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.qc-field {
|
||
display: grid;
|
||
gap: 0.45rem;
|
||
}
|
||
|
||
.qc-field label {
|
||
color: var(--qc-text);
|
||
font-size: 0.9rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.qc-counter {
|
||
text-align: right;
|
||
color: var(--qc-muted);
|
||
font-size: 0.8rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.qc-input-row {
|
||
position: relative;
|
||
}
|
||
|
||
.qc-input-suffix {
|
||
position: absolute;
|
||
top: 50%;
|
||
right: 1rem;
|
||
transform: translateY(-50%);
|
||
color: var(--qc-muted);
|
||
font-size: 0.92rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.qc-toggle {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.55rem;
|
||
min-height: 3.25rem;
|
||
padding: 0 1rem;
|
||
border: 1px solid var(--qc-border);
|
||
border-radius: 1rem;
|
||
background: #fff;
|
||
color: var(--qc-text);
|
||
font-size: 0.95rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.qc-toggle input {
|
||
accent-color: var(--qc-primary);
|
||
}
|
||
|
||
.qc-error {
|
||
color: var(--qc-danger);
|
||
font-size: 0.84rem;
|
||
line-height: 1.5;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.qc-footer {
|
||
padding: 1rem 1.1rem;
|
||
border-top: 1px solid var(--qc-border);
|
||
background: rgba(255, 255, 255, 0.96);
|
||
}
|
||
|
||
.qc-footer.is-single {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.qc-review-grid {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 320px;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.qc-review-gallery {
|
||
display: grid;
|
||
gap: 0.8rem;
|
||
}
|
||
|
||
.qc-gallery-main {
|
||
min-height: 420px;
|
||
background: #f0f4f8;
|
||
}
|
||
|
||
.qc-review-thumbs {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 0.7rem;
|
||
}
|
||
|
||
.qc-review-thumb {
|
||
aspect-ratio: 1;
|
||
min-height: 86px;
|
||
}
|
||
|
||
.qc-review-panel {
|
||
padding: 1.1rem;
|
||
}
|
||
|
||
.qc-review-price {
|
||
font-size: clamp(2rem, 4vw, 3rem);
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
letter-spacing: -0.06em;
|
||
}
|
||
|
||
.qc-review-location {
|
||
color: var(--qc-muted);
|
||
font-size: 0.9rem;
|
||
line-height: 1.6;
|
||
text-align: right;
|
||
}
|
||
|
||
.qc-review-title {
|
||
margin: 1rem 0 0;
|
||
font-size: 1.35rem;
|
||
font-weight: 700;
|
||
line-height: 1.25;
|
||
letter-spacing: -0.03em;
|
||
}
|
||
|
||
.qc-review-description {
|
||
margin: 0.8rem 0 0;
|
||
color: var(--qc-text);
|
||
font-size: 0.96rem;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.qc-feature-list {
|
||
display: grid;
|
||
gap: 0.8rem;
|
||
margin-top: 1rem;
|
||
padding-top: 1rem;
|
||
border-top: 1px solid var(--qc-border);
|
||
}
|
||
|
||
.qc-feature-row {
|
||
display: grid;
|
||
grid-template-columns: 150px 1fr;
|
||
gap: 0.9rem;
|
||
align-items: start;
|
||
}
|
||
|
||
.qc-feature-label {
|
||
color: var(--qc-muted);
|
||
font-size: 0.84rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.qc-feature-value {
|
||
color: var(--qc-text);
|
||
font-size: 0.95rem;
|
||
font-weight: 600;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.qc-side-stack {
|
||
display: grid;
|
||
gap: 1rem;
|
||
align-self: start;
|
||
}
|
||
|
||
.qc-seller-card {
|
||
padding: 1rem 1.1rem;
|
||
}
|
||
|
||
.qc-seller-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.8rem;
|
||
}
|
||
|
||
.qc-avatar {
|
||
width: 3.3rem;
|
||
height: 3.3rem;
|
||
border-radius: 999px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--qc-surface-soft);
|
||
color: var(--qc-text);
|
||
font-size: 1.1rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.qc-seller-name {
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.qc-seller-email {
|
||
margin-top: 0.2rem;
|
||
color: var(--qc-muted);
|
||
font-size: 0.88rem;
|
||
}
|
||
|
||
.qc-publish-stack {
|
||
display: grid;
|
||
gap: 0.7rem;
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
.qc-button,
|
||
.qc-button-secondary {
|
||
min-height: 3.25rem;
|
||
padding: 0 1.2rem;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.qc-button:disabled {
|
||
background: #d8dbe1;
|
||
color: #f3f4f6;
|
||
box-shadow: none;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
|
||
.qc-button-secondary {
|
||
box-shadow: none;
|
||
}
|
||
|
||
@media (max-width: 1023px) {
|
||
.qc-review-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.qc-side-stack {
|
||
grid-template-columns: 1fr 1fr;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 767px) {
|
||
.qc-body,
|
||
.qc-footer {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.qc-panel-head,
|
||
.qc-panel-row,
|
||
.qc-summary-card,
|
||
.qc-review-meta,
|
||
.qc-footer,
|
||
.qc-side-stack {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.qc-footer {
|
||
justify-content: stretch;
|
||
}
|
||
|
||
.qc-upload-zone {
|
||
min-height: 260px;
|
||
}
|
||
|
||
.qc-category-grid,
|
||
.qc-photo-grid,
|
||
.qc-photo-strip,
|
||
.qc-review-thumbs,
|
||
.qc-fields.two-col {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.qc-feature-row {
|
||
grid-template-columns: 1fr;
|
||
gap: 0.3rem;
|
||
}
|
||
|
||
.qc-review-location {
|
||
text-align: left;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 540px) {
|
||
.qc-category-grid,
|
||
.qc-photo-grid,
|
||
.qc-photo-strip,
|
||
.qc-review-thumbs,
|
||
.qc-fields.two-col {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.qc-category-row {
|
||
grid-template-columns: 1fr auto;
|
||
}
|
||
|
||
.qc-category-check {
|
||
display: none;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<div class="qc-shell">
|
||
<div class="qc-header">
|
||
<span class="qc-step-chip">Step {{ $currentStep }} of 5</span>
|
||
<h1 class="qc-title">{{ $this->currentStepTitle }}</h1>
|
||
<div class="qc-progress" aria-hidden="true">
|
||
@for ($step = 1; $step <= 5; $step++)
|
||
<span @class(['is-on' => $step <= $currentStep])></span>
|
||
@endfor
|
||
</div>
|
||
</div>
|
||
|
||
<div class="qc-card">
|
||
@if ($publishError)
|
||
<div class="px-4 pt-4">
|
||
<div class="rounded-[18px] border border-rose-200 bg-rose-50 px-4 py-3 text-sm font-semibold text-rose-700">
|
||
{{ $publishError }}
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($currentStep === 1)
|
||
<div class="qc-body">
|
||
<div class="qc-stack">
|
||
<label class="qc-upload-zone" for="quick-listing-photo-input">
|
||
<span class="qc-upload-icon">
|
||
<x-heroicon-o-photo class="h-7 w-7" />
|
||
</span>
|
||
<div class="qc-upload-title">Add photos</div>
|
||
<p class="qc-copy">1 to {{ $maxPhotoCount }} photos.</p>
|
||
<span class="qc-primary-pill">Select photos</span>
|
||
</label>
|
||
|
||
<input
|
||
id="quick-listing-photo-input"
|
||
type="file"
|
||
wire:model="photos"
|
||
accept="image/jpeg,image/jpg,image/png"
|
||
multiple
|
||
class="hidden"
|
||
>
|
||
|
||
@error('photos')
|
||
<div class="qc-error">{{ $message }}</div>
|
||
@enderror
|
||
|
||
@error('photos.*')
|
||
<div class="qc-error">{{ $message }}</div>
|
||
@enderror
|
||
|
||
@if (count($photos) > 0)
|
||
<div class="qc-panel">
|
||
<div class="qc-panel-head">
|
||
<div>
|
||
<h2>Your photos</h2>
|
||
</div>
|
||
<span class="qc-count">{{ count($photos) }}/{{ $maxPhotoCount }}</span>
|
||
</div>
|
||
|
||
<div class="qc-photo-grid">
|
||
@for ($index = 0; $index < $visiblePhotoSlotCount; $index++)
|
||
<div class="qc-photo-slot">
|
||
@if (isset($photos[$index]))
|
||
<img src="{{ $photos[$index]->temporaryUrl() }}" alt="Uploaded photo {{ $index + 1 }}">
|
||
<button type="button" class="qc-remove" wire:click="removePhoto({{ $index }})">×</button>
|
||
@if ($index === 0)
|
||
<div class="qc-cover">Cover</div>
|
||
@endif
|
||
@else
|
||
<x-heroicon-o-photo class="h-8 w-8 text-slate-400" />
|
||
@endif
|
||
</div>
|
||
@endfor
|
||
</div>
|
||
|
||
@if (count($photos) > $visiblePhotoSlotCount)
|
||
<p class="qc-meta-copy mt-3">{{ count($photos) - $visiblePhotoSlotCount }} more photos added.</p>
|
||
@endif
|
||
</div>
|
||
@else
|
||
<div class="qc-empty">Add one cover photo to continue.</div>
|
||
@endif
|
||
|
||
<div class="qc-panel">
|
||
<div class="qc-panel-row">
|
||
<h2>Video</h2>
|
||
<label for="quick-listing-video-input" class="qc-secondary-pill cursor-pointer">
|
||
Add video
|
||
</label>
|
||
</div>
|
||
|
||
<input
|
||
id="quick-listing-video-input"
|
||
type="file"
|
||
wire:model="videos"
|
||
accept="video/mp4,video/quicktime,video/webm,video/x-matroska,video/x-msvideo"
|
||
multiple
|
||
class="hidden"
|
||
data-video-upload-optimizer="{{ config('video.client_side.enabled', true) ? 'true' : 'false' }}"
|
||
data-video-optimize-width="{{ config('video.client_side.max_width', 854) }}"
|
||
data-video-optimize-bitrate="{{ config('video.client_side.bitrate', 900000) }}"
|
||
data-video-optimize-fps="{{ config('video.client_side.fps', 24) }}"
|
||
data-video-optimize-min-bytes="{{ config('video.client_side.min_size_bytes', 1048576) }}"
|
||
/>
|
||
|
||
@error('videos')
|
||
<div class="qc-error">{{ $message }}</div>
|
||
@enderror
|
||
|
||
@error('videos.*')
|
||
<div class="qc-error">{{ $message }}</div>
|
||
@enderror
|
||
|
||
@if (count($videos) > 0)
|
||
<div class="qc-video-list">
|
||
@foreach ($videos as $index => $video)
|
||
@php
|
||
$videoName = method_exists($video, 'getClientOriginalName') ? $video->getClientOriginalName() : 'Video '.($index + 1);
|
||
$videoSize = method_exists($video, 'getSize') ? (int) $video->getSize() : 0;
|
||
@endphp
|
||
<div class="qc-video-item">
|
||
<div class="qc-video-meta">
|
||
<div class="qc-video-name">{{ $videoName }}</div>
|
||
<div class="qc-video-size">{{ $videoSize > 0 ? number_format($videoSize / 1048576, 1, ',', '.') : '-' }} MB</div>
|
||
</div>
|
||
<button type="button" class="qc-icon-button h-11 w-11 p-0" wire:click="removeVideo({{ $index }})">×</button>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="qc-footer is-single">
|
||
<button
|
||
type="button"
|
||
class="qc-button"
|
||
wire:click="goToCategoryStep"
|
||
@disabled(count($photos) === 0 || $isDetecting)
|
||
>
|
||
Next
|
||
</button>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($currentStep === 2)
|
||
<div class="qc-body">
|
||
<div class="qc-stack">
|
||
@if ($isDetecting)
|
||
<div class="qc-notice">Finding the best category...</div>
|
||
@elseif ($detectedCategoryId)
|
||
<div class="qc-notice">Suggested: <strong>{{ $this->selectedCategoryName }}</strong></div>
|
||
@elseif ($detectedError)
|
||
<div class="qc-notice">{{ $detectedError }}</div>
|
||
@endif
|
||
|
||
@if ($detectedAlternatives !== [])
|
||
<div class="qc-chip-row">
|
||
@foreach ($detectedAlternatives as $alternativeId)
|
||
@php
|
||
$alternativeCategory = collect($categories)->firstWhere('id', $alternativeId);
|
||
@endphp
|
||
@if ($alternativeCategory)
|
||
<button type="button" class="qc-chip" wire:click="selectCategory({{ $alternativeId }})">
|
||
{{ $alternativeCategory['name'] }}
|
||
</button>
|
||
@endif
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
|
||
@if (is_null($activeParentCategoryId))
|
||
<div class="qc-panel">
|
||
<div class="qc-panel-head">
|
||
<div>
|
||
<h2>Choose a category</h2>
|
||
</div>
|
||
<button type="button" class="qc-text-link" wire:click="detectCategoryFromImage" @disabled($isDetecting || count($photos) === 0)>
|
||
Try again
|
||
</button>
|
||
</div>
|
||
|
||
<div class="qc-category-grid">
|
||
@foreach ($this->rootCategories as $category)
|
||
<button
|
||
type="button"
|
||
class="qc-category-card {{ $selectedCategoryId === $category['id'] ? 'is-selected' : '' }}"
|
||
wire:click="enterCategory({{ $category['id'] }})"
|
||
>
|
||
<span class="qc-category-icon">
|
||
<x-dynamic-component :component="$this->categoryIconComponent($category['icon'])" class="h-8 w-8" />
|
||
</span>
|
||
<div class="qc-category-name">{{ $category['name'] }}</div>
|
||
</button>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
@else
|
||
<div class="qc-panel">
|
||
<div class="qc-panel-head">
|
||
<button type="button" class="qc-back-link" wire:click="backToRootCategories">Back</button>
|
||
<div class="text-center">
|
||
<h2>{{ $this->currentParentName }}</h2>
|
||
</div>
|
||
<span class="qc-count">Pick</span>
|
||
</div>
|
||
|
||
<div class="qc-search-wrap">
|
||
<input type="text" class="qc-input" placeholder="Search categories" wire:model.live.debounce.300ms="categorySearch">
|
||
|
||
<div class="qc-category-list">
|
||
@forelse ($this->currentCategories as $category)
|
||
<div class="qc-category-row">
|
||
<button
|
||
type="button"
|
||
class="qc-category-main {{ $selectedCategoryId === $category['id'] ? 'is-selected' : '' }}"
|
||
wire:click="selectCategory({{ $category['id'] }})"
|
||
>
|
||
{{ $category['name'] }}
|
||
</button>
|
||
|
||
@if ($category['has_children'] && $category['id'] !== $activeParentCategoryId)
|
||
<button type="button" class="qc-category-next" wire:click="enterCategory({{ $category['id'] }})">
|
||
<x-heroicon-o-chevron-right class="h-5 w-5" />
|
||
</button>
|
||
@else
|
||
<span></span>
|
||
@endif
|
||
|
||
<span class="qc-category-check">
|
||
@if ($selectedCategoryId === $category['id'])
|
||
<x-heroicon-o-check-circle class="h-5 w-5" />
|
||
@endif
|
||
</span>
|
||
</div>
|
||
@empty
|
||
<div class="qc-empty">No categories found.</div>
|
||
@endforelse
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($errors->has('selectedCategoryId'))
|
||
<div class="qc-error">{{ $errors->first('selectedCategoryId') }}</div>
|
||
@endif
|
||
|
||
@if ($this->selectedCategoryName)
|
||
<div class="qc-summary-card">
|
||
<div>
|
||
<span class="qc-summary-label">Selected</span>
|
||
<span class="qc-summary-value">{{ $this->selectedCategoryName }}</span>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
<div class="qc-footer">
|
||
<button type="button" class="qc-button-secondary" wire:click="goToStep(1)">Back</button>
|
||
<button
|
||
type="button"
|
||
class="qc-button"
|
||
wire:click="goToDetailsStep"
|
||
@disabled(! $selectedCategoryId)
|
||
>
|
||
Next
|
||
</button>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($currentStep === 3)
|
||
<div class="qc-body">
|
||
<div class="qc-stack">
|
||
<div class="qc-photo-strip">
|
||
@foreach (array_slice($photos, 0, 4) as $index => $photo)
|
||
<div class="qc-photo-slot">
|
||
<img src="{{ $photo->temporaryUrl() }}" alt="Selected photo {{ $index + 1 }}">
|
||
<button type="button" class="qc-remove" wire:click="removePhoto({{ $index }})">×</button>
|
||
@if ($index === 0)
|
||
<div class="qc-cover">Cover</div>
|
||
@endif
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
|
||
<div class="qc-summary-card">
|
||
<div>
|
||
<span class="qc-summary-label">Category</span>
|
||
<span class="qc-summary-value">{{ $this->selectedCategoryPath ?: '-' }}</span>
|
||
</div>
|
||
<button type="button" class="qc-text-link" wire:click="goToStep(2)">Change</button>
|
||
</div>
|
||
|
||
<div class="qc-fields">
|
||
<div class="qc-field">
|
||
<label for="quick-title">Title</label>
|
||
<input id="quick-title" type="text" class="qc-input" placeholder="Listing title" wire:model.live.debounce.300ms="listingTitle" maxlength="70">
|
||
<div class="qc-counter">{{ $this->titleCharacters }}/70</div>
|
||
@error('listingTitle')<div class="qc-error">{{ $message }}</div>@enderror
|
||
</div>
|
||
|
||
<div class="qc-fields two-col">
|
||
<div class="qc-field">
|
||
<label for="quick-price">Price</label>
|
||
<div class="qc-input-row">
|
||
<input id="quick-price" type="number" step="0.01" class="qc-input" placeholder="Price" wire:model.live.debounce.300ms="price">
|
||
<span class="qc-input-suffix">{{ $currency }}</span>
|
||
</div>
|
||
@error('price')<div class="qc-error">{{ $message }}</div>@enderror
|
||
</div>
|
||
|
||
<div class="qc-field">
|
||
<label>Location</label>
|
||
<div class="qc-fields two-col">
|
||
<div>
|
||
<select class="qc-select" wire:model.live="selectedCountryId">
|
||
<option value="">Country</option>
|
||
@foreach ($countries as $country)
|
||
<option value="{{ $country['id'] }}">{{ $country['name'] }}</option>
|
||
@endforeach
|
||
</select>
|
||
@error('selectedCountryId')<div class="qc-error">{{ $message }}</div>@enderror
|
||
</div>
|
||
<div>
|
||
<select class="qc-select" wire:model.live="selectedCityId" @disabled(! $selectedCountryId)>
|
||
<option value="">City</option>
|
||
@foreach ($this->availableCities as $city)
|
||
<option value="{{ $city['id'] }}">{{ $city['name'] }}</option>
|
||
@endforeach
|
||
</select>
|
||
@error('selectedCityId')<div class="qc-error">{{ $message }}</div>@enderror
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="qc-field">
|
||
<label for="quick-description">Description</label>
|
||
<textarea id="quick-description" class="qc-textarea" placeholder="Describe the item" wire:model.live.debounce.300ms="description" maxlength="1450"></textarea>
|
||
<div class="qc-counter">{{ $this->descriptionCharacters }}/1450</div>
|
||
@error('description')<div class="qc-error">{{ $message }}</div>@enderror
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="qc-footer">
|
||
<button type="button" class="qc-button-secondary" wire:click="goToStep(2)">Back</button>
|
||
<button type="button" class="qc-button" wire:click="goToFeaturesStep">Next</button>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($currentStep === 4)
|
||
<div class="qc-body">
|
||
<div class="qc-stack">
|
||
<div class="qc-summary-card">
|
||
<div>
|
||
<span class="qc-summary-label">Category</span>
|
||
<span class="qc-summary-value">{{ $this->selectedCategoryPath ?: '-' }}</span>
|
||
</div>
|
||
<button type="button" class="qc-text-link" wire:click="goToStep(2)">Change</button>
|
||
</div>
|
||
|
||
@if ($listingCustomFields === [])
|
||
<div class="qc-empty">No extra details for this category.</div>
|
||
@else
|
||
<div class="qc-fields two-col">
|
||
@foreach ($listingCustomFields as $field)
|
||
<div class="qc-field">
|
||
<label>
|
||
{{ $field['label'] }}
|
||
@if ($field['is_required'])
|
||
*
|
||
@endif
|
||
</label>
|
||
|
||
@if ($field['type'] === 'text')
|
||
<input
|
||
type="text"
|
||
class="qc-input"
|
||
wire:model.live="customFieldValues.{{ $field['name'] }}"
|
||
placeholder="{{ $field['placeholder'] ?: $field['label'] }}"
|
||
>
|
||
@elseif ($field['type'] === 'textarea')
|
||
<textarea
|
||
class="qc-textarea"
|
||
wire:model.live="customFieldValues.{{ $field['name'] }}"
|
||
placeholder="{{ $field['placeholder'] ?: $field['label'] }}"
|
||
></textarea>
|
||
@elseif ($field['type'] === 'number')
|
||
<input
|
||
type="number"
|
||
step="0.01"
|
||
class="qc-input"
|
||
wire:model.live="customFieldValues.{{ $field['name'] }}"
|
||
placeholder="{{ $field['placeholder'] ?: $field['label'] }}"
|
||
>
|
||
@elseif ($field['type'] === 'select')
|
||
<select class="qc-select" wire:model.live="customFieldValues.{{ $field['name'] }}">
|
||
<option value="">Select</option>
|
||
@foreach ($field['options'] as $option)
|
||
<option value="{{ $option }}">{{ $option }}</option>
|
||
@endforeach
|
||
</select>
|
||
@elseif ($field['type'] === 'boolean')
|
||
<label class="qc-toggle">
|
||
<input type="checkbox" wire:model.live="customFieldValues.{{ $field['name'] }}">
|
||
<span>Yes</span>
|
||
</label>
|
||
@elseif ($field['type'] === 'date')
|
||
<input type="date" class="qc-input" wire:model.live="customFieldValues.{{ $field['name'] }}">
|
||
@endif
|
||
|
||
@if ($field['help_text'])
|
||
<p class="qc-meta-copy">{{ $field['help_text'] }}</p>
|
||
@endif
|
||
|
||
@error('customFieldValues.'.$field['name'])
|
||
<div class="qc-error">{{ $message }}</div>
|
||
@enderror
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
<div class="qc-footer">
|
||
<button type="button" class="qc-button-secondary" wire:click="goToStep(3)">Back</button>
|
||
<button type="button" class="qc-button" wire:click="goToPreviewStep">Review</button>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($currentStep === 5)
|
||
<div class="qc-body">
|
||
<div class="qc-review-grid">
|
||
<div class="qc-stack">
|
||
<div class="qc-review-gallery">
|
||
<div class="qc-gallery-main">
|
||
@if (isset($photos[0]))
|
||
<img src="{{ $photos[0]->temporaryUrl() }}" alt="Preview cover photo">
|
||
@else
|
||
<x-heroicon-o-photo class="h-12 w-12 text-slate-400" />
|
||
@endif
|
||
</div>
|
||
|
||
<div class="qc-review-thumbs">
|
||
@foreach (array_slice($photos, 0, 4) as $photo)
|
||
<div class="qc-review-thumb">
|
||
<img src="{{ $photo->temporaryUrl() }}" alt="Preview photo">
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
|
||
<div class="qc-panel qc-review-panel">
|
||
<div class="qc-review-meta">
|
||
<div class="qc-review-price">{{ $displayPrice }} {{ $currency }}</div>
|
||
<div class="qc-review-location">
|
||
<div>{{ $this->selectedCityName ?: '-' }}, {{ $this->selectedCountryName ?: '-' }}</div>
|
||
<div>{{ now()->format('d.m.Y') }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="qc-review-title">{{ $listingTitle ?: 'Untitled listing' }}</h2>
|
||
<p class="qc-review-description">{{ $description ?: 'No description added.' }}</p>
|
||
|
||
<div class="qc-feature-list">
|
||
<div class="qc-feature-row">
|
||
<div class="qc-feature-label">Category</div>
|
||
<div class="qc-feature-value">{{ $this->selectedCategoryPath ?: '-' }}</div>
|
||
</div>
|
||
|
||
@if ($this->previewCustomFields !== [])
|
||
@foreach ($this->previewCustomFields as $field)
|
||
<div class="qc-feature-row">
|
||
<div class="qc-feature-label">{{ $field['label'] }}</div>
|
||
<div class="qc-feature-value">{{ $field['value'] }}</div>
|
||
</div>
|
||
@endforeach
|
||
@endif
|
||
|
||
@if (count($videos) > 0)
|
||
<div class="qc-feature-row">
|
||
<div class="qc-feature-label">Videos</div>
|
||
<div class="qc-feature-value">{{ count($videos) }} added</div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="qc-side-stack">
|
||
<div class="qc-panel qc-seller-card">
|
||
<div class="qc-seller-head">
|
||
<span class="qc-avatar">{{ $this->currentUserInitial }}</span>
|
||
<div>
|
||
<div class="qc-seller-name">{{ $this->currentUserName }}</div>
|
||
<div class="qc-seller-email">{{ auth()->user()?->email }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="qc-panel">
|
||
<div class="qc-publish-stack">
|
||
<button
|
||
type="button"
|
||
class="qc-button"
|
||
wire:click.prevent="publishListing"
|
||
wire:loading.attr="disabled"
|
||
wire:target="publishListing"
|
||
>
|
||
<span wire:loading.remove wire:target="publishListing">Publish listing</span>
|
||
<span wire:loading wire:target="publishListing">Publishing...</span>
|
||
</button>
|
||
<button type="button" class="qc-button-secondary" wire:click="goToStep(4)" wire:loading.attr="disabled" wire:target="publishListing">Back</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@include('video::partials.video-upload-optimizer')
|