Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions admin/app/Filament/Resources/WishlistResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace App\Filament\Resources;

use App\Filament\Resources\WishlistResource\Pages\CreateWishlist;
use App\Filament\Resources\WishlistResource\Pages\ListWishlists;
use App\Models\Wishlist;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Forms\Components\Select;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;

class WishlistResource extends Resource
{
protected static ?string $model = Wishlist::class;

protected static string | \UnitEnum | null $navigationGroup = 'Content';

protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-heart';

protected static ?int $navigationSort = 9;

public static function form(Schema $schema): Schema
{
return $schema
->components([
Select::make('user_id')
->relationship('user', 'email')
->required()
->searchable()
->preload()
->native(false)
->hintIcon('heroicon-o-information-circle')
->hintIconTooltip('The user who saved this product.'),
Select::make('product_id')
->relationship('product', 'heading')
->required()
->searchable()
->preload()
->native(false)
->hintIcon('heroicon-o-information-circle')
->hintIconTooltip('The product saved to this wishlist.'),
]);
}

public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('user.email')
->label('User')
->searchable()
->sortable(),
TextColumn::make('product.heading')
->label('Product')
->limit(40)
->searchable(),
TextColumn::make('created_at')
->label('Saved at')
->dateTime()
->sortable(),
])
->defaultSort('created_at', 'desc')
->filters([
//
])
->recordActions([
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}

public static function getRelations(): array
{
return [];
}

public static function getPages(): array
{
return [
'index' => ListWishlists::route('/'),
'create' => CreateWishlist::route('/create'),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace App\Filament\Resources\WishlistResource\Pages;

use App\Filament\Resources\WishlistResource;
use Filament\Resources\Pages\CreateRecord;

class CreateWishlist extends CreateRecord
{
protected static string $resource = WishlistResource::class;

protected function getRedirectUrl(): string
{
return $this->getResource()::getUrl('index');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace App\Filament\Resources\WishlistResource\Pages;

use App\Filament\Resources\WishlistResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;

class ListWishlists extends ListRecords
{
protected static string $resource = WishlistResource::class;

protected ?string $subheading = 'View and manage products saved to user wishlists.';

protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}
36 changes: 36 additions & 0 deletions admin/app/Models/Wishlist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
* @property positive-int $id
* @property positive-int $user_id
* @property positive-int $product_id
* @property User $user
* @property Product $product
*/
class Wishlist extends Model
{
use HasFactory;

protected $fillable = [
'user_id',
'product_id',
];

public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}

public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
}
27 changes: 27 additions & 0 deletions admin/database/factories/WishlistFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Database\Factories;

use App\Models\Product;
use App\Models\User;
use App\Models\Wishlist;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends Factory<Wishlist>
*/
class WishlistFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'product_id' => Product::factory(),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

use App\Models\Product;
use App\Models\User;
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::create('wishlists', function (Blueprint $table): void {
$table->id();
$table->foreignIdFor(User::class)->constrained()->cascadeOnDelete();
$table->foreignIdFor(Product::class)->constrained()->cascadeOnDelete();
$table->timestamps();
$table->unique(['user_id', 'product_id']);
});
}

public function down(): void
{
Schema::dropIfExists('wishlists');
}
};
1 change: 1 addition & 0 deletions admin/database/seeders/TestSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public function run(): void
PageSeeder::class,
FaqSeeder::class,
ReviewSeeder::class,
WishlistSeeder::class,
]);
}
}
18 changes: 18 additions & 0 deletions admin/database/seeders/WishlistSeeder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Database\Seeders;

use App\Models\Wishlist;
use Illuminate\Database\Seeder;

class WishlistSeeder extends Seeder
{
public function run(): void
{
Wishlist::all()->each->delete();

Wishlist::factory()->count(20)->create();
}
}
3 changes: 2 additions & 1 deletion admin/docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Catalog layer and platform basics.
- [x] Pages (CMS static pages; polymorphic image; SCHEDULED status with `published_at`)
- [x] FAQs (question + answer; `order` and nullable `position` for placement context)
- [x] Reviews (user reviews for products; `parent_id` for replies; `status` moderation)
- [x] Wishlists (`user_id` + `product_id` pivot; cascades on user/product delete; list + delete resource)
- [~] Addresses (model only, no resource yet)

Sample data for manual admin testing lives in `TestSeeder` (`php artisan db:seed --class=TestSeeder`); `DatabaseSeeder` holds only necessary data.
Expand Down Expand Up @@ -72,7 +73,7 @@ Depend mostly on Images only.
- [x] Pages
- [x] FAQs
- [x] Reviews
- [ ] Wishlists
- [x] Wishlists
- [ ] Tags
- [ ] Brand-Category pages
- [ ] Redirects
Expand Down
43 changes: 21 additions & 22 deletions admin/docs/ShoFlow db doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
Used to store user addresses.

* `user_id` refers to the user who owns the address.
* `seller_id` refers to the seller who owns the address; either `user_id` or `seller_id` must have a value.
* `city_id` selects the city code and is mandatory.
* `name` specifies the address name, such as home, office, or any user-defined name.
* `phone` specifies the phone number associated with the address.
Expand Down Expand Up @@ -185,9 +184,8 @@ Stores discount coupons. Unlike discounts, a coupon is applied manually: the cus
* `total_used`: How many times this coupon has already been used.
* `total_uses`: How many times this coupon is allowed to be used in total.
* `user_id`: Limits usage to a specific user. Nullable foreign key to `users`; set to null when the user is deleted.
* `user_creator_id`: The admin user who created the coupon, if created by an admin. Nullable foreign key to `users`.
* `seller_creator_id`: The seller who created the coupon, if created by a seller. Nullable foreign key to `users`.
* `status`: Has states "active" (default, usable), "canceled", "used", and "under review".
* `user_creator_id`: The admin user who created the coupon. Nullable foreign key to `users`.
* `status`: Has states "active" (default, usable), "canceled", "used", and "under review".
* `is_percent`: Indicates if the discount is a percentage or a fixed amount.
* `shipping`: Indicates if this coupon includes free shipping (applies only to free shipping, not to the price).
* `is_for`: Whether the coupon is usable by everyone, or only by users, or only by partners.
Expand Down Expand Up @@ -243,11 +241,11 @@ In short: discounts are automatic, per-variety, condition-based price rules. The

# faqs

* This table is used to store frequently asked questions (FAQ stands for Frequently Asked Questions).
* `heading`: Specifies the title of each question.
* `content`: Provides the answer to each question.
* `order`: Specifies the display order of the FAQs.
* `position`: Added to the FAQs table. All records with a null value in the `position` column are displayed on the FAQ page. Other records with a position are displayed in their respective positions, for example on the homepage or the products page (not the product details, but the page displaying all products regardless of category).
* Stores frequently asked questions shown on the storefront.
* `heading`: The question text shown to the visitor.
* `content`: The answer to the question.
* `order`: Display order — lower numbers appear first.
* `position`: Placement context. Null = shown on the main FAQ page. Any value (e.g. `"homepage"`, `"products"`) shows the FAQ in that specific section of the site.

# transactions

Expand Down Expand Up @@ -601,15 +599,15 @@ Used to store products.

# reviews

* Contains user reviews for each product.
* The `heading` column is the title of the review, for example, "Good product, I recommend it."
* The `content` column stores the content of the user's review.
* The `user_id` column specifies which user submitted the review.
* The `seller_id` column specifies which seller submitted the review. Only one of the columns `user_id` or `seller_id` can have a value at any time.
* The `product_id` column specifies which product the review is related to.
* The `variety_id` column specifies which product variant the review is related to. If not null, it means the review is for a purchased product variant.
* The `parent_id` column is used for replying to a review.
* The `status` column specifies the status of the review.
* Contains user reviews for each product.
* `heading`: The review title written by the user (e.g. "Great product!").
* `content`: The full review text.
* `user_id`: The user who submitted the review. Nullable; set to null if the user is deleted.
* `product_id`: The product being reviewed. Cascade-deletes the review when the product is deleted.
* `variety_id`: The specific variety (e.g. size/color) the user purchased. Nullable; set to null if the variety is deleted.
* `parent_id`: Used to reply to another review (self-referential FK). Set to null when the parent is deleted.
* `status`: Moderation state — `PENDING` (default, hidden from storefront), `APPROVED` (visible), `REJECTED`, `DELETED`.
* Note: no `seller_id` — ShopFlow is single-vendor, there are no sellers.

# roles

Expand Down Expand Up @@ -872,11 +870,12 @@ For storing warehouses. Warehouses can determine the location of each product, a

# wishlists

Stores the products added to user wishlists.
Stores the products saved to user wishlists.

* `user_id`: Indicates which user this record belongs to.
* `product_id`: Indicates which product this record belongs to.
* `created_at`: Indicates when this record was created.
* `user_id`: The user who saved the product. Cascade-deletes the entry when the user is deleted.
* `product_id`: The saved product. Cascade-deletes the entry when the product is deleted.
* Unique constraint on `(user_id, product_id)` — a user can only save each product once.
* Admin resource is read-only (list + delete); entries are created by users on the storefront.

# working\_hours

Expand Down
Loading
Loading