diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml
deleted file mode 100644
index 18b32b3..0000000
--- a/.github/workflows/pull-requests.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-name: pull requests
-
-on:
- pull_request_target:
- types: [opened]
-
-permissions:
- pull-requests: write
-
-jobs:
- uneditable:
- uses: laravel/.github/.github/workflows/pull-requests.yml@main
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
deleted file mode 100644
index 7766203..0000000
--- a/.github/workflows/tests.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-name: Tests
-
-on:
- push:
- branches:
- - master
- - '*.x'
- pull_request:
- schedule:
- - cron: '0 0 * * *'
-
-permissions:
- contents: read
-
-jobs:
- tests:
- runs-on: ubuntu-latest
-
- strategy:
- fail-fast: true
- matrix:
- php: ['8.0', 8.1, 8.2]
-
- name: PHP ${{ matrix.php }}
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
-
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php }}
- extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite
- coverage: none
-
- - name: Install Composer dependencies
- run: composer install --prefer-dist --no-interaction --no-progress
-
- - name: Copy environment file
- run: cp .env.example .env
-
- - name: Generate app key
- run: php artisan key:generate
-
- - name: Execute tests
- run: vendor/bin/phpunit
diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml
deleted file mode 100644
index ebda620..0000000
--- a/.github/workflows/update-changelog.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-name: update changelog
-
-on:
- release:
- types: [released]
-
-permissions: {}
-
-jobs:
- update:
- permissions:
- contents: write
- uses: laravel/.github/.github/workflows/update-changelog.yml@main
diff --git a/app/Http/Controllers/Main/CinemaController.php b/app/Http/Controllers/Main/CinemaController.php
new file mode 100644
index 0000000..3c17865
--- /dev/null
+++ b/app/Http/Controllers/Main/CinemaController.php
@@ -0,0 +1,19 @@
+ "Cinemas", 'cinemas' => \App\Models\Cinema::all()]);
+ }
+
+ public function show($id)
+ {
+ return view('main.cinemas.cinema', ['title' => "Cinema", 'cinema' => \App\Models\Cinema::findOrfail($id)]);
+ }
+}
diff --git a/app/Http/Controllers/Main/GenreController.php b/app/Http/Controllers/Main/GenreController.php
new file mode 100644
index 0000000..a3a8e07
--- /dev/null
+++ b/app/Http/Controllers/Main/GenreController.php
@@ -0,0 +1,19 @@
+ "Genre", 'genre' => \App\Models\Genre::findOrfail($id)]);
+ }
+
+ public function showAllGenres()
+ {
+ return view('main.genres.index', ['title' => "Genres", 'genres' => \App\Models\Genre::all()]);
+ }
+}
diff --git a/app/Http/Controllers/Main/MovieController.php b/app/Http/Controllers/Main/MovieController.php
new file mode 100644
index 0000000..e30500c
--- /dev/null
+++ b/app/Http/Controllers/Main/MovieController.php
@@ -0,0 +1,35 @@
+ "Movies", 'movies' => \App\Models\Movie::all(), 'genres' => \App\Models\Genre::all(), 'showings' => \App\Models\Showing::all()]);
+ }
+
+ public function moviesNowShowing()
+ {
+ // map showings that are in the future to movies
+ $showings = \App\Models\Showing::all()->filter(function ($showing) {
+ return $showing->showing_start > now();
+ });
+ // $movies must be a collection of unique movies
+ $movies = collect();
+ foreach ($showings as $showing) {
+ if (!$movies->contains($showing->movie)) {
+ $movies->push($showing->movie);
+ }
+ }
+ return view('main.movies.index', ['title' => "Movies Now Showing", 'movies' => $movies, 'genres' => \App\Models\Genre::all(), 'showings' => \App\Models\Showing::all()]);
+ }
+
+ public function show($id)
+ {
+ return view('main.movies.movie', ['title' => "Movie", 'movie' => \App\Models\Movie::findOrfail($id)]);
+ }
+}
diff --git a/app/Http/Controllers/Main/ShowingController.php b/app/Http/Controllers/Main/ShowingController.php
new file mode 100644
index 0000000..df75349
--- /dev/null
+++ b/app/Http/Controllers/Main/ShowingController.php
@@ -0,0 +1,24 @@
+ "Showing", 'showing' => \App\Models\Showing::findOrfail($id)]);
+ }
+
+ public function showAllShowings()
+ {
+ return view('main.showings.index', ['title' => "Showings", 'showings' => \App\Models\Showing::all()]);
+ }
+
+ public function order($id)
+ {
+ return view('main.order', ['title' => "Order Tickets", 'showing' => \App\Models\Showing::findOrfail($id)]);
+ }
+}
diff --git a/app/Http/Controllers/Managing/CinemaController.php b/app/Http/Controllers/Managing/CinemaController.php
index 4fcb3b2..89c6b28 100644
--- a/app/Http/Controllers/Managing/CinemaController.php
+++ b/app/Http/Controllers/Managing/CinemaController.php
@@ -8,12 +8,15 @@ public function __construct()
{
$this->middleware('auth');
$this->middleware('atleast:employee');
- $this->middleware('permission:manage_cinemas')->only(['create', 'store', 'edit', 'update', 'destroy']);
+ $this->middleware('permission:READ_CINEMAS')->only('index', 'show');
+ $this->middleware('permission:CREATE_CINEMAS')->only('create', 'store');
+ $this->middleware('permission:UPDATE_CINEMAS')->only('edit', 'update');
+ $this->middleware('permission:DELETE_CINEMAS')->only('destroy');
}
- public function showAllCinemas()
+ public function index()
{
- return view('manage.cinemas.index', ['title' => "Manage Cinemas", 'cinemas' => \App\Models\Cinema::all()]);
+ return view('manage.cinemas.index', ['title' => "Manage Cinemas", 'cinemas' => \App\Models\Cinema::all(), 'users' => \App\Models\User::all()]);
}
public function edit($id)
diff --git a/app/Http/Controllers/Managing/GenreController.php b/app/Http/Controllers/Managing/GenreController.php
index d1da3c0..30c6155 100644
--- a/app/Http/Controllers/Managing/GenreController.php
+++ b/app/Http/Controllers/Managing/GenreController.php
@@ -10,10 +10,13 @@ class GenreController extends Controller
public function __construct() {
$this->middleware('auth');
$this->middleware('atleast:employee');
- $this->middleware('permission:manage_genres')->only(['create', 'store', 'edit', 'update', 'destroy']);
+ $this->middleware('permission:READ_GENRES')->only('index', 'show');
+ $this->middleware('permission:CREATE_GENRES')->only('create', 'store');
+ $this->middleware('permission:UPDATE_GENRES')->only('edit', 'update');
+ $this->middleware('permission:DELETE_GENRES')->only('destroy');
}
- public function showAllGenres() {
+ public function index() {
return view('manage.genres.index', ['title' => "Manage Genres", 'genres' => \App\Models\Genre::all()]);
}
diff --git a/app/Http/Controllers/Managing/MovieController.php b/app/Http/Controllers/Managing/MovieController.php
index d4117ba..c3a30e9 100644
--- a/app/Http/Controllers/Managing/MovieController.php
+++ b/app/Http/Controllers/Managing/MovieController.php
@@ -11,14 +11,13 @@ class MovieController extends Controller
public function __construct() {
$this->middleware('auth');
$this->middleware('atleast:employee');
- $this->middleware('permission:manage_movies')->only(['create', 'store', 'edit', 'update', 'destroy']);
+ $this->middleware('permission:READ_MOVIES')->only('index', 'show');
+ $this->middleware('permission:CREATE_MOVIES')->only('create', 'store');
+ $this->middleware('permission:UPDATE_MOVIES')->only('edit', 'update');
+ $this->middleware('permission:DELETE_MOVIES')->only('destroy');
}
- public function showAllMovies() {
- // has permission READ_MOVIES ?
- if(!Auth::user()->allowedTo('READ_MOVIES')) {
- abort(403);
- }
+ public function index() {
return view('manage.movies.index', ['title' => "Manage Movies", 'movies' => \App\Models\Movie::all()]);
}
@@ -37,6 +36,8 @@ public function store() {
$movie->movie_description = request('movie_description');
$movie->movie_year = request('movie_year');
$movie->movie_image = request('movie_image');
+ $movie->movie_length = request('movie_length');
+ $movie->movie_age_limit = request('movie_age_limit');
// $movie->user_id = auth()->user()->user_id;
$movie->genre_id = request('genre_id');
$movie->save();
diff --git a/app/Http/Controllers/Managing/ShowingsController.php b/app/Http/Controllers/Managing/ShowingsController.php
new file mode 100644
index 0000000..97eeb92
--- /dev/null
+++ b/app/Http/Controllers/Managing/ShowingsController.php
@@ -0,0 +1,66 @@
+middleware('auth');
+ $this->middleware('atleast:employee');
+ $this->middleware('permission:READ_SHOWINGS')->only('index', 'show');
+ $this->middleware('permission:CREATE_SHOWINGS')->only('create', 'store');
+ $this->middleware('permission:UPDATE_SHOWINGS')->only('edit', 'update');
+ $this->middleware('permission:DELETE_SHOWINGS')->only('destroy');
+ }
+
+ public function index()
+ {
+ return view('manage.showings.index', ['title' => "Manage Showings", 'showings' => \App\Models\Showing::all()]);
+ }
+
+ public function edit($id)
+ {
+ $s = \App\Models\Showing::findOrfail($id);
+ return view('manage.showings.showing', ['title' => "Manage Showing", 'showing' => $s, 'movies' => \App\Models\Movie::all(), 'rooms' => \App\Models\Room::all(), 'cinemas' => \App\Models\Cinema::all()]);
+ }
+
+ public function createShowing()
+ {
+ return view('manage.showings.create', ['title' => "Create Showing", 'movies' => \App\Models\Movie::all(), 'rooms' => \App\Models\Room::all()]);
+ }
+
+ public function store()
+ {
+ $showing = new \App\Models\Showing();
+ $showing->movie_id = request('movie_id');
+ $showing->room_id = request('room_id');
+ $showing->start_time = request('start_time');
+ $showing->save();
+ return redirect()->route('manage.showings');
+ }
+
+ public function show($id)
+ {
+ return view('main.showings.showing', ['title' => "Edit Showing", 'showing' => \App\Models\Showing::findOrfail($id)]);
+ }
+
+ public function update($id)
+ {
+ $showing = \App\Models\Showing::findOrfail($id);
+ $showing->movie_id = request('movie_id');
+ $showing->room_id = request('room_id');
+ $showing->showing_start = request('showing_start');
+ $showing->save();
+ return redirect()->route('manage.showings');
+ }
+
+ public function destroy($id)
+ {
+ $showing = \App\Models\Showing::findOrfail($id);
+ $showing->delete();
+ return redirect()->route('manage.showings');
+ }
+}
diff --git a/app/Http/Controllers/Managing/UsersController.php b/app/Http/Controllers/Managing/UsersController.php
new file mode 100644
index 0000000..ffad832
--- /dev/null
+++ b/app/Http/Controllers/Managing/UsersController.php
@@ -0,0 +1,81 @@
+middleware('auth');
+ $this->middleware('atleast:manage');
+ $this->middleware('permission:READ_USERS')->only('index', 'show');
+ $this->middleware('permission:CREATE_USERS')->only('create', 'store');
+ $this->middleware('permission:UPDATE_USERS')->only('edit', 'update');
+ $this->middleware('permission:DELETE_USERS')->only('destroy');
+
+ }
+
+ public function index()
+ {
+ return view('manage.users.index', ['title' => "Manage Users", 'users' => \App\Models\User::all()]);
+ }
+
+ public function create()
+ {
+ return view('manage.users.create');
+ }
+
+ public function store()
+ {
+ $user = new \App\Models\User();
+ $user->name = request('name');
+ $user->email = request('email');
+ $user->password = request('password');
+ $user->role = request('role');
+ $user->save();
+
+ // user assignments (user_assignments.user_id, user_assignments.cinema_id)
+ $cinemas = request('cinemas');
+ if ($cinemas) {
+ foreach ($cinemas as $cinema) {
+ $user->cinemas()->attach($cinema);
+ }
+ }
+
+ return redirect()->route('manage.users');
+ }
+
+ public function show($id)
+ {
+ return view('manage.users.user', ['user' => \App\Models\User::findOrfail($id)]);
+ }
+
+ public function update($id)
+ {
+ $user = \App\Models\User::findOrfail($id);
+ $user->name = request('name');
+ $user->email = request('email');
+ $user->password = request('password');
+ $user->role = request('role');
+ $user->save();
+
+ // user assignments (user_assignments.user_id, user_assignments.cinema_id)
+ $cinemas = request('cinemas');
+ if ($cinemas) {
+ foreach ($cinemas as $cinema) {
+ $user->cinemas()->attach($cinema);
+ }
+ }
+
+ return redirect()->route('manage.users');
+ }
+
+ public function destroy($id)
+ {
+ $user = \App\Models\User::findOrfail($id);
+ $user->delete();
+ return redirect()->route('manage.users');
+ }
+}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index 2da7d06..82d11f5 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -55,6 +55,7 @@ class Kernel extends HttpKernel
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'atleast' => \App\Http\Middleware\AtleastRole::class,
+ 'permission' => \App\Http\Middleware\Permission::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
diff --git a/app/Http/Middleware/CinemaAccess.php b/app/Http/Middleware/CinemaAccess.php
new file mode 100644
index 0000000..422d12e
--- /dev/null
+++ b/app/Http/Middleware/CinemaAccess.php
@@ -0,0 +1,33 @@
+user()->atleast('admin')) {
+ return $next($request);
+ }
+
+ if (auth()->user()->cinemas->contains($cinema_id)) {
+ return $next($request);
+ }
+
+
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/Permission.php b/app/Http/Middleware/Permission.php
new file mode 100644
index 0000000..4d2dce4
--- /dev/null
+++ b/app/Http/Middleware/Permission.php
@@ -0,0 +1,28 @@
+user()->role == 'admin') {
+ return $next($request);
+ }
+ if (auth()->user()->hasPermission($permission)) {
+ return $next($request);
+ }
+ abort(403, "You need \"$permission\" permission");
+ }
+}
diff --git a/app/Models/Cinema.php b/app/Models/Cinema.php
new file mode 100644
index 0000000..9bf5bd3
--- /dev/null
+++ b/app/Models/Cinema.php
@@ -0,0 +1,53 @@
+belongsTo(Address::class, 'address_id', 'address_id');
+ }
+
+ public function rooms()
+ {
+ return $this->hasMany(Room::class, 'cinema_id', 'cinema_id');
+ }
+
+ public function showings()
+ {
+ return $this->hasManyThrough(Showing::class, Room::class, 'cinema_id', 'room_id', 'cinema_id', 'room_id');
+ }
+
+ public function find($id)
+ {
+ return $this->where('cinema_id', $id)->first();
+ }
+
+ public function users() {
+ //users associated
+ return $this->belongsToMany('App\Models\User', 'user_assignments', 'cinema_id', 'user_id');
+ }
+
+}
diff --git a/app/Models/Permission.php b/app/Models/Permission.php
index fb14ecc..71a052d 100644
--- a/app/Models/Permission.php
+++ b/app/Models/Permission.php
@@ -22,4 +22,16 @@ public function users()
return $this->belongsToMany('App\Models\User', 'user_permissions', 'permission_id', 'user_id');
}
+ public function find(mixed $permission_id)
+ {
+ return $this->where('permission_id', $permission_id)->first();
+ }
+
+ public function create(array $array)
+ {
+ $this->permission_name = $array['permission_name'];
+ $this->save();
+ return $this;
+ }
+
}
diff --git a/app/Models/Showing.php b/app/Models/Showing.php
index 0c1d074..8b510b5 100644
--- a/app/Models/Showing.php
+++ b/app/Models/Showing.php
@@ -41,12 +41,17 @@ public function movie()
return $this->belongsTo(Movie::class, 'movie_id', 'movie_id');
}
+ public function prices()
+ {
+ return $this->hasMany(Price::class, 'showing_id', 'showing_id');
+ }
+
public function nowPlaying()
{
return $this->where('showing_start', '>=', now())->get();
}
- public function end_time() {
+ public function showing_end() {
$date = new Carbon($this->showing_start);
$date->addMinutes($this->movie->movie_length);
return $date;
diff --git a/app/Models/User.php b/app/Models/User.php
index 25df236..0977180 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -54,14 +54,17 @@ public function permissions()
// the permissions are in the permissions table
// only return valid permissions
return $this->belongsToMany('App\Models\Permission', 'user_permissions', 'user_id', 'permission_id')->where(function ($query) {
- $query->where('user_permission_start', '<=', now())->where(function ($query) {
- $query->where('user_permission_end', '>=', now())->orWhereNull('user_permission_end');
- });
+// $query->where('user_permission_start', '<=', now())->where(function ($query) {
+// $query->where('user_permission_end', '>=', now())->orWhereNull('user_permission_end');
+// });
});
}
- public function allowedTo($permission): bool
+ public function hasPermission($permission): bool
{
+ if ($this->role == 'admin') {
+ return true;
+ }
if ($this->permissions()->where('permission_name', $permission)->first()) {
return true;
}
@@ -73,7 +76,8 @@ public function atleast($role): bool
$hierarchy = [
'default' => 0,
'employee' => 1,
- 'manage' => 2
+ 'manage' => 2,
+ 'admin' => 3
];
return $hierarchy[$this->role] >= $hierarchy[$role];
}
@@ -83,4 +87,21 @@ public static function find($id)
return User::all()->where('user_id', $id)->first();
}
+ public function cinemas()
+ {
+ // check user_assignments table for all assignments for this user
+ if ($this->atleast('admin')) {
+ // if user is a manager or admin, return all cinemas
+ return Cinema::all();
+ } else {
+ // if user is an employee, return only the cinemas they are assigned to
+ return $this->belongsToMany('App\Models\Cinema', 'user_assignments', 'user_id', 'cinema_id')->get();
+ }
+ }
+
+ public function orders()
+ {
+ return $this->hasMany('App\Models\Order', 'user_id', 'user_id');
+ }
+
}
diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php
index b5b9869..bbd9d2e 100644
--- a/database/migrations/2014_10_12_000000_create_users_table.php
+++ b/database/migrations/2014_10_12_000000_create_users_table.php
@@ -19,7 +19,7 @@ public function up()
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
- $table->enum('role', ['default', 'employee', 'manage'])->default('default');
+ $table->enum('role', ['default', 'employee', 'manage', 'admin'])->default('default');
$table->rememberToken();
$table->timestamps();
});
diff --git a/database/migrations/2022_11_22_000009_create_user_permissions_table.php b/database/migrations/2022_11_22_000009_create_user_permissions_table.php
index 33250ca..5181ce3 100644
--- a/database/migrations/2022_11_22_000009_create_user_permissions_table.php
+++ b/database/migrations/2022_11_22_000009_create_user_permissions_table.php
@@ -19,8 +19,8 @@ public function up()
$table->foreignId('permission_id')->constrained('permissions', 'permission_id');
$table->foreignId('user_id')->constrained('users', 'user_id');
// end can be null to mark indefinitely
- $table->date('user_permission_start')->useCurrent();
- $table->date('user_permission_end')->nullable();
+// $table->date('user_permission_start')->useCurrent();
+// $table->date('user_permission_end')->nullable();
});
}
diff --git a/database/migrations/2022_11_22_000013_create_user_assignment.php b/database/migrations/2022_11_22_000013_create_user_assignment.php
new file mode 100644
index 0000000..43b1d7d
--- /dev/null
+++ b/database/migrations/2022_11_22_000013_create_user_assignment.php
@@ -0,0 +1,34 @@
+id('user_assignment_id');
+ $table->timestamps();
+ $table->foreignId('cinema_id')->constrained('cinemas', 'cinema_id');
+ $table->foreignId('user_id')->constrained('users', 'user_id');
+ $table->unique(['cinema_id', 'user_id']); // only one user per cinema
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_assignments');
+ }
+};
diff --git a/database/seeders/RoomSeeder.php b/database/seeders/RoomSeeder.php
index b3f329a..f9b1cc3 100644
--- a/database/seeders/RoomSeeder.php
+++ b/database/seeders/RoomSeeder.php
@@ -23,17 +23,14 @@ public function run()
],
[
'room_name' => 'Room 2',
- 'room_rows' => 10,
- 'room_columns' => 10,
+ 'room_rows' => 5,
+ 'room_columns' => 5,
],
- [
- 'room_name' => 'Room 3',
- 'room_rows' => 10,
- 'room_columns' => 10,
- ],
- ];
+ ];
foreach ($rooms as $room) {
+ $this->command->info("Creating room {$room['room_name']} for cinema {$cinema->cinema_name}");
+
$r = new \App\Models\Room();
$r->room_name = $room['room_name'];
$r->room_rows = $room['room_rows'];
@@ -43,6 +40,7 @@ public function run()
$r->save();
for ($row = 1; $row <= $r->room_rows; $row++) {
+ $this->command->info("Creating row {$row} for room {$r->room_name}");
for ($column = 1; $column <= $r->room_columns; $column++) {
$s = new \App\Models\Seat();
$s->seat_row = $row;
diff --git a/database/seeders/ShowingSeeder.php b/database/seeders/ShowingSeeder.php
index 255ce18..1f7e5c2 100644
--- a/database/seeders/ShowingSeeder.php
+++ b/database/seeders/ShowingSeeder.php
@@ -27,22 +27,60 @@ public function run()
'showing_end' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'),
'movie_id' => 1,
'room_id' => $room->room_id,
+ 'pricings' => [
+ [
+ 'price' => 10,
+ 'price_type' => 'adult',
+ ],
+ [
+ 'price' => 8,
+ 'price_type' => 'senior',
+ ],
+ [
+ 'price' => 2.5,
+ 'price_type' => 'child',
+ ],
+ ],
],
[
'showing_start' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'),
'showing_end' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'),
'movie_id' => 2,
'room_id' => $room->room_id,
- ],
+ 'pricings' => [
+ [
+ 'price' => 10,
+ 'price_type' => 'adult',
+ ],
+ [
+ 'price' => 8,
+ 'price_type' => 'senior',
+ ],
+ [
+ 'price' => 3.5,
+ 'price_type' => 'child',
+ ],
+ ],
+ ]
];
foreach ($showings as $showing) {
+ $this->command->info("Creating showing for movie {$showing['movie_id']} in room {$room->room_name}");
$s = new \App\Models\Showing();
$s->showing_start = $showing['showing_start'];
$s->movie_id = $showing['movie_id'];
$s->room_id = $showing['room_id'];
$s->user_id = 1;
$s->save();
+
+ foreach ($showing['pricings'] as $pricing) {
+ $p = new \App\Models\Price();
+ $p->price = $pricing['price'];
+ $p->price_type = $pricing['price_type'];
+ $p->showing_id = $s->showing_id;
+ $p->user_id = 1;
+ $p->save();
+ }
}
}
diff --git a/docker-compose.yml b/docker-compose.yml
index eb29913..006b328 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,6 +3,17 @@ version: '3.3'
# LARAVEL ENVIRONMENT COMPOSE
services:
+ mysql:
+ image: mysql:5.7
+ container_name: mysql
+ environment:
+ MYSQL_ROOT_PASSWORD: root
+ MYSQL_DATABASE: laravel
+ MYSQL_USER: laravel
+ MYSQL_PASSWORD: laravel
+ ports:
+ - "3306:3306"
+
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: phpmyadmin
@@ -10,9 +21,8 @@ services:
ports:
- "8080:80"
environment:
- PMA_HOST: neo.faulty.nl
- PMA_PORT: 33061
- PMA_USER: laravel
- PMA_PASSWORD: laravel
+ PMA_HOST: mysql
+ PMA_USER: root
+ PMA_PASSWORD: root
PMA_ARBITRARY: 1
PMA_ABSOLUTE_URI: http://localhost:8080/
diff --git a/public/css/generic.css b/public/css/generic.css
index 38e6229..2d382a9 100644
--- a/public/css/generic.css
+++ b/public/css/generic.css
@@ -130,6 +130,19 @@ a.button:hover {
color: var(--secondary-color-text);
}
+button.button:active,
+a.button:active {
+ background: var(--primary-color);
+ color: var(--primary-color-text);
+}
+
+button.button:disabled,
+a.button:disabled {
+ background: var(--inactive-item-bg);
+ color: var(--dim-text);
+ cursor: not-allowed;
+}
+
table {
border-collapse: collapse;
width: 100%;
@@ -175,3 +188,14 @@ .op0 {
.mono {
font-family: monospace;
}
+
+input.text {
+ /* generic input adjustments */
+ background: var(--second-bg);
+ color: var(--default-text);
+ border: none;
+ outline: none;
+ padding: 0.5rem;
+ border-radius: 0.5rem;
+ font-size: 1rem;
+}
diff --git a/public/css/main.css b/public/css/main.css
index 2c66f8b..82405c5 100644
--- a/public/css/main.css
+++ b/public/css/main.css
@@ -68,9 +68,16 @@ #movies a .details {
justify-content: flex-end;
align-items: center;
opacity: 0;
+ padding: 1rem;
+ max-width: calc(100% - 2rem);
transition: opacity 0.5s;
}
+#movies a .details span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
#movies a:hover .details {
opacity: 1;
}
diff --git a/public/css/manage.css b/public/css/manage.css
index bbac90a..18b85d1 100644
--- a/public/css/manage.css
+++ b/public/css/manage.css
@@ -46,22 +46,30 @@ #sidebar a {
color: var(--default-text);
display: block;
margin-top: 0.5rem;
+ transition: all 0.5s;
}
#sidebar a:hover {
- filter: brightness(1.5);
+ background: var(--second-bg);
+ color: var(--default-text);
+
}
#sidebar a.active {
- background: var(--highlight-bg);
+ border-left: 0.5rem solid var(--highlight-bg);
+ background-color: var(--default-bg);
color: var(--highlight-text);
}
#sidebar a.child {
padding-left: 2rem;
margin-top: 0;
-/* nice visual*/
- border-left: 1rem solid var(--second-bg);
+ /* nice visual*/
+ border-left: 0.5rem solid var(--second-bg);
+}
+
+#sidebar a.child.active {
+ border-left: 1rem solid var(--highlight-bg);
}
main {
@@ -135,10 +143,10 @@ .form .form-group label {
margin: 0.5rem 0;
}
-form .form-group button,
-form .form-group select,
-form .form-group textarea,
-form .form-group input {
+.form .form-group button,
+.form .form-group select,
+.form .form-group textarea,
+.form .form-group input {
padding: 0.5rem;
border-radius: 0.5rem;
background: var(--default-bg);
@@ -147,14 +155,14 @@ form .form-group input {
border: none;
}
-form .form-group button:hover,
-form .form-group select:hover,
-form .form-group textarea:hover,
-form .form-group input:hover,
-form .form-group button:focus,
-form .form-group select:focus,
-form .form-group textarea:focus,
-form .form-group input:focus {
+.form .form-group button:hover,
+.form .form-group select:hover,
+.form .form-group textarea:hover,
+.form .form-group input:hover,
+.form .form-group button:focus,
+.form .form-group select:focus,
+.form .form-group textarea:focus,
+.form .form-group input:focus {
filter: brightness(1.5);
}
@@ -189,3 +197,15 @@ #showings .showing .actions {
justify-content: center;
align-items: center;
}
+
+.warning {
+ background-color: rgba(255, 0, 0, 0.5);
+ padding: 1rem;
+ border-radius: 0.5rem;
+ margin: 1rem 0;
+ min-width: 25vw;
+}
+
+.warning h3 {
+ margin: 0;
+}
diff --git a/public/css/order.css b/public/css/order.css
new file mode 100644
index 0000000..fc4fab6
--- /dev/null
+++ b/public/css/order.css
@@ -0,0 +1,53 @@
+body {
+ /* center with spacing on the left and right*/
+ margin: 0 auto;
+ width: 100%;
+ max-width: 1000px;
+ padding: 0 20px 3rem;
+}
+
+#seat-chooser {
+ border: 1px solid black;
+ padding: 2rem;
+}
+
+#summary {
+ background-color: var(--second-bg);
+ border-radius: 5px;
+ padding: 1rem;
+ margin: 1rem 0;
+}
+
+#summary-dyn .seat {
+ background-color: var(--darkest-bg);
+ color: var(--primary-color-text);
+ border-radius: 5px;
+ padding: 0.5rem;
+ margin: 0.5rem;
+}
+
+#summary-dyn .seat select.ticket-select {
+ background-color: var(--primary-color);
+ color: var(--primary-color-text);
+ border: 0;
+ border-radius: 5px;
+ padding: 0.5rem;
+ margin: 0.5rem;
+}
+
+#summary-dyn .seat .seat-span {
+ background-color: var(--primary-color);
+ color: var(--primary-color-text);
+ border-radius: 5px;
+ padding: 0.5rem;
+ margin: 0.5rem;
+}
+
+#summary-dyn .seat .price {
+ background-color: var(--secondary-color);
+ color: var(--darkest-bg);
+ border-radius: 5px;
+ padding: 0.5rem;
+ margin: 0.5rem;
+ float: right;
+}
diff --git a/public/css/seat-chooser.css b/public/css/seat-chooser.css
index 860d90b..a29daa9 100644
--- a/public/css/seat-chooser.css
+++ b/public/css/seat-chooser.css
@@ -12,8 +12,6 @@ #seat-chooser {
flex-direction: column;
align-items: center;
justify-content: center;
- width: 100%;
- height: 100%;
--seat-width: 2.5rem;
--seat-height: var(--seat-width);
@@ -95,24 +93,7 @@ #seat-chooser .row .seat:hover {
filter: brightness(0.8);
}
-#load-screen {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: #000;
- z-index: 9999;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-div.spinner {
- width: 40px;
- height: 40px;
- border: 4px solid #f3f3f3;
- border-top: 4px solid #3498db;
- border-radius: 50%;
- animation: spin 2s linear infinite;
+#seat-chooser #seat-chooser-status {
+ text-align: center;
+ margin: 1rem;
}
diff --git a/public/js/freq.js b/public/js/freq.js
new file mode 100644
index 0000000..7619eb0
--- /dev/null
+++ b/public/js/freq.js
@@ -0,0 +1,24 @@
+class FreqManager {
+ #store;
+ static getInstance() {
+ if (this._instance === null) {
+ this._instance = new FreqManager();
+ }
+ return this._instance;
+ }
+
+ constructor() {
+ this.#store = {};
+ }
+
+ set(key, value) {
+ this.#store[key] = value;
+ }
+
+ get(key) {
+ return this.#store[key];
+ }
+
+}
+
+window.freq = FreqManager.getInstance();
diff --git a/public/js/order.js b/public/js/order.js
new file mode 100644
index 0000000..3bcb429
--- /dev/null
+++ b/public/js/order.js
@@ -0,0 +1,106 @@
+// DEPENDS ON seat-chooser.js
+(async () => {
+ let scDiv = document.querySelector('#seat-chooser');
+ let summaryDiv = document.querySelector('#summary-dyn');
+
+ let showingId = scDiv.getAttribute('data-showing-id');
+ let ticketTypes = await (await fetch(`/api/showing/${showingId}/prices`)).json();
+
+ let sc = new SeatChooser(
+ showingId,
+ scDiv,
+ (sc, s, add) => {
+ // seat click callback
+ // sc is the seat chooser
+ // s is the seat // but it's better to look at sc.selectedSeats
+ // add is a boolean indicating whether the seat is being added or removed
+
+ // update the summary
+ summaryDiv.innerHTML = '';
+ sc.selectedSeats.forEach((seat, index) => {
+ // set seat ticket if ticket is not set
+ if (!seat.ticket) seat.ticket = ticketTypes[0];
+ let seatDiv = document.createElement('div');
+ seatDiv.classList.add('seat');
+ let ticketSelect = document.createElement('select');
+ ticketTypes.forEach((ticketType, index) => {
+ let option = document.createElement('option');
+ option.value = index;
+ option.innerText = ticketType.price_type;
+ ticketSelect.appendChild(option);
+ });
+ ticketSelect.classList.add('ticket-select');
+ ticketSelect.value = ticketTypes.indexOf(seat.ticket);
+ ticketSelect.addEventListener('change', (e) => {
+ seat.ticket = ticketTypes[e.target.value];
+ updateTicket(sc, seat, seatDiv);
+ updateTotal(sc);
+ });
+ seatDiv.appendChild(ticketSelect);
+
+ seatDiv.appendChild(document.createTextNode(' '));
+
+ let seatSpan = document.createElement('span');
+ seatSpan.innerText = `Row ${seat.row}, Seat ${seat.col}`;
+ seatSpan.classList.add('seat-span');
+ seatDiv.appendChild(seatSpan);
+ seatDiv.appendChild(document.createTextNode(' '));
+
+ let priceSpan = document.createElement('span');
+ priceSpan.classList.add('price');
+ seatDiv.appendChild(priceSpan);
+ summaryDiv.appendChild(seatDiv);
+
+ updateTicket(sc, seat, seatDiv);
+ });
+
+ updateTotal(sc);
+
+ if (sc.selectedSeats.length == 0) document.querySelector('button#order-button').disabled = true;
+ else document.querySelector('button#order-button').disabled = false;
+
+ }
+ );
+
+ window.freq.set('seat-chooser', sc);
+})();
+
+let total = 0;
+
+function updateTicket(sc, seat, seatDiv) {
+ seatDiv.querySelector('span:last-child').innerText = `€${seat.ticket.price}`;
+}
+
+function updateTotal(sc) {
+ total = 0;
+ sc.selectedSeats.forEach(_seat => {
+ total += Number(_seat.ticket.price);
+ });
+ document.querySelector('#summary-total').innerText = `Total: €${total}`;
+}
+
+document.querySelector('button#order-button').addEventListener('click', (e) => {
+ let seats = [];
+ window.sc.selectedSeats.forEach(seat => {
+ seats.push({
+ seat: seat.id,
+ ticket: seat.ticket.id
+ });
+ });
+ fetch(`/api/order`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ showing: sc.showingId,
+ seats: seats
+ })
+ }).then(res => {
+ if (res.ok) {
+ window.location = '/order/complete';
+ } else {
+ alert('Error placing order');
+ }
+ });
+});
diff --git a/public/js/seat-chooser.js b/public/js/seat-chooser.js
index 4bae2c5..8a89b8c 100644
--- a/public/js/seat-chooser.js
+++ b/public/js/seat-chooser.js
@@ -26,19 +26,21 @@ class SeatChooser {
status.style.textAlign = 'center';
this.container.appendChild(status);
}
- // set the message
- this.container.querySelector('#seat-chooser-status-message').innerText = msg;
+ // set the message if it exists
+ if (this.container.querySelector('#seat-chooser-status-message'))
+ this.container.querySelector('#seat-chooser-status-message').innerText = msg;
}
- constructor(showingId, container, submitCallback) {
+ constructor(showingId, container, seatClickCallback = (sc, s) => {}, submitCallback = null) {
this.showingId = showingId; // id of the showing
this.container = container; // the container to render the seat chooser in
- this.submitCallback = submitCallback; // callback function to call when the user submits the form
this.setLoadingStatus("Initializing...");
- this.seats = []; // array of nulls and Seat objects
+ this.submitCallback = submitCallback; // callback function to call when the user submits the form
+ this.seatClickCallback = seatClickCallback; // callback function to call when the user clicks on a seat
+ this.seats = []; // array of nulls and Seat objects
this.selectedSeats = []; // array of Seat objects
this.init();
@@ -171,15 +173,17 @@ class SeatChooser {
});
buttonContainer.appendChild(clearButton);
- // submit button
- let submitButton = document.createElement('button');
- submitButton.classList.add('button');
- submitButton.style.margin = '1rem';
- submitButton.innerText = 'Submit';
- submitButton.addEventListener('click', () => {
- this.submitCallback(this);
- });
- buttonContainer.appendChild(submitButton);
+ if (this.submitCallback !== null) {
+ // submit button
+ let submitButton = document.createElement('button');
+ submitButton.classList.add('button');
+ submitButton.style.margin = '1rem';
+ submitButton.innerText = 'Submit';
+ submitButton.addEventListener('click', () => {
+ this.submitCallback(this);
+ });
+ buttonContainer.appendChild(submitButton);
+ }
this.container.appendChild(buttonContainer);
}
@@ -209,6 +213,7 @@ class SeatChooser {
this.selectedSeats.push(seat);
}
}
+ this.seatClickCallback(this, seat, this.selectedSeats.includes(seat));
}
clearSelection() {
@@ -219,21 +224,3 @@ class SeatChooser {
this.selectedSeats = [];
}
}
-
-let seatChooser = new SeatChooser(1, document.getElementById('seat-chooser'), (sc) => {
- // gray overlay and show selected seats
- let overlay = document.createElement('div');
- overlay.style.position = 'fixed';
- overlay.style.top = '0';
- overlay.style.left = '0';
- overlay.style.width = '100vw';
- overlay.style.height = '100vh';
- overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
- overlay.style.display = 'flex';
- overlay.style.justifyContent = 'center';
- overlay.innerText = 'You selected the following seats: ' + sc.selectedSeats.map(seat => `${seat.row}-${seat.col}`).join(', ');
- overlay.addEventListener('click', () => {
- overlay.remove();
- });
- sc.container.appendChild(overlay);
-});
diff --git a/resources/views/components/orders.blade.php b/resources/views/components/orders.blade.php
new file mode 100644
index 0000000..dea2c85
--- /dev/null
+++ b/resources/views/components/orders.blade.php
@@ -0,0 +1,22 @@
+
+
+
+
+ Order ID
+ Order Date
+ Order Status
+ Order Total
+
+
+
+ @foreach ($orders as $order)
+
+ {{ $order->order_id }}
+ {{ $order->order_date }}
+ {{ $order->order_status }}
+ {{ $order->order_total }}
+
+ @endforeach
+
+
+
diff --git a/resources/views/components/seat-chooser.blade.php b/resources/views/components/seat-chooser.blade.php
index da51a17..dfc3924 100644
--- a/resources/views/components/seat-chooser.blade.php
+++ b/resources/views/components/seat-chooser.blade.php
@@ -3,7 +3,15 @@
@push('head')
-
+
@endpush
-
+
+
+
+
+
Loading...
+ Fetching prices...
+
+
+
diff --git a/resources/views/dash.blade.php b/resources/views/dash.blade.php
index 88a2df6..d4528a1 100644
--- a/resources/views/dash.blade.php
+++ b/resources/views/dash.blade.php
@@ -14,6 +14,10 @@
@endif
+ Orders
+
+
+
Permissions
diff --git a/resources/views/layout.blade.php b/resources/views/layout.blade.php
index 5a65f0a..ec92381 100644
--- a/resources/views/layout.blade.php
+++ b/resources/views/layout.blade.php
@@ -4,6 +4,7 @@
{{ isset($title) ? $title : 'Page' }} - {{ config('app.name', 'CineFlex') }} Manage
+
@stack('head')
diff --git a/resources/views/main/cinemas/index.blade.php b/resources/views/main/cinemas/index.blade.php
index c0ff3a0..7d662ba 100644
--- a/resources/views/main/cinemas/index.blade.php
+++ b/resources/views/main/cinemas/index.blade.php
@@ -7,8 +7,8 @@