Skip to content

Commit

Permalink
#69 - protected page (#79)
Browse files Browse the repository at this point in the history
* #69 - redirect back when 405 occurs

* #69 - add custom error page

* #7 - handle different types of errors

* #69 - handle different types of errors

* #69 - change text size

* #69 - fix

* #69 - csf

* #69 - fix duplicate call of StartSession::class in Kernel.php

* #69 - don't show auth dialog

* #69 - fix test

* #69 - add custom messages to popular statuses, handle all errors

* #69 - add translations

* #69 - get rid of assertStatus() in tests

* #69 - delete name of queue, quick map fix and favorite button improvement
  • Loading branch information
vojcc authored Aug 8, 2023
1 parent 6ab0fe0 commit 191ff66
Show file tree
Hide file tree
Showing 21 changed files with 227 additions and 80 deletions.
84 changes: 84 additions & 0 deletions app/Exceptions/ExceptionHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler;
use Illuminate\Support\Facades\Crypt;
use Inertia\Inertia;
use Symfony\Component\HttpFoundation\Response;
use Throwable;

class ExceptionHandler extends Handler
{
protected $dontFlash = [
"current_password",
"password",
"password_confirmation",
];

public function render($request, Throwable $e)
{
$response = parent::render($request, $e);
$statusCode = $response->status();

app()->setLocale("en");

$encryptedCookie = $request->cookie("locale");

if ($encryptedCookie) {
if (in_array($encryptedCookie, ["pl", "en"], true)) {
app()->setLocale($encryptedCookie);
} else {
$decryptedCookie = Crypt::decryptString($encryptedCookie);

$languageCode = substr($decryptedCookie, strpos($decryptedCookie, "|") + 1);

if (in_array($languageCode, ["pl", "en"], true)) {
app()->setLocale($languageCode);
}
}
}

switch ($statusCode) {
case Response::HTTP_INTERNAL_SERVER_ERROR:
case Response::HTTP_SERVICE_UNAVAILABLE:
case Response::HTTP_TOO_MANY_REQUESTS:
$statusTitle = __($response->statusText());
$statusDescription = $this->getDescriptionByStatusCode($statusCode);

break;
case 419:
$statusTitle = __("Session Expired");
$statusDescription = __("Description: Session expired");

break;
default:
$statusTitle = __("Not Found");
$statusDescription = __("Description: Not found");
$statusCode = Response::HTTP_NOT_FOUND;

break;
}

return Inertia::render("Error", [
"statusTitle" => $statusTitle,
"statusDescription" => $statusDescription,
"statusCode" => $statusCode,
])
->toResponse($request)
->setStatusCode($statusCode);
}

protected function getDescriptionByStatusCode(int $statusCode): string
{
$descriptions = [
Response::HTTP_INTERNAL_SERVER_ERROR => __("Description: Server error"),
Response::HTTP_SERVICE_UNAVAILABLE => __("Description: Server unavailable"),
Response::HTTP_TOO_MANY_REQUESTS => __("Description: Too many requests"),
];

return $descriptions[$statusCode] ?? __("Description: Other");
}
}
11 changes: 0 additions & 11 deletions app/Exceptions/Handler.php

This file was deleted.

5 changes: 1 addition & 4 deletions app/Http/Middleware/Authenticate.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@

class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*/
protected function redirectTo(Request $request): ?string
{
return $request->expectsJson() ? null : route("login");
return $request->expectsJson() ? null : route("home");
}
}
2 changes: 1 addition & 1 deletion app/Services/DataImporterService.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ public function run(string $whoRunsIt = "admin"): void
ImportInfo::query()->where("id", $this->importInfoId)->update([
"status" => "finished",
]);
})->onQueue("importers")->dispatch();
})->dispatch();
}
}
2 changes: 1 addition & 1 deletion bootstrap/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
declare(strict_types=1);

use App\Console\Kernel as ConsoleKernel;
use App\Exceptions\Handler;
use App\Exceptions\ExceptionHandler as Handler;
use App\Http\Kernel as HttpKernel;
use Illuminate\Contracts\Console\Kernel as ConsoleKernelContract;
use Illuminate\Contracts\Debug\ExceptionHandler;
Expand Down
9 changes: 8 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
{
"advert1": "There are many e-scooter providers out there, so sometimes you may feel a bit confused about which apps you should use on your trips.",
"advert2": "This app will help you plan your vacations or business trips."
"advert2": "This app will help you plan your vacations or business trips.",

"Description: Not found": "Sorry, the page you were looking for could not be found.",
"Description: Server error": "Oops! Something went wrong on our end. Please try again later.",
"Description: Server unavailable": "Oops! The service is currently unavailable. Please try again later.",
"Description: Too many requests": "Oops! Too many requests. Please try again later.",
"Description: Session expired": "Your session has expired. Please log in and try again.",
"Description: Other": "Oops. Something went wrong. Try again later."
}
17 changes: 15 additions & 2 deletions lang/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@
"Status": "Status",
"Didn`t find anything. Just empty space.": "Nie znaleziono niczego",
"Open main menu": "Otwórz menu główne",
"Please verify your credentials and try again" : "Sprawdź swoje dane logowania i spróbuj ponownie",
"Invalid password or username" : "Nieprawidłowe hasło lub nazwa użytkownika"
"Please verify your credentials and try again": "Sprawdź swoje dane logowania i spróbuj ponownie",
"Invalid password or username": "Nieprawidłowe hasło lub nazwa użytkownika",

"Not Found": "Nie znaleziono strony",
"Internal Server Error": "Błąd serwera",
"Service Unavailable": "Serwis niedostępny",
"Too Many Requests": "Przekroczono limit zapytań",
"Session Expired": "Strona wygasła",

"Description: Not found": "Przykro nam, ale strona, której szukasz, nie istnieje.",
"Description: Server error": "Oj! Serwer napotkał na niespodziewane trudności. Spróbuj ponownie później.",
"Description: Server unavailable": "Oj! Serwis jest tymczasowo niedostępny. Spróbuj ponownie później.",
"Description: Too many requests": "Wysłano ostatnio zbyt wiele zapytań. Poczekaj i spróbuj ponownie później.",
"Description: Session expired": "Ta strona wygasła. Zaloguj się ponownie.",
"Description: Other": "Oj! Coś poszło nie tak. Spróbuj ponownie później."
}
2 changes: 1 addition & 1 deletion lang/pl/passwords.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"sent" => "Wysłaliśmy Ci link do resetowania hasła!",
"throttled" => "Proszę poczekać przed ponowną próbą.",
"token" => "Token resetowania hasła jest nieprawidłowy.",
"user" => "Nie możemy znaleźć użytkownika z takim adresem email.",
"user" => "Nie możemy znaleźć użytkownika z takim adresem e-mail.",
];
36 changes: 36 additions & 0 deletions resources/js/Pages/Error.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script setup>
import { ArrowLeftIcon } from '@heroicons/vue/24/outline'
defineProps({
statusTitle: String,
statusDescription: String,
statusCode: Number,
})
</script>

<template>
<div class="grid min-h-screen grid-cols-1 grid-rows-[1fr,auto,1fr] bg-white lg:grid-cols-[max(50%,36rem),1fr]">
<main class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8">
<div class="max-w-lg">
<p class="px-0.5 text-2xl font-semibold leading-8 text-blumilk-500">
{{ statusCode }}
</p>
<h1 class="mt-4 text-4xl font-bold tracking-tight text-gray-800 sm:text-7xl">
{{ statusTitle }}
</h1>
<p class="mt-6 px-0.5 text-base leading-7 text-gray-600">
{{ statusDescription }}
</p>
<div class="mt-10">
<InertiaLink href="/" class="flex w-fit items-center py-4 pr-4 text-sm font-semibold leading-7 text-blumilk-500">
<ArrowLeftIcon class="mr-2 h-10 w-10" />
</InertiaLink>
</div>
</div>
</main>

<div class="hidden lg:relative lg:col-start-2 lg:row-start-1 lg:row-end-4 lg:block">
<img src="../assets/error-page-photo.jpg" alt="" class="absolute inset-0 h-full w-full object-cover">
</div>
</div>
</template>
2 changes: 1 addition & 1 deletion resources/js/Pages/Importers/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function runImporters() {
<div class="flex w-full md:justify-end">
<div class="mt-16 flex h-full w-full flex-col justify-between md:mt-0 md:w-2/3 lg:w-3/4 xl:w-5/6">
<div class="m-4 flex flex-col lg:mx-8">
<button class="my-5 w-fit rounded bg-blumilk-500 px-5 py-8 text-sm font-medium text-white shadow-md hover:bg-blumilk-400 md:py-2" @click="runImporters">
<button class="my-5 w-fit rounded bg-blumilk-500 px-5 py-3 text-sm font-medium text-white shadow-md hover:bg-blumilk-400 md:py-2" @click="runImporters">
{{ __('Run importers') }}
</button>

Expand Down
22 changes: 14 additions & 8 deletions resources/js/Pages/Landing/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ const breakpoints = useBreakpoints(breakpointsTailwind)
const showInfo = ref(true)
const isMobile = ref(breakpoints.smaller('lg'))
const isDesktop = ref(breakpoints.greaterOrEqual('lg'))
const showMap = ref(false)
const shouldShowMap = ref(false)
function switchPanel() {
showInfo.value = !showInfo.value
}
function switchMap() {
showMap.value = !showMap.value
shouldShowMap.value = !shouldShowMap.value
}
const nav = ref(null)
Expand Down Expand Up @@ -52,14 +52,22 @@ onMounted(async () => {
await fetchProviders()
})
const shouldShowButton = computed(() => {
return (!showInfo.value && isMobile.value) || (isAuth.value && isMobile.value)
})
const buttonIcon = computed(() => {
return shouldShowMap.value ? XMarkIcon : MapIcon
})
</script>

<template>
<div class="flex h-screen flex-col">
<Nav ref="nav" class="z-30" />

<div class="relative mt-16 flex grow flex-col lg:flex-row">
<div v-if="!showMap || (showMap && isDesktop)" class="grow lg:w-1/2">
<div v-if="isDesktop || !shouldShowMap" class="grow lg:w-1/2">
<Info v-if="showInfo && !isAuth" @create-account="nav.toggleCreateAccountOption()" @try-it-out="switchPanel" />

<div v-else class="w-full">
Expand All @@ -68,7 +76,7 @@ onMounted(async () => {
</div>
</div>

<div v-show="isDesktop || (showMap && isMobile)" class="relative h-full lg:w-1/2">
<div v-if="isDesktop || shouldShowMap" class="relative h-full lg:w-1/2">
<Map v-if="data.providers.length" :key="`${filterStore.selectedCountryId}-${filterStore.selectedProviderName}`" :cities="data.cities" class="z-10" />
<div v-else class="flex h-full flex-col items-center justify-center bg-blumilk-25" aria-label="Loading..." role="status">
<svg class="h-24 w-24 animate-spin" viewBox="3 3 18 18">
Expand All @@ -87,11 +95,9 @@ onMounted(async () => {
</div>
</div>


<div v-if="(!showInfo && isMobile) || (isAuth && isMobile)" class="flex justify-center">
<div v-if="shouldShowButton" class="flex justify-center">
<button class="hover:blumilk-600 fixed bottom-5 z-20 flex items-center justify-center rounded-full bg-blumilk-500 px-2 py-1.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" @click="switchMap">
<XMarkIcon v-if="showMap" class="h-6 w-6" />
<MapIcon v-else class="h-6 w-6" />
<component :is="buttonIcon" class="h-6 w-6" />
</button>
</div>
</div>
Expand Down
28 changes: 17 additions & 11 deletions resources/js/Pages/Landing/SearchPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const props = defineProps({
countries: Array,
})
const authenticated = computed(() => usePage().props.auth.isAuth)
const isAuth = computed(() => usePage().props.auth.isAuth)
const filteredCities = computed(() => {
const selectedCountryId = filterStore.selectedCountryId
Expand Down Expand Up @@ -165,16 +165,22 @@ function showCity(city) {
class="group flex cursor-pointer flex-col items-start justify-between gap-x-6 pb-1 pt-4 sm:flex-row sm:pb-4"
@click="showCity(city)"
>
<div class="flex min-w-max items-center">
<i :class="city.country.iso" class="flat flag huge shrink-0" @click="filterCountry(city.country.id)" />
<div class="ml-4 flex flex-col justify-start">
<p class="mr-2 break-all font-bold text-gray-900 group-hover:text-gray-500">
{{ city.name }}
</p>
<p class="break-all text-xs font-semibold text-blumilk-500">
{{ city.country.name }}
</p>
<FavoriteButton v-if="authenticated" :cityid="city.id" />
<div class="flex w-full justify-between sm:flex-col sm:justify-start">
<div class="flex w-max items-center">
<i :class="city.country.iso" class="flat flag huge shrink-0" @click="filterCountry(city.country.id)" />

<div class="ml-3 flex flex-col justify-start">
<p class="mr-2 break-all font-bold text-gray-900 group-hover:text-gray-500">
{{ city.name }}
</p>
<p class="break-all text-xs font-semibold text-blumilk-500">
{{ city.country.name }}
</p>
</div>
</div>

<div class="mt-0 flex w-fit items-center justify-end sm:ml-[64px] sm:mt-1 sm:justify-start">
<FavoriteButton v-if="isAuth" :cityid="city.id" />
<InfoPopup v-else />
</div>
</div>
Expand Down
8 changes: 6 additions & 2 deletions resources/js/Shared/Components/FavoriteButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const toggleFavorite = async () => {
await router.post('/favorites', {
city_id: props.cityid,
}, {
preserveScroll: true,
})
result.value = !result.value
} catch (error) {
Expand Down Expand Up @@ -63,9 +65,11 @@ onMounted(() => {
<div ref="intersectionTarget">
<button @click="toggleFavorite">
<component :is="result ? SolidHeartIcon : OutlineHeartIcon" v-if="result !== null"
class="h-6 w-6 text-red-500"
class="h-6 w-6 text-rose-500"
/>
<span v-else>{{ __('Loading') }}...</span>
<span v-else class="animate-pulse text-rose-200">
<SolidHeartIcon class="h-6 w-6" />
</span>
</button>
</div>
</template>
24 changes: 15 additions & 9 deletions resources/js/Shared/Components/LanguageSwitch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@ const currentLocale = computed(() => usePage().props.locale)
</script>

<template>
<div>
<div class="flex space-x-2 pt-1.5">
<InertiaLink v-for="locale in locales" :key="locale.lang" :href="`/language/${locale.lang}`" method="post" as="button"
:class="[currentLocale === locale.lang ? 'opacity-100' : 'opacity-50', 'large flat flags']"
:disabled="currentLocale === locale.lang"
>
<i :class="`${locale.iso} flat flag`" />
</InertiaLink>
</div>
<div class="flex space-x-2 pt-1.5">
<InertiaLink
v-for="locale in locales"
:key="locale.lang"
:href="`/language/${locale.lang}`"
method="post"
as="button"
:class="[currentLocale === locale.lang ? 'opacity-100' : 'opacity-30']"
:disabled="currentLocale === locale.lang"
>
<i
:class="`${locale.iso} flat flag`"
class="!h-[24px] !w-[36px] rounded md:!h-[18px] md:!w-[27px]"
/>
</InertiaLink>
</div>
</template>
6 changes: 3 additions & 3 deletions resources/js/Shared/Layout/AdminNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function toggleMobileMenu() {
</div>
</div>
</div>
<div class="mx-auto flex items-center justify-center px-6 py-3">
<div class="mx-auto flex items-center pt-6">
<LanguageSwitch class="text-2xl" />
</div>
</DialogPanel>
Expand All @@ -83,9 +83,9 @@ function toggleMobileMenu() {
<span class="ml-3 hidden md:flex"> {{ __(item.name) }} </span>
</div>
</InertiaLink>
<div class="mx-auto flex items-center justify-center px-6 py-3">
<li class="flex px-5 md:pt-6">
<LanguageSwitch />
</div>
</li>
</ul>
</div>
</div>
Expand Down
Loading

0 comments on commit 191ff66

Please sign in to comment.