forked from TFMM/silent-auction
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58450a1262 | |||
| 878236ae2f | |||
| 05ab1c6014 | |||
| 9065dcd911 | |||
| dcd08ae530 | |||
| d468991bd1 | |||
| 7d1ec59dea | |||
| 24d1eda717 | |||
| 9d981f3a34 | |||
| b9c018a4a6 | |||
| 23b0b30434 | |||
| 4b9220c89e | |||
| 298f3fa22b | |||
| 98be7131f4 | |||
| 999c1dfc58 | |||
| a3fd278536 | |||
| 290498c728 | |||
| aca21ae115 | |||
| 982efbf2bd | |||
| 3953a6f4c8 | |||
| fbb10a01d4 | |||
| 6ccb754e0c | |||
| 50826c8c20 | |||
| 68f75ac2fc | |||
| 9587c44657 | |||
| 06de3c0145 | |||
| d2d53e961b | |||
| b0816231d6 | |||
| e4cccba942 | |||
| e023f68a84 | |||
| 4edb7fac10 | |||
| 19f93f95bb | |||
| 885161cc2b | |||
| a8fd03f256 | |||
| 00ff08e3a6 | |||
| 7e8b35bf20 | |||
| 681d8b73d6 | |||
| f294ded6e8 | |||
| eca564eac7 | |||
| e8df889f7e | |||
| ad02f65b54 | |||
| c2c9464133 | |||
| c0176f9924 | |||
| ffa8d30be4 | |||
| d64e1d5578 | |||
| ad66fef19b | |||
| 5a7684bc8c | |||
| aed29fbf56 | |||
| 816fba5121 | |||
| ddbfc642f2 |
@@ -33,3 +33,13 @@ MAIL_FROM_NAME=null
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
|
||||
OIDC_BASE_URL=
|
||||
OIDC_CLIENT_ID=
|
||||
OIDC_CLIENT_SECRET=
|
||||
OIDC_REDIRECT_URI="${APP_URL}/auth/social/oidc/callback"
|
||||
|
||||
NORTH_CHECKOUT_ID=
|
||||
NORTH_PROFILE_ID=
|
||||
NORTH_PRIVATE_API_KEY=
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"version":2,"defects":{"Tests\\Feature\\MyWinningsTest::test_mywinnings_form_is_accessible":8,"Tests\\Feature\\MyWinningsTest::test_mywinnings_results_show_correct_data":8,"Tests\\Feature\\MyWinningsTest::test_mywinnings_invalid_bidder_shows_error":8},"times":{"Tests\\Unit\\PhoneFormattingTest::it_formats_a_10_digit_phone_number":0.018,"Tests\\Unit\\PhoneFormattingTest::it_strips_non_numeric_characters_before_formatting":0,"Tests\\Unit\\PhoneFormattingTest::it_returns_original_value_if_not_10_digits":0,"Tests\\Unit\\PhoneFormattingTest::bidder_model_accessor_formats_phone_number":0.003}}
|
||||
@@ -57,7 +57,8 @@ class BiddersResource extends Resource
|
||||
TextColumn::make('bidder_city')->label('City')->sortable(),
|
||||
TextColumn::make('bidder_state')->label('State')->sortable(),
|
||||
TextColumn::make('bidder_zip')->label('Zip')->sortable(),
|
||||
TextColumn::make('bidder_phone')->label('Phone Number')->sortable(),
|
||||
TextColumn::make('bidder_phone')->label('Phone Number')->sortable()
|
||||
->formatStateUsing(fn ($state) => \App\Helpers\PhoneHelper::format($state)),
|
||||
TextColumn::make('bidder_email')->label('Email')->sortable(),
|
||||
])
|
||||
->filters([
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\User;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use BackedEnum;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
protected static string | BackedEnum | null $navigationIcon = 'heroicon-o-users';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('email')
|
||||
->email()
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('provider_name')
|
||||
->label('OIDC Provider')
|
||||
->disabled(),
|
||||
TextInput::make('provider_id')
|
||||
->label('OIDC ID')
|
||||
->disabled(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')->sortable()->searchable(),
|
||||
TextColumn::make('email')->sortable()->searchable(),
|
||||
TextColumn::make('provider_name')->label('OIDC Provider'),
|
||||
TextColumn::make('provider_id')->label('OIDC ID'),
|
||||
TextColumn::make('created_at')->dateTime()->sortable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => UserResource\Pages\ListUsers::route('/'),
|
||||
'create' => UserResource\Pages\CreateUsers::route('/create'),
|
||||
'edit' => UserResource\Pages\EditUsers::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateUsers extends CreateRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditUsers extends EditRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace App\Helpers;
|
||||
|
||||
class PhoneHelper
|
||||
{
|
||||
public static function format($phone)
|
||||
{
|
||||
$phone = preg_replace('/[^0-9]/', '', $phone);
|
||||
if (strlen($phone) == 10) {
|
||||
return preg_replace('/([0-9]{3})([0-9]{3})([0-9]{4})/', '$1-$2-$3', $phone);
|
||||
}
|
||||
return $phone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SocialiteController extends Controller
|
||||
{
|
||||
public function redirectToProvider($provider)
|
||||
{
|
||||
return Socialite::driver($provider)->redirect();
|
||||
}
|
||||
|
||||
public function handleProviderCallback($provider)
|
||||
{
|
||||
try {
|
||||
$socialUser = Socialite::driver($provider)->user();
|
||||
} catch (\Exception $e) {
|
||||
return redirect('/login')->with('error', 'Authentication failed.');
|
||||
}
|
||||
|
||||
if (Auth::check()) {
|
||||
$user = Auth::user();
|
||||
$user->update([
|
||||
'provider_name' => $provider,
|
||||
'provider_id' => $socialUser->getId(),
|
||||
]);
|
||||
|
||||
return redirect()->intended('/admin')->with('status', 'Account linked successfully!');
|
||||
}
|
||||
|
||||
// Find user by provider_id
|
||||
$user = User::where('provider_name', $provider)
|
||||
->where('provider_id', $socialUser->getId())
|
||||
->first();
|
||||
|
||||
if (!$user) {
|
||||
// Find user by email to link
|
||||
$user = User::where('email', $socialUser->getEmail())->first();
|
||||
|
||||
if ($user) {
|
||||
// Link the account
|
||||
$user->update([
|
||||
'provider_name' => $provider,
|
||||
'provider_id' => $socialUser->getId(),
|
||||
]);
|
||||
} else {
|
||||
// Optionally create a new user
|
||||
$user = User::create([
|
||||
'name' => $socialUser->getName() ?? $socialUser->getNickname() ?? $socialUser->getEmail(),
|
||||
'email' => $socialUser->getEmail(),
|
||||
'provider_name' => $provider,
|
||||
'provider_id' => $socialUser->getId(),
|
||||
'password' => bcrypt(Str::random(24)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return redirect()->intended('/admin'); // Redirect to filament admin or home
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Bidders;
|
||||
use App\Models\Checkout;
|
||||
use App\Models\WinningBids;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NorthCheckoutController extends Controller
|
||||
{
|
||||
public function checkout(Request $request, $bidder_id)
|
||||
{
|
||||
$bidder = Bidders::where('idbidders', $bidder_id)->firstOrFail();
|
||||
|
||||
// Check if already checked out
|
||||
if (Checkout::where('bidder_num', $bidder->idbidders)->exists()) {
|
||||
return redirect('/mywinnings?bidder_number=' . $bidder->bidder_assigned_number)->with('error', 'Bidder has already checked out.');
|
||||
}
|
||||
|
||||
$winnings = WinningBids::where('winning_bidder_num', $bidder->idbidders)->get();
|
||||
$total_cost = $winnings->sum('winning_cost');
|
||||
|
||||
if ($total_cost <= 0) {
|
||||
return redirect('/mywinnings?bidder_number=' . $bidder->bidder_assigned_number)->with('error', 'No winnings found for this bidder.');
|
||||
}
|
||||
|
||||
return view('north_checkout', [
|
||||
'bidder' => $bidder,
|
||||
'total_cost' => $total_cost,
|
||||
]);
|
||||
}
|
||||
|
||||
public function createSession(Request $request, $bidder_id)
|
||||
{
|
||||
$bidder = Bidders::findOrFail($bidder_id);
|
||||
$winnings = WinningBids::with('items')->where('winning_bidder_num', $bidder->idbidders)->get();
|
||||
$total_cost = $winnings->sum('winning_cost');
|
||||
|
||||
$products = $winnings->map(function($winning) {
|
||||
return [
|
||||
'name' => $winning->items->item_desc ?? 'Auction Item',
|
||||
'price' => (float)$winning->winning_cost,
|
||||
'quantity' => 1
|
||||
];
|
||||
})->toArray();
|
||||
|
||||
$apiKey = config('services.north.private_api_key');
|
||||
$checkoutId = config('services.north.checkout_id');
|
||||
$profileId = config('services.north.profile_id');
|
||||
|
||||
if (!$apiKey || !$checkoutId || !$profileId) {
|
||||
return response()->json(['error' => 'North configuration missing.'], 500);
|
||||
}
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
])->post('https://checkout.north.com/api/sessions', [
|
||||
'checkoutId' => $checkoutId,
|
||||
'profileId' => $profileId,
|
||||
'amount' => (float)$total_cost,
|
||||
'products' => $products,
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::error('North Session Creation Failed: ' . $response->status() . ' ' . $response->body());
|
||||
return response()->json([
|
||||
'error' => 'Failed to create checkout session: ' . ($response->json('message') ?? 'Unknown error'),
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body()
|
||||
], 500);
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
// If json() is null but body is not empty, it might be a parsing error
|
||||
if (is_null($data) && !empty($response->body())) {
|
||||
Log::error('North Session Response is not JSON: ' . $response->body());
|
||||
return response()->json([
|
||||
'error' => 'Response from North is not valid JSON.',
|
||||
'raw_body' => $response->body()
|
||||
], 500);
|
||||
}
|
||||
|
||||
$token = $data['token'] ??
|
||||
$data['sessionToken'] ??
|
||||
$data['id'] ??
|
||||
$data['session_id'] ??
|
||||
($data['session']['id'] ?? null);
|
||||
|
||||
if (!$token) {
|
||||
Log::error('North Session Token Missing in Response: ' . json_encode($data));
|
||||
return response()->json([
|
||||
'error' => 'Session token not found in API response.',
|
||||
'debug_response' => $data,
|
||||
'raw_body' => $response->body()
|
||||
], 500);
|
||||
}
|
||||
|
||||
return response()->json(['sessionToken' => $token]);
|
||||
}
|
||||
|
||||
public function verify(Request $request, $bidder_id)
|
||||
{
|
||||
$bidder = Bidders::findOrFail($bidder_id);
|
||||
$sessionToken = $request->query('sessionToken');
|
||||
if (!$sessionToken) {
|
||||
return redirect('/mywinnings?bidder_number=' . $bidder->bidder_assigned_number)->with('error', 'Missing session token.');
|
||||
}
|
||||
|
||||
$apiKey = config('services.north.private_api_key');
|
||||
$checkoutId = config('services.north.checkout_id');
|
||||
$profileId = config('services.north.profile_id');
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
'SessionToken' => $sessionToken,
|
||||
'CheckoutId' => $checkoutId,
|
||||
'ProfileId' => $profileId,
|
||||
'Accept' => 'application/json',
|
||||
])->get('https://checkout.north.com/api/sessions/status');
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::error('North Session Verification Failed: ' . $response->status() . ' ' . $response->body());
|
||||
return redirect('/mywinnings?bidder_number=' . $bidder->bidder_assigned_number)->with('error', 'Failed to verify payment status. Status: ' . $response->status());
|
||||
}
|
||||
|
||||
$status = $response->json();
|
||||
$currentStatus = $status['status'] ?? '';
|
||||
|
||||
// The North API status check might return Approved, completed, success, authorized, or captured.
|
||||
$successStatuses = ['approved', 'completed', 'success', 'authorized', 'captured'];
|
||||
|
||||
if (in_array(strtolower($currentStatus), $successStatuses)) {
|
||||
// Check if already checked out to avoid duplicates
|
||||
$existingCheckout = Checkout::where('bidder_num', $bidder->idbidders)->first();
|
||||
|
||||
if (!$existingCheckout) {
|
||||
// According to docs, when status is Approved, transaction details are in 'body'
|
||||
// Digital wallets might have these at the top level
|
||||
$body = $status['body'] ?? [];
|
||||
$winnertotal = $status['amount'] ??
|
||||
($body['amount'] ??
|
||||
($status['amount_total'] ??
|
||||
WinningBids::where('winning_bidder_num', $bidder->idbidders)->sum('winning_cost')));
|
||||
|
||||
$payment_method = 3; // Credit Card
|
||||
$cc_transaction = $body['auth_guid'] ??
|
||||
($status['transaction_id'] ??
|
||||
($status['transactionId'] ??
|
||||
($status['id'] ?? 'NORTH_EC')));
|
||||
|
||||
$cc_amount = $winnertotal;
|
||||
$check_number = null;
|
||||
|
||||
$checkout_id = DB::table('checkout')->insertGetID(
|
||||
[
|
||||
'bidder_num' => $bidder->idbidders,
|
||||
'winnertotal' => $winnertotal,
|
||||
'payment_method' => $payment_method,
|
||||
'check_number' => $check_number,
|
||||
'cc_transaction' => $cc_transaction,
|
||||
'cc_amount' => $cc_amount,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$checkout_id = $existingCheckout->checkout_id;
|
||||
$payment_method = $existingCheckout->payment_method;
|
||||
$cc_transaction = $existingCheckout->cc_transaction;
|
||||
$check_number = $existingCheckout->check_number;
|
||||
}
|
||||
|
||||
// Replicate the data for checkout_complete view
|
||||
$checkout_list_results = DB::select("SELECT
|
||||
*, items.item_assigned_num, items.item_desc
|
||||
FROM winning_bids
|
||||
INNER JOIN items AS items
|
||||
ON winning_bids.winning_item_num=items.iditems
|
||||
WHERE winning_bidder_num = $bidder->idbidders
|
||||
");
|
||||
|
||||
$checkout_info_results = DB::select("SELECT
|
||||
winning_bids.*,
|
||||
bidders.*,
|
||||
sum(winning_cost) AS total_cost
|
||||
FROM winning_bids
|
||||
INNER JOIN bidders AS bidders
|
||||
ON winning_bids.winning_bidder_num=bidders.idbidders
|
||||
WHERE winning_bidder_num = $bidder->idbidders
|
||||
GROUP BY winning_bids.winning_bidder_num
|
||||
");
|
||||
|
||||
return view('checkout_complete', [
|
||||
'checkout_result' => $checkout_id,
|
||||
'checkout_list_results' => $checkout_list_results,
|
||||
'checkout_info_results' => $checkout_info_results,
|
||||
'payment_method' => $payment_method,
|
||||
'check_number' => $check_number,
|
||||
'cc_transaction' => $cc_transaction
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect('/mywinnings?bidder_number=' . $bidder->bidder_assigned_number)->with('error', 'Payment not completed. Status: ' . ($status['status'] ?? 'unknown'));
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use View;
|
||||
use App\helpers;
|
||||
use PDF;
|
||||
use Spatie\LaravelPdf\Facades\Pdf;
|
||||
use App\Models\Bidders;
|
||||
use App\Models\Items;
|
||||
use App\Models\Checkout;
|
||||
@@ -19,7 +19,7 @@ use App\Models\CarShowCategory;
|
||||
use App\Models\Types;
|
||||
use App\Models\Vehicles;
|
||||
use App\Models\VehicleScores;
|
||||
|
||||
use Dompdf\Dompdf;
|
||||
class PagesController extends Controller
|
||||
{
|
||||
public function home()
|
||||
@@ -250,6 +250,49 @@ class PagesController extends Controller
|
||||
return view('receiptpdf', $checkout_data);
|
||||
}
|
||||
|
||||
public function downloadReceiptPdf(Request $request)
|
||||
{
|
||||
$checkoutid = $request->checkout_id;
|
||||
$checkout_final_results = Checkout::where('checkout_id', '=', $checkoutid)->first();
|
||||
if (!$checkout_final_results) {
|
||||
return redirect('/mywinnings')->with('error', 'Checkout record not found.');
|
||||
}
|
||||
|
||||
$bidder_num = $checkout_final_results->bidder_num;
|
||||
$checkout_list_results = DB::select("SELECT
|
||||
*, items.item_assigned_num, items.item_desc
|
||||
FROM winning_bids
|
||||
INNER JOIN items AS items ON winning_bids.winning_item_num=items.iditems
|
||||
WHERE winning_bidder_num = $bidder_num
|
||||
");
|
||||
$checkout_info_results = DB::select("SELECT
|
||||
winning_bids.*,
|
||||
bidders.*,
|
||||
sum(winning_cost) AS total_cost
|
||||
FROM winning_bids
|
||||
INNER JOIN bidders AS bidders ON winning_bids.winning_bidder_num=bidders.idbidders
|
||||
WHERE winning_bidder_num = $bidder_num
|
||||
GROUP BY winning_bids.winning_bidder_num
|
||||
");
|
||||
|
||||
$options = new \Dompdf\Options();
|
||||
$options->set('isHtml5ParserEnabled', true);
|
||||
$options->set('isRemoteEnabled', true);
|
||||
$dompdf = new Dompdf($options);
|
||||
|
||||
$html = view('receiptpdf', [
|
||||
'checkout_final_results' => $checkout_final_results,
|
||||
'checkout_list_results' => $checkout_list_results,
|
||||
'checkout_info_results' => $checkout_info_results
|
||||
])->render();
|
||||
|
||||
$dompdf->loadHtml($html);
|
||||
$dompdf->setPaper('letter', 'portrait');
|
||||
$dompdf->render();
|
||||
|
||||
return $dompdf->stream('receipt-'.$checkoutid.'.pdf');
|
||||
}
|
||||
|
||||
public function reprintReceipt(Request $reprint_receipt_req)
|
||||
{
|
||||
if (!$reprint_receipt_req->reprintbiddernum) {
|
||||
@@ -535,10 +578,13 @@ class PagesController extends Controller
|
||||
|
||||
$total_cost = $winnings->sum('winning_cost');
|
||||
|
||||
$is_checked_out = \App\Models\Checkout::where('bidder_num', $bidder->idbidders)->exists();
|
||||
|
||||
return view('mywinnings_results', [
|
||||
'bidder' => $bidder,
|
||||
'winnings' => $winnings,
|
||||
'total_cost' => $total_cost
|
||||
'total_cost' => $total_cost,
|
||||
'is_checked_out' => $is_checked_out
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ class EventServiceProvider extends ServiceProvider
|
||||
'App\Events\Event' => [
|
||||
'App\Listeners\EventListener',
|
||||
],
|
||||
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
|
||||
\SocialiteProviders\OIDC\OIDCExtendSocialite::class.'@handle',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,8 +18,41 @@ use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
use Filament\Support\Facades\FilamentView;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
|
||||
use Filament\Navigation\MenuItem;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function boot()
|
||||
{
|
||||
FilamentView::registerRenderHook(
|
||||
'panels::auth.login.form.after',
|
||||
fn (): string => Blade::render('
|
||||
<div class="mt-4">
|
||||
<x-filament::button
|
||||
:href="url(\'auth/social/oidc\')"
|
||||
tag="a"
|
||||
color="info"
|
||||
class="w-full"
|
||||
>
|
||||
Login with OIDC
|
||||
</x-filament::button>
|
||||
</div>
|
||||
'),
|
||||
);
|
||||
|
||||
Filament::registerUserMenuItems([
|
||||
MenuItem::make()
|
||||
->label('Link OIDC Account')
|
||||
->icon('heroicon-o-link')
|
||||
->url(fn (): string => url('auth/social/oidc')),
|
||||
]);
|
||||
}
|
||||
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
@@ -27,6 +60,13 @@ class AdminPanelProvider extends PanelProvider
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
->login()
|
||||
->profile()
|
||||
->userMenuItems([
|
||||
'oidc' => MenuItem::make()
|
||||
->label('Link OIDC Account')
|
||||
->icon('heroicon-o-link')
|
||||
->url(fn (): string => url('auth/social/oidc')),
|
||||
])
|
||||
->brandName(env('APP_NAME'))
|
||||
->homeUrl('/')
|
||||
->colors([
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'email', 'password',
|
||||
'name', 'email', 'password', 'provider_name', 'provider_id',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
+11
-4
@@ -5,13 +5,18 @@
|
||||
"license": "MIT",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"php": "^8.3",
|
||||
"barryvdh/laravel-snappy": "^1.0",
|
||||
"carlos-meneses/laravel-mpdf": "^2.1",
|
||||
"dompdf/dompdf": "^3.1",
|
||||
"filament/filament": "^5.0",
|
||||
"laravel/framework": "^11.0",
|
||||
"kovah/laravel-socialite-oidc": "^0.7.0",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/socialite": "^5.26",
|
||||
"laravel/tinker": "^2.9",
|
||||
"laravel/ui": "^4.2"
|
||||
"laravel/ui": "^4.2",
|
||||
"socialiteproviders/manager": "^4.9",
|
||||
"spatie/laravel-pdf": "^2.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.8",
|
||||
@@ -66,5 +71,7 @@
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"optimize-autoloader": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
|
||||
Generated
+1279
-129
File diff suppressed because it is too large
Load Diff
@@ -35,4 +35,17 @@ return [
|
||||
'secret' => env('STRIPE_SECRET'),
|
||||
],
|
||||
|
||||
'oidc' => [
|
||||
'base_url' => env('OIDC_BASE_URL'),
|
||||
'client_id' => env('OIDC_CLIENT_ID'),
|
||||
'client_secret' => env('OIDC_CLIENT_SECRET'),
|
||||
'redirect' => env('OIDC_REDIRECT_URI'),
|
||||
],
|
||||
|
||||
'north' => [
|
||||
'checkout_id' => env('NORTH_CHECKOUT_ID'),
|
||||
'profile_id' => env('NORTH_PROFILE_ID'),
|
||||
'private_api_key' => env('NORTH_PRIVATE_API_KEY'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -23,14 +23,131 @@ $factory->define(App\User::class, function (Faker\Generator $faker) {
|
||||
];
|
||||
});
|
||||
|
||||
//Bidder
|
||||
$factory->define(App\Models\Bidder::class, function (Faker\Generator $faker) {
|
||||
static $password;
|
||||
|
||||
// Bidders
|
||||
$factory->define(App\Models\Bidders::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'name' => $faker->name,
|
||||
'email' => $faker->unique()->safeEmail,
|
||||
'password' => $password ?: $password = bcrypt('secret'),
|
||||
'remember_token' => str_random(10),
|
||||
'bidder_fname' => $faker->firstName,
|
||||
'bidder_lname' => $faker->lastName,
|
||||
'bidder_addr' => $faker->streetAddress,
|
||||
'bidder_city' => $faker->city,
|
||||
'bidder_state' => $faker->stateAbbr,
|
||||
'bidder_zip' => $faker->postcode,
|
||||
'bidder_phone' => $faker->phoneNumber,
|
||||
'bidder_email' => $faker->unique()->safeEmail,
|
||||
'bidder_assigned_number' => $faker->unique()->numberBetween(1, 1000),
|
||||
];
|
||||
});
|
||||
|
||||
// Items
|
||||
$factory->define(App\Models\Items::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'item_desc' => $faker->sentence(3),
|
||||
'item_min_bid' => $faker->numberBetween(10, 100),
|
||||
'item_est_value' => $faker->numberBetween(50, 500),
|
||||
'item_assigned_num' => $faker->unique()->numberBetween(1, 500),
|
||||
];
|
||||
});
|
||||
|
||||
// CarShowCategory
|
||||
$factory->define(App\Models\CarShowCategory::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'category_name' => $faker->words(2, true),
|
||||
'vehicle_type' => $faker->boolean,
|
||||
];
|
||||
});
|
||||
|
||||
// Vehicles
|
||||
$factory->define(App\Models\Vehicles::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'year' => $faker->year,
|
||||
'make' => $faker->company,
|
||||
'model' => $faker->word,
|
||||
'type' => function () {
|
||||
return factory(App\Models\CarShowCategory::class)->create()->id;
|
||||
},
|
||||
'doNotJudge' => $faker->boolean,
|
||||
'owner' => function () {
|
||||
return factory(App\Models\Bidders::class)->create()->bidder_assigned_number;
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
// PaymentMethods
|
||||
$factory->define(App\Models\PaymentMethods::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'pm_name' => $faker->randomElement(['Cash', 'Check', 'Credit Card']),
|
||||
];
|
||||
});
|
||||
|
||||
// Judges
|
||||
$factory->define(App\Models\Judges::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'judge_number' => $faker->unique()->numberBetween(1, 50),
|
||||
];
|
||||
});
|
||||
|
||||
// VehicleScores
|
||||
$factory->define(App\Models\VehicleScores::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'judge' => function () {
|
||||
return factory(App\Models\Judges::class)->create()->id;
|
||||
},
|
||||
'overall_score' => $faker->numberBetween(1, 100),
|
||||
'vehicle' => function () {
|
||||
return factory(App\Models\Vehicles::class)->create()->id;
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
// PeoplesChoice
|
||||
$factory->define(App\Models\PeoplesChoice::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'vehicle' => function () {
|
||||
return factory(App\Models\Vehicles::class)->create()->id;
|
||||
},
|
||||
'pc_count' => $faker->numberBetween(0, 50),
|
||||
];
|
||||
});
|
||||
|
||||
// WinningBids
|
||||
$factory->define(App\Models\WinningBids::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'winning_bidder_num' => function () {
|
||||
return factory(App\Models\Bidders::class)->create()->idbidders;
|
||||
},
|
||||
'winning_cost' => $faker->numberBetween(10, 1000),
|
||||
'winning_item_num' => function () {
|
||||
return factory(App\Models\Items::class)->create()->iditems;
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
// Checkout
|
||||
$factory->define(App\Models\Checkout::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'bidder_num' => function () {
|
||||
return factory(App\Models\Bidders::class)->create()->idbidders;
|
||||
},
|
||||
'winnertotal' => $faker->numberBetween(10, 2000),
|
||||
'payment_method' => function () {
|
||||
return factory(App\Models\PaymentMethods::class)->create()->pm_id;
|
||||
},
|
||||
'check_number' => $faker->optional()->numberBetween(100, 999),
|
||||
'cc_transaction' => $faker->optional()->uuid,
|
||||
'cc_amount' => $faker->optional()->numberBetween(10, 2000),
|
||||
];
|
||||
});
|
||||
|
||||
// CarShowWinner
|
||||
$factory->define(App\Models\CarShowWinner::class, function (Faker\Generator $faker) {
|
||||
return [
|
||||
'vehicle' => function () {
|
||||
return factory(App\Models\Vehicles::class)->create()->id;
|
||||
},
|
||||
'category' => function () {
|
||||
return factory(App\Models\CarShowCategory::class)->create()->id;
|
||||
},
|
||||
'place' => $faker->numberBetween(1, 3),
|
||||
'total_score' => $faker->numberBetween(50, 300),
|
||||
];
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('provider_name')->nullable();
|
||||
$table->string('provider_id')->nullable();
|
||||
$table->unique(['provider_name', 'provider_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropUnique(['provider_name', 'provider_id']);
|
||||
$table->dropColumn(['provider_name', 'provider_id']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Bidders;
|
||||
|
||||
class BiddersTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
factory(Bidders::class, 50)->create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\CarShowCategory;
|
||||
|
||||
class CarShowCategoryTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
factory(CarShowCategory::class, 10)->create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\CarShowWinner;
|
||||
use App\Models\Vehicles;
|
||||
use App\Models\CarShowCategory;
|
||||
|
||||
class CarShowWinnerTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$categories = CarShowCategory::all();
|
||||
$vehicles = Vehicles::all();
|
||||
|
||||
foreach ($categories as $category) {
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
factory(CarShowWinner::class)->create([
|
||||
'category' => $category->id,
|
||||
'vehicle' => $vehicles->random()->id,
|
||||
'place' => $i,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Checkout;
|
||||
use App\Models\Bidders;
|
||||
use App\Models\PaymentMethods;
|
||||
|
||||
class CheckoutTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$bidders = Bidders::all();
|
||||
$paymentMethods = PaymentMethods::all();
|
||||
|
||||
foreach ($bidders as $bidder) {
|
||||
if (rand(0, 1)) {
|
||||
factory(Checkout::class)->create([
|
||||
'bidder_num' => $bidder->idbidders,
|
||||
'payment_method' => $paymentMethods->random()->pm_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,17 @@ class DatabaseSeeder extends Seeder
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// $this->call(UsersTableSeeder::class);
|
||||
$this->call(UsersTableSeeder::class);
|
||||
$this->call(PaymentMethodsTableSeeder::class);
|
||||
$this->call(CarShowCategoryTableSeeder::class);
|
||||
$this->call(BiddersTableSeeder::class);
|
||||
$this->call(ItemsTableSeeder::class);
|
||||
$this->call(JudgesTableSeeder::class);
|
||||
$this->call(VehiclesTableSeeder::class);
|
||||
$this->call(VehicleScoresTableSeeder::class);
|
||||
$this->call(PeoplesChoiceTableSeeder::class);
|
||||
$this->call(WinningBidsTableSeeder::class);
|
||||
$this->call(CheckoutTableSeeder::class);
|
||||
$this->call(CarShowWinnerTableSeeder::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Items;
|
||||
|
||||
class ItemsTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
factory(Items::class, 30)->create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Judges;
|
||||
|
||||
class JudgesTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
factory(Judges::class, 5)->create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\PaymentMethods;
|
||||
|
||||
class PaymentMethodsTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
PaymentMethods::create(['pm_name' => 'Cash']);
|
||||
PaymentMethods::create(['pm_name' => 'Check']);
|
||||
PaymentMethods::create(['pm_name' => 'Credit Card']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\PeoplesChoice;
|
||||
use App\Models\Vehicles;
|
||||
|
||||
class PeoplesChoiceTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$vehicles = Vehicles::all();
|
||||
|
||||
foreach ($vehicles as $vehicle) {
|
||||
factory(PeoplesChoice::class)->create([
|
||||
'vehicle' => $vehicle->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\User;
|
||||
|
||||
class UsersTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
factory(User::class, 10)->create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\VehicleScores;
|
||||
use App\Models\Judges;
|
||||
use App\Models\Vehicles;
|
||||
|
||||
class VehicleScoresTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$judges = Judges::all();
|
||||
$vehicles = Vehicles::all();
|
||||
|
||||
foreach ($vehicles as $vehicle) {
|
||||
foreach ($judges as $judge) {
|
||||
factory(VehicleScores::class)->create([
|
||||
'judge' => $judge->id,
|
||||
'vehicle' => $vehicle->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Vehicles;
|
||||
use App\Models\CarShowCategory;
|
||||
use App\Models\Bidders;
|
||||
|
||||
class VehiclesTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$categories = CarShowCategory::all();
|
||||
$bidders = Bidders::all();
|
||||
|
||||
factory(Vehicles::class, 40)->create([
|
||||
'type' => function () use ($categories) {
|
||||
return $categories->random()->id;
|
||||
},
|
||||
'owner' => function () use ($bidders) {
|
||||
return $bidders->random()->bidder_assigned_number;
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\WinningBids;
|
||||
use App\Models\Bidders;
|
||||
use App\Models\Items;
|
||||
|
||||
class WinningBidsTableSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$bidders = Bidders::all();
|
||||
$items = Items::all();
|
||||
|
||||
foreach ($items as $item) {
|
||||
if (rand(0, 1)) {
|
||||
factory(WinningBids::class)->create([
|
||||
'winning_bidder_num' => $bidders->random()->idbidders,
|
||||
'winning_item_num' => $item->iditems,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,10 @@
|
||||
Login
|
||||
</button>
|
||||
|
||||
<a href="{{ url('auth/social/oidc') }}" class="btn btn-primary">
|
||||
Login with OIDC
|
||||
</a>
|
||||
|
||||
<a class="btn btn-link" href="{{ route('password.request') }}">
|
||||
Forgot Your Password?
|
||||
</a>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
{{ $bidderlist_result->bidder_zip }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $bidderlist_result->bidder_phone }}
|
||||
{{ \App\Helpers\PhoneHelper::format($bidderlist_result->bidder_phone) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $bidderlist_result->bidder_email }}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
Name: {{ $checkout_info_result->bidder_fname }} {{ $checkout_info_result->bidder_lname }}
|
||||
</th>
|
||||
<th colspan="2">
|
||||
Phone Number: {{ $checkout_info_result->bidder_phone }}
|
||||
Phone Number: {{ \App\Helpers\PhoneHelper::format($checkout_info_result->bidder_phone) }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -14,21 +14,24 @@
|
||||
<tr>
|
||||
<th>
|
||||
<h4>
|
||||
St. John Catholic Church
|
||||
North Hackathon
|
||||
<br>
|
||||
Car Show Silent Auction
|
||||
<br>
|
||||
2099 N. Hacker Rd.
|
||||
250 Stephenson Hwy
|
||||
<br>
|
||||
Howell, MI 48855
|
||||
Troy, MI 48083
|
||||
</h4>
|
||||
</th>
|
||||
<th align='right'>
|
||||
<h2>
|
||||
<a class="btn btn-primary" target=_blank href="receiptpdf?checkout_id={{$checkout_result }}" role="button">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-primary" target=_blank href="{{ route('receiptpdf', ['checkout_id' => $checkout_result]) }}" role="button">
|
||||
Print Receipt
|
||||
</a>
|
||||
</h2>
|
||||
<a class="btn btn-success" href="{{ route('download_receipt', ['checkout_id' => $checkout_result]) }}" role="button">
|
||||
Save Receipt PDF
|
||||
</a>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -51,7 +54,7 @@
|
||||
Name: {{ $checkout_info_result->bidder_fname }} {{ $checkout_info_result->bidder_lname }}
|
||||
</th>
|
||||
<th colspan="2">
|
||||
Phone Number: {{ $checkout_info_result->bidder_phone }}
|
||||
Phone Number: {{ \App\Helpers\PhoneHelper::format($checkout_info_result->bidder_phone) }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
{{ $checkout_complete_result->bidder_zip }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $checkout_complete_result->bidder_phone }}
|
||||
{{ \App\Helpers\PhoneHelper::format($checkout_complete_result->bidder_phone) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $checkout_complete_result->bidder_email }}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<label for=winneritemnum class="col-sm-3 col-sm-offset-1 control-label">
|
||||
Item Number:
|
||||
</label>
|
||||
<div class=col-sm-2>
|
||||
<div class=col-sm-4>
|
||||
<select name="winid" id="winid" required class=form-control>
|
||||
<?php echo App\Helpers\WinningBidSelectList::winningBidShowNumbers(); ?>
|
||||
</select>
|
||||
@@ -23,7 +23,7 @@
|
||||
<label for=winnerbiddernum class="col-sm-3 col-sm-offset-1 control-label">
|
||||
Bidder Number:
|
||||
</label>
|
||||
<div class=col-sm-2>
|
||||
<div class=col-sm-4>
|
||||
<select name="winnerbiddernum" id="winnerbiddernum" required class=form-control>
|
||||
<?php echo App\Helpers\BidderSelectList::bidderShowNumbers(); ?>
|
||||
</select>
|
||||
|
||||
@@ -67,8 +67,7 @@
|
||||
{{ $finaltally_result->bidder_zip }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $finaltally_result->bidder_phone }}
|
||||
</td>
|
||||
{{ \App\Helpers\PhoneHelper::format($finaltally_result->bidder_phone) }} </td>
|
||||
<td>
|
||||
{{ $finaltally_result->bidder_email }}
|
||||
</td>
|
||||
|
||||
@@ -12,6 +12,21 @@
|
||||
|
||||
<!-- Styles -->
|
||||
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<style>
|
||||
.select2-container .select2-selection--single {
|
||||
height: 34px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||
line-height: 34px;
|
||||
}
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||
height: 32px;
|
||||
}
|
||||
</style>
|
||||
@stack('styles')
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
@@ -102,6 +117,11 @@
|
||||
Winners by Item
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/mywinnings">
|
||||
Check My Winnings
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
@@ -202,11 +222,6 @@
|
||||
Checkout Bidder
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/mywinnings">
|
||||
Check My Winnings
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Right Side Of Navbar -->
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
@@ -220,6 +235,16 @@
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a href="{{ url('admin') }}">
|
||||
Admin
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('auth/social/oidc') }}">
|
||||
Link OIDC Account
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ route('logout') }}"
|
||||
onclick="event.preventDefault();
|
||||
@@ -244,5 +269,15 @@
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="{{ asset('js/app.js') }}"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('select').select2({
|
||||
placeholder: "Select an option",
|
||||
allowClear: true
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@stack('scripts')
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,6 +8,24 @@
|
||||
<div class="panel-heading">Winnings for Bidder #{{ $bidder->bidder_assigned_number }} - {{ $bidder->bidder_fname }} {{ $bidder->bidder_lname }}</div>
|
||||
|
||||
<div class="panel-body">
|
||||
@if (isset($error))
|
||||
<div class="alert alert-danger">
|
||||
{{ $error }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session('error'))
|
||||
<div class="alert alert-danger">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session('success'))
|
||||
<div class="alert alert-success">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($winnings->count() > 0)
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
@@ -33,6 +51,16 @@
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
@if(!$is_checked_out)
|
||||
<div class="text-right">
|
||||
<a href="{{ route('north.checkout', ['bidder_id' => $bidder->idbidders]) }}" class="btn btn-success btn-lg">Pay Now with Credit Card</a>
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-success text-center">
|
||||
<strong>Checked Out!</strong> Your payment has been processed.
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<p>No winning bids found for this bidder number.</p>
|
||||
@endif
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-md-offset-0">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Checkout for Bidder #{{ $bidder->bidder_assigned_number }}</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<h4>Total Amount Due: ${{ number_format($total_cost, 2) }}</h4>
|
||||
<hr>
|
||||
|
||||
<div id="checkout-container" style="height: 100vh; overflow: hidden;">
|
||||
<div class="text-center">
|
||||
<p>Loading secure checkout...</p>
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<a href="/mywinnings?bidder_number={{ $bidder->bidder_assigned_number }}" class="btn btn-default">Cancel and Return</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://checkout.north.com/checkout.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const bidderId = "{{ $bidder->idbidders }}";
|
||||
const csrfToken = "{{ csrf_token() }}";
|
||||
|
||||
try {
|
||||
// Create a checkout session
|
||||
const response = await fetch(`/north/session/${bidderId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log('North Session Response:', data);
|
||||
|
||||
if (data.error) {
|
||||
document.getElementById('checkout-container').innerHTML = `<div class="alert alert-danger">${data.error}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionToken = data.sessionToken;
|
||||
if (!sessionToken) {
|
||||
document.getElementById('checkout-container').innerHTML = `<div class="alert alert-danger">Invalid session token received from server.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize North Checkout
|
||||
// The global 'checkout' object is provided by checkout.js
|
||||
console.log('Mounting checkout with token:', sessionToken);
|
||||
await checkout.mount(sessionToken, 'checkout-container');
|
||||
|
||||
const handleCompletion = (result) => {
|
||||
console.log('Payment complete event received:', result);
|
||||
// Redirect to verify the payment on the server
|
||||
window.location.href = `/north/verify/${bidderId}?sessionToken=${sessionToken}`;
|
||||
};
|
||||
|
||||
// Handle completion
|
||||
checkout.onPaymentComplete(handleCompletion);
|
||||
|
||||
// Support for possible variations in event names
|
||||
if (typeof checkout.onPaymentSuccess === 'function') {
|
||||
checkout.onPaymentSuccess(handleCompletion);
|
||||
}
|
||||
|
||||
// Handle errors
|
||||
if (typeof checkout.onPaymentError === 'function') {
|
||||
checkout.onPaymentError((error) => {
|
||||
console.error('Payment Error:', error);
|
||||
// Don't clear the container, just prepend the error
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'alert alert-danger';
|
||||
errorDiv.innerHTML = `<strong>Payment Error:</strong> ${error.message || 'An error occurred during payment.'}`;
|
||||
document.querySelector('.panel-body').prepend(errorDiv);
|
||||
});
|
||||
}
|
||||
|
||||
// Show a fallback button after a short delay to allow for manual verification if the redirect fails
|
||||
setTimeout(() => {
|
||||
const fallbackDiv = document.createElement('div');
|
||||
fallbackDiv.className = 'text-center';
|
||||
fallbackDiv.style.marginTop = '20px';
|
||||
fallbackDiv.innerHTML = `
|
||||
<p class="text-muted">Already completed your payment but still on this page?</p>
|
||||
<a href="/north/verify/${bidderId}?sessionToken=${sessionToken}" class="btn btn-info">Verify Payment Status</a>
|
||||
`;
|
||||
document.querySelector('.panel-body').appendChild(fallbackDiv);
|
||||
}, 5000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Checkout Error:', error);
|
||||
document.getElementById('checkout-container').innerHTML = '<div class="alert alert-danger">An error occurred while initializing checkout. Please try again.</div>';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -10,13 +10,13 @@
|
||||
<tr>
|
||||
<th>
|
||||
<h4>
|
||||
St. John Catholic Church
|
||||
North Hackathon
|
||||
<br>
|
||||
Car Show Silent Auction
|
||||
<br>
|
||||
2099 N. Hacker Rd.
|
||||
250 Stephenson Hwy
|
||||
<br>
|
||||
Howell, MI 48855
|
||||
Troy, MI 48083
|
||||
</h4>
|
||||
</th>
|
||||
<th align='right'>
|
||||
@@ -49,8 +49,7 @@
|
||||
Name: {{ $checkout_info_result->bidder_fname }} {{ $checkout_info_result->bidder_lname }}
|
||||
</td>
|
||||
<td colspan="2">
|
||||
Phone Number: {{ $checkout_info_result->bidder_phone }}
|
||||
</td>
|
||||
Phone Number: {{ \App\Helpers\PhoneHelper::format($checkout_info_result->bidder_phone) }} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<tr>
|
||||
<th>
|
||||
<h4>
|
||||
St. John Catholic Church
|
||||
North Hackathon
|
||||
<br>
|
||||
Car Show Silent Auction
|
||||
<br>
|
||||
2099 N. Hacker Rd.
|
||||
250 Stephenson Hwy
|
||||
<br>
|
||||
Howell, MI 48855
|
||||
Troy, MI 48083
|
||||
</h4>
|
||||
</th>
|
||||
<th align='right'>
|
||||
@@ -49,8 +49,7 @@
|
||||
Name: {{ $checkout_info_result->bidder_fname }} {{ $checkout_info_result->bidder_lname }}
|
||||
</td>
|
||||
<td colspan="2">
|
||||
Phone Number: {{ $checkout_info_result->bidder_phone }}
|
||||
</td>
|
||||
Phone Number: {{ \App\Helpers\PhoneHelper::format($checkout_info_result->bidder_phone) }} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
Select Bidder Number:
|
||||
</label>
|
||||
<div class=col-sm-4>
|
||||
<select name="reprintbiddernum" id="checkoutbiddernum" required class=form-control>
|
||||
<select name="reprintbiddernum" id="reprintbiddernum" required class=form-control>
|
||||
<?php echo App\Helpers\CheckoutBidderSelectList::checkoutBidderShowNumbers(); ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
{{ $showcarlist_result->bidder_zip }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $showcarlist_result->bidder_phone }}
|
||||
{{ \App\Helpers\PhoneHelper::format($showcarlist_result->bidder_phone) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $showcarlist_result->bidder_email }}
|
||||
|
||||
@@ -14,22 +14,12 @@
|
||||
<label for=winneritemnum class="col-sm-3 col-sm-offset-1 control-label">
|
||||
Item Number:
|
||||
</label>
|
||||
<div class=col-sm-2>
|
||||
<div class=col-sm-4>
|
||||
<select name="winneritemnum" id="winneritemnum" required class=form-control>
|
||||
<?php echo App\Helpers\ItemSelectList::itemShowNumbers(); ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class=form-group>
|
||||
<label for=winnerbiddernum class="col-sm-3 col-sm-offset-1 control-label">
|
||||
Bidder Number:
|
||||
</label>
|
||||
<div class=col-sm-2>
|
||||
<select name="winnerbiddernum" id="winnerbiddernum" required class=form-control>
|
||||
<?php echo App\Helpers\BidderSelectList::bidderShowNumbers(); ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class=form-group>
|
||||
<label for=winnerbid class="col-sm-3 col-sm-offset-1 control-label">
|
||||
Winning Bid:
|
||||
@@ -38,6 +28,16 @@
|
||||
<input type="text" name="winnerbid" id="winnerbid" class=form-control />
|
||||
</div>
|
||||
</div>
|
||||
<div class=form-group>
|
||||
<label for=winnerbiddernum class="col-sm-3 col-sm-offset-1 control-label">
|
||||
Bidder Number:
|
||||
</label>
|
||||
<div class=col-sm-4>
|
||||
<select name="winnerbiddernum" id="winnerbiddernum" required class=form-control>
|
||||
<?php echo App\Helpers\BidderSelectList::bidderShowNumbers(); ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class=form-group>
|
||||
<div class="col-sm-offset-5 col-sm-10">
|
||||
<button type="submit" value="Submit" class="btn btn-primary">Submit</button>
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
Name: {{ $winnertotal_info_result->bidder_fname }} {{ $winnertotal_info_result->bidder_lname }}
|
||||
</th>
|
||||
<th>
|
||||
Phone Number: {{ $winnertotal_info_result->bidder_phone }}
|
||||
</th>
|
||||
Phone Number: {{ \App\Helpers\PhoneHelper::format($winnertotal_info_result->bidder_phone) }} </th>
|
||||
<th>
|
||||
Total: ${{ $winnertotal_info_result->total_cost }}
|
||||
</th>
|
||||
|
||||
@@ -14,6 +14,10 @@ if (env('APP_ENV') === 'production') {
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
Auth::routes(['register' => false]);
|
||||
|
||||
Route::get('auth/social/{provider}', 'Auth\SocialiteController@redirectToProvider');
|
||||
Route::get('auth/social/{provider}/callback', 'Auth\SocialiteController@handleProviderCallback');
|
||||
|
||||
Route::get('/', [ 'uses' => 'PagesController@home']);
|
||||
Route::get('home', [ 'uses' => 'PagesController@home']);
|
||||
Route::get('winningbidderlist', [ 'uses' => 'PagesController@winningbidderlist']);
|
||||
@@ -23,6 +27,11 @@ Route::get('showscoresbycar', [ 'uses' => 'PagesController@showscoresbycar']);
|
||||
Route::get('mywinnings', [ 'uses' => 'PagesController@myWinnings']);
|
||||
Route::post('mywinnings', [ 'uses' => 'PagesController@myWinnings']);
|
||||
|
||||
// North Embedded Checkout
|
||||
Route::get('north/checkout/{bidder_id}', [ 'uses' => 'NorthCheckoutController@checkout' ])->name('north.checkout');
|
||||
Route::post('north/session/{bidder_id}', [ 'uses' => 'NorthCheckoutController@createSession' ])->name('north.session');
|
||||
Route::get('north/verify/{bidder_id}', [ 'uses' => 'NorthCheckoutController@verify' ])->name('north.verify');
|
||||
|
||||
Route::group(['middleware' => 'auth'], function() {
|
||||
Route::get('bidders', [ 'uses' => 'PagesController@bidders']);
|
||||
Route::post('bidders', [ 'uses' => 'PagesController@bidders']);
|
||||
@@ -41,6 +50,7 @@ Route::group(['middleware' => 'auth'], function() {
|
||||
Route::get('reprint_receipt', ['uses' => 'PagesController@reprintReceipt']);
|
||||
Route::post('reprint_receipt', ['uses' => 'PagesController@reprintReceipt']);
|
||||
Route::get('receiptpdf', ['uses' => 'PagesController@receiptpdf'])->name('receiptpdf');
|
||||
Route::get('download_receipt', ['uses' => 'PagesController@downloadReceiptPdf'])->name('download_receipt');
|
||||
Route::get('winners', [ 'uses' => 'PagesController@winners']);
|
||||
Route::post('winners', [ 'uses' => 'PagesController@winners']);
|
||||
Route::get('winnerlist', [ 'uses' => 'PagesController@winnerlist']);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Tests\TestCase;
|
||||
use App\Models\Bidders;
|
||||
use App\Helpers\PhoneHelper;
|
||||
|
||||
class PhoneFormattingTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function it_formats_a_10_digit_phone_number()
|
||||
{
|
||||
$formatted = PhoneHelper::format('1234567890');
|
||||
$this->assertEquals('123-456-7890', $formatted);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_strips_non_numeric_characters_before_formatting()
|
||||
{
|
||||
$formatted = PhoneHelper::format('(123) 456-7890');
|
||||
$this->assertEquals('123-456-7890', $formatted);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_original_value_if_not_10_digits()
|
||||
{
|
||||
$formatted = PhoneHelper::format('1234567');
|
||||
$this->assertEquals('1234567', $formatted);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user