131 lines
16 KiB
PHP
131 lines
16 KiB
PHP
@extends('layouts.app')
|
|
@section('title', 'Settings')
|
|
@section('content')
|
|
<div class="flex flex-wrap items-end justify-between gap-4">
|
|
<div><p class="text-xs font-bold uppercase tracking-[.2em] text-brand">Workspace connections</p><h2 class="mt-2 text-2xl font-bold">Integration settings</h2><p class="mt-2 max-w-2xl text-sm text-slate-400">Manage the services behind your orders, shipping and workshop. Saved credentials are encrypted in the database.</p></div>
|
|
<span class="rounded-full border border-slate-700 px-3 py-1.5 text-xs text-slate-400">Administrators only</span>
|
|
</div>
|
|
|
|
<nav class="grid gap-3 sm:grid-cols-2 xl:grid-cols-4" aria-label="Integration settings sections">
|
|
@foreach([
|
|
['etsy', 'Etsy shop', $configured && $shopConnected, $storedCredentials, 'Orders and listings'],
|
|
['parcel2go', 'Parcel2Go', $integrations['parcel2go']['configured'], $integrations['parcel2go']['stored'], 'Shipping and labels'],
|
|
['bambuddy', 'BamBuddy', $integrations['bambuddy']['configured'], $integrations['bambuddy']['stored'], 'Print farm'],
|
|
['dryer', 'Filament dryer', $integrations['dryer']['configured'], $integrations['dryer']['stored'], 'Drying controls'],
|
|
] as [$section, $name, $isConfigured, $isStored, $description])
|
|
<a href="#{{ $section }}" class="group rounded-2xl border border-slate-800 bg-slate-900 p-4 transition-colors hover:border-brand/60 hover:bg-slate-800/70">
|
|
<span class="flex items-center justify-between gap-2"><strong class="text-sm text-white">{{ $name }}</strong><span aria-hidden="true" class="text-slate-500 group-hover:text-brand">↗</span></span>
|
|
<span class="mt-1 block text-xs text-slate-400">{{ $description }}</span>
|
|
<span @class(['mt-4 inline-flex rounded-full px-2.5 py-1 text-xs font-semibold', 'bg-emerald-500/10 text-emerald-300' => $isConfigured, 'bg-amber-500/10 text-amber-200' => ! $isConfigured])>{{ $section === 'etsy' && $configured ? ($shopConnected ? 'Shop connected' : 'Connect shop') : ($isConfigured ? ($isStored ? 'Saved in dashboard' : 'Using server settings') : 'Setup needed') }}</span>
|
|
</a>
|
|
@endforeach
|
|
</nav>
|
|
|
|
<section id="etsy" class="scroll-mt-6 rounded-2xl border border-slate-800 bg-slate-900 p-6">
|
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
<div>
|
|
<h3 class="text-lg font-semibold">Etsy shop</h3>
|
|
<p class="mt-1 text-sm text-slate-400">{{ $storedCredentials ? 'App credentials saved in dashboard.' : ($configured ? 'Using server app credentials.' : 'Add your Etsy app credentials.') }}</p>
|
|
</div>
|
|
<span @class(['rounded-full px-3 py-1 text-xs font-semibold', 'bg-emerald-500/10 text-emerald-300' => $configured && $shopConnected, 'bg-amber-500/10 text-amber-200' => ! ($configured && $shopConnected)])>{{ $configured && $shopConnected ? 'Shop connected' : ($configured ? 'Connect shop' : 'Setup needed') }}</span>
|
|
</div>
|
|
|
|
<form method="post" action="{{ route('settings.etsy.update') }}" class="mt-6 grid gap-5 md:grid-cols-2">
|
|
@csrf
|
|
@method('PUT')
|
|
<label class="grid gap-2 text-sm text-slate-300">
|
|
App keystring
|
|
<input name="keystring" type="password" autocomplete="new-password" maxlength="255" placeholder="{{ $configured ? 'Saved — leave blank to keep' : 'Enter Etsy app keystring' }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100 placeholder:text-slate-500" {{ $configured ? '' : 'required' }}>
|
|
</label>
|
|
<label class="grid gap-2 text-sm text-slate-300">
|
|
Shared secret
|
|
<input name="shared_secret" type="password" autocomplete="new-password" maxlength="255" placeholder="{{ $configured ? 'Saved — leave blank to keep' : 'Enter Etsy shared secret' }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100 placeholder:text-slate-500" {{ $configured ? '' : 'required' }}>
|
|
</label>
|
|
<label class="grid gap-2 text-sm text-slate-300">
|
|
Shop name
|
|
<input name="shop_name" type="text" maxlength="255" value="{{ old('shop_name', $shopName) }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100">
|
|
</label>
|
|
<details class="rounded-xl border border-slate-800 bg-slate-950/60 p-4 md:col-span-2" @if($errors->has('redirect_uri')) open @endif>
|
|
<summary class="cursor-pointer text-sm font-semibold text-slate-200">Advanced: OAuth callback</summary>
|
|
<label class="mt-4 grid gap-2 text-sm text-slate-300">Callback URL<input name="redirect_uri" type="url" maxlength="2048" value="{{ old('redirect_uri', $redirectUri) }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100"></label>
|
|
<p class="mt-2 text-xs text-slate-400">Register this exact HTTPS URL in your Etsy developer app.</p>
|
|
</details>
|
|
<div class="md:col-span-2">
|
|
<button class="rounded-xl bg-brand px-5 py-3 text-sm font-semibold text-slate-950 hover:bg-brand/90">Save Etsy settings</button>
|
|
@if($configured)<a href="{{ route('etsy.index') }}" class="ml-2 inline-flex rounded-xl border border-slate-700 px-5 py-3 text-sm font-semibold text-slate-200 hover:border-brand">{{ $shopConnected ? 'Open Etsy shop' : 'Connect Etsy shop' }}</a>@endif
|
|
</div>
|
|
</form>
|
|
<p class="mt-4 text-xs text-slate-500">Leave saved credentials blank to keep them. Saving imports server credentials if needed. Changing the app key disconnects the shop until you reconnect it.</p>
|
|
</section>
|
|
|
|
<div class="grid gap-5 xl:grid-cols-2">
|
|
<section id="parcel2go" class="scroll-mt-6 rounded-2xl border border-slate-800 bg-slate-900 p-6 xl:col-span-2">
|
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
<div><h3 class="text-lg font-semibold">Parcel2Go</h3><p class="mt-1 text-sm text-slate-400">Client Credentials for shipping, account history and labels.</p></div>
|
|
<span @class(['rounded-full px-3 py-1 text-xs font-semibold', 'bg-emerald-500/10 text-emerald-300' => $integrations['parcel2go']['configured'], 'bg-amber-500/10 text-amber-200' => ! $integrations['parcel2go']['configured']])>{{ $integrations['parcel2go']['configured'] ? ($integrations['parcel2go']['stored'] ? 'Saved in dashboard' : 'Using server settings') : 'Setup needed' }}</span>
|
|
</div>
|
|
<p class="mt-4 text-sm text-slate-400">Use the Client ID and secret from the Parcel2Go client that worked in your previous dashboard. Leave saved credentials blank to keep them.</p>
|
|
<form method="post" action="{{ route('settings.integrations.update', 'parcel2go') }}" class="mt-5 grid gap-4 lg:grid-cols-2">
|
|
@csrf @method('PUT')
|
|
<label class="grid gap-2 text-sm text-slate-300">Client ID<input name="client_id" type="password" autocomplete="new-password" maxlength="255" placeholder="{{ $integrations['parcel2go']['configured'] ? 'Saved — leave blank to keep' : 'Enter Client ID' }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100 placeholder:text-slate-500"></label>
|
|
<label class="grid gap-2 text-sm text-slate-300">Client secret<input name="client_secret" type="password" autocomplete="new-password" maxlength="255" placeholder="{{ $integrations['parcel2go']['configured'] ? 'Saved — leave blank to keep' : 'Enter client secret' }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100 placeholder:text-slate-500"></label>
|
|
<label class="grid gap-2 text-sm text-slate-300 lg:col-span-2">Collection postcode<input name="collection_postcode" maxlength="12" value="{{ old('collection_postcode', $integrations['parcel2go']['collection_postcode']) }}" placeholder="Used when quoting Etsy orders" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100 placeholder:text-slate-500"></label>
|
|
<details class="rounded-xl border border-slate-800 bg-slate-950/60 p-4 lg:col-span-2" @if($errors->has('auth_url') || $errors->has('base_url') || $errors->has('scope') || $errors->has('profile_client_id')) open @endif>
|
|
<summary class="cursor-pointer text-sm font-semibold text-slate-200">Advanced: API endpoints and access</summary>
|
|
<div class="mt-4 grid gap-4 lg:grid-cols-2">
|
|
<label class="grid gap-2 text-sm text-slate-300">OAuth token URL<input name="auth_url" type="url" maxlength="2048" value="{{ old('auth_url', $integrations['parcel2go']['auth_url']) }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100"></label>
|
|
<label class="grid gap-2 text-sm text-slate-300">API base URL<input name="base_url" type="url" maxlength="2048" value="{{ old('base_url', $integrations['parcel2go']['base_url']) }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100"></label>
|
|
<label class="grid gap-2 text-sm text-slate-300">Requested API scope<select name="scope" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100">@foreach(['public-api' => 'public-api (recommended)', 'public-api my-profile' => 'public-api + my-profile', 'public-api payment' => 'public-api + payment', 'public-api payment my-profile' => 'public-api + payment + my-profile'] as $scope => $label)<option value="{{ $scope }}" @selected(old('scope', $integrations['parcel2go']['scope']) === $scope)>{{ $label }}</option>@endforeach</select></label>
|
|
<label class="grid gap-2 text-sm text-slate-300">Optional Authorization Code Client ID<input name="profile_client_id" type="password" autocomplete="new-password" maxlength="255" placeholder="{{ $integrations['parcel2go']['profile_client_configured'] ? 'Saved — leave blank to keep' : 'Only needed for optional consent' }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100 placeholder:text-slate-500"></label>
|
|
</div>
|
|
</details>
|
|
<div class="lg:col-span-2"><button class="rounded-xl bg-brand px-5 py-3 text-sm font-semibold text-slate-950 hover:bg-brand/90">Save Parcel2Go settings</button></div>
|
|
</form>
|
|
<div class="mt-5 grid gap-3 rounded-xl border border-slate-700 bg-slate-950 p-4 text-sm">
|
|
<div class="flex flex-wrap items-center justify-between gap-2"><p class="font-semibold">Optional Parcel2Go account consent</p><span @class(['rounded-full px-2 py-1 text-xs font-semibold', 'bg-emerald-500/10 text-emerald-300' => $integrations['parcel2go']['profile_connected'], 'bg-amber-500/10 text-amber-200' => ! $integrations['parcel2go']['profile_connected']])>{{ $integrations['parcel2go']['profile_connected'] ? 'Connected' : 'Not connected' }}</span></div>
|
|
<p class="text-slate-400">The dashboard tries shipment history and account details with Client Credentials first. Authorization Code consent is optional.</p>
|
|
<details class="border-t border-slate-800 pt-3" @if($errors->has('parcel2go')) open @endif>
|
|
<summary class="cursor-pointer font-semibold text-brand">Set up optional account consent</summary>
|
|
<div class="mt-3 grid gap-3 text-slate-400">
|
|
<p>Create an Authorization Code client in Parcel2Go. Set Application URL to <code>{{ url('/') }}</code> and register this exact Redirect URI:</p>
|
|
<code class="overflow-x-auto rounded-lg border border-slate-700 bg-slate-900 px-3 py-2 text-slate-200">{{ $integrations['parcel2go']['callback_url'] }}</code>
|
|
<p>Save its Client ID under Advanced API settings, then connect below. If consent is rejected, contact <a class="text-brand underline" href="mailto:apihelp@parcel2go.com">Parcel2Go API support</a>.</p>
|
|
@if($integrations['parcel2go']['profile_client_configured'])<form method="post" action="{{ route('parcel2go.connect') }}">@csrf<button class="rounded-xl border border-brand/40 px-4 py-2 font-semibold text-brand hover:bg-brand/10">{{ $integrations['parcel2go']['profile_connected'] ? 'Reconnect account' : 'Connect account' }}</button></form>@endif
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="bambuddy" class="scroll-mt-6 rounded-2xl border border-slate-800 bg-slate-900 p-6">
|
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
<div><h3 class="text-lg font-semibold">BamBuddy</h3><p class="mt-1 text-sm text-slate-400">Live print farm data.</p></div>
|
|
<span @class(['rounded-full px-3 py-1 text-xs font-semibold', 'bg-emerald-500/10 text-emerald-300' => $integrations['bambuddy']['configured'], 'bg-amber-500/10 text-amber-200' => ! $integrations['bambuddy']['configured']])>{{ $integrations['bambuddy']['configured'] ? ($integrations['bambuddy']['stored'] ? 'Saved in dashboard' : 'Using server settings') : 'Setup needed' }}</span>
|
|
</div>
|
|
<form method="post" action="{{ route('settings.integrations.update', 'bambuddy') }}" class="mt-5 grid gap-4">
|
|
@csrf @method('PUT')
|
|
<input type="hidden" name="settings_section" value="bambuddy">
|
|
<label class="grid gap-2 text-sm text-slate-300">BamBuddy URL<input name="url" type="url" maxlength="2048" value="{{ old('settings_section') === 'bambuddy' ? old('url', $integrations['bambuddy']['url']) : $integrations['bambuddy']['url'] }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100"></label>
|
|
<label class="grid gap-2 text-sm text-slate-300">API key<input name="api_key" type="password" autocomplete="new-password" maxlength="512" placeholder="{{ $integrations['bambuddy']['configured'] ? 'Saved — leave blank to keep' : 'Enter API key' }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100 placeholder:text-slate-500"></label>
|
|
<button class="w-fit rounded-xl bg-brand px-5 py-3 text-sm font-semibold text-slate-950 hover:bg-brand/90">Save BamBuddy settings</button>
|
|
</form>
|
|
<p class="mt-3 text-xs text-slate-500">Leave the API key blank to keep its current value.</p>
|
|
</section>
|
|
|
|
<section id="dryer" class="scroll-mt-6 rounded-2xl border border-slate-800 bg-slate-900 p-6">
|
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
<div><h3 class="text-lg font-semibold">Filament dryer</h3><p class="mt-1 text-sm text-slate-400">Status, cycles and fault controls.</p></div>
|
|
<span @class(['rounded-full px-3 py-1 text-xs font-semibold', 'bg-emerald-500/10 text-emerald-300' => $integrations['dryer']['configured'], 'bg-amber-500/10 text-amber-200' => ! $integrations['dryer']['configured']])>{{ $integrations['dryer']['configured'] ? ($integrations['dryer']['stored'] ? 'Saved in dashboard' : 'Using server settings') : 'Setup needed' }}</span>
|
|
</div>
|
|
<form method="post" action="{{ route('settings.integrations.update', 'dryer') }}" class="mt-5 grid gap-4">
|
|
@csrf @method('PUT')
|
|
<input type="hidden" name="settings_section" value="dryer">
|
|
<label class="grid gap-2 text-sm text-slate-300">Dryer API URL<input name="url" type="url" maxlength="2048" value="{{ old('settings_section') === 'dryer' ? old('url', $integrations['dryer']['url']) : $integrations['dryer']['url'] }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100"></label>
|
|
<label class="grid gap-2 text-sm text-slate-300">API key<input name="api_key" type="password" autocomplete="new-password" maxlength="512" placeholder="{{ $integrations['dryer']['configured'] ? 'Saved — leave blank to keep' : 'Enter API key' }}" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100 placeholder:text-slate-500"></label>
|
|
<button class="w-fit rounded-xl bg-brand px-5 py-3 text-sm font-semibold text-slate-950 hover:bg-brand/90">Save dryer settings</button>
|
|
</form>
|
|
<p class="mt-3 text-xs text-slate-500">Leave the API key blank to keep its current value.</p>
|
|
</section>
|
|
</div>
|
|
|
|
@endsection
|