Files
anonaddy/resources/js/Pages/Aliases/Index.vue
2024-05-15 12:19:56 +01:00

2386 lines
85 KiB
Vue

<template>
<div>
<Head title="Aliases" />
<h1 id="primary-heading" class="sr-only">Aliases</h1>
<div class="sm:flex sm:items-center mb-6">
<div class="sm:flex-auto">
<h1 class="text-2xl font-semibold text-grey-900">Aliases</h1>
<p class="mt-2 text-sm text-grey-700">
A list of all the aliases
{{
Object.keys(route().params).length
? 'found for your search or filters'
: 'in your account'
}}
<button @click="moreInfoOpen = !moreInfoOpen">
<InformationCircleIcon
class="h-6 w-6 inline-block cursor-pointer text-grey-500"
title="Click for more information"
/>
</button>
</p>
</div>
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none flex items-center">
<button
type="button"
@click="createAliasModalOpen = true"
class="inline-flex items-center justify-center rounded-md border border-transparent bg-cyan-400 hover:bg-cyan-300 text-cyan-900 px-4 py-2 font-bold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 sm:w-auto"
>
Create Alias
</button>
</div>
</div>
<!-- Filters -->
<div
v-if="rows.length || Object.keys(route().params).length"
class="flex flex-col sm:flex-row justify-between items-center mb-4 bg-white rounded-lg shadow"
>
<div class="relative py-4 flex items-center space-x-1.5 px-4 text-sm sm:px-6">
<Listbox as="div" v-model="showAliasStatus">
<div class="relative">
<div>
<ListboxButton
class="inline-flex items-center text-sm text-grey-700 hover:text-grey-900 rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<span class="sr-only">Change display</span>
<ListboxLabel class="cursor-pointer">Display</ListboxLabel>
<p class="ml-1 font-medium">{{ showAliasStatus.label }}</p>
<ChevronDownIcon class="h-5 w-5 text-grey-700" aria-hidden="true" />
</ListboxButton>
</div>
<transition
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
class="absolute z-20 mt-2 w-48 origin-top-left overflow-hidden rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<ListboxOption
as="template"
v-for="option in displayOptions"
:key="option.value"
:value="option"
v-slot="{ active, selected }"
>
<li
:class="[
active ? 'text-white bg-indigo-500' : 'text-grey-900',
'cursor-pointer select-none p-2 text-sm',
]"
>
<div class="flex flex-col">
<div class="flex justify-between">
<p :class="selected ? 'font-semibold' : 'font-normal'">
{{ option.label }}
</p>
<span v-if="selected" :class="active ? 'text-white' : 'text-indigo-500'">
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</div>
</div>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
<span
v-if="['all', 'active_inactive', 'active'].includes(showAliasStatus.value)"
class="bg-green-100 tooltip outline-none h-4 w-4 rounded-full flex items-center justify-center"
data-tippy-content="Active"
tabindex="-1"
><span class="bg-green-400 h-2 w-2 rounded-full"></span
></span>
<span
v-if="['all', 'active_inactive', 'inactive'].includes(showAliasStatus.value)"
class="bg-grey-100 tooltip outline-none h-4 w-4 rounded-full flex items-center justify-center"
data-tippy-content="Inactive"
tabindex="-1"
><span class="bg-grey-400 h-2 w-2 rounded-full"></span
></span>
<span
v-if="['all', 'deleted'].includes(showAliasStatus.value)"
class="bg-red-100 tooltip outline-none h-4 w-4 rounded-full flex items-center justify-center"
data-tippy-content="Deleted"
tabindex="-1"
><span class="bg-red-400 h-2 w-2 rounded-full"></span
></span>
</div>
<div class="flex py-4 px-4 sm:px-6 lg:px-8">
<div class="flex items-center">
<Listbox as="div" v-model="currentSort">
<div class="relative">
<div>
<ListboxButton
class="inline-flex items-center text-sm text-grey-700 hover:text-grey-900 rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<span class="sr-only">Change sort by</span>
<ListboxLabel class="cursor-pointer">Sort By</ListboxLabel>
<p class="ml-1 font-medium">{{ currentSort.label }}</p>
<ChevronDownIcon class="h-5 w-5 text-grey-700" aria-hidden="true" />
</ListboxButton>
</div>
<transition
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
class="absolute right-0 z-20 mt-2 w-48 origin-top-right overflow-hidden rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<ListboxOption
as="template"
v-for="option in sortOptions"
:key="option.value"
:value="option"
v-slot="{ active, selected }"
>
<li
:class="[
active ? 'text-white bg-indigo-500' : 'text-grey-900',
'cursor-pointer select-none p-2 text-sm',
]"
>
<div class="flex flex-col">
<div class="flex justify-between">
<p :class="selected ? 'font-semibold' : 'font-normal'">
{{ option.label }}
</p>
<span v-if="selected" :class="active ? 'text-white' : 'text-indigo-500'">
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</div>
</div>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
<button
class="ml-3 disabled:cursor-not-allowed tooltip"
:disabled="changeSortDirLoading"
@click="changeSortDir()"
:data-tippy-content="
$page.props.sortDirection === 'desc' ? 'Change to ascending' : 'Change to descending'
"
>
<BarsArrowDownIcon v-if="$page.props.sortDirection === 'desc'" class="h-5 w-5" />
<BarsArrowUpIcon type="button" v-else class="h-5 w-5" />
</button>
</div>
</div>
</div>
<div v-if="rows.length">
<div class="relative">
<div
v-if="selectedRows.length > 0"
id="bulk-actions"
class="horizontal-scroll absolute px-0.5 top-0 left-12 flex flex-nowrap w-full h-12 items-center space-x-3 bg-gradient-to-r from-white z-10 overflow-x-auto"
style="width: calc(100% - 3rem)"
>
<button
type="button"
class="ml-1 inline-flex items-center rounded border border-grey-300 bg-white px-2.5 py-1.5 text-xs font-medium text-grey-700 shadow-sm hover:bg-grey-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-30"
:disabled="disabledBulkActivate() || bulkActivateAliasLoading"
@click="bulkActivateAlias()"
>
Activate <loader v-if="bulkActivateAliasLoading" />
</button>
<button
type="button"
class="inline-flex items-center rounded border border-grey-300 bg-white px-2.5 py-1.5 text-xs font-medium text-grey-700 shadow-sm hover:bg-grey-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-30"
:disabled="disabledBulkDeactivate() || bulkDeactivateAliasLoading"
@click="bulkDeactivateAlias()"
>
Deactivate <loader v-if="bulkDeactivateAliasLoading" />
</button>
<button
type="button"
class="inline-flex items-center rounded border border-grey-300 bg-white px-2.5 py-1.5 text-xs font-medium text-grey-700 shadow-sm hover:bg-grey-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-30 whitespace-nowrap"
:disabled="bulkEditAliasRecipientsLoading"
@click="
selectedRows.length === 1
? openAliasRecipientsModal(selectedRows[0])
: openBulkAliasRecipientsModal()
"
>
Edit Recipients <loader v-if="bulkEditAliasRecipientsLoading" />
</button>
<button
type="button"
class="inline-flex items-center rounded border border-grey-300 bg-white px-2.5 py-1.5 text-xs font-medium text-grey-700 shadow-sm hover:bg-grey-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-30"
:disabled="disabledBulkDelete()"
@click="
selectedAliasesToDelete.length === 1
? openDeleteModal(selectedAliasesToDelete[0])
: (bulkDeleteAliasModalOpen = true)
"
>
Delete
</button>
<button
type="button"
class="inline-flex items-center rounded border border-grey-300 bg-white px-2.5 py-1.5 text-xs font-medium text-grey-700 shadow-sm hover:bg-grey-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click="
selectedRowIds.length === 1
? openForgetModal(selectedRows[0])
: (bulkForgetAliasModalOpen = true)
"
>
Forget
</button>
<button
type="button"
class="inline-flex items-center rounded border border-grey-300 bg-white px-2.5 py-1.5 text-xs font-medium text-grey-700 shadow-sm hover:bg-grey-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-30"
:disabled="disabledBulkRestore()"
@click="
selectedAliasesToRestore.length === 1
? openRestoreModal(selectedAliasesToRestore[0])
: (bulkRestoreAliasModalOpen = true)
"
>
Restore
</button>
<span class="font-semibold text-indigo-800 hidden md:inline-block">{{
selectedRows.length === 1
? `${selectedRows.length} alias`
: `${selectedRows.length} aliases`
}}</span>
</div>
<vue-good-table
v-on:sort-change="debounceToolips"
v-on:page-change="debounceToolips"
v-on:per-page-change="debounceToolips"
:columns="columns"
:rows="rows"
:sort-options="{
enabled: false,
}"
styleClass="vgt-table"
:row-style-class="rowStyleClassFn"
>
<template #table-column="props">
<span v-if="props.column.field == 'select'">
<input
v-if="rows.length <= 25"
type="checkbox"
class="h-4 w-4 rounded border-grey-300 text-indigo-600 focus:ring-indigo-500 sm:left-6"
:checked="indeterminate || selectedRowIds.length === rows.length"
:indeterminate="indeterminate"
@change="selectedRowIds = $event.target.checked ? rows.map(r => r.id) : []"
/>
<div
v-else
type="checkbox"
class="h-4 w-4 rounded border-grey-300 bg-grey-100 text-indigo-600 focus:ring-indigo-500 sm:left-6 tooltip cursor-not-allowed"
data-tippy-content="'Select All' is only available when the page size is 25"
></div>
</span>
<span v-else :class="selectedRows.length > 0 ? 'blur-sm' : ''">
{{ props.column.label }}
</span>
</template>
<template #table-row="props">
<span v-if="props.column.field === 'select'" class="flex items-center">
<div
v-if="selectedRowIds.includes(props.row.id)"
class="absolute inset-y-0 left-0 w-0.5 bg-indigo-600"
></div>
<div
v-if="selectedRowIds.length >= 25 && !selectedRowIds.includes(props.row.id)"
type="checkbox"
class="h-4 w-4 rounded border-grey-300 bg-grey-100 text-indigo-600 focus:ring-indigo-500 sm:left-6 cursor-not-allowed"
title="You cannot select more than 25 aliases"
></div>
<input
v-else
type="checkbox"
class="h-4 w-4 rounded border-grey-300 text-indigo-600 focus:ring-indigo-500 sm:left-6"
title="Click to select'"
:value="props.row.id"
v-model="selectedRowIds"
/>
</span>
<span v-else-if="props.column.field == 'created_at'" class="flex items-center">
<span
:class="`bg-${getAliasStatus(props.row).colour}-100`"
class="tooltip outline-none h-4 w-4 rounded-full flex items-center justify-center mr-2"
:data-tippy-content="getAliasStatus(props.row).status"
tabindex="-1"
>
<span
:class="`bg-${getAliasStatus(props.row).colour}-400`"
class="h-2 w-2 rounded-full"
></span>
</span>
<span
class="tooltip outline-none cursor-default text-sm whitespace-nowrap text-grey-500"
:data-tippy-content="$filters.formatDate(rows[props.row.originalIndex].created_at)"
>{{ $filters.timeAgo(props.row.created_at) }}
</span>
</span>
<span v-else-if="props.column.field == 'email'" class="block">
<button
class="text-grey-400 tooltip outline-none"
data-tippy-content="Click to copy"
@click="clipboard(getAliasEmail(rows[props.row.originalIndex]))"
>
<span class="font-semibold text-indigo-800">{{
$filters.truncate(getAliasLocalPart(props.row), 60)
}}</span
><span
v-if="getAliasLocalPart(props.row).length <= 60"
class="font-semibold text-grey-500"
>{{
$filters.truncate(
'@' + props.row.domain,
60 - getAliasLocalPart(props.row).length,
)
}}</span
>
</button>
<div v-if="aliasIdToEdit === props.row.id" class="flex items-center">
<input
@keyup.enter="editAliasDescription(rows[props.row.originalIndex])"
@keyup.esc="aliasIdToEdit = aliasDescriptionToEdit = ''"
v-model="aliasDescriptionToEdit"
type="text"
class="grow text-sm appearance-none bg-grey-50 border text-grey-700 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 px-2 py-1"
:class="
aliasDescriptionToEdit.length > 200 ? 'border-red-500' : 'border-transparent'
"
placeholder="Add description"
tabindex="0"
autofocus
/>
<button @click="aliasIdToEdit = aliasDescriptionToEdit = ''">
<icon name="close" class="inline-block w-6 h-6 text-red-300 fill-current" />
</button>
<button @click="editAliasDescription(rows[props.row.originalIndex])">
<icon name="save" class="inline-block w-6 h-6 text-cyan-500 fill-current" />
</button>
</div>
<div v-else-if="props.row.description" class="flex items-center">
<span
class="inline-block text-grey-400 text-sm py-1 border border-transparent mr-2"
>
{{ $filters.truncate(props.row.description, 60) }}
</span>
<button
@click="
;(aliasIdToEdit = props.row.id),
(aliasDescriptionToEdit = props.row.description)
"
>
<icon name="edit" class="inline-block w-6 h-6 text-grey-300 fill-current" />
</button>
</div>
<div v-else>
<button
class="inline-block text-grey-300 text-sm py-1 border border-transparent"
@click=";(aliasIdToEdit = props.row.id), (aliasDescriptionToEdit = '')"
>
Add description
</button>
</div>
</span>
<span
v-else-if="props.column.field == 'recipients'"
class="flex items-center justify-center"
>
<span
v-if="props.row.recipients.length && props.row.id !== recipientsAliasToEdit.id"
class="inline-block tooltip outline-none font-semibold text-indigo-800"
:data-tippy-content="recipientsTooltip(props.row.recipients)"
>
{{ props.row.recipients.length }}
</span>
<span
v-else-if="props.row.id === recipientsAliasToEdit.id"
class="inline-block outline-none font-semibold text-indigo-800"
>{{ aliasRecipientsToEdit.length ? aliasRecipientsToEdit.length : '1' }}</span
>
<span
v-else-if="has(props.row.aliasable, 'default_recipient.email')"
class="py-1 px-2 text-xs bg-yellow-200 text-yellow-900 rounded-full tooltip outline-none cursor-default"
:data-tippy-content="props.row.aliasable.default_recipient.email"
>{{
props.row.aliasable_type === 'App\\Models\\Domain' ? 'domain' : 'username'
}}'s</span
>
<span
v-else
class="py-1 px-2 text-xs bg-yellow-200 text-yellow-900 rounded-full tooltip outline-none cursor-default"
:data-tippy-content="$page.props.user.email"
>default</span
>
<button @click="openAliasRecipientsModal(props.row)" class="ml-2">
<icon name="edit" class="inline-block w-6 h-6 text-grey-300 fill-current" />
</button>
</span>
<span
v-else-if="props.column.field == 'emails_forwarded'"
class="font-semibold text-indigo-800"
>
<span
v-if="props.row.last_forwarded"
class="tooltip outline-none cursor-default"
:data-tippy-content="
$filters.timeAgo(props.row.last_forwarded) +
' (' +
$filters.formatDate(rows[props.row.originalIndex].last_forwarded) +
')'
"
>{{ props.row.emails_forwarded.toLocaleString() }}
</span>
<span v-else>{{ props.row.emails_forwarded.toLocaleString() }} </span>
<span class="text-grey-300 mx-1.5">/</span>
<span
v-if="props.row.last_blocked"
class="tooltip outline-none cursor-default"
:data-tippy-content="
$filters.timeAgo(props.row.last_blocked) +
' (' +
$filters.formatDate(rows[props.row.originalIndex].last_blocked) +
')'
"
>{{ props.row.emails_blocked.toLocaleString() }}
</span>
<span v-else>{{ props.row.emails_blocked.toLocaleString() }} </span>
</span>
<span
v-else-if="props.column.field == 'emails_replied'"
class="font-semibold text-indigo-800"
>
<span
v-if="props.row.last_replied"
class="tooltip outline-none cursor-default"
:data-tippy-content="
$filters.timeAgo(props.row.last_replied) +
' (' +
$filters.formatDate(rows[props.row.originalIndex].last_replied) +
')'
"
>{{ props.row.emails_replied.toLocaleString() }}
</span>
<span v-else>{{ props.row.emails_replied.toLocaleString() }} </span>
<span class="text-grey-300 mx-1.5">/</span>
<span
v-if="props.row.last_sent"
class="tooltip outline-none cursor-default"
:data-tippy-content="
$filters.timeAgo(props.row.last_sent) +
' (' +
$filters.formatDate(rows[props.row.originalIndex].last_sent) +
')'
"
>{{ props.row.emails_sent.toLocaleString() }}
</span>
<span v-else>{{ props.row.emails_sent.toLocaleString() }} </span>
</span>
<span v-else-if="props.column.field === 'active'" class="flex items-center">
<Toggle
v-model="rows[props.row.originalIndex].active"
@on="activateAlias(rows[props.row.originalIndex])"
@off="deactivateAlias(rows[props.row.originalIndex])"
/>
</span>
<span v-else class="flex items-center justify-center outline-none" tabindex="-1">
<Link
:href="route('aliases.edit', props.row.id)"
as="button"
type="button"
class="text-indigo-500 hover:text-indigo-800 font-medium"
>Edit<span class="sr-only">, {{ props.row.email }}</span></Link
>
<button
@click="openSendFromModal(props.row)"
class="group flex items-center text-indigo-500 hover:text-indigo-800 font-medium ml-4 tooltip"
data-tippy-content="Send an email from this alias"
>
Send
<EnvelopeIcon class="ml-1 h-4 w-4" aria-hidden="true" />
</button>
</span>
</template>
</vue-good-table>
<div
class="mt-4 rounded-lg shadow flex items-center justify-between bg-white px-4 py-3 sm:px-6 overflow-x-auto horizontal-scroll"
>
<div class="flex flex-1 justify-between items-center md:hidden gap-x-3">
<Link
v-if="$page.props.initialRows.prev_page_url"
:href="$page.props.initialRows.prev_page_url"
as="button"
class="relative inline-flex items-center rounded-md border border-grey-300 bg-white px-4 py-2 text-sm font-medium text-grey-700 hover:bg-grey-50"
>
Previous
</Link>
<span
v-else
class="relative inline-flex h-min items-center rounded-md border border-grey-300 px-4 py-2 text-sm font-medium text-grey-700 bg-grey-100"
>Previous</span
>
<div class="flex flex-col items-center justify-center gap-y-2">
<p class="text-sm text-grey-700 text-center">
Showing
{{ ' ' }}
<span class="font-medium">{{ $page.props.initialRows.from.toLocaleString() }}</span>
{{ ' ' }}
to
{{ ' ' }}
<span class="font-medium">{{ $page.props.initialRows.to.toLocaleString() }}</span>
{{ ' ' }}
of
{{ ' ' }}
<span class="font-medium">{{
$page.props.initialRows.total.toLocaleString()
}}</span>
{{ ' ' }}
{{ $page.props.initialRows.total === 1 ? 'result' : 'results' }}
</p>
<select
v-model.number="pageSize"
@change="updatePageSize"
:disabled="updatePageSizeLoading"
class="relative rounded border-0 bg-transparent py-1 pr-8 text-grey-900 text-sm ring-1 ring-inset focus:z-10 focus:ring-2 focus:ring-inset ring-grey-300 focus:ring-indigo-600 disabled:cursor-not-allowed"
>
<option v-for="size in pageSizeOptions" :value="size">{{ size }}</option>
</select>
</div>
<Link
v-if="$page.props.initialRows.next_page_url"
:href="$page.props.initialRows.next_page_url"
as="button"
class="relative inline-flex h-min items-center rounded-md border border-grey-300 bg-white px-4 py-2 text-sm font-medium text-grey-700 hover:bg-grey-50"
>
Next
</Link>
<span
v-else
class="relative inline-flex items-center rounded-md border border-grey-300 px-4 py-2 text-sm font-medium text-grey-700 bg-grey-100"
>Next</span
>
</div>
<div class="hidden md:flex md:flex-1 md:items-center md:justify-between md:gap-x-2">
<div class="flex items-center gap-x-2">
<p class="text-sm text-grey-700">
Showing
{{ ' ' }}
<span class="font-medium">{{ $page.props.initialRows.from.toLocaleString() }}</span>
{{ ' ' }}
to
{{ ' ' }}
<span class="font-medium">{{ $page.props.initialRows.to.toLocaleString() }}</span>
{{ ' ' }}
of
{{ ' ' }}
<span class="font-medium">{{
$page.props.initialRows.total.toLocaleString()
}}</span>
{{ ' ' }}
{{ $page.props.initialRows.total === 1 ? 'result' : 'results' }}
</p>
<select
v-model.number="pageSize"
@change="updatePageSize"
:disabled="updatePageSizeLoading"
class="relative rounded border-0 bg-transparent py-1 pr-8 text-grey-900 text-sm ring-1 ring-inset focus:z-10 focus:ring-2 focus:ring-inset ring-grey-300 focus:ring-indigo-600 disabled:cursor-not-allowed"
>
<option v-for="size in pageSizeOptions" :value="size">{{ size }}</option>
</select>
</div>
<nav
class="isolate inline-flex -space-x-px rounded-md shadow-sm break-"
aria-label="Pagination"
>
<Link
v-if="$page.props.initialRows.prev_page_url"
:href="$page.props.initialRows.prev_page_url"
class="relative inline-flex items-center rounded-l-md border border-grey-300 bg-white px-2 py-2 text-sm font-medium text-grey-500 hover:bg-grey-50 focus:z-20"
>
<span class="sr-only">Previous</span>
<ChevronLeftIcon class="h-5 w-5" aria-hidden="true" />
</Link>
<span
v-else
class="disabled cursor-not-allowed relative inline-flex items-center rounded-l-md border border-grey-300 bg-white px-2 py-2 text-sm font-medium text-grey-500 hover:bg-grey-50 focus:z-20"
>
<span class="sr-only">Previous</span>
<ChevronLeftIcon class="h-5 w-5" aria-hidden="true" />
</span>
<div v-for="link in links" v-bind:key="link.label">
<Link
v-if="link.url"
:href="link.url"
aria-current="page"
class="relative inline-flex items-center border z-10 px-4 py-2 text-sm font-medium focus:z-20"
:class="
link.active
? 'border-indigo-500 bg-indigo-50 text-indigo-600'
: 'border-grey-300 bg-white text-grey-500 hover:bg-grey-50'
"
>{{ link.label }}</Link
>
<span
v-else
class="relative inline-flex items-center border border-grey-300 bg-white px-4 py-2 text-sm font-medium text-grey-700"
>...</span
>
</div>
<Link
v-if="$page.props.initialRows.next_page_url"
:href="$page.props.initialRows.next_page_url"
class="relative inline-flex items-center rounded-r-md border border-grey-300 bg-white px-2 py-2 text-sm font-medium text-grey-500 hover:bg-grey-50 focus:z-20"
>
<span class="sr-only">Next</span>
<ChevronRightIcon class="h-5 w-5" aria-hidden="true" />
</Link>
<span
v-else
class="disabled cursor-not-allowed relative inline-flex items-center rounded-r-md border border-grey-300 bg-white px-2 py-2 text-sm font-medium text-grey-500 hover:bg-grey-50 focus:z-20"
>
<span class="sr-only">Next</span>
<ChevronRightIcon class="h-5 w-5" aria-hidden="true" />
</span>
</nav>
</div>
</div>
</div>
</div>
<div v-else-if="Object.keys(route().params).length" class="text-center">
<AtSymbolIcon class="mx-auto h-16 w-16 text-grey-400" />
<h3 class="mt-2 text-lg font-medium text-grey-900">
No Aliases found for that search or with those filters
</h3>
<p class="mt-1 text-md text-grey-500">
Try entering a different search term or changing the filters.
</p>
<div class="mt-6">
<Link
:href="route('aliases.index')"
type="button"
class="inline-flex items-center rounded-md border border-transparent bg-cyan-400 hover:bg-cyan-300 text-cyan-900 px-4 py-2 text-sm font-medium shadow-sm focus:outline-none"
>
View All Aliases
</Link>
</div>
</div>
<div v-else class="text-center">
<AtSymbolIcon class="mx-auto h-16 w-16 text-grey-400" />
<h3 class="mt-2 text-lg font-medium text-grey-900">
It doesn't look like you have any aliases yet!
</h3>
<p class="mb-4 text-md text-grey-700">There are two ways to create new aliases.</p>
<h3 class="mb-2 text-lg text-indigo-800 font-semibold">
Option 1: Create aliases on the fly
</h3>
<p class="mb-2 text-grey-700">
To create aliases on the fly all you have to do is make up any new alias and give that out
instead of your real email address.
</p>
<p class="mb-2 text-grey-700">
Let's say you're signing up to <b>example.com</b> you could enter
<b>example@{{ subdomain }}</b> as your email address.
</p>
<p class="mb-2 text-grey-700">
The alias will show up here automatically as soon as it has forwarded its first email.
</p>
<p class="mb-2 text-grey-700">
If you start receiving spam to the alias you can simply deactivate it or delete it all
together!
</p>
<p class="mb-4 text-grey-700">
Try it out now by sending an email to <b>first@{{ subdomain }}</b> and then refresh this
page.
</p>
<h3 class="mb-2 text-lg text-indigo-800 font-semibold">
Option 2: Create a unique random alias
</h3>
<p class="mb-2 text-grey-700">
You can click the button above to create a random alias that will look something like this:
</p>
<p class="mb-2 text-grey-700">
<b>x481n904@{{ domain }}</b>
</p>
<p clas="text-grey-700">
This is useful if you do not wish to include your username in the email as a potential link
between aliases.
</p>
<div class="mt-4">
<button
@click="createAliasModalOpen = true"
type="button"
class="inline-flex items-center rounded-md border border-transparent bg-cyan-400 hover:bg-cyan-300 text-cyan-900 px-4 py-2 text-sm font-medium shadow-sm focus:outline-none"
>
<PlusIcon class="-ml-1 mr-2 h-5 w-5" aria-hidden="true" />
Create Your First Alias
</button>
</div>
</div>
<Modal :open="createAliasModalOpen" @close="createAliasModalOpen = false">
<template v-slot:title> Create new alias </template>
<template v-slot:content>
<p class="mt-4 text-grey-700">
Other aliases e.g. alias@{{ subdomain }} can also be created automatically when they
receive their first email.
</p>
<label for="alias_domain" class="block font-medium leading-6 text-grey-600 text-sm my-2">
Alias Domain
</label>
<div class="block relative w-full mb-4">
<select
v-model="createAliasDomain"
id="alias_domain"
class="block w-full rounded border-0 bg-transparent py-2 text-grey-900 ring-1 ring-inset focus:z-10 focus:ring-2 focus:ring-inset sm:text-base sm:leading-6"
required
>
<option v-for="domainOption in domainOptions" :key="domainOption" :value="domainOption">
{{ domainOption }}
</option>
</select>
</div>
<label
for="alias_format"
class="block font-medium leading-6 text-grey-600 text-sm mt-4 mb-2"
>
Alias Format
</label>
<div class="block relative w-full mb-4">
<select
v-model="createAliasFormat"
id="alias_format"
class="block w-full rounded border-0 bg-transparent py-2 text-grey-900 ring-1 ring-inset focus:z-10 focus:ring-2 focus:ring-inset sm:text-base sm:leading-6"
required
>
<option
v-for="formatOption in aliasFormatOptions"
:key="formatOption.value"
:value="formatOption.value"
>
{{ formatOption.label }}
</option>
</select>
</div>
<div v-if="createAliasFormat === 'custom'">
<label
for="alias_local_part"
class="block font-medium leading-6 text-grey-600 text-sm my-2"
>
Alias Local Part
</label>
<p v-show="errors.createAliasLocalPart" class="mb-3 text-red-500 text-sm">
{{ errors.createAliasLocalPart }}
</p>
<input
v-model="createAliasLocalPart"
id="alias_local_part"
type="text"
class="block w-full rounded-md border-0 py-2 pr-10 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-base sm:leading-6"
:class="errors.createAliasLocalPart ? 'border-red-500' : ''"
placeholder="Enter local part..."
autofocus
/>
</div>
<label
for="alias_description"
class="block font-medium leading-6 text-grey-600 text-sm my-2"
>
Description
</label>
<p v-show="errors.createAliasDescription" class="mb-3 text-red-500 text-sm">
{{ errors.createAliasDescription }}
</p>
<input
v-model="createAliasDescription"
id="alias_description"
type="text"
class="block w-full rounded-md border-0 py-2 pr-10 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-base sm:leading-6"
:class="errors.createAliasDescription ? 'ring-red-500' : ''"
placeholder="Enter description (optional)..."
autofocus
/>
<label
for="alias_recipient_ids"
class="block font-medium leading-6 text-grey-600 text-sm my-2"
>
Recipients
</label>
<p v-show="errors.createAliasRecipientIds" class="mb-3 text-red-500 text-sm">
{{ errors.createAliasRecipientIds }}
</p>
<multiselect
id="alias_recipient_ids"
v-model="createAliasRecipientIds"
mode="tags"
value-prop="id"
:options="recipientOptions"
:multiple="true"
:close-on-select="true"
:clear-on-select="false"
:searchable="true"
:max="10"
class="p-0"
placeholder="Select recipient(s) (optional)..."
label="email"
track-by="email"
>
</multiselect>
<div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button
@click="createNewAlias"
class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
:disabled="createAliasLoading"
>
Create Alias
<loader v-if="createAliasLoading" />
</button>
<button
@click="createAliasModalOpen = false"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cancel
</button>
</div>
</template>
</Modal>
<Modal :open="editAliasRecipientsModalOpen" @close="closeAliasRecipientsModal">
<template v-slot:title> Update Alias Recipients </template>
<template v-slot:content>
<p class="my-4 text-grey-700">
Select the recipients for this alias. You can choose multiple recipients. Leave it empty
if you would like to use the default recipient.
</p>
<multiselect
v-model="aliasRecipientsToEdit"
mode="tags"
value-prop="id"
:options="recipientOptions"
:multiple="true"
:close-on-select="true"
:clear-on-select="false"
:searchable="true"
:max="10"
class="p-0"
placeholder="Select recipient(s)"
label="email"
track-by="email"
>
</multiselect>
<div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button
type="button"
@click="editAliasRecipients()"
class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
:disabled="editAliasRecipientsLoading"
>
Update Recipients
<loader v-if="editAliasRecipientsLoading" />
</button>
<button
@click="closeAliasRecipientsModal()"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cancel
</button>
</div>
</template>
</Modal>
<Modal :open="bulkEditAliasRecipientsModalOpen" @close="closeBulkAliasRecipientsModal()">
<template v-slot:title> Update Recipients for Aliases </template>
<template v-slot:content>
<p class="my-4 text-grey-700">
Select the recipients for these <b>{{ selectedRowIds.length }}</b> aliases. You can choose
multiple recipients. Leave it empty if you would like to use the default recipient.
</p>
<multiselect
v-model="aliasRecipientsToEdit"
mode="tags"
value-prop="id"
:options="recipientOptions"
:multiple="true"
:close-on-select="true"
:clear-on-select="false"
:searchable="true"
:max="10"
class="p-0"
placeholder="Select recipient(s)"
label="email"
track-by="email"
>
</multiselect>
<div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button
type="button"
@click="bulkEditAliasRecipients()"
class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
:disabled="bulkEditAliasRecipientsLoading"
>
Update Recipients
<loader v-if="bulkEditAliasRecipientsLoading" />
</button>
<button
@click="closeBulkAliasRecipientsModal()"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cancel
</button>
</div>
</template>
</Modal>
<Modal :open="restoreAliasModalOpen" @close="closeRestoreModal">
<template v-slot:title> Restore alias </template>
<template v-slot:content>
<p class="mt-4 text-grey-700">
Are you sure you want to restore <b class="break-words">{{ aliasToRestore.email }}</b
>? Once restored <b class="break-words">{{ aliasToRestore.email }}</b> will be
<b>able to forward emails again</b>.
</p>
<div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button
type="button"
@click="restoreAlias(aliasToRestore.id)"
class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
:disabled="restoreAliasLoading"
>
Restore alias
<loader v-if="restoreAliasLoading" />
</button>
<button
@click="closeRestoreModal"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cancel
</button>
</div>
</template>
</Modal>
<Modal :open="bulkRestoreAliasModalOpen" @close="bulkRestoreAliasModalOpen = false">
<template v-slot:title> Restore aliases </template>
<template v-slot:content>
<p class="mt-4 text-grey-700">
Are you sure you want to restore these
<b>{{ selectedAliasesToRestore.length }}</b> aliases? Once restored they will be
<b>able to forward emails again</b>.
</p>
<div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button
type="button"
@click="bulkRestoreAlias()"
class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
:disabled="bulkRestoreAliasLoading"
>
Restore aliases
<loader v-if="bulkRestoreAliasLoading" />
</button>
<button
@click="bulkRestoreAliasModalOpen = false"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cancel
</button>
</div>
</template>
</Modal>
<Modal :open="deleteAliasModalOpen" @close="closeDeleteModal">
<template v-slot:title> Delete alias </template>
<template v-slot:content>
<p class="mt-4 text-grey-700">
Are you sure you want to delete <b class="break-words">{{ aliasToDelete.email }}</b
>? You can restore it if you later change your mind. Once deleted,
<b class="break-words">{{ aliasToDelete.email }}</b> will
<b>reject any emails sent to it</b>.
</p>
<div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button
type="button"
@click="deleteAlias(aliasToDelete.id)"
class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
:disabled="deleteAliasLoading"
>
Delete alias
<loader v-if="deleteAliasLoading" />
</button>
<button
@click="closeDeleteModal"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cancel
</button>
</div>
</template>
</Modal>
<Modal :open="bulkDeleteAliasModalOpen" @close="bulkDeleteAliasModalOpen = false">
<template v-slot:title> Delete aliases </template>
<template v-slot:content>
<p class="mt-4 text-grey-700">
Are you sure you want to delete these
<b>{{ selectedAliasesToDelete.length }}</b> aliases? You can restore them if you later
change your mind. Once deleted, these aliases will <b>reject any emails sent to them</b>.
</p>
<div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button
type="button"
@click="bulkDeleteAlias()"
class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
:disabled="bulkDeleteAliasLoading"
>
Delete aliases
<loader v-if="bulkDeleteAliasLoading" />
</button>
<button
@click="bulkDeleteAliasModalOpen = false"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cancel
</button>
</div>
</template>
</Modal>
<Modal :open="forgetAliasModalOpen" @close="closeForgetModal">
<template v-slot:title> Forget alias </template>
<template v-slot:content>
<p class="mt-4 text-grey-700">
Are you sure you want to forget <b class="break-words">{{ aliasToForget.email }}</b
>? Forgetting an alias will disassociate it from your account.
</p>
<p v-if="sharedDomains.includes(aliasToForget.domain)" class="mt-4 text-grey-700">
<b>Note:</b> This alias uses a shared domain so it can
<b>never be restored or used again</b> so make sure you are certain. Once forgotten,
<b class="break-words">{{ aliasToForget.email }}</b> will reject any emails sent to it.
</p>
<p v-else class="mt-4 text-grey-700">
<b>Note:</b> This is a standard alias so it
<b>can be created again in the future</b> since it will be as if it never existed in the
database. Once forgotten, if someone sends an email to this alias and you have
<b>catch-all enabled</b> for your
<b
>"{{
aliasToForget.aliasable_type === 'App\\Models\\Username'
? aliasToForget.domain.split('.')[0] + '" username'
: aliasToForget.domain + '" domain'
}}</b
>
then it will be created automatically again. If you would like this alias to reject any
messages sent to it then you should delete it instead.
</p>
<div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button
type="button"
@click="forgetAlias(aliasToForget.id)"
class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
:disabled="forgetAliasLoading"
>
Forget alias
<loader v-if="forgetAliasLoading" />
</button>
<button
@click="closeForgetModal"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cancel
</button>
</div>
</template>
</Modal>
<Modal :open="bulkForgetAliasModalOpen" @close="bulkForgetAliasModalOpen = false">
<template v-slot:title> Forget aliases </template>
<template v-slot:content>
<p class="mt-4 text-grey-700">
Are you sure you want to forget these
<b>{{ selectedRowIds.length }}</b> aliases? Forgetting these aliases will disassociate
them from your account.
</p>
<p class="mt-4 text-grey-700">
<b>Note:</b> If the alias uses a shared domain then it can <b>never be restored</b> or
used again so make sure you are certain. If it is a standard alias then it can be created
again since it will be as if it never existed.
</p>
<div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button
type="button"
@click="bulkForgetAlias()"
class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
:disabled="bulkForgetAliasLoading"
>
Forget aliases
<loader v-if="bulkForgetAliasLoading" />
</button>
<button
@click="bulkForgetAliasModalOpen = false"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cancel
</button>
</div>
</template>
</Modal>
<Modal :open="sendFromAliasModalOpen" @close="closeSendFromModal">
<template v-slot:title> Send from alias </template>
<template v-slot:content>
<p class="mt-4 text-grey-700">
Use this to automatically create the correct address to send an email to in order to send
an <b>email from this alias</b>.
</p>
<p class="mt-4 text-grey-700">
To send from an alias you must send the email from a <b>verified recipient</b> on your
addy.io account.
</p>
<label for="send_from_alias" class="block font-medium leading-6 text-grey-600 text-sm my-2">
Alias to send from
</label>
<input
v-model="aliasToSendFrom.email"
id="send_from_alias"
type="text"
class="block w-full rounded-md border-0 py-2 pr-10 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-base sm:leading-6 bg-grey-50"
disabled
/>
<label
for="send_from_alias_destination"
class="block font-medium leading-6 text-grey-600 text-sm my-2"
>
To email destination
</label>
<p v-show="errors.sendFromAliasDestination" class="mb-3 text-red-500 text-sm">
{{ errors.sendFromAliasDestination }}
</p>
<input
v-model="sendFromAliasDestination"
id="send_from_alias_destination"
type="text"
class="block w-full rounded-md border-0 py-2 pr-10 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-base sm:leading-6"
:class="errors.sendFromAliasDestination ? 'ring-red-500' : ''"
placeholder="Enter email..."
autofocus
/>
<div v-if="sendFromAliasEmailToSendTo">
<p for="alias_domain" class="block font-medium leading-6 text-grey-600 text-sm my-2">
Send your message to this email
</p>
<div
@click="clipboard(sendFromAliasEmailToSendTo), setSendFromAliasCopied()"
class="flex items-center justify-between cursor-pointer text-sm font-medium border-t-4 rounded-sm text-green-800 border-green-600 bg-green-100 p-2 mb-3"
role="alert"
>
<span>
{{ sendFromAliasEmailToSendTo }}
</span>
<svg
v-if="sendFromAliasCopied"
viewBox="0 0 24 24"
width="20"
height="20"
stroke="currentColor"
stroke-width="2"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 11 12 14 22 4"></polyline>
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
</svg>
<svg
v-else
viewBox="0 0 24 24"
width="20"
height="20"
stroke="currentColor"
stroke-width="2"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</div>
<a
:href="'mailto:' + sendFromAliasEmailToSendTo"
class="flex items-center justify-between cursor-pointer text-sm border-t-4 rounded-sm text-green-800 border-green-600 bg-green-100 p-2 mb-4"
role="alert"
title="Click To Open Mail Application"
>
Click to open mail application
</a>
</div>
<div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button
type="button"
@click="displaySendFromAddress(aliasToSendFrom)"
class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
:disabled="sendFromAliasLoading"
>
Show address
<loader v-if="sendFromAliasLoading" />
</button>
<button
@click="closeSendFromModal"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Close
</button>
</div>
</template>
</Modal>
<Modal :open="moreInfoOpen" @close="moreInfoOpen = false">
<template v-slot:title> More information </template>
<template v-slot:content>
<p class="mt-4 text-md text-grey-700">Aliases come under two different categories.</p>
<p class="mt-4 text-grey-700">
<b>Standard Aliases</b> - Standard aliases use a domain that is unique only to you, all
aliases for your custom domains are classed as standard aliases. Standard aliases can be
created automatically when they receive their first email (if catch-all is enabled for the
domain). If you signed up with a username of johndoe and gave out the following alias -
hello@johndoe.anonaddy.com then this would be a standard alias.
</p>
<p class="mt-4 text-grey-700">
<b>Shared Domain Aliases</b> - A shared domain alias is any alias that has a domain name
that is also shared with other users. For example anyone can generate an alias with the
@{{ domain }} domain. Aliases with shared domain names must be pre-generated and cannot be
created on-the-fly like standard aliases.
</p>
<div class="mt-6 flex flex-col sm:flex-row">
<button
@click="moreInfoOpen = false"
class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Close
</button>
</div>
</template>
</Modal>
</div>
</template>
<script setup>
import { ref, watch, computed, onMounted } from 'vue'
import { router, Head, Link } from '@inertiajs/vue3'
import Modal from '../../Components/Modal.vue'
import Toggle from '../../Components/Toggle.vue'
import { roundArrow } from 'tippy.js'
import tippy from 'tippy.js'
import { VueGoodTable } from 'vue-good-table-next'
import Multiselect from '@vueform/multiselect'
import { notify } from '@kyvg/vue3-notification'
import {
Listbox,
ListboxButton,
ListboxLabel,
ListboxOption,
ListboxOptions,
} from '@headlessui/vue'
import {
InformationCircleIcon,
AtSymbolIcon,
BarsArrowDownIcon,
BarsArrowUpIcon,
EnvelopeIcon,
} from '@heroicons/vue/24/outline'
import {
ChevronLeftIcon,
ChevronRightIcon,
ChevronDownIcon,
CheckIcon,
PlusIcon,
} from '@heroicons/vue/20/solid'
const props = defineProps({
initialRows: {
type: Object,
required: true,
},
recipientOptions: {
type: Array,
required: true,
},
domain: {
type: String,
required: true,
},
subdomain: {
type: String,
required: true,
},
domainOptions: {
type: Array,
required: true,
},
defaultAliasDomain: {
type: String,
required: true,
},
defaultAliasFormat: {
type: String,
required: true,
},
search: {
type: String,
},
initialPageSize: {
type: Number,
required: true,
},
sort: {
type: String,
},
sortDirection: {
type: String,
},
currentAliasStatus: {
type: String,
},
sharedDomains: {
type: Array,
required: true,
},
})
const rows = ref(props.initialRows.data)
const selectedRowIds = ref([])
const selectedRows = computed(() =>
_.filter(rows.value, row => selectedRowIds.value.includes(row.id)),
)
const checked = ref(false)
const indeterminate = computed(
() => selectedRows.value.length > 0 && selectedRows.value.length < rows.value.length,
)
const selectedAliasesToDelete = computed(() =>
_.filter(selectedRows.value, row => row.deleted_at === null),
)
const selectedAliasesToRestore = computed(() =>
_.filter(selectedRows.value, row => row.deleted_at !== null),
)
const links = ref(props.initialRows.links.slice(1, -1))
const aliasIdToEdit = ref('')
const aliasDescriptionToEdit = ref('')
const aliasToDelete = ref({})
const aliasToForget = ref({})
const aliasToSendFrom = ref({})
const sendFromAliasDestination = ref('')
const sendFromAliasEmailToSendTo = ref('')
const sendFromAliasCopied = ref(false)
const aliasToRestore = ref({})
const deleteAliasLoading = ref(false)
const forgetAliasLoading = ref(false)
const deleteAliasModalOpen = ref(false)
const forgetAliasModalOpen = ref(false)
const sendFromAliasLoading = ref(false)
const sendFromAliasModalOpen = ref(false)
const restoreAliasLoading = ref(false)
const restoreAliasModalOpen = ref(false)
const editAliasRecipientsLoading = ref(false)
const editAliasRecipientsModalOpen = ref(false)
const createAliasModalOpen = ref(false)
const createAliasLoading = ref(false)
const createAliasDomain = ref(props.defaultAliasDomain)
const createAliasLocalPart = ref('')
const createAliasDescription = ref('')
const createAliasRecipientIds = ref([])
const createAliasFormat = ref(props.defaultAliasFormat)
const moreInfoOpen = ref(false)
const recipientsAliasToEdit = ref({})
const aliasRecipientsToEdit = ref([])
const tippyInstance = ref(null)
const errors = ref({})
const bulkActivateAliasLoading = ref(false)
const bulkDeactivateAliasLoading = ref(false)
const bulkEditAliasRecipientsLoading = ref(false)
const bulkEditAliasRecipientsModalOpen = ref(false)
const bulkDeleteAliasLoading = ref(false)
const bulkDeleteAliasModalOpen = ref(false)
const bulkForgetAliasLoading = ref(false)
const bulkForgetAliasModalOpen = ref(false)
const bulkRestoreAliasLoading = ref(false)
const bulkRestoreAliasModalOpen = ref(false)
const changeSortDirLoading = ref(false)
const pageSize = ref(props.initialPageSize)
const updatePageSizeLoading = ref(false)
const pageSizeOptions = [25, 50, 100]
const displayOptions = [
{
value: 'all',
label: 'All',
params: {
deleted: 'with',
},
omit: ['page', 'active'],
},
{
value: 'active_inactive',
label: 'Active and Inactive',
params: {},
omit: ['page', 'active', 'deleted'],
},
{
value: 'active',
label: 'Active only',
params: {
active: 'true',
},
omit: ['page', 'deleted'],
},
{
value: 'inactive',
label: 'Inactive only',
params: {
active: 'false',
},
omit: ['page', 'deleted'],
},
{
value: 'deleted',
label: 'Deleted only',
params: {
deleted: 'only',
},
omit: ['page', 'active'],
},
]
const showAliasStatus = ref(_.find(displayOptions, ['value', props.currentAliasStatus]))
const sortOptions = [
{
value: 'active',
label: 'Active',
},
{
value: 'email',
label: 'Alias',
},
{
value: 'created_at',
label: 'Created At',
},
{
value: 'deleted_at',
label: 'Deleted At',
},
{
value: 'domain',
label: 'Domain',
},
{
value: 'emails_blocked',
label: 'Emails Blocked',
},
{
value: 'emails_forwarded',
label: 'Emails Forwarded',
},
{
value: 'emails_replied',
label: 'Emails Replied',
},
{
value: 'emails_sent',
label: 'Emails Sent',
},
{
value: 'last_blocked',
label: 'Last Blocked At',
},
{
value: 'last_forwarded',
label: 'Last Forwarded At',
},
{
value: 'last_replied',
label: 'Last Replied At',
},
{
value: 'last_sent',
label: 'Last Sent At',
},
{
value: 'updated_at',
label: 'Updated At',
},
]
const currentSort = ref(_.find(sortOptions, ['value', props.sort]))
const aliasFormatOptions = [
{
value: 'random_characters',
label: 'Random Characters',
},
{
value: 'uuid',
label: 'UUID',
},
{
value: 'random_words',
label: 'Random Words',
},
{
value: 'custom',
label: 'Custom',
},
]
const columns = [
{
label: '',
field: 'select',
},
{
label: 'Created',
field: 'created_at',
},
{
label: 'Alias',
field: 'email',
},
{
label: 'Recipients',
field: 'recipients',
tdClass: 'text-center',
},
{
label: 'Forwards/Blocks',
field: 'emails_forwarded',
type: 'number',
tdClass: 'text-center',
},
{
label: 'Replies/Sends',
field: 'emails_replied',
type: 'number',
tdClass: 'text-center',
},
{
label: 'Active',
field: 'active',
type: 'boolean',
},
{
label: '',
field: 'actions',
},
]
watch(
() => showAliasStatus,
function (status) {
let params = Object.assign(route().params, status.value.params)
router.visit(route('aliases.index', _.omit(params, status.value.omit)), {
only: ['initialRows', 'search', 'sort', 'sortDirection', 'currentAliasStatus'],
})
},
{ deep: true },
)
watch(
() => currentSort,
function (sort) {
let params = Object.assign(route().params, {
sort: props.sortDirection === 'desc' ? '-' + sort.value.value : sort.value.value,
})
router.visit(route('aliases.index', _.omit(params, ['page'])), {
only: ['initialRows', 'search', 'sort', 'sortDirection', 'currentAliasStatus'],
})
},
{ deep: true },
)
onMounted(() => {
debounceToolips()
})
const createNewAlias = () => {
errors.value = {}
// Validate alias local part
if (createAliasFormat.value === 'custom' && !validLocalPart(createAliasLocalPart.value)) {
return (errors.value.createAliasLocalPart = 'Valid local part required')
}
if (createAliasDescription.value.length > 200) {
return (errors.value.createAliasDescription = 'Description cannot exceed 200 characters')
}
createAliasLoading.value = true
axios
.post(
'/api/v1/aliases',
JSON.stringify({
domain: createAliasDomain.value,
local_part: createAliasLocalPart.value,
description: createAliasDescription.value,
format: createAliasFormat.value,
recipient_ids: createAliasRecipientIds.value,
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(({ data }) => {
// Show active/inactive
router.visit(route('aliases.index'), {
only: ['initialRows', 'search', 'currentAliasStatus', 'sort', 'sortDirection'],
onSuccess: page => {
createAliasLoading.value = false
createAliasLocalPart.value = ''
createAliasDescription.value = ''
createAliasRecipientIds.value = []
createAliasModalOpen.value = false
successMessage('New alias created successfully')
},
})
})
.catch(error => {
createAliasLoading.value = false
if ([429, 403].includes(error.response.status)) {
errorMessage(error.response.data)
} else if (error.response.status === 422) {
errorMessage(error.response.data.message)
} else {
errorMessage()
}
})
}
const editAliasDescription = alias => {
if (aliasDescriptionToEdit.value.length > 200) {
return errorMessage('Description cannot be more than 200 characters')
}
axios
.patch(
`/api/v1/aliases/${alias.id}`,
JSON.stringify({
description: aliasDescriptionToEdit.value,
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(response => {
alias.description = aliasDescriptionToEdit.value
aliasIdToEdit.value = ''
aliasDescriptionToEdit.value = ''
successMessage('Alias description updated')
})
.catch(error => {
aliasIdToEdit.value = ''
aliasDescriptionToEdit.value = ''
errorMessage()
})
}
const editAliasRecipients = () => {
editAliasRecipientsLoading.value = true
axios
.post(
'/api/v1/alias-recipients',
JSON.stringify({
alias_id: recipientsAliasToEdit.value.id,
recipient_ids: aliasRecipientsToEdit.value,
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(response => {
let alias = _.find(rows.value, ['id', recipientsAliasToEdit.value.id])
// JSON required to fix failed to execute 'replaceState' on 'History' error
alias.recipients = JSON.parse(
JSON.stringify(
_.filter(props.recipientOptions, recipient =>
aliasRecipientsToEdit.value.includes(recipient.id),
),
),
)
editAliasRecipientsLoading.value = false
closeAliasRecipientsModal()
successMessage('Alias recipients updated')
})
.catch(error => {
editAliasRecipientsLoading.value = false
closeAliasRecipientsModal()
errorMessage()
})
}
const bulkEditAliasRecipients = () => {
bulkEditAliasRecipientsLoading.value = true
// No need to filter
let selectedAliasesToEditRecipients = selectedRows.value
axios
.post(
'/api/v1/aliases/recipients/bulk',
JSON.stringify({
ids: selectedAliasesToEditRecipients.map(a => a.id),
recipient_ids: aliasRecipientsToEdit.value,
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(response => {
_.each(selectedAliasesToEditRecipients, alias => {
// JSON required to fix failed to execute 'replaceState' on 'History' error
alias.recipients = JSON.parse(
JSON.stringify(
_.filter(props.recipientOptions, recipient =>
aliasRecipientsToEdit.value.includes(recipient.id),
),
),
)
})
bulkEditAliasRecipientsLoading.value = false
closeBulkAliasRecipientsModal()
successMessage(response.data.message)
})
.catch(error => {
bulkEditAliasRecipientsLoading.value = false
closeBulkAliasRecipientsModal()
errorMessage()
})
}
const activateAlias = alias => {
axios
.post(
`/api/v1/active-aliases`,
JSON.stringify({
id: alias.id,
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(response => {
alias.active = true
debounceToolips()
})
.catch(error => {
alias.active = false
if (error.response !== undefined) {
errorMessage(error.response.data)
} else {
errorMessage()
}
})
}
const bulkActivateAlias = () => {
bulkActivateAliasLoading.value = true
// First filter selected rows to remove any that are already active or are currently deleted
let selectedAliasesToActivate = _.filter(selectedRows.value, r => {
return !r.active && r.deleted_at === null
})
axios
.post(
`/api/v1/aliases/activate/bulk`,
JSON.stringify({
ids: selectedAliasesToActivate.map(a => a.id),
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(response => {
_.each(selectedAliasesToActivate, r => {
r.active = true
})
bulkActivateAliasLoading.value = false
debounceToolips()
successMessage(response.data.message)
})
.catch(error => {
bulkActivateAliasLoading.value = false
if (error.response.status === 429) {
errorMessage('Too many bulk requests, please wait a little while before trying again')
} else if (error.response.data.message !== undefined) {
errorMessage(error.response.data.message)
} else {
errorMessage()
}
})
}
const deactivateAlias = alias => {
axios
.delete(`/api/v1/active-aliases/${alias.id}`)
.then(response => {
alias.active = false
})
.catch(error => {
alias.active = true
debounceToolips()
if (error.response !== undefined) {
errorMessage(error.response.data)
} else {
errorMessage()
}
})
}
const bulkDeactivateAlias = () => {
bulkDeactivateAliasLoading.value = true
// First filter selected rows to remove any that are already deactivated
let selectedAliasesToDeactivate = _.filter(selectedRows.value, r => {
return r.active
})
axios
.post(
`/api/v1/aliases/deactivate/bulk`,
JSON.stringify({
ids: selectedAliasesToDeactivate.map(a => a.id),
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(response => {
_.each(selectedAliasesToDeactivate, r => {
r.active = false
})
bulkDeactivateAliasLoading.value = false
debounceToolips()
successMessage(response.data.message)
})
.catch(error => {
bulkDeactivateAliasLoading.value = false
if (error.response.status === 429) {
errorMessage('Too many bulk requests, please wait a little while before trying again')
} else if (error.response.data.message !== undefined) {
errorMessage(error.response.data.message)
} else {
errorMessage()
}
})
}
const deleteAlias = id => {
deleteAliasLoading.value = true
axios
.delete(`/api/v1/aliases/${id}`)
.then(response => {
// If showing deleted then set as deleted and inactive
if (['all', 'deleted'].includes(props.currentAliasStatus)) {
let alias = _.find(rows.value, ['id', id])
alias.deleted_at = dayjs.utc().format()
alias.active = false
alias.recipients = []
deleteAliasModalOpen.value = false
deleteAliasLoading.value = false
selectedRowIds.value = []
debounceToolips()
successMessage('Alias deleted successfully')
} else {
router.reload({
only: ['initialRows', 'search', 'currentAliasStatus', 'sort', 'sortDirection'],
onSuccess: page => {
deleteAliasModalOpen.value = false
deleteAliasLoading.value = false
selectedRowIds.value = []
rows.value = props.initialRows.data
successMessage('Alias deleted successfully')
},
})
}
})
.catch(error => {
errorMessage()
deleteAliasModalOpen.value = false
deleteAliasLoading.value = false
})
}
const bulkDeleteAlias = () => {
bulkDeleteAliasLoading.value = true
axios
.post(
`/api/v1/aliases/delete/bulk`,
JSON.stringify({
ids: selectedAliasesToDelete.value.map(a => a.id),
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(response => {
// If showing deleted then set as deleted and inactive
if (['all', 'deleted'].includes(props.currentAliasStatus)) {
_.each(selectedAliasesToDelete.value, r => {
r.deleted_at = dayjs.utc().format()
r.active = false
r.recipients = []
})
bulkDeleteAliasLoading.value = false
bulkDeleteAliasModalOpen.value = false
selectedRowIds.value = []
debounceToolips()
successMessage(response.data.message)
} else {
router.reload({
only: ['initialRows', 'search', 'currentAliasStatus', 'sort', 'sortDirection'],
onSuccess: page => {
bulkDeleteAliasLoading.value = false
bulkDeleteAliasModalOpen.value = false
selectedRowIds.value = []
rows.value = props.initialRows.data
successMessage(response.data.message)
},
})
}
})
.catch(error => {
bulkDeleteAliasLoading.value = false
bulkDeleteAliasModalOpen.value = false
if (error.response.status === 429) {
errorMessage('Too many bulk requests, please wait a little while before trying again')
} else if (error.response.data.message !== undefined) {
errorMessage(error.response.data.message)
} else {
errorMessage()
}
})
}
const forgetAlias = id => {
forgetAliasLoading.value = true
axios
.delete(`/api/v1/aliases/${id}/forget`)
.then(response => {
router.reload({
only: ['initialRows', 'search', 'currentAliasStatus', 'sort', 'sortDirection'],
onSuccess: page => {
forgetAliasModalOpen.value = false
forgetAliasLoading.value = false
selectedRowIds.value = []
rows.value = props.initialRows.data
successMessage('Alias forgotten successfully')
},
})
})
.catch(error => {
errorMessage()
forgetAliasModalOpen.value = false
forgetAliasLoading.value = false
})
}
const bulkForgetAlias = () => {
bulkForgetAliasLoading.value = true
// No need to filter
let selectedAliasesToForget = selectedRows.value
axios
.post(
`/api/v1/aliases/forget/bulk`,
JSON.stringify({
ids: selectedAliasesToForget.map(a => a.id),
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(response => {
router.reload({
only: ['initialRows', 'search', 'currentAliasStatus', 'sort', 'sortDirection'],
onSuccess: page => {
bulkForgetAliasLoading.value = false
bulkForgetAliasModalOpen.value = false
selectedRowIds.value = []
rows.value = props.initialRows.data
successMessage(response.data.message)
},
})
})
.catch(error => {
bulkForgetAliasLoading.value = false
bulkForgetAliasModalOpen.value = false
if (error.response.status === 429) {
errorMessage('Too many bulk requests, please wait a little while before trying again')
} else if (error.response.data.message !== undefined) {
errorMessage(error.response.data.message)
} else {
errorMessage()
}
})
}
const restoreAlias = id => {
restoreAliasLoading.value = true
axios
.patch(`/api/v1/aliases/${id}/restore`, {
headers: { 'Content-Type': 'application/json' },
})
.then(response => {
// If showing only deleted then reload all aliases
if (props.currentAliasStatus === 'deleted') {
router.reload({
only: ['initialRows', 'search', 'currentAliasStatus', 'sort', 'sortDirection'],
onSuccess: page => {
restoreAliasModalOpen.value = false
restoreAliasLoading.value = false
selectedRowIds.value = []
rows.value = props.initialRows.data
successMessage('Alias restored successfully')
},
})
} else {
let alias = _.find(rows.value, ['id', id])
alias.deleted_at = null
alias.active = true
restoreAliasModalOpen.value = false
restoreAliasLoading.value = false
selectedRowIds.value = []
successMessage('Alias restored successfully')
}
})
.catch(error => {
errorMessage()
restoreAliasModalOpen.value = false
restoreAliasLoading.value = false
})
}
const bulkRestoreAlias = () => {
bulkRestoreAliasLoading.value = true
axios
.post(
`/api/v1/aliases/restore/bulk`,
JSON.stringify({
ids: selectedAliasesToRestore.value.map(a => a.id),
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(response => {
// If showing only deleted then reload all aliases
if (props.currentAliasStatus === 'deleted') {
router.reload({
only: ['initialRows', 'search', 'currentAliasStatus', 'sort', 'sortDirection'],
onSuccess: page => {
bulkRestoreAliasLoading.value = false
bulkRestoreAliasModalOpen.value = false
selectedRowIds.value = []
rows.value = props.initialRows.data
successMessage(response.data.message)
},
})
} else {
_.each(selectedAliasesToRestore.value, r => {
r.deleted_at = null
r.active = true
})
bulkRestoreAliasLoading.value = false
bulkRestoreAliasModalOpen.value = false
selectedRowIds.value = []
successMessage(response.data.message)
}
})
.catch(error => {
bulkRestoreAliasLoading.value = false
bulkRestoreAliasModalOpen.value = false
if (error.response.status === 429) {
errorMessage('Too many bulk requests, please wait a little while before trying again')
} else if (error.response.data.message !== undefined) {
errorMessage(error.response.data.message)
} else {
errorMessage()
}
})
}
const changeSortDir = () => {
changeSortDirLoading.value = true
let params = Object.assign(route().params, {
sort: props.sortDirection === 'desc' ? _.trimStart(props.sort, '-') : '-' + props.sort,
})
router.visit(route(route().current(), _.omit(params, ['page'])), {
only: ['initialRows', 'search', 'currentAliasStatus', 'sort', 'sortDirection'],
onSuccess: page => {
changeSortDirLoading.value = false
},
})
}
const updatePageSize = () => {
updatePageSizeLoading.value = true
let params = Object.assign(route().params, {
page_size: pageSize.value,
})
let omit = pageSize.value === 25 ? ['page', 'page_size'] : ['page']
router.visit(route('aliases.index', _.omit(params, omit)), {
only: [
'initialRows',
'search',
'sort',
'sortDirection',
'currentAliasStatus',
'initialPageSize',
],
onSuccess: page => {
updatePageSizeLoading.value = false
},
})
}
const openDeleteModal = alias => {
deleteAliasModalOpen.value = true
aliasToDelete.value = alias
}
const closeDeleteModal = () => {
deleteAliasModalOpen.value = false
_.delay(() => (aliasToDelete.value = {}), 300)
}
const openForgetModal = alias => {
forgetAliasModalOpen.value = true
aliasToForget.value = alias
}
const closeForgetModal = () => {
forgetAliasModalOpen.value = false
_.delay(() => (aliasToForget.value = {}), 300)
}
const openSendFromModal = alias => {
sendFromAliasDestination.value = ''
sendFromAliasEmailToSendTo.value = ''
sendFromAliasCopied.value = false
sendFromAliasModalOpen.value = true
aliasToSendFrom.value = alias
}
const closeSendFromModal = () => {
sendFromAliasModalOpen.value = false
_.delay(() => (aliasToSendFrom.value = {}), 300)
}
const openRestoreModal = alias => {
restoreAliasModalOpen.value = true
aliasToRestore.value = alias
}
const closeRestoreModal = () => {
restoreAliasModalOpen.value = false
_.delay(() => (aliasToRestore.value = {}), 300)
}
const openAliasRecipientsModal = alias => {
editAliasRecipientsModalOpen.value = true
recipientsAliasToEdit.value = alias
aliasRecipientsToEdit.value = _.map(alias.recipients, recipient => recipient.id)
}
const closeAliasRecipientsModal = () => {
editAliasRecipientsModalOpen.value = false
_.delay(() => (aliasRecipientsToEdit.value = []), 300)
recipientsAliasToEdit.value = {}
debounceToolips()
}
const openBulkAliasRecipientsModal = () => {
bulkEditAliasRecipientsModalOpen.value = true
aliasRecipientsToEdit.value = []
// Leave preselected recipients as blank
/* aliasRecipientsToEdit.value = _
.chain(selectedRows.value)
.flatMap(row => row.recipients.map(r => r.id))
.uniq()
.take(10)
.value() */
}
const closeBulkAliasRecipientsModal = () => {
bulkEditAliasRecipientsModalOpen.value = false
_.delay(() => (aliasRecipientsToEdit.value = []), 300)
debounceToolips()
}
const addTooltips = () => {
if (tippyInstance.value) {
_.each(tippyInstance.value, instance => instance.destroy())
}
tippyInstance.value = tippy('.tooltip', {
arrow: roundArrow,
allowHTML: true,
})
}
const debounceToolips = _.debounce(function () {
addTooltips()
}, 50)
const recipientsTooltip = recipients => {
return _.reduce(recipients, (list, recipient) => list + `${recipient.email}<br>`, '')
}
const displaySendFromAddress = alias => {
errors.value = {}
if (!validEmail(sendFromAliasDestination.value)) {
errors.value.sendFromAliasDestination = 'Valid Email required'
return
}
sendFromAliasEmailToSendTo.value = `${alias.local_part}+${sendFromAliasDestination.value.replace(
'@',
'=',
)}@${alias.domain}`
}
const setSendFromAliasCopied = () => {
sendFromAliasCopied.value = true
}
const getAliasEmail = alias => {
return alias.extension ? `${alias.local_part}+${alias.extension}@${alias.domain}` : alias.email
}
const getAliasLocalPart = alias => {
return alias.extension ? `${alias.local_part}+${alias.extension}` : alias.local_part
}
const getAliasStatus = alias => {
if (alias.deleted_at) {
return {
colour: 'red',
status: 'Deleted',
}
} else {
return {
colour: alias.active ? 'green' : 'grey',
status: alias.active ? 'Active' : 'Inactive',
}
}
}
const has = (object, path) => {
return _.has(object, path)
}
const validLocalPart = part => {
let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))$/
return re.test(part)
}
const validEmail = email => {
let re =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return re.test(email)
}
const disabledBulkActivate = () => {
return !_.find(selectedRows.value, { active: false, deleted_at: null })
}
const disabledBulkDeactivate = () => {
return !_.find(selectedRows.value, 'active')
}
const disabledBulkDelete = () => {
return !_.find(selectedRows.value, r => {
return r.deleted_at === null
})
}
const disabledBulkRestore = () => {
return !_.find(selectedRows.value, r => {
return r.deleted_at !== null
})
}
const rowStyleClassFn = row => {
return selectedRowIds.value.includes(row.id) ? 'bg-grey-50' : ''
}
const clipboard = (str, success, error) => {
// Needed as v-clipboard doesn't work inside modals!
navigator.clipboard.writeText(str).then(
() => {
successMessage('Copied to clipboard')
},
() => {
errorMessage('Could not copy to clipboard')
},
)
}
const successMessage = (text = '') => {
notify({
title: 'Success',
text: text,
type: 'success',
})
}
const errorMessage = (text = 'An error has occurred, please try again later') => {
notify({
title: 'Error',
text: text,
type: 'error',
})
}
</script>