Tirage des carte
This commit is contained in:
parent
a81ec57958
commit
5e4a4955f3
60
app/Http/Controllers/CardController.php
Normal file
60
app/Http/Controllers/CardController.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Repositories\CardRepositoryInterface;
|
||||||
|
|
||||||
|
|
||||||
|
class CardController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $cardRepository;
|
||||||
|
|
||||||
|
public function __construct(CardRepositoryInterface $cardRepository)
|
||||||
|
{
|
||||||
|
$this->cardRepository = $cardRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$cards = app('App\Repositories\CardRepositoryInterface')->all();
|
||||||
|
|
||||||
|
return Inertia::render('cards/shuffle', [
|
||||||
|
'cards' => $cards,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Log the error for debugging
|
||||||
|
Log::error('Error fetching cards: '.$e->getMessage(), [
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Optionally, you can return an Inertia error page or empty array
|
||||||
|
return Inertia::render('Cards/Index', [
|
||||||
|
'cards' => [],
|
||||||
|
'error' => 'Impossible de récupérer les cartes pour le moment.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function drawCard(Request $request)
|
||||||
|
{
|
||||||
|
// Validate the request if needed
|
||||||
|
$request->validate([
|
||||||
|
'count' => 'sometimes|integer'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cardDraw = $this->cardRepository->draw($request->count);
|
||||||
|
|
||||||
|
// Return the response (Inertia will automatically handle this)
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'card' => $cardDraw,
|
||||||
|
'message' => 'Card drawn successfully'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
app/Models/Card.php
Normal file
25
app/Models/Card.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
|
class Card extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'cards';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'description_upright',
|
||||||
|
'description_reversed',
|
||||||
|
'symbolism',
|
||||||
|
'image_url',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'symbolism' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -4,6 +4,9 @@ namespace App\Providers;
|
|||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
use App\Repositories\CardRepository;
|
||||||
|
use App\Repositories\CardRepositoryInterface;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -11,7 +14,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
//
|
$this->app->bind(CardRepositoryInterface::class, CardRepository::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
84
app/Repositories/CardRepository.php
Normal file
84
app/Repositories/CardRepository.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Models\Card;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use App\Repositories\CardRepositoryInterface;
|
||||||
|
|
||||||
|
class CardRepository implements CardRepositoryInterface
|
||||||
|
{
|
||||||
|
public function all(): Collection
|
||||||
|
{
|
||||||
|
return Card::all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find(int $id): ?Card
|
||||||
|
{
|
||||||
|
return Card::find($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(array $data): Card
|
||||||
|
{
|
||||||
|
return Card::create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $id, array $data): ?Card
|
||||||
|
{
|
||||||
|
$card = Card::find($id);
|
||||||
|
|
||||||
|
if (! $card) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$card->update($data);
|
||||||
|
|
||||||
|
return $card;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(int $id): bool
|
||||||
|
{
|
||||||
|
$card = Card::find($id);
|
||||||
|
|
||||||
|
if (! $card) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool) $card->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw oracle cards
|
||||||
|
*
|
||||||
|
* @param int $count Number of cards to draw (1, 6, 18, 21, etc.)
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function draw(int $count = 1): array
|
||||||
|
{
|
||||||
|
// Récupère toutes les cartes (80 dans la DB)
|
||||||
|
$cards = Card::all();
|
||||||
|
|
||||||
|
// Mélange avec shuffle (Fisher–Yates est fait par Laravel via ->shuffle())
|
||||||
|
$shuffled = $cards->shuffle();
|
||||||
|
|
||||||
|
// Prend les $count premières cartes
|
||||||
|
$selected = $shuffled->take($count);
|
||||||
|
|
||||||
|
// Pour chaque carte, ajoute orientation + description
|
||||||
|
$results = $selected->map(function ($card) {
|
||||||
|
$isReversed = (bool) random_int(0, 1); // 50% upright / 50% reversed
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $card->id,
|
||||||
|
'name' => $card->name,
|
||||||
|
'image_url' => $card->image_url,
|
||||||
|
'orientation' => $isReversed ? 'reversed' : 'upright',
|
||||||
|
'description' => $isReversed ? $card->description_reversed : $card->description_upright,
|
||||||
|
'symbolism' => $card->symbolism,
|
||||||
|
'created_at' => now(),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return $results->toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/Repositories/CardRepositoryInterface.php
Normal file
21
app/Repositories/CardRepositoryInterface.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Models\Card;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
|
interface CardRepositoryInterface
|
||||||
|
{
|
||||||
|
public function all(): Collection;
|
||||||
|
|
||||||
|
public function find(int $id): ?Card;
|
||||||
|
|
||||||
|
public function create(array $data): Card;
|
||||||
|
|
||||||
|
public function update(int $id, array $data): ?Card;
|
||||||
|
|
||||||
|
public function delete(int $id): bool;
|
||||||
|
|
||||||
|
public function draw(): array;
|
||||||
|
}
|
||||||
0
bootstrap/cache/.gitignore
vendored
Normal file → Executable file
0
bootstrap/cache/.gitignore
vendored
Normal file → Executable file
66
database/seeders/CardSeeder.php
Normal file
66
database/seeders/CardSeeder.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use App\Models\Card;
|
||||||
|
|
||||||
|
class CardSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$cards = [
|
||||||
|
[
|
||||||
|
'name' => 'Le Fou',
|
||||||
|
'description_upright' => 'Nouveaux départs, spontanéité, innocence, esprit libre.',
|
||||||
|
'description_reversed' => 'Imprudence, prise de risques inconsidérée, blocages.',
|
||||||
|
'symbolism' => [
|
||||||
|
'numéro' => 0,
|
||||||
|
'élément' => 'Air',
|
||||||
|
'planète' => 'Uranus'
|
||||||
|
],
|
||||||
|
'image_url' => 'storage/cards/1.png',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Le Magicien',
|
||||||
|
'description_upright' => 'Manifestation, ingéniosité, pouvoir, action inspirée.',
|
||||||
|
'description_reversed' => 'Manipulation, talents inexploités, illusions.',
|
||||||
|
'symbolism' => [
|
||||||
|
'numéro' => 1,
|
||||||
|
'élément' => 'Air',
|
||||||
|
'planète' => 'Mercure'
|
||||||
|
],
|
||||||
|
'image_url' => 'storage/cards/2.png',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'La Grande Prêtresse',
|
||||||
|
'description_upright' => 'Intuition, savoir sacré, féminin divin, mystère.',
|
||||||
|
'description_reversed' => 'Secrets, déconnexion de l’intuition, retrait.',
|
||||||
|
'symbolism' => [
|
||||||
|
'numéro' => 2,
|
||||||
|
'élément' => 'Eau',
|
||||||
|
'planète' => 'Lune'
|
||||||
|
],
|
||||||
|
'image_url' => 'storage/cards/3.png',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => "L'Impératrice",
|
||||||
|
'description_upright' => 'Féminité, abondance, fertilité, créativité, nature.',
|
||||||
|
'description_reversed' => 'Dépendance, blocages créatifs, excès ou manque.',
|
||||||
|
'symbolism' => [
|
||||||
|
'numéro' => 3,
|
||||||
|
'élément' => 'Terre',
|
||||||
|
'planète' => 'Vénus'
|
||||||
|
],
|
||||||
|
'image_url' => 'storage/cards/4.png',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($cards as $card) {
|
||||||
|
Card::create($card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
145
package-lock.json
generated
145
package-lock.json
generated
@ -6,11 +6,14 @@
|
|||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inertiajs/vue3": "^2.1.0",
|
"@inertiajs/vue3": "^2.1.0",
|
||||||
|
"@vue-stripe/vue-stripe": "^4.5.0",
|
||||||
"@vueuse/core": "^12.8.2",
|
"@vueuse/core": "^12.8.2",
|
||||||
|
"axios": "^1.11.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"laravel-vite-plugin": "^2.0.0",
|
"laravel-vite-plugin": "^2.0.0",
|
||||||
"lucide-vue-next": "^0.468.0",
|
"lucide-vue-next": "^0.468.0",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
"reka-ui": "^2.2.0",
|
"reka-ui": "^2.2.0",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss": "^4.1.1",
|
"tailwindcss": "^4.1.1",
|
||||||
@ -1223,6 +1226,11 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@stripe/stripe-js": {
|
||||||
|
"version": "1.54.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.54.2.tgz",
|
||||||
|
"integrity": "sha512-R1PwtDvUfs99cAjfuQ/WpwJ3c92+DAMy9xGApjqlWQMj0FKQabUAys2swfTRNzuYAYJh7NqK2dzcYVNkKLEKUg=="
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.17",
|
"version": "0.5.17",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
||||||
@ -1854,6 +1862,15 @@
|
|||||||
"vscode-uri": "^3.0.8"
|
"vscode-uri": "^3.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue-stripe/vue-stripe": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue-stripe/vue-stripe/-/vue-stripe-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-BU449XT5zegjNQirl+SSztbzGIvPjhxlHv8ybomSZcI1jB6qEpLgpk2eHMFDKnOGZZRhqtg4C5FiErwSJ/yuRw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@stripe/stripe-js": "^1.13.2",
|
||||||
|
"vue-coerce-props": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vue/compiler-core": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.5.18",
|
"version": "3.5.18",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.18.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.18.tgz",
|
||||||
@ -1915,6 +1932,36 @@
|
|||||||
"he": "^1.2.0"
|
"he": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue/devtools-api": {
|
||||||
|
"version": "7.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
|
||||||
|
"integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-kit": "^7.7.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/devtools-kit": {
|
||||||
|
"version": "7.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz",
|
||||||
|
"integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-shared": "^7.7.7",
|
||||||
|
"birpc": "^2.3.0",
|
||||||
|
"hookable": "^5.5.3",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
|
"perfect-debounce": "^1.0.0",
|
||||||
|
"speakingurl": "^14.0.1",
|
||||||
|
"superjson": "^2.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/devtools-shared": {
|
||||||
|
"version": "7.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz",
|
||||||
|
"integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
|
||||||
|
"dependencies": {
|
||||||
|
"rfdc": "^1.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vue/eslint-config-typescript": {
|
"node_modules/@vue/eslint-config-typescript": {
|
||||||
"version": "14.6.0",
|
"version": "14.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.6.0.tgz",
|
||||||
@ -2154,7 +2201,6 @@
|
|||||||
"version": "1.11.0",
|
"version": "1.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
"form-data": "^4.0.4",
|
"form-data": "^4.0.4",
|
||||||
@ -2168,6 +2214,14 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/birpc": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/boolbase": {
|
"node_modules/boolbase": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
@ -2378,6 +2432,20 @@
|
|||||||
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/copy-anything": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
|
||||||
|
"dependencies": {
|
||||||
|
"is-what": "^4.1.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.13"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/mesqueeb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@ -3303,6 +3371,11 @@
|
|||||||
"he": "bin/he"
|
"he": "bin/he"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hookable": {
|
||||||
|
"version": "5.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
||||||
|
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@ -3383,6 +3456,17 @@
|
|||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-what": {
|
||||||
|
"version": "4.1.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
|
||||||
|
"integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.13"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/mesqueeb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/isexe": {
|
"node_modules/isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
@ -3847,6 +3931,11 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mitt": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
|
||||||
|
},
|
||||||
"node_modules/mkdirp": {
|
"node_modules/mkdirp": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
||||||
@ -4023,6 +4112,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/perfect-debounce": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@ -4041,6 +4135,26 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pinia": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^7.7.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.4.4",
|
||||||
|
"vue": "^2.7.0 || ^3.5.11"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||||
@ -4317,6 +4431,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rfdc": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="
|
||||||
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.46.3",
|
"version": "4.46.3",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.3.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.3.tgz",
|
||||||
@ -4533,6 +4652,14 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/speakingurl": {
|
||||||
|
"version": "14.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
|
||||||
|
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
@ -4574,6 +4701,17 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/superjson": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"copy-anything": "^3.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "8.1.1",
|
"version": "8.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||||
@ -4959,6 +5097,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-coerce-props": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-coerce-props/-/vue-coerce-props-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-4fdRMXO6FHzmE7H4soAph6QmPg3sL/RiGdd+axuxuU07f02LNMns0jMM88fmt1bvSbN+2Wyd8raho6p6nXUzag=="
|
||||||
|
},
|
||||||
"node_modules/vue-eslint-parser": {
|
"node_modules/vue-eslint-parser": {
|
||||||
"version": "10.2.0",
|
"version": "10.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz",
|
||||||
|
|||||||
@ -30,11 +30,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inertiajs/vue3": "^2.1.0",
|
"@inertiajs/vue3": "^2.1.0",
|
||||||
|
"@vue-stripe/vue-stripe": "^4.5.0",
|
||||||
"@vueuse/core": "^12.8.2",
|
"@vueuse/core": "^12.8.2",
|
||||||
|
"axios": "^1.11.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"laravel-vite-plugin": "^2.0.0",
|
"laravel-vite-plugin": "^2.0.0",
|
||||||
"lucide-vue-next": "^0.468.0",
|
"lucide-vue-next": "^0.468.0",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
"reka-ui": "^2.2.0",
|
"reka-ui": "^2.2.0",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss": "^4.1.1",
|
"tailwindcss": "^4.1.1",
|
||||||
|
|||||||
BIN
public/cards/1.png
Normal file
BIN
public/cards/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/cards/2.png
Normal file
BIN
public/cards/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
public/cards/3.png
Normal file
BIN
public/cards/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
BIN
public/cards/4.png
Normal file
BIN
public/cards/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
@ -2,17 +2,20 @@ import '../css/app.css';
|
|||||||
|
|
||||||
import { createInertiaApp } from '@inertiajs/vue3';
|
import { createInertiaApp } from '@inertiajs/vue3';
|
||||||
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
import type { DefineComponent } from 'vue';
|
import type { DefineComponent } from 'vue';
|
||||||
import { createApp, h } from 'vue';
|
import { createApp, h } from 'vue';
|
||||||
import { initializeTheme } from './composables/useAppearance';
|
import { initializeTheme } from './composables/useAppearance';
|
||||||
|
|
||||||
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
|
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
|
||||||
|
const pinia = createPinia();
|
||||||
|
|
||||||
createInertiaApp({
|
createInertiaApp({
|
||||||
title: (title) => (title ? `${title} - ${appName}` : appName),
|
title: (title) => (title ? `${title} - ${appName}` : appName),
|
||||||
resolve: (name) => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob<DefineComponent>('./pages/**/*.vue')),
|
resolve: (name) => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob<DefineComponent>('./pages/**/*.vue')),
|
||||||
setup({ el, App, props, plugin }) {
|
setup({ el, App, props, plugin }) {
|
||||||
createApp({ render: () => h(App, props) })
|
createApp({ render: () => h(App, props) })
|
||||||
|
.use(pinia)
|
||||||
.use(plugin)
|
.use(plugin)
|
||||||
.mount(el);
|
.mount(el);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1,563 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CardShuffleTemplate from '@/components/template/CardShuffleTemplate.vue';
|
||||||
|
import { Card } from '@/types/cart';
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['drawCard']);
|
||||||
|
|
||||||
|
const isClicked = ref(false);
|
||||||
|
const isDrawing = ref(false);
|
||||||
|
const drawnCards = ref<Card[]>([]); // Changed to array to handle multiple cards
|
||||||
|
const showResult = ref(false);
|
||||||
|
const isFlipped = ref<boolean[]>([]); // Array to track flip state for each card
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (isDrawing.value) return;
|
||||||
|
|
||||||
|
isClicked.value = true;
|
||||||
|
isDrawing.value = true;
|
||||||
|
emit('drawCard');
|
||||||
|
setTimeout(() => (isClicked.value = false), 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// This function would be called from the parent component when the card data is received
|
||||||
|
const setDrawnCards = (cardData: Card[]) => {
|
||||||
|
drawnCards.value = cardData;
|
||||||
|
isDrawing.value = false;
|
||||||
|
showResult.value = true;
|
||||||
|
|
||||||
|
// Initialize flip states for each card
|
||||||
|
isFlipped.value = new Array(cardData.length).fill(false);
|
||||||
|
|
||||||
|
// Add confetti effect
|
||||||
|
createConfetti();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(drawnCards, (newVal) => {
|
||||||
|
console.log('Drawn cards:', newVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
const flipCard = (index: number) => {
|
||||||
|
isFlipped.value[index] = !isFlipped.value[index];
|
||||||
|
};
|
||||||
|
|
||||||
|
const createConfetti = () => {
|
||||||
|
const confettiContainer = document.createElement('div');
|
||||||
|
confettiContainer.style.position = 'fixed';
|
||||||
|
confettiContainer.style.top = '0';
|
||||||
|
confettiContainer.style.left = '0';
|
||||||
|
confettiContainer.style.width = '100%';
|
||||||
|
confettiContainer.style.height = '100%';
|
||||||
|
confettiContainer.style.pointerEvents = 'none';
|
||||||
|
confettiContainer.style.zIndex = '5'; // Lower z-index so cards appear above
|
||||||
|
document.body.appendChild(confettiContainer);
|
||||||
|
|
||||||
|
const colors = ['#D7BA8D', '#A06D52', '#1F2A44', '#FFFFFF'];
|
||||||
|
const confettiCount = 100;
|
||||||
|
|
||||||
|
for (let i = 0; i < confettiCount; i++) {
|
||||||
|
const confetti = document.createElement('div');
|
||||||
|
confetti.style.position = 'absolute';
|
||||||
|
confetti.style.width = '10px';
|
||||||
|
confetti.style.height = '10px';
|
||||||
|
confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
|
||||||
|
confetti.style.borderRadius = Math.random() > 0.5 ? '50%' : '0';
|
||||||
|
confetti.style.top = '50%';
|
||||||
|
confetti.style.left = '50%';
|
||||||
|
confetti.style.opacity = '0';
|
||||||
|
|
||||||
|
confettiContainer.appendChild(confetti);
|
||||||
|
|
||||||
|
const animation = confetti.animate(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
transform: 'translate(0, 0) rotate(0deg)',
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
transform: `translate(${Math.random() * 400 - 200}px, ${Math.random() * 400 - 200}px) rotate(${Math.random() * 360}deg)`,
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
duration: 1000 + Math.random() * 1000,
|
||||||
|
easing: 'cubic-bezier(0.1, 0.8, 0.3, 1)',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
animation.onfinish = () => {
|
||||||
|
confetti.remove();
|
||||||
|
if (confettiContainer.children.length === 0) {
|
||||||
|
confettiContainer.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Expose the setDrawnCards function to parent component
|
||||||
|
defineExpose({ setDrawnCards });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CardShuffleTemplate>
|
||||||
|
<template #card-shuffle-slot>
|
||||||
|
<div class="card-container">
|
||||||
|
<div
|
||||||
|
class="card-stack relative mt-4 mb-4 flex h-[500px] w-[300px] items-center justify-center"
|
||||||
|
:class="{ clicked: isClicked, drawing: isDrawing }"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<div class="card" style="transform: rotate(-3deg) translateZ(0); z-index: 3">
|
||||||
|
<div class="card-back">
|
||||||
|
<div class="card-back-design">
|
||||||
|
<svg
|
||||||
|
class="h-16 w-16 text-[var(--subtle-gold)] opacity-80"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M12 4v16m8-8H4" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"></path>
|
||||||
|
<path
|
||||||
|
d="M14.828 7.172a4 4 0 015.656 5.656l-5.656 5.657a4 4 0 01-5.657-5.657l5.657-5.656z"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="0.5"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M9.172 7.172a4 4 0 00-5.657 5.656l5.657 5.657a4 4 0 005.656-5.657L9.172 7.172z"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="0.5"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="transform: rotate(1deg) translateZ(-10px); z-index: 2">
|
||||||
|
<div class="card-back">
|
||||||
|
<div class="card-back-design">
|
||||||
|
<svg
|
||||||
|
class="h-16 w-16 text-[var(--subtle-gold)] opacity-80"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M12 4v16m8-8H4" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"></path>
|
||||||
|
<path
|
||||||
|
d="M14.828 7.172a4 4 0 015.656 5.656l-5.656 5.657a4 4 0 01-5.657-5.657l5.657-5.656z"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="0.5"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M9.172 7.172a4 4 0 00-5.657 5.656l5.657 5.657a4 4 0 005.656-5.657L9.172 7.172z"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="0.5"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" style="transform: rotate(4deg) translateZ(-20px); z-index: 1">
|
||||||
|
<div class="card-back">
|
||||||
|
<div class="card-back-design">
|
||||||
|
<svg
|
||||||
|
class="h-16 w-16 text-[var(--subtle-gold)] opacity-80"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M12 4v16m8-8H4" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"></path>
|
||||||
|
<path
|
||||||
|
d="M14.828 7.172a4 4 0 015.656 5.656l-5.656 5.657a4 4 0 01-5.657-5.657l5.657-5.656z"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="0.5"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M9.172 7.172a4 4 0 00-5.657 5.656l5.657 5.657a4 4 0 005.656-5.657L9.172 7.172z"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="0.5"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showResult && drawnCards.length" class="cards-result-container">
|
||||||
|
<div v-for="(card, index) in drawnCards" :key="index" class="card-result-wrapper">
|
||||||
|
<div class="result-card" :class="{ flipped: isFlipped[index] }" @click="flipCard(index)">
|
||||||
|
<div class="card-face card-unknown-front">
|
||||||
|
<div class="card-back-design">
|
||||||
|
<svg
|
||||||
|
class="h-16 w-16 text-[var(--subtle-gold)] opacity-80"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M12 4v16m8-8H4" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"></path>
|
||||||
|
<path
|
||||||
|
d="M14.828 7.172a4 4 0 015.656 5.656l-5.656 5.657a4 4 0 01-5.657-5.657l5.657-5.656z"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="0.5"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M9.172 7.172a4 4 0 00-5.657 5.656l5.657 5.657a4 4 0 005.656-5.657L9.172 7.172z"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="0.5"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-face card-known-back">
|
||||||
|
<img :src="card.image_url!" :alt="card.name" class="card-image" />
|
||||||
|
<div class="card-description-overlay">
|
||||||
|
<h3>{{ card.name }}</h3>
|
||||||
|
<p class="description">{{ card.description }}</p>
|
||||||
|
<p v-if="card.orientation" class="orientation">
|
||||||
|
{{ card.orientation === 'reversed' ? 'Inversée' : 'Droite' }}
|
||||||
|
</p>
|
||||||
|
<div v-if="card.symbolism" class="symbolism">
|
||||||
|
<p><strong>Numéro:</strong> {{ card.symbolism.numéro }}</p>
|
||||||
|
<p><strong>Planète:</strong> {{ card.symbolism.planète }}</p>
|
||||||
|
<p><strong>Élément:</strong> {{ card.symbolism.élément }}</p>
|
||||||
|
</div>
|
||||||
|
<p class="click-hint">Cliquez pour retourner</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</CardShuffleTemplate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 250px;
|
||||||
|
height: 400px;
|
||||||
|
background: linear-gradient(145deg, var(--pure-white), var(--linen));
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow:
|
||||||
|
0 10px 20px rgba(0, 0, 0, 0.1),
|
||||||
|
0 6px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
transition:
|
||||||
|
transform 0.5s ease-in-out,
|
||||||
|
box-shadow 0.5s ease-in-out;
|
||||||
|
cursor: pointer;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation stack globale */
|
||||||
|
.card-stack {
|
||||||
|
transition: transform 0.6s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
perspective: 1000px; /* ajoute profondeur */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover sur la pile */
|
||||||
|
.card-stack:hover {
|
||||||
|
transform: translateY(-10px) rotateX(2deg) rotateY(-2deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stack:hover .card:nth-child(1) {
|
||||||
|
transform: rotateY(-5deg) rotateX(5deg) translateZ(30px) translateX(-20px);
|
||||||
|
}
|
||||||
|
.card-stack:hover .card:nth-child(2) {
|
||||||
|
transform: rotateY(0deg) rotateX(2deg) translateZ(20px);
|
||||||
|
}
|
||||||
|
.card-stack:hover .card:nth-child(3) {
|
||||||
|
transform: rotateY(5deg) rotateX(5deg) translateZ(10px) translateX(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glow doré subtil au hover */
|
||||||
|
.card-stack:hover .card-back {
|
||||||
|
box-shadow: 0 0 20px rgba(215, 186, 141, 0.6);
|
||||||
|
transition: box-shadow 0.6s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation click */
|
||||||
|
@keyframes card-click-tilt {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-10px) rotateX(2deg) rotateY(-2deg);
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
transform: translateY(-5px) rotateX(-4deg) rotateY(4deg);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateY(-12px) rotateX(3deg) rotateY(-3deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(-10px) rotateX(2deg) rotateY(-2deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drawing animation */
|
||||||
|
@keyframes card-drawing {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-10px) rotateX(2deg) rotateY(-2deg);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateY(-30px) rotateX(10deg) rotateY(-10deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-40px) rotateX(-5deg) rotateY(5deg);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateY(-30px) rotateX(5deg) rotateY(-5deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(-10px) rotateX(2deg) rotateY(-2deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active sur clic */
|
||||||
|
.card-stack.clicked {
|
||||||
|
animation: card-click-tilt 0.4s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stack.drawing {
|
||||||
|
animation: card-drawing 1.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Back des cartes */
|
||||||
|
.card-back {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid var(--subtle-gold);
|
||||||
|
background: radial-gradient(circle, var(--midnight-blue) 0%, #121a2c 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-result-wrapper {
|
||||||
|
width: 250px;
|
||||||
|
height: 400px;
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-back-design-wrapper {
|
||||||
|
background: radial-gradient(circle, var(--midnight-blue) 0%, #121a2c 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-back-design {
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
border: 2px solid var(--subtle-gold);
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.card-back-design::before,
|
||||||
|
.card-back-design::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
border-color: var(--subtle-gold);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-face {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
backface-visibility: hidden; /* This is crucial for the flip effect */
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden; /* To keep content within borders */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-unknown-front {
|
||||||
|
background: radial-gradient(circle, var(--midnight-blue) 0%, #121a2c 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid var(--subtle-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-known-back {
|
||||||
|
transform: rotateY(180deg); /* This face starts rotated, so it's hidden */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-back-design::before {
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-top-style: solid;
|
||||||
|
border-left-style: solid;
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
}
|
||||||
|
.card-back-design::after {
|
||||||
|
bottom: -2px;
|
||||||
|
right: -2px;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-right-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-right-style: solid;
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Result card styles */
|
||||||
|
.cards-result-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 2rem;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-result-container {
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: transform 0.8s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card.flipped {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-front,
|
||||||
|
.card-back-info {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem;
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
text-align: center;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-back-info p {
|
||||||
|
color: var(--midnight-blue); /* Adjust color if needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-front {
|
||||||
|
background: linear-gradient(145deg, var(--pure-white), var(--linen));
|
||||||
|
color: var(--midnight-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-back-info {
|
||||||
|
background: linear-gradient(145deg, var(--midnight-blue), #121a2c);
|
||||||
|
color: var(--pure-white);
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
text-align: center;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content-wrapper {
|
||||||
|
background: linear-gradient(145deg, var(--pure-white), var(--linen));
|
||||||
|
color: var(--midnight-blue);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem;
|
||||||
|
transform: rotateY(180deg); /* This is the key part to make it the 'back' of the card */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 16px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-description-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.6); /* Semi-transparent overlay for readability */
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end; /* Align content to the bottom */
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-description-overlay h3,
|
||||||
|
.card-description-overlay p {
|
||||||
|
margin: 0.2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orientation {
|
||||||
|
font-style: italic;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
color: var(--spiritual-earth);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbolism {
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbolism p {
|
||||||
|
margin: 0.3rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-hint {
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
0
resources/js/components/organism/StripePayment.vue
Normal file
0
resources/js/components/organism/StripePayment.vue
Normal file
17
resources/js/components/template/CardShuffleTemplate.vue
Normal file
17
resources/js/components/template/CardShuffleTemplate.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<main class="flex flex-1 flex-col items-center justify-center px-4 pt-12 pb-24 sm:px-6 lg:px-8">
|
||||||
|
<div class="relative z-10 flex w-full max-w-4xl flex-col items-center justify-center text-center">
|
||||||
|
<h1 class="text-5xl font-bold text-[var(--midnight-blue)] md:text-6xl">
|
||||||
|
L'Oracle de votre<span class="citadel-script ml-4 text-6xl text-[var(--spiritual-earth)] md:text-7xl">Destinée</span>
|
||||||
|
</h1>
|
||||||
|
<p class="mt-4 max-w-2xl text-lg text-[var(--midnight-blue)]/80">
|
||||||
|
Puisez dans la sagesse ancestrale pour éclairer votre chemin. Tirez une carte et recevez le message qui vous est destiné aujourd'hui.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Card shuffle slot -->
|
||||||
|
|
||||||
|
<slot name="card-shuffle-slot" />
|
||||||
|
<!-- Button tirage -->
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
50
resources/js/lib/http.ts
Normal file
50
resources/js/lib/http.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import axios, { type AxiosError, type AxiosInstance } from 'axios'
|
||||||
|
|
||||||
|
// SSR-safe guard for browser-only features
|
||||||
|
const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'
|
||||||
|
|
||||||
|
function getCsrfTokenFromMeta(): string | null {
|
||||||
|
if (!isBrowser) return null
|
||||||
|
const el = document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement | null
|
||||||
|
return el?.content ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a preconfigured Axios instance for the app
|
||||||
|
const http: AxiosInstance = axios.create({
|
||||||
|
baseURL: '/',
|
||||||
|
withCredentials: true, // include cookies for same-origin requests
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
// If you use Laravel Sanctum's CSRF cookie, these defaults help automatically send it
|
||||||
|
xsrfCookieName: 'XSRF-TOKEN',
|
||||||
|
xsrfHeaderName: 'X-XSRF-TOKEN',
|
||||||
|
timeout: 30000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Attach CSRF token from Blade <meta name="csrf-token" ...> when present
|
||||||
|
http.interceptors.request.use((config) => {
|
||||||
|
const token = getCsrfTokenFromMeta()
|
||||||
|
if (token) {
|
||||||
|
// Laravel will accept either X-CSRF-TOKEN (meta) or X-XSRF-TOKEN (cookie)
|
||||||
|
config.headers = config.headers ?? {}
|
||||||
|
;(config.headers as Record<string, string>)['X-CSRF-TOKEN'] = token
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
// Basic error passthrough; customize as needed
|
||||||
|
http.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
async (error: AxiosError) => {
|
||||||
|
// Example handling: if (error.response?.status === 401) { /* redirect to login */ }
|
||||||
|
// Example handling: if (error.response?.status === 419) { /* CSRF token mismatch */ }
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export type { AxiosError, AxiosInstance }
|
||||||
|
export { http }
|
||||||
|
export default http
|
||||||
|
|
||||||
19
resources/js/pages/cards/index.vue
Normal file
19
resources/js/pages/cards/index.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Liste des cards</h1>
|
||||||
|
<ul>
|
||||||
|
<li v-for="card in cards" :key="card.id">{{ card.name }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Card } from '@/types/cart';
|
||||||
|
import { defineProps } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
cards: Array<Card>,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(props.cards);
|
||||||
|
</script>
|
||||||
107
resources/js/pages/cards/shuffle.vue
Normal file
107
resources/js/pages/cards/shuffle.vue
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import ShuffleCardPresentation from '@/components/organism/ShuffleCard/ShuffleCardPresentation.vue';
|
||||||
|
import LandingLayout from '@/layouts/app/LandingLayout.vue';
|
||||||
|
import { useTarotStore } from '@/stores/tarot';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const cardComponent = ref();
|
||||||
|
const tarotStore = useTarotStore();
|
||||||
|
const isSelectionScreen = ref(true);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
// This variable will hold the number of cards to draw
|
||||||
|
const drawCount = ref(0);
|
||||||
|
|
||||||
|
// This function will be called from the "offer" buttons
|
||||||
|
const handleSelection = (count: number) => {
|
||||||
|
drawCount.value = count;
|
||||||
|
|
||||||
|
// Check if the draw is free or requires payment
|
||||||
|
if (count === 1) {
|
||||||
|
// Free draw
|
||||||
|
if (tarotStore.freeDrawsRemaining > 0) {
|
||||||
|
tarotStore.useFreeDraw();
|
||||||
|
isSelectionScreen.value = false; // Switch to the shuffle screen
|
||||||
|
} else {
|
||||||
|
alert('You have used your free draw. Please choose a paid option to unlock more.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Paid draw
|
||||||
|
// This is where you'd trigger your Stripe payment component
|
||||||
|
alert(`Initiating payment process for a ${count}-card draw.`);
|
||||||
|
// For now, let's simulate a successful payment and then proceed
|
||||||
|
tarotStore.unlockNewDraws().then(() => {
|
||||||
|
isSelectionScreen.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCard = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await axios.post('/draw-card', { count: drawCount.value });
|
||||||
|
if (res.data) {
|
||||||
|
cardComponent.value.setDrawnCards(res.data.card);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LandingLayout>
|
||||||
|
<section v-if="isSelectionScreen" class="py-20 sm:py-24">
|
||||||
|
<h2 class="mb-16 text-center text-4xl font-bold text-[var(--midnight-blue)] md:text-5xl">Explorez Nos Lectures</h2>
|
||||||
|
<div class="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<div
|
||||||
|
class="flex flex-col gap-6 rounded-2xl border border-[var(--linen)] bg-[var(--pure-white)] p-8 shadow-lg transition-all duration-300 hover:-translate-y-2 hover:shadow-2xl"
|
||||||
|
>
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-2xl font-bold text-[var(--midnight-blue)]">Lecture Gratuite</h3>
|
||||||
|
<p class="mt-2 text-5xl font-bold text-[var(--subtle-gold)]">Gratuit</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="mt-4 flex h-12 w-full items-center justify-center rounded-full bg-[var(--linen)] px-8 font-bold tracking-wide text-[var(--midnight-blue)] transition-all duration-300 hover:bg-[var(--spiritual-earth)] hover:text-[var(--pure-white)]"
|
||||||
|
@click="handleSelection(1)"
|
||||||
|
>
|
||||||
|
Commencer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex scale-105 flex-col gap-6 rounded-2xl bg-[var(--midnight-blue)] p-8 shadow-lg ring-2 ring-[var(--subtle-gold)] transition-all duration-300 hover:-translate-y-2 hover:shadow-2xl"
|
||||||
|
>
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-2xl font-bold text-[var(--pure-white)]">Profilage</h3>
|
||||||
|
<p class="mt-2 text-5xl font-bold text-[var(--subtle-gold)]">29€</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="mt-4 flex h-12 w-full items-center justify-center rounded-full bg-[var(--subtle-gold)] px-8 font-bold tracking-wide text-[var(--midnight-blue)] transition-all duration-300 hover:bg-[var(--pure-white)]"
|
||||||
|
@click="handleSelection(3)"
|
||||||
|
>
|
||||||
|
Découvrir
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex flex-col gap-6 rounded-2xl border border-[var(--linen)] bg-[var(--pure-white)] p-8 shadow-lg transition-all duration-300 hover:-translate-y-2 hover:shadow-2xl"
|
||||||
|
>
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-2xl font-bold text-[var(--midnight-blue)]">Quadrige Doré</h3>
|
||||||
|
<p class="mt-2 text-5xl font-bold text-[var(--subtle-gold)]">99€</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="mt-4 flex h-12 w-full items-center justify-center rounded-full bg-[var(--linen)] px-8 font-bold tracking-wide text-[var(--midnight-blue)] transition-all duration-300 hover:bg-[var(--spiritual-earth)] hover:text-[var(--pure-white)]"
|
||||||
|
@click="handleSelection(4)"
|
||||||
|
>
|
||||||
|
Explorer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<ShuffleCardPresentation v-else ref="cardComponent" @draw-card="getCard" />
|
||||||
|
</LandingLayout>
|
||||||
|
</template>
|
||||||
39
resources/js/stores/tarot.ts
Normal file
39
resources/js/stores/tarot.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export const useTarotStore = defineStore('tarot', () => {
|
||||||
|
// State
|
||||||
|
const freeDrawsRemaining = ref(1);
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
function useFreeDraw() {
|
||||||
|
if (freeDrawsRemaining.value > 0) {
|
||||||
|
freeDrawsRemaining.value--;
|
||||||
|
return true; // Indicates a free draw was used
|
||||||
|
}
|
||||||
|
return false; // No more free draws
|
||||||
|
}
|
||||||
|
|
||||||
|
// You would integrate Stripe here in a more advanced application
|
||||||
|
// This is a placeholder for your payment logic.
|
||||||
|
function unlockNewDraws() {
|
||||||
|
// You would typically call a backend endpoint here to create a Stripe Checkout Session
|
||||||
|
// and redirect the user. For this example, we'll simulate a successful payment.
|
||||||
|
console.log('Redirecting to Stripe for payment...');
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Payment successful! Adding 1 new draw.');
|
||||||
|
// After successful payment from Stripe, you would update the state.
|
||||||
|
// This state update would likely come from a backend webhook.
|
||||||
|
freeDrawsRemaining.value++;
|
||||||
|
resolve(true);
|
||||||
|
}, 2000); // Simulate a network delay
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
freeDrawsRemaining,
|
||||||
|
useFreeDraw,
|
||||||
|
unlockNewDraws,
|
||||||
|
};
|
||||||
|
});
|
||||||
12
resources/js/types/cart.d.ts
vendored
Normal file
12
resources/js/types/cart.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export interface Card {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description_upright: string;
|
||||||
|
description_reversed: string;
|
||||||
|
symbolism: Record<string, string> | null; // objet JSON ou null
|
||||||
|
orientation?: string;
|
||||||
|
image_url: string | null;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
@ -11,5 +11,9 @@ Route::get('dashboard', function () {
|
|||||||
return Inertia::render('Dashboard');
|
return Inertia::render('Dashboard');
|
||||||
})->middleware(['auth', 'verified'])->name('dashboard');
|
})->middleware(['auth', 'verified'])->name('dashboard');
|
||||||
|
|
||||||
|
// Route::get('/cards', [App\Http\Controllers\CardController::class, 'index'])->name('cards.index');
|
||||||
|
Route::get('/tirage',[App\Http\Controllers\CardController::class, 'index'])->name('cards.shuffle');
|
||||||
|
Route::post('/draw-card', [App\Http\Controllers\CardController::class, 'drawCard']);
|
||||||
|
|
||||||
require __DIR__.'/settings.php';
|
require __DIR__.'/settings.php';
|
||||||
require __DIR__.'/auth.php';
|
require __DIR__.'/auth.php';
|
||||||
|
|||||||
4
storage 1/app/.gitignore
vendored
Executable file
4
storage 1/app/.gitignore
vendored
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
*
|
||||||
|
!private/
|
||||||
|
!public/
|
||||||
|
!.gitignore
|
||||||
2
storage 1/app/private/.gitignore
vendored
Executable file
2
storage 1/app/private/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
2
storage 1/app/public/.gitignore
vendored
Executable file
2
storage 1/app/public/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
9
storage 1/framework/.gitignore
vendored
Executable file
9
storage 1/framework/.gitignore
vendored
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
compiled.php
|
||||||
|
config.php
|
||||||
|
down
|
||||||
|
events.scanned.php
|
||||||
|
maintenance.php
|
||||||
|
routes.php
|
||||||
|
routes.scanned.php
|
||||||
|
schedule-*
|
||||||
|
services.json
|
||||||
3
storage 1/framework/cache/.gitignore
vendored
Executable file
3
storage 1/framework/cache/.gitignore
vendored
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
*
|
||||||
|
!data/
|
||||||
|
!.gitignore
|
||||||
2
storage 1/framework/cache/data/.gitignore
vendored
Executable file
2
storage 1/framework/cache/data/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
2
storage 1/framework/sessions/.gitignore
vendored
Executable file
2
storage 1/framework/sessions/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
2
storage 1/framework/testing/.gitignore
vendored
Executable file
2
storage 1/framework/testing/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
2
storage 1/framework/views/.gitignore
vendored
Executable file
2
storage 1/framework/views/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
2
storage 1/logs/.gitignore
vendored
Executable file
2
storage 1/logs/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
2
storage 1/pail/.gitignore
vendored
Executable file
2
storage 1/pail/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
0
storage/app/.gitignore
vendored
Normal file → Executable file
0
storage/app/.gitignore
vendored
Normal file → Executable file
0
storage/app/private/.gitignore
vendored
Normal file → Executable file
0
storage/app/private/.gitignore
vendored
Normal file → Executable file
0
storage/app/public/.gitignore
vendored
Normal file → Executable file
0
storage/app/public/.gitignore
vendored
Normal file → Executable file
0
storage/framework/.gitignore
vendored
Normal file → Executable file
0
storage/framework/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/data/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/data/.gitignore
vendored
Normal file → Executable file
0
storage/framework/sessions/.gitignore
vendored
Normal file → Executable file
0
storage/framework/sessions/.gitignore
vendored
Normal file → Executable file
0
storage/framework/testing/.gitignore
vendored
Normal file → Executable file
0
storage/framework/testing/.gitignore
vendored
Normal file → Executable file
0
storage/framework/views/.gitignore
vendored
Normal file → Executable file
0
storage/framework/views/.gitignore
vendored
Normal file → Executable file
0
storage/logs/.gitignore
vendored
Normal file → Executable file
0
storage/logs/.gitignore
vendored
Normal file → Executable file
Loading…
x
Reference in New Issue
Block a user