From 54861755b4ef06e3d5fcbc1a57f9baa971a01345 Mon Sep 17 00:00:00 2001 From: Etienne Marais Date: Wed, 16 Jan 2019 20:33:32 +0200 Subject: [PATCH] Hidden ticket access codes (#1) # Summary Added the ability to specify hidden ticket access codes on an event. This would allow for special types of tickets to be created on an event and have an unlock code link to one or many hidden tickets. ## Value The value this adds is to allow event organisers to play with marketing to aid in ticket sales etc. ## Changes **Admin** - Added migrations to allow for the access codes table on events and the pivot for many to many codes to tickets - Added the ability to create an access code in the event customisation - Added the ability to view access codes on a hidden ticket - Added the ability to attach one or multiple access code(s) onto the hidden ticket **Public event page** - Shows a box to enter your access code if the event has hidden tickets - Added the validation messages for empty codes - Added the check to fetch the hidden tickets if the code was entered correctly --- Gruntfile.js | 15 ++-- .../EventAccessCodesController.php | 56 +++++++++++++++ .../Controllers/EventTicketsController.php | 25 +++++++ app/Http/Controllers/EventViewController.php | 46 +++++++++++- app/Http/routes.php | 20 +++++- app/Models/Event.php | 10 +++ app/Models/EventAccessCodes.php | 45 ++++++++++++ app/Models/Ticket.php | 13 ++++ composer.json | 7 +- ...124052_create_event_access_codes_table.php | 37 ++++++++++ ..._create_ticket_event_access_code_table.php | 38 ++++++++++ public/assets/javascript/app-public.js | 25 +++++++ public/assets/javascript/frontend.js | 25 +++++++ public/assets/stylesheet/frontend.css | 2 +- resources/lang/en/EventAccessCode.php | 10 +++ resources/lang/en/Public_ViewEvent.php | 1 + resources/lang/en/Ticket.php | 1 + resources/lang/en/basic.php | 21 +++--- .../views/ManageEvent/Customize.blade.php | 46 ++++++++++++ .../Modals/CreateAccessCode.blade.php | 32 +++++++++ .../ManageEvent/Modals/EditTicket.blade.php | 28 +++++++- .../EventHiddenTicketsSelection.blade.php | 71 +++++++++++++++++++ .../Partials/EventTicketsSection.blade.php | 34 +++++++-- 23 files changed, 576 insertions(+), 32 deletions(-) create mode 100644 app/Http/Controllers/EventAccessCodesController.php create mode 100644 app/Models/EventAccessCodes.php create mode 100644 database/migrations/2019_01_14_124052_create_event_access_codes_table.php create mode 100644 database/migrations/2019_01_14_185419_create_ticket_event_access_code_table.php create mode 100644 resources/lang/en/EventAccessCode.php create mode 100644 resources/views/ManageEvent/Modals/CreateAccessCode.blade.php create mode 100644 resources/views/Public/ViewEvent/Partials/EventHiddenTicketsSelection.blade.php diff --git a/Gruntfile.js b/Gruntfile.js index 8e069f8f..a5c73ef8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -70,17 +70,22 @@ module.exports = function (grunt) { } }, }, - phpunit: { - classes: {}, - options: {} - }, + watch: { + scripts: { + files: ['./public/assets/**/*.js'], + tasks: ['default'], + options: { + spawn: false, + }, + }, + } }); // Plugin loading grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-less'); grunt.loadNpmTasks('grunt-contrib-uglify'); - //grunt.loadNpmTasks('grunt-phpunit'); + grunt.loadNpmTasks('grunt-contrib-watch'); // Task definition grunt.registerTask('default', ['less', 'concat']); grunt.registerTask('deploy', ['less', 'concat', 'uglify']); diff --git a/app/Http/Controllers/EventAccessCodesController.php b/app/Http/Controllers/EventAccessCodesController.php new file mode 100644 index 00000000..a908cb2b --- /dev/null +++ b/app/Http/Controllers/EventAccessCodesController.php @@ -0,0 +1,56 @@ + Event::scope()->find($event_id), + ]); + } + + /** + * Creates a ticket + * + * @param Request $request + * @param $event_id + * @return \Illuminate\Http\JsonResponse + */ + public function postCreate(Request $request, $event_id) + { + $eventAccessCode = new EventAccessCodes(); + + if (!$eventAccessCode->validate($request->all())) { + return response()->json([ + 'status' => 'error', + 'messages' => $eventAccessCode->errors(), + ]); + } + + $eventAccessCode->event_id = $event_id; + $eventAccessCode->code = strtoupper(strip_tags($request->get('code'))); + $eventAccessCode->save(); + + session()->flash('message', 'Successfully Created Access Code'); + + return response()->json([ + 'status' => 'success', + 'id' => $eventAccessCode->id, + 'message' => trans("Controllers.refreshing"), + 'redirectUrl' => route('showEventCustomize', [ + 'event_id' => $event_id, + '#access_codes', + ]), + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/EventTicketsController.php b/app/Http/Controllers/EventTicketsController.php index a8964199..662009d3 100644 --- a/app/Http/Controllers/EventTicketsController.php +++ b/app/Http/Controllers/EventTicketsController.php @@ -111,6 +111,15 @@ class EventTicketsController extends MyBaseController $ticket->save(); + // Attach the access codes to the ticket if it's hidden and the code ids have come from the front + if ($ticket->is_hidden) { + $ticketAccessCodes = $request->get('ticket_access_codes', []); + if (empty($ticketAccessCodes) === false) { + // Sync the access codes on the ticket + $ticket->event_access_codes()->attach($ticketAccessCodes); + } + } + session()->flash('message', 'Successfully Created Ticket'); return response()->json([ @@ -223,6 +232,9 @@ class EventTicketsController extends MyBaseController ]); } + // Check if the ticket visibility changed on update + $ticketPreviouslyHidden = (bool)$ticket->is_hidden; + $ticket->title = $request->get('title'); $ticket->quantity_available = !$request->get('quantity_available') ? null : $request->get('quantity_available'); $ticket->price = $request->get('price'); @@ -235,6 +247,19 @@ class EventTicketsController extends MyBaseController $ticket->save(); + // Attach the access codes to the ticket if it's hidden and the code ids have come from the front + if ($ticket->is_hidden) { + $ticketAccessCodes = $request->get('ticket_access_codes', []); + if (empty($ticketAccessCodes) === false) { + // Sync the access codes on the ticket + $ticket->event_access_codes()->detach(); + $ticket->event_access_codes()->attach($ticketAccessCodes); + } + } else if ($ticketPreviouslyHidden) { + // Delete access codes on ticket if the visibility changed to visible + $ticket->event_access_codes()->detach(); + } + return response()->json([ 'status' => 'success', 'id' => $ticket->id, diff --git a/app/Http/Controllers/EventViewController.php b/app/Http/Controllers/EventViewController.php index 6e5c0a4e..49746fd5 100644 --- a/app/Http/Controllers/EventViewController.php +++ b/app/Http/Controllers/EventViewController.php @@ -32,8 +32,8 @@ class EventViewController extends Controller } $data = [ - 'event' => $event, - 'tickets' => $event->tickets()->where('is_hidden', 0)->orderBy('sort_order', 'asc')->get(), + 'event' => $event, + 'tickets' => $event->tickets()->orderBy('sort_order', 'asc')->get(), 'is_embedded' => 0, ]; /* @@ -136,4 +136,46 @@ class EventViewController extends Controller 'Content-Disposition' => 'attachment; filename="event.ics' ]); } + + /** + * @param Request $request + * @param $event_id + * @return \Illuminate\Http\JsonResponse + */ + public function postShowHiddenTickets(Request $request, $event_id) + { + $event = Event::findOrFail($event_id); + + $accessCode = $request->get('access_code'); + if (!$accessCode) { + return response()->json([ + 'status' => 'error', + 'message' => 'A valid access code is required', + ]); + } + + $unlockedHiddenTickets = $event->tickets() + ->where('is_hidden', true) + ->orderBy('sort_order', 'asc') + ->get() + ->filter(function($ticket) use ($accessCode) { + // Only return the hidden tickets that match the access code + return ($ticket->event_access_codes()->where('code', $accessCode)->get()->count() > 0); + }); + + if ($unlockedHiddenTickets->count() === 0) { + return response()->json([ + 'status' => 'error', + 'message' => 'No Tickets matched to your unlock code', + ]); + } + + $data = [ + 'event' => $event, + 'tickets' => $unlockedHiddenTickets, + 'is_embedded' => 0, + ]; + + return view('Public.ViewEvent.Partials.EventHiddenTicketsSelection', $data); + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 62a377bc..b0307ffe 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -145,6 +145,11 @@ Route::group( 'uses' => 'EventViewController@postContactOrganiser', ]); + Route::post('/{event_id}/show_hidden', [ + 'as' => 'postShowHiddenTickets', + 'uses' => 'EventViewController@postShowHiddenTickets', + ]); + /* * Used for previewing designs in the backend. Doesn't log page views etc. */ @@ -559,12 +564,10 @@ Route::group( 'as' => 'showEventCustomize', 'uses' => 'EventCustomizeController@showCustomize', ]); - Route::get('{event_id}/customize/{tab?}', [ 'as' => 'showEventCustomizeTab', 'uses' => 'EventCustomizeController@showCustomize', ]); - Route::post('{event_id}/customize/order_page', [ 'as' => 'postEditEventOrderPage', 'uses' => 'EventCustomizeController@postEditEventOrderPage', @@ -581,12 +584,23 @@ Route::group( 'as' => 'postEditEventSocial', 'uses' => 'EventCustomizeController@postEditEventSocial', ]); - Route::post('{event_id}/customize/fees', [ 'as' => 'postEditEventFees', 'uses' => 'EventCustomizeController@postEditEventFees', ]); + /** + * Event access codes + */ + Route::get('{event_id}/access_codes/create', [ + 'as' => 'showCreateEventAccessCode', + 'uses' => 'EventAccessCodesController@showCreate', + ]); + + Route::post('{event_id}/access_codes/create', [ + 'as' => 'postCreateEventAccessCode', + 'uses' => 'EventAccessCodesController@postCreate', + ]); /* * ------- diff --git a/app/Models/Event.php b/app/Models/Event.php index 154986ef..f602ccd6 100644 --- a/app/Models/Event.php +++ b/app/Models/Event.php @@ -137,6 +137,16 @@ class Event extends MyBaseModel return $this->hasMany(\App\Models\Order::class); } + /** + * The access codes associated with the event. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function access_codes() + { + return $this->hasMany(\App\Models\EventAccessCodes::class, 'event_id', 'id'); + } + /** * The account associated with the event. * diff --git a/app/Models/EventAccessCodes.php b/app/Models/EventAccessCodes.php new file mode 100644 index 00000000..963e0b36 --- /dev/null +++ b/app/Models/EventAccessCodes.php @@ -0,0 +1,45 @@ + 'required|string', + ]; + } + + /** + * The Event associated with the event access code. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function event() + { + return $this->belongsTo(\App\Models\Event::class, 'event_id', 'id'); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + function tickets() + { + return $this->belongsToMany( + Ticket::class, + 'ticket_event_access_code', + 'event_access_code_id', + 'ticket_id' + )->withTimestamps(); + } +} \ No newline at end of file diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index 5e4808fc..f057fac5 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -72,6 +72,19 @@ class Ticket extends MyBaseModel return $this->belongsToMany(\App\Models\Question::class); } + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + function event_access_codes() + { + return $this->belongsToMany( + EventAccessCodes::class, + 'ticket_event_access_code', + 'ticket_id', + 'event_access_code_id' + )->withTimestamps(); + } + /** * TODO:implement the reserved method. */ diff --git a/composer.json b/composer.json index d388108c..f87b0355 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "illuminate/support": "~5.6", "intervention/image": "~2.4", "laracasts/utilities": "~2.1", - "laravel/framework": "~5.6", + "laravel/framework": "~5.6", "laravel/socialite": "~3.0", "laravelcollective/html": "~5.6", "league/flysystem-aws-s3-v3": "~1.0", @@ -37,14 +37,15 @@ "php-http/curl-client": "^1.7", "php-http/message": "^1.6", "predis/predis": "~1.1", - "vinelab/http": "~1.5" + "vinelab/http": "~1.5", + "laravel/tinker": "^1.0" }, "require-dev": { "phpunit/phpunit": "7.3.*", "phpspec/phpspec": "5.0.*", "fzaninotto/faker": "1.8.*", "symfony/dom-crawler": "~3.0", - "symfony/css-selector": "~3.0" + "symfony/css-selector": "~3.0" }, "autoload": { "classmap": [ diff --git a/database/migrations/2019_01_14_124052_create_event_access_codes_table.php b/database/migrations/2019_01_14_124052_create_event_access_codes_table.php new file mode 100644 index 00000000..4e5c086d --- /dev/null +++ b/database/migrations/2019_01_14_124052_create_event_access_codes_table.php @@ -0,0 +1,37 @@ +increments('id'); + $table->unsignedInteger('event_id'); + $table->string('code')->default(''); + $table->timestamps(); + $table->softDeletes(); + + // Add events table foreign key + $table->foreign('event_id')->references('id')->on('events')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('event_access_codes'); + } +} diff --git a/database/migrations/2019_01_14_185419_create_ticket_event_access_code_table.php b/database/migrations/2019_01_14_185419_create_ticket_event_access_code_table.php new file mode 100644 index 00000000..a3bc0c90 --- /dev/null +++ b/database/migrations/2019_01_14_185419_create_ticket_event_access_code_table.php @@ -0,0 +1,38 @@ +increments('id'); + $table->unsignedInteger('ticket_id'); + $table->unsignedInteger('event_access_code_id'); + $table->timestamps(); + + $table->foreign('ticket_id')->references('id')->on('tickets'); + $table->foreign('event_access_code_id')->references('id')->on('event_access_codes'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::disableForeignKeyConstraints(); + Schema::dropIfExists('ticket_event_access_code'); + Schema::enableForeignKeyConstraints(); + } +} diff --git a/public/assets/javascript/app-public.js b/public/assets/javascript/app-public.js index 83ccd158..3c739b4c 100644 --- a/public/assets/javascript/app-public.js +++ b/public/assets/javascript/app-public.js @@ -176,7 +176,32 @@ $(function() { }).change(); + // Apply access code here to unlock hidden tickets + $('#apply_access_code').click(function(e) { + var $clicked = $(this); + // Hide any previous errors + $clicked.closest('.form-group') + .removeClass('has-error'); + var url = $clicked.closest('.has-access-codes').data('url'); + var data = { + 'access_code': $('#unlock_code').val(), + '_token': $('input:hidden[name=_token]').val() + }; + + $.post(url, data, function(response) { + if (response.status === 'error') { + // Show any access code errors + $clicked.closest('.form-group').addClass('has-error'); + showMessage(response.message); + return; + } + + $clicked.closest('.has-access-codes').before(response); + $('#unlock_code').val(''); + $clicked.closest('.has-access-codes').remove(); + }); + }); }); function processFormErrors($form, errors) diff --git a/public/assets/javascript/frontend.js b/public/assets/javascript/frontend.js index ff1a363d..b71b0b00 100644 --- a/public/assets/javascript/frontend.js +++ b/public/assets/javascript/frontend.js @@ -4744,7 +4744,32 @@ function log() { }).change(); + // Apply access code here to unlock hidden tickets + $('#apply_access_code').click(function(e) { + var $clicked = $(this); + // Hide any previous errors + $clicked.closest('.form-group') + .removeClass('has-error'); + var url = $clicked.closest('.has-access-codes').data('url'); + var data = { + 'access_code': $('#unlock_code').val(), + '_token': $('input:hidden[name=_token]').val() + }; + + $.post(url, data, function(response) { + if (response.status === 'error') { + // Show any access code errors + $clicked.closest('.form-group').addClass('has-error'); + showMessage(response.message); + return; + } + + $clicked.closest('.has-access-codes').before(response); + $('#unlock_code').val(''); + $clicked.closest('.has-access-codes').remove(); + }); + }); }); function processFormErrors($form, errors) diff --git a/public/assets/stylesheet/frontend.css b/public/assets/stylesheet/frontend.css index 404bee82..c25ac536 100644 --- a/public/assets/stylesheet/frontend.css +++ b/public/assets/stylesheet/frontend.css @@ -92,4 +92,4 @@ body { opacity: 0.7; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); } -html,body{height:100%}body{font-family:'Open Sans',sans-serif}table{margin:0}label.required::after{content:'*';color:red;padding-left:3px;font-size:9px}@media (min-width:1200px){.container{width:960px}}section.container{padding:40px;background-color:#ffffff;margin-bottom:25px}.section_head{border:none !important;font-size:35px;text-align:center;margin:0;margin-bottom:30px;letter-spacing:.2em;font-weight:200}.section_head h1{margin:0;font-weight:100;text-align:center}section{color:#666}#organiser_page_wrap #intro{position:relative;text-align:center;font-weight:100;padding:20px 0 20px 0;border:none;margin-bottom:0;margin-top:20px;color:#fff;background-color:#AF5050}#organiser_page_wrap .organiser_logo{max-width:150px;margin:0 auto}#organiser_page_wrap .organiser_logo .thumbnail{background-color:transparent;border:none}#goLiveBar{background-color:rgba(255,255,255,0.9);text-align:center;padding:10px}.adminLink,.adminLink:hover{color:#fff}#event_page_wrap{min-height:100%;margin:0 auto -60px;background:rgba(0,0,0,0.4)}#event_page_wrap #organiserHead{text-align:center;color:#fff;border:none;font-size:15px;opacity:.6;transition:all .15s ease-in-out;background:rgba(0,0,0,0.4);line-height:30px;cursor:pointer}#event_page_wrap #organiserHead:hover{opacity:1}#event_page_wrap #intro{position:relative;text-align:center;font-weight:100;padding:20px 0 20px 0;color:#ffffff;border:none;background-color:transparent;margin-bottom:0}#event_page_wrap #intro h1{position:relative;padding:10px 10px;margin:0;font-weight:400;font-size:60px;margin-bottom:10px}#event_page_wrap #intro .event_date{font-size:15px;padding:10px;font-weight:500}#event_page_wrap #intro .event_venue{font-size:19px}#event_page_wrap #intro .event_buttons{margin-top:30px;margin-bottom:30px}#event_page_wrap #intro .event_buttons .btn-event-link{line-height:35px;font-size:17px;text-transform:uppercase;letter-spacing:5px;border:1px solid;border-color:rgba(255,255,255,0.2);text-decoration:none;color:#fff;padding:0 15px;transition:all .15s ease-in-out;width:100%;background:rgba(0,0,0,0.3)}#event_page_wrap #intro .event_buttons .btn-event-link:hover{border-color:rgba(255,255,255,0.6)}#event_page_wrap #tickets .input-group-addon{background:none;border:none}#event_page_wrap #tickets table tr:first-child td{border-top:none}#event_page_wrap #tickets table tr td{padding:20px 0px}#event_page_wrap #details .event_poster img{border:4px solid #f6f6f6;max-width:100%;min-width:100%}#event_page_wrap #details .event_details iframe,#event_page_wrap #details .event_details img{max-width:100%}#event_page_wrap #details .event_details h1,#event_page_wrap #details .event_details h2,#event_page_wrap #details .event_details h3,#event_page_wrap #details .event_details h4,#event_page_wrap #details .event_details h5,#event_page_wrap #details .event_details h6{margin-top:0px;margin-bottom:15px;font-weight:100}#event_page_wrap #details .event_details h1{font-size:28px}#event_page_wrap #details .event_details h2{font-size:24px}#event_page_wrap #details .event_details h3{font-size:20px}#event_page_wrap #details .event_details h4{font-size:17px}#event_page_wrap #share .btn{margin-bottom:20px}#event_page_wrap #location{padding:0px}#event_page_wrap #location .google-maps{position:relative;overflow:hidden}#event_page_wrap #location .google-maps iframe{width:100% !important;height:100% !important;min-height:500px}footer,.push{height:60px}#footer{background-color:#888;background-color:rgba(0,0,0,0.4);min-height:60px;line-height:60px;color:#fff;text-align:center}#organiser{text-align:center}#organiser .contact_form{display:none;padding:20px;margin-top:25px;text-align:left}.totop{border-radius:0;background-color:#888;background-color:rgba(0,0,0,0.4)}.totop:hover{background-color:#fff;background-color:rgba(255,255,255,0.4);color:#000}.powered_by_embedded{text-align:center;padding:4px}.powered_by_embedded a{color:#333 !important}@media (min-width:100px) and (max-width:767px){.row{margin:0}section.container{margin-bottom:0;padding:10px}.section_head{padding:10px;font-size:30px}.main_content{padding:0px;background:#fff}#organiser_page_wrap #intro{padding:30px;margin-top:0}#organiser_page_wrap #intro h1{font-size:2.236em;padding:15px}#organiser_page_wrap #events{min-height:350px}#event_page_wrap #intro{padding:30px}#event_page_wrap #intro .event_date h2{font-size:20px}#event_page_wrap #intro .event_date h4{font-size:11px}#event_page_wrap #intro h1{font-size:2.236em;padding:15px}#event_page_wrap #intro .event_venue{color:#fff;font-size:20px;margin-top:10px}#event_page_wrap #intro .event_buttons{margin-top:50px}#event_page_wrap #intro .event_buttons .btn-event-link{padding:5px 0px;font-size:18px;margin-bottom:5px;line-height:30px}#event_page_wrap #tickets .btn-checkout{width:100%}#event_page_wrap #location .google-maps iframe{min-height:290px}.content{padding:15px}}.rrssb-buttons.large-format li a{border-radius:0}.event-listing-heading{margin-top:0;margin-bottom:10px;font-size:20px}.event-list{list-style:none;margin:0px;padding:0px}.event-list>li{background-color:#F3F3F3;padding:0px;margin:0px 0px 20px}.event-list>li>time{display:inline-block;width:100%;padding:5px;text-align:center;text-transform:uppercase}.event-list>li>time>span{display:none}.event-list>li>time>.day{display:block;font-size:18pt;font-weight:100;line-height:1}.event-list>li time>.month{display:block;font-size:24pt;font-weight:900;line-height:1}.event-list>li>img{width:100%}.event-list>li>.info{padding-top:10px;text-align:center}.event-list>li>.info>.title{font-size:15pt;font-weight:500;margin:0px}.event-list>li>.info>.desc{font-size:10pt;font-weight:300;margin:0px}.event-list>li>.info>ul{display:table;list-style:none;margin:10px 0px 0px;padding:0px;width:100%;text-align:center;background-color:#DEDEDE}.event-list>li>.info>ul>li{display:table-cell;cursor:pointer;color:#1e1e1e;font-size:11pt;font-weight:300;padding:3px 0px}.event-list>li>.info>ul>li>a{display:block;width:100%;color:#6D6D6D;text-decoration:none}.event-list>li>.info>ul>li:hover{color:#1e1e1e;background-color:#c8c8c8}@media (min-width:768px){.event-list>li{position:relative;display:block;width:100%;height:120px;padding:0px}.event-list>li>time,.event-list>li>img{display:inline-block}.event-list>li>time,.event-list>li>img{width:120px;float:left}.event-list>li>.info{background-color:#f5f5f5;overflow:hidden}.event-list>li>time,.event-list>li>img{width:120px;height:120px;padding:0px;margin:0px}.event-list>li>time>.day{font-size:56pt}.event-list>li>.info{position:relative;height:120px;text-align:left;padding-right:40px;padding-top:30px}.event-list>li>.info>.title,.event-list>li>.info>.desc{padding:0px 10px}.event-list>li>.info>ul{position:absolute;left:0px;bottom:0px;background-color:#D2D2D2}} +html,body{height:100%}body{font-family:'Open Sans',sans-serif}table{margin:0}label.required::after{content:'*';color:red;padding-left:3px;font-size:9px}@media (min-width:1200px){.container{width:960px}}section.container{padding:40px;background-color:#ffffff;margin-bottom:25px}.section_head{border:none !important;font-size:35px;text-align:center;margin:0;margin-bottom:30px;letter-spacing:.2em;font-weight:200}.section_head h1{margin:0;font-weight:100;text-align:center}section{color:#666}#organiser_page_wrap #intro{position:relative;text-align:center;font-weight:100;padding:20px 0 20px 0;border:none;margin-bottom:0;margin-top:20px;color:#fff;background-color:#AF5050}#organiser_page_wrap .organiser_logo{max-width:150px;margin:0 auto}#organiser_page_wrap .organiser_logo .thumbnail{background-color:transparent;border:none}#goLiveBar{background-color:rgba(255,255,255,0.9);text-align:center;padding:10px}.adminLink,.adminLink:hover{color:#fff}#event_page_wrap{min-height:100%;margin:0 auto -60px;background:rgba(0,0,0,0.4)}#event_page_wrap #organiserHead{text-align:center;color:#fff;border:none;font-size:15px;opacity:.6;transition:all .15s ease-in-out;background:rgba(0,0,0,0.4);line-height:30px;cursor:pointer}#event_page_wrap #organiserHead:hover{opacity:1}#event_page_wrap #intro{position:relative;text-align:center;font-weight:100;padding:20px 0 20px 0;color:#ffffff;border:none;background-color:transparent;margin-bottom:0}#event_page_wrap #intro h1{position:relative;padding:10px 10px;margin:0;font-weight:400;font-size:60px;margin-bottom:10px}#event_page_wrap #intro .event_date{font-size:15px;padding:10px;font-weight:500}#event_page_wrap #intro .event_venue{font-size:19px}#event_page_wrap #intro .event_buttons{margin-top:30px;margin-bottom:30px}#event_page_wrap #intro .event_buttons .btn-event-link{line-height:35px;font-size:17px;text-transform:uppercase;letter-spacing:5px;border:1px solid;border-color:rgba(255,255,255,0.2);text-decoration:none;color:#fff;padding:0 15px;transition:all .15s ease-in-out;width:100%;background:rgba(0,0,0,0.3)}#event_page_wrap #intro .event_buttons .btn-event-link:hover{border-color:rgba(255,255,255,0.6)}#event_page_wrap #tickets .input-group-addon{background:none;border:none}#event_page_wrap #tickets table tr:first-child td{border-top:none}#event_page_wrap #tickets table tr td{padding:20px 0px}#event_page_wrap #details .event_poster img{border:4px solid #f6f6f6;max-width:100%;min-width:100%}#event_page_wrap #details .event_details iframe,#event_page_wrap #details .event_details img{max-width:100%}#event_page_wrap #details .event_details h1,#event_page_wrap #details .event_details h2,#event_page_wrap #details .event_details h3,#event_page_wrap #details .event_details h4,#event_page_wrap #details .event_details h5,#event_page_wrap #details .event_details h6{margin-top:0px;margin-bottom:15px;font-weight:100}#event_page_wrap #details .event_details h1{font-size:28px}#event_page_wrap #details .event_details h2{font-size:24px}#event_page_wrap #details .event_details h3{font-size:20px}#event_page_wrap #details .event_details h4{font-size:17px}#event_page_wrap #share .btn{margin-bottom:20px}#event_page_wrap #location{padding:0px}#event_page_wrap #location .google-maps{position:relative;overflow:hidden}#event_page_wrap #location .google-maps iframe{width:100% !important;height:100% !important;min-height:500px}footer,.push{height:60px}#footer{background-color:#888;background-color:rgba(0,0,0,0.4);min-height:60px;line-height:60px;color:#fff;text-align:center}#organiser{text-align:center}#organiser .contact_form{display:none;padding:20px;margin-top:25px;text-align:left}.totop{border-radius:0;background-color:#888;background-color:rgba(0,0,0,0.4)}.totop:hover{background-color:#fff;background-color:rgba(255,255,255,0.4);color:#000}.powered_by_embedded{text-align:center;padding:4px}.powered_by_embedded a{color:#333 !important}@media (min-width:100px) and (max-width:767px){.row{margin:0}section.container{margin-bottom:0;padding:10px}.section_head{padding:10px;font-size:30px}.main_content{padding:0px;background:#fff}#organiser_page_wrap #intro{padding:30px;margin-top:0}#organiser_page_wrap #intro h1{font-size:2.236em;padding:15px}#organiser_page_wrap #events{min-height:350px}#event_page_wrap #intro{padding:30px}#event_page_wrap #intro .event_date h2{font-size:20px}#event_page_wrap #intro .event_date h4{font-size:11px}#event_page_wrap #intro h1{font-size:2.236em;padding:15px}#event_page_wrap #intro .event_venue{color:#fff;font-size:20px;margin-top:10px}#event_page_wrap #intro .event_buttons{margin-top:50px}#event_page_wrap #intro .event_buttons .btn-event-link{padding:5px 0px;font-size:18px;margin-bottom:5px;line-height:30px}#event_page_wrap #tickets .btn-checkout{width:100%}#event_page_wrap #location .google-maps iframe{min-height:290px}.content{padding:15px}}.rrssb-buttons.large-format li a{border-radius:0}.event-listing-heading{margin-top:0;margin-bottom:10px;font-size:20px}.event-list{list-style:none;margin:0px;padding:0px}.event-list>li{background-color:#F3F3F3;padding:0px;margin:0px 0px 20px}.event-list>li>time{display:inline-block;width:100%;padding:5px;text-align:center;text-transform:uppercase}.event-list>li>time>span{display:none}.event-list>li>time>.day{display:block;font-size:18pt;font-weight:100;line-height:1}.event-list>li time>.month{display:block;font-size:24pt;font-weight:900;line-height:1}.event-list>li>img{width:100%}.event-list>li>.info{padding-top:10px;text-align:center}.event-list>li>.info>.title{font-size:15pt;font-weight:500;margin:0px}.event-list>li>.info>.desc{font-size:10pt;font-weight:300;margin:0px}.event-list>li>.info>ul{display:table;list-style:none;margin:10px 0px 0px;padding:0px;width:100%;text-align:center;background-color:#DEDEDE}.event-list>li>.info>ul>li{display:table-cell;cursor:pointer;color:#1e1e1e;font-size:11pt;font-weight:300;padding:3px 0px}.event-list>li>.info>ul>li>a{display:block;width:100%;color:#6D6D6D;text-decoration:none}.event-list>li>.info>ul>li:hover{color:#1e1e1e;background-color:#c8c8c8}@media (min-width:768px){.event-list>li{position:relative;display:block;width:100%;height:120px;padding:0px}.event-list>li>time,.event-list>li>img{display:inline-block}.event-list>li>time,.event-list>li>img{width:120px;float:left}.event-list>li>.info{background-color:#f5f5f5;overflow:hidden}.event-list>li>time,.event-list>li>img{width:120px;height:120px;padding:0px;margin:0px}.event-list>li>time>.day{font-size:56pt}.event-list>li>.info{position:relative;height:120px;text-align:left;padding-right:40px;padding-top:30px}.event-list>li>.info>.title,.event-list>li>.info>.desc{padding:0px 10px}.event-list>li>.info>ul{position:absolute;left:0px;bottom:0px;background-color:#D2D2D2}} \ No newline at end of file diff --git a/resources/lang/en/EventAccessCode.php b/resources/lang/en/EventAccessCode.php new file mode 100644 index 00000000..dd12dd47 --- /dev/null +++ b/resources/lang/en/EventAccessCode.php @@ -0,0 +1,10 @@ + 'Access Codes to unlock hidden tickets', + 'access_codes_code' => 'Code', + 'access_codes_created_at' => 'Created At', + 'no_access_codes_yet' => 'No Access Codes yet!', + 'create_access_code' => 'Create Access Code', + 'access_code_title' => 'Code', + 'access_code_title_placeholder' => 'ex: CONFERENCE2019', +]; \ No newline at end of file diff --git a/resources/lang/en/Public_ViewEvent.php b/resources/lang/en/Public_ViewEvent.php index 7a65b5f6..1d535d93 100644 --- a/resources/lang/en/Public_ViewEvent.php +++ b/resources/lang/en/Public_ViewEvent.php @@ -36,6 +36,7 @@ return [ 'expiry_year' => 'Expiry year', 'first_name' => 'First name', 'free' => 'FREE', + 'has_unlock_codes' => 'Do you have an unlock code?', 'inc_fees' => 'Booking Fees', 'last_name' => 'Last name', 'offline_payment_instructions' => 'Offline payment instructions', diff --git a/resources/lang/en/Ticket.php b/resources/lang/en/Ticket.php index f61ab9dc..4f78b306 100644 --- a/resources/lang/en/Ticket.php +++ b/resources/lang/en/Ticket.php @@ -43,6 +43,7 @@ return array ( 'ticket_background_color' => 'Ticket Background Color', 'ticket_border_color' => 'Ticket Border Color', 'ticket_design' => 'Ticket Design', + 'access_codes' => 'Access Codes', 'ticket_preview' => 'Ticket Preview', 'ticket_sales_paused' => 'Sales Paused', 'ticket_sub_text_color' => 'Ticket Sub Text Color', diff --git a/resources/lang/en/basic.php b/resources/lang/en/basic.php index aff05cae..cefb9758 100644 --- a/resources/lang/en/basic.php +++ b/resources/lang/en/basic.php @@ -5,6 +5,7 @@ return array ( //==================================== Translations ====================================// + 'apply' => 'Apply', 'action' => 'Action', 'affiliates' => 'Affiliates', 'attendees' => 'Attendees', @@ -42,21 +43,21 @@ return array ( 'submit' => 'Submit', 'success' => 'Success', 'ticket_design' => 'Ticket Design', + 'access_codes' => 'Access Codes', 'tickets' => 'Tickets', - 'TOP' => 'TOP', + 'TOP' => 'TOP', 'total' => 'total', 'whoops' => 'Whoops!', 'yes' => 'Yes', 'no' => 'No', - /* - * Lines below will turn obsolete in localization helper, it is declared in app/Helpers/macros. - * If you run it, it will break file input fields. - */ - 'upload' => 'Upload', - 'browse' => 'Browse', + /* + * Lines below will turn obsolete in localization helper, it is declared in app/Helpers/macros. + * If you run it, it will break file input fields. + */ + 'upload' => 'Upload', + 'browse' => 'Browse', //================================== Obsolete strings ==================================// - 'LLH:obsolete' => - array ( + 'LLH:obsolete' => [ 'months_long' => 'January|February|March|April|May|June|July|August|September|October|November|December', - ), + ], ); \ No newline at end of file diff --git a/resources/views/ManageEvent/Customize.blade.php b/resources/views/ManageEvent/Customize.blade.php index f19d1d06..606dd8b1 100644 --- a/resources/views/ManageEvent/Customize.blade.php +++ b/resources/views/ManageEvent/Customize.blade.php @@ -201,6 +201,8 @@ class="{{$tab == 'fees' ? 'active' : ''}}">@lang("basic.service_fees")
  • @lang("basic.ticket_design")
  • +
  • @lang("basic.access_codes")
  • @@ -587,6 +589,50 @@ +
    +
    +
    + +
    +
    +
     
    +
    +
    + @if($event->access_codes->count()) +
    + + + + + + + + + + @foreach($event->access_codes as $access_code) + + + + + @endforeach + +
    @lang("EventAccessCode.access_codes_code")@lang("EventAccessCode.access_codes_created_at")
    {{ $access_code->code }}{{ $access_code->created_at }}
    +
    + @else +
    + @lang("EventAccessCode.no_access_codes_yet") +
    + @endif +
    +
    +
    diff --git a/resources/views/ManageEvent/Modals/CreateAccessCode.blade.php b/resources/views/ManageEvent/Modals/CreateAccessCode.blade.php new file mode 100644 index 00000000..11a6ded0 --- /dev/null +++ b/resources/views/ManageEvent/Modals/CreateAccessCode.blade.php @@ -0,0 +1,32 @@ + \ No newline at end of file diff --git a/resources/views/ManageEvent/Modals/EditTicket.blade.php b/resources/views/ManageEvent/Modals/EditTicket.blade.php index a0ef94ce..e45b0ae9 100644 --- a/resources/views/ManageEvent/Modals/EditTicket.blade.php +++ b/resources/views/ManageEvent/Modals/EditTicket.blade.php @@ -100,9 +100,35 @@ {!! Form::checkbox('is_hidden', null, null, ['id' => 'is_hidden']) !!} {!! Form::label('is_hidden', trans("ManageEvent.hide_this_ticket"), array('class'=>' control-label')) !!} - + @if ($ticket->is_hidden) +
    +

    Select access codes

    + @if($ticket->event->access_codes->count()) + event_access_codes()->get()->map(function($accessCode) { + return $accessCode->pivot->event_access_code_id; + })->toArray(); + ?> + @foreach($ticket->event->access_codes as $access_code) +
    +
    +
    + {!! Form::checkbox('ticket_access_codes[]', $access_code->id, in_array($access_code->id, $selectedAccessCodes), ['id' => 'ticket_access_code_' . $access_code->id, 'data-toggle' => 'toggle']) !!} + {!! Form::label('ticket_access_code_' . $access_code->id, $access_code->code) !!} +
    +
    +
    + @endforeach + @else +
    + @lang("EventAccessCode.no_access_codes_yet") +
    + @endif +
    + @endif @lang("ManageEvent.more_options") diff --git a/resources/views/Public/ViewEvent/Partials/EventHiddenTicketsSelection.blade.php b/resources/views/Public/ViewEvent/Partials/EventHiddenTicketsSelection.blade.php new file mode 100644 index 00000000..7af77d31 --- /dev/null +++ b/resources/views/Public/ViewEvent/Partials/EventHiddenTicketsSelection.blade.php @@ -0,0 +1,71 @@ + +@foreach($tickets as $ticket) + + + + {{$ticket->title}} + +

    + {{$ticket->description}} +

    + + +
    + @if($ticket->is_free) + @lang("Public_ViewEvent.free") + + @else + + {{money($ticket->total_price, $event->currency)}} + {{ ($event->organiser->tax_name && $event->organiser->tax_value) ? '(+'.money(($ticket->total_price*($event->organiser->tax_value)/100), $event->currency).' '.$event->organiser->tax_name.')' : '' }} + + + @endif +
    + + + @if($ticket->is_paused) + + + @lang("Public_ViewEvent.currently_not_on_sale") + + + @else + + @if($ticket->sale_status === config('attendize.ticket_status_sold_out')) + + @lang("Public_ViewEvent.sold_out") + + @elseif($ticket->sale_status === config('attendize.ticket_status_before_sale_date')) + + @lang("Public_ViewEvent.sales_have_not_started") + + @elseif($ticket->sale_status === config('attendize.ticket_status_after_sale_date')) + + @lang("Public_ViewEvent.sales_have_ended") + + @else + {!! Form::hidden('tickets[]', $ticket->id) !!} + + + @endif + + @endif + + +@endforeach \ No newline at end of file diff --git a/resources/views/Public/ViewEvent/Partials/EventTicketsSection.blade.php b/resources/views/Public/ViewEvent/Partials/EventTicketsSection.blade.php index 86eb4269..99da6d7f 100644 --- a/resources/views/Public/ViewEvent/Partials/EventTicketsSection.blade.php +++ b/resources/views/Public/ViewEvent/Partials/EventTicketsSection.blade.php @@ -22,7 +22,7 @@ - @foreach($tickets as $ticket) + @foreach($tickets->where('is_hidden', false) as $ticket) @@ -90,12 +90,32 @@ @endforeach - - - - @lang("Public_ViewEvent.below_tickets") - - + @if ($tickets->where('is_hidden', true)->count() > 0) + + + @lang("Public_ViewEvent.has_unlock_codes") +
    + {!! Form::text('unlock_code', null, [ + 'class' => 'form-control', + 'id' => 'unlock_code', + 'style' => 'display:inline-block;width:65%;text-transform:uppercase;', + 'placeholder' => 'ex: UNLOCKCODE01', + ]) !!} + {!! Form::button(trans("basic.apply"), [ + 'class' => "btn btn-success", + 'id' => 'apply_access_code', + 'style' => 'display:inline-block;margin-top:-2px;', + 'data-dismiss' => 'modal', + ]) !!} +
    + + + @endif + + + @lang("Public_ViewEvent.below_tickets") + + @if(!$is_free_event)