Skip to content

Commit

Permalink
feat(app): improve inbox (#100)
Browse files Browse the repository at this point in the history
Co-authored-by: Benjamin Canac <[email protected]>
  • Loading branch information
HugoRCD and benjamincanac authored Feb 17, 2025
1 parent eee30ce commit 550db84
Show file tree
Hide file tree
Showing 9 changed files with 748 additions and 200 deletions.
70 changes: 0 additions & 70 deletions app/_components/inbox/InboxMail.vue

This file was deleted.

14 changes: 4 additions & 10 deletions app/components/home/HomePeriodSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@
import { eachDayOfInterval } from 'date-fns'
import type { Period, Range } from '~/types'
const model = defineModel({
type: String as PropType<Period>,
required: true
})
const model = defineModel<Period>()
const props = defineProps({
range: {
type: Object as PropType<Range>,
required: true
}
})
const props = defineProps<{
range: Range
}>()
const days = computed(() => eachDayOfInterval(props.range))
Expand Down
32 changes: 6 additions & 26 deletions app/components/inbox/InboxList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,18 @@
import { format, isToday } from 'date-fns'
import type { Mail } from '~/types'
const props = defineProps({
modelValue: {
type: Object as PropType<Mail | null>,
default: null
},
mails: {
type: Array as PropType<Mail[]>,
default: () => []
}
})
const emit = defineEmits(['update:modelValue'])
const props = defineProps<{
mails: Mail[]
}>()
const mailsRefs = ref<Element[]>([])
const selectedMail = computed({
get() {
return props.modelValue
},
set(value: Mail | null) {
emit('update:modelValue', value)
}
})
const selectedMail = defineModel<Mail | null>()
watch(selectedMail, () => {
if (!selectedMail.value) {
return
}
const ref = mailsRefs.value[selectedMail.value.id]
if (ref) {
ref.scrollIntoView({ block: 'nearest' })
Expand Down Expand Up @@ -74,10 +57,7 @@ defineShortcuts({
]"
@click="selectedMail = mail"
>
<div
class="flex items-center justify-between"
:class="[mail.unread && 'font-semibold']"
>
<div class="flex items-center justify-between" :class="[mail.unread && 'font-semibold']">
<div class="flex items-center gap-3">
{{ mail.from.name }}

Expand All @@ -86,7 +66,7 @@ defineShortcuts({

<span>{{ isToday(new Date(mail.date)) ? format(new Date(mail.date), 'HH:mm') : format(new Date(mail.date), 'dd MMM') }}</span>
</div>
<p :class="[mail.unread && 'font-semibold']">
<p class="truncate" :class="[mail.unread && 'font-semibold']">
{{ mail.subject }}
</p>
<p class="text-(--ui-text-dimmed) line-clamp-1">
Expand Down
165 changes: 165 additions & 0 deletions app/components/inbox/InboxMail.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<script setup lang="ts">
import { format } from 'date-fns'
import type { Mail } from '~/types'
defineProps<{
mail: Mail
}>()
const emits = defineEmits(['close'])
const dropdownItems = [[{
label: 'Mark as unread',
icon: 'i-lucide-check-circle'
}, {
label: 'Mark as important',
icon: 'i-lucide-triangle-alert'
}], [{
label: 'Star thread',
icon: 'i-lucide-star'
}, {
label: 'Mute thread',
icon: 'i-lucide-circle-pause'
}]]
const toast = useToast()
const reply = ref('')
const loading = ref(false)
function onSubmit() {
loading.value = true
setTimeout(() => {
reply.value = ''
toast.add({
title: 'Email sent',
description: 'Your email has been sent successfully',
icon: 'i-lucide-check-circle',
color: 'success'
})
loading.value = false
}, 1000)
}
</script>

<template>
<UDashboardPanel id="inbox-2">
<UDashboardNavbar :title="mail.subject" :toggle="false">
<template #leading>
<UButton
icon="i-lucide-x"
color="neutral"
variant="ghost"
class="-ms-1.5"
@click="emits('close')"
/>
</template>

<template #right>
<UTooltip text="Archive">
<UButton
icon="i-lucide-inbox"
color="neutral"
variant="ghost"
/>
</UTooltip>

<UTooltip text="Reply">
<UButton icon="i-lucide-reply" color="neutral" variant="ghost" />
</UTooltip>

<UDropdownMenu :items="dropdownItems">
<UButton
icon="i-lucide-ellipsis-vertical"
color="neutral"
variant="ghost"
/>
</UDropdownMenu>
</template>
</UDashboardNavbar>

<div class="flex flex-col sm:flex-row justify-between gap-1 p-4 sm:px-6 border-b border-(--ui-border)">
<div class="flex items-start gap-4 sm:my-1.5">
<UAvatar
v-bind="mail.from.avatar"
:alt="mail.from.name"
size="3xl"
/>

<div class="min-w-0">
<p class="font-semibold text-(--ui-text-highlighted)">
{{ mail.from.name }}
</p>
<p class="text-(--ui-text-muted)">
{{ mail.from.email }}
</p>
</div>
</div>

<p class="max-sm:pl-16 text-(--ui-text-muted) text-sm sm:mt-2">
{{ format(new Date(mail.date), 'dd MMM HH:mm') }}
</p>
</div>

<div class="flex-1 p-4 sm:p-6 overflow-y-auto">
<p class="whitespace-pre-wrap">
{{ mail.body }}
</p>
</div>

<div class="pb-4 px-4 sm:px-6 shrink-0">
<UCard variant="subtle" class="mt-auto" :ui="{ header: 'flex items-center gap-1.5 text-(--ui-text-dimmed)' }">
<template #header>
<UIcon name="i-lucide-reply" class="size-5" />

<span class="text-sm truncate">
Reply to {{ mail.from.name }} ({{ mail.from.email }})
</span>
</template>

<form @submit.prevent="onSubmit">
<UTextarea
v-model="reply"
color="neutral"
variant="none"
required
autoresize
placeholder="Write your reply..."
:rows="4"
:disabled="loading"
class="w-full"
:ui="{ base: 'p-0 resize-none' }"
/>

<div class="flex items-center justify-between">
<UTooltip text="Attach file">
<UButton
color="neutral"
variant="ghost"
icon="i-lucide-paperclip"
/>
</UTooltip>

<div class="flex items-center justify-end gap-2">
<UButton
color="neutral"
variant="ghost"
label="Save draft"
/>
<UButton
type="submit"
color="neutral"
:loading="loading"
label="Send"
icon="i-lucide-send"
/>
</div>
</div>
</form>
</UCard>
</div>
</UDashboardPanel>
</template>
11 changes: 4 additions & 7 deletions app/error.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
<script setup lang="ts">
import type { NuxtError } from '#app'
defineProps<{
error: NuxtError
}>()
useSeoMeta({
title: 'Page not found',
description: 'We are sorry but this page could not be found.'
})
defineProps({
error: {
type: Object as PropType<NuxtError>,
required: true
}
})
useHead({
htmlAttrs: {
lang: 'en'
Expand Down
2 changes: 1 addition & 1 deletion app/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ onMounted(async () => {
</template>

<template #default="{ collapsed }">
<UDashboardSearchButton :square="collapsed" class="bg-transparent ring-(--ui-border)" />
<UDashboardSearchButton :collapsed="collapsed" class="bg-transparent ring-(--ui-border)" />

<UNavigationMenu
:collapsed="collapsed"
Expand Down
Loading

0 comments on commit 550db84

Please sign in to comment.