123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792 |
- <template>
- <div>
- <div class="mb-6 flex flex-col md:flex-row justify-between md:items-center">
- <div class="relative">
- <input
- v-model="search"
- @keyup.esc="search = ''"
- tabindex="0"
- type="text"
- class="w-full md:w-64 appearance-none shadow bg-white text-grey-700 focus:outline-none rounded py-3 pl-3 pr-8"
- placeholder="Search Domains"
- />
- <icon
- v-if="search"
- @click.native="search = ''"
- name="close-circle"
- class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
- />
- <icon
- v-else
- name="search"
- class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current pointer-events-none mr-2 flex items-center"
- />
- </div>
- <div class="mt-4 md:mt-0">
- <button
- @click="addDomainModalOpen = true"
- class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none ml-auto"
- >
- Add Custom Domain
- </button>
- </div>
- </div>
- <vue-good-table
- v-if="initialDomains.length"
- @on-search="debounceToolips"
- :columns="columns"
- :rows="rows"
- :search-options="{
- enabled: true,
- skipDiacritics: true,
- externalQuery: search,
- }"
- :sort-options="{
- enabled: true,
- initialSortBy: { field: 'created_at', type: 'desc' },
- }"
- styleClass="vgt-table"
- >
- <div slot="emptystate" class="flex items-center justify-center h-24 text-lg text-grey-700">
- No domains found for that search!
- </div>
- <template slot="table-row" slot-scope="props">
- <span
- v-if="props.column.field == 'created_at'"
- class="tooltip outline-none text-sm"
- :data-tippy-content="rows[props.row.originalIndex].created_at | formatDate"
- >{{ props.row.created_at | timeAgo }}
- </span>
- <span v-else-if="props.column.field == 'domain'">
- <span
- class="tooltip cursor-pointer outline-none"
- data-tippy-content="Click to copy"
- v-clipboard="() => rows[props.row.originalIndex].domain"
- v-clipboard:success="clipboardSuccess"
- v-clipboard:error="clipboardError"
- >{{ props.row.domain | truncate(30) }}</span
- >
- </span>
- <span v-else-if="props.column.field == 'description'">
- <div v-if="domainIdToEdit === props.row.id" class="flex items-center">
- <input
- @keyup.enter="editDomain(rows[props.row.originalIndex])"
- @keyup.esc="domainIdToEdit = domainDescriptionToEdit = ''"
- v-model="domainDescriptionToEdit"
- type="text"
- class="flex-grow appearance-none bg-grey-100 border text-grey-700 focus:outline-none rounded px-2 py-1"
- :class="
- domainDescriptionToEdit.length > 100 ? 'border-red-500' : 'border-transparent'
- "
- placeholder="Add description"
- tabindex="0"
- autofocus
- />
- <icon
- name="close"
- class="inline-block w-6 h-6 text-red-300 fill-current cursor-pointer"
- @click.native="domainIdToEdit = domainDescriptionToEdit = ''"
- />
- <icon
- name="save"
- class="inline-block w-6 h-6 text-cyan-500 fill-current cursor-pointer"
- @click.native="editDomain(rows[props.row.originalIndex])"
- />
- </div>
- <div v-else-if="props.row.description" class="flex items-centers">
- <span
- class="tooltip outline-none"
- :data-tippy-content="rows[props.row.originalIndex].description"
- >{{ props.row.description | truncate(60) }}</span
- >
- <icon
- name="edit"
- class="inline-block w-6 h-6 text-grey-200 fill-current cursor-pointer ml-2"
- @click.native="
- ;(domainIdToEdit = props.row.id), (domainDescriptionToEdit = props.row.description)
- "
- />
- </div>
- <div v-else class="flex justify-center">
- <icon
- name="plus"
- class="block w-6 h-6 text-grey-200 fill-current cursor-pointer"
- @click.native=";(domainIdToEdit = props.row.id), (domainDescriptionToEdit = '')"
- />
- </div>
- </span>
- <span v-else-if="props.column.field === 'default_recipient'">
- <div v-if="props.row.default_recipient">
- {{ props.row.default_recipient.email | truncate(30) }}
- <icon
- name="edit"
- class="ml-2 inline-block w-6 h-6 text-grey-200 fill-current cursor-pointer"
- @click.native="openDomainDefaultRecipientModal(props.row)"
- />
- </div>
- <div class="flex justify-center" v-else>
- <icon
- name="plus"
- class="block w-6 h-6 text-grey-200 fill-current cursor-pointer"
- @click.native="openDomainDefaultRecipientModal(props.row)"
- />
- </div>
- </span>
- <span v-else-if="props.column.field === 'aliases_count'">
- {{ props.row.aliases_count }}
- </span>
- <span v-else-if="props.column.field === 'active'" class="flex items-center">
- <Toggle
- v-model="rows[props.row.originalIndex].active"
- @on="activateDomain(props.row.id)"
- @off="deactivateDomain(props.row.id)"
- />
- </span>
- <span v-else-if="props.column.field === 'catch_all'" class="flex items-center">
- <Toggle
- v-model="rows[props.row.originalIndex].catch_all"
- @on="enableCatchAll(props.row.id)"
- @off="disableCatchAll(props.row.id)"
- />
- </span>
- <span v-else-if="props.column.field === 'domain_sending_verified_at'">
- <span
- name="check"
- v-if="props.row.domain_sending_verified_at"
- class="py-1 px-2 bg-green-200 text-green-900 rounded-full text-xs"
- >
- verified
- </span>
- <button
- v-else
- @click="openCheckRecordsModal(rows[props.row.originalIndex])"
- class="focus:outline-none text-sm"
- >
- Check Records
- </button>
- </span>
- <span v-else class="flex items-center justify-center outline-none" tabindex="-1">
- <icon
- name="trash"
- class="block w-6 h-6 text-grey-200 fill-current cursor-pointer"
- @click.native="openDeleteModal(props.row.id)"
- />
- </span>
- </template>
- </vue-good-table>
- <div v-else class="bg-white rounded shadow overflow-x-auto">
- <div class="p-8 text-center text-lg text-grey-700">
- <h1 class="mb-6 text-xl text-indigo-800 font-semibold">
- This is where you can set up and view custom domains
- </h1>
- <div class="mx-auto mb-6 w-24 border-b-2 border-grey-200"></div>
- <p class="mb-4">
- To get started all you have to do is add a TXT record to your domain to verify ownership
- and then add the domain here by clicking the button above.
- </p>
- <p class="mb-4">
- The TXT record needs to have the following values:
- </p>
- <p class="mb-4">
- Type: <b>TXT</b><br />
- Host: <b>@</b><br />
- Value: <b>aa-verify={{ aaVerify }}</b
- ><br />
- </p>
- <p>
- Once the DNS changes propagate and you have verified ownership of the domain you will need
- to add a few more records to be able to recieve emails at your own domain.
- </p>
- </div>
- </div>
- <Modal :open="addDomainModalOpen" @close="closeCheckRecordsModal">
- <div v-if="!domainToCheck" class="max-w-2xl w-full bg-white rounded-lg shadow-2xl p-6">
- <h2
- class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
- >
- Add new domain
- </h2>
- <p class="mt-4 mb-2 text-grey-700">
- To verify ownership of the domain, please add the following TXT record and then click Add
- Domain below.
- </p>
- <div class="table w-full">
- <div class="table-row">
- <div class="table-cell py-2 font-semibold">Type</div>
- <div class="table-cell p-2 font-semibold">Host</div>
- <div class="table-cell py-2 font-semibold">Value/Points to</div>
- </div>
- <div class="table-row">
- <div class="table-cell py-2">TXT</div>
- <div class="table-cell p-2">@</div>
- <div class="table-cell py-2 break-all">aa-verify={{ aaVerify }}</div>
- </div>
- </div>
- <div class="mt-6">
- <p v-show="errors.newDomain" class="mb-3 text-red-500 text-sm">
- {{ errors.newDomain }}
- </p>
- <input
- v-model="newDomain"
- type="text"
- class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 mb-6"
- :class="errors.newDomain ? 'border-red-500' : ''"
- placeholder="example.com"
- autofocus
- />
- <button
- @click="validateNewDomain"
- class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
- :class="addDomainLoading ? 'cursor-not-allowed' : ''"
- :disabled="addDomainLoading"
- >
- Add Domain
- <loader v-if="addDomainLoading" />
- </button>
- <button
- @click="addDomainModalOpen = false"
- class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
- >
- Cancel
- </button>
- </div>
- </div>
- <div v-else class="max-w-2xl w-full bg-white rounded-lg shadow-2xl p-6">
- <h2
- class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
- >
- Check DNS records
- </h2>
- <p class="mt-4 mb-2 text-grey-700">
- Please set the following DNS records for your custom domain. If you have more than one MX
- record then the MX record below should have the lowest priority (e.g. 10).
- </p>
- <div class="table w-full">
- <div class="table-row">
- <div class="table-cell py-2 font-semibold">Type</div>
- <div class="table-cell py-2 px-4 font-semibold">Host</div>
- <div class="table-cell py-2 font-semibold">Value/Points to</div>
- </div>
- <div class="table-row">
- <div class="table-cell py-2">MX</div>
- <div class="table-cell py-2 px-4">@</div>
- <div class="table-cell py-2 break-words">{{ hostname }}</div>
- </div>
- <div class="table-row">
- <div class="table-cell py-2">TXT</div>
- <div class="table-cell py-2 px-4">@</div>
- <div class="table-cell py-2 break-words">v=spf1 include:spf.{{ domainName }} -all</div>
- </div>
- <div class="table-row">
- <div class="table-cell py-2">CNAME</div>
- <div class="table-cell py-2 px-4">default._domainkey</div>
- <div class="table-cell py-2 break-words">default._domainkey.{{ domainName }}.</div>
- </div>
- <div class="table-row">
- <div class="table-cell py-2">TXT</div>
- <div class="table-cell py-2 px-4">_dmarc</div>
- <div class="table-cell py-2 break-words">v=DMARC1; p=quarantine; adkim=s</div>
- </div>
- </div>
- <div class="mt-6">
- <button
- @click="checkRecords(domainToCheck)"
- class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
- :class="checkRecordsLoading ? 'cursor-not-allowed' : ''"
- :disabled="checkRecordsLoading"
- >
- Check Records
- <loader v-if="checkRecordsLoading" />
- </button>
- <button
- @click="closeCheckRecordsModal"
- class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
- >
- Cancel
- </button>
- </div>
- </div>
- </Modal>
- <Modal :open="domainDefaultRecipientModalOpen" @close="closeDomainDefaultRecipientModal">
- <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
- <h2
- class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
- >
- Update Default Recipient
- </h2>
- <p class="my-4 text-grey-700">
- Select the default recipient for this domain. This overrides the default recipient in your
- account settings. Leave it empty if you would like to use the default recipient in your
- account settings.
- </p>
- <multiselect
- v-model="defaultRecipient"
- :options="recipientOptions"
- :multiple="false"
- :close-on-select="true"
- :clear-on-select="false"
- :searchable="false"
- :allow-empty="true"
- placeholder="Select recipient"
- label="email"
- track-by="email"
- :preselect-first="false"
- :show-labels="false"
- >
- </multiselect>
- <div class="mt-6">
- <button
- type="button"
- @click="editDefaultRecipient()"
- class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus:outline-none"
- :class="editDefaultRecipientLoading ? 'cursor-not-allowed' : ''"
- :disabled="editDefaultRecipientLoading"
- >
- Update Default Recipient
- <loader v-if="editDefaultRecipientLoading" />
- </button>
- <button
- @click="closeDomainDefaultRecipientModal()"
- class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
- >
- Cancel
- </button>
- </div>
- </div>
- </Modal>
- <Modal :open="deleteDomainModalOpen" @close="closeDeleteModal">
- <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
- <h2
- class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
- >
- Delete domain
- </h2>
- <p class="mt-4 text-grey-700">
- Are you sure you want to delete this domain? This will also delete all aliases associated
- with this domain. You will no longer be able to receive any emails at this domain.
- </p>
- <div class="mt-6">
- <button
- type="button"
- @click="deleteDomain(domainIdToDelete)"
- class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
- :class="deleteDomainLoading ? 'cursor-not-allowed' : ''"
- :disabled="deleteDomainLoading"
- >
- Delete domain
- <loader v-if="deleteDomainLoading" />
- </button>
- <button
- @click="closeDeleteModal"
- class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
- >
- Cancel
- </button>
- </div>
- </div>
- </Modal>
- </div>
- </template>
- <script>
- import Modal from './../components/Modal.vue'
- import Toggle from './../components/Toggle.vue'
- import tippy from 'tippy.js'
- import Multiselect from 'vue-multiselect'
- export default {
- props: {
- initialDomains: {
- type: Array,
- required: true,
- },
- domainName: {
- type: String,
- required: true,
- },
- hostname: {
- type: String,
- required: true,
- },
- recipientOptions: {
- type: Array,
- required: true,
- },
- aaVerify: {
- type: String,
- required: true,
- },
- },
- components: {
- Modal,
- Toggle,
- Multiselect,
- },
- mounted() {
- this.addTooltips()
- },
- data() {
- return {
- newDomain: '',
- search: '',
- addDomainLoading: false,
- addDomainModalOpen: false,
- domainIdToDelete: null,
- domainIdToEdit: null,
- domainDescriptionToEdit: '',
- domainToCheck: null,
- deleteDomainLoading: false,
- deleteDomainModalOpen: false,
- checkRecordsLoading: false,
- domainDefaultRecipientModalOpen: false,
- defaultRecipientDomainToEdit: {},
- defaultRecipient: {},
- editDefaultRecipientLoading: false,
- errors: {},
- columns: [
- {
- label: 'Created',
- field: 'created_at',
- globalSearchDisabled: true,
- },
- {
- label: 'Domain',
- field: 'domain',
- },
- {
- label: 'Description',
- field: 'description',
- },
- {
- label: 'Default Recipient',
- field: 'default_recipient',
- sortable: false,
- globalSearchDisabled: true,
- },
- {
- label: 'Alias Count',
- field: 'aliases_count',
- type: 'number',
- globalSearchDisabled: true,
- },
- {
- label: 'Active',
- field: 'active',
- type: 'boolean',
- globalSearchDisabled: true,
- },
- {
- label: 'Catch-All',
- field: 'catch_all',
- type: 'boolean',
- globalSearchDisabled: true,
- },
- {
- label: 'Verified for Sending',
- field: 'domain_sending_verified_at',
- globalSearchDisabled: true,
- },
- {
- label: '',
- field: 'actions',
- sortable: false,
- globalSearchDisabled: true,
- },
- ],
- rows: this.initialDomains,
- }
- },
- watch: {
- domainIdToEdit: _.debounce(function() {
- this.addTooltips()
- }, 50),
- },
- methods: {
- addTooltips() {
- tippy('.tooltip', {
- arrow: true,
- arrowType: 'round',
- })
- },
- debounceToolips: _.debounce(function() {
- this.addTooltips()
- }, 50),
- validateNewDomain(e) {
- this.errors = {}
- if (!this.newDomain) {
- this.errors.newDomain = 'Domain name required'
- } else if (!this.validDomain(this.newDomain)) {
- this.errors.newDomain = 'Please enter a valid domain name'
- }
- if (!this.errors.newDomain) {
- this.addNewDomain()
- }
- e.preventDefault()
- },
- addNewDomain() {
- this.addDomainLoading = true
- axios
- .post(
- '/api/v1/domains',
- JSON.stringify({
- domain: this.newDomain,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- }
- )
- .then(({ data }) => {
- this.addDomainLoading = false
- this.rows.push(data.data)
- this.newDomain = ''
- this.domainToCheck = data.data
- this.success('Custom domain added')
- })
- .catch(error => {
- this.addDomainLoading = false
- if (error.response.status === 422) {
- this.error(error.response.data.errors.domain[0])
- } else if (error.response.status === 429) {
- this.error('Please wait a little while before checking the records again')
- } else if (error.response.status === 404) {
- this.warn(
- 'Verification TXT record not found, this could be due to DNS caching, please try again shortly.'
- )
- } else {
- this.error()
- }
- })
- },
- checkRecords(domain) {
- this.checkRecordsLoading = true
- axios
- .get(`/domains/${domain.id}/check-sending`)
- .then(({ data }) => {
- this.checkRecordsLoading = false
- if (data.success === true) {
- this.closeCheckRecordsModal()
- this.success(data.message)
- domain.domain_sending_verified_at = data.data.domain_sending_verified_at
- } else {
- this.warn(data.message)
- }
- })
- .catch(error => {
- this.checkRecordsLoading = false
- if (error.response.status === 429) {
- this.error('Please wait a little while before checking the records again')
- } else {
- this.error()
- }
- })
- },
- openDeleteModal(id) {
- this.deleteDomainModalOpen = true
- this.domainIdToDelete = id
- },
- closeDeleteModal() {
- this.deleteDomainModalOpen = false
- this.domainIdToDelete = null
- },
- openDomainDefaultRecipientModal(domain) {
- this.domainDefaultRecipientModalOpen = true
- this.defaultRecipientDomainToEdit = domain
- this.defaultRecipient = domain.default_recipient
- },
- closeDomainDefaultRecipientModal() {
- this.domainDefaultRecipientModalOpen = false
- this.defaultRecipientDomainToEdit = {}
- this.defaultRecipient = {}
- },
- openCheckRecordsModal(domain) {
- this.domainToCheck = domain
- this.addDomainModalOpen = true
- },
- closeCheckRecordsModal() {
- this.domainToCheck = null
- this.addDomainModalOpen = false
- },
- editDomain(domain) {
- if (this.domainDescriptionToEdit.length > 100) {
- return this.error('Description cannot be more than 100 characters')
- }
- axios
- .patch(
- `/api/v1/domains/${domain.id}`,
- JSON.stringify({
- description: this.domainDescriptionToEdit,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- }
- )
- .then(response => {
- domain.description = this.domainDescriptionToEdit
- this.domainIdToEdit = null
- this.domainDescriptionToEdit = ''
- this.success('Domain description updated')
- })
- .catch(error => {
- this.domainIdToEdit = null
- this.domainDescriptionToEdit = ''
- this.error()
- })
- },
- editDefaultRecipient() {
- this.editDefaultRecipientLoading = true
- axios
- .patch(
- `/api/v1/domains/${this.defaultRecipientDomainToEdit.id}/default-recipient`,
- JSON.stringify({
- default_recipient: this.defaultRecipient ? this.defaultRecipient.id : '',
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- }
- )
- .then(response => {
- let domain = _.find(this.rows, ['id', this.defaultRecipientDomainToEdit.id])
- domain.default_recipient = this.defaultRecipient
- this.domainDefaultRecipientModalOpen = false
- this.editDefaultRecipientLoading = false
- this.defaultRecipient = {}
- this.success("Domain's default recipient updated")
- })
- .catch(error => {
- this.domainDefaultRecipientModalOpen = false
- this.editDefaultRecipientLoading = false
- this.defaultRecipient = {}
- this.error()
- })
- },
- activateDomain(id) {
- axios
- .post(
- `/api/v1/active-domains`,
- JSON.stringify({
- id: id,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- }
- )
- .then(response => {
- //
- })
- .catch(error => {
- this.error()
- })
- },
- deactivateDomain(id) {
- axios
- .delete(`/api/v1/active-domains/${id}`)
- .then(response => {
- //
- })
- .catch(error => {
- this.error()
- })
- },
- enableCatchAll(id) {
- axios
- .post(
- `/api/v1/catch-all-domains`,
- JSON.stringify({
- id: id,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- }
- )
- .then(response => {
- //
- })
- .catch(error => {
- if (error.response !== undefined) {
- this.error(error.response.data)
- } else {
- this.error()
- }
- })
- },
- disableCatchAll(id) {
- axios
- .delete(`/api/v1/catch-all-domains/${id}`)
- .then(response => {
- //
- })
- .catch(error => {
- if (error.response !== undefined) {
- this.error(error.response.data)
- } else {
- this.error()
- }
- })
- },
- deleteDomain(id) {
- this.deleteDomainLoading = true
- axios
- .delete(`/api/v1/domains/${id}`)
- .then(response => {
- this.rows = _.reject(this.rows, domain => domain.id === id)
- this.deleteDomainModalOpen = false
- this.deleteDomainLoading = false
- })
- .catch(error => {
- this.error()
- this.deleteDomainLoading = false
- this.deleteDomainModalOpen = false
- })
- },
- validDomain(domain) {
- let re = /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/
- return re.test(domain)
- },
- clipboardSuccess() {
- this.success('Copied to clipboard')
- },
- clipboardError() {
- this.error('Could not copy to clipboard')
- },
- warn(text = '') {
- this.$notify({
- title: 'Information',
- text: text,
- type: 'warn',
- })
- },
- success(text = '') {
- this.$notify({
- title: 'Success',
- text: text,
- type: 'success',
- })
- },
- error(text = 'An error has occurred, please try again later') {
- this.$notify({
- title: 'Error',
- text: text,
- type: 'error',
- })
- },
- },
- }
- </script>
|