Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a43a468

Browse files
Merge pull request #191 from laravel/add_error_state_in_hook
Add an error state in `useTwoFactorAuth.ts`
2 parents ccdb9dd + 519ecfd commit a43a468

File tree

8 files changed

+209
-75
lines changed

8 files changed

+209
-75
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<script setup lang="ts">
2+
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
3+
import { AlertCircle } from 'lucide-vue-next';
4+
import { computed } from 'vue';
5+
6+
interface Props {
7+
errors: string[];
8+
title?: string;
9+
}
10+
11+
const props = withDefaults(defineProps<Props>(), {
12+
title: 'Something went wrong.',
13+
});
14+
15+
const uniqueErrors = computed(() => Array.from(new Set(props.errors)));
16+
</script>
17+
18+
<template>
19+
<Alert variant="destructive">
20+
<AlertCircle class="size-4" />
21+
<AlertTitle>{{ title }}</AlertTitle>
22+
<AlertDescription>
23+
<ul class="list-inside list-disc text-sm">
24+
<li v-for="(error, index) in uniqueErrors" :key="index">
25+
{{ error }}
26+
</li>
27+
</ul>
28+
</AlertDescription>
29+
</Alert>
30+
</template>

resources/js/components/TwoFactorRecoveryCodes.vue

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import AlertError from '@/components/AlertError.vue';
23
import { Button } from '@/components/ui/button';
34
import {
45
Card,
@@ -13,7 +14,7 @@ import { Form } from '@inertiajs/vue3';
1314
import { Eye, EyeOff, LockKeyhole, RefreshCw } from 'lucide-vue-next';
1415
import { nextTick, onMounted, ref } from 'vue';
1516
16-
const { recoveryCodesList, fetchRecoveryCodes } = useTwoFactorAuth();
17+
const { recoveryCodesList, fetchRecoveryCodes, errors } = useTwoFactorAuth();
1718
const isRecoveryCodesVisible = ref<boolean>(false);
1819
const recoveryCodeSectionRef = ref<HTMLDivElement | null>(null);
1920
@@ -38,7 +39,7 @@ onMounted(async () => {
3839
</script>
3940

4041
<template>
41-
<Card>
42+
<Card class="w-full">
4243
<CardHeader>
4344
<CardTitle class="flex gap-3">
4445
<LockKeyhole class="size-4" />2FA Recovery Codes
@@ -62,7 +63,7 @@ onMounted(async () => {
6263
</Button>
6364

6465
<Form
65-
v-if="isRecoveryCodesVisible"
66+
v-if="isRecoveryCodesVisible && recoveryCodesList.length"
6667
v-bind="regenerateRecoveryCodes.form()"
6768
method="post"
6869
:options="{ preserveScroll: true }"
@@ -86,7 +87,10 @@ onMounted(async () => {
8687
: 'h-0 opacity-0',
8788
]"
8889
>
89-
<div class="mt-3 space-y-3">
90+
<div v-if="errors?.length" class="mt-6">
91+
<AlertError :errors="errors" />
92+
</div>
93+
<div v-else class="mt-3 space-y-3">
9094
<div
9195
ref="recoveryCodeSectionRef"
9296
class="grid gap-1 rounded-lg bg-muted p-4 font-mono text-sm"

resources/js/components/TwoFactorSetupModal.vue

Lines changed: 59 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import AlertError from '@/components/AlertError.vue';
23
import InputError from '@/components/InputError.vue';
34
import { Button } from '@/components/ui/button';
45
import {
@@ -29,7 +30,7 @@ const props = defineProps<Props>();
2930
const isOpen = defineModel<boolean>('isOpen');
3031
3132
const { copy, copied } = useClipboard();
32-
const { qrCodeSvg, manualSetupKey, clearSetupData, fetchSetupData } =
33+
const { qrCodeSvg, manualSetupKey, clearSetupData, fetchSetupData, errors } =
3334
useTwoFactorAuth();
3435
3536
const showVerificationStep = ref(false);
@@ -150,79 +151,82 @@ watch(
150151
class="relative flex w-auto flex-col items-center justify-center space-y-5"
151152
>
152153
<template v-if="!showVerificationStep">
153-
<div
154-
class="relative mx-auto flex max-w-md items-center overflow-hidden"
155-
>
154+
<AlertError v-if="errors?.length" :errors="errors" />
155+
<template v-else>
156156
<div
157-
class="relative mx-auto aspect-square w-64 overflow-hidden rounded-lg border border-border"
157+
class="relative mx-auto flex max-w-md items-center overflow-hidden"
158158
>
159159
<div
160-
v-if="!qrCodeSvg"
161-
class="absolute inset-0 z-10 flex aspect-square h-auto w-full animate-pulse items-center justify-center bg-background"
162-
>
163-
<Loader2 class="size-6 animate-spin" />
164-
</div>
165-
<div
166-
v-else
167-
class="relative z-10 overflow-hidden border p-5"
160+
class="relative mx-auto aspect-square w-64 overflow-hidden rounded-lg border border-border"
168161
>
169162
<div
170-
v-html="qrCodeSvg"
171-
class="flex aspect-square size-full items-center justify-center"
172-
/>
163+
v-if="!qrCodeSvg"
164+
class="absolute inset-0 z-10 flex aspect-square h-auto w-full animate-pulse items-center justify-center bg-background"
165+
>
166+
<Loader2 class="size-6 animate-spin" />
167+
</div>
168+
<div
169+
v-else
170+
class="relative z-10 overflow-hidden border p-5"
171+
>
172+
<div
173+
v-html="qrCodeSvg"
174+
class="flex aspect-square size-full items-center justify-center"
175+
/>
176+
</div>
173177
</div>
174178
</div>
175-
</div>
176179

177-
<div class="flex w-full items-center space-x-5">
178-
<Button class="w-full" @click="handleModalNextStep">
179-
{{ modalConfig.buttonText }}
180-
</Button>
181-
</div>
180+
<div class="flex w-full items-center space-x-5">
181+
<Button class="w-full" @click="handleModalNextStep">
182+
{{ modalConfig.buttonText }}
183+
</Button>
184+
</div>
182185

183-
<div
184-
class="relative flex w-full items-center justify-center"
185-
>
186186
<div
187-
class="absolute inset-0 top-1/2 h-px w-full bg-border"
188-
/>
189-
<span class="relative bg-card px-2 py-1"
190-
>or, enter the code manually</span
187+
class="relative flex w-full items-center justify-center"
191188
>
192-
</div>
189+
<div
190+
class="absolute inset-0 top-1/2 h-px w-full bg-border"
191+
/>
192+
<span class="relative bg-card px-2 py-1"
193+
>or, enter the code manually</span
194+
>
195+
</div>
193196

194-
<div
195-
class="flex w-full items-center justify-center space-x-2"
196-
>
197197
<div
198-
class="flex w-full items-stretch overflow-hidden rounded-xl border border-border"
198+
class="flex w-full items-center justify-center space-x-2"
199199
>
200200
<div
201-
v-if="!manualSetupKey"
202-
class="flex h-full w-full items-center justify-center bg-muted p-3"
201+
class="flex w-full items-stretch overflow-hidden rounded-xl border border-border"
203202
>
204-
<Loader2 class="size-4 animate-spin" />
205-
</div>
206-
<template v-else>
207-
<input
208-
type="text"
209-
readonly
210-
:value="manualSetupKey"
211-
class="h-full w-full bg-background p-3 text-foreground"
212-
/>
213-
<button
214-
@click="copy(manualSetupKey || '')"
215-
class="relative block h-auto border-l border-border px-3 hover:bg-muted"
203+
<div
204+
v-if="!manualSetupKey"
205+
class="flex h-full w-full items-center justify-center bg-muted p-3"
216206
>
217-
<Check
218-
v-if="copied"
219-
class="w-4 text-green-500"
207+
<Loader2 class="size-4 animate-spin" />
208+
</div>
209+
<template v-else>
210+
<input
211+
type="text"
212+
readonly
213+
:value="manualSetupKey"
214+
class="h-full w-full bg-background p-3 text-foreground"
220215
/>
221-
<Copy v-else class="w-4" />
222-
</button>
223-
</template>
216+
<button
217+
@click="copy(manualSetupKey || '')"
218+
class="relative block h-auto border-l border-border px-3 hover:bg-muted"
219+
>
220+
<Check
221+
v-if="copied"
222+
class="w-4 text-green-500"
223+
/>
224+
<Copy v-else class="w-4" />
225+
</button>
226+
</template>
227+
</div>
224228
</div>
225-
</div>
229+
</template>
226230
</template>
227231

228232
<template v-else>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import type { AlertVariants } from "."
4+
import { cn } from "@/lib/utils"
5+
import { alertVariants } from "."
6+
7+
const props = defineProps<{
8+
class?: HTMLAttributes["class"]
9+
variant?: AlertVariants["variant"]
10+
}>()
11+
</script>
12+
13+
<template>
14+
<div
15+
data-slot="alert"
16+
:class="cn(alertVariants({ variant }), props.class)"
17+
role="alert"
18+
>
19+
<slot />
20+
</div>
21+
</template>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes["class"]
7+
}>()
8+
</script>
9+
10+
<template>
11+
<div
12+
data-slot="alert-description"
13+
:class="cn('text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed', props.class)"
14+
>
15+
<slot />
16+
</div>
17+
</template>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue"
3+
import { cn } from "@/lib/utils"
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes["class"]
7+
}>()
8+
</script>
9+
10+
<template>
11+
<div
12+
data-slot="alert-title"
13+
:class="cn('col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight', props.class)"
14+
>
15+
<slot />
16+
</div>
17+
</template>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { VariantProps } from "class-variance-authority"
2+
import { cva } from "class-variance-authority"
3+
4+
export { default as Alert } from "./Alert.vue"
5+
export { default as AlertDescription } from "./AlertDescription.vue"
6+
export { default as AlertTitle } from "./AlertTitle.vue"
7+
8+
export const alertVariants = cva(
9+
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
10+
{
11+
variants: {
12+
variant: {
13+
default: "bg-card text-card-foreground",
14+
destructive:
15+
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
16+
},
17+
},
18+
defaultVariants: {
19+
variant: "default",
20+
},
21+
},
22+
)
23+
24+
export type AlertVariants = VariantProps<typeof alertVariants>

0 commit comments

Comments
 (0)