generated from boboko/starter
order emails theming, sold out product card, contact page hcaptcha
This commit is contained in:
@@ -19,7 +19,7 @@ final class ProductCard
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $product one item from ProductService's localized array shape
|
* @param array<string, mixed> $product one item from ProductService's localized array shape
|
||||||
* @return array{name: ?string, price: ?string, image: ?string, href: string, variantId: ?int, hasCustomFields: bool}
|
* @return array{name: ?string, price: ?string, image: ?string, href: string, variantId: ?int, hasCustomFields: bool, soldOut: bool}
|
||||||
*/
|
*/
|
||||||
public static function fromIndexed(array $product): array
|
public static function fromIndexed(array $product): array
|
||||||
{
|
{
|
||||||
@@ -38,6 +38,10 @@ public static function fromIndexed(array $product): array
|
|||||||
// can't be quick-added from a card — the card links to the
|
// can't be quick-added from a card — the card links to the
|
||||||
// product page instead, even when every field is optional.
|
// product page instead, even when every field is optional.
|
||||||
'hasCustomFields' => ! empty($product['custom_fields']),
|
'hasCustomFields' => ! empty($product['custom_fields']),
|
||||||
|
// Index-time stock (see boboko-core's ProductIndexer `in_stock`),
|
||||||
|
// so only as fresh as the last reindex. A document missing the
|
||||||
|
// field is treated as in stock rather than hiding its cart button.
|
||||||
|
'soldOut' => ! ($product['in_stock'] ?? true),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Rules\HCaptcha;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class ContactRequest extends FormRequest
|
class ContactRequest extends FormRequest
|
||||||
@@ -17,6 +18,14 @@ public function rules(): array
|
|||||||
'name' => ['required', 'string', 'max:100'],
|
'name' => ['required', 'string', 'max:100'],
|
||||||
'email' => ['required', 'email', 'max:255'],
|
'email' => ['required', 'email', 'max:255'],
|
||||||
'message' => ['required', 'string', 'max:5000'],
|
'message' => ['required', 'string', 'max:5000'],
|
||||||
|
'h-captcha-response' => ['bail', 'required', new HCaptcha],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'h-captcha-response.required' => __('storefront.auth.captcha_failed'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@
|
|||||||
'email' => env('CONTACT_EMAIL'),
|
'email' => env('CONTACT_EMAIL'),
|
||||||
],
|
],
|
||||||
|
|
||||||
// Bot check on guest forms (login), verified by App\Rules\HCaptcha. For
|
// Bot check on guest forms (login, contact), verified by App\Rules\HCaptcha. For
|
||||||
// local dev use hCaptcha's test keys, the real ones reject localhost:
|
// local dev use hCaptcha's test keys, the real ones reject localhost:
|
||||||
// sitekey 10000000-ffff-ffff-ffff-000000000001,
|
// sitekey 10000000-ffff-ffff-ffff-000000000001,
|
||||||
// secret 0x0000000000000000000000000000000000000000.
|
// secret 0x0000000000000000000000000000000000000000.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
:href="$product['href'] ?? '#'"
|
:href="$product['href'] ?? '#'"
|
||||||
:variant-id="$product['variantId'] ?? null"
|
:variant-id="$product['variantId'] ?? null"
|
||||||
:has-custom-fields="$product['hasCustomFields'] ?? false"
|
:has-custom-fields="$product['hasCustomFields'] ?? false"
|
||||||
|
:sold-out="$product['soldOut'] ?? false"
|
||||||
/>
|
/>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
'href' => '#',
|
'href' => '#',
|
||||||
'variantId' => null,
|
'variantId' => null,
|
||||||
'hasCustomFields' => false,
|
'hasCustomFields' => false,
|
||||||
|
// Shows the "sold" star and drops the quick add-to-cart/personalize button.
|
||||||
|
'soldOut' => false,
|
||||||
// Heart toggle at the top right of the image; needs productId.
|
// Heart toggle at the top right of the image; needs productId.
|
||||||
'productId' => null,
|
'productId' => null,
|
||||||
'wishlist' => false,
|
'wishlist' => false,
|
||||||
@@ -36,7 +38,9 @@ class="w-full h-auto block"
|
|||||||
<x-wishlist-button :product-id="$productId" class="absolute top-4 right-4 z-10" />
|
<x-wishlist-button :product-id="$productId" class="absolute top-4 right-4 z-10" />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($hasCustomFields)
|
@if ($soldOut)
|
||||||
|
<x-ui.sold-badge class="absolute top-2 right-2 {{ $wishlist && $productId ? 'mt-12' : '' }}" />
|
||||||
|
@elseif ($hasCustomFields)
|
||||||
{{-- Custom fields have to be filled in on the product page. --}}
|
{{-- Custom fields have to be filled in on the product page. --}}
|
||||||
<x-ui.button
|
<x-ui.button
|
||||||
:href="$href"
|
:href="$href"
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{{-- Black "sold" star for out-of-stock products. Position it from the caller
|
||||||
|
(e.g. class="absolute top-2 right-2"). --}}
|
||||||
|
<div {{ $attributes->class('z-10 flex h-20 w-22 items-center justify-center text-black') }}>
|
||||||
|
<svg viewBox="0 0 87 80" class="absolute inset-0 size-full" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M56.942,0l2.607,18.642,19.143-3.363L69.468,31.842,87,40,69.468,48.158l9.224,16.563L59.549,61.358,56.942,80,43.5,66.4,30.058,80,27.451,61.358,8.308,64.721l9.224-16.563L0,40l17.532-8.158L8.308,15.279l19.143,3.363L30.058,0,43.5,13.6Z"/>
|
||||||
|
</svg>
|
||||||
|
<span class="relative text-base italic font-black text-white uppercase">{{ __('storefront.product.sold') }}</span>
|
||||||
|
</div>
|
||||||
@@ -3,6 +3,11 @@
|
|||||||
@section('title', __('storefront.nav.contact'))
|
@section('title', __('storefront.nav.contact'))
|
||||||
@section('description', 'Επικοινώνησε μαζί μας για οποιαδήποτε απορία ή ιδέα έχεις και θα σου απαντήσουμε το συντομότερο δυνατό.')
|
@section('description', 'Επικοινώνησε μαζί μας για οποιαδήποτε απορία ή ιδέα έχεις και θα σου απαντήσουμε το συντομότερο δυνατό.')
|
||||||
|
|
||||||
|
{{-- Only on pages with a captcha, not in the global layout. --}}
|
||||||
|
@push('scripts')
|
||||||
|
<script src="https://js.hcaptcha.com/1/api.js?hl={{ app()->getLocale() }}" async defer></script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<section class="grid grid-cols-1 lg:grid-cols-2 lg:divide-x lg:divide-black lg:min-h-[calc(100vh-102px)]">
|
<section class="grid grid-cols-1 lg:grid-cols-2 lg:divide-x lg:divide-black lg:min-h-[calc(100vh-102px)]">
|
||||||
@@ -30,25 +35,35 @@
|
|||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2">
|
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2">
|
||||||
<x-ui.field label="Το όνομά σου" for="contact-name" :error="$errors->first('name')">
|
<x-ui.field label="Το όνομά σου *" for="contact-name" :error="$errors->first('name')">
|
||||||
<x-ui.input id="contact-name" name="name" autocomplete="name" maxlength="100" :value="old('name')" :required="true"
|
<x-ui.input id="contact-name" name="name" autocomplete="name" maxlength="100" :value="old('name')" :required="true"
|
||||||
:aria-invalid="$errors->has('name') ? 'true' : null"
|
:aria-invalid="$errors->has('name') ? 'true' : null"
|
||||||
:aria-describedby="$errors->has('name') ? 'contact-name-error' : null" />
|
:aria-describedby="$errors->has('name') ? 'contact-name-error' : null" />
|
||||||
</x-ui.field>
|
</x-ui.field>
|
||||||
|
|
||||||
<x-ui.field label="Το email σου" for="contact-email" :error="$errors->first('email')">
|
<x-ui.field label="Το email σου *" for="contact-email" :error="$errors->first('email')">
|
||||||
<x-ui.input id="contact-email" name="email" type="email" autocomplete="email" maxlength="255" :value="old('email')" :required="true"
|
<x-ui.input id="contact-email" name="email" type="email" autocomplete="email" maxlength="255" :value="old('email')" :required="true"
|
||||||
:aria-invalid="$errors->has('email') ? 'true' : null"
|
:aria-invalid="$errors->has('email') ? 'true' : null"
|
||||||
:aria-describedby="$errors->has('email') ? 'contact-email-error' : null" />
|
:aria-describedby="$errors->has('email') ? 'contact-email-error' : null" />
|
||||||
</x-ui.field>
|
</x-ui.field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-ui.field label="Το μήνυμά σου" for="contact-message" :error="$errors->first('message')">
|
<x-ui.field label="Το μήνυμά σου *" for="contact-message" :error="$errors->first('message')">
|
||||||
<x-ui.textarea id="contact-message" name="message" rows="5" maxlength="5000" :required="true"
|
<x-ui.textarea id="contact-message" name="message" rows="5" maxlength="5000" :required="true"
|
||||||
:aria-invalid="$errors->has('message') ? 'true' : null"
|
:aria-invalid="$errors->has('message') ? 'true' : null"
|
||||||
:aria-describedby="$errors->has('message') ? 'contact-message-error' : null">{{ old('message') }}</x-ui.textarea>
|
:aria-describedby="$errors->has('message') ? 'contact-message-error' : null">{{ old('message') }}</x-ui.textarea>
|
||||||
</x-ui.field>
|
</x-ui.field>
|
||||||
|
|
||||||
|
{{-- hCaptcha checkbox, verified by App\Rules\HCaptcha (see auth/login). --}}
|
||||||
|
<x-ui.field for="contact-captcha" :error="$errors->first('h-captcha-response')">
|
||||||
|
<div
|
||||||
|
id="contact-captcha"
|
||||||
|
class="h-captcha min-h-[78px]"
|
||||||
|
data-sitekey="{{ config('services.hcaptcha.sitekey') }}"
|
||||||
|
@if ($errors->has('h-captcha-response')) aria-describedby="contact-captcha-error" @endif
|
||||||
|
></div>
|
||||||
|
</x-ui.field>
|
||||||
|
|
||||||
@if (session('contact_error'))
|
@if (session('contact_error'))
|
||||||
<p role="alert" class="text-sm text-red-600">{{ session('contact_error') }}</p>
|
<p role="alert" class="text-sm text-red-600">{{ session('contact_error') }}</p>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
@media only screen and (max-width: 480px) {
|
@media only screen and (max-width: 480px) {
|
||||||
.email-container { width: 100% !important; max-width: 100% !important; }
|
.email-container { width: 100% !important; max-width: 100% !important; }
|
||||||
.email-header { padding: 24px 20px 12px !important; }
|
.email-header { padding: 24px 20px 12px !important; }
|
||||||
|
.email-gutter { padding: 0 !important; }
|
||||||
.email-card { padding: 28px 20px !important; }
|
.email-card { padding: 28px 20px !important; }
|
||||||
.email-footer { padding: 0 20px 32px !important; }
|
.email-footer { padding: 0 20px 32px !important; }
|
||||||
}
|
}
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:0 24px;">
|
<td class="email-gutter" style="padding:0 24px;">
|
||||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" bgcolor="#ffffff" style="background-color:#ffffff;border-radius:0;">
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" bgcolor="#ffffff" style="background-color:#ffffff;border-radius:0;">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="email-card" style="padding:40px 32px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:#000000;text-align:left;">
|
<td class="email-card" style="padding:40px 32px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:#000000;text-align:left;">
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{{--
|
||||||
|
@include('emails.partials.address', ['address' => $order->shippingAddress])
|
||||||
|
|
||||||
|
An order address, read-only, same fields as the account order page.
|
||||||
|
--}}
|
||||||
|
<p style="margin:0;font-size:15px;line-height:1.6;">
|
||||||
|
{{ trim($address->first_name.' '.$address->last_name) }}<br>
|
||||||
|
@if ($address->company_name){{ $address->company_name }}<br>@endif
|
||||||
|
@if ($address->tax_identifier)ΑΦΜ: {{ $address->tax_identifier }}<br>@endif
|
||||||
|
{{ $address->line_one }}<br>
|
||||||
|
@if ($address->line_two){{ $address->line_two }}<br>@endif
|
||||||
|
{{ trim($address->postcode.' '.$address->city) }}<br>
|
||||||
|
@if ($address->state){{ Lang::has("core::states.{$address->state}") ? __("core::states.{$address->state}") : $address->state }}<br>@endif
|
||||||
|
@if ($address->contact_phone){{ $address->contact_phone }}@endif
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{{--
|
||||||
|
@include('emails.partials.button', ['url' => ..., 'label' => ...])
|
||||||
|
|
||||||
|
Bulletproof button: the padding sits on the link (whole area clickable)
|
||||||
|
with mso-padding-alt on the cell so Outlook keeps the same size.
|
||||||
|
--}}
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0" style="margin:0;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#18c28a" style="background-color:#18c28a;border:1px solid #000000;border-radius:0;mso-padding-alt:14px 28px;">
|
||||||
|
<a href="{{ $url }}" target="_blank" style="display:inline-block;padding:14px 28px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:16px;line-height:1.2;font-weight:700;color:#000000;text-decoration:none;">{{ $label }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{{-- @include('emails.partials.section-heading', ['text' => ...]) --}}
|
||||||
|
<h2 style="margin:0 0 12px;padding:0 0 8px;border-bottom:1px solid #000000;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:18px;line-height:1.3;font-weight:800;color:#000000;">{{ $text }}</h2>
|
||||||
@@ -43,7 +43,7 @@ class="absolute left-4 lg:left-8 top-1/2 z-10 -translate-y-1/2 hover:-translate-
|
|||||||
class="{{ $loop->first ? '' : 'hidden' }} group h-full flex flex-col justify-center pb-8 relative"
|
class="{{ $loop->first ? '' : 'hidden' }} group h-full flex flex-col justify-center pb-8 relative"
|
||||||
>
|
>
|
||||||
<div class="relative flex items-center justify-center">
|
<div class="relative flex items-center justify-center">
|
||||||
<a href="{{ $product['href'] }}" class="block border border-black bg-white flex items-center justify-center overflow-hidden max-w-xs xl:max-w-sm">
|
<a href="{{ $product['href'] }}" class="relative block border border-black bg-white flex items-center justify-center overflow-hidden max-w-xs xl:max-w-sm">
|
||||||
@if($product['image'])
|
@if($product['image'])
|
||||||
<img
|
<img
|
||||||
src="{{ $product['image'] }}"
|
src="{{ $product['image'] }}"
|
||||||
@@ -58,9 +58,16 @@ class="w-full h-full object-cover"
|
|||||||
@else
|
@else
|
||||||
<span class="text-neutral-500 text-sm">{{ __('storefront.product.no_image') }}</span>
|
<span class="text-neutral-500 text-sm">{{ __('storefront.product.no_image') }}</span>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
{{-- Inside the link so it sits on the image's corner, not the full-width slide's. --}}
|
||||||
|
@if ($product['soldOut'] ?? false)
|
||||||
|
<x-ui.sold-badge class="absolute top-2 right-2" />
|
||||||
|
@endif
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@if ($product['hasCustomFields'] ?? false)
|
@if ($product['soldOut'] ?? false)
|
||||||
|
{{-- Sold: no quick add-to-cart/personalize, same as x-ui.product-card. --}}
|
||||||
|
@elseif ($product['hasCustomFields'] ?? false)
|
||||||
<x-ui.button
|
<x-ui.button
|
||||||
:href="$product['href']"
|
:href="$product['href']"
|
||||||
size="md"
|
size="md"
|
||||||
@@ -173,6 +180,7 @@ class="max-w-xs"
|
|||||||
:href="$product['href']"
|
:href="$product['href']"
|
||||||
:variant-id="$product['variantId'] ?? null"
|
:variant-id="$product['variantId'] ?? null"
|
||||||
:has-custom-fields="$product['hasCustomFields'] ?? false"
|
:has-custom-fields="$product['hasCustomFields'] ?? false"
|
||||||
|
:sold-out="$product['soldOut'] ?? false"
|
||||||
/>
|
/>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,26 @@
|
|||||||
<p>Hi,</p>
|
{{-- Core passes only the order reference here (and the amount for payment emails). Greek on purpose: sent from the queue in the app default locale. --}}
|
||||||
|
@extends('emails.layout')
|
||||||
|
|
||||||
<p>Payment of <strong>{{ $amount }}</strong> for your order <strong>{{ $reference }}</strong> has been captured.</p>
|
@section('title', "Λάβαμε την πληρωμή σου")
|
||||||
|
|
||||||
|
@section('preheader', "Λάβαμε την πληρωμή {$amount} για την παραγγελία #{$reference}.")
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h1 style="margin:0 0 16px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:22px;line-height:1.3;font-weight:700;color:#000000;">
|
||||||
|
Λάβαμε την πληρωμή σου
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p style="margin:0 0 32px;">Λάβαμε την πληρωμή σου <strong>{{ $amount }}</strong> για την παραγγελία <strong>#{{ $reference }}</strong>.</p>
|
||||||
|
|
||||||
|
@include('emails.partials.button', [
|
||||||
|
'url' => route('account.orders', ['locale' => 'el']),
|
||||||
|
'label' => 'Οι παραγγελίες μου',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
<p style="margin:0;">
|
||||||
|
Έχεις ερώτηση για την παραγγελία σου;
|
||||||
|
<a href="{{ route('contact', ['locale' => 'el']) }}" style="color:#7a7a7a;text-decoration:underline;">Επικοινώνησε μαζί μας</a>.
|
||||||
|
</p>
|
||||||
|
@endsection
|
||||||
|
|||||||
@@ -1,3 +1,26 @@
|
|||||||
<p>Hi,</p>
|
{{-- Core passes only the order reference here (and the amount for payment emails). Greek on purpose: sent from the queue in the app default locale. --}}
|
||||||
|
@extends('emails.layout')
|
||||||
|
|
||||||
<p>Your order <strong>{{ $reference }}</strong> is complete. Thanks for shopping with us!</p>
|
@section('title', "Η παραγγελία σου ολοκληρώθηκε")
|
||||||
|
|
||||||
|
@section('preheader', "Η παραγγελία σου #{$reference} ολοκληρώθηκε.")
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h1 style="margin:0 0 16px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:22px;line-height:1.3;font-weight:700;color:#000000;">
|
||||||
|
Η παραγγελία σου ολοκληρώθηκε
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p style="margin:0 0 32px;">Η παραγγελία σου <strong>#{{ $reference }}</strong> ολοκληρώθηκε. Ευχαριστούμε που ψώνισες από εμάς!</p>
|
||||||
|
|
||||||
|
@include('emails.partials.button', [
|
||||||
|
'url' => route('account.orders', ['locale' => 'el']),
|
||||||
|
'label' => 'Οι παραγγελίες μου',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
<p style="margin:0;">
|
||||||
|
Έχεις ερώτηση για την παραγγελία σου;
|
||||||
|
<a href="{{ route('contact', ['locale' => 'el']) }}" style="color:#7a7a7a;text-decoration:underline;">Επικοινώνησε μαζί μας</a>.
|
||||||
|
</p>
|
||||||
|
@endsection
|
||||||
|
|||||||
@@ -1,3 +1,26 @@
|
|||||||
<p>Hi,</p>
|
{{-- Core passes only the order reference here (and the amount for payment emails). Greek on purpose: sent from the queue in the app default locale. --}}
|
||||||
|
@extends('emails.layout')
|
||||||
|
|
||||||
<p>Good news — your order <strong>{{ $reference }}</strong> has been delivered.</p>
|
@section('title', "Η παραγγελία σου παραδόθηκε")
|
||||||
|
|
||||||
|
@section('preheader', "Η παραγγελία σου #{$reference} παραδόθηκε.")
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h1 style="margin:0 0 16px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:22px;line-height:1.3;font-weight:700;color:#000000;">
|
||||||
|
Η παραγγελία σου παραδόθηκε
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p style="margin:0 0 32px;">Η παραγγελία σου <strong>#{{ $reference }}</strong> παραδόθηκε. Ελπίζουμε να σου αρέσει!</p>
|
||||||
|
|
||||||
|
@include('emails.partials.button', [
|
||||||
|
'url' => route('account.orders', ['locale' => 'el']),
|
||||||
|
'label' => 'Οι παραγγελίες μου',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
<p style="margin:0;">
|
||||||
|
Έχεις ερώτηση για την παραγγελία σου;
|
||||||
|
<a href="{{ route('contact', ['locale' => 'el']) }}" style="color:#7a7a7a;text-decoration:underline;">Επικοινώνησε μαζί μας</a>.
|
||||||
|
</p>
|
||||||
|
@endsection
|
||||||
|
|||||||
@@ -1,3 +1,26 @@
|
|||||||
<p>Hi,</p>
|
{{-- Core passes only the order reference here (and the amount for payment emails). Greek on purpose: sent from the queue in the app default locale. --}}
|
||||||
|
@extends('emails.layout')
|
||||||
|
|
||||||
<p>Your order <strong>{{ $reference }}</strong> is on its way.</p>
|
@section('title', "Η παραγγελία σου στάλθηκε")
|
||||||
|
|
||||||
|
@section('preheader', "Η παραγγελία σου #{$reference} είναι καθ’ οδόν.")
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h1 style="margin:0 0 16px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:22px;line-height:1.3;font-weight:700;color:#000000;">
|
||||||
|
Η παραγγελία σου στάλθηκε
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p style="margin:0 0 32px;">Η παραγγελία σου <strong>#{{ $reference }}</strong> είναι καθ’ οδόν. Θα τη λάβεις σύντομα.</p>
|
||||||
|
|
||||||
|
@include('emails.partials.button', [
|
||||||
|
'url' => route('account.orders', ['locale' => 'el']),
|
||||||
|
'label' => 'Οι παραγγελίες μου',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
<p style="margin:0;">
|
||||||
|
Έχεις ερώτηση για την παραγγελία σου;
|
||||||
|
<a href="{{ route('contact', ['locale' => 'el']) }}" style="color:#7a7a7a;text-decoration:underline;">Επικοινώνησε μαζί μας</a>.
|
||||||
|
</p>
|
||||||
|
@endsection
|
||||||
|
|||||||
@@ -1,3 +1,26 @@
|
|||||||
<p>Hi,</p>
|
{{-- Core passes only the order reference here (and the amount for payment emails). Greek on purpose: sent from the queue in the app default locale. --}}
|
||||||
|
@extends('emails.layout')
|
||||||
|
|
||||||
<p>Your order <strong>{{ $reference }}</strong> is ready for pickup in store.</p>
|
@section('title', "Η παραγγελία σου είναι έτοιμη για παραλαβή")
|
||||||
|
|
||||||
|
@section('preheader', "Η παραγγελία σου #{$reference} σε περιμένει στο κατάστημα.")
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h1 style="margin:0 0 16px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:22px;line-height:1.3;font-weight:700;color:#000000;">
|
||||||
|
Η παραγγελία σου είναι έτοιμη για παραλαβή
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p style="margin:0 0 32px;">Η παραγγελία σου <strong>#{{ $reference }}</strong> είναι έτοιμη και σε περιμένει στο κατάστημα.</p>
|
||||||
|
|
||||||
|
@include('emails.partials.button', [
|
||||||
|
'url' => route('account.orders', ['locale' => 'el']),
|
||||||
|
'label' => 'Οι παραγγελίες μου',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
<p style="margin:0;">
|
||||||
|
Έχεις ερώτηση για την παραγγελία σου;
|
||||||
|
<a href="{{ route('contact', ['locale' => 'el']) }}" style="color:#7a7a7a;text-decoration:underline;">Επικοινώνησε μαζί μας</a>.
|
||||||
|
</p>
|
||||||
|
@endsection
|
||||||
|
|||||||
+172
-16
@@ -1,19 +1,175 @@
|
|||||||
<p>Hi,</p>
|
{{--
|
||||||
|
Order confirmation (boboko-core's OrderPlacedNotification).
|
||||||
|
|
||||||
<p>Thanks for your order! Your order <strong>{{ $reference }}</strong> is confirmed.</p>
|
Core only passes $reference, $total and $lines, so the order itself is
|
||||||
|
reached through a line (every OrderLine belongs to it). Interim until core
|
||||||
|
passes the Order to this view.
|
||||||
|
|
||||||
<ul>
|
Sent from the queue with no locale of its own, so it always renders in the
|
||||||
@foreach ($lines as $line)
|
app default (Greek). Copy is Greek on purpose, like the other core emails.
|
||||||
<li>
|
--}}
|
||||||
{{ $line->quantity }} × {{ $line->description }} — {{ $line->total?->formatted }}
|
@extends('emails.layout')
|
||||||
{{-- Text answers only — photo answers are deliberately left out of emails. --}}
|
|
||||||
@foreach ($line->meta['custom_fields'] ?? [] as $field)
|
|
||||||
@if ($field['type'] !== 'file')
|
|
||||||
<br>{{ $field['label'] }}: {!! nl2br(e($field['value'])) !!}
|
|
||||||
@endif
|
|
||||||
@endforeach
|
|
||||||
</li>
|
|
||||||
@endforeach
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Total: <strong>{{ $total }}</strong></p>
|
@php
|
||||||
|
$order = $lines->first()->order;
|
||||||
|
$productLines = $lines->where('type', '!=', 'shipping');
|
||||||
|
$shippingLine = $lines->firstWhere('type', 'shipping');
|
||||||
|
|
||||||
|
// By type, like the account order page: the method may since have been
|
||||||
|
// disabled, but the order still shows what was used.
|
||||||
|
$paymentMethodName = \Modules\Core\Payment\Models\PaymentMethod::where('type', $order->meta['payment_method'] ?? null)
|
||||||
|
->first()
|
||||||
|
?->translate('name');
|
||||||
|
|
||||||
|
// Lunar doesn't copy the coupon onto the order; the cart row keeps it.
|
||||||
|
$couponCode = $order->cart?->coupon_code;
|
||||||
|
|
||||||
|
$email = $order->billingAddress?->contact_email ?? $order->shippingAddress?->contact_email;
|
||||||
|
$comments = $order->shippingAddress?->delivery_instructions;
|
||||||
|
|
||||||
|
$muted = 'color:#5c5c5c;font-size:14px;';
|
||||||
|
$font = "font-family:'Manrope',Arial,Helvetica,sans-serif;";
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@section('title', "Η παραγγελία σου #{$reference} επιβεβαιώθηκε")
|
||||||
|
|
||||||
|
@section('preheader', "Ευχαριστούμε! Λάβαμε την παραγγελία σου #{$reference}, σύνολο {$total}.")
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h1 style="margin:0 0 16px;{{ $font }}font-size:22px;line-height:1.3;font-weight:700;color:#000000;">
|
||||||
|
Ευχαριστούμε για την παραγγελία σου!
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p style="margin:0 0 24px;">
|
||||||
|
Λάβαμε την παραγγελία σου <strong>#{{ $reference }}</strong>
|
||||||
|
στις {{ $order->placed_at?->format('d/m/Y') }}.
|
||||||
|
Θα σε ενημερώσουμε με email μόλις σταλεί.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style="margin:0 0 40px;">
|
||||||
|
@include('emails.partials.button', [
|
||||||
|
'url' => route('account.orders.show', ['locale' => 'el', 'orderId' => $order->id]),
|
||||||
|
'label' => 'Δες την παραγγελία σου',
|
||||||
|
])
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Items --}}
|
||||||
|
@include('emails.partials.section-heading', ['text' => 'Προϊόντα'])
|
||||||
|
|
||||||
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 8px;">
|
||||||
|
@foreach ($productLines as $line)
|
||||||
|
<tr>
|
||||||
|
<td width="64" valign="top" style="width:64px;padding:12px 12px 12px 0;{{ $loop->last ? '' : 'border-bottom:1px solid #e5e5e5;' }}">
|
||||||
|
@if ($thumb = $line->purchasable?->getThumbnailImage())
|
||||||
|
<img src="{{ $thumb }}" width="64" height="64" alt="{{ $line->description }}" style="display:block;width:64px;height:64px;object-fit:cover;background-color:#e5e5e5;border:0;{{ $font }}font-size:11px;color:#5c5c5c;">
|
||||||
|
@else
|
||||||
|
<div style="width:64px;height:64px;background-color:#e5e5e5;"></div>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td valign="top" style="padding:12px 12px 12px 0;{{ $font }}font-size:15px;line-height:1.4;color:#000000;{{ $loop->last ? '' : 'border-bottom:1px solid #e5e5e5;' }}">
|
||||||
|
<strong>{{ $line->description }}</strong>
|
||||||
|
|
||||||
|
@if ($line->option)
|
||||||
|
<br><span style="{{ $muted }}">{{ $line->option }}</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Text answers only: photo answers are deliberately left out of emails. --}}
|
||||||
|
@foreach ($line->meta['custom_fields'] ?? [] as $field)
|
||||||
|
@if ($field['type'] !== 'file' && filled($field['value'] ?? null))
|
||||||
|
<br><span style="{{ $muted }}">{{ $field['label'] }}: {!! nl2br(e($field['value'])) !!}</span>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<br><span style="{{ $muted }}">
|
||||||
|
Ποσότητα: {{ $line->quantity }}@if ($line->quantity > 1) × {{ $line->unit_price->formatted() }}@endif
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td valign="top" align="right" style="padding:12px 0;white-space:nowrap;{{ $font }}font-size:15px;line-height:1.4;font-weight:700;color:#000000;{{ $loop->last ? '' : 'border-bottom:1px solid #e5e5e5;' }}">
|
||||||
|
{{ $line->sub_total?->formatted() }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{-- Totals --}}
|
||||||
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 40px;border-top:1px solid #000000;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding:12px 0 4px;{{ $font }}font-size:15px;color:#000000;">Υποσύνολο</td>
|
||||||
|
<td align="right" style="padding:12px 0 4px;white-space:nowrap;{{ $font }}font-size:15px;color:#000000;">{{ $order->sub_total?->formatted() }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
@if ($order->discount_total?->value > 0)
|
||||||
|
<tr>
|
||||||
|
<td style="padding:4px 0;{{ $font }}font-size:15px;color:#000000;">
|
||||||
|
Έκπτωση@if ($couponCode) <span style="{{ $muted }}">({{ $couponCode }})</span>@endif
|
||||||
|
</td>
|
||||||
|
<td align="right" style="padding:4px 0;white-space:nowrap;{{ $font }}font-size:15px;color:#000000;">−{{ $order->discount_total->formatted() }}</td>
|
||||||
|
</tr>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding:4px 0;{{ $font }}font-size:15px;color:#000000;">
|
||||||
|
Μεταφορικά@if ($shippingLine) <span style="{{ $muted }}">({{ $shippingLine->description }})</span>@endif
|
||||||
|
</td>
|
||||||
|
<td align="right" style="padding:4px 0;white-space:nowrap;{{ $font }}font-size:15px;color:#000000;">
|
||||||
|
{{ $order->shipping_total?->value > 0 ? $order->shipping_total->formatted() : 'Δωρεάν' }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding:12px 0 0;border-top:1px solid #000000;{{ $font }}font-size:20px;line-height:1.3;font-weight:800;color:#000000;">Σύνολο</td>
|
||||||
|
<td align="right" style="padding:12px 0 0;border-top:1px solid #000000;white-space:nowrap;{{ $font }}font-size:20px;line-height:1.3;font-weight:800;color:#000000;">{{ $total }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{{-- Prices include VAT (lunar.pricing.stored_inclusive_of_tax), so this is informational. --}}
|
||||||
|
@if ($order->tax_total?->value > 0)
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" align="right" style="padding:4px 0 0;{{ $font }}{{ $muted }}">Περιλαμβάνεται Φ.Π.Α. {{ $order->tax_total->formatted() }}</td>
|
||||||
|
</tr>
|
||||||
|
@endif
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{-- Payment + shipping --}}
|
||||||
|
@include('emails.partials.section-heading', ['text' => 'Πληρωμή και αποστολή'])
|
||||||
|
|
||||||
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 40px;">
|
||||||
|
@foreach (array_filter([
|
||||||
|
'Τρόπος πληρωμής' => $paymentMethodName,
|
||||||
|
'Τρόπος αποστολής' => $shippingLine?->description,
|
||||||
|
'Παραστατικό' => ($order->meta['wants_invoice'] ?? false) ? 'Τιμολόγιο' : 'Απόδειξη',
|
||||||
|
'Email' => $email,
|
||||||
|
]) as $label => $value)
|
||||||
|
<tr>
|
||||||
|
<td valign="top" style="padding:4px 12px 4px 0;{{ $font }}{{ $muted }}line-height:1.5;">{{ $label }}</td>
|
||||||
|
<td valign="top" align="right" style="padding:4px 0;{{ $font }}font-size:15px;line-height:1.5;color:#000000;word-break:break-word;">{{ $value }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{-- Addresses: stacked, never side by side, so they read well on a phone. --}}
|
||||||
|
@if ($order->shippingAddress)
|
||||||
|
@include('emails.partials.section-heading', ['text' => 'Διεύθυνση αποστολής'])
|
||||||
|
<div style="margin:0 0 {{ $comments ? '16px' : '40px' }};">
|
||||||
|
@include('emails.partials.address', ['address' => $order->shippingAddress])
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($comments)
|
||||||
|
<p style="margin:0 0 40px;font-size:15px;line-height:1.6;">
|
||||||
|
<span style="{{ $muted }}">Σχόλια</span><br>
|
||||||
|
{!! nl2br(e($comments)) !!}
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($order->billingAddress)
|
||||||
|
@include('emails.partials.section-heading', ['text' => 'Στοιχεία χρέωσης'])
|
||||||
|
@include('emails.partials.address', ['address' => $order->billingAddress])
|
||||||
|
@endif
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
<p style="margin:0;">
|
||||||
|
Έχεις ερώτηση για την παραγγελία σου;
|
||||||
|
<a href="{{ route('contact', ['locale' => 'el']) }}" style="color:#7a7a7a;text-decoration:underline;">Επικοινώνησε μαζί μας</a>.
|
||||||
|
</p>
|
||||||
|
@endsection
|
||||||
|
|||||||
@@ -1,3 +1,26 @@
|
|||||||
<p>Hi,</p>
|
{{-- Core passes only the order reference here (and the amount for payment emails). Greek on purpose: sent from the queue in the app default locale. --}}
|
||||||
|
@extends('emails.layout')
|
||||||
|
|
||||||
<p>A refund of <strong>{{ $amount }}</strong> has been issued for your order <strong>{{ $reference }}</strong>.</p>
|
@section('title', "Έγινε επιστροφή χρημάτων")
|
||||||
|
|
||||||
|
@section('preheader', "Επιστρέψαμε {$amount} για την παραγγελία #{$reference}.")
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h1 style="margin:0 0 16px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:22px;line-height:1.3;font-weight:700;color:#000000;">
|
||||||
|
Έγινε επιστροφή χρημάτων
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p style="margin:0 0 32px;">Σου επιστρέψαμε <strong>{{ $amount }}</strong> για την παραγγελία <strong>#{{ $reference }}</strong>.</p>
|
||||||
|
|
||||||
|
@include('emails.partials.button', [
|
||||||
|
'url' => route('account.orders', ['locale' => 'el']),
|
||||||
|
'label' => 'Οι παραγγελίες μου',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
<p style="margin:0;">
|
||||||
|
Έχεις ερώτηση για την παραγγελία σου;
|
||||||
|
<a href="{{ route('contact', ['locale' => 'el']) }}" style="color:#7a7a7a;text-decoration:underline;">Επικοινώνησε μαζί μας</a>.
|
||||||
|
</p>
|
||||||
|
@endsection
|
||||||
|
|||||||
@@ -1,3 +1,34 @@
|
|||||||
<p>Hi,</p>
|
{{-- Core passes only the order reference here (and the amount for payment emails). Greek on purpose: sent from the queue in the app default locale. --}}
|
||||||
|
@extends('emails.layout')
|
||||||
|
|
||||||
<p>Your order <strong>{{ $reference }}</strong> is now: <strong>{{ $statusLabel }}</strong></p>
|
@php
|
||||||
|
// Core passes Lunar's English label; map it back to the status key so the
|
||||||
|
// shared <x-order-status> can show the translated one. Interim, until core
|
||||||
|
// passes the order (or its status) to this view.
|
||||||
|
$status = collect(config('lunar.orders.statuses'))
|
||||||
|
->search(fn (array $config) => $config['label'] === $statusLabel) ?: $statusLabel;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@section('title', "Ενημέρωση παραγγελίας")
|
||||||
|
|
||||||
|
@section('preheader', "Η παραγγελία σου #{$reference} ενημερώθηκε.")
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h1 style="margin:0 0 16px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:22px;line-height:1.3;font-weight:700;color:#000000;">
|
||||||
|
Η παραγγελία σου ενημερώθηκε
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p style="margin:0 0 32px;">Η κατάσταση της παραγγελίας σου <strong>#{{ $reference }}</strong> είναι τώρα: <strong><x-order-status :status="$status" /></strong></p>
|
||||||
|
|
||||||
|
@include('emails.partials.button', [
|
||||||
|
'url' => route('account.orders', ['locale' => 'el']),
|
||||||
|
'label' => 'Οι παραγγελίες μου',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
<p style="margin:0;">
|
||||||
|
Έχεις ερώτηση για την παραγγελία σου;
|
||||||
|
<a href="{{ route('contact', ['locale' => 'el']) }}" style="color:#7a7a7a;text-decoration:underline;">Επικοινώνησε μαζί μας</a>.
|
||||||
|
</p>
|
||||||
|
@endsection
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
:href="$product['href']"
|
:href="$product['href']"
|
||||||
:variant-id="$product['variantId']"
|
:variant-id="$product['variantId']"
|
||||||
:has-custom-fields="$product['hasCustomFields']"
|
:has-custom-fields="$product['hasCustomFields']"
|
||||||
|
:sold-out="$product['soldOut']"
|
||||||
:product-id="$product['id']"
|
:product-id="$product['id']"
|
||||||
:wishlist="true"
|
:wishlist="true"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user