generated from boboko/starter
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b522d60954 | ||
|
|
60ae6cf7a6 | ||
|
|
296d6001cd | ||
|
|
aa051525a1 |
@@ -5,7 +5,6 @@ vendor
|
|||||||
public/build
|
public/build
|
||||||
public/hot
|
public/hot
|
||||||
storage/app/public/*
|
storage/app/public/*
|
||||||
storage/app/private/*
|
|
||||||
storage/framework/cache/*
|
storage/framework/cache/*
|
||||||
storage/framework/sessions/*
|
storage/framework/sessions/*
|
||||||
storage/framework/views/*
|
storage/framework/views/*
|
||||||
|
|||||||
+8
-2
@@ -41,7 +41,7 @@ BROADCAST_CONNECTION=log
|
|||||||
FILESYSTEM_DISK=local
|
FILESYSTEM_DISK=local
|
||||||
QUEUE_CONNECTION=database
|
QUEUE_CONNECTION=database
|
||||||
|
|
||||||
CACHE_STORE=redis
|
CACHE_STORE=database
|
||||||
# CACHE_PREFIX=
|
# CACHE_PREFIX=
|
||||||
|
|
||||||
MEMCACHED_HOST=127.0.0.1
|
MEMCACHED_HOST=127.0.0.1
|
||||||
@@ -58,8 +58,8 @@ MAIL_PORT=1025
|
|||||||
MAIL_USERNAME=null
|
MAIL_USERNAME=null
|
||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_FROM_ADDRESS="hello@example.com"
|
MAIL_FROM_ADDRESS="hello@example.com"
|
||||||
|
# Fallback only: the sender name comes from Store Details' "Mail from name" when set.
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
CONTACT_EMAIL=
|
|
||||||
|
|
||||||
HCAPTCHA_SITEKEY=
|
HCAPTCHA_SITEKEY=
|
||||||
HCAPTCHA_SECRET=
|
HCAPTCHA_SECRET=
|
||||||
@@ -72,6 +72,12 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
|
|||||||
|
|
||||||
VITE_APP_NAME="${APP_NAME}"
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
OTEL_SERVICE_NAME=boboko-starter
|
||||||
|
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
|
||||||
|
OTEL_PROPAGATORS=baggage,tracecontext
|
||||||
|
OTEL_PHP_AUTOLOAD_ENABLED=true
|
||||||
|
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||||
|
|
||||||
# Stoic is the headless CMS, proxied at /stoic/ behind the app in prod (see docker/nginx/prod.conf)
|
# Stoic is the headless CMS, proxied at /stoic/ behind the app in prod (see docker/nginx/prod.conf)
|
||||||
STOIC_SECRET=dev-secret-change-me
|
STOIC_SECRET=dev-secret-change-me
|
||||||
STOIC_SSO_SECRET=dev-sso-secret-change-me
|
STOIC_SSO_SECRET=dev-sso-secret-change-me
|
||||||
|
|||||||
@@ -17,7 +17,11 @@
|
|||||||
/public/build
|
/public/build
|
||||||
/public/hot
|
/public/hot
|
||||||
/public/sitemap.xml
|
/public/sitemap.xml
|
||||||
|
/public/logos/core
|
||||||
/public/storage
|
/public/storage
|
||||||
|
/public/css/filament
|
||||||
|
/public/js/filament
|
||||||
|
/public/fonts/filament
|
||||||
/storage/*.key
|
/storage/*.key
|
||||||
/storage/framework/migrated
|
/storage/framework/migrated
|
||||||
/storage/pail
|
/storage/pail
|
||||||
|
|||||||
+10
-3
@@ -26,9 +26,6 @@ ENTRYPOINT ["/entrypoint-vite.sh"]
|
|||||||
|
|
||||||
# ── Stage 2b: Node / Vite build ──────────────────────────────────────────────
|
# ── Stage 2b: Node / Vite build ──────────────────────────────────────────────
|
||||||
FROM node:22-alpine AS node-build
|
FROM node:22-alpine AS node-build
|
||||||
# git: same reason as vite-dev above — npm needs it to resolve @boboko/core's
|
|
||||||
# git+https tagged-VCS form.
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci --ignore-scripts
|
RUN npm ci --ignore-scripts
|
||||||
@@ -94,6 +91,16 @@ COPY . .
|
|||||||
RUN chown -R www-data:www-data storage bootstrap/cache \
|
RUN chown -R www-data:www-data storage bootstrap/cache \
|
||||||
&& chmod -R 775 storage bootstrap/cache
|
&& chmod -R 775 storage bootstrap/cache
|
||||||
|
|
||||||
|
# Package discovery and asset publishing are fully determined by composer.lock + code,
|
||||||
|
# so they belong in the image, not in every container's entrypoint. Only asset-type
|
||||||
|
# tags are force-republished here — config tags (core-config, lunar) are a deliberate
|
||||||
|
# one-time `vendor:publish` step you run and commit by hand, since re-running them would
|
||||||
|
# silently overwrite any local edits to config/core.php or config/lunar/*.php.
|
||||||
|
RUN php artisan package:discover --ansi \
|
||||||
|
&& php artisan vendor:publish --tag=core-assets --force --ansi \
|
||||||
|
&& php artisan vendor:publish --tag=public --force --ansi \
|
||||||
|
&& php artisan filament:assets --ansi
|
||||||
|
|
||||||
COPY docker/entrypoint.sh /entrypoint.sh
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
COPY docker/entrypoint-worker.sh /entrypoint-worker.sh
|
COPY docker/entrypoint-worker.sh /entrypoint-worker.sh
|
||||||
RUN chmod +x /entrypoint.sh /entrypoint-worker.sh
|
RUN chmod +x /entrypoint.sh /entrypoint-worker.sh
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
use App\Http\Requests\ContactRequest;
|
use App\Http\Requests\ContactRequest;
|
||||||
use App\Mail\ContactConfirmationMail;
|
use App\Mail\ContactConfirmationMail;
|
||||||
use App\Mail\ContactMessageMail;
|
use App\Mail\ContactMessageMail;
|
||||||
|
use App\Models\StoicPage;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
@@ -15,7 +16,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Contact form. Nothing is stored: the message is emailed to the store
|
* Contact form. Nothing is stored: the message is emailed to the store
|
||||||
* (CONTACT_EMAIL) and the sender gets a generic confirmation. Both are sent
|
* (Store Details' contact email) and the sender gets a generic confirmation. Both are sent
|
||||||
* synchronously so a failed store email can be reported back on the form.
|
* synchronously so a failed store email can be reported back on the form.
|
||||||
*
|
*
|
||||||
* Limited per IP in here rather than with throttle middleware, so the limit
|
* Limited per IP in here rather than with throttle middleware, so the limit
|
||||||
@@ -28,11 +29,15 @@ class ContactController extends Controller
|
|||||||
|
|
||||||
public function index(string $locale): View
|
public function index(string $locale): View
|
||||||
{
|
{
|
||||||
// Address and phone come from Store Details; the email is still
|
$page = StoicPage::localized('contact');
|
||||||
// CONTACT_EMAIL until Store Details has an email field.
|
|
||||||
|
if (! $page) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
return view('contact', [
|
return view('contact', [
|
||||||
|
'page' => $page,
|
||||||
'store' => app(StoreDetailsService::class)->current(),
|
'store' => app(StoreDetailsService::class)->current(),
|
||||||
'contactEmail' => config('services.contact.email'),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,11 +52,11 @@ public function send(string $locale, ContactRequest $request): RedirectResponse
|
|||||||
RateLimiter::hit($key, self::SEND_DECAY_SECONDS);
|
RateLimiter::hit($key, self::SEND_DECAY_SECONDS);
|
||||||
|
|
||||||
$data = $request->validated();
|
$data = $request->validated();
|
||||||
$to = config('services.contact.email');
|
$to = app(StoreDetailsService::class)->current()->contact_email;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (blank($to)) {
|
if (blank($to)) {
|
||||||
throw new \RuntimeException('CONTACT_EMAIL is not set.');
|
throw new \RuntimeException('Store Details has no contact email.');
|
||||||
}
|
}
|
||||||
|
|
||||||
Mail::to($to)->send(new ContactMessageMail(
|
Mail::to($to)->send(new ContactMessageMail(
|
||||||
|
|||||||
@@ -4,27 +4,72 @@
|
|||||||
|
|
||||||
use App\Catalog\ProductCard;
|
use App\Catalog\ProductCard;
|
||||||
use App\Models\StoicPage;
|
use App\Models\StoicPage;
|
||||||
|
use Modules\Core\Catalog\DTOs\ProductFilters;
|
||||||
|
use Modules\Core\Catalog\Services\CollectionService;
|
||||||
use Modules\Core\Catalog\Services\ProductService;
|
use Modules\Core\Catalog\Services\ProductService;
|
||||||
|
|
||||||
class HomeController extends Controller
|
class HomeController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(private readonly ProductService $products) {}
|
/** Slug of the hero collection in the "Homepage" collection group. */
|
||||||
|
private const HERO_COLLECTION_SLUG = 'hero';
|
||||||
|
|
||||||
|
private const HERO_FALLBACK_COUNT = 5;
|
||||||
|
|
||||||
|
private const CLASSICS_COUNT = 8;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ProductService $products,
|
||||||
|
private readonly CollectionService $collections,
|
||||||
|
) {}
|
||||||
|
|
||||||
public function index(string $locale)
|
public function index(string $locale)
|
||||||
{
|
{
|
||||||
$page = StoicPage::firstWhere('slug', 'home');
|
$page = StoicPage::localized('home');
|
||||||
|
|
||||||
if (! $page) {
|
if (! $page) {
|
||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$products = collect($this->products->random(13))
|
// Random pool for the classics carousel, and for the hero too when the
|
||||||
->map(fn (array $product) => ProductCard::fromIndexed($product));
|
// hero collection is missing or empty. Oversized so hero products can be
|
||||||
|
// excluded from it and still leave enough.
|
||||||
|
$random = collect($this->products->random(self::HERO_FALLBACK_COUNT + self::CLASSICS_COUNT));
|
||||||
|
|
||||||
|
$hero = collect($this->heroCollectionProducts());
|
||||||
|
|
||||||
|
if ($hero->isEmpty()) {
|
||||||
|
$hero = $random->take(self::HERO_FALLBACK_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
$heroIds = $hero->pluck('id')->all();
|
||||||
|
|
||||||
|
$classics = $random
|
||||||
|
->reject(fn (array $product) => in_array($product['id'], $heroIds))
|
||||||
|
->take(self::CLASSICS_COUNT);
|
||||||
|
|
||||||
return view('home', [
|
return view('home', [
|
||||||
'page' => $page,
|
'page' => $page,
|
||||||
'heroProducts' => $products->take(5)->values(),
|
'heroProducts' => $hero->map(fn (array $product) => ProductCard::fromIndexed($product))->values(),
|
||||||
'classicsProducts' => $products->slice(5)->values(),
|
'classicsProducts' => $classics->map(fn (array $product) => ProductCard::fromIndexed($product))->values(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Products the store owner picked for the hero, via the Lunar collection
|
||||||
|
* with slug "hero". Not in the owner's drag order yet: the product index
|
||||||
|
* doesn't carry per-collection position (pending a boboko-core task).
|
||||||
|
*/
|
||||||
|
private function heroCollectionProducts(): array
|
||||||
|
{
|
||||||
|
$collection = $this->collections->getBySlug(self::HERO_COLLECTION_SLUG);
|
||||||
|
|
||||||
|
if ($collection === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->products
|
||||||
|
->list(new ProductFilters(collectionId: $collection['id']))
|
||||||
|
->products
|
||||||
|
->items();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ class LegalPageController extends Controller
|
|||||||
'{store_name}' => 'name',
|
'{store_name}' => 'name',
|
||||||
'{store_address}' => 'address',
|
'{store_address}' => 'address',
|
||||||
'{store_phone}' => 'phone',
|
'{store_phone}' => 'phone',
|
||||||
|
'{store_contact_email}' => 'contact_email',
|
||||||
|
'{store_legal_name}' => 'legal_name',
|
||||||
'{store_tax_identifier}' => 'tax_identifier',
|
'{store_tax_identifier}' => 'tax_identifier',
|
||||||
'{store_registration_number}' => 'registration_number',
|
'{store_registration_number}' => 'registration_number',
|
||||||
];
|
];
|
||||||
@@ -45,7 +47,7 @@ public function cookies(string $locale)
|
|||||||
|
|
||||||
private function show(string $slug)
|
private function show(string $slug)
|
||||||
{
|
{
|
||||||
$page = StoicPage::firstWhere('slug', $slug);
|
$page = StoicPage::localized($slug);
|
||||||
|
|
||||||
if (! $page) {
|
if (! $page) {
|
||||||
abort(404);
|
abort(404);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
use Illuminate\Mail\Mailables\Envelope;
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A contact-form message, sent to the store's CONTACT_EMAIL. Reply-To is the
|
* A contact-form message, sent to Store Details' contact email. Reply-To is the
|
||||||
* sender, so replying from the inbox goes straight to them.
|
* sender, so replying from the inbox goes straight to them.
|
||||||
*/
|
*/
|
||||||
class ContactMessageMail extends Mailable
|
class ContactMessageMail extends Mailable
|
||||||
|
|||||||
@@ -2,16 +2,32 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Services\Stoic;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use JacobJoergensen\LaravelPaper\Attributes\ContentPath;
|
use JacobJoergensen\LaravelPaper\Attributes\ContentPath;
|
||||||
use JacobJoergensen\LaravelPaper\Attributes\Driver;
|
use JacobJoergensen\LaravelPaper\Attributes\Driver;
|
||||||
use JacobJoergensen\LaravelPaper\Attributes\Timestamps;
|
use JacobJoergensen\LaravelPaper\Attributes\Timestamps;
|
||||||
use JacobJoergensen\LaravelPaper\Paper;
|
use JacobJoergensen\LaravelPaper\Paper;
|
||||||
|
|
||||||
|
// Points at the default locale (el), which Stoic writes every field to. Other
|
||||||
|
// locales hold only the translatable fields — use localized() to get a page
|
||||||
|
// with those layered on top for the current locale.
|
||||||
#[Driver("markdown")]
|
#[Driver("markdown")]
|
||||||
#[ContentPath("resources/stoic/content/pages")]
|
#[ContentPath("resources/stoic/content/el/pages")]
|
||||||
#[Timestamps]
|
#[Timestamps]
|
||||||
class StoicPage extends Model
|
class StoicPage extends Model
|
||||||
{
|
{
|
||||||
use Paper;
|
use Paper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The page in the current locale: the default-locale entry, with the
|
||||||
|
* current locale's non-empty fields (and body) swapped in. Untranslated
|
||||||
|
* fields fall back to the default locale.
|
||||||
|
*/
|
||||||
|
public static function localized(string $slug): ?static
|
||||||
|
{
|
||||||
|
$page = static::firstWhere('slug', $slug);
|
||||||
|
|
||||||
|
return $page?->forceFill(Stoic::localeOverrides("pages/{$slug}.md"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,20 @@
|
|||||||
use App\Models\Staff;
|
use App\Models\Staff;
|
||||||
use App\Services\StoicSettings;
|
use App\Services\StoicSettings;
|
||||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
use Illuminate\Mail\Events\MessageSending;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\Facades\URL;
|
use Illuminate\Support\Facades\URL;
|
||||||
use Illuminate\Support\Facades\View;
|
use Illuminate\Support\Facades\View;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Illuminate\View\View as ViewInstance;
|
use Illuminate\View\View as ViewInstance;
|
||||||
use Lunar\Facades\ModelManifest;
|
use Lunar\Facades\ModelManifest;
|
||||||
use Lunar\Facades\Telemetry;
|
use Lunar\Facades\Telemetry;
|
||||||
|
use Lunar\Models\CollectionGroup;
|
||||||
use Modules\Core\Catalog\DTOs\CollectionFilters;
|
use Modules\Core\Catalog\DTOs\CollectionFilters;
|
||||||
use Modules\Core\Catalog\Enums\CollectionSort;
|
use Modules\Core\Catalog\Enums\CollectionSort;
|
||||||
use Modules\Core\Catalog\Services\CollectionService;
|
use Modules\Core\Catalog\Services\CollectionService;
|
||||||
|
use Modules\Core\Store\Services\StoreDetailsService;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -27,12 +32,17 @@ public function boot(): void
|
|||||||
{
|
{
|
||||||
Telemetry::optOut();
|
Telemetry::optOut();
|
||||||
|
|
||||||
// header.blade.php's category dropdown — root collections only, resolved
|
// header.blade.php's category dropdown — root collections of the catalog
|
||||||
// per-request so the composer runs after `locale` middleware has already
|
// group only, so merchandising groups (e.g. "Homepage") stay out of the
|
||||||
// set App::getLocale(), which CollectionService's name resolution depends on.
|
// nav. Resolved per-request so the composer runs after `locale` middleware
|
||||||
|
// has already set App::getLocale(), which CollectionService's name
|
||||||
|
// resolution depends on. The group is looked up by handle, not id, since
|
||||||
|
// ids can differ between environments.
|
||||||
View::composer('components.header', function (ViewInstance $view) {
|
View::composer('components.header', function (ViewInstance $view) {
|
||||||
$view->with('categories', app(CollectionService::class)->list(
|
$groupId = CollectionGroup::where('handle', 'shopify')->value('id');
|
||||||
filters: new CollectionFilters(rootOnly: true),
|
|
||||||
|
$view->with('categories', $groupId === null ? [] : app(CollectionService::class)->list(
|
||||||
|
filters: new CollectionFilters(groupId: $groupId, rootOnly: true),
|
||||||
perPage: 100,
|
perPage: 100,
|
||||||
sort: CollectionSort::Position,
|
sort: CollectionSort::Position,
|
||||||
)->items());
|
)->items());
|
||||||
@@ -45,6 +55,29 @@ public function boot(): void
|
|||||||
fn (ViewInstance $view) => $view->with('stoicSettings', app(StoicSettings::class)),
|
fn (ViewInstance $view) => $view->with('stoicSettings', app(StoicSettings::class)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// INTERIM, belongs in boboko-core's Store module: the sender name on
|
||||||
|
// outgoing emails comes from Store Details' "Mail from name", with
|
||||||
|
// MAIL_FROM_NAME as the fallback when it's empty. Done per message
|
||||||
|
// rather than by setting config at boot, so a long-running queue
|
||||||
|
// worker picks up a changed name without a restart. Only the default
|
||||||
|
// from address is renamed; a mailable with its own from is left alone.
|
||||||
|
Event::listen(MessageSending::class, function (MessageSending $event) {
|
||||||
|
$name = app(StoreDetailsService::class)->current()->mail_from_name;
|
||||||
|
|
||||||
|
if (blank($name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultAddress = config('mail.from.address');
|
||||||
|
|
||||||
|
$event->message->from(...array_map(
|
||||||
|
fn (Address $from) => $from->getAddress() === $defaultAddress
|
||||||
|
? new Address($from->getAddress(), $name)
|
||||||
|
: $from,
|
||||||
|
$event->message->getFrom(),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
if ($this->app->environment('production')) {
|
if ($this->app->environment('production')) {
|
||||||
URL::forceScheme('https');
|
URL::forceScheme('https');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Spatie\YamlFrontMatter\YamlFrontMatter;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class Stoic
|
class Stoic
|
||||||
@@ -29,6 +30,42 @@ public static function presets(): array
|
|||||||
return static::$presets;
|
return static::$presets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stoic's default locale (first in i18n.locales), or null when i18n is off.
|
||||||
|
// Its content folder holds every field; other locales only translatable ones.
|
||||||
|
public static function defaultLocale(): ?string
|
||||||
|
{
|
||||||
|
static::loadConfig();
|
||||||
|
return static::$config["i18n"]["locales"][0]["code"] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current locale's translated fields for a content file (path relative
|
||||||
|
// to a locale folder, e.g. "pages/home.md"), with the markdown body as
|
||||||
|
// `content`. Empty when the current locale is the default one (or i18n is
|
||||||
|
// off), the file doesn't exist, or a field is left blank — so layering this
|
||||||
|
// over the default-locale values falls back to them field by field.
|
||||||
|
public static function localeOverrides(string $file): array
|
||||||
|
{
|
||||||
|
$default = static::defaultLocale();
|
||||||
|
$locale = app()->getLocale();
|
||||||
|
|
||||||
|
if ($default === null || $locale === $default) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = resource_path("stoic/content/{$locale}/{$file}");
|
||||||
|
|
||||||
|
if (!is_file($path)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$document = YamlFrontMatter::parse(file_get_contents($path));
|
||||||
|
|
||||||
|
return array_filter(
|
||||||
|
[...$document->matter(), "content" => trim($document->body())],
|
||||||
|
fn($value) => filled($value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the public URL for a stoic image at a given preset.
|
// Returns the public URL for a stoic image at a given preset.
|
||||||
public static function image(string $filename, string $preset): string
|
public static function image(string $filename, string $preset): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,13 +6,19 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Site-wide, marketing-editable settings from Stoic's `settings` singleton
|
* Site-wide, marketing-editable settings from Stoic's `settings` singleton
|
||||||
* (resources/stoic/content/settings.md). Read directly rather than through a
|
* (resources/stoic/content/{locale}/settings.md). Read directly rather than
|
||||||
* Paper model: Paper models map to a folder of entries, and this is one file
|
* through a Paper model: Paper models map to a folder of entries, and this is
|
||||||
* at the content root. Legal and transactional details (address, ΑΦΜ, phone)
|
* one file per locale. Legal and transactional details (address, ΑΦΜ, phone)
|
||||||
* live in boboko-core's StoreDetails instead.
|
* live in boboko-core's StoreDetails instead.
|
||||||
*
|
*
|
||||||
* Bound as scoped in AppServiceProvider, so the file is parsed once per
|
* Stoic writes every field to the default locale's file and only the
|
||||||
* request (or queued job) and edits in Stoic show up on the next one.
|
* translatable ones (e.g. meta_description) to the others, so the current
|
||||||
|
* locale's file is layered over the default one; empty values fall through.
|
||||||
|
*
|
||||||
|
* Bound as scoped in AppServiceProvider, so files are parsed at most once per
|
||||||
|
* locale per request (or queued job) and edits in Stoic show up on the next one.
|
||||||
|
* The locale is read per call, not at construction, so a mail rendered with
|
||||||
|
* ->locale() still gets its own locale's values.
|
||||||
*/
|
*/
|
||||||
class StoicSettings
|
class StoicSettings
|
||||||
{
|
{
|
||||||
@@ -22,26 +28,20 @@ class StoicSettings
|
|||||||
'tiktok' => 'TikTok',
|
'tiktok' => 'TikTok',
|
||||||
];
|
];
|
||||||
|
|
||||||
private array $data;
|
/** @var array<string, array<string, mixed>> keyed by locale */
|
||||||
|
private array $data = [];
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$path = resource_path('stoic/content/settings.md');
|
|
||||||
|
|
||||||
$this->data = is_file($path)
|
|
||||||
? YamlFrontMatter::parse(file_get_contents($path))->matter()
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get(string $key, mixed $default = null): mixed
|
public function get(string $key, mixed $default = null): mixed
|
||||||
{
|
{
|
||||||
return filled($this->data[$key] ?? null) ? $this->data[$key] : $default;
|
$value = $this->data()[$key] ?? null;
|
||||||
|
|
||||||
|
return filled($value) ? $value : $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Absolute og:image URL for a Stoic image field value (the page's own, else
|
* Absolute og:image URL for a Stoic image field value (the page's own, else
|
||||||
* this file's default), or null. Image fields store a comma-separated list;
|
* the default from settings.md), or null. Image fields store a
|
||||||
* only the first is used.
|
* comma-separated list; only the first is used.
|
||||||
*/
|
*/
|
||||||
public function ogImageUrl(?string $pageImage = null): ?string
|
public function ogImageUrl(?string $pageImage = null): ?string
|
||||||
{
|
{
|
||||||
@@ -67,4 +67,30 @@ public function socialLinks(): array
|
|||||||
|
|
||||||
return $links;
|
return $links;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function data(): array
|
||||||
|
{
|
||||||
|
$locale = app()->getLocale();
|
||||||
|
|
||||||
|
return $this->data[$locale] ??= $this->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function load(): array
|
||||||
|
{
|
||||||
|
$default = Stoic::defaultLocale();
|
||||||
|
|
||||||
|
if ($default === null) {
|
||||||
|
return $this->parse(resource_path('stoic/content/settings.md'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_merge(
|
||||||
|
$this->parse(resource_path("stoic/content/{$default}/settings.md")),
|
||||||
|
Stoic::localeOverrides('settings.md'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parse(string $path): array
|
||||||
|
{
|
||||||
|
return is_file($path) ? YamlFrontMatter::parse(file_get_contents($path))->matter() : [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -12,6 +12,9 @@
|
|||||||
"artesaos/seotools": "^1.4",
|
"artesaos/seotools": "^1.4",
|
||||||
"boboko/core": "0.*",
|
"boboko/core": "0.*",
|
||||||
"jacobjoergensen/laravel-paper": "^1.12",
|
"jacobjoergensen/laravel-paper": "^1.12",
|
||||||
|
"open-telemetry/exporter-otlp": "^1.4",
|
||||||
|
"open-telemetry/opentelemetry-auto-laravel": "^1.7",
|
||||||
|
"open-telemetry/sdk": "^1.14",
|
||||||
"php-http/guzzle7-adapter": "^1.1",
|
"php-http/guzzle7-adapter": "^1.1",
|
||||||
"spatie/laravel-sitemap": "^8.1",
|
"spatie/laravel-sitemap": "^8.1",
|
||||||
"spatie/laravel-webhook-server": "^3.10",
|
"spatie/laravel-webhook-server": "^3.10",
|
||||||
@@ -97,8 +100,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true
|
||||||
"require-dev": {
|
|
||||||
"laravel/envoy": "^2.12"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+674
-66
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "e3ff3260847361a66e2d2907b0be9de1",
|
"content-hash": "a89bc3e4e93372ee52664163d56f783b",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "anourvalar/eloquent-serialize",
|
"name": "anourvalar/eloquent-serialize",
|
||||||
@@ -2439,6 +2439,50 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-12-03T09:33:47+00:00"
|
"time": "2025-12-03T09:33:47+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "google/protobuf",
|
||||||
|
"version": "v5.36.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/protocolbuffers/protobuf-php.git",
|
||||||
|
"reference": "020bc8e831611869ac387ddd38fbbadd78d15195"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/020bc8e831611869ac387ddd38fbbadd78d15195",
|
||||||
|
"reference": "020bc8e831611869ac387ddd38fbbadd78d15195",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": ">=11.5.50 <12.0.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-bcmath": "Need to support JSON deserialization"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Google\\Protobuf\\": "src/Google/Protobuf",
|
||||||
|
"GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"description": "proto library for PHP",
|
||||||
|
"homepage": "https://developers.google.com/protocol-buffers/",
|
||||||
|
"keywords": [
|
||||||
|
"proto"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v5.36.2"
|
||||||
|
},
|
||||||
|
"time": "2026-09-17T15:56:42+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "graham-campbell/result-type",
|
"name": "graham-campbell/result-type",
|
||||||
"version": "v1.2.0",
|
"version": "v1.2.0",
|
||||||
@@ -5818,6 +5862,554 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-09-09T07:06:30+00:00"
|
"time": "2024-09-09T07:06:30+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "nyholm/psr7-server",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Nyholm/psr7-server.git",
|
||||||
|
"reference": "4335801d851f554ca43fa6e7d2602141538854dc"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc",
|
||||||
|
"reference": "4335801d851f554ca43fa6e7d2602141538854dc",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.1 || ^8.0",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.0 || ^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"nyholm/nsa": "^1.1",
|
||||||
|
"nyholm/psr7": "^1.3",
|
||||||
|
"phpunit/phpunit": "^7.0 || ^8.5 || ^9.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nyholm\\Psr7Server\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Tobias Nyholm",
|
||||||
|
"email": "tobias.nyholm@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Martijn van der Ven",
|
||||||
|
"email": "martijn@vanderven.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Helper classes to handle PSR-7 server requests",
|
||||||
|
"homepage": "http://tnyholm.se",
|
||||||
|
"keywords": [
|
||||||
|
"psr-17",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Nyholm/psr7-server/issues",
|
||||||
|
"source": "https://github.com/Nyholm/psr7-server/tree/1.1.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Zegnat",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nyholm",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2023-11-08T09:30:43+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "open-telemetry/api",
|
||||||
|
"version": "1.10.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/opentelemetry-php/api.git",
|
||||||
|
"reference": "7c029c4a6fd457094a20569bf98f93d95e9a7559"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/7c029c4a6fd457094a20569bf98f93d95e9a7559",
|
||||||
|
"reference": "7c029c4a6fd457094a20569bf98f93d95e9a7559",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"open-telemetry/context": "^1.4",
|
||||||
|
"php": "^8.1",
|
||||||
|
"psr/log": "^1.1|^2.0|^3.0",
|
||||||
|
"symfony/polyfill-php82": "^1.26"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"open-telemetry/sdk": "<=1.11"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"spi": {
|
||||||
|
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [
|
||||||
|
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.8.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"Trace/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"OpenTelemetry\\API\\": "."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "opentelemetry-php contributors",
|
||||||
|
"homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "API for OpenTelemetry PHP.",
|
||||||
|
"keywords": [
|
||||||
|
"Metrics",
|
||||||
|
"api",
|
||||||
|
"apm",
|
||||||
|
"logging",
|
||||||
|
"opentelemetry",
|
||||||
|
"otel",
|
||||||
|
"tracing"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V",
|
||||||
|
"docs": "https://opentelemetry.io/docs/languages/php",
|
||||||
|
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||||
|
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||||
|
},
|
||||||
|
"time": "2026-07-06T12:28:04+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "open-telemetry/context",
|
||||||
|
"version": "1.5.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/opentelemetry-php/context.git",
|
||||||
|
"reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/opentelemetry-php/context/zipball/3c414b246e0dabb7d6145404e6a5e4536ca18d07",
|
||||||
|
"reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"symfony/polyfill-php82": "^1.26"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-ffi": "To allow context switching in Fibers"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"fiber/initialize_fiber_handler.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"OpenTelemetry\\Context\\": "."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "opentelemetry-php contributors",
|
||||||
|
"homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Context implementation for OpenTelemetry PHP.",
|
||||||
|
"keywords": [
|
||||||
|
"Context",
|
||||||
|
"opentelemetry",
|
||||||
|
"otel"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V",
|
||||||
|
"docs": "https://opentelemetry.io/docs/languages/php",
|
||||||
|
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||||
|
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||||
|
},
|
||||||
|
"time": "2025-10-19T06:44:33+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "open-telemetry/exporter-otlp",
|
||||||
|
"version": "1.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/opentelemetry-php/exporter-otlp.git",
|
||||||
|
"reference": "283a0d66522f2adc6d8d7debfd7686be91c282be"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/283a0d66522f2adc6d8d7debfd7686be91c282be",
|
||||||
|
"reference": "283a0d66522f2adc6d8d7debfd7686be91c282be",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"open-telemetry/api": "^1.0",
|
||||||
|
"open-telemetry/gen-otlp-protobuf": "^1.1",
|
||||||
|
"open-telemetry/sdk": "^1.0",
|
||||||
|
"php": "^8.1",
|
||||||
|
"php-http/discovery": "^1.14"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"_register.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"OpenTelemetry\\Contrib\\Otlp\\": "."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "opentelemetry-php contributors",
|
||||||
|
"homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "OTLP exporter for OpenTelemetry.",
|
||||||
|
"keywords": [
|
||||||
|
"Metrics",
|
||||||
|
"exporter",
|
||||||
|
"gRPC",
|
||||||
|
"http",
|
||||||
|
"opentelemetry",
|
||||||
|
"otel",
|
||||||
|
"otlp",
|
||||||
|
"tracing"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V",
|
||||||
|
"docs": "https://opentelemetry.io/docs/languages/php",
|
||||||
|
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||||
|
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||||
|
},
|
||||||
|
"time": "2026-02-05T09:44:52+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "open-telemetry/gen-otlp-protobuf",
|
||||||
|
"version": "1.10.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git",
|
||||||
|
"reference": "66f04d0e448ad333033bfc7baae1aa56330be088"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/66f04d0e448ad333033bfc7baae1aa56330be088",
|
||||||
|
"reference": "66f04d0e448ad333033bfc7baae1aa56330be088",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"google/protobuf": "^3.22 || ^4.0 || ^5.0",
|
||||||
|
"php": "^8.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-protobuf": "For better performance, when dealing with the protobuf format"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Opentelemetry\\Proto\\": "Opentelemetry/Proto/",
|
||||||
|
"GPBMetadata\\Opentelemetry\\": "GPBMetadata/Opentelemetry/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "opentelemetry-php contributors",
|
||||||
|
"homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP protobuf files for communication with OpenTelemetry OTLP collectors/servers.",
|
||||||
|
"keywords": [
|
||||||
|
"Metrics",
|
||||||
|
"apm",
|
||||||
|
"gRPC",
|
||||||
|
"logging",
|
||||||
|
"opentelemetry",
|
||||||
|
"otel",
|
||||||
|
"otlp",
|
||||||
|
"protobuf",
|
||||||
|
"tracing"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V",
|
||||||
|
"docs": "https://opentelemetry.io/docs/languages/php",
|
||||||
|
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||||
|
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||||
|
},
|
||||||
|
"time": "2026-06-17T12:06:32+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "open-telemetry/opentelemetry-auto-laravel",
|
||||||
|
"version": "1.9.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/opentelemetry-php/contrib-auto-laravel.git",
|
||||||
|
"reference": "fd81cb8e18af4b5405fb67c84622833886fa51da"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/opentelemetry-php/contrib-auto-laravel/zipball/fd81cb8e18af4b5405fb67c84622833886fa51da",
|
||||||
|
"reference": "fd81cb8e18af4b5405fb67c84622833886fa51da",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-opentelemetry": "*",
|
||||||
|
"laravel/framework": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"open-telemetry/api": "^1.8",
|
||||||
|
"open-telemetry/sem-conv": "^1.38",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.50",
|
||||||
|
"guzzlehttp/guzzle": "*",
|
||||||
|
"nunomaduro/collision": "*",
|
||||||
|
"open-telemetry/sdk": "^1.8",
|
||||||
|
"orchestra/testbench": ">=7.41.3",
|
||||||
|
"phan/phan": "^6.0",
|
||||||
|
"php-http/mock-client": "*",
|
||||||
|
"phpstan/phpstan": "^1.1",
|
||||||
|
"phpstan/phpstan-phpunit": "^1.0",
|
||||||
|
"phpunit/phpunit": "^9.5 || ^10 || ^11 || ^12 || ^13",
|
||||||
|
"psalm/plugin-phpunit": "^0.19.2",
|
||||||
|
"spatie/laravel-ignition": "*",
|
||||||
|
"vimeo/psalm": "6.4.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"open-telemetry/opentelemetry-propagation-server-timing": "Automatically propagate the context to the client through server-timing headers.",
|
||||||
|
"open-telemetry/opentelemetry-propagation-traceresponse": "Automatically propagate the context to the client through trace-response headers."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"_register.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"OpenTelemetry\\Contrib\\Instrumentation\\Laravel\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"description": "OpenTelemetry auto-instrumentation for Laravel",
|
||||||
|
"homepage": "https://opentelemetry.io/docs/languages/php/",
|
||||||
|
"keywords": [
|
||||||
|
"instrumentation",
|
||||||
|
"laravel",
|
||||||
|
"open-telemetry",
|
||||||
|
"opentelemetry",
|
||||||
|
"otel",
|
||||||
|
"tracing"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/opentelemetry-php/contrib-auto-laravel/tree/1.9.1"
|
||||||
|
},
|
||||||
|
"time": "2026-09-17T05:26:08+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "open-telemetry/sdk",
|
||||||
|
"version": "1.15.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/opentelemetry-php/sdk.git",
|
||||||
|
"reference": "77e1aa73850154abb86937d52a70883edc3b4547"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/77e1aa73850154abb86937d52a70883edc3b4547",
|
||||||
|
"reference": "77e1aa73850154abb86937d52a70883edc3b4547",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"nyholm/psr7-server": "^1.1",
|
||||||
|
"open-telemetry/api": "^1.8",
|
||||||
|
"open-telemetry/context": "^1.4",
|
||||||
|
"open-telemetry/sem-conv": "^1.38.0",
|
||||||
|
"php": "^8.1",
|
||||||
|
"php-http/discovery": "^1.14",
|
||||||
|
"psr/http-client": "^1.0",
|
||||||
|
"psr/http-client-implementation": "^1.0",
|
||||||
|
"psr/http-factory-implementation": "^1.0",
|
||||||
|
"psr/http-message": "^1.0.1|^2.0",
|
||||||
|
"psr/log": "^1.1|^2.0|^3.0",
|
||||||
|
"ramsey/uuid": "^3.0 || ^4.0",
|
||||||
|
"symfony/polyfill-mbstring": "^1.23",
|
||||||
|
"symfony/polyfill-php82": "^1.26",
|
||||||
|
"tbachert/spi": "^1.0.5"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-gmp": "To support unlimited number of synchronous metric readers",
|
||||||
|
"ext-mbstring": "To increase performance of string operations",
|
||||||
|
"open-telemetry/sdk-configuration": "File-based OpenTelemetry SDK configuration"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"spi": {
|
||||||
|
"OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [
|
||||||
|
"OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig",
|
||||||
|
"OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig",
|
||||||
|
"OpenTelemetry\\SDK\\ConfigEnv\\Trace\\SpanSuppressionStrategySemConv",
|
||||||
|
"OpenTelemetry\\SDK\\ConfigEnv\\Trace\\SpanSuppressionStrategySpanKind",
|
||||||
|
"OpenTelemetry\\SDK\\ConfigEnv\\Distribution\\DistributionConfigurationSdk"
|
||||||
|
],
|
||||||
|
"OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [
|
||||||
|
"OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver"
|
||||||
|
],
|
||||||
|
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [
|
||||||
|
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.14.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"Common/Util/functions.php",
|
||||||
|
"Logs/Exporter/_register.php",
|
||||||
|
"Metrics/MetricExporter/_register.php",
|
||||||
|
"Propagation/_register.php",
|
||||||
|
"Trace/SpanExporter/_register.php",
|
||||||
|
"Common/Dev/Compatibility/_load.php",
|
||||||
|
"_autoload.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"OpenTelemetry\\SDK\\": "."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "opentelemetry-php contributors",
|
||||||
|
"homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "SDK for OpenTelemetry PHP.",
|
||||||
|
"keywords": [
|
||||||
|
"Metrics",
|
||||||
|
"apm",
|
||||||
|
"logging",
|
||||||
|
"opentelemetry",
|
||||||
|
"otel",
|
||||||
|
"sdk",
|
||||||
|
"tracing"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V",
|
||||||
|
"docs": "https://opentelemetry.io/docs/languages/php",
|
||||||
|
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||||
|
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||||
|
},
|
||||||
|
"time": "2026-07-14T13:09:54+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "open-telemetry/sem-conv",
|
||||||
|
"version": "1.44.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/opentelemetry-php/sem-conv.git",
|
||||||
|
"reference": "c94bc7ebacef039cda4f4c9270807dcc58b4e3af"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/c94bc7ebacef039cda4f4c9270807dcc58b4e3af",
|
||||||
|
"reference": "c94bc7ebacef039cda4f4c9270807dcc58b4e3af",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"OpenTelemetry\\SemConv\\": "."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "opentelemetry-php contributors",
|
||||||
|
"homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Semantic conventions for OpenTelemetry PHP.",
|
||||||
|
"keywords": [
|
||||||
|
"Metrics",
|
||||||
|
"apm",
|
||||||
|
"logging",
|
||||||
|
"opentelemetry",
|
||||||
|
"otel",
|
||||||
|
"semantic conventions",
|
||||||
|
"semconv",
|
||||||
|
"tracing"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V",
|
||||||
|
"docs": "https://opentelemetry.io/docs/languages/php",
|
||||||
|
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||||
|
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||||
|
},
|
||||||
|
"time": "2026-09-09T22:06:17+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "openspout/openspout",
|
"name": "openspout/openspout",
|
||||||
"version": "v4.32.0",
|
"version": "v4.32.0",
|
||||||
@@ -10968,6 +11560,86 @@
|
|||||||
],
|
],
|
||||||
"time": "2026-05-26T12:45:58+00:00"
|
"time": "2026-05-26T12:45:58+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-php82",
|
||||||
|
"version": "v1.38.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-php82.git",
|
||||||
|
"reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b",
|
||||||
|
"reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"url": "https://github.com/symfony/polyfill",
|
||||||
|
"name": "symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Php82\\": ""
|
||||||
|
},
|
||||||
|
"classmap": [
|
||||||
|
"Resources/stubs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-05-26T12:45:58+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php83",
|
"name": "symfony/polyfill-php83",
|
||||||
"version": "v1.41.0",
|
"version": "v1.41.0",
|
||||||
@@ -12569,71 +13241,7 @@
|
|||||||
"time": "2026-06-15T15:31:57+00:00"
|
"time": "2026-06-15T15:31:57+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [],
|
||||||
{
|
|
||||||
"name": "laravel/envoy",
|
|
||||||
"version": "v2.12.2",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/laravel/envoy.git",
|
|
||||||
"reference": "2f475252d9f2ab45e259d9b24e18cd179a6814df"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/laravel/envoy/zipball/2f475252d9f2ab45e259d9b24e18cd179a6814df",
|
|
||||||
"reference": "2f475252d9f2ab45e259d9b24e18cd179a6814df",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"guzzlehttp/guzzle": "^6.0|^7.0",
|
|
||||||
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
|
|
||||||
"php": "^7.2|^8.0",
|
|
||||||
"symfony/console": "^4.3|^5.0|^6.0|^7.0|^8.0",
|
|
||||||
"symfony/process": "^4.3|^5.0|^6.0|^7.0|^8.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpstan/phpstan": "^1.10",
|
|
||||||
"phpunit/phpunit": "^8.0|^9.0|^10.4"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"ext-posix": "Required to determine the System user on Unix systems."
|
|
||||||
},
|
|
||||||
"bin": [
|
|
||||||
"bin/envoy"
|
|
||||||
],
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "2.x-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Laravel\\Envoy\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Taylor Otwell",
|
|
||||||
"email": "taylor@laravel.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Elegant SSH tasks for PHP.",
|
|
||||||
"keywords": [
|
|
||||||
"laravel",
|
|
||||||
"ssh"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/laravel/envoy/issues",
|
|
||||||
"source": "https://github.com/laravel/envoy/tree/v2.12.2"
|
|
||||||
},
|
|
||||||
"time": "2026-04-01T12:56:10+00:00"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": {},
|
"stability-flags": {},
|
||||||
|
|||||||
@@ -35,11 +35,6 @@
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
// Where contact-form messages are sent (ContactController).
|
|
||||||
'contact' => [
|
|
||||||
'email' => env('CONTACT_EMAIL'),
|
|
||||||
],
|
|
||||||
|
|
||||||
// Bot check on guest forms (login, contact), 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,
|
||||||
|
|||||||
+8
-19
@@ -131,6 +131,14 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
|
otel-collector:
|
||||||
|
image: otel/opentelemetry-collector-contrib:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
SIGNOZ_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT}
|
||||||
|
volumes:
|
||||||
|
- ./docker/otel/collector.yml:/etc/otelcol-contrib/config.yaml:ro
|
||||||
|
|
||||||
stoic:
|
stoic:
|
||||||
image: ghcr.io/lexx27/stoic:latest
|
image: ghcr.io/lexx27/stoic:latest
|
||||||
ports:
|
ports:
|
||||||
@@ -147,24 +155,6 @@ services:
|
|||||||
- storage:/var/www/html/storage/app
|
- storage:/var/www/html/storage/app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
vector:
|
|
||||||
image: timberio/vector:latest-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
APP_ENV: ${APP_ENV:-production}
|
|
||||||
VICTORIALOGS_ENDPOINT: ${VICTORIALOGS_ENDPOINT:?VICTORIALOGS_ENDPOINT is required}
|
|
||||||
VICTORIALOGS_USER: ${VICTORIALOGS_USER:?VICTORIALOGS_USER is required}
|
|
||||||
VICTORIALOGS_PASSWORD: ${VICTORIALOGS_PASSWORD:?VICTORIALOGS_PASSWORD is required}
|
|
||||||
VECTOR_DANGEROUSLY_ALLOW_ENV_VAR_INTERPOLATION: "true"
|
|
||||||
volumes:
|
|
||||||
- ./docker/vector/vector.yaml:/etc/vector/vector.yaml:ro
|
|
||||||
- logs:/var/www/html/storage/logs:ro
|
|
||||||
- vector_data:/var/lib/vector
|
|
||||||
depends_on:
|
|
||||||
app:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
storage:
|
storage:
|
||||||
@@ -172,4 +162,3 @@ volumes:
|
|||||||
framework:
|
framework:
|
||||||
valkeydata:
|
valkeydata:
|
||||||
meilidata:
|
meilidata:
|
||||||
vector_data:
|
|
||||||
|
|||||||
+12
-10
@@ -41,17 +41,19 @@ echo "[entrypoint] Caches cleared"
|
|||||||
|
|
||||||
php artisan storage:link --quiet 2>/dev/null || true
|
php artisan storage:link --quiet 2>/dev/null || true
|
||||||
|
|
||||||
# Dev-only for the same reason as the composer step above — production's
|
if [ "$APP_ENV" != "production" ]; then
|
||||||
# image already has these published at build time.
|
# Dev-only for the same reason as the composer step above — production's
|
||||||
php artisan vendor:publish --tag=core-assets --force --ansi --quiet
|
# image already has these published at build time.
|
||||||
php artisan vendor:publish --tag=public --force --ansi --quiet
|
php artisan vendor:publish --tag=core-assets --force --ansi --quiet
|
||||||
php artisan filament:assets --ansi --quiet
|
php artisan vendor:publish --tag=public --force --ansi --quiet
|
||||||
|
php artisan filament:assets --ansi --quiet
|
||||||
|
|
||||||
# No --force: core-views publishes editable Blade templates (order
|
# No --force: core-views publishes editable Blade templates (order
|
||||||
# notification emails), meant to be hand-customized per app — unlike
|
# notification emails), meant to be hand-customized per app — unlike
|
||||||
# core-assets above, republishing must not silently wipe local edits.
|
# core-assets above, republishing must not silently wipe local edits.
|
||||||
# Only fills in the vendor/core views directory if it doesn't exist yet.
|
# Only fills in the vendor/core views directory if it doesn't exist yet.
|
||||||
php artisan vendor:publish --tag=core-views --ansi --quiet
|
php artisan vendor:publish --tag=core-views --ansi --quiet
|
||||||
|
fi
|
||||||
|
|
||||||
# Everything below is universal, in both dev and production: application STATE
|
# Everything below is universal, in both dev and production: application STATE
|
||||||
# that must be current on every boot, not a build-time concern composer/assets
|
# that must be current on every boot, not a build-time concern composer/assets
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
receivers:
|
||||||
|
otlp:
|
||||||
|
protocols:
|
||||||
|
http:
|
||||||
|
endpoint: 0.0.0.0:4318
|
||||||
|
|
||||||
|
processors:
|
||||||
|
batch:
|
||||||
|
timeout: 5s
|
||||||
|
send_batch_size: 512
|
||||||
|
|
||||||
|
exporters:
|
||||||
|
otlphttp:
|
||||||
|
endpoint: ${env:SIGNOZ_ENDPOINT}
|
||||||
|
compression: gzip
|
||||||
|
|
||||||
|
service:
|
||||||
|
pipelines:
|
||||||
|
traces:
|
||||||
|
receivers: [otlp]
|
||||||
|
processors: [batch]
|
||||||
|
exporters: [otlphttp]
|
||||||
|
metrics:
|
||||||
|
receivers: [otlp]
|
||||||
|
processors: [batch]
|
||||||
|
exporters: [otlphttp]
|
||||||
|
logs:
|
||||||
|
receivers: [otlp]
|
||||||
|
processors: [batch]
|
||||||
|
exporters: [otlphttp]
|
||||||
@@ -9,6 +9,14 @@ content_path: /content
|
|||||||
media_path: /media
|
media_path: /media
|
||||||
thumbs_path: /var/www/html/storage/app/public/stoic/
|
thumbs_path: /var/www/html/storage/app/public/stoic/
|
||||||
repository_path: /app
|
repository_path: /app
|
||||||
|
# First locale is Stoic's default: it holds every field, the others only the
|
||||||
|
# translatable ones (content/{locale}/...). Codes match app.available_locales.
|
||||||
|
i18n:
|
||||||
|
locales:
|
||||||
|
- code: el
|
||||||
|
label: Ελληνικά
|
||||||
|
- code: en
|
||||||
|
label: English
|
||||||
presets:
|
presets:
|
||||||
- code: large
|
- code: large
|
||||||
mode: resize
|
mode: resize
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
sources:
|
|
||||||
laravel_logs:
|
|
||||||
type: file
|
|
||||||
include:
|
|
||||||
- /var/www/html/storage/logs/laravel.log
|
|
||||||
read_from: beginning
|
|
||||||
|
|
||||||
transforms:
|
|
||||||
laravel_json:
|
|
||||||
type: remap
|
|
||||||
inputs:
|
|
||||||
- laravel_logs
|
|
||||||
source: |
|
|
||||||
. = parse_json!(.message)
|
|
||||||
.service = "boboko-app"
|
|
||||||
.env = "${APP_ENV}"
|
|
||||||
|
|
||||||
sinks:
|
|
||||||
victorialogs:
|
|
||||||
type: http
|
|
||||||
inputs:
|
|
||||||
- laravel_json
|
|
||||||
uri: "${VICTORIALOGS_ENDPOINT}/insert/jsonline?_stream_fields=service,env,channel,level_name&_msg_field=message&_time_field=datetime"
|
|
||||||
encoding:
|
|
||||||
codec: json
|
|
||||||
framing:
|
|
||||||
method: newline_delimited
|
|
||||||
compression: gzip
|
|
||||||
auth:
|
|
||||||
strategy: basic
|
|
||||||
user: "${VICTORIALOGS_USER}"
|
|
||||||
password: "${VICTORIALOGS_PASSWORD}"
|
|
||||||
batch:
|
|
||||||
max_bytes: 1048576
|
|
||||||
timeout_secs: 5
|
|
||||||
buffer:
|
|
||||||
type: disk
|
|
||||||
max_size: 268435488
|
|
||||||
when_full: block
|
|
||||||
healthcheck:
|
|
||||||
enabled: false
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-ext-wght-normal-SP7Z6XGK.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-wght-normal-DR6K5BQD.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-ext-wght-normal-A2J6H3EX.woff2") format("woff2-variations");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-wght-normal-ZABHMKQG.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-vietnamese-wght-normal-VWEHJHBA.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-ext-wght-normal-ZIT2UBIY.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-wght-normal-DIHYUR35.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
(()=>{var o=({livewireId:s})=>({actionNestingIndex:null,shouldOverlayParentActions:!1,closedActionNestingIndexes:[],focusTargetsByNestingIndex:{},boundSyncActionModals:null,boundOnModalClosed:null,init(){this.boundSyncActionModals=e=>{e.detail.id===s&&this.syncActionModals(e.detail.newActionNestingIndex,e.detail.shouldOverlayParentActions??!1)},this.boundOnModalClosed=e=>{let t=this.getActionNestingIndexFromModalId(e.detail.id);t!==null&&((this.shouldOverlayParentActions||t===0)&&this.restorePreviouslyFocusedElement(t-1),this.closedActionNestingIndexes.push(t))},window.addEventListener("sync-action-modals",this.boundSyncActionModals),window.addEventListener("modal-closed",this.boundOnModalClosed)},destroy(){this.boundSyncActionModals&&(window.removeEventListener("sync-action-modals",this.boundSyncActionModals),this.boundSyncActionModals=null),this.boundOnModalClosed&&(window.removeEventListener("modal-closed",this.boundOnModalClosed),this.boundOnModalClosed=null)},syncActionModals(e,t=!1){if(this.actionNestingIndex===e){this.actionNestingIndex!==null&&this.$nextTick(()=>this.openModal());return}let n=this.actionNestingIndex!==null&&e!==null&&e>this.actionNestingIndex,i=this.actionNestingIndex!==null&&e!==null&&e<this.actionNestingIndex,d=this.actionNestingIndex===null&&e!==null;if((n||d)&&this.rememberPreviouslyFocusedElement(),this.actionNestingIndex!==null&&!(t&&n)&&this.closeModal(),this.actionNestingIndex=e,this.actionNestingIndex===null){this.restorePreviouslyFocusedElement(-1),this.closedActionNestingIndexes=[],this.focusTargetsByNestingIndex={},this.shouldOverlayParentActions=!1;return}if(this.shouldOverlayParentActions=t,this.closedActionNestingIndexes=this.closedActionNestingIndexes.filter(l=>l<=this.actionNestingIndex),!this.closedActionNestingIndexes.includes(this.actionNestingIndex)){if(!this.$el.querySelector(`#${this.generateModalId(e)}`)){this.$nextTick(()=>{this.openModal(),i&&this.restorePreviouslyFocusedElement()});return}this.openModal(),i&&this.restorePreviouslyFocusedElement()}},rememberPreviouslyFocusedElement(){let e=this.$focus.focused();if(!e)return;if(this.actionNestingIndex===null){this.focusTargetsByNestingIndex[-1]=e;return}this.$el.querySelector(`#${this.generateModalId(this.actionNestingIndex)}`)?.contains(e)&&(this.focusTargetsByNestingIndex[this.actionNestingIndex]=e)},restorePreviouslyFocusedElement(e=this.actionNestingIndex){let t=this.focusTargetsByNestingIndex[e];if(t){for(let n in this.focusTargetsByNestingIndex)Number(n)>=e&&delete this.focusTargetsByNestingIndex[n];requestAnimationFrame(()=>requestAnimationFrame(()=>this.$nextTick(()=>{t.focus({preventScroll:!0})})))}},generateModalId(e){return`fi-${s}-action-`+e},getActionNestingIndexFromModalId(e){let t=`fi-${s}-action-`;if(!e?.startsWith(t))return null;let n=Number(e.slice(t.length));return Number.isInteger(n)?n:null},openModal(){let e=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("open-modal",{bubbles:!0,composed:!0,detail:{id:e}}))},closeModal(){let e=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("close-modal-quietly",{bubbles:!0,composed:!0,detail:{id:e}}))}});document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentActionModals",o)});})();
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
function t(){return{search:"",blockLabels:[],observer:null,init(){let e=()=>{this.blockLabels=Array.from(this.$root.querySelectorAll("[data-block-label]"),r=>r.dataset.blockLabel)};e(),this.observer=new MutationObserver(e),this.observer.observe(this.$root,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-block-label"]})},destroy(){this.observer?.disconnect()},clearSearch(){this.search="",this.$refs.searchInput&&(this.$refs.searchInput.value="")},handleEscape(e){!this.search&&!this.$refs.searchInput?.value||(e.preventDefault(),this.clearSearch())},isBlockVisible(e){return this.search?e.dataset.blockLabel.includes(this.search.toLowerCase()):!0},get hasNoSearchResults(){return this.search?!this.blockLabels.some(e=>e.includes(this.search.toLowerCase())):!1}}}export{t as default};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",unsubscribeLivewireHook:null,visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),this.unsubscribeLivewireHook=Livewire.hook("commit",({component:e,commit:t,succeed:i,fail:o,respond:h})=>{i(({snapshot:r,effect:l})=>{this.$nextTick(()=>{e.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||i.checked!==e&&(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))},destroy(){this.unsubscribeLivewireHook?.()}}}export{c as default};
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
function a({state:r}){return{state:r,rows:[],init(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(e,t)=>{if(!Array.isArray(e))return;let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(e)===0&&s(t)===0||this.updateRows()})},addRow(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow(e){this.rows.splice(e,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows(e){let t=Alpine.raw(this.rows);this.rows=[];let s=t.splice(e.oldIndex,1)[0];t.splice(e.newIndex,0,s),this.$nextTick(()=>{this.rows=t,this.updateState()})},updateRows(){let t=Alpine.raw(this.state).map(({key:s,value:i})=>({key:s,value:i}));this.rows.forEach(s=>{(s.key===""||s.key===null)&&t.push({key:"",value:s.value})}),this.rows=t},updateState(){let e=[];this.rows.forEach(t=>{t.key===""||t.key===null||e.push({key:t.key,value:t.value})}),JSON.stringify(this.state)!==JSON.stringify(e)&&(this.state=e)}}}export{a as default};
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
function r({state:n,splitKeys:i,tagAddedMessage:a,tagRemovedMessage:s}){return{newTag:"",state:n,liveRegionClearTimeout:null,announce(e){let t=this.$refs.liveRegion;t&&(this.liveRegionClearTimeout!==null&&clearTimeout(this.liveRegionClearTimeout),t.textContent=e,this.liveRegionClearTimeout=setTimeout(()=>{t.textContent="",this.liveRegionClearTimeout=null},3e3))},createTag(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.announce(a?.replace(":tag",()=>this.newTag)),this.newTag=""}},deleteTag(e){this.state=this.state.filter(t=>t!==e),this.announce(s?.replace(":tag",()=>e))},reorderTags(e){let t=this.state.splice(e.oldIndex,1)[0];this.state.splice(e.newIndex,0,t),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(e){["Enter",...i].includes(e.key)&&(e.preventDefault(),e.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(i.length===0){this.createTag();return}let e=i.map(t=>t.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(e,"g")).forEach(t=>{this.newTag=t,this.createTag()})})}}}}export{r as default};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
function n({initialHeight:e,shouldAutosize:s,state:i}){return{state:i,resizeObserver:null,wrapperEl:null,init(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),s?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=e+"rem")},resize(){if(this.$el.scrollHeight<=0)return;let r=this.$el.style.height;this.$el.style.height="0px";let h=this.$el.scrollHeight;this.$el.style.height=r;let l=parseFloat(e)*parseFloat(getComputedStyle(document.documentElement).fontSize),t=Math.max(h,l)+"px";this.wrapperEl.style.height!==t&&(this.wrapperEl.style.height=t)},setUpResizeObserver(){this.resizeObserver=new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}),this.resizeObserver.observe(this.$el)},destroy(){this.resizeObserver?.disconnect()}}}export{n as default};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
var i=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let e=this.$el.parentElement;e&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(e),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let e=this.$el.parentElement;if(!e)return;let t=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=e.offsetWidth+parseInt(t.marginInlineStart,10)*-1+parseInt(t.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});export{i as default};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
function x({activeTab:p,isScrollable:m,isTabPersisted:T,isTabPersistedInQueryString:w,livewireId:g,schemaKey:D,tab:W,tabQueryStringKey:r}){return{boundResizeHandler:null,boundResetHandler:null,isScrollable:m,resizeDebounceTimer:null,tab:W,unsubscribeLivewireHook:null,withinDropdownIndex:null,withinDropdownMounted:!1,init(){let t=this.getTabs(),e=new URLSearchParams(window.location.search);w&&e.has(r)&&t.includes(e.get(r))&&(this.tab=e.get(r)),(!this.tab||!t.includes(this.tab))&&(this.tab=t[p-1]),this.$watch("tab",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0),this.unsubscribeLivewireHook=Livewire.hook("commit",({component:i,commit:d,succeed:c,fail:h,respond:u})=>{c(({snapshot:b,effect:n})=>{this.$nextTick(()=>{if(i.id!==g)return;let o=this.getTabs();o.includes(this.tab)||(this.tab=o[p-1]??this.tab)})})}),this.boundResetHandler=i=>{i.detail.livewireId!==g||i.detail.schemaKey!==D||T||w||this.$nextTick(()=>{this.tab=this.getTabs()[p-1]??this.tab})},window.addEventListener("reset-schema-component-state",this.boundResetHandler),m||(this.boundResizeHandler=this.debouncedUpdateTabsWithinDropdown.bind(this),window.addEventListener("resize",this.boundResizeHandler),this.updateTabsWithinDropdown())},calculateAvailableWidth(t){let e=window.getComputedStyle(t);return Math.floor(t.clientWidth)-Math.ceil(parseFloat(e.paddingLeft))*2},calculateContainerGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap))},calculateDropdownIconWidth(t){let e=t.querySelector(".fi-icon");return Math.ceil(e.clientWidth)},calculateTabItemGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap)||8)},calculateTabItemPadding(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.paddingLeft))+Math.ceil(parseFloat(e.paddingRight))},findOverflowIndex(t,e,i,d,c,h){let u=t.map(n=>Math.ceil(n.clientWidth)),b=t.map(n=>{let o=n.querySelector(".fi-tabs-item-label"),s=n.querySelector(".fi-badge"),a=Math.ceil(o.clientWidth),l=s?Math.ceil(s.clientWidth):0;return{label:a,badge:l,total:a+(l>0?d+l:0)}});for(let n=0;n<t.length;n++){let o=u.slice(0,n+1).reduce((f,I)=>f+I,0),s=n*i,a=b.slice(n+1),l=a.length>0,v=l?Math.max(...a.map(f=>f.total)):0,y=l?c+v+d+h+i:0;if(o+s+y>e)return n}return-1},get isDropdownButtonVisible(){return this.withinDropdownMounted?this.withinDropdownIndex===null?!1:this.getTabs().findIndex(e=>e===this.tab)<this.withinDropdownIndex:!0},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!w)return;let t=new URL(window.location.href);t.searchParams.set(r,this.tab),history.replaceState(null,document.title,t.toString())},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$el.querySelectorAll(".fi-sc-tabs-tab.fi-active [autofocus]");for(let i of e)if(i.focus(),document.activeElement===i)break})},debouncedUpdateTabsWithinDropdown(){clearTimeout(this.resizeDebounceTimer),this.resizeDebounceTimer=setTimeout(()=>this.updateTabsWithinDropdown(),150)},async updateTabsWithinDropdown(){this.withinDropdownIndex=null,this.withinDropdownMounted=!1,await this.$nextTick();let t=this.$el.querySelector(".fi-tabs"),e=t.querySelector(".fi-tabs-item:last-child"),i=Array.from(t.children).slice(0,-1),d=i.map(s=>s.style.display);i.forEach(s=>s.style.display=""),t.offsetHeight;let c=this.calculateAvailableWidth(t),h=this.calculateContainerGap(t),u=this.calculateDropdownIconWidth(e),b=this.calculateTabItemGap(i[0]),n=this.calculateTabItemPadding(i[0]),o=this.findOverflowIndex(i,c,h,b,n,u);i.forEach((s,a)=>s.style.display=d[a]),o!==-1&&(this.withinDropdownIndex=o),this.withinDropdownMounted=!0},destroy(){this.unsubscribeLivewireHook?.(),this.boundResetHandler&&window.removeEventListener("reset-schema-component-state",this.boundResetHandler),this.boundResizeHandler&&window.removeEventListener("resize",this.boundResizeHandler),clearTimeout(this.resizeDebounceTimer)}}}export{x as default};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
function l({isSkippable:i,isStepPersistedInQueryString:n,key:o,livewireId:h,schemaKey:p,startStep:r,stepQueryStringKey:d}){return{boundResetHandler:null,step:null,init(){this.step=this.getSteps().at(r-1),this.$watch("step",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0),this.boundResetHandler=t=>{t.detail.livewireId!==h||t.detail.schemaKey!==p||n||this.$nextTick(()=>{this.step=this.getSteps().at(r-1)??this.step})},window.addEventListener("reset-schema-component-state",this.boundResetHandler)},async requestNextStep(){await this.$wire.callSchemaComponentMethod(o,"nextStep",{currentStepIndex:this.getStepIndex(this.step)})},goToNextStep(){let t=this.getStepIndex(this.step)+1;t>=this.getSteps().length||(this.step=this.getSteps()[t],this.scroll())},goToPreviousStep(){let t=this.getStepIndex(this.step)-1;t<0||(this.step=this.getSteps()[t],this.scroll())},goToStep(t){let e=this.getStepIndex(t);e<=-1||!i&&e>this.getStepIndex(this.step)||(this.step=t,this.scroll())},scroll(){this.$nextTick(()=>{this.$refs.header?.children[this.getStepIndex(this.step)].scrollIntoView({behavior:"smooth",block:"start"})})},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$refs[`step-${this.step}`]?.querySelectorAll("[autofocus]")??[];for(let s of e)if(s.focus(),document.activeElement===s)break})},getStepIndex(t){let e=this.getSteps().findIndex(s=>s===t);return e===-1?0:e},getSteps(){return JSON.parse(this.$refs.stepsData.value)},isFirstStep(){return this.getStepIndex(this.step)<=0},isLastStep(){return this.getStepIndex(this.step)+1>=this.getSteps().length},isStepAccessible(t){return i||this.getStepIndex(this.step)>this.getStepIndex(t)},updateQueryString(){if(!n)return;let t=new URL(window.location.href);t.searchParams.set(d,this.step),history.replaceState(null,document.title,t.toString())},destroy(){this.boundResetHandler&&window.removeEventListener("reset-schema-component-state",this.boundResetHandler)}}}export{l as default};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
(()=>{var c=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let i=this.$el.parentElement;i&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(i),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let i=this.$el.parentElement;if(!i)return;let e=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=i.offsetWidth+parseInt(e.marginInlineStart,10)*-1+parseInt(e.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});var u=function(i,e,n){let t=i;if(e.startsWith("/")&&(n=!0,e=e.slice(1)),n)return e;for(;e.startsWith("../");)t=t.includes(".")?t.slice(0,t.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(t)?e:["",null,void 0].includes(e)?t:`${t}.${e}`},h=i=>{let e=Alpine.findClosest(i,n=>n.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire},l=!1;document.addEventListener("invalid",i=>{let e=i.target;if(!e.closest("[data-field-wrapper]")||e.offsetParent!==null&&getComputedStyle(e).visibility!=="hidden"||(i.preventDefault(),l))return;l=!0;let n=e;for(;n;)n.dispatchEvent(new CustomEvent("expand")),n=n.parentNode;requestAnimationFrame(()=>requestAnimationFrame(()=>{e.form?.reportValidity(),setTimeout(()=>l=!1,100)}))},!0);document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:i,schemaKey:e,isLoadingDeferred:n=!1})=>({intersectionObserver:null,isLoadingDeferredSchema:!1,init(){n&&(this.intersectionObserver=new IntersectionObserver(async t=>{if(!(!t[0]?.isIntersecting||this.isLoadingDeferredSchema)){this.isLoadingDeferredSchema=!0;try{await this.$wire.loadDeferredSchema(e),this.intersectionObserver?.disconnect()}catch{}finally{this.isLoadingDeferredSchema=!1}}}),this.intersectionObserver.observe(this.$el))},destroy(){this.intersectionObserver?.disconnect()},handleFormValidationError(t){t.detail.livewireId===i&&this.$nextTick(()=>{let r=this.$el.querySelector("[data-validation-error]");if(!r)return;let s=r;for(;s;)s.dispatchEvent(new CustomEvent("expand")),s=s.parentNode;setTimeout(()=>r.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},handleClientSideStateReset(t){t.detail.livewireId!==i||t.detail.schemaKey!==e||this.$nextTick(()=>{let r=this.$el.querySelectorAll("[autofocus]");for(let s of r)if(s.offsetParent!==null&&(s.focus(),document.activeElement===s))break})},isStateChanged(t,r){if(t===void 0)return!1;try{return JSON.stringify(t)!==JSON.stringify(r)}catch{return t!==r}}})),window.Alpine.data("filamentSchemaComponent",({path:i,containerPath:e,$wire:n})=>({$statePath:i,$get:(t,r)=>n.$get(u(e,t,r)),$set:(t,r,s,o=!1)=>n.$set(u(e,t,s),r,o),get $state(){return n.$get(i)}})),window.Alpine.data("filamentActionsSchemaComponent",c),Livewire.hook("commit",({component:i,commit:e,respond:n,succeed:t,fail:r})=>{t(({snapshot:s,effects:o})=>{o.dispatches?.forEach(a=>{if(!a.params?.awaitSchemaComponent)return;let d=Array.from(i.el.querySelectorAll(`[wire\\:partial="schema-component::${a.params.awaitSchemaComponent}"]`)).filter(f=>h(f)===i);if(d.length!==1){if(d.length>1)throw`Multiple schema components found with key [${a.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${i.id}-${a.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(a.name,{detail:a.params}))},{once:!0})}})})})});})();
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
function o({name:r,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.hook("commit",({component:e,commit:i,succeed:a,fail:u,respond:h})=>{a(({snapshot:d,effect:f})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let i=await this.$wire.updateTableColumnState(r,s,this.state);this.error=i?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)},destroy(){this.unsubscribeLivewireHook?.()}}}export{o as default};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
function o({name:i,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.hook("commit",({component:e,commit:r,succeed:a,fail:u,respond:d})=>{a(({snapshot:h,effect:l})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||this.getNormalizedState()===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.getNormalizedState()),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[null,void 0].includes(this.$refs.serverState.value)?"":this.$refs.serverState.value.replaceAll('\\"','"')},getNormalizedState(){let e=Alpine.raw(this.state);return[null,void 0].includes(e)?"":e},destroy(){this.unsubscribeLivewireHook?.()}}}export{o as default};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
function o({name:r,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.hook("commit",({component:e,commit:i,succeed:a,fail:u,respond:h})=>{a(({snapshot:d,effect:f})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let i=await this.$wire.updateTableColumnState(r,s,this.state);this.error=i?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)},destroy(){this.unsubscribeLivewireHook?.()}}}export{o as default};
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: "Επικοινωνία"
|
||||||
|
slug: "contact"
|
||||||
|
|
||||||
|
meta_title: "Επικοινωνία"
|
||||||
|
meta_description: "Επικοινώνησε μαζί μας για οποιαδήποτε απορία ή ιδέα έχεις και θα σου απαντήσουμε το συντομότερο δυνατό."
|
||||||
|
og_image: ""
|
||||||
|
|
||||||
|
heading_first: "Επικοινώνησε"
|
||||||
|
heading_second: "μαζί μας"
|
||||||
|
heading_highlight: "άμεσα"
|
||||||
|
intro: "Έχεις απορία, ιδέα ή θες να μας πεις κάτι; Γράψε μας το μήνυμά σου και θα σου απαντήσουμε το συντομότερο δυνατό."
|
||||||
|
---
|
||||||
+6
-6
@@ -18,12 +18,12 @@ ## 1. Υπεύθυνος επεξεργασίας
|
|||||||
|
|
||||||
Υπεύθυνος επεξεργασίας των προσωπικών δεδομένων που συλλέγονται μέσω του ηλεκτρονικού καταστήματος είναι:
|
Υπεύθυνος επεξεργασίας των προσωπικών δεδομένων που συλλέγονται μέσω του ηλεκτρονικού καταστήματος είναι:
|
||||||
|
|
||||||
**3DEALER**
|
**{store_legal_name}**
|
||||||
Πάρου 31, Κυψέλη, ΤΚ 11255, Αθήνα
|
Πάρου 31, Κυψέλη, ΤΚ 11255, Αθήνα
|
||||||
ΑΦΜ: {store_tax_identifier}
|
ΑΦΜ: {store_tax_identifier}
|
||||||
ΓΕΜΗ: {store_registration_number}
|
ΓΕΜΗ: {store_registration_number}
|
||||||
Email: [print@3dealer.gr](mailto:print@3dealer.gr)
|
Email: [{store_contact_email}](mailto:{store_contact_email})
|
||||||
Τηλέφωνο: 6976 443140
|
Τηλέφωνο: {store_phone}
|
||||||
|
|
||||||
Η 3DEALER καθορίζει τους σκοπούς και τα μέσα επεξεργασίας των προσωπικών δεδομένων των πελατών και χρηστών του ηλεκτρονικού καταστήματος.
|
Η 3DEALER καθορίζει τους σκοπούς και τα μέσα επεξεργασίας των προσωπικών δεδομένων των πελατών και χρηστών του ηλεκτρονικού καταστήματος.
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ ## 6. Newsletter και εμπορική επικοινωνία
|
|||||||
|
|
||||||
Η εγγραφή στο newsletter πραγματοποιείται με τη συγκατάθεσή σας.
|
Η εγγραφή στο newsletter πραγματοποιείται με τη συγκατάθεσή σας.
|
||||||
|
|
||||||
Μπορείτε να ανακαλέσετε τη συγκατάθεσή σας οποιαδήποτε στιγμή, χρησιμοποιώντας τον σύνδεσμο απεγγραφής που περιλαμβάνεται στα emails ή επικοινωνώντας μαζί μας στο [print@3dealer.gr](mailto:print@3dealer.gr).
|
Μπορείτε να ανακαλέσετε τη συγκατάθεσή σας οποιαδήποτε στιγμή, χρησιμοποιώντας τον σύνδεσμο απεγγραφής που περιλαμβάνεται στα emails ή επικοινωνώντας μαζί μας στο [{store_contact_email}](mailto:{store_contact_email}).
|
||||||
|
|
||||||
Η ανάκληση της συγκατάθεσης δεν επηρεάζει τη νομιμότητα της επεξεργασίας που πραγματοποιήθηκε πριν από αυτήν.
|
Η ανάκληση της συγκατάθεσης δεν επηρεάζει τη νομιμότητα της επεξεργασίας που πραγματοποιήθηκε πριν από αυτήν.
|
||||||
|
|
||||||
@@ -186,8 +186,8 @@ ## 16. Τα δικαιώματά σας
|
|||||||
|
|
||||||
Για την άσκηση των δικαιωμάτων σας μπορείτε να επικοινωνήσετε μαζί μας:
|
Για την άσκηση των δικαιωμάτων σας μπορείτε να επικοινωνήσετε μαζί μας:
|
||||||
|
|
||||||
**Email:** [print@3dealer.gr](mailto:print@3dealer.gr)
|
**Email:** [{store_contact_email}](mailto:{store_contact_email})
|
||||||
**Τηλέφωνο:** 6976 443140
|
**Τηλέφωνο:** {store_phone}
|
||||||
|
|
||||||
Θα απαντήσουμε στο αίτημά σας χωρίς αδικαιολόγητη καθυστέρηση και, κατά κανόνα, εντός ενός (1) μήνα από την παραλαβή του.
|
Θα απαντήσουμε στο αίτημά σας χωρίς αδικαιολόγητη καθυστέρηση και, κατά κανόνα, εντός ενός (1) μήνα από την παραλαβή του.
|
||||||
|
|
||||||
+4
-4
@@ -45,7 +45,7 @@ ## 3. Χρόνος παράδοσης
|
|||||||
|
|
||||||
Η 3DEALER δεν ευθύνεται για καθυστερήσεις που οφείλονται αποκλειστικά στην εταιρεία μεταφοράς, σε ακραία καιρικά φαινόμενα, απεργίες, προβλήματα στο δίκτυο μεταφορών ή άλλες περιστάσεις που βρίσκονται εκτός του εύλογου ελέγχου της.
|
Η 3DEALER δεν ευθύνεται για καθυστερήσεις που οφείλονται αποκλειστικά στην εταιρεία μεταφοράς, σε ακραία καιρικά φαινόμενα, απεργίες, προβλήματα στο δίκτυο μεταφορών ή άλλες περιστάσεις που βρίσκονται εκτός του εύλογου ελέγχου της.
|
||||||
|
|
||||||
Σε περίπτωση καθυστέρησης, μπορείτε να επικοινωνήσετε μαζί μας στο [print@3dealer.gr](mailto:print@3dealer.gr).
|
Σε περίπτωση καθυστέρησης, μπορείτε να επικοινωνήσετε μαζί μας στο [{store_contact_email}](mailto:{store_contact_email}).
|
||||||
|
|
||||||
## 4. Παραλαβή παραγγελίας
|
## 4. Παραλαβή παραγγελίας
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ ## 4. Παραλαβή παραγγελίας
|
|||||||
|
|
||||||
## 5. Προϊόν που παραδόθηκε κατεστραμμένο
|
## 5. Προϊόν που παραδόθηκε κατεστραμμένο
|
||||||
|
|
||||||
Σε περίπτωση που το προϊόν παραδοθεί εμφανώς κατεστραμμένο, παρακαλούμε να επικοινωνήσετε μαζί μας το συντομότερο δυνατό στο [print@3dealer.gr](mailto:print@3dealer.gr) και να μας αποστείλετε φωτογραφίες του προϊόντος και της συσκευασίας.
|
Σε περίπτωση που το προϊόν παραδοθεί εμφανώς κατεστραμμένο, παρακαλούμε να επικοινωνήσετε μαζί μας το συντομότερο δυνατό στο [{store_contact_email}](mailto:{store_contact_email}) και να μας αποστείλετε φωτογραφίες του προϊόντος και της συσκευασίας.
|
||||||
|
|
||||||
Η 3DEALER θα εξετάσει το περιστατικό και, εφόσον διαπιστωθεί ζημιά που προέκυψε κατά τη μεταφορά ή άλλη έλλειψη συμμόρφωσης, θα προχωρήσει στην κατάλληλη λύση σύμφωνα με την ισχύουσα νομοθεσία.
|
Η 3DEALER θα εξετάσει το περιστατικό και, εφόσον διαπιστωθεί ζημιά που προέκυψε κατά τη μεταφορά ή άλλη έλλειψη συμμόρφωσης, θα προχωρήσει στην κατάλληλη λύση σύμφωνα με την ισχύουσα νομοθεσία.
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ ## 6. Δικαίωμα υπαναχώρησης
|
|||||||
|
|
||||||
Για τις αγορές που εμπίπτουν στο πεδίο εφαρμογής του δικαιώματος υπαναχώρησης, ο καταναλωτής μπορεί να υπαναχωρήσει από τη σύμβαση εντός δεκατεσσάρων (14) ημερολογιακών ημερών από την ημέρα παραλαβής του προϊόντος.
|
Για τις αγορές που εμπίπτουν στο πεδίο εφαρμογής του δικαιώματος υπαναχώρησης, ο καταναλωτής μπορεί να υπαναχωρήσει από τη σύμβαση εντός δεκατεσσάρων (14) ημερολογιακών ημερών από την ημέρα παραλαβής του προϊόντος.
|
||||||
|
|
||||||
Για την άσκηση του δικαιώματος υπαναχώρησης, ο πελάτης πρέπει να ενημερώσει την 3DEALER με σαφή δήλωση στο [print@3dealer.gr](mailto:print@3dealer.gr).
|
Για την άσκηση του δικαιώματος υπαναχώρησης, ο πελάτης πρέπει να ενημερώσει την 3DEALER με σαφή δήλωση στο [{store_contact_email}](mailto:{store_contact_email}).
|
||||||
|
|
||||||
Το προϊόν πρέπει να επιστραφεί χωρίς αδικαιολόγητη καθυστέρηση και, σε κάθε περίπτωση, εντός δεκατεσσάρων (14) ημερών από την ημέρα κατά την οποία ο πελάτης ενημέρωσε την 3DEALER για την απόφασή του να υπαναχωρήσει.
|
Το προϊόν πρέπει να επιστραφεί χωρίς αδικαιολόγητη καθυστέρηση και, σε κάθε περίπτωση, εντός δεκατεσσάρων (14) ημερών από την ημέρα κατά την οποία ο πελάτης ενημέρωσε την 3DEALER για την απόφασή του να υπαναχωρήσει.
|
||||||
|
|
||||||
@@ -101,4 +101,4 @@ ## 10. Ελαττωματικά ή μη συμμορφούμενα προϊόν
|
|||||||
|
|
||||||
Η ύπαρξη μικρών αισθητικών χαρακτηριστικών που είναι φυσιολογικά για την τρισδιάστατη εκτύπωση, όπως γραμμές στρώσεων ή μικρές διαφοροποιήσεις, δεν αποτελεί από μόνη της ελάττωμα.
|
Η ύπαρξη μικρών αισθητικών χαρακτηριστικών που είναι φυσιολογικά για την τρισδιάστατη εκτύπωση, όπως γραμμές στρώσεων ή μικρές διαφοροποιήσεις, δεν αποτελεί από μόνη της ελάττωμα.
|
||||||
|
|
||||||
Για οποιοδήποτε πρόβλημα με προϊόν, επικοινωνήστε μαζί μας στο [print@3dealer.gr](mailto:print@3dealer.gr), αναφέροντας τον αριθμό παραγγελίας και, όπου είναι δυνατόν, επισυνάπτοντας φωτογραφίες.
|
Για οποιοδήποτε πρόβλημα με προϊόν, επικοινωνήστε μαζί μας στο [{store_contact_email}](mailto:{store_contact_email}), αναφέροντας τον αριθμό παραγγελίας και, όπου είναι δυνατόν, επισυνάπτοντας φωτογραφίες.
|
||||||
+5
-7
@@ -1,25 +1,23 @@
|
|||||||
---
|
---
|
||||||
name: "Όροι & Προϋποθέσεις"
|
name: "Όροι & Προϋποθέσεις Terms"
|
||||||
slug: "terms-and-conditions"
|
slug: "terms-and-conditions"
|
||||||
|
|
||||||
meta_title: ""
|
meta_title: ""
|
||||||
meta_description: ""
|
meta_description: ""
|
||||||
og_image: ""
|
og_image: ""
|
||||||
noindex: true
|
noindex: true
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Γενικά
|
## 1. Γενικά
|
||||||
|
|
||||||
Το ηλεκτρονικό κατάστημα 3DEALER (εφεξής «το Κατάστημα» ή «3DEALER») λειτουργεί από την επιχείρηση 3DEALER, με έδρα στην Πάρου 31, Κυψέλη, ΤΚ 11255, Αθήνα.
|
Το ηλεκτρονικό κατάστημα 3DEALER (εφεξής «το Κατάστημα» ή «3DEALER») λειτουργεί από την επιχείρηση {store_legal_name}, με έδρα στην Πάρου 31, Κυψέλη, ΤΚ 11255, Αθήνα.
|
||||||
|
|
||||||
**ΑΦΜ:** {store_tax_identifier}
|
**ΑΦΜ:** {store_tax_identifier}\
|
||||||
**ΓΕΜΗ:** {store_registration_number}
|
**ΓΕΜΗ:** {store_registration_number}
|
||||||
|
|
||||||
Για οποιαδήποτε πληροφορία ή επικοινωνία σχετικά με παραγγελίες, προϊόντα ή τη λειτουργία του Καταστήματος, μπορείτε να επικοινωνείτε μαζί μας:
|
Για οποιαδήποτε πληροφορία ή επικοινωνία σχετικά με παραγγελίες, προϊόντα ή τη λειτουργία του Καταστήματος, μπορείτε να επικοινωνείτε μαζί μας:
|
||||||
|
|
||||||
**Email:** [print@3dealer.gr](mailto:print@3dealer.gr)
|
**Email:** [{store_contact_email}](mailto:{store_contact_email})\
|
||||||
**Τηλέφωνο:** 6976 443140
|
**Τηλέφωνο:** {store_phone}
|
||||||
|
|
||||||
Η χρήση του Καταστήματος και η πραγματοποίηση αγορών μέσω αυτού προϋποθέτουν την αποδοχή των παρόντων Όρων & Προϋποθέσεων.
|
Η χρήση του Καταστήματος και η πραγματοποίηση αγορών μέσω αυτού προϋποθέτουν την αποδοχή των παρόντων Όρων & Προϋποθέσεων.
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
meta_description: "Your local 3Dealer of 3D printed, high-quality products. Geek culture, decoration, fun and skulls, and custom orders."
|
meta_description: ""
|
||||||
og_image: ""
|
og_image: ""
|
||||||
facebook_url: "https://www.facebook.com/3Dealer.gr/"
|
facebook_url: "https://www.facebook.com/3Dealer.gr/"
|
||||||
instagram_url: "https://www.instagram.com/3dealer.gr/"
|
instagram_url: "https://www.instagram.com/3dealer.gr/"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
name: "Contact"
|
||||||
|
meta_title: "Contact"
|
||||||
|
meta_description: "Get in touch with any question or idea you have and we'll get back to you as soon as we can."
|
||||||
|
heading_first: "Contact"
|
||||||
|
heading_second: "us"
|
||||||
|
heading_highlight: "today"
|
||||||
|
intro: "Got a question, an idea or something to tell us? Send us your message and we'll get back to you as soon as we can."
|
||||||
|
---
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
name: "Cookie Policy"
|
||||||
|
meta_title: ""
|
||||||
|
meta_description: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
This Cookie Policy explains how 3DEALER uses cookies and similar technologies when you visit the online store.
|
||||||
|
|
||||||
|
## 1. What cookies are
|
||||||
|
|
||||||
|
Cookies are small text files stored on your device when you visit a website. They are used to make the website work properly, store preferences, collect statistics and, where permitted, show or measure advertising.
|
||||||
|
|
||||||
|
Besides cookies, we may also use similar technologies, such as pixels and tags.
|
||||||
|
|
||||||
|
## 2. Which cookies we use
|
||||||
|
|
||||||
|
3DEALER uses different categories of cookies, depending on their purpose.
|
||||||
|
|
||||||
|
### Essential cookies
|
||||||
|
|
||||||
|
Essential cookies are those required for the online store to work correctly and securely.
|
||||||
|
|
||||||
|
They can be used, among other things, for:
|
||||||
|
|
||||||
|
* running the shopping cart,
|
||||||
|
* keeping your session,
|
||||||
|
* signing in to your account,
|
||||||
|
* completing checkout,
|
||||||
|
* keeping the website secure,
|
||||||
|
* storing your cookie choices.
|
||||||
|
|
||||||
|
These cookies cannot be turned off through the consent tool, because without them some basic functions of the online store would not be available.
|
||||||
|
|
||||||
|
### Functionality cookies
|
||||||
|
|
||||||
|
Functionality cookies are used to store choices and preferences that improve your experience of the online store.
|
||||||
|
|
||||||
|
**@TODO: List the specific functionality cookies used by Boboko/3DEALER.**
|
||||||
|
|
||||||
|
### Analytics cookies
|
||||||
|
|
||||||
|
Analytics cookies are used to understand how visitors use the online store.
|
||||||
|
|
||||||
|
3DEALER uses services such as **Google Analytics** and may use **Google Tag Manager** to manage the related tags.
|
||||||
|
|
||||||
|
The information collected may include details such as:
|
||||||
|
|
||||||
|
* which pages you visit,
|
||||||
|
* how long you stay on the website,
|
||||||
|
* which page you came from,
|
||||||
|
* the device and browser you use,
|
||||||
|
* general information about how you interact with the website.
|
||||||
|
|
||||||
|
These cookies are only activated in line with your consent choices, where required by applicable law.
|
||||||
|
|
||||||
|
### Advertising and marketing cookies
|
||||||
|
|
||||||
|
3DEALER uses advertising measurement and marketing technologies, such as:
|
||||||
|
|
||||||
|
* **Meta Pixel**
|
||||||
|
* **TikTok Pixel**
|
||||||
|
|
||||||
|
These technologies can be used to measure how effective advertising is, to understand interactions with ads and, where supported and permitted, to show more relevant ads.
|
||||||
|
|
||||||
|
The related cookies and pixels are only activated in line with your consent choices, where required.
|
||||||
|
|
||||||
|
**@TODO: Confirm the exact Meta/TikTok cookies and events used in production.**
|
||||||
|
|
||||||
|
## 3. Cookie table
|
||||||
|
|
||||||
|
The table below should be updated based on the cookies actually used in the production environment.
|
||||||
|
|
||||||
|
| Cookie / Technology | Provider | Category | Purpose | Duration |
|
||||||
|
| ------------------- | ---------------- | --------- | ------------------------------------------- | -------- |
|
||||||
|
| @TODO | Boboko / 3DEALER | Essential | Store operation | @TODO |
|
||||||
|
| @TODO | Boboko / 3DEALER | Essential | Cart / checkout | @TODO |
|
||||||
|
| @TODO | Google | Analytics | Usage statistics | @TODO |
|
||||||
|
| @TODO | Meta | Marketing | Advertising measurement | @TODO |
|
||||||
|
| @TODO | TikTok | Marketing | Advertising measurement | @TODO |
|
||||||
|
| @TODO | hCaptcha | Security | Protection against automated/malicious use | @TODO |
|
||||||
|
|
||||||
|
**@TODO: The table must be completed after checking the production site, ideally with a cookie scan.**
|
||||||
|
|
||||||
|
## 4. Consent to the use of cookies
|
||||||
|
|
||||||
|
On your first visit to the online store, a cookie consent tool is shown, where you can choose which categories of non-essential cookies you allow.
|
||||||
|
|
||||||
|
You can:
|
||||||
|
|
||||||
|
* accept all cookies,
|
||||||
|
* reject non-essential cookies,
|
||||||
|
* choose specific categories of cookies.
|
||||||
|
|
||||||
|
Essential cookies stay active, as they are needed for the online store to work.
|
||||||
|
|
||||||
|
Your choice is saved and applied on your future visits.
|
||||||
|
|
||||||
|
## 5. Withdrawing or changing your consent
|
||||||
|
|
||||||
|
You can change or withdraw your consent to the use of non-essential cookies at any time.
|
||||||
|
|
||||||
|
**@TODO: Add how Boboko's cookie preference centre is reopened, e.g. through a "Cookie settings" link in the footer.**
|
||||||
|
|
||||||
|
Withdrawing consent does not affect the lawfulness of processing carried out before the withdrawal.
|
||||||
|
|
||||||
|
## 6. Third-party cookies
|
||||||
|
|
||||||
|
Some of the services we use are provided by third parties, such as Google, Meta, TikTok and hCaptcha.
|
||||||
|
|
||||||
|
These third-party providers may process information collected through cookies or similar technologies in accordance with their own policies and terms.
|
||||||
|
|
||||||
|
Using third-party services may also involve transferring data outside the European Economic Area. Where required, the safeguards provided by law for such transfers are applied.
|
||||||
|
|
||||||
|
## 7. Cookies and personal data
|
||||||
|
|
||||||
|
Some cookies or similar technologies may lead to the processing of information that counts as personal data.
|
||||||
|
|
||||||
|
For more information on how 3DEALER collects and processes personal data, see the **Privacy Policy**.
|
||||||
|
|
||||||
|
## 8. Browser settings
|
||||||
|
|
||||||
|
You can also limit or delete cookies through your browser settings.
|
||||||
|
|
||||||
|
However, turning off some cookies may affect how the online store works or prevent some services from working properly.
|
||||||
|
|
||||||
|
## 9. Changes to this Cookie Policy
|
||||||
|
|
||||||
|
This Cookie Policy may be changed when the technologies we use, the services of the online store or the relevant legislation change.
|
||||||
|
|
||||||
|
The latest version will always be available on the 3DEALER website.
|
||||||
|
|
||||||
|
**Last updated:** @TODO
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
name: "Home"
|
||||||
|
meta_title: "3dealer: Whatever you imagine, printed in 3D"
|
||||||
|
meta_description: "Gaming figures, skulls, key holders and the craziest things you can dream up. Designed and 3D printed, one by one."
|
||||||
|
trivia_body: "Gaming figures, skulls, key holders, dark humor and the craziest things you can dream up, all designed and printed right here in Greece."
|
||||||
|
classics_title: "Legendary & unbeatable"
|
||||||
|
---
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
---
|
||||||
|
name: "Privacy Policy"
|
||||||
|
meta_title: ""
|
||||||
|
meta_description: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
3DEALER ("3DEALER", "we", "us") respects the privacy of its visitors and customers and protects their personal data in accordance with applicable law, including the General Data Protection Regulation (EU) 2016/679 ("GDPR") and the related Greek legislation.
|
||||||
|
|
||||||
|
This Privacy Policy explains what personal data we collect, what we use it for, who we may share it with and what rights you have.
|
||||||
|
|
||||||
|
## 1. Data controller
|
||||||
|
|
||||||
|
The controller of the personal data collected through the online store is:
|
||||||
|
|
||||||
|
**{store_legal_name}**
|
||||||
|
Parou 31, Kypseli, 11255 Athens, Greece
|
||||||
|
Tax ID (ΑΦΜ): {store_tax_identifier}
|
||||||
|
Company registration number (ΓΕΜΗ): {store_registration_number}
|
||||||
|
Email: [{store_contact_email}](mailto:{store_contact_email})
|
||||||
|
Phone: {store_phone}
|
||||||
|
|
||||||
|
3DEALER determines the purposes and means of processing the personal data of the online store's customers and users.
|
||||||
|
|
||||||
|
## 2. What data we collect
|
||||||
|
|
||||||
|
Depending on how you use the online store, we may collect:
|
||||||
|
|
||||||
|
* your full name,
|
||||||
|
* your email address,
|
||||||
|
* your phone number,
|
||||||
|
* your shipping and billing address,
|
||||||
|
* order and product details,
|
||||||
|
* payment-related details,
|
||||||
|
* contact details and the content of messages you send us,
|
||||||
|
* user account details,
|
||||||
|
* reviews you choose to publish,
|
||||||
|
* technical data about your use of the website,
|
||||||
|
* data collected through cookies and similar technologies, as described in the Cookie Policy.
|
||||||
|
|
||||||
|
We do not store your full credit or debit card details on 3DEALER's servers. Electronic payments are made through external payment providers.
|
||||||
|
|
||||||
|
## 3. Creating an account
|
||||||
|
|
||||||
|
You can create an account in the online store to manage your orders and details.
|
||||||
|
|
||||||
|
To create and run your account, we may process details such as your name, email and login details.
|
||||||
|
|
||||||
|
This processing is necessary to provide the account service.
|
||||||
|
|
||||||
|
The online store also supports guest checkout. With guest checkout, no user account is created. The details you provide are used only to the extent necessary to process and complete the order and for the related legal obligations.
|
||||||
|
|
||||||
|
## 4. Orders and purchases
|
||||||
|
|
||||||
|
When you place an order, we process the details needed to:
|
||||||
|
|
||||||
|
* record and fulfil the order,
|
||||||
|
* process the payment,
|
||||||
|
* ship and deliver the products,
|
||||||
|
* contact you about the order,
|
||||||
|
* issue the necessary invoices and receipts,
|
||||||
|
* meet our tax and accounting obligations,
|
||||||
|
* handle any returns, cancellations or complaints.
|
||||||
|
|
||||||
|
The legal basis for this processing is, as the case may be, the performance of the contract, compliance with a legal obligation and 3DEALER's legitimate interest in the proper operation and protection of its business.
|
||||||
|
|
||||||
|
## 5. Contacting us
|
||||||
|
|
||||||
|
If you contact us by email, phone or through the contact form, we process the details you give us in order to respond to your request.
|
||||||
|
|
||||||
|
The legal basis is, depending on the case, taking steps at your request before entering into a contract, the performance of a contract, or our legitimate interest in serving our users and customers.
|
||||||
|
|
||||||
|
## 6. Newsletter and marketing communication
|
||||||
|
|
||||||
|
If you choose to subscribe to the 3DEALER newsletter, we use your email address to send you updates, offers and other marketing content.
|
||||||
|
|
||||||
|
Subscribing to the newsletter is based on your consent.
|
||||||
|
|
||||||
|
You can withdraw your consent at any time by using the unsubscribe link included in the emails or by contacting us at [{store_contact_email}](mailto:{store_contact_email}).
|
||||||
|
|
||||||
|
Withdrawing consent does not affect the lawfulness of processing carried out before the withdrawal.
|
||||||
|
|
||||||
|
## 7. Product reviews
|
||||||
|
|
||||||
|
If you choose to submit a product review, we may publish your name or another identifier you chose to display with the review.
|
||||||
|
|
||||||
|
Please do not include your own or other people's personal data in a public review unless it is necessary.
|
||||||
|
|
||||||
|
## 8. Payment providers
|
||||||
|
|
||||||
|
To complete payments we may use third-party providers such as Stripe and PayPal, as well as banks for payments by bank transfer or IRIS.
|
||||||
|
|
||||||
|
The data required to complete a payment may be passed to the relevant payment provider.
|
||||||
|
|
||||||
|
**@TODO: Confirm the exact payment providers/banks that will be used in production and add them to the final version.**
|
||||||
|
|
||||||
|
These providers process data in accordance with their own privacy policies and applicable terms.
|
||||||
|
|
||||||
|
## 9. Shipping and delivery companies
|
||||||
|
|
||||||
|
To ship your orders we use shipping and delivery services, including ELTA Courier and BOX NOW.
|
||||||
|
|
||||||
|
To deliver an order, details such as your full name, address, phone number and other details needed for delivery may be passed to the relevant provider.
|
||||||
|
|
||||||
|
## 10. Accounting and tax services
|
||||||
|
|
||||||
|
Order details and the necessary customer details may be passed to the Epsilon system that 3DEALER uses to issue and manage invoices and receipts and to meet its tax and accounting obligations.
|
||||||
|
|
||||||
|
## 11. Security and abuse protection
|
||||||
|
|
||||||
|
We use technical and organizational measures to protect personal data against loss, unauthorized access, alteration or unlawful processing.
|
||||||
|
|
||||||
|
To protect the website against malicious or automated use, we may use the hCaptcha service. Using this service may involve processing technical data in accordance with the relevant provider's privacy policy.
|
||||||
|
|
||||||
|
## 12. Analytics and advertising services
|
||||||
|
|
||||||
|
3DEALER uses traffic analytics and advertising measurement services, such as:
|
||||||
|
|
||||||
|
* Google Analytics
|
||||||
|
* Google Tag Manager
|
||||||
|
* Meta Pixel
|
||||||
|
* TikTok Pixel
|
||||||
|
|
||||||
|
These technologies may use cookies or similar technologies to collect information about how the website is used and how effective advertising is.
|
||||||
|
|
||||||
|
Non-essential technologies are only activated in line with the consent choices you make through the cookie management tool.
|
||||||
|
|
||||||
|
More information is available in the Cookie Policy.
|
||||||
|
|
||||||
|
**@TODO: Confirm the exact tools, accounts and services that will be active in production.**
|
||||||
|
|
||||||
|
## 13. Who may have access to your data
|
||||||
|
|
||||||
|
3DEALER does not sell or rent your personal data.
|
||||||
|
|
||||||
|
To the extent necessary to provide the relevant services, the following may have access to personal data:
|
||||||
|
|
||||||
|
* payment providers,
|
||||||
|
* courier companies,
|
||||||
|
* hosting and technical support providers,
|
||||||
|
* email/newsletter service providers,
|
||||||
|
* analytics and advertising providers,
|
||||||
|
* accounting and tax services,
|
||||||
|
* public authorities and bodies, where required by law.
|
||||||
|
|
||||||
|
Third-party providers that process data on behalf of 3DEALER are used in accordance with the requirements of applicable data protection law.
|
||||||
|
|
||||||
|
## 14. Transfers outside the European Economic Area
|
||||||
|
|
||||||
|
Some of the providers we use may process data outside the European Economic Area.
|
||||||
|
|
||||||
|
In these cases, 3DEALER takes appropriate measures to ensure the transfer complies with the GDPR and that personal data receives the required level of protection.
|
||||||
|
|
||||||
|
**@TODO: Check the exact processing countries and transfer mechanisms for Stripe, PayPal, Google, Meta, TikTok, hCaptcha and the newsletter provider.**
|
||||||
|
|
||||||
|
## 15. Retention period
|
||||||
|
|
||||||
|
We keep personal data only for as long as necessary for the purpose it was collected for.
|
||||||
|
|
||||||
|
Data relating to orders and transactions is kept for as long as required by tax, accounting and commercial law.
|
||||||
|
|
||||||
|
Account data is kept for as long as the account remains active, unless there is a legal reason to keep it longer.
|
||||||
|
|
||||||
|
Data used for the newsletter is kept until you withdraw your consent or unsubscribe, unless there is another legal reason to keep it.
|
||||||
|
|
||||||
|
Contact data is kept for as long as necessary to handle your request and, where required, to protect our legitimate interests.
|
||||||
|
|
||||||
|
## 16. Your rights
|
||||||
|
|
||||||
|
Under the GDPR, and subject to the conditions set out in the law, you have the right to:
|
||||||
|
|
||||||
|
* access your personal data,
|
||||||
|
* correct inaccurate or incomplete data,
|
||||||
|
* have your data erased,
|
||||||
|
* restrict processing,
|
||||||
|
* data portability,
|
||||||
|
* object to certain types of processing,
|
||||||
|
* withdraw your consent, where processing is based on consent.
|
||||||
|
|
||||||
|
Exercising a right does not mean it can be applied in every case. For example, we may be required to keep certain data because of a tax or other legal obligation.
|
||||||
|
|
||||||
|
To exercise your rights, you can contact us:
|
||||||
|
|
||||||
|
**Email:** [{store_contact_email}](mailto:{store_contact_email})
|
||||||
|
**Phone:** {store_phone}
|
||||||
|
|
||||||
|
We will respond to your request without undue delay and, as a rule, within one (1) month of receiving it.
|
||||||
|
|
||||||
|
## 17. Right to lodge a complaint
|
||||||
|
|
||||||
|
If you believe the processing of your personal data breaches applicable law, you have the right to lodge a complaint with the Hellenic Data Protection Authority.
|
||||||
|
|
||||||
|
## 18. Changes to this Privacy Policy
|
||||||
|
|
||||||
|
This Privacy Policy may be changed when necessary, for example due to changes in legislation, in the services we use or in the way the online store operates.
|
||||||
|
|
||||||
|
The latest version will always be available on our website.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
name: "Shipping & Returns"
|
||||||
|
meta_title: ""
|
||||||
|
meta_description: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Order preparation
|
||||||
|
|
||||||
|
All 3DEALER products are printed and prepared to order.
|
||||||
|
|
||||||
|
The usual preparation time for an order is **2 to 7 business days**.
|
||||||
|
|
||||||
|
Preparation time does not include the courier's transit time.
|
||||||
|
|
||||||
|
During periods of high demand there may be delays. In the event of a significant delay, 3DEALER will inform the customer.
|
||||||
|
|
||||||
|
## 2. Shipping
|
||||||
|
|
||||||
|
Orders are shipped via:
|
||||||
|
|
||||||
|
* ELTA Courier
|
||||||
|
* BOX NOW
|
||||||
|
|
||||||
|
We ship to the regions supported by the online store:
|
||||||
|
|
||||||
|
* Greece
|
||||||
|
* Cyprus
|
||||||
|
|
||||||
|
The available shipping options and costs are shown at checkout.
|
||||||
|
|
||||||
|
**Shipping costs:** @TODO — shipping rates are set dynamically in the Store's settings.
|
||||||
|
|
||||||
|
Free shipping is not offered.
|
||||||
|
|
||||||
|
Cash on delivery is not supported.
|
||||||
|
|
||||||
|
## 3. Delivery time
|
||||||
|
|
||||||
|
Delivery time depends on the courier and the destination.
|
||||||
|
|
||||||
|
3DEALER is not responsible for delays caused solely by the carrier, extreme weather, strikes, problems in the transport network or other circumstances beyond its reasonable control.
|
||||||
|
|
||||||
|
In case of a delay, you can contact us at [{store_contact_email}](mailto:{store_contact_email}).
|
||||||
|
|
||||||
|
## 4. Receiving your order
|
||||||
|
|
||||||
|
The customer is responsible for the accuracy of the delivery details entered when placing the order.
|
||||||
|
|
||||||
|
If the address is incorrect or incomplete, delivery may be delayed or a new shipment may be required. Any additional costs arising from incorrect delivery details may be charged to the customer.
|
||||||
|
|
||||||
|
## 5. Product delivered damaged
|
||||||
|
|
||||||
|
If the product arrives visibly damaged, please contact us as soon as possible at [{store_contact_email}](mailto:{store_contact_email}) and send us photos of the product and the packaging.
|
||||||
|
|
||||||
|
3DEALER will look into the case and, if damage that occurred in transit or another non-conformity is found, will provide an appropriate remedy in accordance with applicable law.
|
||||||
|
|
||||||
|
## 6. Right of withdrawal
|
||||||
|
|
||||||
|
For purchases that fall within the scope of the right of withdrawal, the consumer may withdraw from the contract within fourteen (14) calendar days of the day the product was received.
|
||||||
|
|
||||||
|
To exercise the right of withdrawal, the customer must inform 3DEALER with a clear statement at [{store_contact_email}](mailto:{store_contact_email}).
|
||||||
|
|
||||||
|
The product must be returned without undue delay and, in any case, within fourteen (14) days of the day the customer informed 3DEALER of their decision to withdraw.
|
||||||
|
|
||||||
|
The customer bears the direct cost of returning the product, unless otherwise agreed or 3DEALER did not inform them of this beforehand.
|
||||||
|
|
||||||
|
The customer is responsible for any reduction in the product's value resulting from handling beyond what is necessary to establish its nature, characteristics and functioning.
|
||||||
|
|
||||||
|
## 7. Exceptions to the right of withdrawal
|
||||||
|
|
||||||
|
The right of withdrawal does not apply in the cases excluded by applicable law.
|
||||||
|
|
||||||
|
In particular, it may not apply to products made to the customer's specifications or clearly personalized.
|
||||||
|
|
||||||
|
**@TODO: Confirm which 3DEALER products are considered personalized/custom and whether any products are not subject to the right of withdrawal.**
|
||||||
|
|
||||||
|
The fact that a product is produced after the order is placed does not in itself exclude it from the right of withdrawal.
|
||||||
|
|
||||||
|
## 8. Refunds
|
||||||
|
|
||||||
|
In the case of a valid withdrawal, 3DEALER refunds the money received from the customer for the purchase, including, where required by law, the standard delivery costs.
|
||||||
|
|
||||||
|
The refund is made using the same payment method as the original transaction, unless otherwise agreed.
|
||||||
|
|
||||||
|
3DEALER may withhold the refund until it has received the returned product or until the customer has provided proof of having sent it, whichever comes first.
|
||||||
|
|
||||||
|
## 9. Product exchanges
|
||||||
|
|
||||||
|
**@TODO: Confirm whether 3DEALER offers product/size exchanges or whether exchanges are handled only through a return and a new order.**
|
||||||
|
|
||||||
|
Where exchanges are offered, the procedure and any shipping costs will be communicated to the customer before the exchange is completed.
|
||||||
|
|
||||||
|
## 10. Defective or non-conforming products
|
||||||
|
|
||||||
|
If a product is defective or does not conform to the contract, the customer has the rights provided by applicable legislation on the sale of goods and consumer protection.
|
||||||
|
|
||||||
|
Minor cosmetic characteristics that are normal for 3D printing, such as layer lines or small variations, do not in themselves constitute a defect.
|
||||||
|
|
||||||
|
For any problem with a product, contact us at [{store_contact_email}](mailto:{store_contact_email}), stating your order number and, where possible, attaching photos.
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
---
|
||||||
|
name: "Terms & Conditions"
|
||||||
|
meta_title: ""
|
||||||
|
meta_description: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. General
|
||||||
|
|
||||||
|
The online store 3DEALER (hereinafter "the Store" or "3DEALER") is operated by the business {store_legal_name}, registered at Parou 31, Kypseli, 11255 Athens, Greece.
|
||||||
|
|
||||||
|
**Tax ID (ΑΦΜ):** {store_tax_identifier}\
|
||||||
|
**Company registration number (ΓΕΜΗ):** {store_registration_number}
|
||||||
|
|
||||||
|
For any information or questions about orders, products or the operation of the Store, you can contact us:
|
||||||
|
|
||||||
|
**Email:** [{store_contact_email}](mailto:{store_contact_email})\
|
||||||
|
**Phone:** {store_phone}
|
||||||
|
|
||||||
|
Using the Store and making purchases through it requires acceptance of these Terms & Conditions.
|
||||||
|
|
||||||
|
These terms govern purchases made through the online store and are supplemented by the Privacy Policy and the Shipping & Returns Policy.
|
||||||
|
|
||||||
|
## 2. Products
|
||||||
|
|
||||||
|
The products offered through the Store are described and pictured as accurately as possible.
|
||||||
|
|
||||||
|
3DEALER products are made by 3D printing. Due to the nature of the production process, they may show layer lines, seams, small variations or other minor cosmetic imperfections. These characteristics are a normal result of the 3D printing process and are not in themselves considered a product defect.
|
||||||
|
|
||||||
|
All products are printed and prepared to order. The usual preparation time is 2 to 7 business days.
|
||||||
|
|
||||||
|
Unless otherwise stated in the description of a specific product, products are intended for decorative use or for use within reasonable and realistic limits. Use with excessive force, at very high temperatures or in a way other than intended may damage the product.
|
||||||
|
|
||||||
|
## 3. Prices
|
||||||
|
|
||||||
|
All product prices are shown in euros (€) and include the applicable VAT.
|
||||||
|
|
||||||
|
Shipping costs are calculated at checkout and shown before the purchase is confirmed.
|
||||||
|
|
||||||
|
**Shipping costs:** @TODO — the exact cost is calculated dynamically based on the Store's shipping settings.
|
||||||
|
|
||||||
|
## 4. Orders
|
||||||
|
|
||||||
|
Customers can make a purchase either by creating an account in the Store or by using guest checkout, without creating an account.
|
||||||
|
|
||||||
|
Completing an order requires the details necessary for processing, payment and delivery.
|
||||||
|
|
||||||
|
Before finalizing the order, the customer can review the order details and the total cost, including any shipping charges.
|
||||||
|
|
||||||
|
Once the order is completed, a confirmation is sent to the email address provided by the customer.
|
||||||
|
|
||||||
|
**@TODO: Confirm whether the order is considered final once payment is completed or once the order confirmation is sent.**
|
||||||
|
|
||||||
|
## 5. Payment methods
|
||||||
|
|
||||||
|
The Store supports the following payment methods:
|
||||||
|
|
||||||
|
* Credit or debit card
|
||||||
|
* PayPal
|
||||||
|
* Stripe
|
||||||
|
* IRIS
|
||||||
|
* Bank transfer
|
||||||
|
|
||||||
|
Electronic payments may use third-party payment services. Card details or other sensitive payment details are not stored by 3DEALER, but are processed by the relevant payment provider in accordance with its own terms and privacy policy.
|
||||||
|
|
||||||
|
## 6. Order cancellations
|
||||||
|
|
||||||
|
**@TODO: Confirm the cancellation policy.**
|
||||||
|
|
||||||
|
As a general rule, customers who wish to cancel an order can contact 3DEALER as soon as possible after placing it.
|
||||||
|
|
||||||
|
If preparation of the order has not yet started, 3DEALER can cancel it and refund the amount paid.
|
||||||
|
|
||||||
|
If production has already started, the terms of the Shipping & Returns Policy apply, together with the statutory right of withdrawal where applicable.
|
||||||
|
|
||||||
|
## 7. Right of withdrawal and returns
|
||||||
|
|
||||||
|
Where provided for by applicable law, consumers have the right to withdraw from a distance contract within fourteen (14) calendar days of receiving the product, without having to give a reason.
|
||||||
|
|
||||||
|
Exceptions to the right of withdrawal apply in the cases provided for by applicable law, including for products made to the consumer's specifications or clearly personalized.
|
||||||
|
|
||||||
|
The returns procedure and terms are described in detail in the Shipping & Returns Policy.
|
||||||
|
|
||||||
|
## 8. Legal guarantee and defective products
|
||||||
|
|
||||||
|
3DEALER is responsible for the conformity of products with the contract and for the consumer's statutory rights in the event of non-conformity or a defect, in accordance with applicable law.
|
||||||
|
|
||||||
|
Normal characteristics of 3D printing, such as layer lines or small cosmetic variations that are expected from this production process, do not in themselves constitute non-conformity.
|
||||||
|
|
||||||
|
## 9. Availability
|
||||||
|
|
||||||
|
3DEALER makes every effort to keep product availability information accurate.
|
||||||
|
|
||||||
|
In exceptional cases where a product cannot be produced or shipped, 3DEALER will contact the customer and explain the available options.
|
||||||
|
|
||||||
|
## 10. Intellectual property
|
||||||
|
|
||||||
|
The content of the Store, including but not limited to designs, photographs, texts, graphics, logos and other material, is the property of 3DEALER or is lawfully used by it, and is protected by applicable intellectual and industrial property law.
|
||||||
|
|
||||||
|
Copying, reproducing, modifying, distributing or commercially exploiting the content without prior written permission is not allowed.
|
||||||
|
|
||||||
|
## 11. Personal data
|
||||||
|
|
||||||
|
Customers' personal data is processed in accordance with the 3DEALER Privacy Policy.
|
||||||
|
|
||||||
|
## 12. Changes to these Terms
|
||||||
|
|
||||||
|
3DEALER reserves the right to change these Terms & Conditions when necessary, in particular due to changes in legislation, services or the way the Store operates.
|
||||||
|
|
||||||
|
The terms that apply to each order are those that were available at the time that order was placed.
|
||||||
|
|
||||||
|
## 13. Governing law
|
||||||
|
|
||||||
|
These Terms & Conditions are governed by Greek law.
|
||||||
|
|
||||||
|
Any dispute relating to the use of the Store or the purchase of products is subject to the applicable Greek and European consumer protection legislation.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
meta_description: "Your local 3Dealer of 3D printed, high-quality products. Geek culture, decoration, fun and skulls, and custom orders."
|
||||||
|
---
|
||||||
@@ -9,6 +9,14 @@ content_path: /content
|
|||||||
media_path: /media
|
media_path: /media
|
||||||
thumbs_path: /storage/app/public/stoic/
|
thumbs_path: /storage/app/public/stoic/
|
||||||
repository_path: /repository
|
repository_path: /repository
|
||||||
|
# First locale is Stoic's default: it holds every field, the others only the
|
||||||
|
# translatable ones (content/{locale}/...). Codes match app.available_locales.
|
||||||
|
i18n:
|
||||||
|
locales:
|
||||||
|
- code: el
|
||||||
|
label: Ελληνικά
|
||||||
|
- code: en
|
||||||
|
label: English
|
||||||
presets:
|
presets:
|
||||||
- code: large
|
- code: large
|
||||||
mode: resize
|
mode: resize
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ schemas:
|
|||||||
- name: pages
|
- name: pages
|
||||||
label: Pages
|
label: Pages
|
||||||
type: files
|
type: files
|
||||||
|
translatable: true
|
||||||
files:
|
files:
|
||||||
- name: home
|
- name: home
|
||||||
label: Home Page
|
label: Home Page
|
||||||
@@ -10,15 +11,18 @@ schemas:
|
|||||||
- name: name
|
- name: name
|
||||||
label: Name
|
label: Name
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: slug
|
- name: slug
|
||||||
label: Slug
|
label: Slug
|
||||||
ui: slug
|
ui: slug
|
||||||
- name: meta_title
|
- name: meta_title
|
||||||
label: Meta Title
|
label: Meta Title
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: meta_description
|
- name: meta_description
|
||||||
label: Meta Description
|
label: Meta Description
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: og_image
|
- name: og_image
|
||||||
label: Og Image
|
label: Og Image
|
||||||
ui: image
|
ui: image
|
||||||
@@ -27,10 +31,50 @@ schemas:
|
|||||||
ui: image
|
ui: image
|
||||||
- name: trivia_body
|
- name: trivia_body
|
||||||
label: "Trivia Body (intro paragraph under the hero)"
|
label: "Trivia Body (intro paragraph under the hero)"
|
||||||
ui: markdown
|
ui: text
|
||||||
|
translatable: true
|
||||||
- name: classics_title
|
- name: classics_title
|
||||||
label: "Classics Section Title (favorites carousel heading)"
|
label: "Classics Section Title (favorites carousel heading)"
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
|
- name: contact
|
||||||
|
label: Contact Page
|
||||||
|
file: pages/contact.md
|
||||||
|
fields:
|
||||||
|
- name: name
|
||||||
|
label: Name
|
||||||
|
ui: string
|
||||||
|
translatable: true
|
||||||
|
- name: slug
|
||||||
|
label: Slug
|
||||||
|
ui: slug
|
||||||
|
- name: meta_title
|
||||||
|
label: Meta Title
|
||||||
|
ui: string
|
||||||
|
translatable: true
|
||||||
|
- name: meta_description
|
||||||
|
label: Meta Description
|
||||||
|
ui: string
|
||||||
|
translatable: true
|
||||||
|
- name: og_image
|
||||||
|
label: Og Image
|
||||||
|
ui: image
|
||||||
|
- name: heading_first
|
||||||
|
label: "Heading, first line (animated)"
|
||||||
|
ui: string
|
||||||
|
translatable: true
|
||||||
|
- name: heading_second
|
||||||
|
label: "Heading, second line"
|
||||||
|
ui: string
|
||||||
|
translatable: true
|
||||||
|
- name: heading_highlight
|
||||||
|
label: "Heading, last word(s) of the second line (animated)"
|
||||||
|
ui: string
|
||||||
|
translatable: true
|
||||||
|
- name: intro
|
||||||
|
label: "Intro (paragraph above the form)"
|
||||||
|
ui: text
|
||||||
|
translatable: true
|
||||||
- name: terms-and-conditions
|
- name: terms-and-conditions
|
||||||
label: "Terms & Conditions Page"
|
label: "Terms & Conditions Page"
|
||||||
file: pages/terms-and-conditions.md
|
file: pages/terms-and-conditions.md
|
||||||
@@ -38,15 +82,18 @@ schemas:
|
|||||||
- name: name
|
- name: name
|
||||||
label: Name
|
label: Name
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: slug
|
- name: slug
|
||||||
label: Slug
|
label: Slug
|
||||||
ui: slug
|
ui: slug
|
||||||
- name: meta_title
|
- name: meta_title
|
||||||
label: Meta Title
|
label: Meta Title
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: meta_description
|
- name: meta_description
|
||||||
label: Meta Description
|
label: Meta Description
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: og_image
|
- name: og_image
|
||||||
label: Og Image
|
label: Og Image
|
||||||
ui: image
|
ui: image
|
||||||
@@ -56,6 +103,7 @@ schemas:
|
|||||||
- name: body
|
- name: body
|
||||||
label: Body
|
label: Body
|
||||||
ui: markdown
|
ui: markdown
|
||||||
|
translatable: true
|
||||||
- name: shipping-returns
|
- name: shipping-returns
|
||||||
label: "Shipping & Returns Page"
|
label: "Shipping & Returns Page"
|
||||||
file: pages/shipping-returns.md
|
file: pages/shipping-returns.md
|
||||||
@@ -63,15 +111,18 @@ schemas:
|
|||||||
- name: name
|
- name: name
|
||||||
label: Name
|
label: Name
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: slug
|
- name: slug
|
||||||
label: Slug
|
label: Slug
|
||||||
ui: slug
|
ui: slug
|
||||||
- name: meta_title
|
- name: meta_title
|
||||||
label: Meta Title
|
label: Meta Title
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: meta_description
|
- name: meta_description
|
||||||
label: Meta Description
|
label: Meta Description
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: og_image
|
- name: og_image
|
||||||
label: Og Image
|
label: Og Image
|
||||||
ui: image
|
ui: image
|
||||||
@@ -81,6 +132,7 @@ schemas:
|
|||||||
- name: body
|
- name: body
|
||||||
label: Body
|
label: Body
|
||||||
ui: markdown
|
ui: markdown
|
||||||
|
translatable: true
|
||||||
- name: privacy-policy
|
- name: privacy-policy
|
||||||
label: "Privacy Policy Page"
|
label: "Privacy Policy Page"
|
||||||
file: pages/privacy-policy.md
|
file: pages/privacy-policy.md
|
||||||
@@ -88,15 +140,18 @@ schemas:
|
|||||||
- name: name
|
- name: name
|
||||||
label: Name
|
label: Name
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: slug
|
- name: slug
|
||||||
label: Slug
|
label: Slug
|
||||||
ui: slug
|
ui: slug
|
||||||
- name: meta_title
|
- name: meta_title
|
||||||
label: Meta Title
|
label: Meta Title
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: meta_description
|
- name: meta_description
|
||||||
label: Meta Description
|
label: Meta Description
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: og_image
|
- name: og_image
|
||||||
label: Og Image
|
label: Og Image
|
||||||
ui: image
|
ui: image
|
||||||
@@ -106,6 +161,7 @@ schemas:
|
|||||||
- name: body
|
- name: body
|
||||||
label: Body
|
label: Body
|
||||||
ui: markdown
|
ui: markdown
|
||||||
|
translatable: true
|
||||||
- name: cookies-policy
|
- name: cookies-policy
|
||||||
label: "Cookies Policy Page"
|
label: "Cookies Policy Page"
|
||||||
file: pages/cookies-policy.md
|
file: pages/cookies-policy.md
|
||||||
@@ -113,15 +169,18 @@ schemas:
|
|||||||
- name: name
|
- name: name
|
||||||
label: Name
|
label: Name
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: slug
|
- name: slug
|
||||||
label: Slug
|
label: Slug
|
||||||
ui: slug
|
ui: slug
|
||||||
- name: meta_title
|
- name: meta_title
|
||||||
label: Meta Title
|
label: Meta Title
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: meta_description
|
- name: meta_description
|
||||||
label: Meta Description
|
label: Meta Description
|
||||||
ui: string
|
ui: string
|
||||||
|
translatable: true
|
||||||
- name: og_image
|
- name: og_image
|
||||||
label: Og Image
|
label: Og Image
|
||||||
ui: image
|
ui: image
|
||||||
@@ -131,14 +190,17 @@ schemas:
|
|||||||
- name: body
|
- name: body
|
||||||
label: Body
|
label: Body
|
||||||
ui: markdown
|
ui: markdown
|
||||||
|
translatable: true
|
||||||
- name: settings
|
- name: settings
|
||||||
label: Settings
|
label: Settings
|
||||||
type: singleton
|
type: singleton
|
||||||
file: settings.md
|
file: settings.md
|
||||||
|
translatable: true
|
||||||
fields:
|
fields:
|
||||||
- name: meta_description
|
- name: meta_description
|
||||||
label: "Default Meta Description (used when a page has none)"
|
label: "Default Meta Description (used when a page has none)"
|
||||||
ui: text
|
ui: text
|
||||||
|
translatable: true
|
||||||
- name: og_image
|
- name: og_image
|
||||||
label: "Default Og Image (used when a page has none, ideally 1200×630)"
|
label: "Default Og Image (used when a page has none, ideally 1200×630)"
|
||||||
ui: image
|
ui: image
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
{{--
|
{{--
|
||||||
Store address and phone from Lunar's Store Details (Modules\Core\Store),
|
Store address, phone and email from Lunar's Store Details
|
||||||
email from CONTACT_EMAIL until Store Details has an email field too.
|
(Modules\Core\Store). Each line is left out when its value is empty.
|
||||||
Each line is left out when its value is empty.
|
|
||||||
--}}
|
--}}
|
||||||
@props([
|
@props([
|
||||||
'store' => null,
|
'store' => null,
|
||||||
'email' => null,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
@php
|
@php
|
||||||
$address = $store?->translate('address');
|
$address = $store?->translate('address');
|
||||||
$phone = $store?->phone;
|
$phone = $store?->phone;
|
||||||
|
$email = $store?->contact_email;
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@if (filled($address) || filled($phone) || filled($email))
|
@if (filled($address) || filled($phone) || filled($email))
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('title', __('storefront.nav.contact'))
|
@section('title', $page->meta_title ?: $page->name)
|
||||||
@section('description', 'Επικοινώνησε μαζί μας για οποιαδήποτε απορία ή ιδέα έχεις και θα σου απαντήσουμε το συντομότερο δυνατό.')
|
@section('description', $page->meta_description)
|
||||||
|
@section('og_image', $page->og_image)
|
||||||
|
|
||||||
{{-- Only on pages with a captcha, not in the global layout. --}}
|
{{-- Only on pages with a captcha, not in the global layout. --}}
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
@@ -21,13 +22,12 @@
|
|||||||
<h1 class="font-display text-h1 font-semibold leading-tight"
|
<h1 class="font-display text-h1 font-semibold leading-tight"
|
||||||
data-controller="appear"
|
data-controller="appear"
|
||||||
data-appear-visible-class="is-visible">
|
data-appear-visible-class="is-visible">
|
||||||
<span class="text-dance">Επικοινώνησε</span><br>
|
<span class="text-dance" data-stoic="pages/contact#heading_first">{{ $page->heading_first }}</span><br>
|
||||||
μαζί μας <span class="text-dance [--dance-delay:200ms]">άμεσα</span>
|
<span data-stoic="pages/contact#heading_second">{{ $page->heading_second }}</span>
|
||||||
|
<span class="text-dance [--dance-delay:200ms]" data-stoic="pages/contact#heading_highlight">{{ $page->heading_highlight }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="max-w-xl ">
|
<p class="max-w-xl" data-stoic="pages/contact#intro">{{ $page->intro }}</p>
|
||||||
Έχεις απορία, ιδέα ή θες να μας πεις κάτι; Γράψε μας το μήνυμά σου και θα σου απαντήσουμε το συντομότερο δυνατό.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<x-ui.status class="max-w-xl" />
|
<x-ui.status class="max-w-xl" />
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ class="h-captcha min-h-[78px]"
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col justify-center border-t border-black px-8 py-16 lg:border-t-0 lg:px-16">
|
<div class="flex flex-col justify-center border-t border-black px-8 py-16 lg:border-t-0 lg:px-16">
|
||||||
<x-contact-details :store="$store" :email="$contactEmail" />
|
<x-contact-details :store="$store" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@extends('errors.layout')
|
||||||
|
|
||||||
|
@section('error_title', __('storefront.errors.404_title'))
|
||||||
|
@section('error_text', __('storefront.errors.404_text'))
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@extends('errors.layout')
|
||||||
|
|
||||||
|
@section('error_title', __('storefront.errors.4xx_title'))
|
||||||
|
@section('error_text', __('storefront.errors.4xx_text'))
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@extends('errors.layout')
|
||||||
|
|
||||||
|
@section('error_title', __('storefront.errors.500_title'))
|
||||||
|
@section('error_text', __('storefront.errors.500_text'))
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@extends('errors.layout')
|
||||||
|
|
||||||
|
@section('error_title', __('storefront.errors.5xx_title'))
|
||||||
|
@section('error_text', __('storefront.errors.5xx_text'))
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{{-- Shared by every errors/{code} view: each one only sets title + text. --}}
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
{{-- Block form: the inline @section('title', …) form would escape the
|
||||||
|
already-escaped error_title a second time. --}}
|
||||||
|
@section('title')
|
||||||
|
@yield('error_title')
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('seo')
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<section class="flex flex-col items-center justify-center gap-8 px-8 py-24 text-center min-h-[calc(100vh-12rem)] sm:min-h-[calc(100vh-8rem)]">
|
||||||
|
|
||||||
|
<h1 class="font-display text-h1 font-semibold leading-tight">@yield('error_title')</h1>
|
||||||
|
|
||||||
|
<p class="max-w-xl">@yield('error_text')</p>
|
||||||
|
|
||||||
|
<x-ui.button :href="route('home')" class="mt-4">{{ __('storefront.errors.back_home') }}</x-ui.button>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@endsection
|
||||||
@@ -131,4 +131,10 @@
|
|||||||
->name('account.email.resend');
|
->name('account.email.resend');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Unmatched URLs would otherwise 404 during routing, before `web` and
|
||||||
|
// `locale` middleware run, so errors/404 would render with no session,
|
||||||
|
// no App::setLocale() and no URL::defaults() — the header's route()
|
||||||
|
// calls would then throw on the missing {locale}. Catching them here
|
||||||
|
// renders the 404 inside the locale group like any other page.
|
||||||
|
Route::fallback(fn () => abort(404));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user