8 Commits
Author SHA1 Message Date
marcoandClaude Sonnet 4.6 9dbc6e7ea1 Add delivery_note field to products for planned delivery info
Deploy to Production (witstore.wende.it) / deploy (push) Successful in 7s
Release erstellen / build-release (push) Successful in 47s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 07:17:40 +02:00
marcoandClaude Sonnet 4.6 6d16a6b0f9 Fix Alpine timing: use alpine:init + Alpine.data() so productDetail is registered before Alpine starts
Deploy to Production (witstore.wende.it) / deploy (push) Successful in 7s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 07:14:29 +02:00
marcoandClaude Sonnet 4.6 0a98740877 Fix product main image: use @php + @json to avoid quote conflicts in Alpine x-data
Deploy to Production (witstore.wende.it) / deploy (push) Successful in 6s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 07:06:08 +02:00
marcoandClaude Sonnet 4.6 714807f648 Fix product image: pass firstImage to Alpine, increase display size to h-96
Deploy to Production (witstore.wende.it) / deploy (push) Successful in 7s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 07:02:50 +02:00
marcoandClaude Sonnet 4.6 b29fa5b2aa Fix: add storage:link to deploy script for product images
Deploy to Production (witstore.wende.it) / deploy (push) Successful in 6s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 06:57:41 +02:00
marcoandClaude Sonnet 4.6 460f78ba3e Fix release workflow: replace actions/checkout with direct curl download
Release erstellen / build-release (push) Successful in 53s
Runner cannot resolve internal 'gitea' hostname. Download source archive
from external URL git.wende.it instead. Also replace release-action with
direct Gitea API calls to avoid the same DNS issue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 11:45:04 +02:00
marco 791c12442e Merge: keep DEV version of .gitignore
Release erstellen / build-release (push) Failing after 35s
2026-06-21 11:38:21 +02:00
marco 49e122b717 Initial commit 2026-06-20 00:33:42 +02:00
8 changed files with 100 additions and 32 deletions
+1
View File
@@ -46,6 +46,7 @@ jobs:
grep -q "^APP_KEY=$" "$DEPLOY_PATH/.env" && \
/opt/plesk/php/8.4/bin/php -d open_basedir="" artisan key:generate --force || true
/opt/plesk/php/8.4/bin/php -d open_basedir="" artisan storage:link 2>/dev/null || true
/opt/plesk/php/8.4/bin/php -d open_basedir="" artisan config:clear
/opt/plesk/php/8.4/bin/php -d open_basedir="" artisan route:clear
/opt/plesk/php/8.4/bin/php -d open_basedir="" artisan view:clear
+42 -16
View File
@@ -10,8 +10,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Code auschecken
uses: actions/checkout@v4
- name: Code herunterladen
run: |
VERSION="${{ github.ref_name }}"
curl -sSL -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
-o source.tar.gz \
"https://git.wende.it/marco/WitShop/archive/${VERSION}.tar.gz"
tar xzf source.tar.gz
# Gitea entpackt in einen Unterordner WitShop-{version}/
SRCDIR=$(tar tzf source.tar.gz 2>/dev/null | head -1 | cut -d/ -f1 || true)
if [ -n "$SRCDIR" ] && [ -d "$SRCDIR" ]; then
mv "$SRCDIR"/* . 2>/dev/null || true
mv "$SRCDIR"/.[!.]* . 2>/dev/null || true
rmdir "$SRCDIR" 2>/dev/null || true
fi
rm -f source.tar.gz
- name: Node.js einrichten
uses: actions/setup-node@v4
@@ -51,19 +64,32 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Release in Gitea veröffentlichen
uses: https://gitea.com/actions/release-action@main
with:
files: ${{ env.PACKAGE }}
api_key: ${{ secrets.RELEASE_TOKEN }}
title: "WITshop ${{ env.VERSION }}"
body: |
## WITshop ${{ env.VERSION }}
run: |
# Release erstellen (oder existierenden verwenden)
RELEASE_ID=$(curl -sSL -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
"https://git.wende.it/api/v1/repos/marco/WitShop/releases/tags/${{ github.ref_name }}" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
### Installation (Erstinstallation)
1. ZIP herunterladen und auf den Server entpacken
2. `install.sh` ausführen: `bash install.sh`
3. Fertig
if [ -z "$RELEASE_ID" ]; then
RELEASE_ID=$(curl -sSL -X POST \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
-H "Content-Type: application/json" \
"https://git.wende.it/api/v1/repos/marco/WitShop/releases" \
-d "{
\"tag_name\": \"${{ github.ref_name }}\",
\"name\": \"WITshop ${{ github.ref_name }}\",
\"body\": \"## WITshop ${{ github.ref_name }}\n\n### Installation (Erstinstallation)\n1. ZIP herunterladen und auf den Server entpacken\n2. \`install.sh\` ausführen: \`bash install.sh\`\n3. Fertig\n\n### Update (bestehende Installation)\n1. ZIP herunterladen und auf den Server entpacken\n2. \`update.sh\` ausführen: \`bash update.sh\`\",
\"draft\": false,
\"prerelease\": false
}" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
fi
### Update (bestehende Installation)
1. ZIP herunterladen und auf den Server entpacken
2. `update.sh` ausführen: `bash update.sh`
echo "Release ID: $RELEASE_ID"
# ZIP anhängen
curl -sSL -X POST \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
-F "attachment=@${{ env.PACKAGE }}" \
"https://git.wende.it/api/v1/repos/marco/WitShop/releases/${RELEASE_ID}/assets?name=${{ env.PACKAGE }}"
echo "ZIP erfolgreich hochgeladen: ${{ env.PACKAGE }}"
@@ -46,6 +46,7 @@ class ProductController extends Controller
'featured' => 'boolean',
'track_stock' => 'boolean',
'sort_order' => 'integer',
'delivery_note' => 'nullable|string|max:255',
]);
$data['slug'] = Str::slug($data['name']);
$data['active'] = $request->boolean('active');
@@ -91,6 +92,7 @@ class ProductController extends Controller
'featured' => 'boolean',
'track_stock' => 'boolean',
'sort_order' => 'integer',
'delivery_note' => 'nullable|string|max:255',
]);
$data['slug'] = Str::slug($data['name']);
$data['active'] = $request->boolean('active');
+1 -1
View File
@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
class Product extends Model
{
protected $fillable = ['category_id','name','slug','description','base_price','sale_price','tax_rate','weight','active','featured','track_stock','sort_order'];
protected $fillable = ['category_id','name','slug','description','base_price','sale_price','tax_rate','weight','active','featured','track_stock','sort_order','delivery_note'];
protected $casts = ['active' => 'boolean', 'featured' => 'boolean', 'track_stock' => 'boolean', 'base_price' => 'decimal:2', 'sale_price' => 'decimal:2', 'tax_rate' => 'decimal:2'];
@@ -0,0 +1,18 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::table('products', function (Blueprint $table) {
$table->string('delivery_note')->nullable()->after('track_stock');
});
}
public function down(): void {
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('delivery_note');
});
}
};
@@ -165,6 +165,12 @@
</label>
</div>
<div>
<label class="block text-sm font-medium mb-1">Lieferhinweis <span class="text-gray-400 font-normal">(optional, z.B. "Geplante Lieferung: Mai 2026")</span></label>
<input type="text" name="delivery_note" value="{{ old('delivery_note', $product->delivery_note ?? '') }}"
class="w-full border rounded px-3 py-2 text-sm" placeholder="z.B. Geplante Lieferung: Mai 2026">
</div>
<div class="flex gap-2">
<button type="submit" class="flex-1 bg-blue-600 text-white py-2 rounded hover:bg-blue-700 text-sm">Speichern</button>
<a href="{{ route('admin.products.index') }}" class="flex-1 text-center py-2 rounded border text-sm hover:bg-gray-50">Abbrechen</a>
@@ -24,7 +24,13 @@
<span class="text-xs text-gray-400">inkl. {{ number_format($product->tax_rate, 0) }}% MwSt.</span>
</div>
@if($product->track_stock && $product->variants->sum('stock') <= 0)
@if($product->delivery_note)
<p class="text-xs text-orange-500 mt-1">📦 {{ $product->delivery_note }}</p>
@else
<p class="text-xs text-red-500 mt-1">Nicht vorrätig</p>
@endif
@elseif($product->delivery_note && !($product->track_stock && $product->variants->sum('stock') > 0))
<p class="text-xs text-orange-500 mt-1">📦 {{ $product->delivery_note }}</p>
@endif
</div>
</a>
+19 -10
View File
@@ -1,12 +1,13 @@
@extends('layouts.shop')
@section('title', $product->name)
@section('content')
<div class="grid grid-cols-1 md:grid-cols-2 gap-10 mb-12" x-data="productDetail(@json($product->variants))">
@php $firstImageUrl = $product->images->count() ? asset('storage/'.$product->images->first()->path) : ''; @endphp
<div class="grid grid-cols-1 md:grid-cols-2 gap-10 mb-12" x-data="productDetail">
{{-- Bilder --}}
<div>
@if($product->images->count())
<img :src="mainImage" x-init="mainImage = '{{ asset('storage/'.$product->images->first()->path) }}'" class="w-full rounded-lg mb-3 border">
<img :src="mainImage" class="w-full h-96 object-contain rounded-lg mb-3 border bg-gray-50">
<div class="flex gap-2 flex-wrap">
@foreach($product->images as $img)
<img src="{{ asset('storage/'.$img->path) }}" @click="mainImage = '{{ asset('storage/'.$img->path) }}'"
@@ -14,7 +15,7 @@
@endforeach
</div>
@else
<div class="w-full h-72 bg-gray-100 rounded-lg flex items-center justify-center text-7xl">📦</div>
<div class="w-full h-96 bg-gray-100 rounded-lg flex items-center justify-center text-7xl">📦</div>
@endif
</div>
@@ -73,6 +74,10 @@
@endif
@endif
@if($product->delivery_note)
<p class="text-sm text-orange-500 mb-3">📦 {{ $product->delivery_note }}</p>
@endif
{{-- In den Warenkorb --}}
<form method="POST" action="{{ route('cart.add') }}">
@csrf
@@ -109,13 +114,17 @@
@endif
<script>
function productDetail(variants) {
return {
document.addEventListener('alpine:init', () => {
const variants = @json($product->variants->load('attributeValues'));
const firstImage = @json($firstImageUrl);
const basePrice = {{ $product->base_price }};
Alpine.data('productDetail', () => ({
variants,
selectedAttrs: {},
selectedVariant: null,
mainImage: '',
currentPrice: {{ $product->base_price }},
mainImage: firstImage,
currentPrice: basePrice,
selectAttr(name, id) {
this.selectedAttrs[name] = id;
this.findVariant();
@@ -127,13 +136,13 @@ function productDetail(variants) {
return selectedIds.every(id => ids.includes(id)) && ids.length === selectedIds.length;
});
if (this.selectedVariant) {
this.currentPrice = this.selectedVariant.price_override ?? {{ $product->base_price }};
this.currentPrice = this.selectedVariant.price_override ?? basePrice;
}
},
formatPrice(p) {
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(p);
}
}
}
}));
});
</script>
@endsection