bega updates

This commit is contained in:
Mekan1206
2026-07-21 21:30:27 +05:00
parent 6093a45761
commit db94922e9a
22 changed files with 1033 additions and 26 deletions

View File

@@ -12,6 +12,21 @@ use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
/**
* Translate
*/
function tr(string $text, string $locale = 'tk'): string
{
$text = json_decode($text);
if ($text) {
return $text->{$locale} ?? '';
}
return '';
}
if (! function_exists('modules_path')) {
/**
* Single translation for all locales
@@ -32,20 +47,6 @@ if (! function_exists('translatable')) {
}
}
/**
* Translate
*/
function tr(string $text, string $locale = 'tk'): string
{
$text = json_decode($text);
if ($text) {
return $text->{$locale} ?? '';
}
return '';
}
if (! function_exists('removeWhiteSpace')) {
/**
* Remove white sapce from string
@@ -105,22 +106,59 @@ if (! function_exists('sendSMS')) {
}
}
if (! function_exists('normalizeTurkmenPhoneNumber')) {
/**
* Normalize a Turkmen phone number to the local 8-digit format.
*/
function normalizeTurkmenPhoneNumber(null|string|int $phone): null|string
{
if ($phone === null) {
return null;
}
$phone = preg_replace('/\D+/', '', (string) $phone);
if (str_starts_with($phone, '993') && strlen($phone) === 11) {
return substr($phone, 3);
}
return $phone;
}
}
if (! function_exists('isOtpBypass')) {
/**
* Fixed OTP bypass for the approved test phone number.
*/
function isOtpBypass(null|string|int $phone, null|string|int $code): bool
{
return normalizeTurkmenPhoneNumber($phone) === '65222548' && (string) $code === '12212';
}
}
if (! function_exists('sendSMSVerification')) {
/**
* Send a sms verification code
*
* @return App\Models\Verification | null
* @return \App\Models\Verification | null
*/
function sendSMSVerification(string|int $phone_number): ?Verification
{
$phone_number = normalizeTurkmenPhoneNumber($phone_number);
/* for apple testing */
$phone_code = ($phone_number == '61126667') ? 77777 : rand(10000, 99999);
$phone_code = ($phone_number == '65222548') ? 12212 : $phone_code;
$verification = Verification::where(['username' => $phone_number])->first();
$verification
? $verification->update(['code' => $phone_code])
: Verification::create(['username' => $phone_number, 'code' => $phone_code]);
if (isOtpBypass($phone_number, $phone_code)) {
return Verification::where(['username' => $phone_number])->first();
}
sendSMS($phone_number, 'Tassyklaýyş belgi: '.$phone_code);
return $verification;
@@ -181,7 +219,7 @@ if (! function_exists('tmpostChannel')) {
/**
* Default channel
*
* @return App\Models\Shop\Channel
* @return \App\Models\Shop\Channel
*/
function tmpostChannel(): Channel
{
@@ -337,7 +375,7 @@ if (! function_exists('orderAdminNumber')) {
*/
function orderAdminNumber(): int
{
return 61126667;
return 65223722;
}
}

View File

@@ -2,15 +2,24 @@
namespace App\Http\Controllers\Api\V1\Auth\Register;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class AuthRegisterRequest extends FormRequest
{
/**
* Prepare the data for validation.
*/
protected function prepareForValidation(): void
{
$this->merge([
'phone_number' => normalizeTurkmenPhoneNumber($this->phone_number),
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{

View File

@@ -17,6 +17,10 @@ class EntrepreneurAuthController extends Controller
{
public function register(Request $request)
{
$request->merge([
'phone_number' => normalizeTurkmenPhoneNumber($request->phone_number),
]);
$validator = Validator::make($request->all(), [
'first_name' => ['required', 'string'],
'phone_number' => ['required', 'integer', 'between:61000000,71999999', 'unique:users,phone_number'],
@@ -53,13 +57,19 @@ class EntrepreneurAuthController extends Controller
public function verifyPhoneNumber(Request $request)
{
$request->merge([
'phone_number' => normalizeTurkmenPhoneNumber($request->phone_number),
]);
$validator = Validator::make($request->all(), ['phone_number' => 'required|integer|between:61000000,65999999', 'code' => 'required|string']);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, 'Wrong credentials');
}
$verification = Verification::where('username', $request->phone_number)->where('code', $request->code)->first();
$verification = isOtpBypass($request->phone_number, $request->code)
? true
: Verification::where('username', $request->phone_number)->where('code', $request->code)->first();
if (! $verification) {
return response()->rest([], 400, 'Wrong credentials');

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Controllers\Api\V1\FlashSale;
use App\Http\Controllers\Api\V1\FlashSale\Resources\FlashSaleResource;
use App\Http\Controllers\Controller;
use App\Models\CMS\Marketing\FlashSale;
use Illuminate\Http\JsonResponse;
class FlashSaleController extends Controller
{
/**
* Active flash sales.
*/
public function index(): JsonResponse
{
return response()->rest(
FlashSaleResource::collection(
FlashSale::query()
->active()
->with(['products' => function ($query) {
$query
->with(['brand', 'media', 'reviews'])
->where('products.is_visible', true)
->where('products.parent_id', null)
->where('products.stock', '>', 0);
}])
->latest('starts_at')
->get()
)
);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Api\V1\FlashSale\Resources;
use App\Http\Controllers\Api\V1\Product\Resources\ProductIndexResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class FlashSaleResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->whenHas('id'),
'title' => $this->whenHas('title'),
'starts_at' => $this->starts_at?->toISOString(),
'ends_at' => $this->ends_at?->toISOString(),
'products' => ProductIndexResource::collection($this->whenLoaded('products')),
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\Api\V1\Story\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class StoryResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->whenHas('id'),
'title' => $this->whenHas('title'),
'photo' => $this->photo(),
'thumbnail' => $this->photo('thumb720x1280'),
'expires_at' => $this->expires_at?->toISOString(),
];
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Api\V1\Story;
use App\Http\Controllers\Api\V1\Story\Resources\StoryResource;
use App\Http\Controllers\Controller;
use App\Models\CMS\Media\Story;
use Illuminate\Http\JsonResponse;
class StoryController extends Controller
{
/**
* (Media) Active stories.
*/
public function index(): JsonResponse
{
return response()->rest(
StoryResource::collection(
Story::with('media')
->active()
->ordered()
->get()
)
);
}
}

View File

@@ -2,15 +2,24 @@
namespace App\Http\Requests\Api\V1\Auth;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class AuthLoginRequest extends FormRequest
{
/**
* Prepare the data for validation.
*/
protected function prepareForValidation(): void
{
$this->merge([
'phone_number' => normalizeTurkmenPhoneNumber($this->phone_number),
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{

View File

@@ -3,15 +3,24 @@
namespace App\Http\Requests\Api\V1\Auth;
use App\Rules\VerificationRule;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class AuthVerifyRequest extends FormRequest
{
/**
* Prepare the data for validation.
*/
protected function prepareForValidation(): void
{
$this->merge([
'phone_number' => normalizeTurkmenPhoneNumber($this->phone_number),
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Models\CMS\Marketing;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Spatie\Translatable\HasTranslations;
class FlashSale extends Model
{
use HasFactory;
use HasTranslations;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'id',
'title',
'starts_at',
'ends_at',
'is_visible',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'starts_at' => 'datetime',
'ends_at' => 'datetime',
'is_visible' => 'boolean',
];
/**
* Translatable attributes.
*
* @var array<string>
*/
public $translatable = ['title'];
/**
* Related products.
*/
public function products(): BelongsToMany
{
return $this->belongsToMany(Product::class)
->withTimestamps();
}
/**
* Active flash sales are visible and inside their timer window.
*/
public function scopeActive(Builder $query): Builder
{
return $query
->where('is_visible', true)
->where('starts_at', '<=', now())
->where('ends_at', '>', now());
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace App\Models\CMS\Media;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\EloquentSortable\SortableTrait;
use Spatie\Image\Manipulations;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\Translatable\HasTranslations;
class Story extends Model implements HasMedia
{
use HasFactory;
use HasTranslations;
use InteractsWithMedia;
use SortableTrait;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'id',
'title',
'expires_at',
'sort_order',
'is_visible',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'expires_at' => 'datetime',
'is_visible' => 'boolean',
];
/**
* Translatable attributes.
*
* @var array<string>
*/
public $translatable = ['title'];
/**
* Sortable attributes.
*
* @var array<string, mixed>
*/
public $sortable = [
'order_column_name' => 'sort_order',
'sort_when_creating' => true,
];
/**
* The "booted" method of the model.
*/
protected static function booted(): void
{
static::saving(function (Story $story) {
if (blank($story->expires_at)) {
$story->expires_at = now()->addDay();
}
});
}
/**
* Media collections.
*/
public function registerMediaCollections(): void
{
$this->addMediaCollection('photo')
->singleFile();
}
/**
* Media conversions.
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb720x1280')
->fit(Manipulations::FIT_CONTAIN, 720, 1280);
}
/**
* Active stories are visible and not expired.
*/
public function scopeActive(Builder $query): Builder
{
return $query
->where('is_visible', true)
->where('expires_at', '>', now());
}
/**
* Story photo URL.
*/
public function photo(?string $conversion = null): string
{
if ($conversion) {
return $this->getFirstMediaUrl('photo', $conversion);
}
return $this->getFirstMediaUrl('photo');
}
}

View File

@@ -0,0 +1,175 @@
<?php
namespace App\Nova\Resources\CMS\Marketing;
use App\Models\CMS\Marketing\FlashSale as FlashSaleModel;
use App\Nova\Forms\NovaForm;
use App\Nova\Resource;
use App\Nova\Resources\Ecommerce\Product\Product\Product;
use Illuminate\Database\Eloquent\Model;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Panel;
use Outl1ne\MultiselectField\Multiselect;
use Trin4ik\NovaSwitcher\NovaSwitcher;
class FlashSale extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<FlashSaleModel>
*/
public static $model = FlashSaleModel::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'id';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = [
'title',
];
/**
* The relationships that should be eager loaded on index queries.
*
* @var array
*/
public static $with = ['products'];
/**
* Get the displayable label of the resource.
*/
public static function label(): string
{
return __('Flash sales');
}
/**
* Get the displayable singular label of the resource.
*/
public static function singularLabel(): string
{
return __('Flash sale');
}
/**
* Get the fields displayed by the resource.
*/
public function fields(NovaRequest $request): array
{
return [
new Panel(__('Basic information'), [
ID::make()->sortable(),
Text::make(__('Title'), 'title')
->rules('required')
->translatable(),
DateTime::make(__('Starts at'), 'starts_at')
->rules('required', 'date')
->default(now()),
DateTime::make(__('Ends at'), 'ends_at')
->rules('required', 'date', 'after:starts_at')
->default(now()->addDay()),
Text::make(__('Products count'), fn () => $this->products->count())
->exceptOnForms(),
Text::make(__('Products'), fn () => $this->products->pluck('name')->implode(', '))
->onlyOnDetail(),
Multiselect::make(__('Products'), 'products')
->asyncResource(Product::class)
->placeholder(__('Search by id, name, sku, barcode...'))
->rules('required')
->resolveUsing(fn () => $this->products->pluck('id')->toArray())
->fillUsing(NovaForm::fillEmpty())
->onlyOnForms(),
NovaSwitcher::make(__('Visible'), 'is_visible')
->default(true)
->sortable(),
]),
];
}
/**
* Register a callback to be called after the resource is created.
*/
public static function afterCreate(NovaRequest $request, Model $model): void
{
static::syncProducts($request, $model);
}
/**
* Register a callback to be called after the resource is updated.
*/
public static function afterUpdate(NovaRequest $request, Model $model): void
{
static::syncProducts($request, $model);
}
/**
* Sync selected products.
*/
protected static function syncProducts(NovaRequest $request, Model $model): void
{
$products = $request->input('products', []);
if (is_string($products)) {
$products = str_contains($products, ',') ? explode(',', $products) : [$products];
}
$products = collect($products)
->filter()
->map(fn ($product) => intval($product))
->values()
->toArray();
$model->products()->sync($products);
}
/**
* Get the cards available for the request.
*/
public function cards(NovaRequest $request): array
{
return [];
}
/**
* Get the filters available for the resource.
*/
public function filters(NovaRequest $request): array
{
return [];
}
/**
* Get the lenses available for the resource.
*/
public function lenses(NovaRequest $request): array
{
return [];
}
/**
* Get the actions available for the resource.
*/
public function actions(NovaRequest $request): array
{
return [];
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace App\Nova\Resources\CMS\Media;
use App\Models\CMS\Media\Story as StoryModel;
use App\Nova\Filters\VisableFilter;
use App\Nova\Resource;
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Panel;
use Outl1ne\NovaSortable\Traits\HasSortableRows;
use Trin4ik\NovaSwitcher\NovaSwitcher;
class Story extends Resource
{
/**
* Sortable behavior.
*/
use HasSortableRows;
/**
* The model the resource corresponds to.
*
* @var class-string<StoryModel>
*/
public static $model = StoryModel::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'id';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = [
'title',
];
/**
* The relationships that should be eager loaded on index queries.
*
* @var array
*/
public static $with = ['media'];
/**
* Get the displayable label of the resource.
*/
public static function label(): string
{
return __('Stories');
}
/**
* Get the displayable singular label of the resource.
*/
public static function singularLabel(): string
{
return __('Story');
}
/**
* Get the fields displayed by the resource.
*/
public function fields(NovaRequest $request): array
{
return [
new Panel(__('Basic information'), [
ID::make()->sortable(),
Images::make(__('Photo'), 'photo')
->conversionOnIndexView('thumb200x200')
->rules('required')
->required(),
Text::make(__('Title'), 'title')
->rules('required')
->translatable(),
DateTime::make(__('Expires at'), 'expires_at')
->default(now()->addDay())
->nullable()
->rules('nullable', 'date')
->help(__('Default is 24 hours from creation. Set a specific date and hour to control when the story disappears from the mobile app.')),
NovaSwitcher::make(__('Visible'), 'is_visible')
->default(true)
->sortable(),
]),
];
}
/**
* Get the cards available for the request.
*/
public function cards(NovaRequest $request): array
{
return [];
}
/**
* Get the filters available for the resource.
*/
public function filters(NovaRequest $request): array
{
return [
new VisableFilter(),
];
}
/**
* Get the lenses available for the resource.
*/
public function lenses(NovaRequest $request): array
{
return [];
}
/**
* Get the actions available for the resource.
*/
public function actions(NovaRequest $request): array
{
return [];
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace App\Policies\CMS\Marketing;
use App\Models\CMS\Marketing\FlashSale;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Auth\Access\Response;
class FlashSalePolicy
{
use HandlesAuthorization;
/**
* Perform pre-authorization checks.
*/
public function before(User $user, string $ability): ?Response
{
if ($user->isMe()) {
return $this->allow();
}
return null;
}
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, FlashSale $flashSale): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, FlashSale $flashSale): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, FlashSale $flashSale): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, FlashSale $flashSale): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, FlashSale $flashSale): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace App\Policies\CMS\Media;
use App\Models\CMS\Media\Story;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Auth\Access\Response;
class StoryPolicy
{
use HandlesAuthorization;
/**
* Perform pre-authorization checks.
*/
public function before(User $user, string $ability): ?Response
{
if ($user->isMe()) {
return $this->allow();
}
return null;
}
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Story $story): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Story $story): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Story $story): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Story $story): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Story $story): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
}
return $this->deny();
}
}

View File

@@ -3,11 +3,13 @@
namespace App\Providers;
use App\Models\CMS\Forms\ContactUS;
use App\Models\CMS\Marketing\FlashSale;
use App\Models\CMS\Marketing\Newsletter;
use App\Models\CMS\Marketing\NewsletterUser;
use App\Models\CMS\Media\Banner;
use App\Models\CMS\Media\Carousel;
use App\Models\CMS\Media\Gallery;
use App\Models\CMS\Media\Story;
use App\Models\Ecommerce\Channel\Channel;
use App\Models\Ecommerce\Payouts\Payout;
use App\Models\Ecommerce\Product\Brand\Brand;
@@ -29,11 +31,13 @@ use App\Models\System\Settings\Payments\PaymentType;
use App\Models\System\VersionManagement\AppVersion;
use App\Models\User;
use App\Policies\CMS\Forms\ContactUSPolicy;
use App\Policies\CMS\Marketing\FlashSalePolicy;
use App\Policies\CMS\Marketing\NewsletterPolicy;
use App\Policies\CMS\Marketing\NewsletterUserPolicy;
use App\Policies\CMS\Media\BannerPolicy;
use App\Policies\CMS\Media\CarouselPolicy;
use App\Policies\CMS\Media\GalleryPolicy;
use App\Policies\CMS\Media\StoryPolicy;
use App\Policies\Ecommerce\Channel\ChannelPolicy;
use App\Policies\Ecommerce\Payout\PayoutPolicy;
use App\Policies\Ecommerce\Product\Brand\BrandPolicy;
@@ -106,12 +110,16 @@ class AuthServiceProvider extends ServiceProvider
ContactUS::class => ContactUSPolicy::class,
// Marketing...
FlashSale::class => FlashSalePolicy::class,
Newsletter::class => NewsletterPolicy::class,
NewsletterUser::class => NewsletterUserPolicy::class,
// Banners...
Banner::class => BannerPolicy::class,
// Stories...
Story::class => StoryPolicy::class,
// Permissions (settings)...
Role::class => RolePolicy::class,
Permission::class => PermissionPolicy::class,

View File

@@ -5,11 +5,13 @@ namespace App\Providers;
use App\Modules\GlobalOrder\Nova\Resources\GlobalOrderResource;
use App\Nova\Dashboards\Main;
use App\Nova\Resources\CMS\Forms\ContactUS;
use App\Nova\Resources\CMS\Marketing\FlashSale;
use App\Nova\Resources\CMS\Marketing\Newsletter;
use App\Nova\Resources\CMS\Marketing\NewsletterUser;
use App\Nova\Resources\CMS\Media\Banner;
use App\Nova\Resources\CMS\Media\Carousel;
use App\Nova\Resources\CMS\Media\Gallery;
use App\Nova\Resources\CMS\Media\Story;
use App\Nova\Resources\Ecommerce\Channel\Channel;
use App\Nova\Resources\Ecommerce\Payout\PayoutResource;
use App\Nova\Resources\Ecommerce\Product\Attribute\Attribute;
@@ -180,6 +182,7 @@ class NovaServiceProvider extends NovaApplicationServiceProvider
MenuItem::resource(Banner::class),
MenuItem::resource(Carousel::class),
MenuItem::resource(Gallery::class),
MenuItem::resource(Story::class),
])->collapsedByDefault(),
MenuGroup::make(__('Legal'), [
@@ -188,6 +191,7 @@ class NovaServiceProvider extends NovaApplicationServiceProvider
])->icon('newspaper')->collapsedByDefault(),
MenuSection::make(__('Marketing'), [
MenuItem::resource(FlashSale::class),
MenuItem::resource(NewsletterUser::class),
MenuItem::resource(Newsletter::class),
MenuItem::resource(Coupon::class),

View File

@@ -5,7 +5,6 @@ namespace App\Rules;
use App\Models\Auth\Verification;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Translation\PotentiallyTranslatedString;
class VerificationRule implements ValidationRule
{
@@ -22,7 +21,7 @@ class VerificationRule implements ValidationRule
/**
* Run the validation rule.
*
* @param Closure(string): PotentiallyTranslatedString $fail
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
@@ -30,6 +29,10 @@ class VerificationRule implements ValidationRule
$fail(__('No phone provided'));
}
if (isOtpBypass($this->phone, $value)) {
return;
}
Verification::where('username', $this->phone)
->where('code', $value)
->existsOr(fn () => $fail(__('Write a correct data please')));