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

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions resources/js/components/AlertError.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { AlertCircle } from 'lucide-vue-next';
import { computed } from 'vue';

interface Props {
errors: string[];
title?: string;
}

const props = withDefaults(defineProps<Props>(), {
title: 'Something went wrong.',
});

const uniqueErrors = computed(() => Array.from(new Set(props.errors)));
</script>

<template>
<Alert variant="destructive">
<AlertCircle class="size-4" />
<AlertTitle>{{ title }}</AlertTitle>
<AlertDescription>
<ul class="list-inside list-disc text-sm">
<li v-for="(error, index) in uniqueErrors" :key="index">
{{ error }}
</li>
</ul>
</AlertDescription>
</Alert>
</template>
12 changes: 8 additions & 4 deletions resources/js/components/TwoFactorRecoveryCodes.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import AlertError from '@/components/AlertError.vue';
import { Button } from '@/components/ui/button';
import {
Card,
Expand All @@ -13,7 +14,7 @@ import { Form } from '@inertiajs/vue3';
import { Eye, EyeOff, LockKeyhole, RefreshCw } from 'lucide-vue-next';
import { nextTick, onMounted, ref } from 'vue';
const { recoveryCodesList, fetchRecoveryCodes } = useTwoFactorAuth();
const { recoveryCodesList, fetchRecoveryCodes, errors } = useTwoFactorAuth();
const isRecoveryCodesVisible = ref<boolean>(false);
const recoveryCodeSectionRef = ref<HTMLDivElement | null>(null);
Expand All @@ -38,7 +39,7 @@ onMounted(async () => {
</script>

<template>
<Card>
<Card class="w-full">
<CardHeader>
<CardTitle class="flex gap-3">
<LockKeyhole class="size-4" />2FA Recovery Codes
Expand All @@ -62,7 +63,7 @@ onMounted(async () => {
</Button>

<Form
v-if="isRecoveryCodesVisible"
v-if="isRecoveryCodesVisible && recoveryCodesList.length"
v-bind="regenerateRecoveryCodes.form()"
method="post"
:options="{ preserveScroll: true }"
Expand All @@ -86,7 +87,10 @@ onMounted(async () => {
: 'h-0 opacity-0',
]"
>
<div class="mt-3 space-y-3">
<div v-if="errors?.length" class="mt-6">
<AlertError :errors="errors" />
</div>
<div v-else class="mt-3 space-y-3">
<div
ref="recoveryCodeSectionRef"
class="grid gap-1 rounded-lg bg-muted p-4 font-mono text-sm"
Expand Down
114 changes: 59 additions & 55 deletions resources/js/components/TwoFactorSetupModal.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import AlertError from '@/components/AlertError.vue';
import InputError from '@/components/InputError.vue';
import { Button } from '@/components/ui/button';
import {
Expand Down Expand Up @@ -29,7 +30,7 @@ const props = defineProps<Props>();
const isOpen = defineModel<boolean>('isOpen');

const { copy, copied } = useClipboard();
const { qrCodeSvg, manualSetupKey, clearSetupData, fetchSetupData } =
const { qrCodeSvg, manualSetupKey, clearSetupData, fetchSetupData, errors } =
useTwoFactorAuth();

const showVerificationStep = ref(false);
Expand Down Expand Up @@ -150,79 +151,82 @@ watch(
class="relative flex w-auto flex-col items-center justify-center space-y-5"
>
<template v-if="!showVerificationStep">
<div
class="relative mx-auto flex max-w-md items-center overflow-hidden"
>
<AlertError v-if="errors?.length" :errors="errors" />
<template v-else>
<div
class="relative mx-auto aspect-square w-64 overflow-hidden rounded-lg border border-border"
class="relative mx-auto flex max-w-md items-center overflow-hidden"
>
<div
v-if="!qrCodeSvg"
class="absolute inset-0 z-10 flex aspect-square h-auto w-full animate-pulse items-center justify-center bg-background"
>
<Loader2 class="size-6 animate-spin" />
</div>
<div
v-else
class="relative z-10 overflow-hidden border p-5"
class="relative mx-auto aspect-square w-64 overflow-hidden rounded-lg border border-border"
>
<div
v-html="qrCodeSvg"
class="flex aspect-square size-full items-center justify-center"
/>
v-if="!qrCodeSvg"
class="absolute inset-0 z-10 flex aspect-square h-auto w-full animate-pulse items-center justify-center bg-background"
>
<Loader2 class="size-6 animate-spin" />
</div>
<div
v-else
class="relative z-10 overflow-hidden border p-5"
>
<div
v-html="qrCodeSvg"
class="flex aspect-square size-full items-center justify-center"
/>
</div>
</div>
</div>
</div>

<div class="flex w-full items-center space-x-5">
<Button class="w-full" @click="handleModalNextStep">
{{ modalConfig.buttonText }}
</Button>
</div>
<div class="flex w-full items-center space-x-5">
<Button class="w-full" @click="handleModalNextStep">
{{ modalConfig.buttonText }}
</Button>
</div>

<div
class="relative flex w-full items-center justify-center"
>
<div
class="absolute inset-0 top-1/2 h-px w-full bg-border"
/>
<span class="relative bg-card px-2 py-1"
>or, enter the code manually</span
class="relative flex w-full items-center justify-center"
>
</div>
<div
class="absolute inset-0 top-1/2 h-px w-full bg-border"
/>
<span class="relative bg-card px-2 py-1"
>or, enter the code manually</span
>
</div>

<div
class="flex w-full items-center justify-center space-x-2"
>
<div
class="flex w-full items-stretch overflow-hidden rounded-xl border border-border"
class="flex w-full items-center justify-center space-x-2"
>
<div
v-if="!manualSetupKey"
class="flex h-full w-full items-center justify-center bg-muted p-3"
class="flex w-full items-stretch overflow-hidden rounded-xl border border-border"
>
<Loader2 class="size-4 animate-spin" />
</div>
<template v-else>
<input
type="text"
readonly
:value="manualSetupKey"
class="h-full w-full bg-background p-3 text-foreground"
/>
<button
@click="copy(manualSetupKey || '')"
class="relative block h-auto border-l border-border px-3 hover:bg-muted"
<div
v-if="!manualSetupKey"
class="flex h-full w-full items-center justify-center bg-muted p-3"
>
<Check
v-if="copied"
class="w-4 text-green-500"
<Loader2 class="size-4 animate-spin" />
</div>
<template v-else>
<input
type="text"
readonly
:value="manualSetupKey"
class="h-full w-full bg-background p-3 text-foreground"
/>
<Copy v-else class="w-4" />
</button>
</template>
<button
@click="copy(manualSetupKey || '')"
class="relative block h-auto border-l border-border px-3 hover:bg-muted"
>
<Check
v-if="copied"
class="w-4 text-green-500"
/>
<Copy v-else class="w-4" />
</button>
</template>
</div>
</div>
</div>
</template>
</template>

<template v-else>
Expand Down
21 changes: 21 additions & 0 deletions resources/js/components/ui/alert/Alert.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import type { AlertVariants } from "."
import { cn } from "@/lib/utils"
import { alertVariants } from "."

const props = defineProps<{
class?: HTMLAttributes["class"]
variant?: AlertVariants["variant"]
}>()
</script>

<template>
<div
data-slot="alert"
:class="cn(alertVariants({ variant }), props.class)"
role="alert"
>
<slot />
</div>
</template>
17 changes: 17 additions & 0 deletions resources/js/components/ui/alert/AlertDescription.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"

const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>

<template>
<div
data-slot="alert-description"
:class="cn('text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed', props.class)"
>
<slot />
</div>
</template>
17 changes: 17 additions & 0 deletions resources/js/components/ui/alert/AlertTitle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"

const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>

<template>
<div
data-slot="alert-title"
:class="cn('col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight', props.class)"
>
<slot />
</div>
</template>
24 changes: 24 additions & 0 deletions resources/js/components/ui/alert/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { VariantProps } from "class-variance-authority"
import { cva } from "class-variance-authority"

export { default as Alert } from "./Alert.vue"
export { default as AlertDescription } from "./AlertDescription.vue"
export { default as AlertTitle } from "./AlertTitle.vue"

export const alertVariants = cva(
"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",
{
variants: {
variant: {
default: "bg-card text-card-foreground",
destructive:
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
},
},
defaultVariants: {
variant: "default",
},
},
)

export type AlertVariants = VariantProps<typeof alertVariants>
Loading