Work on QR code checkin

Minor style changes to login / signup pages
This commit is contained in:
Dave 2016-04-11 12:52:50 +01:00 committed by Dave Earley
parent 34d5f7e983
commit bc730c959c
14 changed files with 421 additions and 232 deletions

View File

@ -365,8 +365,7 @@ class EventAttendeesController extends MyBaseController
->setCompany(config('attendize.app_name'));
$excel->sheet('attendees_sheet_1', function ($sheet) use ($event_id) {
DB::connection()->setFetchMode(\PDO::FETCH_ASSOC);
$data = DB::table('attendees')
->where('attendees.event_id', '=', $event_id)
@ -385,11 +384,9 @@ class EventAttendeesController extends MyBaseController
'orders.created_at',
DB::raw("(CASE WHEN attendees.has_arrived = 1 THEN 'YES' ELSE 'NO' END) AS `attendees.has_arrived`"),
'attendees.arrival_time',
'question_answers.answer_text'
])->get();
$sheet->fromArray($data);
$event = Event::scope()->findOrFail($event_id);
$sheet->row(1, [
'First Name', 'Last Name', 'Email', 'Ticket Reference', 'Order Reference', 'Ticket Type', 'Purchase Date', 'Has Arrived', 'Arrival Time',
]);

View File

@ -106,4 +106,92 @@ class EventCheckInController extends MyBaseController
'id' => $attendee->id,
]);
}
/**
* Check in an attendee
*
* @param $event_id
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function postCheckInAttendeeQr($event_id, Request $request)
{
$event = Event::scope()->findOrFail($event_id);
$qrcodeToken = $request->get('qrcode_token');
$attendee = Attendee::scope()->withoutCancelled()
->join('tickets', 'tickets.id', '=', 'attendees.ticket_id')
->where(function ($query) use ($event, $qrcodeToken) {
$query->where('attendees.event_id', $event->id)
->where('attendees.private_reference_number', $qrcodeToken);
})->select([
'attendees.id',
'attendees.order_id',
'attendees.first_name',
'attendees.last_name',
'attendees.email',
'attendees.reference',
'attendees.arrival_time',
'attendees.has_arrived',
'tickets.title as ticket',
])->first();
if(is_null($attendee)){
return response()->json(['status' => 'error', 'message' => "Invalid Ticket! Please try again."]);
}
$relatedAttendesCount = Attendee::where('id', '!=', $attendee->id)
->where([
'order_id' => $attendee->order_id,
'has_arrived' => false
])->count();
if($relatedAttendesCount >= 1){
$confirmOrderTicketsRoute = route('confirmCheckInOrderTickets', [$event->id, $attendee->order_id]);
$appendedText = '<br><br><form class="ajax" action="'. $confirmOrderTicketsRoute .'" method="POST">'. csrf_field() .'<button class="btn btn-primary btn-sm" type="submit"><i class="ico-ticket"></i> Check in all tickets associated to this order</button></form>';
} else {
$appendedText = '';
}
if ($attendee->has_arrived) {
return response()->json([
'status' => 'error',
'message' => 'Warning: This attendee has already been checked in at '. $attendee->arrival_time->format('H:i A, F j'). '.' . $appendedText
]);
}
Attendee::find($attendee->id)->update(['has_arrived' => true, 'arrival_time' => Carbon::now()]);
return response()->json([
'status' => 'success',
'message' => 'Success !<br>Name: ' . $attendee->first_name . ' ' . $attendee->last_name . '<br>Reference: '. $attendee->reference . '<br>Ticket: '. $attendee->ticket . '.' . $appendedText
]);
}
/**
* Confirm tickets of same order.
*
* @param $event_id
* @param $order_id
* @return \Illuminate\Http\Response
*/
public function confirmOrderTicketsQr($event_id, $order_id)
{
$updateRowsCount = Attendee::scope()->where([
'event_id' => $event_id,
'order_id' => $order_id,
'has_arrived' => 0,
])->update([
'has_arrived' => 1,
'arrival_time' => Carbon::now(),
]);
return response()->json([
'message' => $updateRowsCount . ' Attendee(s) Checked in.'
]);
}
}

View File

@ -72,7 +72,7 @@ class EventQrcodeCheckInController extends Controller
if($relatedAttendesCount >= 1){
$confirmOrderTicketsRoute = route('confirmCheckInOrderTickets', [$event->id, $attendee->order_id]);
$appendedText = '<br><br><form action="'. $confirmOrderTicketsRoute .'" method="POST">'. csrf_field() .'<input type="hidden" name="_method" value="PUT"><div class="row"><div class="col-md-10 col-md-offset-1 col-xs-12"><button class="btn btn-primary btn-block btn-lg" type="submit"><i class="fa fa-ticket"></i> Check in other tickets associated to this order</button></div></div></form>';
$appendedText = '<br><br><form class="ajax" action="'. $confirmOrderTicketsRoute .'" method="POST">'. csrf_field() .'<button class="btn btn-primary btn-sm" type="submit"><i class="ico-ticket"></i> Check in all tickets associated to this order</button></form>';
} else {
$appendedText = '';
}
@ -101,14 +101,17 @@ class EventQrcodeCheckInController extends Controller
*/
public function confirmOrderTickets($event_id, $order_id)
{
$updateRowsCount = Attendee::where([
$updateRowsCount = Attendee::scope()->where([
'event_id' => $event_id,
'order_id' => $order_id,
'has_arrived' => false
'has_arrived' => false,
'arrival_time' => Carbon::now(),
])
->update(['has_arrived' => true, 'arrival_time' => Carbon::now()]);
session()->flash('success_message', $updateRowsCount . ' other tickets checked in.');
return back();
return response()->json([
'message' => $updateRowsCount . ' Attendee(s) Checked in.'
]);
}
}

View File

@ -15,7 +15,6 @@ use Input;
use Response;
use Validator;
use Hash;
use View;
class ManageAccountController extends MyBaseController
{

View File

@ -609,11 +609,17 @@ Route::group(['middleware' => ['auth', 'first.run']], function () {
'uses' => 'EventCheckInController@postCheckInAttendee',
]);
Route::get('{event_id}/check_in_qrcode', [
'as' => 'showQRCodeModal',
'uses' => 'EventCheckInController@showQRCodeModal'
Route::post('{event_id}/qrcode_check_in', [
'as' => 'postQRCodeCheckInAttendee',
'uses' => 'EventCheckInController@postCheckInAttendeeQr',
]);
Route::post( '{event_id}/confirm_order_tickets/{order_id}', [
'as' => 'confirmCheckInOrderTickets',
'uses' => 'EventCheckInController@confirmOrderTicketsQr',
]);
/*
* -------
* QRCode Check In App

View File

@ -1,6 +1,6 @@
{
"name": "Attendize",
"version": "1.0.0",
"version": "0.6.0",
"description": "Attendize Ticketing Platform",
"main": "public/index.php",
"dependencies": {

View File

@ -1,99 +1 @@
body{
font-family: "Source Sans Pro", sans-serif;
}
a:active, a:focus, a:visited{
text-decoration: none;
}
a:hover{
text-decoration: underline;
}
#outdiv
{
width: 400px;
height: 250px;
margin: 0;
padding: 0;
}
#v{
width: 400px;
height: auto;
margin: 0;
padding: 0;
border: 2px solid #c5c5c5;
}
#qrfile{
border: 2px solid #c5c5c5;
width: 400px;
height: 250px;
}
#qr-canvas{
display:none;
}
#help-text{
z-index: 9999999999;
position: relative;
color: #00AEFB;
top: 0;
}
#imghelp{
position:relative;
left:0px;
top:-160px;
z-index:100;
background:#f2f2f2;
margin-left:35px;
margin-right:35px;
padding-top:15px;
padding-bottom:15px;
border-radius:20px;
}
.selector{
cursor:pointer;
}
#result{
border: 1px solid #eaeaea;
padding: 10px;
margin-top: 3em;
color: #456D86;
width: 50%;
background-color: #f2f2f2;
font-size: 1.5em;
font-weight: 500;
box-shadow: none;
border-radius: 3px;
transition: background 0.4s ease-in-out;
}
/* Mobile */
@media only screen and (max-width: 480px) {
#result{
width: 90%;
}
#outdiv{
width: 300px;
height: auto;
}
#help-text{
top: -35px;
}
#qrfile{
width: 300px;
}
#v{
width: 300px;
}
}

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,6 @@
{!! HTML::style('assets/stylesheet/application.css') !!}
{!! HTML::script('vendor/jquery/jquery.js') !!}
{!! HTML::style('assets/stylesheet/qrcode-check-in.css') !!}
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
@ -48,9 +47,6 @@
.attendeeList .container {
background: #fff;
border-radius: 2px;
-webkit-box-shadow: 0 2px 2px #ccc;
box-shadow: 0 2px 2px #ccc;
margin-bottom: 50px;
padding-top: 10px;
}
@ -60,10 +56,6 @@
padding-top: 0;
}
.list-group-item:first-child {
border-top-right-radius: 4px;
border-top-left-radius: 4px;
}
.attendeeList .container .attendee_list .attendees_title {
margin: 10px 0 20px 0;
@ -160,6 +152,29 @@
left: 0;
}
.modal-dialog {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.modal-content {
height: auto;
min-height: 100%;
border-radius: 0;
}
.modal-footer {
position: absolute;
width: 100%;
bottom: 0;
}
.modal-body {
padding: 0;
}
/* Small Devices, Tablets */
@media (min-width: 100px) and (max-width: 767px) {
@ -221,7 +236,7 @@
+ attendees[i].last_name
+ ' </b><br>Reference: <b>' + attendees[i].reference + '</b>'
+ ' <br>Ticket: <b>' + attendees[i].ticket + '</b>'
+ '<a href="" class="ci btn btn-success"><i class="ico-checkmark"></i></a> '
+ '<a href="" class="ci btn btn-successfulQrRead"><i class="ico-checkmark"></i></a> '
+ '</li>');
}
}
@ -321,8 +336,8 @@
$('.qr_search').on('click', function(e) {
load();
$('#QrModal').modal('show');
loadQrReader();
});
$("input#search").on("keyup", function(e) {
@ -405,13 +420,6 @@
<div role="dialog" id="QrModal" class="modal fade" style="display: none;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header text-center">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 class="modal-title">
<i class="ico-qrcode"></i>
Check-in
</h3>
</div>
<div class="modal-body">
@if(session()->has('success_message'))
<div class="container">
@ -427,15 +435,16 @@
@endif
<div id="outdiv">
<div id="ScanVideoOutter">
</div>
<p><a onclick="event.preventDefault(); workingAway = false; load();" href="{{ Request::url() }}"><i class="fa fa-refresh"></i> Scan another ticket</a></p>
<div id="result"></div>
<canvas id="qr-canvas" width="800" height="600"></canvas>
<div class="well" id="ScanResult"></div>
<canvas id="QrCanvas" width="800" height="600"></canvas>
</div>
<div class="modal-footer">
{!! Form::button('Close', ['class'=>"btn modal-close btn-danger",'data-dismiss'=>'modal']) !!}
{!! Form::button('Close Scanner', ['class'=>"btn modal-close btn-danger",'data-dismiss'=>'modal']) !!}
<a class="btn btn-primary" onclick="event.preventDefault(); workingAway = false; loadQrReader();" href="{{ Request::url() }}"><i class="fa fa-refresh"></i> Scan another ticket</a>
</div>
</div><!-- /end modal content-->
</div>
@ -445,36 +454,223 @@
{!! HTML::script('vendor/qrcode-scan/llqrcode.js') !!}
<style>
#ScanResult {
width: 90%;
}
#ScanVideoOutter {
min-width: 100%;
min-height: 300px;
position: relative;
background: #999;
}
#ScanVideo {
position: absolute;
width: 100%;
height:100%;
min-height:250px;
min-width: 250px !important;
top: 0;
left: 0;
right: 0;
}
#qr-file {
}
#QrCanvas{
display:none;
}
#help-text{
z-index: 9999999999;
position: relative;
color: #00AEFB;
top: 0;
}
#imghelp{
position:relative;
left:0px;
top:-160px;
z-index:100;
background:#f2f2f2;
margin-left:35px;
margin-right:35px;
padding-top:15px;
padding-bottom:15px;
border-radius:20px;
}
#ScanResult {
transition: background 0.4s ease-in-out;
width: 100%;
border: none;
border-bottom: 1px solid #ccc;
font-size: 16px;
}
/* Mobile */
/*@media only screen and (max-width: 480px) {*/
/*#ScanResult {*/
/*width: 90%;*/
/*}*/
/*#ScanVideoOutter {*/
/*min-width: 100%;*/
/*max-width: 100%;*/
/*position: relative;*/
/*}*/
/*#ScanVideo {*/
/*position: absolute;*/
/*width: 100%;*/
/*height:100%;*/
/*top: 0;*/
/*left: 0;*/
/*right: 0;*/
/*}*/
/*#help-text{*/
/*top: -35px;*/
/*}*/
/*#qrfile{*/
/*width: 300px;*/
/*}*/
/*}*/
@-webkit-keyframes opacity {
0% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
100% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
}
}
@-moz-keyframes opacity {
0% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
100% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
}
}
@-webkit-keyframes opacity {
0% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
100% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
}
}
@-moz-keyframes opacity {
0% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
100% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
}
}
@-o-keyframes opacity {
0% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
100% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
}
}
@keyframes opacity {
0% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
100% {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
}
}
#scanning-ellipsis span {
-webkit-animation-name: opacity;
-webkit-animation-duration: 1s;
-webkit-animation-iteration-count: infinite;
-moz-animation-name: opacity;
-moz-animation-duration: 1s;
-moz-animation-iteration-count: infinite;
-ms-animation-name: opacity;
-ms-animation-duration: 1s;
-ms-animation-iteration-count: infinite;
}
#scanning-ellipsis span:nth-child(2) {
-webkit-animation-delay: 100ms;
-moz-animation-delay: 100ms;
-ms-animation-delay: 100ms;
-o-animation-delay: 100ms;
animation-delay: 100ms;
}
#scanning-ellipsis span:nth-child(3) {
-webkit-animation-delay: 300ms;
-moz-animation-delay: 300ms;
-ms-animation-delay: 300ms;
-o-animation-delay: 300ms;
animation-delay: 300ms;
}
</style>
{{--QR JS - THIS WILL BE MOVED--}}
<script>
// QRCODE reader Copyright 2011 Lazar Laszlo
// http://www.webqr.com
var workingAway = false;
var gCtx = null;
function resizeVideo() {
var $videoWrapper = $('#ScanVideoOutter');
var $video = $('#ScanVideo');
$video.height($videoWrapper.height());
$video.width($videoWrapper.width());
}
$(function() {
$( window ).resize(resizeVideo);
});
var canvasContext = null;
var gCanvas = null;
var c=0;
var stype=0;
var gUM=false;
var webkit=false;
var moz=false;
var v=null;
var theVideo=null;
var beepSound = new Audio('/mp3/beep.mp3');
var vidhtml = '<video id="v" autoplay></video>';
function initCanvas(w,h)
{
gCanvas = document.getElementById("qr-canvas");
gCanvas.style.width = w + "px";
gCanvas.style.height = h + "px";
gCanvas.width = w;
gCanvas.height = h;
gCtx = gCanvas.getContext("2d");
gCtx.clearRect(0, 0, w, h);
}
var vidhtml = '<video id="ScanVideo" autoplay></video>';
function captureToCanvas() {
if(stype!=1)
@ -482,7 +678,7 @@
if(gUM)
{
try{
gCtx.drawImage(v,0,0);
canvasContext.drawImage(theVideo,0,0);
try{
qrcode.decode();
}
@ -494,7 +690,7 @@
catch(e){
console.log(e);
setTimeout(captureToCanvas, 500);
};
}
}
}
@ -504,12 +700,6 @@
function read(qrcode_token)
{
if(workingAway) {
return;
}
workingAway = true;
$.ajax({
type: "POST",
url: '{{ route('postQRCodeCheckInAttendee', ['event_id' => $event->id]) }}',
@ -519,29 +709,28 @@
beepSound.play();
},
error: function() {
showMessage('Something has gone wrong. Please try again.');
},
success: function(response) {
document.getElementById("result").innerHTML = "<b>" + response.message +"</b>";
$('#ScanResult').html("<b>" + response.message +"</b>");
}
});
}
function isCanvasSupported(){
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
function success(stream) {
function successfulQrRead(stream) {
if(webkit)
v.src = window.webkitURL.createObjectURL(stream);
else
if(moz)
theVideo.src = window.URL.createObjectURL(stream);
else if(moz)
{
v.mozSrcObject = stream;
v.play();
theVideo.mozSrcObject = stream;
theVideo.play();
}
else
v.src = stream;
else {
theVideo.src = stream;
}
gUM=true;
setTimeout(captureToCanvas, 500);
}
@ -551,61 +740,52 @@
return;
}
function load()
function loadQrReader()
{
if(isCanvasSupported() && window.File && window.FileReader)
{
initCanvas(800, 600);
qrcode.callback = read;
setwebcam();
}
else
{
document.getElementById("mainbody").style.display="inline";
document.getElementById("mainbody").innerHTML='<p id="mp1">Attendize Checkpoint Manager for HTML5 capable browsers</p><br>'+
'<br><p id="mp2">sorry your browser is not supported</p><br><br>'+
'<p id="mp1">try <a href="http://www.mozilla.com/firefox"><img src="/assets/images/firefox.png"/></a> or <a href="http://chrome.google.com"><img src="/assets/images/chrome_logo.gif"/></a> or <a href="http://www.opera.com"><img src="/assets/images/Opera-logo.png"/></a></p>';
}
}
function setwebcam()
{
// document.getElementById("help-text").style.display = "block";
document.getElementById("result").innerHTML='Scanning&nbsp;&nbsp;&nbsp;<i class="fa fa-spinner fa-spin"></i>';
var $canvas = $('#QrCanvas');
$canvas.height('300px');
$canvas.width('600px');
canvasContext = $canvas[0].getContext('2d');
canvasContext.clearRect(0, 0, 600, 300);
qrcode.callback = read;
$('#ScanResult').html('<div id="scanning-ellipsis">Scanning<span>.</span><span>.</span><span>.</span></div>');
if(stype==1)
{
setTimeout(captureToCanvas, 500);
return;
}
var n=navigator;
document.getElementById("outdiv").innerHTML = vidhtml;
v=document.getElementById("v");
if(n.getUserMedia)
n.getUserMedia({video: true, audio: false}, success, error);
else
if(n.webkitGetUserMedia)
$('#ScanVideoOutter').html(vidhtml);
theVideo = $("#ScanVideo")[0];
if(navigator.getUserMedia)
{
navigator.getUserMedia({video: true, audio: false}, successfulQrRead, error);
} else if(navigator.webkitGetUserMedia)
{
webkit=true;
n.webkitGetUserMedia({video:true, audio: false}, success, error);
navigator.webkitGetUserMedia({video:true, audio: false}, successfulQrRead, error);
}
else
if(n.mediaDevices.getUserMedia)
else if(navigator.mediaDevices.getUserMedia)
{
moz=true;
n.mozGetUserMedia({video: true, audio: false}, success, error);
navigator.mozGetUserMedia({video: true, audio: false}, successfulQrRead, error);
}
else
if(n.mozGetUserMedia)
else if(navigator.mozGetUserMedia)
{
moz=true;
n.mozGetUserMedia({video: true, audio: false}, success, error);
navigator.mozGetUserMedia({video: true, audio: false}, successfulQrRead, error);
}
stype=1;
setTimeout(captureToCanvas, 500);
}
</script>

View File

@ -22,7 +22,7 @@
<div class="logo">
{!!HTML::image('assets/images/logo-100x100-lightBg.png')!!}
</div>
<h1>Create Organiser</h1>
<h2>Create Organiser</h2>
{!! Form::open(array('url' => route('postCreateOrganiser'), 'class' => 'ajax')) !!}
@if(@$_GET['first_run'] == '1')
@ -31,22 +31,31 @@
</div>
@endif
<div class="form-group">
{!! Form::label('name', 'Organiser Name', array('class'=>'required control-label ')) !!}
{!! Form::text('name', Input::old('name'),
array(
'class'=>'form-control'
)) !!}
</div>
<div class="form-group">
{!! Form::label('email', 'Organiser Email', array('class'=>'control-label required')) !!}
{!! Form::text('email', Input::old('email'),
array(
'class'=>'form-control ',
'placeholder'=>''
)) !!}
<div class="row">
<div class="col-md-6">
<div class="form-group">
{!! Form::label('name', 'Organiser Name', array('class'=>'required control-label ')) !!}
{!! Form::text('name', Input::old('name'),
array(
'class'=>'form-control'
)) !!}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
{!! Form::label('email', 'Organiser Email', array('class'=>'control-label required')) !!}
{!! Form::text('email', Input::old('email'),
array(
'class'=>'form-control ',
'placeholder'=>''
)) !!}
</div>
</div>
</div>
<div class="form-group">
{!! Form::label('about', 'Organiser Description', array('class'=>'control-label ')) !!}
{!! Form::textarea('about', Input::old('about'),

View File

@ -15,6 +15,7 @@ Forgot Password
<div class="logo">
{!!HTML::image('assets/images/logo-100x100-lightBg.png')!!}
</div>
<h2>Forgot Password</h2>
@if (Session::has('status'))
<div class="alert alert-info">

View File

@ -14,7 +14,7 @@ Reset Password
<div class="logo">
{!!HTML::image('assets/images/logo-100x100-lightBg.png')!!}
</div>
<h2>Reset Password</h2>
@if (Session::has('status'))
<div class="alert alert-info">
An email with the password reset has been sent to your email.

View File

@ -12,6 +12,7 @@ Sign Up
<div class="logo">
{!! HTML::image('assets/images/logo-100x100-lightBg.png') !!}
</div>
<h2>Sign up</h2>
@if(Input::get('first_run'))
<div class="alert alert-info">

View File

@ -17,6 +17,18 @@
<style>
body {
background: url({{asset('assets/images/splash.jpg')}}) no-repeat center center fixed;
background-size: cover;
}
h2 {
text-align: center;
margin-bottom: 31px;
text-transform: uppercase;
letter-spacing: 4px;
font-size: 23px;
}
.panel {
background-color: #ffffff;
background-color: rgba(255,255,255,.95);
@ -55,15 +67,6 @@
</section>
{!!HTML::script('assets/javascript/backend.js')!!}
{!!HTML::script('vendor/jquery-backstretch/jquery.backstretch.min.js')!!}
<script>
$(function() {
$.backstretch([
'{{asset('assets/images/splash.jpg')}}'
], {duration: 3500, fade: 1000});
});
</script>
</body>
@include('Shared.Partials.GlobalFooterJS')
</html>