Skip to content

Commit

Permalink
feat: handle unauthenticated users w/ reactions, create helper dialog…
Browse files Browse the repository at this point in the history
… for logged in actions
  • Loading branch information
mdshack committed Jan 14, 2024
1 parent 9773921 commit d261efd
Show file tree
Hide file tree
Showing 16 changed files with 290 additions and 20 deletions.
6 changes: 5 additions & 1 deletion app/Http/Controllers/Auth/AuthenticatedSessionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ class AuthenticatedSessionController extends Controller
/**
* Display the login view.
*/
public function create(): Response
public function create(Request $request): Response
{
if($redirectTo = $request->get('redirect_to')) {
$request->session()->put('url.intended', $redirectTo);
}

return Inertia::render('Auth/Login', [
'canResetPassword' => Route::has('password.request'),
'status' => session('status'),
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/ShotController.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function show(Request $request, string $id)
'shot' => fn () => $shot->fresh(),
'childShots' => fn () => Shot::whereParentShotId($id)->get(),
'author' => fn() => $shot->user->only(["id", "name"]),
'reaction' => fn () => $request->user()->reactions()->whereShotId($id)->first(),
'reaction' => fn () => $request->user()?->reactions()->whereShotId($id)->first(),
'reactionCounts' => fn () => ShotReaction::whereShotId($id)
->select('reaction', DB::raw('count(*) as count'))
->groupBy('reaction')
Expand Down
52 changes: 52 additions & 0 deletions resources/js/Components/MustBeAuthenticatedDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script setup>
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/Components/ui/alert-dialog'
import { router } from '@inertiajs/vue3';
defineProps({
action: Function
})
const redirectToLogin = () => {
router.visit(route('login', {
redirect_to: window.location.pathname
}))
}
</script>

<template>
<AlertDialog v-if="!$page.props.auth.user?.id">
<AlertDialogTrigger>
<slot/>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Must be signed in</AlertDialogTitle>
<AlertDialogDescription>
You must be authenticated to perform this action. Would you like to sign in?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
Cancel
</AlertDialogCancel>
<AlertDialogAction @click="redirectToLogin">
Sign In
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>

<span v-else @click.prevent="action">
<slot />
</span>
</template>
17 changes: 17 additions & 0 deletions resources/js/Components/ui/alert-dialog/AlertDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup>
import { AlertDialogRoot, useForwardPropsEmits } from "radix-vue";
const props = defineProps({
open: { type: Boolean, required: false },
defaultOpen: { type: Boolean, required: false },
});
const emits = defineEmits(["update:open"]);
const forwarded = useForwardPropsEmits(props, emits);
</script>

<template>
<AlertDialogRoot v-bind="forwarded">
<slot />
</AlertDialogRoot>
</template>
19 changes: 19 additions & 0 deletions resources/js/Components/ui/alert-dialog/AlertDialogAction.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup>
import { AlertDialogAction } from "radix-vue";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/Components/ui/button";
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
});
</script>

<template>
<AlertDialogAction
v-bind="props"
:class="cn(buttonVariants(), $attrs.class ?? '')"
>
<slot />
</AlertDialogAction>
</template>
25 changes: 25 additions & 0 deletions resources/js/Components/ui/alert-dialog/AlertDialogCancel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script setup>
import { AlertDialogCancel } from "radix-vue";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/Components/ui/button";
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
});
</script>

<template>
<AlertDialogCancel
v-bind="props"
:class="
cn(
buttonVariants({ variant: 'outline' }),
'mt-2 sm:mt-0',
$attrs.class ?? ''
)
"
>
<slot />
</AlertDialogCancel>
</template>
48 changes: 48 additions & 0 deletions resources/js/Components/ui/alert-dialog/AlertDialogContent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script setup>
import {
AlertDialogContent,
AlertDialogOverlay,
AlertDialogPortal,
useEmitAsProps,
} from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps({
forceMount: { type: Boolean, required: false },
trapFocus: { type: Boolean, required: false },
disableOutsidePointerEvents: { type: Boolean, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: String, required: false },
});
const emits = defineEmits([
"escapeKeyDown",
"pointerDownOutside",
"focusOutside",
"interactOutside",
"dismiss",
"openAutoFocus",
"closeAutoFocus",
]);
const emitsAsProps = useEmitAsProps(emits);
</script>

<template>
<AlertDialogPortal>
<AlertDialogOverlay
class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
/>
<AlertDialogContent
v-bind="{ ...props, ...emitsAsProps }"
:class="
cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
props.class
)
"
>
<slot />
</AlertDialogContent>
</AlertDialogPortal>
</template>
19 changes: 19 additions & 0 deletions resources/js/Components/ui/alert-dialog/AlertDialogDescription.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup>
import { AlertDialogDescription } from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: String, required: false },
});
</script>

<template>
<AlertDialogDescription
:class="cn('text-muted-foreground text-sm', props.class)"
:as-child="props.asChild"
>
<slot />
</AlertDialogDescription>
</template>
23 changes: 23 additions & 0 deletions resources/js/Components/ui/alert-dialog/AlertDialogFooter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script setup>
import { cn } from "@/lib/utils";
const props = defineProps({
class: {
type: String,
default: "",
},
});
</script>

<template>
<div
:class="
cn(
'flex flex-col space-y-2 sm:space-y-0 mt-3.5 sm:flex-row sm:justify-end sm:space-x-2',
props.class
)
"
>
<slot />
</div>
</template>
18 changes: 18 additions & 0 deletions resources/js/Components/ui/alert-dialog/AlertDialogHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script setup>
import { cn } from "@/lib/utils";
const props = defineProps({
class: {
type: String,
default: "",
},
});
</script>

<template>
<div
:class="cn('flex flex-col space-y-2 text-center sm:text-left', props.class)"
>
<slot />
</div>
</template>
19 changes: 19 additions & 0 deletions resources/js/Components/ui/alert-dialog/AlertDialogTitle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup>
import { AlertDialogTitle } from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: String, required: false },
});
</script>

<template>
<AlertDialogTitle
:as-child="props.asChild"
:class="cn('text-lg text-foreground font-semibold', props.class)"
>
<slot />
</AlertDialogTitle>
</template>
14 changes: 14 additions & 0 deletions resources/js/Components/ui/alert-dialog/AlertDialogTrigger.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup>
import { AlertDialogTrigger } from "radix-vue";
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
});
</script>

<template>
<AlertDialogTrigger v-bind="props">
<slot />
</AlertDialogTrigger>
</template>
9 changes: 9 additions & 0 deletions resources/js/Components/ui/alert-dialog/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export { default as AlertDialog } from "./AlertDialog.vue";
export { default as AlertDialogTrigger } from "./AlertDialogTrigger.vue";
export { default as AlertDialogContent } from "./AlertDialogContent.vue";
export { default as AlertDialogHeader } from "./AlertDialogHeader.vue";
export { default as AlertDialogTitle } from "./AlertDialogTitle.vue";
export { default as AlertDialogDescription } from "./AlertDialogDescription.vue";
export { default as AlertDialogFooter } from "./AlertDialogFooter.vue";
export { default as AlertDialogAction } from "./AlertDialogAction.vue";
export { default as AlertDialogCancel } from "./AlertDialogCancel.vue";
2 changes: 0 additions & 2 deletions resources/js/Layouts/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ const initials = computed(() => {
</script>
<template>
<Head title="Home" />
<div class="max-w-2xl mx-auto pb-14">
<nav class="flex justify-between items-center py-4 border-b mb-8">
<Link :href="route('home')">
Expand Down
4 changes: 3 additions & 1 deletion resources/js/Pages/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Layout from '@/Layouts/Layout.vue'
import { Input } from '@/Components/ui/input'
import { Progress } from '@/Components/ui/progress'
import { useForm } from '@inertiajs/vue3';
import { useForm, Head } from '@inertiajs/vue3';
import { ref } from 'vue';
const form = useForm({
Expand Down Expand Up @@ -54,6 +54,8 @@ const pasteFile = (event) => {
</script>

<template>
<Head title="Home" />

<Layout>
<template v-if="$page.props.auth.user">
<form v-if="!form.progress" @submit.prevent="submit" class="flex flex-col items-center space-y-8">
Expand Down
33 changes: 18 additions & 15 deletions resources/js/Pages/Shots/Partials/ShotDetails.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup>
import MustBeAuthenticatedDialog from '@/Components/MustBeAuthenticatedDialog.vue';
import UserAvatar from '@/Components/ui/UserAvatar.vue'
import { HandThumbUpIcon, HandThumbDownIcon, TrashIcon, PencilSquareIcon } from "@heroicons/vue/24/outline"
Expand Down Expand Up @@ -31,7 +32,7 @@ const reactToShot = (shotId, reaction) => {
<h1 class="text-xl font-semibold">Name of my screen shot</h1>

<!-- Owner Options -->
<div v-if="author.id === $page.props.auth.user.id" class="flex items-center space-x-1">
<div v-if="author.id === $page.props.auth.user?.id" class="flex items-center space-x-1">
<button class="text-gray-500 hover:text-primary transition">
<PencilSquareIcon class="h-6 w-6" />
</button>
Expand All @@ -54,21 +55,23 @@ const reactToShot = (shotId, reaction) => {

<!-- Reactions -->
<div v-if="$page.props.features.reactions" class="flex space-x-4 text-xs">
<button
@click.prevent="() => reactToShot(shot.id, 'upvote')"
class="text-gray-500 hover:text-green-500 transition"
:class="{'text-green-500': reaction?.reaction === 'upvote'}">
<HandThumbUpIcon class="h-6 w-6" />
{{ reactionCounts?.upvote ?? '0' }}
</button>
<MustBeAuthenticatedDialog :action="() => reactToShot(shot.id, 'upvote')">
<button
class="text-gray-500 hover:text-green-500 transition"
:class="{'text-green-500': reaction?.reaction === 'upvote'}">
<HandThumbUpIcon class="h-6 w-6" />
{{ reactionCounts?.upvote ?? '0' }}
</button>
</MustBeAuthenticatedDialog>

<button
@click.prevent="() => reactToShot(shot.id, 'downvote')"
class="text-gray-500 hover:text-red-500 transition"
:class="{'text-red-500': reaction?.reaction === 'downvote'}">
<HandThumbDownIcon class="h-6 w-6" />
{{ reactionCounts?.downvote ?? '0' }}
</button>
<MustBeAuthenticatedDialog :action="() => reactToShot(shot.id, 'downvote')">
<button
class="text-gray-500 hover:text-red-500 transition"
:class="{'text-red-500': reaction?.reaction === 'downvote'}">
<HandThumbDownIcon class="h-6 w-6" />
{{ reactionCounts?.downvote ?? '0' }}
</button>
</MustBeAuthenticatedDialog>
</div>
</div>
</div>
Expand Down

0 comments on commit d261efd

Please sign in to comment.