Skip to content

Commit

Permalink
Add lottery and other changes
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Nov 19, 2024
1 parent 028fda7 commit 8433f1a
Show file tree
Hide file tree
Showing 14 changed files with 1,565 additions and 4,369 deletions.
123 changes: 123 additions & 0 deletions app/Actions/DetermineBlackFridayRewardAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

namespace App\Actions;

use App\Data\BlackFridayRewardData;
use App\Enums\BlackFridayRewardType;
use App\Models\User;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;

class DetermineBlackFridayRewardAction
{
public function execute(
User $user,
int $day,
?BlackFridayRewardType $predefinedRewardType = null,
): BlackFridayRewardData {
$redeemedReward = BlackFridayRewardData::forUserAndDay($user, $day);

if($redeemedReward) {
return $redeemedReward;
}

$specialRewards = DB::table('bf24_rewards')
->where('available_at', '<=', now())
->where('day', $day)
->whereNotExists(fn (Builder $query) => $query
->select(DB::raw(1))
->from('bf24_redeemed_rewards')
->whereColumn('bf24_rewards.id', 'bf24_redeemed_rewards.reward_id'))
->get();

if ($specialRewards->isNotEmpty()) {
$reward = $specialRewards->random();

return $this->redeemReward(
$user,
$day,
BlackFridayRewardType::from($reward->type),
rewardId: $reward->id
);
}

$rewardTypes = [
BlackFridayRewardType::Flare50Off,
BlackFridayRewardType::Mailcoach50Off,
BlackFridayRewardType::MerchDiscount,
BlackFridayRewardType::NextPurchaseDiscount,
];

/** @var BlackFridayRewardType $rewardType */
$rewardType = $predefinedRewardType ?? Arr::random($rewardTypes);

if (! $rewardType->requiresSaasCode()) {
return $this->redeemNonSaasCodeReward($user, $day, $rewardType);
}

$code = $this->getSaasCode($rewardType);

if ($code) {
return $this->redeemReward($user, $day, $rewardType, code: $code);
}

return $this->redeemNonSaasCodeReward(
$user,
$day,
Arr::random([BlackFridayRewardType::NextPurchaseDiscount, BlackFridayRewardType::MerchDiscount])
);
}

protected function redeemReward(
User $user,
int $day,
BlackFridayRewardType $rewardType,
?int $rewardId = null,
?string $code = null
): BlackFridayRewardData {
DB::table('bf24_redeemed_rewards')->insert([
'user_id' => $user->id,
'day' => $day,
'type' => $rewardType,
'reward_id' => $rewardId,
'code' => $code,
'created_at' => now(),
'updated_at' => now(),
]);

return new BlackFridayRewardData(type: $rewardType, code: $code);
}

protected function redeemNonSaasCodeReward(
User $user,
int $day,
BlackFridayRewardType $rewardType,
): BlackFridayRewardData {
$code = match ($rewardType) {
BlackFridayRewardType::MerchDiscount => config('black-friday.merch_discount_code'),
BlackFridayRewardType::NextPurchaseDiscount => config('black-friday.next_purchase_discount_code'),
default => throw new \Exception('Invalid reward type'),
};

return $this->redeemReward($user, $day, $rewardType, code: $code);
}

protected function getSaasCode(BlackFridayRewardType $rewardType): ?string
{
$code = DB::table('bf24_codes_pool')
->select('id', 'code')
->where('type', $rewardType)
->first();

if ($code === null) {
return null;
}

DB::table('bf24_codes_pool')
->where('id', $code->id)
->delete();

return $code->code;
}
}
54 changes: 54 additions & 0 deletions app/Data/BlackFridayRewardData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Data;

use App\Enums\BlackFridayRewardType;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Livewire\Wireable;

class BlackFridayRewardData implements Wireable
{
public function __construct(
public BlackFridayRewardType $type,
public ?string $code = null,
public bool $enteredRaffle = false,
) {
}

public static function forUserAndDay(User $user, int $day): ?self
{
$redeem = DB::table('bf24_redeemed_rewards')
->where('user_id', $user->id)
->where('day', $day)
->first();

if (! $redeem) {
return null;
}

return new BlackFridayRewardData(
type: BlackFridayRewardType::from($redeem->type),
code: $redeem->code,
enteredRaffle: $redeem->entered_raffle,
);
}

public function toLivewire(): array
{
return [
'type' => $this->type->value,
'code' => $this->code,
'enteredRaffle' => $this->enteredRaffle,
];
}

public static function fromLivewire($value): self
{
return new BlackFridayRewardData(
type: BlackFridayRewardType::from($value['type']),
code: $value['code'],
enteredRaffle: $value['enteredRaffle'],
);
}
}
33 changes: 33 additions & 0 deletions app/Enums/BlackFridayRewardType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace App\Enums;

enum BlackFridayRewardType: string
{
case NextPurchaseDiscount = 'next_purchase_discount';
case MerchDiscount = 'merch_discount';
case Mailcoach50Off = '50_off_mailcoach';
case Flare50Off = '50_off_flare';
case FreeMerch = 'free_merch';
case FreeRay = 'free_ray';

public function requiresSaasCode(): bool
{
return in_array($this, [
self::Mailcoach50Off,
self::Flare50Off,
]);
}

public function wonLabel(): string
{
return match ($this) {
self::NextPurchaseDiscount => 'Use the next coupon to get 20% discount on your next purchase on spatie.be:',
self::MerchDiscount => 'Use the next coupon to get 30% discount on merchandise in our merch store:',
self::Mailcoach50Off => 'Use the next coupon to get 50% off Mailcoach plan:',
self::Flare50Off => 'Use the next coupon to get 50% off Flare plan:',
self::FreeMerch => 'A free piece of Spatie merch, we will contact you to get your details',
self::FreeRay => 'A free yearly Ray license, we will contact you to get your details',
};
}
}
131 changes: 78 additions & 53 deletions app/Livewire/TopSecretComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

namespace App\Livewire;

use App\Actions\DetermineBlackFridayRewardAction;
use App\Data\BlackFridayRewardData;
use App\Exceptions\BlackFridayRewardException;
use Arr;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Locked;
use Livewire\Component;
Expand All @@ -19,21 +22,31 @@ class TopSecretComponent extends Component
#[Locked]
public array $days;

public array $completedDays = [];

#[Locked]
public ?string $question = '';

#[Locked]
public ?string $hint = '';

public string $answer = '';

public ?string $reward = '';
#[Locked]
public ?BlackFridayRewardData $reward = null;

public bool $showReward = false;

public bool $showHint = false;

public function mount(): void
{
$this->days = [
1 => Date::create(2024, 11, 25),
2 => Date::create(2024, 11, 26),
3 => Date::create(2024, 11, 27),
4 => Date::create(2024, 11, 28),
5 => Date::create(2024, 11, 29),
];
$current = CarbonImmutable::parse(config('black-friday.start_date'));

foreach (range(1, 5) as $day) {
$this->days[$day] = $current;
$current = $current->addDay();
}

$this->currentDay = collect($this->days)->search(function (CarbonInterface $date) {
return now()->between($date->startOfDay(), $date->endOfDay());
Expand All @@ -46,73 +59,85 @@ public function setDay(int $currentDay): void
return;
}

/*if ($this->days[$day]->isFuture()) {
if ($this->days[$currentDay]->isFuture()) {
return;
}*/
}

$this->currentDay = $currentDay;
$this->answer = '';
$this->question = '';
}

public function submitAnswer(): void
{
$answer = DB::table('bf24_questions')
$questionRow = DB::table('bf24_questions')
->where('day', $this->currentDay)
->first()
?->answer;
->firstOrFail();

if ($this->answer !== $answer) {
$this->answer = 'This is the wrong solution.';
if ($this->answer !== $questionRow?->answer) {

Check failure on line 77 in app/Livewire/TopSecretComponent.php

View workflow job for this annotation

GitHub Actions / phpstan

Using nullsafe property access on non-nullable type object. Use -> instead.
$this->answer = '';
$this->hint = $questionRow->hint;
$this->showHint = true;

return;
}

Auth::user()->flag("bf-day-{$this->currentDay}");

// We don't want to give rewards for past days
if ($this->days[$this->currentDay]->endOfDay()->isPast()) {
$this->answer = 'This is the correct solution, but the time has run out.';

return;
}

if ($this->days[$this->currentDay]->startOfDay()->isFuture()) {
$this->answer = 'This is the correct solution, but this challenge is not yet open.';

return;
}

/**
* 20% discount on your next purchase on spatie.be
* 30% discount on merchandise on our Merch Store
* 50% off on Mailcoach and Flare plans
* Free Spatie merchandise
* Free yearly licenses for Ray
*/

$reward = Arr::random([
'next_purchase_discount',
'merch_discount',
'50_off_mailcoach',
'50_off_flare',
'free_merch', // 10 / day
'free_ray', // x / day
]);

DB::table('bf24_rewards')->insert([
'user_id' => Auth::user()->id,
'day' => $this->currentDay,
'reward' => $reward,
'created_at' => now(),
'updated_at' => now(),
]);

// TODO: Actual rewards
$this->reward = app(DetermineBlackFridayRewardAction::class)->execute(
Auth::user(),
$this->currentDay
);

$this->showReward = true;
}

public function render(): View
public function enterRaffle(): void
{
$this->question = DB::table('bf24_questions')
if ($this->days[$this->currentDay]->endOfDay()->isPast() || $this->days[$this->currentDay]->startOfDay()->isFuture()) {
return;
}

if ($this->reward === null) {
return;
}

DB::table('bf24_redeemed_rewards')
->where('user_id', Auth::id())
->where('day', $this->currentDay)
->first()
?->question;
->update(['entered_raffle' => true]);
}

$this->reward = DB::table('bf24_rewards')
public function render(): View
{
$questionRow = DB::table('bf24_questions')
->where('day', $this->currentDay)
->where('user_id', Auth::user()->id ?? null)
->first()
?->reward;
->first();

$this->question = $questionRow?->question;

$this->reward = Auth::user()
? BlackFridayRewardData::forUserAndDay(Auth::user(), $this->currentDay)
: null;

$this->completedDays = DB::table('bf24_redeemed_rewards')
->where('user_id', Auth::id())
->pluck('day')
->toArray();

if ($this->reward || $this->days[$this->currentDay]->endOfDay()->isPast()) {
$this->answer = $questionRow->answer;
}

return view('front.pages.top-secret.index')
->layout('layout.blank', [
Expand Down
Loading

0 comments on commit 8433f1a

Please sign in to comment.