- improved vies and controllers for rejecting datasets with email for reviewer and editor role
Some checks failed
CI Pipeline / japa-tests (push) Failing after 1m8s

- falsh also error via config/inertia.ts
- npm updates
This commit is contained in:
Kaimbacher 2024-09-26 13:51:35 +02:00
parent b06ccae603
commit 2235f3905a
12 changed files with 627 additions and 370 deletions

View File

@ -21,7 +21,7 @@ export default class LicenseController {
await license.save(); await license.save();
// session.flash({ message: 'person has been deactivated!' }); // session.flash({ message: 'person has been deactivated!' });
return response.flash('person has been deactivated!', 'message').toRoute('settings.license.index') return response.flash('License has been deactivated!', 'message').toRoute('settings.license.index')
} }
public async up({ request, response }: HttpContext) { public async up({ request, response }: HttpContext) {
@ -31,7 +31,7 @@ export default class LicenseController {
await license.save(); await license.save();
// session.flash({ message: 'person has been activated!' }); // session.flash({ message: 'person has been activated!' });
return response.flash('person has been activated!', 'message').toRoute('settings.license.index'); return response.flash('License has been activated!', 'message').toRoute('settings.license.index');
} }
// public async edit({ request, inertia }: HttpContext) { // public async edit({ request, inertia }: HttpContext) {

View File

@ -15,9 +15,12 @@ import { DoiClient } from '#app/Library/Doi/DoiClient';
import DoiClientException from '#app/exceptions/DoiClientException'; import DoiClientException from '#app/exceptions/DoiClientException';
import logger from '@adonisjs/core/services/logger'; import logger from '@adonisjs/core/services/logger';
import { HttpException } from 'node-exceptions'; import { HttpException } from 'node-exceptions';
import { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model"; import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
import vine, { SimpleMessagesProvider } from '@vinejs/vine'; import vine, { SimpleMessagesProvider } from '@vinejs/vine';
import mail from '@adonisjs/mail/services/main';
// import { resolveMx } from 'dns/promises';
// import * as net from 'net';
import { validate } from 'deep-email-validator';
// Create a new instance of the client // Create a new instance of the client
const client = new Client({ node: 'http://localhost:9200' }); // replace with your OpenSearch endpoint const client = new Client({ node: 'http://localhost:9200' }); // replace with your OpenSearch endpoint
@ -146,7 +149,7 @@ export default class DatasetsController {
try { try {
await dataset.related('editor').associate(user); // speichert schon ab await dataset.related('editor').associate(user); // speichert schon ab
// await dataset.save(); // await dataset.save();
return response.toRoute('editor.dataset.list').flash('message', `You have accepted dataset ${dataset.id}!`); return response.toRoute('editor.dataset.list').flash(`You have accepted dataset ${dataset.id}!`, 'message');
} catch (error) { } catch (error) {
// Handle any errors // Handle any errors
console.error(error); console.error(error);
@ -231,7 +234,7 @@ export default class DatasetsController {
// .preload('titles') // .preload('titles')
// .preload('descriptions') // .preload('descriptions')
.preload('user', (builder) => { .preload('user', (builder) => {
builder.select('id', 'login'); builder.select('id', 'login', 'email');
}) })
.firstOrFail(); .firstOrFail();
@ -252,22 +255,86 @@ export default class DatasetsController {
}); });
} }
public async rejectUpdate({ request, response }: HttpContext) { // private async checkEmailDomain(email: string): Promise<boolean> {
// const domain = email.split('@')[1];
// try {
// // Step 1: Check MX records for the domain
// const mxRecords = await resolveMx(domain);
// if (mxRecords.length === 0) {
// return false; // No MX records, can't send email
// }
// // Sort MX records by priority
// mxRecords.sort((a, b) => a.priority - b.priority);
// // Step 2: Attempt SMTP connection to the first available mail server
// const smtpServer = mxRecords[0].exchange;
// return await this.checkMailboxExists(smtpServer, email);
// } catch (error) {
// console.error('Error during MX lookup or SMTP validation:', error);
// return false;
// }
// }
//// Helper function to check if the mailbox exists using SMTP
// private async checkMailboxExists(smtpServer: string, email: string): Promise<boolean> {
// return new Promise((resolve, reject) => {
// const socket = net.createConnection(25, smtpServer);
// socket.on('connect', () => {
// socket.write(`HELO ${smtpServer}\r\n`);
// socket.write(`MAIL FROM: <test@example.com>\r\n`);
// socket.write(`RCPT TO: <${email}>\r\n`);
// });
// socket.on('data', (data) => {
// const response = data.toString();
// if (response.includes('250')) {
// // 250 is an SMTP success code
// socket.end();
// resolve(true); // Email exists
// } else if (response.includes('550')) {
// // 550 means the mailbox doesn't exist
// socket.end();
// resolve(false); // Email doesn't exist
// }
// });
// socket.on('error', (error) => {
// console.error('SMTP connection error:', error);
// socket.end();
// resolve(false);
// });
// socket.on('end', () => {
// // SMTP connection closed
// });
// socket.setTimeout(5000, () => {
// // Timeout after 5 seconds
// socket.end();
// resolve(false); // Assume email doesn't exist if no response
// });
// });
// }
public async rejectUpdate({ request, response, auth }: HttpContext) {
const authUser = auth.user!;
const id = request.param('id'); const id = request.param('id');
const dataset = await Dataset.query() const dataset = await Dataset.query()
.where('id', id) .where('id', id)
.preload('user', (builder) => { .preload('user', (builder) => {
builder.select('id', 'login'); builder.select('id', 'login', 'email');
}) })
.firstOrFail(); .firstOrFail();
// const newSchema = schema.create({
// server_state: schema.string({ trim: true }),
// reject_reviewer_note: schema.string({ trim: true }, [rules.minLength(10), rules.maxLength(500)]),
// });
const newSchema = vine.object({ const newSchema = vine.object({
server_state: vine.string().trim(), server_state: vine.string().trim(),
reject_editor_note: vine.string().trim().minLength(10).maxLength(500), reject_editor_note: vine.string().trim().minLength(10).maxLength(500),
send_mail: vine.boolean().optional(),
}); });
try { try {
@ -285,29 +352,56 @@ export default class DatasetsController {
// return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back(); // return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
return response return response
.flash( .flash(
'warning',
`Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`, `Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`,
'warning'
) )
.redirect() .redirect()
.toRoute('reviewer.dataset.list'); .toRoute('editor.dataset.list');
} }
// dataset.server_state = 'reviewed';
dataset.server_state = 'rejected_editor'; dataset.server_state = 'rejected_editor';
const rejectEditorNote = request.input('reject_editor_note', ''); const rejectEditorNote = request.input('reject_editor_note', '');
dataset.reject_editor_note = rejectEditorNote; dataset.reject_editor_note = rejectEditorNote;
// add logic for sending reject message
const sendMail = request.input('send_email', false);
// const validRecipientEmail = await this.checkEmailDomain('arno.kaimbacher@outlook.at');
const validationResult = await validate({
email: dataset.user.email,
validateSMTP: false,
});
const validRecipientEmail: boolean = validationResult.valid;
let emailStatusMessage = '';
if (sendMail == true) {
if (dataset.user.email && validRecipientEmail) {
try { try {
// await dataset.related('editor').associate(user); // speichert schon ab await mail.send((message) => {
message.to(dataset.user.email).subject('Dataset Rejection Notification').html(`
<p>Dear ${dataset.user.login},</p>
<p>Your dataset with ID ${dataset.id} has been rejected.</p>
<p>Reason for rejection: ${rejectEditorNote}</p>
<p>Best regards,<br>Your Tethys editor: ${authUser.login}</p>
`);
});
emailStatusMessage = ` A rejection email was successfully sent to ${dataset.user.email}.`;
} catch (error) {
logger.error(error);
return response.flash('Dataset has not been rejected due to an email error: ' + error.message, 'error').toRoute('editor.dataset.list');
}
} else {
emailStatusMessage = ` However, the email could not be sent because the submitter's email address (${dataset.user.email}) is not valid.`;
}
}
await dataset.save(); await dataset.save();
return response return response
.toRoute('editor.dataset.list') .flash(
.flash('message', `You have rejected dataset ${dataset.id}! to submitter ${dataset.user.login}`); `You have successfully rejected dataset ${dataset.id} submitted by ${dataset.user.login}.${emailStatusMessage}`,
} catch (error) { 'message',
// Handle any errors )
console.error(error); .toRoute('editor.dataset.list');
return response.status(500).json({ error: 'An error occurred while reviewing the data.' });
}
} }
public async publish({ request, inertia, response }: HttpContext) { public async publish({ request, inertia, response }: HttpContext) {
@ -405,7 +499,6 @@ export default class DatasetsController {
prefix = process.env.DATACITE_PREFIX || ''; prefix = process.env.DATACITE_PREFIX || '';
base_domain = process.env.BASE_DOMAIN || ''; base_domain = process.env.BASE_DOMAIN || '';
// register DOI: // register DOI:
const doiValue = prefix + '/tethys.' + dataset.publish_id; //'10.21388/tethys.213' const doiValue = prefix + '/tethys.' + dataset.publish_id; //'10.21388/tethys.213'
const landingPageUrl = 'https://doi.' + getDomain(base_domain) + '/' + prefix + '/tethys.' + dataset.publish_id; //https://doi.dev.tethys.at/10.21388/tethys.213 const landingPageUrl = 'https://doi.' + getDomain(base_domain) + '/' + prefix + '/tethys.' + dataset.publish_id; //https://doi.dev.tethys.at/10.21388/tethys.213

View File

@ -4,14 +4,16 @@ import Dataset from '#models/dataset';
import Field from '#app/Library/Field'; import Field from '#app/Library/Field';
import BaseModel from '#models/base_model'; import BaseModel from '#models/base_model';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model"; import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
import vine from '@vinejs/vine'; import vine from '@vinejs/vine';
import mail from '@adonisjs/mail/services/main';
import logger from '@adonisjs/core/services/logger';
import { validate } from 'deep-email-validator';
interface Dictionary { interface Dictionary {
[index: string]: string; [index: string]: string;
} }
export default class DatasetsController { export default class DatasetsController {
public async index({ auth, request, inertia }: HttpContext) { public async index({ auth, request, inertia }: HttpContext) {
const user = (await User.find(auth.user?.id)) as User; const user = (await User.find(auth.user?.id)) as User;
@ -196,8 +198,8 @@ export default class DatasetsController {
.where('id', id) .where('id', id)
// .preload('titles') // .preload('titles')
// .preload('descriptions') // .preload('descriptions')
.preload('user', (builder) => { .preload('editor', (builder) => {
builder.select('id', 'login'); builder.select('id', 'login', 'email');
}) })
.firstOrFail(); .firstOrFail();
@ -218,12 +220,14 @@ export default class DatasetsController {
}); });
} }
public async rejectUpdate({ request, response }: HttpContext) { public async rejectUpdate({ request, response, auth }: HttpContext) {
const authUser = auth.user!;
const id = request.param('id'); const id = request.param('id');
const dataset = await Dataset.query() const dataset = await Dataset.query()
.where('id', id) .where('id', id)
.preload('editor', (builder) => { .preload('editor', (builder) => {
builder.select('id', 'login'); builder.select('id', 'login', 'email');
}) })
.firstOrFail(); .firstOrFail();
@ -234,6 +238,7 @@ export default class DatasetsController {
const newSchema = vine.object({ const newSchema = vine.object({
server_state: vine.string().trim(), server_state: vine.string().trim(),
reject_reviewer_note: vine.string().trim().minLength(10).maxLength(500), reject_reviewer_note: vine.string().trim().minLength(10).maxLength(500),
send_mail: vine.boolean().optional(),
}); });
try { try {
@ -251,8 +256,8 @@ export default class DatasetsController {
// return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back(); // return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
return response return response
.flash( .flash(
'warning',
`Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`, `Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`,
'warning',
) )
.redirect() .redirect()
.toRoute('reviewer.dataset.list'); .toRoute('reviewer.dataset.list');
@ -263,16 +268,43 @@ export default class DatasetsController {
const rejectReviewerNote = request.input('reject_reviewer_note', ''); const rejectReviewerNote = request.input('reject_reviewer_note', '');
dataset.reject_reviewer_note = rejectReviewerNote; dataset.reject_reviewer_note = rejectReviewerNote;
// add logic for sending reject message
const sendMail = request.input('send_email', false);
// const validRecipientEmail = await this.checkEmailDomain('arno.kaimbacher@outlook.at');
const validationResult = await validate({
email: dataset.editor.email,
validateSMTP: false,
});
const validRecipientEmail: boolean = validationResult.valid;
let emailStatusMessage = '';
if (sendMail == true) {
if (dataset.user.email && validRecipientEmail) {
try { try {
// await dataset.related('editor').associate(user); // speichert schon ab await mail.send((message) => {
message.to(dataset.editor.email).subject('Dataset Rejection Notification').html(`
<p>Dear editor ${dataset.editor.login},</p>
<p>Your approved dataset with ID ${dataset.id} has been rejected.</p>
<p>Reason for rejection: ${rejectReviewerNote}</p>
<p>Best regards,<br>Your Tethys reviewer: ${authUser.login}</p>
`);
});
emailStatusMessage = ` A rejection email was successfully sent to ${dataset.editor.email}.`;
} catch (error) {
logger.error(error);
return response
.flash('Dataset has not been rejected due to an email error: ' + error.message, 'error')
.toRoute('reviewer.dataset.list');
}
} else {
emailStatusMessage = ` However, the email could not be sent because the editor's email address (${dataset.editor.email}) is not valid.`;
}
}
await dataset.save(); await dataset.save();
return response return response
.toRoute('reviewer.dataset.list') .toRoute('reviewer.dataset.list')
.flash('message', `You have rejected dataset ${dataset.id}! to editor ${dataset.editor.login}`); .flash(`You have rejected dataset ${dataset.id}! to editor ${dataset.editor.login}`, 'message');
} catch (error) {
// Handle any errors
console.error(error);
return response.status(500).json({ error: 'An error occurred while reviewing the data.' });
}
} }
} }

View File

@ -771,7 +771,7 @@ export default class DatasetController {
} }
if (await dataset.merge(input).save()) { if (await dataset.merge(input).save()) {
return response.toRoute('dataset.list').flash('message', 'You have released your dataset!'); return response.toRoute('dataset.list').flash('You have released your dataset!', 'message');
} }
// throw new GeneralException(trans('exceptions.publish.release.update_error')); // throw new GeneralException(trans('exceptions.publish.release.update_error'));
} }

View File

@ -24,6 +24,7 @@ export default defineConfig({
return { return {
message: ctx.session?.flashMessages.get('message'), message: ctx.session?.flashMessages.get('message'),
warning: ctx.session?.flashMessages.get('warning'), warning: ctx.session?.flashMessages.get('warning'),
error: ctx.session?.flashMessages.get('error'),
}; };
}, },

View File

@ -1,21 +1,24 @@
import env from '#start/env' import env from '#start/env';
import { defineConfig, transports } from '@adonisjs/mail' import { defineConfig, transports } from '@adonisjs/mail';
const mailConfig = defineConfig({ const mailConfig = defineConfig({
default: 'smtp', default: 'smtp',
from: 'tethys@geosphere.at',
/** /**
* The mailers object can be used to configure multiple mailers * The mailers object can be used to configure multiple mailers
* each using a different transport or same transport with different * each using a different transport or same transport with different
* options. * options.
*/ */
mailers: { mailers: {
smtp: transports.smtp({ smtp: transports.smtp({
socketTimeout: 5000,// Overall timeout (5 seconds)
host: env.get('SMTP_HOST', ''), host: env.get('SMTP_HOST', ''),
port: env.get('SMTP_PORT'), port: env.get('SMTP_PORT'),
secure: false, secure: false,
// ignoreTLS: true, // ignoreTLS: true,
requireTLS: false, requireTLS: false,
/** /**
* Uncomment the auth block if your SMTP * Uncomment the auth block if your SMTP
* server needs authentication * server needs authentication
@ -31,19 +34,15 @@ const mailConfig = defineConfig({
key: env.get('RESEND_API_KEY'), key: env.get('RESEND_API_KEY'),
baseUrl: 'https://api.resend.com', baseUrl: 'https://api.resend.com',
}), }),
}, },
}) });
export default mailConfig export default mailConfig;
declare module '@adonisjs/mail/types' { declare module '@adonisjs/mail/types' {
export interface MailersList extends InferMailers<typeof mailConfig> {} export interface MailersList extends InferMailers<typeof mailConfig> {}
} }
// const mailConfig = defineConfig({ // const mailConfig = defineConfig({
// default: 'smtp', // default: 'smtp',

635
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@
"@types/escape-html": "^1.0.4", "@types/escape-html": "^1.0.4",
"@types/leaflet": "^1.9.3", "@types/leaflet": "^1.9.3",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^20.1.1", "@types/node": "^22.5.5",
"@types/proxy-addr": "^2.0.0", "@types/proxy-addr": "^2.0.0",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/source-map-support": "^0.5.6", "@types/source-map-support": "^0.5.6",
@ -54,14 +54,14 @@
"babel-preset-typescript-vue3": "^2.0.17", "babel-preset-typescript-vue3": "^2.0.17",
"chart.js": "^4.2.0", "chart.js": "^4.2.0",
"dotenv-webpack": "^8.0.1", "dotenv-webpack": "^8.0.1",
"eslint": "^8.57.0", "eslint": "^8.57.1",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-adonis": "^2.1.1", "eslint-plugin-adonis": "^2.1.1",
"eslint-plugin-prettier": "^5.0.0-alpha.2", "eslint-plugin-prettier": "^5.0.0-alpha.2",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"pinia": "^2.0.30", "pinia": "^2.0.30",
"pino-pretty": "^11.2.2", "pino-pretty": "^11.2.2",
"postcss-loader": "^7.3.4", "postcss-loader": "^8.1.1",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4",
@ -99,6 +99,7 @@
"clamscan": "^2.1.2", "clamscan": "^2.1.2",
"crypto": "^1.0.1", "crypto": "^1.0.1",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"deep-email-validator": "^0.1.21",
"edge.js": "^6.0.1", "edge.js": "^6.0.1",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"focus-trap": "^7.5.4", "focus-trap": "^7.5.4",

View File

@ -94,15 +94,19 @@ const formatServerState = (state: string) => {
<template> <template>
<LayoutAuthenticated> <LayoutAuthenticated>
<Head title="Dataset List" /> <Head title="Editor Datasets" />
<SectionMain> <SectionMain>
<!-- <FormValidationErrors v-bind:errors="errors" /> -->
<NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline"> <NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
{{ flash.message }} {{ flash.message }}
</NotificationBar> </NotificationBar>
<NotificationBar v-if="flash.warning" color="warning" :icon="mdiAlertBoxOutline"> <NotificationBar v-if="flash.warning" color="warning" :icon="mdiAlertBoxOutline">
{{ flash.warning }} {{ flash.warning }}
</NotificationBar> </NotificationBar>
<NotificationBar v-if="flash.error" color="danger" :icon="mdiAlertBoxOutline">
{{ flash.error }}
</NotificationBar>
<!-- table --> <!-- table -->
<CardBox class="mb-6" has-table> <CardBox class="mb-6" has-table>

View File

@ -22,8 +22,12 @@ const props = defineProps({
// Define the computed property for the label // Define the computed property for the label
const computedLabel = computed(() => { const computedLabel = computed(() => {
return `Reject to ${props.dataset.user?.login || 'Unknown User'}`; return `Reject to submitter: ${props.dataset.user?.login || 'Unknown User'}`;
}); });
const computedEmailLabel = computed(() => {
return props.dataset.user?.email || '';
});
const flash: Ref<any> = computed(() => { const flash: Ref<any> = computed(() => {
return usePage().props.flash; return usePage().props.flash;
@ -35,6 +39,7 @@ const errors: Ref<any> = computed(() => {
const form = useForm({ const form = useForm({
server_state: 'rejected_editor', server_state: 'rejected_editor',
reject_editor_note: '', reject_editor_note: '',
send_email: false,
}); });
const handleSubmit = async (e: SubmitEvent) => { const handleSubmit = async (e: SubmitEvent) => {
@ -75,6 +80,24 @@ const handleSubmit = async (e: SubmitEvent) => {
</FormControl> </FormControl>
</FormField> </FormField>
<!-- <FormControl
type="checkbox"
v-model="form.send_email"
:error="form.errors.send_email">
</FormControl> -->
<FormField label="Email Notification">
<label for="send_email" class="flex items-center mr-6 mb-3">
<input type="checkbox" id="send_email" v-model="form.send_email" class="mr-2" />
<span class="check"></span>
<a class="pl-2 " target="_blank">send email to
<span class="text-blue-600 hover:underline">
{{ computedEmailLabel }}
</span>
</a>
</label>
</FormField>
<!-- <NotificationBar v-if="flash && flash.message" color="warning" :icon="mdiAlertBoxOutline"> <!-- <NotificationBar v-if="flash && flash.message" color="warning" :icon="mdiAlertBoxOutline">
{{ flash.message }} {{ flash.message }}

View File

@ -19,6 +19,12 @@ const props = defineProps({
default: () => ({}), default: () => ({}),
}, },
}); });
const computedLabel = computed(() => {
return `Reject to editor: ${props.dataset.editor?.login || ''}`;
});
const computedEmailLabel = computed(() => {
return props.dataset.editor?.email || '';
});
const flash: Ref<any> = computed(() => { const flash: Ref<any> = computed(() => {
return usePage().props.flash; return usePage().props.flash;
@ -30,9 +36,10 @@ const errors: Ref<any> = computed(() => {
const form = useForm({ const form = useForm({
server_state: 'rejected_reviewer', server_state: 'rejected_reviewer',
reject_reviewer_note: '', reject_reviewer_note: '',
send_email: false,
}); });
const handleSubmit = async (e) => { const handleSubmit = async (e: SubmitEvent) => {
e.preventDefault(); e.preventDefault();
await form.put(stardust.route('reviewer.dataset.rejectUpdate', [props.dataset.id])); await form.put(stardust.route('reviewer.dataset.rejectUpdate', [props.dataset.id]));
// await form.put(stardust.route('editor.dataset.update', [props.dataset.id])); // await form.put(stardust.route('editor.dataset.update', [props.dataset.id]));
@ -63,13 +70,25 @@ const handleSubmit = async (e) => {
</FormField> </FormField>
<FormField label="reject note" :class="{ 'text-red-400': form.errors.reject_reviewer_note }"> <FormField label="reject note" :class="{ 'text-red-400': form.errors.reject_reviewer_note }">
<FormControl v-model="form.reject_reviewer_note" type="textarea" <FormControl v-model="form.reject_reviewer_note" type="textarea"
placeholder="-- reject note for editor --" :error="form.errors.reject_reviewer_note"> :placeholder="`-- reject note for editor ${dataset.editor.login}--`" :error="form.errors.reject_reviewer_note">
<div class="text-red-400 text-sm" v-if="form.errors.reject_reviewer_note"> <div class="text-red-400 text-sm" v-if="form.errors.reject_reviewer_note">
{{ form.errors.reject_reviewer_note }} {{ form.errors.reject_reviewer_note }}
</div> </div>
</FormControl> </FormControl>
</FormField> </FormField>
<FormField label="Email Notification">
<label for="send_email" class="flex items-center mr-6 mb-3">
<input type="checkbox" id="send_email" v-model="form.send_email" class="mr-2" />
<span class="check"></span>
<a class="pl-2 " target="_blank">send email to
<span class="text-blue-600 hover:underline">
{{ computedEmailLabel }}
</span>
</a>
</label>
</FormField>
<!-- <NotificationBar v-if="flash && flash.message" color="warning" :icon="mdiAlertBoxOutline"> <!-- <NotificationBar v-if="flash && flash.message" color="warning" :icon="mdiAlertBoxOutline">
{{ flash.message }} {{ flash.message }}
@ -84,7 +103,7 @@ const handleSubmit = async (e) => {
<template #footer> <template #footer>
<BaseButtons> <BaseButtons>
<BaseButton type="submit" color="info" label="Reject to editor" <BaseButton type="submit" color="info" :label="computedLabel"
:class="{ 'opacity-25': form.processing }" :disabled="form.processing" /> :class="{ 'opacity-25': form.processing }" :disabled="form.processing" />
</BaseButtons> </BaseButtons>
</template> </template>

View File

@ -31,7 +31,7 @@ declare module '@adonisjs/core/http' {
// this.ctx!.session.flash(key, message); // this.ctx!.session.flash(key, message);
// return this; // return this;
// }); // });
Response.macro('flash', function (this: Response, message: any, key?: string,) { Response.macro('flash', function (this: Response, message: any, key?: string) {
if (!this.ctx) { if (!this.ctx) {
throw new Error('Context is not available'); throw new Error('Context is not available');
} }
@ -51,7 +51,7 @@ Response.macro('toRoute', function (this: Response, route: string) {
declare module '@adonisjs/core/http' { declare module '@adonisjs/core/http' {
interface Response { interface Response {
flash(message: any): Response; flash(message: any): Response;
flash(key: string, message: any): Response; flash(message: any, key: string): Response;
toRoute(route: string): Response; toRoute(route: string): Response;
} }
} }