diff --git a/cms/config/plugins.js b/cms/config/plugins.js index 7d2ea5f1214d3e8ac1a1a247e34d4032e4a8e4e8..e95ee17caba1ec3549fd32801e78c6352edce7c9 100644 --- a/cms/config/plugins.js +++ b/cms/config/plugins.js @@ -2,9 +2,12 @@ module.exports = ({ env }) => ({ email: { config: { provider: 'sendmail', + providerOptions: { + smtpHost: 'localhost' + }, settings: { - defaultFrom: 'No-Reply <no-reply@ddcode.org>', - defaultReplyTo: 'No-Reply <no-reply@ddcode.org>', + defaultFrom: 'DDCode <ddcode@mpi-cbg.de>', + defaultReplyTo: 'DDCode <ddcode@mpi-cbg.de>', }, }, }, diff --git a/cms/package.json b/cms/package.json index fc96853ba0f5b38c50b73b7b1cce352ed707af17..6466d3d6e8a9cb3145608d752f07b56121c0dd7e 100644 --- a/cms/package.json +++ b/cms/package.json @@ -11,10 +11,10 @@ }, "devDependencies": {}, "dependencies": { - "@strapi/plugin-i18n": "4.0.7", - "@strapi/plugin-users-permissions": "4.0.7", - "@strapi/strapi": "4.0.7", - "@strapi/provider-email-sendmail": "4.0.7", + "@strapi/plugin-i18n": "4.1.5", + "@strapi/plugin-users-permissions": "4.1.5", + "@strapi/strapi": "4.1.5", + "@strapi/provider-email-sendmail": "4.1.5", "pg": "^8.7.1", "sqlite3": "5.0.2" }, diff --git a/cms/src/api/password/controllers/password.js b/cms/src/api/password/controllers/password.js new file mode 100644 index 0000000000000000000000000000000000000000..a2f724062c4dc8ae454b0fef10c23c4cb35bdca2 --- /dev/null +++ b/cms/src/api/password/controllers/password.js @@ -0,0 +1,119 @@ +'use strict'; + +const { sanitize } = require('@strapi/utils'); +const formatError = error => [ + { messages: [{ id: error.id, message: error.message, field: error.field }] }, +]; + +module.exports = { + index: async ctx => { + // Get posted params + // const params = JSON.parse(ctx.request.body); //if post raw object using Postman + const params = ctx.request.body; + + // The identifier is required. + if (!params.identifier) { + return ctx.badRequest( + null, + formatError({ + id: 'Auth.form.error.email.provide', + message: 'Please provide your username or your e-mail.', + }) + ); + } + + // The password is required. + if (!params.password) { + return ctx.badRequest( + null, + formatError({ + id: 'Auth.form.error.password.provide', + message: 'Please provide your password.', + }) + ); + } + + // The new password is required. + if (!params.newPassword) { + return ctx.badRequest( + null, + formatError({ + id: 'Auth.form.error.password.provide', + message: 'Please provide your new password.', + }) + ); + } + + // The new password confirmation is required. + if (!params.confirmPassword) { + return ctx.badRequest( + null, + formatError({ + id: 'Auth.form.error.password.provide', + message: 'Please provide your new password confirmation.', + }) + ); + } + + if ( + params.newPassword && + params.confirmPassword && + params.newPassword !== params.confirmPassword + ) { + return ctx.badRequest( + null, + formatError({ + id: 'Auth.form.error.password.matching', + message: 'New Passwords do not match.', + }) + ); + } else if ( + params.newPassword && + params.confirmPassword && + params.newPassword === params.confirmPassword + ) { + + // console.log(params); + + // Get User based on identifier + const user = await strapi.db.query('plugin::users-permissions.user').findOne({where: { id: params.identifier }}); + + // console.log(user); + + // Validate given password against user query result password + const validPassword = await strapi.plugins[ + 'users-permissions' + ].services.user.validatePassword(params.password, user.password); + + if (!validPassword) { + return ctx.badRequest( + null, + formatError({ + id: 'Auth.form.error.invalid', + message: 'Identifier or password invalid.', + }) + ); + } else { + const bcrypt = require('bcryptjs'); + + // Generate new hash password + const password = await bcrypt.hash(params.newPassword, 10); + + // Update user password + await strapi.db.query('plugin::users-permissions.user').update({ + where: { id: user.id }, + data: { resetPasswordToken: null, password } + }); + + // Return new jwt token + // console.log(user.toJSON) + ctx.send({ + jwt: strapi.plugins['users-permissions'].services.jwt.issue({ + id: user.id, + }), + user: await sanitize.contentAPI.output(user), + }); + } + } + } +}; diff --git a/cms/src/api/password/routes/password.js b/cms/src/api/password/routes/password.js new file mode 100644 index 0000000000000000000000000000000000000000..8d6aebfbd6c7367cb481f3ca1707a5da81d916ce --- /dev/null +++ b/cms/src/api/password/routes/password.js @@ -0,0 +1,9 @@ +module.exports = { + routes: [ + { + "method": "POST", + "path": "/password", + "handler": "password.index" + } + ] +} diff --git a/web/src/components/CMS/addDeleteMarker.vue b/web/src/components/CMS/addDeleteMarker.vue index fffdb80146ab9c281a2219f30d0a5c70d3cf743a..f90c8b27830d902de25be6b6c60206cf69da1e3a 100644 --- a/web/src/components/CMS/addDeleteMarker.vue +++ b/web/src/components/CMS/addDeleteMarker.vue @@ -1,9 +1,6 @@ <template> <div> - <base-toaster - :open="toasterIsOpen" - @close="hideDialog" - > + <base-toaster :open="toasterIsOpen" @close="hideDialog"> <div class="flex justify-between items-center"> <font-awesome-icon class="ml-3" @@ -12,14 +9,8 @@ /> <h4>Request submitted successfully!</h4> - <button - class="btn btn-outline" - @click="hideDialog" - > - <font-awesome-icon - icon="fa-regular fa-circle-xmark" - size="2x" - /> + <button class="btn btn-outline" @click="hideDialog"> + <font-awesome-icon icon="fa-regular fa-circle-xmark" size="2x" /> </button> </div> </base-toaster> @@ -35,14 +26,10 @@ <div v-if="isLoading"> <base-spinner /> </div> - <div - v-if="markerData" - class="form-group" - > - <label - class="control-label col-sm-2" - for="actionType" - >Select Action</label> + <div v-if="markerData" class="form-group"> + <label class="control-label col-sm-2" for="actionType" + >Select Action</label + > <div class="col-sm-10"> <select id="actionType" @@ -50,11 +37,7 @@ class="form-control" @change="selectValue" > - <option - v-for="item in actionOptions" - :key="item" - class="" - > + <option v-for="item in actionOptions" :key="item" class=""> {{ item }} </option> </select> @@ -62,10 +45,9 @@ </div> <div v-if="showAddMarker || !markerData"> <div class="form-group"> - <label - class="control-label col-sm-2" - for="marker" - >Add a Marker</label> + <label class="control-label col-sm-2" for="marker" + >Add a Marker</label + > <div class="col-sm-10"> <div class="panel panel-default"> <div class="panel-body"> @@ -75,7 +57,7 @@ class="form-control" type="text" placeholder="Uniprot ID" - > + /> <div class="overflow-auto h-60"> <div v-for="(item, index) in filteredAddSearch" @@ -88,11 +70,8 @@ type="radio" :value="item" @click="selectMarker" - > - <label - class="control-label" - :for="index" - > + /> + <label class="control-label" :for="index"> {{ item }} </label> </div> @@ -108,34 +87,33 @@ </div> </div> <div class="form-group"> - <label - class="control-label col-sm-2" - for="markerComment" - >Reason</label> + <label class="control-label col-sm-2" for="markerComment" + >Reason</label + > <div class="col-sm-10"> <textarea id="markerComment" v-model.trim="comment" class=" - form-control - block - px-3 - py-1.5 - text-base - font-normal - text-gray-700 - bg-white bg-clip-padding - border border-solid border-gray-300 - rounded - transition - ease-in-out - m-0 - focus:text-gray-700 - focus:bg-white - focus:border-blue-600 - focus:outline-none - " + form-control + block + px-3 + py-1.5 + text-base + font-normal + text-gray-700 + bg-white bg-clip-padding + border border-solid border-gray-300 + rounded + transition + ease-in-out + m-0 + focus:text-gray-700 + focus:bg-white + focus:border-blue-600 + focus:outline-none + " rows="5" :placeholder=" role === 'Maintainer' ? 'Optional.' : 'Mandatory.' @@ -163,7 +141,7 @@ class="form-control" type="text" placeholder="Uniprot ID" - > + /> <div class="overflow-auto h-60"> <div v-for="(item, index) in filteredDeleteSearch" @@ -176,11 +154,8 @@ type="radio" :value="item" @click="selectMarker" - > - <label - class="control-label" - :for="index" - > + /> + <label class="control-label" :for="index"> {{ item }} </label> </div> @@ -196,34 +171,33 @@ </div> </div> <div class="form-group -mt-9"> - <label - class="control-label col-sm-2" - for="funComment" - >Reason</label> + <label class="control-label col-sm-2" for="funComment" + >Reason</label + > <div class="col-sm-10"> <textarea id="funComment" v-model.trim="comment" class=" - form-control - block - px-3 - py-1.5 - text-base - font-normal - text-gray-700 - bg-white bg-clip-padding - border border-solid border-gray-300 - rounded - transition - ease-in-out - m-0 - focus:text-gray-700 - focus:bg-white - focus:border-blue-600 - focus:outline-none - " + form-control + block + px-3 + py-1.5 + text-base + font-normal + text-gray-700 + bg-white bg-clip-padding + border border-solid border-gray-300 + rounded + transition + ease-in-out + m-0 + focus:text-gray-700 + focus:bg-white + focus:border-blue-600 + focus:outline-none + " rows="5" :placeholder=" role === 'Maintainer' ? 'Is optional.' : 'Is mandatory.' @@ -239,10 +213,7 @@ </div> </div> </div> - <div - v-if="showBtn || !markerData" - class="form-group" - > + <div v-if="showBtn || !markerData" class="form-group"> <div class="col-sm-2" /> <div class="col-sm-10"> <p @@ -255,10 +226,18 @@ </p> <div class="flex space-x-4"> <button - :class=" - actionType === 'Add' || !markerData - ? 'btn btn-success mb-2' - : 'btn btn-danger mb-2' + class=" + text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold " type="submit" :disabled="!marker || (!comment && role !== 'Maintainer')" @@ -266,11 +245,21 @@ {{ actionType === "Add" || !markerData ? "Add" : "Delete" }} </button> <button - class="btn btn-primary mb-2" + class=" + bg-white + hover:bg-gray-200 + focus:ring-2 focus:ring-gray-300 + rounded-lg + border border-gray-300 + px-5 + py-2.5 + hover:text-gray-900 + font-bold + " type="button" @click="close" > - Back + Cancel </button> </div> </div> @@ -283,8 +272,8 @@ </template> <script> -import BaseSpinner from '../UI/BaseSpinner.vue'; -import BaseToaster from '../UI/BaseToaster.vue' +import BaseSpinner from "../UI/BaseSpinner.vue"; +import BaseToaster from "../UI/BaseToaster.vue"; const _ = require("lodash"); let host = require("../js/const").apiHost; export default { @@ -333,7 +322,7 @@ export default { }, methods: { - showDialog() { + showDialog() { this.toasterIsOpen = true; }, hideDialog() { @@ -410,22 +399,24 @@ export default { Entity: "condensate", EntityId: this.condensateId, ChangeOperation: "add", - Attribute: "proteins", + Attribute: "biomarkers", Value: this.marker, - SubmissionComments: "Maintainer do not need to provide a reason for such change at the moment!", + SubmissionComments: + "Maintainer do not need to provide a reason for such change at the moment!", Status: "accepted", }; } else { if (this.comment === "" || this.comment.length < 50) { this.error = true; - this.commentError = "Reason should not be empty or less than 50 characters!"; + this.commentError = + "Reason should not be empty or less than 50 characters!"; return; } data = { Entity: "condensate", EntityId: this.condensateId, ChangeOperation: "add", - Attribute: "proteins", + Attribute: "biomarkers", Value: this.marker, SubmissionComments: this.comment, Status: "requested", @@ -452,22 +443,24 @@ export default { Entity: "condensate", EntityId: this.condensateId, ChangeOperation: "add", - Attribute: "proteins", + Attribute: "biomarkers", Value: this.marker, - SubmissionComments: "Maintainer do not need to provide a reason for such change at the moment!", + SubmissionComments: + "Maintainer do not need to provide a reason for such change at the moment!", Status: "accepted", }; } else { if (this.comment === "" || this.comment.length < 50) { this.error = true; - this.commentError = "Reason should not be empty or less than 50 characters!"; + this.commentError = + "Reason should not be empty or less than 50 characters!"; return; } data = { Entity: "condensate", EntityId: this.condensateId, ChangeOperation: "add", - Attribute: "proteins", + Attribute: "biomarkers", Value: this.marker, SubmissionComments: this.comment, Status: "requested", @@ -494,22 +487,24 @@ export default { Entity: "condensate", EntityId: this.condensateId, ChangeOperation: "remove", - Attribute: "proteins", + Attribute: "biomarkers", Value: this.marker, - SubmissionComments: "Maintainer do not need to provide a reason for such change at the moment!", + SubmissionComments: + "Maintainer do not need to provide a reason for such change at the moment!", Status: "accepted", }; } else { if (this.comment === "" || this.comment.length < 50) { this.error = true; - this.commentError = "Reason should not be empty or less than 50 characters!"; + this.commentError = + "Reason should not be empty or less than 50 characters!"; return; } data = { Entity: "condensate", EntityId: this.condensateId, ChangeOperation: "remove", - Attribute: "proteins", + Attribute: "biomarkers", Value: this.marker, SubmissionComments: this.comment, Status: "requested", @@ -517,7 +512,7 @@ export default { } } console.log(data); - this.isLoading=true; + this.isLoading = true; try { await this.axios.post( url, @@ -528,9 +523,8 @@ export default { }, } ); - this.isLoading=false; - this.toasterIsOpen= true - + this.isLoading = false; + this.toasterIsOpen = true; this.comment = ""; this.error = false; diff --git a/web/src/components/CMS/addDeletePubmed.vue b/web/src/components/CMS/addDeletePubmed.vue index adf542ef200070fb39940439feb5e43228548276..d47ab112dbacb87bc447fbf690623b5a746c7b21 100644 --- a/web/src/components/CMS/addDeletePubmed.vue +++ b/web/src/components/CMS/addDeletePubmed.vue @@ -1,9 +1,6 @@ <template> <div> - <base-toaster - :open="toasterIsOpen" - @close="hideDialog" - > + <base-toaster :open="toasterIsOpen" @close="hideDialog"> <div class="flex justify-between items-center"> <font-awesome-icon class="ml-3" @@ -12,14 +9,8 @@ /> <h4>Request submitted successfully!</h4> - <button - class="btn btn-outline" - @click="hideDialog" - > - <font-awesome-icon - icon="fa-regular fa-circle-xmark" - size="2x" - /> + <button class="btn btn-outline" @click="hideDialog"> + <font-awesome-icon icon="fa-regular fa-circle-xmark" size="2x" /> </button> </div> </base-toaster> @@ -36,10 +27,9 @@ </div> <div v-if="mode === 'condensate' && type === 'Add'"> <div class="form-group"> - <label - class="control-label col-sm-4" - for="pubId" - >Enter pubmed ID</label> + <label class="control-label col-sm-4" for="pubId" + >Enter pubmed ID</label + > <div class="col-sm-8"> <input id="pubId" @@ -48,44 +38,40 @@ type="text" placeholder="Pubmed ID" @keyup="validation" - > - <p - v-if="!isValid && error" - class="text-red-600 mt-4 font-bold" - > + /> + <p v-if="!isValid && error" class="text-red-600 mt-4 font-bold"> {{ error }} </p> </div> </div> <div class="form-group"> - <label - class="control-label col-sm-4" - for="funComment" - >Reason</label> + <label class="control-label col-sm-4" for="funComment" + >Reason</label + > <div class="col-sm-8"> <textarea id="funComment" v-model.trim="comment" class=" - form-control - block - px-3 - py-1.5 - text-base - font-normal - text-gray-700 - bg-white bg-clip-padding - border border-solid border-gray-300 - rounded - transition - ease-in-out - m-0 - focus:text-gray-700 - focus:bg-white - focus:border-blue-600 - focus:outline-none - " + form-control + block + px-3 + py-1.5 + text-base + font-normal + text-gray-700 + bg-white bg-clip-padding + border border-solid border-gray-300 + rounded + transition + ease-in-out + m-0 + focus:text-gray-700 + focus:bg-white + focus:border-blue-600 + focus:outline-none + " rows="5" :placeholder=" role === 'Maintainer' ? 'Optional.' : 'Mandatory' @@ -114,18 +100,51 @@ </p> <div class="flex space-x-4"> <button - class="btn btn-success mb-2" + :class=" + disableButton + ? ` text-white + bg-blue-300 + + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold` + : `text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold` + " type="submit" :disabled="disableButton" > Add </button> <button - class="btn btn-primary mb-2" + class=" + bg-white + hover:bg-gray-200 + focus:ring-2 focus:ring-gray-300 + rounded-lg + border border-gray-300 + px-5 + py-2.5 + hover:text-gray-900 + font-bold + " type="button" @click="closeAddPubmed" > - Back + Cancel </button> </div> </div> @@ -148,11 +167,8 @@ type="radio" :value="item" @click="selectPubmedId" - > - <label - class="control-label" - :for="index" - > + /> + <label class="control-label" :for="index"> {{ item }} </label> </div> @@ -161,44 +177,40 @@ </div> </div> <div class="form-group -mt-9"> - <label - class="control-label col-sm-4" - for="funComment" - >Reason</label> + <label class="control-label col-sm-4" for="funComment" + >Reason</label + > <div class="col-sm-8"> <textarea id="funComment" v-model.trim="comment" class=" - form-control - block - px-3 - py-1.5 - text-base - font-normal - text-gray-700 - bg-white bg-clip-padding - border border-solid border-gray-300 - rounded - transition - ease-in-out - m-0 - focus:text-gray-700 - focus:bg-white - focus:border-blue-600 - focus:outline-none - " + form-control + block + px-3 + py-1.5 + text-base + font-normal + text-gray-700 + bg-white bg-clip-padding + border border-solid border-gray-300 + rounded + transition + ease-in-out + m-0 + focus:text-gray-700 + focus:bg-white + focus:border-blue-600 + focus:outline-none + " rows="5" :placeholder=" role === 'Maintainer' ? 'Optional.' : 'Mandatory' " @keyup="validateComment" /> - <p - v-if="!isValid && error" - class="text-red-600 mt-4 font-bold" - > + <p v-if="!isValid && error" class="text-red-600 mt-4 font-bold"> {{ error }} </p> <p @@ -223,31 +235,60 @@ </p> <div class="flex space-x-4"> <button - class="btn btn-danger mb-2" + :class=" + btnDisabled + ? ` text-white + bg-blue-300 + + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold` + : `text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold` + " type="submit" :disabled="!pubmedId || (!comment && role !== 'Maintainer')" > Delete </button> <button - class="btn btn-primary mb-2" + class=" + bg-white + hover:bg-gray-200 + focus:ring-2 focus:ring-gray-300 + rounded-lg + border border-gray-300 + px-5 + py-2.5 + hover:text-gray-900 + font-bold + " type="button" @click="closeDeletePubmed" > - Back + Cancel </button> </div> </div> </div> </div> - <div - v-if="mode === 'protein'" - class="form-group" - > - <label - class="control-label col-sm-4" - for="actionType" - >Select Action</label> + <div v-if="mode === 'protein'" class="form-group"> + <label class="control-label col-sm-4" for="actionType" + >Select Action</label + > <div class="col-sm-8"> <div> <select @@ -256,11 +297,7 @@ class="form-control" @change="selectValue" > - <option - v-for="item in actionOptions" - :key="item" - class="" - > + <option v-for="item in actionOptions" :key="item" class=""> {{ item }} </option> </select> @@ -270,10 +307,9 @@ <div v-if="showAddPubmedId"> <div class="form-group"> - <label - class="control-label col-sm-4" - for="pubId" - >Enter pubmed ID</label> + <label class="control-label col-sm-4" for="pubId" + >Enter pubmed ID</label + > <div class="col-sm-8"> <input id="pubId" @@ -282,44 +318,40 @@ type="text" placeholder="Pubmed ID" @keyup="validation" - > - <p - v-if="!isValid && error" - class="text-red-600 mt-4 font-bold" - > + /> + <p v-if="!isValid && error" class="text-red-600 mt-4 font-bold"> {{ error }} </p> </div> </div> <div class="form-group"> - <label - class="control-label col-sm-4" - for="funComment" - >Reason</label> + <label class="control-label col-sm-4" for="funComment" + >Reason</label + > <div class="col-sm-8"> <textarea id="funComment" v-model.trim="comment" class=" - form-control - block - px-3 - py-1.5 - text-base - font-normal - text-gray-700 - bg-white bg-clip-padding - border border-solid border-gray-300 - rounded - transition - ease-in-out - m-0 - focus:text-gray-700 - focus:bg-white - focus:border-blue-600 - focus:outline-none - " + form-control + block + px-3 + py-1.5 + text-base + font-normal + text-gray-700 + bg-white bg-clip-padding + border border-solid border-gray-300 + rounded + transition + ease-in-out + m-0 + focus:text-gray-700 + focus:bg-white + focus:border-blue-600 + focus:outline-none + " rows="5" :placeholder=" role === 'Maintainer' ? 'Optional.' : 'Mandatory' @@ -352,11 +384,8 @@ type="radio" :value="item" @click="selectPubmedId" - > - <label - class="control-label" - :for="index" - > + /> + <label class="control-label" :for="index"> {{ item }} </label> </div> @@ -365,44 +394,40 @@ </div> </div> <div class="form-group -mt-9"> - <label - class="control-label col-sm-4" - for="funComment" - >Reason</label> + <label class="control-label col-sm-4" for="funComment" + >Reason</label + > <div class="col-sm-8"> <textarea id="funComment" v-model.trim="comment" class=" - form-control - block - px-3 - py-1.5 - text-base - font-normal - text-gray-700 - bg-white bg-clip-padding - border border-solid border-gray-300 - rounded - transition - ease-in-out - m-0 - focus:text-gray-700 - focus:bg-white - focus:border-blue-600 - focus:outline-none - " + form-control + block + px-3 + py-1.5 + text-base + font-normal + text-gray-700 + bg-white bg-clip-padding + border border-solid border-gray-300 + rounded + transition + ease-in-out + m-0 + focus:text-gray-700 + focus:bg-white + focus:border-blue-600 + focus:outline-none + " rows="5" :placeholder=" role === 'Maintainer' ? 'Optional.' : 'Mandatory' " @keyup="validateComment" /> - <p - v-if="!isValid && error" - class="text-red-600 mt-4 font-bold" - > + <p v-if="!isValid && error" class="text-red-600 mt-4 font-bold"> {{ error }} </p> <p @@ -423,21 +448,55 @@ <div class="col-sm-8"> <p :class=" - serverError ? 'text-danger font-bold' : 'text-success font-bold' + serverError + ? 'text-danger font-bold' + : 'text-success font-bold' " > {{ message }} </p> <div class="flex space-x-4"> <button - class="btn btn-primary mb-2" + :class=" + btnDisabled + ? ` text-white + bg-blue-300 + + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold` + : `text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold` + " type="submit" - :disabled="!pubmedId || (!comment && role !== 'Maintainer')" > {{ actionType === "Add" ? "Add" : "Remove" }} </button> <button - class="btn btn-danger mb-2" + class=" + bg-white + hover:bg-gray-200 + focus:ring-2 focus:ring-gray-300 + rounded-lg + border border-gray-300 + px-5 + py-2.5 + hover:text-gray-900 + font-bold + " type="button" @click="close" > @@ -453,8 +512,8 @@ </template> <script> -import BaseSpinner from '../UI/BaseSpinner.vue'; -import BaseToaster from '../UI/BaseToaster.vue'; +import BaseSpinner from "../UI/BaseSpinner.vue"; +import BaseToaster from "../UI/BaseToaster.vue"; //import BaseToaster from '../UI/BaseToaster.vue' const _ = require("lodash"); let host = require("../js/const").apiHost; @@ -485,6 +544,13 @@ export default { }; }, computed: { + btnDisabled() { + if (!this.pubmedId || (!this.comment && this.role !== "Maintainer")) { + console.log("true in protein page"); + return true; + } + return false; + }, jwt: function () { return this.$store.getters["User/jwt"]; }, @@ -603,13 +669,15 @@ export default { ChangeOperation: "add", Attribute: "pubmed_ids", Value: this.pubmedId, - SubmissionComments: "Maintainer do not need to provide a reason for such change at the moment!", + SubmissionComments: + "Maintainer do not need to provide a reason for such change at the moment!", Status: "accepted", }; } else { - if (this.comment === "" || this.comment.length<50) { + if (this.comment === "" || this.comment.length < 50) { this.isValid = false; - this.commentError = "Reason should not be empty or less than 50 character!"; + this.commentError = + "Reason should not be empty or less than 50 character!"; return false; } @@ -642,16 +710,18 @@ export default { data = { Entity: "condensate_protein", EntityId: entityId, - ChangeOperation: "add", + ChangeOperation: "remove", Attribute: "pubmed_ids", Value: this.pubmedId, - SubmissionComments: "Maintainer do not need to provide a reason for such change at the moment!", + SubmissionComments: + "Maintainer do not need to provide a reason for such change at the moment!", Status: "accepted", }; } else { - if (this.comment === "" || this.comment.length<50) { + if (this.comment === "" || this.comment.length < 50) { this.isValid = false; - this.commentError = "Reason should not be empty or less than 50 character!"; + this.commentError = + "Reason should not be empty or less than 50 character!"; return false; } @@ -677,13 +747,15 @@ export default { ChangeOperation: "add", Attribute: "proteins", Value: this.pubmedId, - SubmissionComments: "Maintainer do not need to provide a reason for such change at the moment!", + SubmissionComments: + "Maintainer do not need to provide a reason for such change at the moment!", Status: "accepted", }; } else { - if (this.comment === "" || this.comment.length<50) { + if (this.comment === "" || this.comment.length < 50) { this.isValid = false; - this.commentError = "Reason should not be empty or less than 50 character!"; + this.commentError = + "Reason should not be empty or less than 50 character!"; return false; } @@ -714,13 +786,15 @@ export default { ChangeOperation: "remove", Attribute: "proteins", Value: this.pubmedId, - SubmissionComments: "Maintainer do not need to provide a reason for such change at the moment!", + SubmissionComments: + "Maintainer do not need to provide a reason for such change at the moment!", Status: "accepted", }; } else { - if (this.comment === "" || this.comment.length<50) { + if (this.comment === "" || this.comment.length < 50) { this.isValid = false; - this.commentError = "Reason should not be empty or less than 50 character!"; + this.commentError = + "Reason should not be empty or less than 50 character!"; return false; } @@ -739,9 +813,9 @@ export default { this.isValid = true; this.error = ""; this.message = ""; - console.log("pass from", data); - this.isLoading= true; - this.toasterIsOpen= true; + + this.isLoading = true; + this.toasterIsOpen = true; try { await this.axios.post( url, @@ -752,8 +826,8 @@ export default { }, } ); - this.isLoading=false; - + this.isLoading = false; + this.isValid = true; this.comment = ""; this.error = ""; diff --git a/web/src/components/CMS/evidenceStarRating.vue b/web/src/components/CMS/evidenceStarRating.vue index 7eeed7554a0d7cd763e03b54b65693c9cec4c02f..1d7d446cb8e56766ae1a97c223b2aaecef0c5f3c 100644 --- a/web/src/components/CMS/evidenceStarRating.vue +++ b/web/src/components/CMS/evidenceStarRating.vue @@ -1,9 +1,6 @@ <template> <section> - <base-toaster - :open="toasterIsOpen" - @close="hideDialog" - > + <base-toaster :open="toasterIsOpen" @close="hideDialog"> <div class="flex justify-between items-center"> <font-awesome-icon class="ml-3" @@ -12,14 +9,8 @@ /> <h4>Request submitted successfully!</h4> - <button - class="btn btn-outline" - @click="hideDialog" - > - <font-awesome-icon - icon="fa-regular fa-circle-xmark" - size="2x" - /> + <button class="btn btn-outline" @click="hideDialog"> + <font-awesome-icon icon="fa-regular fa-circle-xmark" size="2x" /> </button> </div> </base-toaster> @@ -75,40 +66,37 @@ <button class=" text-white - font-bold - rounded-lg bg-blue-600 - hover:bg-blue-800 - focus:ring-4 focus:ring-blue-300 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 rounded-lg + inline-flex items-center px-5 - py-4 + py-2.5 text-center + font-bold mr-2 " @click="updateEvidenceStar" > - Save Changes + Update </button> <button class=" - text-white - font-bold + bg-white + hover:bg-gray-200 + focus:ring-2 focus:ring-gray-300 rounded-lg - bg-blue-600 - hover:bg-blue-800 - focus:ring-4 focus:ring-blue-300 - rounded-lg - items-center + border border-gray-300 px-5 - py-4 - text-center - mr-2 + py-2.5 + hover:text-gray-900 + font-bold " @click="close" > - Back + Cancel </button> </div> </div> diff --git a/web/src/components/CMS/fetchProfile.vue b/web/src/components/CMS/fetchProfile.vue index 57c7be7095e6fea3e3beecf49ad77db78d6508f8..fb20e2664b4be2f3d4fd3972fd0b8f0b480fb921 100644 --- a/web/src/components/CMS/fetchProfile.vue +++ b/web/src/components/CMS/fetchProfile.vue @@ -1,81 +1,102 @@ <template> <div> - <slot - :response="response" - :loading="loading" - /> + <slot :response="response" :loading="loading" :error="error" /> </div> </template> <script> // require modules -const _ = require('lodash'); -let host = require('../js/const').apiHost; +const _ = require("lodash"); +let host = require("../js/const").apiHost; export default { name: "FetchProfile", data() { return { loading: true, - response: '', - isDev: process.env.NODE_ENV === 'development' - } + response: "", + error: false, + isDev: process.env.NODE_ENV === "development", + }; }, computed: { jwt: function () { - return this.$store.getters['User/jwt'] + return this.$store.getters["User/jwt"]; }, }, mounted() { - const vm = this + const vm = this; // /* eslint-disable no-console */ // console.log(vm.locus); vm.getItems(); }, methods: { - getItems() { + async getItems() { const vm = this; // console.log(host) - if(vm.isDev) { - host = require('../js/const').devApiHost; + if (vm.isDev) { + host = require("../js/const").devApiHost; } // console.log(vm.isExperimental) let url = `${host}/api/users/me`; - const jwt = vm.jwt; - if(jwt === null) { + if (jwt === null) { vm.loading = false; vm.response = null; - return + return; } - fetch(url, { - method: 'GET', - mode: 'cors', - cache: 'no-cache', + const res = await fetch(url, { + method: "GET", + mode: "cors", + cache: "no-cache", headers: { - Authorization: `Bearer ${jwt}` - } - }) - .then(response => response.json()) - .then((response) => { - // /* eslint-disable no-console */ - //console.log(response); + "Content-Type": "application/json", + Authorization: `Bearer ${jwt}`, + }, + }); - setTimeout(() => { - vm.loading = false; - vm.response = response - }, 10); - }); - } + if (res.status >= 400 && res.status <= 499) { + vm.loading = false; + vm.error = true; + + vm.response = `${res.status}-${res.statusText} Client Error, please write a mail to DDCode Admin.`; + return; + } + if (res.status >= 500 && res.status <= 599) { + vm.loading = false; + vm.error = true; + + vm.response = `${res.status}-${res.statusText} Server Error, please write a mail to DDCode Admin.`; + return; + } + + vm.error = false; + const responseData = await res.json(); + + setTimeout(() => { + vm.loading = false; + vm.response = responseData; + }, 10); + + // .then(response => response.json()) + // .then((response) => { + // // /* eslint-disable no-console */ + // console.log(response); + + // setTimeout(() => { + // vm.loading = false; + // vm.response = response + // }, 10); + // }) + }, }, -} +}; </script> <style scoped> - </style> \ No newline at end of file diff --git a/web/src/components/CMS/updateFunctionalType.vue b/web/src/components/CMS/updateFunctionalType.vue index c99e0f7dfd4f62294b3cab2be7900684ad6f3142..0fdcb2e26cf51a65ea3e91aa8026649a1aa8c40d 100644 --- a/web/src/components/CMS/updateFunctionalType.vue +++ b/web/src/components/CMS/updateFunctionalType.vue @@ -1,9 +1,6 @@ <template> <div> - <base-toaster - :open="toasterIsOpen" - @close="hideDialog" - > + <base-toaster :open="toasterIsOpen" @close="hideDialog"> <div class="flex justify-between items-center"> <font-awesome-icon class="ml-3" @@ -12,14 +9,8 @@ /> <h4>Request submitted successfully!</h4> - <button - class="btn btn-outline" - @click="hideDialog" - > - <font-awesome-icon - icon="fa-regular fa-circle-xmark" - size="2x" - /> + <button class="btn btn-outline" @click="hideDialog"> + <font-awesome-icon icon="fa-regular fa-circle-xmark" size="2x" /> </button> </div> </base-toaster> @@ -34,10 +25,9 @@ <base-spinner /> </div> <div class="form-group"> - <label - class="control-label col-sm-4" - for="FuncType" - >Choose a functional type</label> + <label class="control-label col-sm-4" for="FuncType" + >Choose a functional type</label + > <div class="col-sm-8"> <div> <select @@ -62,10 +52,9 @@ </div> </div> <div class="form-group"> - <label - class="control-label col-sm-4" - for="funComment" - >Reason</label> + <label class="control-label col-sm-4" for="funComment" + >Reason</label + > <div class="col-sm-8"> <textarea @@ -95,10 +84,7 @@ @keyup="validateComment" /> - <p - v-if="!isValid" - class="text-danger mt-4 font-bold" - > + <p v-if="!isValid" class="text-danger mt-4 font-bold"> {{ error }} </p> <p @@ -113,13 +99,36 @@ <div class="flex space-x-4 mt-4"> <button - class="btn btn-primary mb-2" + class=" + text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold + mr-2 + " type="submit" > Update </button> <button - class="btn btn-danger mb-2" + class=" + bg-white + hover:bg-gray-200 + focus:ring-2 focus:ring-gray-300 + rounded-lg + border border-gray-300 + px-5 + py-2.5 + hover:text-gray-900 + font-bold + " type="button" @click="close" > diff --git a/web/src/components/CondensateDetailPage.vue b/web/src/components/CondensateDetailPage.vue index 7595e0e2b39ab8e54c2e88e0a668ca65694fc9eb..27a6ecf15c4475051775207a9b0631ca0a84f534 100644 --- a/web/src/components/CondensateDetailPage.vue +++ b/web/src/components/CondensateDetailPage.vue @@ -1,9 +1,6 @@ <template> <div> - <base-toaster - :open="toasterIsOpen" - @close="hideDialog" - > + <base-toaster :open="toasterIsOpen" @close="hideDialog"> <div class="flex justify-between items-center"> <font-awesome-icon class="ml-3" @@ -12,22 +9,13 @@ /> <h4>Request submitted successfully!</h4> - <button - class="btn btn-outline" - @click="hideDialog" - > - <font-awesome-icon - icon="fa-regular fa-circle-xmark" - size="2x" - /> + <button class="btn btn-outline" @click="hideDialog"> + <font-awesome-icon icon="fa-regular fa-circle-xmark" size="2x" /> </button> </div> </base-toaster> - - <div - id="wrapper" - class="d-flex" - > + + <div id="wrapper" class="d-flex"> <!-- Sidebar --> <!--<div class="bg-light border-right" id="sidebar-wrapper">--> <!--<!–<div class="sidebar-heading">Menu </div>–>--> @@ -41,16 +29,10 @@ <!--</div>--> <!-- /#sidebar-wrapper --> - <div - id="page-content-wrapper" - class="main" - > + <div id="page-content-wrapper" class="main"> <fetch-condensate :condensate="condensate"> <template slot-scope="{ response, loading }"> - <slot - :response="response" - :loading="loading" - > + <slot :response="response" :loading="loading"> <div v-if="loading || response === null"> <base-spinner /> </div> @@ -58,39 +40,175 @@ <base-spinner></base-spinner> </div> --> <div v-else> - <h2>{{ response.data.name }}</h2> - <h4 class="round"> - General Information - </h4> + <div class="flex space-x-4"> + <h2>{{ response.data.name }}</h2> + + <button + v-if=" + getUserData !== null && + (getUserData === 'Maintainer' || + getUserData === 'Contributor') + " + class="rounded-lg px-5 py-4 text-center" + @click="toggleChangeName" + > + <font-awesome-icon + size="lg" + icon="fa-solid fa-pen-to-square fa-xl" + /> + </button> + </div> + + <div class="panel panel-default" v-if="changeName"> + <div class="panel-body"> + <form + class="form-horizontal" + @submit.prevent="changeCondensateName(response)" + > + <div class="form-group space-y-4"> + <div class="row"> + <label + for="condensateName" + class="control-label col-sm-2" + >Name</label + > + <div class="col-sm-10"> + <div class="w-1/4"> + <input + id="condensateName" + v-model.trim="condensateName" + class="form-control" + type="text" + placeholder="Enter condensate name." + @keyup="validateName" + /> + <p + v-if="nameError && nameErrMsg" + class="text-red-500 mt-2 text-2xl font-bold" + > + {{ nameErrMsg }} + </p> + </div> + </div> + </div> + + <div class="row"> + <label + for="condensateName" + class="control-label col-sm-2" + >Reason</label + > + <div class="col-sm-10"> + <div class="w-3/4"> + <textarea + v-model.trim="condensateNameComment" + class=" + form-control + block + px-3 + py-1.5 + text-base + font-normal + text-gray-700 + bg-white bg-clip-padding + border border-solid border-gray-300 + rounded + transition + ease-in-out + m-0 + focus:text-gray-700 + focus:bg-white + focus:border-blue-600 + focus:outline-none + " + rows="5" + :placeholder=" + getUserData === 'Maintainer' + ? 'Optional' + : 'Mandatory' + " + @keyup="descriptionKeyup" + /> + <p + v-if=" + nameCommentErr && condensateNameCommentErrMsg + " + class="text-red-500 mt-2 text-2xl font-bold" + > + {{ condensateNameCommentErrMsg }} + </p> + </div> + + <div class="flex space-x-4 mt-4"> + <button + type="submit" + class=" + text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold + " + > + Save + </button> + <button + @click="toggleChangeName" + class=" + bg-white + hover:bg-gray-200 + focus:ring-2 focus:ring-gray-300 + rounded-lg + border border-gray-300 + px-5 + py-2.5 + hover:text-gray-900 + font-bold + " + > + Cancel + </button> + </div> + </div> + </div> + </div> + </form> + </div> + </div> + <h4 class="round">General Information</h4> <div class="panel panel-default"> <div class="panel-body"> <div class="container-fluid col-md-12"> - <div class="row"> + <!-- <div class="row"> <div class="text col-sm-3"> Canonical ID </div> <div class="col-sm-9"> {{ response.data.canonical_id }} </div> - </div> + </div> --> <div class="row"> - <div class="text col-sm-3"> - Species - </div> + <div class="text col-sm-3">Species</div> <div class="col-sm-9"> - {{ response.data.species_name }} + {{ response.data.species_name }} ({{ + response.data.species_tax_id + }}) </div> </div> <div class="row"> - <div class="text col-sm-3"> - Description - </div> + <div class="text col-sm-3">Description</div> <div class="col-sm-9"> {{ response.data.description }} <button v-if=" getUserData !== null && - (getUserData === 'Maintainer' || + (getUserData === 'Maintainer' || getUserData === 'Contributor') " class="btn btn-primary btn-link" @@ -120,7 +238,8 @@ <label class="control-label col-sm-2" for="keyword" - >Update description</label> + >Update description</label + > <div class="col-sm-10"> <textarea id="comment" @@ -161,12 +280,13 @@ <label class="control-label col-sm-2" for="funComment" - >Reason</label> + >Reason</label + > <div class="col-sm-10"> <textarea id="funComment" - v-model.trim="comment" + v-model.trim="descriptionComment" class=" form-control block @@ -195,7 +315,7 @@ @keyup="commentKeyup" /> <p - v-if="isCommentError" + v-if="descriptionCommentErr" class="text-red-600 font-bold" > {{ commentErrorMsg }} @@ -209,18 +329,40 @@ <div class="flex space-x-4"> <button id="dropdownMenuButton" - class="btn btn-primary" + class=" + text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold + " type="submit" > Update </button> <button id="dropdownMenuButton" - class="btn btn-danger border" + class=" + bg-white + hover:bg-gray-200 + focus:ring-2 focus:ring-gray-300 + rounded-lg + border border-gray-300 + px-5 + py-2.5 + hover:text-gray-900 + font-bold + " type="button" @click="toggleUpdateDescription" > - cancel + Cancel </button> </div> </div> @@ -231,30 +373,18 @@ </div> </div> </div> - <!--<div class="row">--> - <!--<div class="text col-sm-3">Evidence Stars</div>--> - <!--<div class="col-sm-9 tooltipped tooltipped-w"--> - <!--:aria-label="getDbNames(response.data.data_sources)">--> - <!--<span v-for="(item, index) in getRating(response.data.data_sources)" :class="item" v-bind:key="index"/>--> - <!--</div>--> - <!--</div>--> - <div - v-show="response.data.synonyms" - class="row" - > - <div class="text col-sm-3"> - Also Known As - </div> + <div v-show="response.data.synonyms" class="row"> + <div class="text col-sm-3">Also Known As</div> <div class="col-sm-9"> {{ response.data.synonyms ? response.data.synonyms - .map((a) => - a - .replace("-", " ") - .replace(/\b\w/g, (l) => l.toUpperCase()) - ) - .join(", ") + .map((a) => + a + .replace("-", " ") + .replace(/\b\w/g, (l) => l.toUpperCase()) + ) + .join(", ") : "" }} </div> @@ -281,7 +411,7 @@ <button v-if=" getUserData !== null && - (getUserData === 'Maintainer' || + (getUserData === 'Maintainer' || getUserData === 'Contributor') " class="btn btn-primary btn-link" @@ -302,21 +432,33 @@ </div> <div class="row"> - <div class="text col-sm-3"> - No. of Proteins - </div> + <div class="text col-sm-3">No. of Proteins</div> <div class="col-sm-9"> {{ response.data.protein_count }} </div> </div> + <div class="row"> + <div class="text col-sm-3">Evidence star</div> + <div class="flex col-sm-9"> + <star-rating + :star-size="20" + :show-rating="false" + :rating=" + response.data.confidence_score + ? response.data.confidence_score + : '0' + " + :read-only="true" + :increment="0.01" + ></star-rating> + </div> + </div> </div> </div> </div> <div v-show="response.data.experiments.length > 0"> - <h4 class="round"> - Experiments - </h4> + <h4 class="round">Experiments</h4> <div class="panel panel-default"> <table class="csi table table-hover table-responsive"> <thead> @@ -347,10 +489,7 @@ <td> <fetch-pub-med :link="item.publication_link"> <template slot-scope="{ response, loading }"> - <slot - :response="response" - :loading="loading" - > + <slot :response="response" :loading="loading"> <div v-if="loading" /> <div v-else> <a @@ -402,26 +541,33 @@ </div> </div> - <h4 class="round"> - Proteins - </h4> + <h4 class="round">Proteins</h4> <button v-if=" getUserData !== null && - (getUserData === 'Maintainer' || + (getUserData === 'Maintainer' || getUserData === 'Contributor') " - class="btn btn-primary dropdown-toggle" + class=" + text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-3 + text-center + font-bold + " type="button" @click="showAddProtein = !showAddProtein" > Add a protein to this condensate </button> - <div - v-if="showAddProtein" - class="panel panel-default mt-4" - > + <div v-if="showAddProtein" class="panel panel-default mt-4"> <div class="panel-body"> <div class="container-fluid col-md-12"> <div v-if="isLoading"> @@ -433,10 +579,9 @@ @submit.prevent="addProtein(response)" > <div class="form-group"> - <label - class="control-label col-sm-2" - for="species" - >Add protein with uniprot ID</label> + <label class="control-label col-sm-2" for="species" + >Add protein with uniprot ID</label + > <div class="col-sm-4"> <input id="keyword" @@ -445,7 +590,7 @@ type="text" placeholder="Uniprot ID" @keyup="uniprotKeyup" - > + /> <p v-if="isUniProtIdError" class="text-red-600 mt-4 font-bold" @@ -455,14 +600,13 @@ </div> </div> <div class="form-group"> - <label - class="control-label col-sm-2" - for="keyword" - >Reason</label> + <label class="control-label col-sm-2" for="keyword" + >Reason</label + > <div class="col-sm-8"> <textarea id="comment" - v-model.trim="comment" + v-model.trim="proteinComment" class=" form-control block @@ -488,10 +632,10 @@ ? 'Optional.' : 'Mandatory.' " - @keyup="commentKeyup" + @keyup="validateProteinComment" /> <p - v-if="isCommentError" + v-if="proteinCommentErr" class="text-red-600 font-bold" > {{ commentErrorMsg }} @@ -509,18 +653,40 @@ <div class="flex space-x-4"> <button id="dropdownMenuButton" - class="btn btn-primary" + class=" + text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold + " type="submit" > Add </button> <button id="dropdownMenuButton" - class="btn btn-danger border" + class=" + bg-white + hover:bg-gray-200 + focus:ring-2 focus:ring-gray-300 + rounded-lg + border border-gray-300 + px-5 + py-2.5 + hover:text-gray-900 + font-bold + " type="button" @click="showAddProtein = !showAddProtein" > - cancel + Cancel </button> </div> </div> @@ -550,15 +716,12 @@ </template> </fetch-user-specific-update-items> --> <div v-if="getUserData"> - <h4 class="round"> - Requests - </h4> + <h4 class="round">Requests</h4> <condensate-update-items-table id="condensateUpdateItem" :data="response.data.canonical_id" /> </div> - <!-- <request-update-item-table id="protein-table" @@ -618,6 +781,7 @@ import AddDeleteMarker from "./CMS/addDeleteMarker.vue"; import BaseSpinner from "./UI/BaseSpinner.vue"; import CondensateUpdateItemsTable from "./CondensateUpdateItemsTable.vue"; import BaseToaster from "./UI/BaseToaster.vue"; +import StarRating from "vue-star-rating"; //import FetchUserSpecificUpdateItems from './CMS/fetchUserSpecificUpdateItems.vue'; //import RequestUpdateItemTable from "./RequestUpdateItemTable.vue"; @@ -637,6 +801,7 @@ export default { BaseSpinner, CondensateUpdateItemsTable, BaseToaster, + StarRating, // FetchUserSpecificUpdateItems, // RequestUpdateItemTable, @@ -652,8 +817,14 @@ export default { showAddProtein: false, uniprotId: "", comment: "", + proteinComment: "", + condensateNameComment: "", + condensateNameCommentErrMsg: "", + markerComment: "", + descriptionComment: "", description: "", descriptionMsg: "", + condensateName: "", descriptionErrorMsg: "", isUniProtIdError: false, isCommentError: false, @@ -662,12 +833,18 @@ export default { showUpdateDescription: false, showAddDeleteMarker: false, commentErrorMsg: "", + descriptionCommentErr: false, + nameCommentErr: false, + proteinCommentErr: false, + markerCommentErr: false, uniprotIdErrorMsg: "", message: "", whitespaceRegex: /(\s)/, + nameError: false, toggleModel: false, isLoading: false, toasterIsOpen: false, + changeName: false, }; }, computed: { @@ -682,6 +859,9 @@ export default { }, methods: { + validateName() { + (this.nameErrMsg = ""), (this.nameError = false); + }, showDialog() { this.toasterIsOpen = true; }, @@ -694,6 +874,10 @@ export default { cancel() { this.toggleModel = false; }, + toggleChangeName() { + this.changeName = !this.changeName; + this.condensateName = ""; + }, closeAddDeleteMarker() { this.comment = ""; this.showAddDeleteMarker = false; @@ -704,6 +888,7 @@ export default { }, commentKeyup() { this.message = ""; + this.descriptionCommentErr = false; this.isCommentError = false; }, descriptionKeyup() { @@ -714,9 +899,85 @@ export default { this.description = res; this.descriptionMsg = ""; this.descriptionErrorMsg = ""; - this.comment = ""; + this.descriptionCommentErr = false; + this.descriptionComment = ""; this.showUpdateDescription = !this.showUpdateDescription; }, + validateProteinComment() { + this.proteinCommentErr = false; + this.commentErrorMsg = ""; + }, + + async changeCondensateName(response) { + if (!this.condensateName) { + this.nameError = true; + this.nameErrMsg = "Enter a condensate name."; + return; + } + this.nameError = false; + this.nameErrMsg = ""; + console.log("name", this.condensateName); + if (this.isDev) { + host = require("./js/const").devApiHost; + } + let url = `${host}/api/update-items`; + let data; + if (this.getUserData === "Maintainer") { + data = { + Entity: "condensate", + EntityId: response.data.canonical_id, + ChangeOperation: "update", + Attribute: "name", + Value: this.condensateName, + SubmissionComments: + "Maintainer do not need to provide a reason for such change at the moment!", + Status: "accepted", + }; + } else { + if ( + this.condensateNameComment === "" || + this.condensateNameComment.length < 50 + ) { + this.nameCommentErr = true; + this.condensateNameCommentErrMsg = + "Reason should not be empty or less than 50 characters!"; + return; + } + data = { + Entity: "condensate", + EntityId: response.data.canonical_id, + ChangeOperation: "update", + Attribute: "name", + Value: this.condensateName, + SubmissionComments: this.condensateNameComment, + + Status: "requested", + }; + } + this.isLoading = true; + try { + await this.axios.post( + url, + { data: data }, + { + headers: { + Authorization: `Bearer ${this.jwt}`, + }, + } + ); + this.isLoading = false; + this.toasterIsOpen = true; + this.nameCommentErr = false; + this.condensateNameCommentErrMsg = ""; + this.condensateNameComment = ""; + this.condensateName = ""; + } catch (e) { + console.error(e); + this.nameCommentErr = true; + this.condensateNameCommentErrMsg = + e.message || "Something went wrong, please try again later!"; + } + }, async updateDescription(response) { if (this.description === "") { @@ -746,8 +1007,11 @@ export default { Status: "accepted", }; } else { - if (this.comment === "" || this.comment.length < 50) { - this.isCommentError = true; + if ( + this.descriptionComment === "" || + this.descriptionComment.length < 50 + ) { + this.descriptionCommentErr = true; this.commentErrorMsg = "Reason should not be empty or less than 50 characters!"; return; @@ -758,7 +1022,7 @@ export default { ChangeOperation: "update", Attribute: "description", Value: this.description, - SubmissionComments: this.comment, + SubmissionComments: this.descriptionComment, Status: "requested", }; } @@ -775,7 +1039,8 @@ export default { ); this.isLoading = false; this.toasterIsOpen = true; - + this.descriptionCommentErr = false; + this.descriptionComment = ""; this.descriptionErrorMsg = ""; this.description = response.data.description; this.commentErrorMsg = ""; @@ -836,10 +1101,11 @@ export default { Status: "accepted", }; } else { - if (this.comment === "" || this.comment.length < 50) { + if (this.proteinComment === "" || this.proteinComment.length < 50) { + this.proteinCommentErr = true; this.commentErrorMsg = "Reason should not be empty or less than 50 characters!"; - this.isCommentError = true; + return; } data = { @@ -848,7 +1114,7 @@ export default { ChangeOperation: "add", Attribute: "proteins", Value: this.uniprotId, - SubmissionComments: this.comment, + SubmissionComments: this.proteinComment, Status: "requested", }; } @@ -871,6 +1137,8 @@ export default { this.isSubmitted = true; this.uniprotId = ""; this.comment = ""; + this.proteinCommentErr = false; + this.commentErrorMsg = ""; } catch (e) { console.error(e); this.message = @@ -945,20 +1213,6 @@ export default { tokenize(input, token) { return input.replaceAll(token, "<br/>"); }, - getRating(data) { - const scoreMap = { hungarian: 5, blue: 5, pink: 1, grey: 1 }; - - const rating = _.max(_.map(data, (i) => scoreMap[i])); - const r = []; - for (let i = 0; i < 5; i++) { - if (i < rating) { - r.push("fa fa-star checked"); - } else { - r.push("fa fa-star"); - } - } - return r; - }, }, }; </script> @@ -969,6 +1223,16 @@ export default { @import url("~@/assets/datatable.css"); @import url("~@/assets/tooltip.css"); +/* .star-custom-text { + + font-size: 1.5em; + align-items: center; + padding-left: 10px; + padding-right: 10px; + + +} */ + .main { /*margin-left: 200px;*/ margin-left: 20px; diff --git a/web/src/components/CondensateUpdateItemsTable.vue b/web/src/components/CondensateUpdateItemsTable.vue index 0d40ec454a2012b6f2eb9b67ac143c2498fb5306..11815ef4d017e7c6168088bfe1754e66f8ce4f7c 100644 --- a/web/src/components/CondensateUpdateItemsTable.vue +++ b/web/src/components/CondensateUpdateItemsTable.vue @@ -61,6 +61,15 @@ export default { { title: "Resource ID", data: "attributes.EntityId", + fnCreatedCell: (nTd, sData, oData) => { + if (oData.attributes.Entity === 'protein') { + $(nTd).html(`<a href='/protein/${sData}'> ${sData}</a>`); + } else if (oData.attributes.Entity === 'condensate') { + $(nTd).html(`<a href='/condensate/${sData}'> ${sData}</a>`); + } else { + $(nTd).html(`${sData}`); + } + } }, { title: "Attribute", diff --git a/web/src/components/DataTable.vue b/web/src/components/DataTable.vue index 48aa99bd352a4e29124cd6fc0f00a5f8f8e69cac..c5a6f2dbb82ef54e800c4de3327538a202b3d0a7 100644 --- a/web/src/components/DataTable.vue +++ b/web/src/components/DataTable.vue @@ -10,24 +10,25 @@ </template> <script> + const _ = require('lodash'); const $ = window.jQuery = require('jquery'); require('./js/datatable'); let table; - function getRatingValue(data) { - return _.max(_.values(data)); - } - function getRating(data) { - const rating = _.max(_.values(data)); + const rating = data; const r = ['<div style="white-space: nowrap;">'] for (let i = 0; i < 5; i++) { if (i < rating) { - r.push('<span class="fa fa-star checked"/>') + if (rating - i === 0.5) { + r.push('<span class="fa fa-star-half-o checked"/>'); + } else { + r.push('<span class="fa fa-star checked"/>'); + } } else { - r.push('<span class="fa fa-star"/>') + r.push('<span class="fa fa-star"/>'); } } @@ -35,12 +36,16 @@ return r; } + //import StarRating from "vue-star-rating" + export default { name: 'DataTable', props: ['id', 'data', 'keyword', 'category'], + mounted() { const vm = this; vm.createTable(vm.id, vm.data); + console.log("in data table condensate data", vm.data); }, // watch: { // selectedItem: function update(newValue) { @@ -53,6 +58,7 @@ // } // }, // }, + methods: { moveDetailPage(id) { const vm = this; @@ -179,19 +185,22 @@ return data + ' (' + row.species_tax_id + ')'; } }, - // { - // title: 'Evidence Stars', - // data: 'protein_confidence_score', - // render: function ( data, type, row, meta ) { - // return getRatingValue(data); - // }, - // fnCreatedCell: (nTd, sData, oData) => { - // // console.log(sData) - // if(sData) { - // $(nTd).html(getRating(sData).join('\n')); - // } - // } - // }, + { + title: 'Evidence Stars', + data: 'confidence_score', + defaultContent:`0`, + render: function ( data, type, row, meta ) { + return data; + }, + fnCreatedCell: (nTd, sData, oData) => { + // console.log(sData) + if(sData) { + $(nTd).html(getRating(sData).join('\n')); + } else { + $(nTd).html(getRating(0).join('\n')); + } + } + }, // { // title: 'Localization', // render: function ( data, type, row, meta ) { diff --git a/web/src/components/DataTableCondensate.vue b/web/src/components/DataTableCondensate.vue index c2fa81a89de6bda9bec2cfab17c49cec23773bde..514374bc68130b31762f5375a3bb190b54df8b63 100644 --- a/web/src/components/DataTableCondensate.vue +++ b/web/src/components/DataTableCondensate.vue @@ -16,16 +16,16 @@ require('./js/datatable'); let table; -function getRatingValue(data) { - return _.max(_.values(data)); -} - function getRating(data) { - const rating = _.max(_.values(data)); + const rating = data; const r = ['<div style="white-space: nowrap;">']; for (let i = 0; i < 5; i++) { if (i < rating) { - r.push('<span class="fa fa-star checked"/>'); + if (rating - i === 0.5) { + r.push('<span class="fa fa-star-half-o checked"/>'); + } else { + r.push('<span class="fa fa-star checked"/>'); + } } else { r.push('<span class="fa fa-star"/>'); } @@ -44,6 +44,7 @@ export default { mounted() { const vm = this; vm.createTable(vm.id, vm.data); + // console.log("data table condensate", vm.data); }, methods: { moveDetailPage(id) { @@ -100,19 +101,7 @@ export default { // return '(placeholder)'; // } // }, - // { - // title: 'Evidence Stars', - // data: 'protein_confidence_score', - // render: function ( data, type, row, meta ) { - // return getRatingValue(data); - // }, - // fnCreatedCell: (nTd, sData, oData) => { - // // console.log(sData) - // if(sData) { - // $(nTd).html(getRating(sData).join('\n')); - // } - // } - // }, + { title: 'Species', data: 'species_name', @@ -124,6 +113,22 @@ export default { } }, }, + { + title: 'Evidence Stars', + data: 'confidence_score', + defaultContent: `0`, + render: function ( data, type, row, meta ) { + return data; + }, + fnCreatedCell: (nTd, sData, oData) => { + // console.log(sData) + if(sData) { + $(nTd).html(getRating(sData).join('\n')); + } else { + $(nTd).html(getRating(0).join('\n')); + } + } + }, ]; const nTableOptions = { diff --git a/web/src/components/LandingPage.vue b/web/src/components/LandingPage.vue index 4e200340ad8d7bb9720e8271f9a33038188bf06a..9f309bcc28f4aa0ef1324a34ee830e237bdaf8dc 100644 --- a/web/src/components/LandingPage.vue +++ b/web/src/components/LandingPage.vue @@ -1,45 +1,38 @@ <template> - <div - id="page-content-wrapper" - class="main" - > - <h2>Dresden Condensate Database and Encyclopedia {{ isDev?'(Dev version)':'' }}</h2> + <div id="page-content-wrapper" class="main"> + <h2> + Dresden Condensate Database and Encyclopedia + {{ isDev ? "(Dev version)" : "" }} + </h2> <p> <b> - DD-CODE is a comprehensive, manually curated database of biomolecular condensates and an encyclopedia of the scientific terms used to describe and characterize those condensates. + DD-CODE is a comprehensive, manually curated database of biomolecular + condensates and an encyclopedia of the scientific terms used to describe + and characterize those condensates. </b> - <br> - <br> - Please search for your proteins of interest! + <br /> + <br /> </p> - <form - class="form-horizontal" - autocomplete="off" - onSubmit="return false;" - > + <form class="form-horizontal" autocomplete="off" onSubmit="return false;"> <div class="form-group"> - <label - class="control-label col-sm-2" - for="species" - >Species</label> + <label class="control-label col-sm-2" for="species">Species</label> <div class="col-sm-4"> <fetch-species> - <template slot-scope="{response, loading}"> - <div v-if="loading"> - Loading... - </div> + <template slot-scope="{ response, loading }"> + <div v-if="loading">Loading...</div> <div v-else> <select id="species" v-model="species" class="form-control input-sm" > - <option - v-for="item in response.data" - :key="item.tax_id" - > - {{ item._id && item._id !== 0? `${item._id} (${item.tax_id})`: 'All' }} + <option v-for="item in removeNullItems(response.data)" :key="item.tax_id"> + {{ + item._id && item._id !== 0 + ? `${item._id} (${item.tax_id})` + : "All" + }} </option> </select> </div> @@ -48,15 +41,9 @@ </div> </div> <div class="form-group"> - <label - class="control-label col-sm-2" - for="keyword" - >Search</label> + <label class="control-label col-sm-2" for="keyword">Search</label> <div class="col-sm-4"> - <vue-simple-suggest - v-model="selected" - :list="getSuggestionList" - > + <vue-simple-suggest v-model="selected" :list="getSuggestionList"> <!-- Filter by input text to only show the matching results --> <input id="keyword" @@ -64,7 +51,7 @@ class="form-control input-sm" type="text" placeholder="Protein or Condensate name: " - > + /> </vue-simple-suggest> </div> </div> @@ -77,33 +64,44 @@ class="radio-inline" type="radio" value="protein" - > <label - class="radio-label" - for="protein" - >Protein</label> + /> + <label class="radio-label" for="protein">Protein</label> <input id="condensate" v-model="pick" class="radio-inline" type="radio" value="condensate" - > <label - class="radio-label" - for="condensate" - >Condensate</label> + /> + <label class="radio-label" for="condensate">Condensate</label> <span style="float: right"> - Examples: <router-link to="/protein_example">UNE6</router-link>, + Examples: + <router-link to="/protein_example">UNE6</router-link>, <router-link to="/condensate_example">nucleolus__3702</router-link> </span> - </div> - <div class="col-sm-2"> - <button - class="btn btn-primary mb-2" - type="submit" - @click="searchWithKeyword" - > - Search - </button> + <div> + <button + class=" + text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-3 + text-center + font-bold + text-2xl + mt-2 + " + type="submit" + @click="searchWithKeyword" + > + Search + </button> + </div> </div> </div> </form> @@ -114,150 +112,168 @@ <!--<router-link to="/endo2" tag="button">Overview</router-link>--> <!--<router-link to="/pheno_profile" tag="button">Phenotype Profiles</router-link>--> <!--</div>--> - - <Search ref="search"> - <template slot-scope="{response, loading}"> - <div v-if="loading"> - Loading... - </div> - <div v-else-if="response"> - <div v-show="response.count === 0"> - No results! + <div class="mt-20"> + <Search ref="search"> + <template slot-scope="{ response, loading }"> + <div v-if="loading">Loading...</div> + <div v-else-if="response"> + <div v-show="response.count === 0">No results!</div> + <data-table + v-show="response.count > 0" + id="dataTable" + :category="searchPick" + :data="response.data" + :keyword="keyword" + /> </div> - <data-table - v-show="response.count > 0" - id="dataTable" - :category="searchPick" - :data="response.data" - :keyword="keyword" - /> - </div> - </template> - </Search> + </template> + </Search> + </div> </div> </template> <script> - import Search from '@/components/Search.vue' - import DataTable from '@/components/DataTable.vue' - import FetchSpecies from '@/components/DDCODE/fetchSpecies.vue' - import VueSimpleSuggest from 'vue-simple-suggest' +import Search from "@/components/Search.vue"; +import DataTable from "@/components/DataTable.vue"; +import FetchSpecies from "@/components/DDCODE/fetchSpecies.vue"; +import VueSimpleSuggest from "vue-simple-suggest"; - const _ = require('lodash') - const apikey = require('./js/const').apikey; +const _ = require("lodash"); +const apikey = require("./js/const").apikey; - export default { - name: 'LandingPage', - components: { - Search, FetchSpecies, DataTable, VueSimpleSuggest - }, - props: { - msg: String - }, - data() { - return { - selected: '', - keyword: '', - species: 'Homo sapiens (9606)', - searchKeyword: '', - pick: 'protein', - searchPick: '', - isDev: process.env.NODE_ENV === 'development' - } +export default { + name: "LandingPage", + components: { + Search, + FetchSpecies, + DataTable, + VueSimpleSuggest, + }, + props: { + msg: String, + }, + data() { + return { + selected: "", + keyword: "", + species: "Homo sapiens (9606)", + searchKeyword: "", + pick: "protein", + searchPick: "", + isDev: process.env.NODE_ENV === "development", + }; + }, + computed: { + proteinNameList: { + get() { + return _.map(this.proteinList, (d) => { + return { text: d.proteinName }; + }); + }, + set(obj) { + const idx = this.proteinList.findIndex( + (d) => d.proteinName === obj.tag.text + ); + this.$store.dispatch("Param/removeProtein", idx); + obj.deleteTag(); + // console.log(this.$store.getters['Param/proteinList']) + }, }, - computed: { - proteinNameList: { - get() { - return _.map(this.proteinList, d => { return {text: d.proteinName}; }) - }, - set(obj) { - const idx = this.proteinList.findIndex(d => d.proteinName === obj.tag.text) - this.$store.dispatch('Param/removeProtein', idx); - obj.deleteTag() - // console.log(this.$store.getters['Param/proteinList']) - } + }, + watch: { + selected: { + handler: function (key) { + this.keyword = key; }, + deep: true, }, - watch: { - selected: { - handler: function (key) { - this.keyword = key - }, - deep: true - } + }, + methods: { + removeNullItems(data) { + return _.filter(data, (d) => d._id !== null); }, - methods: { - getSuggestionList() { - const vm = this + getSuggestionList() { + const vm = this; - const query = vm.keyword + const query = vm.keyword; - let host = vm.isDev ? require('./js/const').devHost : require('./js/const').host; - const taxId = vm.species === 'All' ? 'all' : /\((\d+)\)$/gm.exec(vm.species)[1]; - let url = vm.species === 'All' ? `${host}/${vm.pick}s?query=${query}&size=10000` : - `${host}/${vm.pick}s?query=${query}&species_tax_id=${taxId}&size=10000`; + let host = vm.isDev + ? require("./js/const").devHost + : require("./js/const").host; + const taxId = + vm.species === "All" ? "all" : /\((\d+)\)$/gm.exec(vm.species)[1]; + let url = + vm.species === "All" + ? `${host}/${vm.pick}s?query=${query}&size=10000` + : `${host}/${vm.pick}s?query=${query}&species_tax_id=${taxId}&size=10000`; - // console.log(process.env.VUE_APP_TITLE) - // console.log(process.env.VUE_APP_API_KEY) + // console.log(process.env.VUE_APP_TITLE) + // console.log(process.env.VUE_APP_API_KEY) - return fetch(url, { - method: 'GET', - mode: 'cors', - cache: 'no-cache', - headers: { - Authorization: `Bearer ${apikey}` - }}) - .then(response => response.json()) - .then(json => _.uniq(_.map(json.data, c => c.gene_name || c.name))); - }, - searchWithKeyword() { - const vm = this + return fetch(url, { + method: "GET", + mode: "cors", + cache: "no-cache", + headers: { + Authorization: `Bearer ${apikey}`, + }, + }) + .then((response) => response.json()) + .then((json) => _.uniq(_.map(json.data, (c) => c.gene_name || c.name))); + }, + searchWithKeyword() { + const vm = this; - // console.log(/\((\d+)\)$/gm.exec(vm.species)) - // console.log(vm.species); - const taxId = vm.species === 'All' ? 'all' : vm.species === 'Chimeras' ? 'null' : /\((\d+)\)$/gm.exec(vm.species)[1]; - const page = 1; - // console.log(`http://${host}/proteins?query=${vm.keyword}&species_tax_id=${taxId}&page=${page}`) - vm.searchKeyword = vm.keyword - // console.log(vm.pick) + // console.log(/\((\d+)\)$/gm.exec(vm.species)) + // console.log(vm.species); + const taxId = + vm.species === "All" + ? "all" + : vm.species === "Chimeras" + ? "null" + : /\((\d+)\)$/gm.exec(vm.species)[1]; + const page = 1; + // console.log(`http://${host}/proteins?query=${vm.keyword}&species_tax_id=${taxId}&page=${page}`) + vm.searchKeyword = vm.keyword; + // console.log(vm.pick) - vm.searchPick = vm.pick; - if(vm.searchPick === 'protein') { - vm.$refs.search.fetchProteinList(vm.keyword, taxId) - } else if(vm.searchPick === 'condensate') { - vm.$refs.search.fetchCondensateList(vm.keyword, taxId) - } - // /* eslint-disable no-console */ - // console.log(this.searchKeyword) + vm.searchPick = vm.pick; + if (vm.searchPick === "protein") { + vm.$refs.search.fetchProteinList(vm.keyword, taxId); + } else if (vm.searchPick === "condensate") { + vm.$refs.search.fetchCondensateList(vm.keyword, taxId); } - } - } + // /* eslint-disable no-console */ + // console.log(this.searchKeyword) + }, + }, +}; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style> - @import url('~@/assets/bootstrap.css'); - @import url('~@/assets/datatable.css'); - @import url('~@/assets/vue-simple-suggest-styles.css'); +@import url("~@/assets/bootstrap.css"); +@import url("~@/assets/datatable.css"); +@import url("~@/assets/vue-simple-suggest-styles.css"); - .main { - margin: 1.5rem; - } +.main { + margin: 1.5rem; +} - h3 { - margin: 40px 0 0; - } +h3 { + margin: 40px 0 0; +} - a { - color: #42b983; - } +a { + color: #42b983; +} - input[type="radio"] { - margin: 2px; - } +input[type="radio"] { + margin: 2px; +} - .radio-label { - margin-left: 0px; - margin-right: 5px; - } +.radio-label { + margin-left: 0px; + margin-right: 5px; +} </style> diff --git a/web/src/components/Links.vue b/web/src/components/Links.vue index 11e9a25c144cf197da18047fd411a382268d9ffb..093386a562fc5fd1047e2ddf6c0a1876372d2197 100644 --- a/web/src/components/Links.vue +++ b/web/src/components/Links.vue @@ -74,7 +74,7 @@ :class="{ active: $route.name === 'updateItems' }" > <router-link to="/updateItems"> - Update Items + {{ userRole === 'Maintainer'? 'All Submitted Changes' : 'My Submitted Changes' }} </router-link> </li> <li diff --git a/web/src/components/LlpsTable.vue b/web/src/components/LlpsTable.vue index ecffc883512e07950b81f9f11fb28de491d846d8..9041efb6fb7b587081dc9aca4eb7a8491eafa620 100644 --- a/web/src/components/LlpsTable.vue +++ b/web/src/components/LlpsTable.vue @@ -59,16 +59,16 @@ function getStartWith(url) { return ret; } -function getRatingValue(data) { - return data -} - function getRating(data) { const rating = data; const r = ['<div style="white-space: nowrap;">']; for (let i = 0; i < 5; i++) { if (i < rating) { - r.push('<span class="fa fa-star checked"/>'); + if (rating - i === 0.5) { + r.push('<span class="fa fa-star-half-o checked"/>'); + } else { + r.push('<span class="fa fa-star checked"/>'); + } } else { r.push('<span class="fa fa-star"/>'); } @@ -318,7 +318,7 @@ export default { // console.log(_.flatMap(row.condensates, c => c.data_sources)) let dat = vm.map[row.uniprot_id]; if (dat) { - return getRatingValue(vm.map[row.uniprot_id]); + return dat; } return ""; }, @@ -453,7 +453,7 @@ export default { let dat = vm.map[row.uniprot_id]; if (dat) { // console.log(getRatingValue(vm.map[row.uniprot_id])) - return getRatingValue(dat); + return dat; } return ""; }, diff --git a/web/src/components/ProteinDetailPage.vue b/web/src/components/ProteinDetailPage.vue index 08f53a9fb77ecd4192c207d0f56af2a54326bd0b..be22544eed24ff99cbda546fc8f95afc14328851 100644 --- a/web/src/components/ProteinDetailPage.vue +++ b/web/src/components/ProteinDetailPage.vue @@ -302,12 +302,6 @@ Show less</a> </div> </div> - <!--<div class="row">--> - <!--<div class="text col-sm-3">Evidence Stars</div>--> - <!--<div class="col-sm-9">--> - <!--<span v-for="(item, index) in getRating(response.data.condensates)" :class="item" v-bind:key="index"/>--> - <!--</div>--> - <!--</div>--> <div class="row"> <div class="text col-sm-3"> Sequence @@ -559,38 +553,6 @@ export default { }); } }, - getRating(data) { - const scoreMap = { hungarian: 5, blue: 5, pink: 1, grey: 1 }; - let r = []; - _.each(data, (c) => { - r.push(_.max(_.map(c.data_sources, (i) => scoreMap[i]))); - }); - - const rating = _.max(r); - r = []; - for (let i = 0; i < 5; i++) { - if (i < rating) { - r.push("fa fa-star checked"); - } else { - r.push("fa fa-star"); - } - } - return r; - }, - getSingleRating(data) { - const scoreMap = { hungarian: 5, blue: 5, pink: 1, grey: 1 }; - - const rating = _.max(_.map(data, (i) => scoreMap[i])); - let r = []; - for (let i = 0; i < 5; i++) { - if (i < rating) { - r.push("fa fa-star checked"); - } else { - r.push("fa fa-star"); - } - } - return r; - }, }, }; </script> diff --git a/web/src/components/Search.vue b/web/src/components/Search.vue index 9527b806627f2bd580c85e938faa5cbb705f1d82..9a8015b939b3731406bae75559beef2ded666a1c 100644 --- a/web/src/components/Search.vue +++ b/web/src/components/Search.vue @@ -39,7 +39,7 @@ let url = taxId === 'all' ? `${host}/proteins?query=${keyword}&size=100000`: `${host}/proteins?query=${keyword}&species_tax_id=${taxId}&size=100000`; - + if (keyword && !_.isEmpty(keyword)) { fetch(url, { method: 'GET', @@ -113,7 +113,7 @@ // if(vm.isDev) { // url = `/json/condensateList.json`; // } - + console.log("condensate list search", url); if (keyword && !_.isEmpty(keyword)) { fetch(url, { method: 'GET', @@ -139,8 +139,8 @@ }, 10); }); } else { - url = taxId === 'all' ? `${host}/condensates?fields=biomarkers,canonical_id,description,is_experimental,name,protein_count,proteins,species_name,species_tax_id,synonyms,protein_confidence_score&size=10000` - :`${host}/condensates?species_tax_id=${taxId}&fields=biomarkers,canonical_id,description,is_experimental,name,protein_count,proteins,species_name,species_tax_id,synonyms,protein_confidence_score&size=10000`; + url = taxId === 'all' ? `${host}/condensates?fields=biomarkers,canonical_id,description,is_experimental,name,protein_count,proteins,species_name,species_tax_id,synonyms,confidence_score&size=10000` + :`${host}/condensates?species_tax_id=${taxId}&fields=biomarkers,canonical_id,description,is_experimental,name,protein_count,proteins,species_name,species_tax_id,synonyms,confidence_score&size=10000`; fetch(url, { method: 'GET', mode: 'cors', diff --git a/web/src/components/UI/TheDeleteModal.vue b/web/src/components/UI/TheDeleteModal.vue index 3a0dcf80325945e306ca36eb5e5341c0d7a7a6a5..92f5b22d0e26db463c088d330a846447507e7589 100644 --- a/web/src/components/UI/TheDeleteModal.vue +++ b/web/src/components/UI/TheDeleteModal.vue @@ -1,9 +1,20 @@ <template> <section> - <div - v-if="show" - id="deleteModal" - > + <base-toaster :open="toasterIsOpen" @close="hideDialog"> + <div class="flex justify-between items-center"> + <font-awesome-icon + class="ml-3" + icon="fa-solid fa-thumbs-up " + size="lg" + /> + + <h4>Request submitted successfully!</h4> + <button class="btn btn-outline" @click="hideDialog"> + <font-awesome-icon icon="fa-regular fa-circle-xmark" size="2x" /> + </button> + </div> + </base-toaster> + <div v-if="show" id="deleteModal"> <div class="fixed z-40 h-full inset-0 opacity-25 bg-black" /> <div class=" @@ -80,7 +91,8 @@ /> </svg> <h2 class="mb-5 text-2xl dark:text-gray-400"> - Explain why you would like to remove this protein {{ uniprotId }}. + Explain why you would like to remove this protein + {{ uniprotId }}. </h2> <textarea id="funComment" @@ -114,7 +126,7 @@ /> <p - v-if="message" + v-if="message && error" :class=" error ? 'text-danger font-bold' : 'text-success font-bold' " @@ -126,9 +138,9 @@ type="button" class=" text-white - bg-red-600 - hover:bg-red-800 - focus:ring-4 focus:ring-red-300 + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 rounded-lg inline-flex items-center @@ -145,8 +157,8 @@ type="button" class=" bg-white - hover:bg-gray-100 - focus:ring-4 focus:ring-gray-300 + hover:bg-gray-200 + focus:ring-2 focus:ring-gray-300 rounded-lg border border-gray-200 px-5 @@ -172,15 +184,18 @@ </template> <script> +import BaseToaster from "./BaseToaster.vue"; const _ = require("lodash"); let host = require("../js/const").apiHost; export default { + components: { BaseToaster }, props: ["show", "title", "uniprotId", "canonicalId"], data() { return { comment: "", message: "", error: false, + toasterIsOpen: false, }; }, computed: { @@ -192,6 +207,12 @@ export default { }, }, methods: { + showDialog() { + this.toasterIsOpen = true; + }, + hideDialog() { + this.toasterIsOpen = false; + }, validateComment() { this.message = ""; }, @@ -253,16 +274,14 @@ export default { }, } ); + this.toasterIsOpen = true; - this.message = "Request submitted successfully!"; this.comment = ""; - this.currentRating = this.rating; } catch (e) { console.error(e); this.error = true; this.message = e.message || "Something went wrong, please try again later!"; - this.currentRating = this.rating; } }, }, diff --git a/web/src/components/UpdateItemTable.vue b/web/src/components/UpdateItemTable.vue index 4f36ef8a7a9bb82a38e69556201148e67673eb4d..b080fdc4b8bf9f116edc3e55d5454db531f3dc7f 100644 --- a/web/src/components/UpdateItemTable.vue +++ b/web/src/components/UpdateItemTable.vue @@ -1,5 +1,5 @@ <template> - <div class="main"> + <div class="main m-4"> <table :id="id" class="table table-striped table-bordered table-hover" @@ -60,6 +60,15 @@ export default { { title: "Resource ID", data: "attributes.EntityId", + fnCreatedCell: (nTd, sData, oData) => { + if (oData.attributes.Entity === 'protein') { + $(nTd).html(`<a href='/protein/${sData}'> ${sData}</a>`); + } else if (oData.attributes.Entity === 'condensate') { + $(nTd).html(`<a href='/condensate/${sData}'> ${sData}</a>`); + } else { + $(nTd).html(`${sData}`); + } + }, }, { title: "Attribute", @@ -77,16 +86,15 @@ export default { title: "Status", data: "attributes.Status", fnCreatedCell: (nTd, sData, oData) => { - if(sData==='synced'){ - $(nTd).html(`<div class="rounded-full px-4 mr-2 bg-gray-500 text-white p-2 rounded flex items-center justify-center">${sData}</div>`); - } else if(sData==='accepted'){ - $(nTd).html(`<div class="rounded-full px-4 mr-2 bg-green-500 text-white p-2 rounded flex items-center justify-center">${sData}</div>`); - }else if(sData==='rejected'){ - $(nTd).html(`<div class="rounded-full px-4 mr-2 bg-red-500 text-white p-2 rounded flex items-center justify-center">${sData}</div>`); - }else if(sData==='requested'){ - $(nTd).html(`<div class="rounded-full px-4 mr-2 bg-yellow-500 text-white p-2 rounded flex items-center justify-center">${sData}</div>`); - } - + if (sData === 'synced') { + $(nTd).html(`<div class="rounded-full px-4 mr-2 bg-gray-500 text-white p-2 rounded flex items-center justify-center">${sData}</div>`); + } else if (sData === 'accepted') { + $(nTd).html(`<div class="rounded-full px-4 mr-2 bg-green-500 text-white p-2 rounded flex items-center justify-center">${sData}</div>`); + } else if (sData === 'rejected') { + $(nTd).html(`<div class="rounded-full px-4 mr-2 bg-red-500 text-white p-2 rounded flex items-center justify-center">${sData}</div>`); + } else if (sData === 'requested') { + $(nTd).html(`<div class="rounded-full px-4 mr-2 bg-yellow-500 text-white p-2 rounded flex items-center justify-center">${sData}</div>`); + } }, }, { diff --git a/web/src/views/ForgotPassword.vue b/web/src/views/ForgotPassword.vue index 10165227011b191715ee550edcce33927989493d..15eef018865cc805d9bd49177cb53e6ea96094a9 100644 --- a/web/src/views/ForgotPassword.vue +++ b/web/src/views/ForgotPassword.vue @@ -1,40 +1,97 @@ <template> <div> + <base-toaster :open="toasterIsOpen" @close="hideDialog"> + <div class="flex justify-between space-x-4 items-center"> + <font-awesome-icon + class="ml-3" + icon="fa-solid fa-thumbs-up " + size="lg" + /> + + <h4>Password reset link has been sent to {{ email.val }}.</h4> + <button class="btn btn-outline" @click="hideDialog"> + <font-awesome-icon icon="fa-regular fa-circle-xmark" size="2x" /> + </button> + </div> + </base-toaster> <div class="flex items-center justify-center"> - <div class="sm:block"> - <div class="p-5 mx-auto text-left font-raleway"> - <h1 class="font-bold text-left font-montserrat text-3xl sm:text-5xl mb-7"> + <div class="sm:block border border-gray-300 rounded-lg mt-6 w-1/3"> + <div class="p-5 mx-auto text-left"> + <h1 + class=" + font-bold + text-left + font-montserrat + text-3xl + sm:text-5xl + mb-7 + " + > Recover Your DD-Code Password </h1> - <p + <!-- <p v-show="done" class="text-lg text-green-500" > Password reset link has been sent to {{ email }} - </p> - <p - v-show="error" - class="text-lg text-red-500" - > - An error occurred - </p> + </p> --> + <form @submit="forgotPassword"> <div class="my-4"> - <h1 class="text-left font-bold mb-5 text-xl sm:text-2xl font-montserrat"> + <h1 + class=" + text-left + font-bold + mb-5 + text-xl + sm:text-2xl + font-montserrat + " + > Email </h1> <input - v-model="email" + v-model.trim="email.val" type="email" - class="text-xl outline-none pb-5 w-4/5 bg-transparent border-b hover:border-blue-700 focus:border-blue-700" - > + class=" + text-xl + outline-none + w-full + bg-transparent + border border-gray-400 + rounded-lg + px-4 + py-5 + hover:border-blue-700 + focus:border-blue-700 + " + placeholder="Enter email address." + @blur="clearValidity('email')" + /> + <p v-if="!email.isValid" class="text-red-500 mt-2"> + Email must not be empty. + </p> + <p v-show="error" class="mt-2 text-red-500"> + {{ errorMsg }} + </p> + </div> + <div v-if="isLoading"> + <base-spinner /> </div> - <button type="submit" - class="bg-green-400 p-5 text-white" + class=" + bg-blue-500 + my-4 + text-2xl + w-full + font-bold + rounded-lg + p-5 + text-white + " > - Send Email link <span class="fa fa-arrow-right" /> + Send email link </button> </form> </div> @@ -43,49 +100,119 @@ </div> </template> <script> -let host = require('@/components/js/const').apiHost; +import BaseSpinner from "../components/UI/BaseSpinner.vue"; +import BaseToaster from "../components/UI/BaseToaster.vue"; +let host = require("@/components/js/const").apiHost; export default { - name: 'ForgotPassword', + components: { BaseSpinner, BaseToaster }, + name: "ForgotPassword", data() { return { - email: '', + email: { + val: "", + isValid: true, + }, + formIsValid: true, done: false, error: false, - isDev: process.env.NODE_ENV === 'development', - } + isDev: process.env.NODE_ENV === "development", + isLoading: false, + toasterIsOpen: false, + errorMsg: "", + }; }, methods: { + showDialog() { + this.toasterIsOpen = true; + }, + hideDialog() { + this.toasterIsOpen = false; + this.$router.push("login"); + }, + clearValidity(input) { + this[input].isValid = true; + }, + validateForm() { + this.formIsValid = true; + if (this.email.val === "") { + this.email.isValid = false; + this.formIsValid = false; + } + }, async forgotPassword(e) { - e.preventDefault() + e.preventDefault(); const vm = this; - if(vm.isDev) { - host = require('@/components/js/const').devApiHost; + vm.validateForm(); + if (!vm.formIsValid) { + return; } + if (vm.isDev) { + host = require("@/components/js/const").devApiHost; + } + this.isLoading = true; this.done = false; this.error = false; - this.axios.post(`${host}/api/auth/forgot-password`, { - email: this.email - }) - .then(() => { - this.done = true - }) - .catch(e => { - e; - this.error = true - }) - } - } -} + // const res= this.axios.post(`${host}/api/auth/forgot-password`, { + // email: this.email.val + // }) + + const res = await fetch(`${host}/api/auth/forgot-password`, { + method: "POST", + mode: "cors", // no-cors, *cors, same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, + headers: { + "Content-Type": "application/json", + // 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: JSON.stringify({ + email: this.email.val, + }), + }); + if (!res.ok && res.status === 500) { + this.isLoading = false; + this.error = true; + this.errorMsg = `${res.status} Internal Error, please write a mail to DDCode Admin`; + + return; + } + if (!res.ok && res.status === 405) { + this.isLoading = false; + this.error = true; + this.errorMsg = `${res.status} Internal Error, please write a mail to DDCode Admin`; + + return; + } + const response = await res.json(); + + if ( + !res.ok && + response.error.status === 400 && + response.error.message === "This email does not exist" + ) { + this.isLoading = false; + this.error = true; + this.errorMsg = + "Provided email address is not yet registered with any existing account (User has not signed up yet)"; + + return; + } + + this.isLoading = false; + this.error= false; + this.toasterIsOpen = true; + }, + }, +}; </script> <style> - @import url('~@/assets/bootstrap.css'); +@import url("~@/assets/bootstrap.css"); - a { - color: #42b983; - } +a { + color: #42b983; +} </style> diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index bcba2272d992ad35fe57b2ccb5b180da8992a45d..1bc41b6103423e4f8208d5cda1bc0a4871dab283 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -4,62 +4,134 @@ <base-spinner /> </div> <div class="flex items-center justify-center"> - <div class="sm:block card mt-6 w-1/3"> + <div class="sm:block border border-gray-300 rounded-lg mt-6 w-1/3"> <div class="p-5 mx-auto text-left font-raleway"> - <h1 class="font-bold text-left font-montserrat text-3xl sm:text-5xl mb-7"> + <h1 + class=" + font-bold + text-left + font-montserrat + text-3xl + sm:text-5xl + mb-7 + " + > Login to DD-Code </h1> - <p - v-show="error" - class="text-lg text-red-500" - > - {{ errorMsg }} - </p> + <form @submit="login"> - <div class="my-4"> - <h1 class="text-left font-bold mb-2 text-xl sm:text-2xl font-montserrat"> + <div class="my-4" :class="{ invalid: !email.isValid }"> + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > Email* </h1> <input - v-model.trim="email" + v-model.trim="email.val" type="email" - class="outline-none pt-3 pb-3 w-4/5 bg-transparent border-b hover:border-blue-700 focus:border-blue-700" - > + class=" + outline-none + bg-transparent + border border-gray-400 + px-4 + py-5 + rounded-lg + w-full + hover:border-blue-700 + focus:border-blue-700 + " + placeholder="Enter email." + @blur="clearValidity('email')" + /> + <p v-if="!email.isValid" class="text-red-500"> + Email must not be empty. + </p> </div> - <div class="my-4"> - <h1 class="text-left font-bold mb-2 text-xl sm:text-2xl font-montserrat"> + <div class="my-4" :class="{ invalid: !password.isValid }"> + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > Password* </h1> <input - v-model.trim="password" + v-model.trim="password.val" type="password" - class="text-xl outline-none pt-3 pb-3 w-4/5 bg-transparent border-b hover:border-blue-700 focus:border-blue-700" - > + class=" + text-xl + outline-none + bg-transparent + border border-gray-400 + px-4 + py-5 + rounded-lg + w-full + hover:border-blue-700 + focus:border-blue-700 + " + placeholder="Enter password." + @blur="clearValidity('password')" + /> + <p v-if="!password.isValid" class="text-red-500"> + Password must not be empty. + </p> + </div> - - - + <p v-show="error" class="text-red-500"> + {{ errorMsg }} + </p> <button type="submit" - :disabled="password.length < 3 || !password ||!email" - :class="password.length < 3 || !password ||!email ? 'bg-green-400 text-3xl font-bold rounded-lg p-5 text-white opacity-75' : 'bg-green-400 text-3xl font-bold rounded-lg p-5 text-white hover:bg-green-600'" + class=" + bg-blue-500 + my-4 + text-2xl + w-full + font-bold + rounded-lg + p-5 + text-white + " > - Login <font-awesome-icon - size="lg" - class="ml-2" - icon="fa-solid fa-right-to-bracket" - /> + Sign in </button> <p class="my-2 font-bold"> - <router-link to="/forgotpassword"> + <router-link + class=" + text-blue-500 text-2xl + hover:text-blue-600 hover:no-underline + " + to="/forgotpassword" + > Forgot Password? </router-link> </p> - <p class="my-2 font-bold"> - <router-link to="/signup"> + <h4> + Don't have an account yet? + <router-link + class=" + text-blue-500 text-2xl + font-bold + hover:text-blue-600 hover:no-underline + " + to="/signup" + > Join DD-CODE as a contributor. </router-link> - </p> + </h4> </form> </div> </div> @@ -67,72 +139,152 @@ </div> </template> <script> -let host = require('@/components/js/const').apiHost; +let host = require("@/components/js/const").apiHost; export default { - name: 'Login', + name: "Login", data() { return { - email: '', - password: '', + email: { + val: "", + isValid: true, + }, + password: { + val: "", + isValid: true, + }, + formIsValid: true, error: false, - errorMsg: `An error occurred, please try again`, - isDev: process.env.NODE_ENV === 'development', - isLoading: false - } + errorMsg: ``, + isDev: process.env.NODE_ENV === "development", + isLoading: false, + }; }, computed: { userData: function () { - return this.$store.getters['User/userData'] + return this.$store.getters["User/userData"]; }, }, mounted: function () { - const vm = this - if(vm.userData !== null) { - this.$router.push('/profile') + const vm = this; + if (vm.userData !== null) { + this.$router.push("/profile"); } }, methods: { + clearValidity(input) { + this[input].isValid = true; + }, + validateForm() { + this.formIsValid = true; + if (this.email.val === "") { + this.email.isValid = false; + this.formIsValid = false; + } + if (this.password.val === "") { + this.password.isValid = false; + this.formIsValid = false; + } + }, async login(e) { - e.preventDefault() + e.preventDefault(); const vm = this; + vm.validateForm(); + if (!vm.formIsValid) { + return; + } + if (vm.isDev) { + host = require("@/components/js/const").devApiHost; + } + this.isLoading = true; + + const res = await fetch(`${host}/api/auth/local`, { + method: "POST", + mode: "cors", // no-cors, *cors, same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, + headers: { + "Content-Type": "application/json", + // 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: JSON.stringify({ + identifier: this.email.val, + password: this.password.val, + }), + }); + + if (!res.ok && res.status === 500) { + this.isLoading = false; + this.error = true; + this.errorMsg = `${res.status} Internal Error, please write a mail to DDCode Admin`; - if(vm.isDev) { - host = require('@/components/js/const').devApiHost; + return; } -this.isLoading= true; - try { - const res = await this.axios.post(`${host}/api/auth/local`, { - identifier: this.email, - password: this.password - }); + if (!res.ok && res.status === 405) { + this.isLoading = false; + this.error = true; + this.errorMsg = `${res.status} Internal Error, please write a mail to DDCode Admin`; - const { jwt, user } = res.data - // console.log(user) - window.localStorage.setItem('jwt', jwt) - window.localStorage.setItem('userData', JSON.stringify(user)) + return; + } - this.$store.dispatch('User/setJwt', jwt) - this.$store.dispatch('User/setUserData', user) - this.isLoading= false - this.$router.push('/profile') - } catch(error) { - // console.log(error.response.data.error.message) - this.isLoading= false; - this.error = true - this.password = '' - this.isLoading= false + const response = await res.json(); + if (!res.ok && response.error.status === 400 && response.error.message === "Invalid identifier or password") { + this.isLoading = false; + this.error = true; + this.errorMsg = "Email Address & Password mismatch."; + this.password.val = ""; + return; } + + const { jwt, user } = response; + + window.localStorage.setItem("jwt", jwt); + window.localStorage.setItem("userData", JSON.stringify(user)); + + this.$store.dispatch("User/setJwt", jwt); + this.$store.dispatch("User/setUserData", user); + this.isLoading = false; + this.error = false; + this.$router.push("/profile"); + + // const res = await this.axios.post(`${host}/api/auth/local`, { + // identifier: this.email.val, + // password: this.password.val + // }); + // console.log("error is ", res); + // const { jwt, user } = res.data + // console.log(user) + // window.localStorage.setItem('jwt', jwt) + // window.localStorage.setItem('userData', JSON.stringify(user)) + + // this.$store.dispatch('User/setJwt', jwt) + // this.$store.dispatch('User/setUserData', user) + // this.isLoading= false; + // this.error= false; + // this.$router.push('/profile') + + // console.log(error.response.data.error.message) + // console.log("error",e.data); + // this.isLoading= false; + // this.error = true + // this.password.val = '' + // this.isLoading= false }, - } -} + }, +}; </script> <style> - @import url('~@/assets/bootstrap.css'); +@import url("~@/assets/bootstrap.css"); - a { - color: #42b983; - } +a { + color: #42b983; +} +.invalid h1 { + color: red; +} +.invalid input { + border: 1px solid red; +} </style> \ No newline at end of file diff --git a/web/src/views/Profile.vue b/web/src/views/Profile.vue index 81ce470fce622774684614f0f7c7e3d0304ff797..5c89f4a5d1507c99d11774922235828761ee1853 100644 --- a/web/src/views/Profile.vue +++ b/web/src/views/Profile.vue @@ -26,15 +26,19 @@ </button> </div> </base-toaster> - <fetch-profile v-if="userData !== null"> - <template slot-scope="{ response, loading }"> + <fetch-profile v-if="userData !== null" :key="updatedKey"> + <template slot-scope="{ response, loading, error }"> <slot :response="response" :loading="loading" + :error="error" > - <div v-if="loading || response === null"> + <div v-if="loading"> <base-spinner /> </div> + <div v-else-if="!loading && error" > + <p class="text-red-500 text-2xl">{{response}}</p> + </div> <div v-else> <!-- {{response}}--> <h4 class="round"> @@ -54,13 +58,19 @@ <button type="button" class=" - bg-blue-600 - text-2xl - font-bold - rounded-lg - p-5 text-white - hover:bg-blue-600 + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-3 + text-center + font-bold + mr-2 + font-bold " @click="isOpen = !isOpen" > @@ -115,7 +125,8 @@ " @click="openProfileLinkEdit" >Profile Link</a> - <!-- <a href="#" @click="openChangePassword" class="text-gray-700 + <a href="#" @click="openChangePassword" class="text-gray-700 + text-gray-700 block px-6 rounded-lg @@ -123,7 +134,7 @@ text-2xl hover:bg-gray-400 hover:text-white - hover:no-underline" >Password</a> --> + hover:no-underline" >Password</a> </div> </div> </div> @@ -159,7 +170,7 @@ </div> <div class="col-sm-9"> <div> - {{ response.current_role }} + {{ response.current_role.replace(/_/g, ' ') }} </div> </div> </div> @@ -169,7 +180,7 @@ </div> <div class="col-sm-9"> <div> - {{ response.scientific_discipline }} + {{response.scientific_discipline.join(', ')}} </div> </div> </div> @@ -228,8 +239,8 @@ </div> <div - v-if="editMode" - class="panel panel-default" + v-if="editMode" + class="panel panel-default" > <div class="panel-body"> <div class="container-fluid col-md-12"> @@ -243,28 +254,28 @@ </div> <div class="col-sm-9 p-4"> <div - v-for="options in scientificDisciplineOptions" - :key="options.id" + v-for="options in scientificDisciplineOptions" + :key="options.id" > <input - :id="options.id" - v-model="selected" - type="checkbox" - class="h-6 w-6" - :name="options" - :value="options.discipline" + :id="options.id" + v-model="selected" + type="checkbox" + class="h-6 w-6" + :name="options" + :value="options.discipline" > <label - class="mx-3" - :for="options.id" + class="mx-3" + :for="options.id" >{{ - options.discipline - }}</label> + options.discipline + }}</label> </div> <p - v-if="error && scientificErrMsg" - class="text-red-500 font-bold mt-2" + v-if="error && scientificErrMsg" + class="text-red-500 font-bold mt-2" > {{ scientificErrMsg }} </p> @@ -282,9 +293,9 @@ <div class="col-sm-9 p-4"> <input - id="inline-profile-link" - v-model.trim="profile_link" - class=" + id="inline-profile-link" + v-model.trim="profile_link" + class=" bg-white w-1/3 py-4 @@ -295,12 +306,12 @@ border border-gray-500 hover:border-gray-700 " - type="text" - placeholder="Enter profile link." + type="text" + placeholder="Enter profile link." > <p - v-if="error && profileLinkErrMsg" - class="text-red-500 font-bold mt-2" + v-if="error && profileLinkErrMsg" + class="text-red-500 font-bold mt-2" > {{ profileLinkErrMsg }} </p> @@ -313,13 +324,59 @@ </h3> <div class="row"> <div class="text col-sm-3 p-4"> - Password + Current password + </div> + <div class="col-sm-9 p-4"> + <input + id="current-password" + v-model.trim="currentPassword" + class=" + bg-white + w-1/3 + py-4 + px-4 + rounded-lg + text-gray-700 + bg-transparent + border border-gray-500 + hover:border-gray-700 + " + type="password" + > + </div> + </div> + <div class="row"> + <div class="text col-sm-3 p-4"> + New Password + </div> + <div class="col-sm-9 p-4"> + <input + id="inline-password" + v-model.trim="newPassword" + class=" + bg-white + w-1/3 + py-4 + px-4 + rounded-lg + text-gray-700 + bg-transparent + border border-gray-500 + hover:border-gray-700 + " + type="password" + > + </div> + </div> + <div class="row"> + <div class="text col-sm-3 p-4"> + Confirm New Password </div> <div class="col-sm-9 p-4"> <input - id="inline-profile-link" - v-model.trim="password" - class=" + id="confirm-password" + v-model.trim="confirmPassword" + class=" bg-white w-1/3 py-4 @@ -330,11 +387,11 @@ border border-gray-500 hover:border-gray-700 " - type="text" + type="password" > <p - v-if="error && passwordErrMsg" - class="text-red-500 font-bold mt-2" + v-if="error && passwordErrMsg" + class="text-red-500 font-bold mt-2" > {{ passwordErrMsg }} </p> @@ -345,13 +402,14 @@ <div class="flex space-x-4 mt-2"> <button class=" - bg-green-600 + bg-blue-500 text-xl font-bold rounded-lg - p-5 + px-5 + py-4 text-white - hover:bg-green-600 + hover:bg-blue-700 " @click="update(response)" > @@ -359,13 +417,14 @@ </button> <button class=" - bg-blue-600 + bg-gray-500 text-xl font-bold rounded-lg - p-5 + px-5 + py-4 text-white - hover:bg-blue-600 + hover:bg-gray-700 " @click="close" > @@ -375,8 +434,8 @@ </div> </div> <p - v-if="error && errorMsg" - class="text-red-500 font-bold mt-2" + v-if="error && errorMsg" + class="text-red-500 font-bold mt-2" > {{ errorMsg }} </p> @@ -444,7 +503,9 @@ export default { scientific_discipline: [], current_affiliation: "", profile_link: "", - password: "", + currentPassword: "", + newPassword: "", + confirmPassword: "", isDev: process.env.NODE_ENV === "development", editMode: false, toasterIsOpen: false, @@ -453,7 +514,7 @@ export default { { id: "1", discipline: "Physics" }, { id: "2", discipline: "Chemistry" }, { id: "3", discipline: "Biology" }, - { id: "4", discipline: "Computer_Science" }, + { id: "4", discipline: "Computer Science" }, { id: "5", discipline: "Mathematics" }, ], selected: [], @@ -465,6 +526,7 @@ export default { profileLinkErrMsg: "", passwordErrMsg: "", errorMsg: "", + updatedKey: 0, }; }, computed: { @@ -531,7 +593,6 @@ export default { this.toasterIsOpen = false; }, showEditMode() { - console.log(true); this.editMode = true; }, setRole(roleName) { @@ -542,7 +603,7 @@ export default { return roleName; }, async update(e) { - console.log("user data", e); + // console.log("user data", e); const vm = this; if (vm.isDev) { @@ -564,7 +625,6 @@ export default { } dat = { id: vm.user, - scientific_discipline: vm.selected, }; } else if (this.showEditProfileLink) { @@ -575,19 +635,30 @@ export default { } dat = { id: vm.user, - profile_link: vm.profile_link, }; } else if (this.showChangePassword) { - if (!this.password) { + if (!this.currentPassword) { this.error = true; - this.passwordErrMsg = "Please enter a password."; + this.passwordErrMsg = "Please enter the current password."; + return; + } + if (!this.newPassword) { + this.error = true; + this.passwordErrMsg = "Please enter new password."; + return; + } + if (!this.confirmPassword) { + this.error = true; + this.passwordErrMsg = "Please enter new password again."; return; } - dat = { - id: vm.user, - password: vm.password, + dat = { + identifier: vm.user, + password: vm.currentPassword, + newPassword: vm.newPassword, + confirmPassword: vm.confirmPassword, }; } this.error = false; @@ -600,21 +671,40 @@ export default { // scientific_discipline: vm.selected, // password: vm.password, // }; - console.log(dat); + // console.log(dat); try { - const res = await this.axios.put(`${host}/api/users/${vm.user}`, dat, { - headers: { - Authorization: `Bearer ${jwt}`, - }, - }); + let res; + if (this.showChangePassword) { + res = await this.axios.post(`${host}/api/password`, dat, { + headers: { + Authorization: `Bearer ${jwt}`, + }, + }); + // console.log(res.data); + const newJwt = res.data.jwt; + window.localStorage.setItem("jwt", newJwt); + this.$store.dispatch("User/setJwt", newJwt); + } else { + res = await this.axios.put(`${host}/api/users/${vm.user}`, dat, { + headers: { + Authorization: `Bearer ${jwt}`, + }, + }); + } - console.log(res.data); + // console.log(res.data); this.error = false; this.toasterIsOpen = true; + this.editMode = false; + this.isOpen = false; + setTimeout(() => { + this.toasterIsOpen =false; + }, 2000); this.selected = []; this.profile_link = ""; this.password = ""; + this.updatedKey += 1; } catch (error) { // console.log(error.response.data.error.message) // console.log(error.response) diff --git a/web/src/views/SignUp.vue b/web/src/views/SignUp.vue index d901f15d0366a24b3a56d6280dc5ced478159f68..08c53fee7bec1a9359a0f12d836a47179366e704 100644 --- a/web/src/views/SignUp.vue +++ b/web/src/views/SignUp.vue @@ -1,9 +1,6 @@ <template> <div> - <base-toaster - :open="toasterIsOpen" - @close="hideDialog" - > + <base-toaster :open="toasterIsOpen" @close="hideDialog"> <div class="flex justify-between space-x-4 items-center"> <font-awesome-icon class="ml-3" @@ -12,101 +9,227 @@ /> <h4> - Sign up request successful. An activation link has been sent to the email <span> <a - class="text-blue-900" - href="" - >{{ email }}</a> </span> . Please click the link to activate your account. + Sign up request successful. An activation link has been sent to the + email {{ email.val }} + + . Please click the link to activate your account. </h4> - <button - class="btn btn-outline" - @click="hideDialog" - > - <font-awesome-icon - icon="fa-regular fa-circle-xmark" - size="2x" - /> + <button class="btn btn-outline" @click="hideDialog"> + <font-awesome-icon icon="fa-regular fa-circle-xmark" size="2x" /> </button> </div> </base-toaster> <div class="flex items-center justify-center"> - <div class="sm:block card w-2/5 mt-6 rounded-lg p-4"> - <div class="p-5 mx-auto text-left font-raleway"> - <h1 class="font-bold text-left font-montserrat text-3xl sm:text-5xl mb-7"> + <div class="sm:block border border-gray-300 rounded-lg w-2/5 mt-6 rounded-lg p-4"> + <div class="p-8 text-left"> + <h1 + class=" + font-bold + text-left + font-montserrat + text-3xl + sm:text-5xl + mb-7 + " + > Sign Up to join DD-Code </h1> - <p - v-show="error" - class="text-lg text-red-500" - > - {{ errorMsg }} - </p> - <form @submit="register"> - <div class="my-4"> - <h1 class="text-left font-bold mb-2 text-xl sm:text-2xl font-montserrat"> - Name - </h1> + + <form @submit.prevent="register"> + <div class="my-4" :class="{ invalid: !name.isValid }"> + <div class="flex justify-between"> + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > + Name* + </h1> + <h1 + class=" + text-left text-gray-400 + mb-2 + text-lg + sm:text-2xl + font-montserrat + " + > + Note: * fields are mandatory. + </h1> + </div> + <input - v-model="name" + v-model.trim="name.val" type="text" - class="text-xl outline-none pb-2 w-4/5 bg-transparent border-b hover:border-blue-700 focus:border-blue-700" - > + @blur="clearValidity('name')" + class=" + text-xl + outline-none + px-4 + py-5 + w-full + bg-transparent + border + rounded-lg + border-gray-400 + hover:border-blue-700 + focus:border-blue-700 + " + placeholder="Enter full name." + /> + <p v-if="!name.isValid" class="text-red-500"> + Name must not be empty. + </p> </div> - <div class="my-4"> - <h1 class="text-left font-bold mb-2 text-xl sm:text-2xl font-montserrat"> - Email + <div class="my-4" :class="{ invalid: !email.isValid }"> + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > + Email* </h1> <input - v-model="email" + v-model.trim="email.val" type="email" - class="text-xl outline-none pb-2 w-4/5 bg-transparent border-b hover:border-blue-700 focus:border-blue-700" - > + @blur="clearValidity('email')" + class=" + text-xl + outline-none + w-full + bg-transparent + px-4 + py-5 + rounded-lg + border border-gray-400 + hover:border-blue-700 + focus:border-blue-700 + " + placeholder="Enter email." + /> + <p v-if="!email.isValid" class="text-red-500"> + Email must not be empty. + </p> + <p v-if="error && errorMsg" class="text-red-500"> + {{ errorMsg }} + </p> </div> - <div class="my-4"> - <h1 class="text-left font-bold mb-2 text-xl sm:text-2xl font-montserrat"> - Password + <div class="my-4" :class="{ invalid: !password.isValid }"> + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > + Password* </h1> <input - v-model="password" + v-model.trim="password.val" type="password" - class="text-xl outline-none pb-2 w-4/5 bg-transparent border-b hover:border-blue-700 focus:border-blue-700" - > + @blur="clearValidity('password')" + class=" + text-xl + outline-none + px-4 + py-5 + rounded-lg + border border-gray-400 + w-full + bg-transparent + hover:border-blue-700 + focus:border-blue-700 + " + placeholder="Password must be minimum 6 character." + /> + <p v-if="!password.isValid" class="text-red-500"> + Password must not be empty or less than 6 character. + </p> </div> - <div class="my-4"> - <h1 class="text-left font-bold mb-2 text-xl sm:text-2xl font-montserrat"> - Username + <div class="my-4" :class="{ invalid: !username.isValid }"> + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > + Username* </h1> <input - v-model="username" + v-model.trim="username.val" + @blur="clearValidity('username')" type="text" - class="text-xl outline-none pb-2 w-4/5 bg-transparent border-b hover:border-blue-700 focus:border-blue-700" - > + class=" + text-xl + outline-none + px-4 + py-5 + rounded-lg + border border-gray-400 + w-full + bg-transparent + border-b + hover:border-blue-700 + focus:border-blue-700 + " + placeholder="Username must be minimum 3 character." + /> + <p v-if="!username.isValid" class="text-red-500"> + Username must not be empty. + </p> </div> - <div class="my-4"> - <h1 class="text-left font-bold mb-2 text-xl sm:text-2xl font-montserrat"> - Current Role + <div class="my-4" :class="{ invalid: !current_role.isValid }"> + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > + Current Role* </h1> <select id="inline-current-role" - v-model="current_role" - class="bg-white pb-2 w-4/5 text-gray-700 outline-none bg-transparent border-b hover:border-blue-700 focus:bg-gray-200 focus:border-blue-700" + v-model="current_role.val" + @blur="clearValidity('current_role')" + class=" + bg-white + px-4 + py-5 + rounded-lg + border border-gray-400 + w-full + text-gray-700 + outline-none + bg-transparent + border-b + hover:border-blue-700 + " > - <option - id="phd" - value="PhD" - > - PhD - </option> - <option - id="postdoc" - value="PostDoc" - > - PostDoc - </option> - <option - id="research_scientist" - value="Research_Scientist" - > + <option id="phd" value="PhD">PhD</option> + <option id="postdoc" value="PostDoc">PostDoc</option> + <option id="research_scientist" value="Research_Scientist"> Research Scientist </option> <option @@ -116,47 +239,189 @@ Principal Investigator </option> </select> + <p v-if="!current_role.isValid" class="text-red-500"> + Select a current role. + </p> + </div> + <div + class="my-4" + :class="{ invalid: !scientific_discipline.isValid }" + > + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > + Scientific Disciplines* + </h1> + <div + v-for="options in scientificDisciplineOptions" + :key="options.id" + > + <input + :id="options.id" + v-model="scientific_discipline.val" + type="checkbox" + class="h-6 w-6" + :name="options" + :value="options.discipline" + @blur="clearValidity('scientific_discipline')" + /> + <label class="mx-3" :for="options.id">{{ + options.discipline + }}</label> + </div> + <p v-if="!scientific_discipline.isValid" class="text-red-500"> + Please select atleast one scientific discipline. + </p> + </div> + <div class="my-4" :class="{ invalid: !affiliation.isValid }"> + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > + Affiliation* + </h1> + <input + v-model.trim="affiliation.val" + type="text" + @blur="clearValidity('affiliation')" + class=" + text-xl + outline-none + px-4 + py-5 + rounded-lg + border border-gray-400 + w-full + bg-transparent + hover:border-blue-700 + focus:border-blue-700 + " + placeholder="Institute name currently associated with." + /> + <p v-if="!affiliation.isValid" class="text-red-500"> + Affiliation must not be empty. + </p> + </div> + <div class="my-4" :class="{ invalid: !profileLink.isValid }"> + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > + Profile Link + </h1> + <input + v-model.trim="profileLink.val" + @blur="clearValidity('profileLink')" + type="text" + class=" + text-xl + outline-none + px-4 + py-5 + rounded-lg + border border-gray-400 + w-full + bg-transparent + hover:border-blue-700 + focus:border-blue-700 + " + placeholder="eg- https://www.google.com (optional)" + /> + <p v-if="!profileLink.isValid" class="text-red-500"> + The url entered is invalid. Please check it again! + </p> + <p + v-if="profileLinkError && profileLinkErrMsg" + class="text-red-500" + > + {{ profileLinkErrMsg }} + </p> </div> - <div class="my-4"> - <h1 class="text-left font-bold mb-2 text-xl sm:text-2xl font-montserrat"> - Motivation + <div class="my-4" :class="{ invalid: !motivation.isValid }"> + <h1 + class=" + text-left + font-bold + mb-2 + text-xl + sm:text-2xl + font-montserrat + " + > + Motivation* </h1> <textarea - v-model.trim="motivation" + v-model.trim="motivation.val" rows="5" type="text" - class="text-xl outline-none pb-2 w-4/5 bg-transparent border-b hover:border-blue-700 focus:border-blue-700" + @blur="clearValidity('motivation')" + class=" + text-xl + outline-none + w-full + bg-transparent + px-4 + py-5 + rounded-lg + border border-gray-400 + hover:border-blue-700 + focus:border-blue-700 + " + placeholder="Your motivation text." /> </div> - <div class="flex space-x-4"> + <p v-if="!motivation.isValid" class="text-red-500"> + Motivation text must not be empty. + </p> + <p v-if="!formIsValid" class="font-bold text-red-400"> + Required fields were not provided. + </p> + <p v-show="error" class="text-red-500"> + {{ errorMsg }} + </p> + <div v-if="isLoading"> + <base-spinner /> + </div> + <button type="submit" - :disabled="name.length < 6 || password.length < 6 || username.length < 3 || motivation==='' || current_role===''" - :class="name.length < 6 || password.length < 6 || username.length < 3 || motivation==='' || current_role==='' ? 'bg-green-400 text-3xl font-bold rounded-lg p-5 text-white opacity-75' : 'bg-green-400 text-3xl font-bold rounded-lg p-5 text-white hover:bg-green-600'" - > - Sign Up <font-awesome-icon - size="lg" - class="ml-2" - icon="fa-solid fa-right-to-bracket" - /> - </button> - <button - - type="button" class=" - - - bg-blue-600 - text-3xl font-bold rounded-lg p-5 text-white hover:bg-blue-600 - - " - @click="$router.go(-1)" + bg-blue-500 + text-2xl + font-bold + rounded-lg + p-5 + mr-4 + w-1/3 + text-white + hover:bg-blue-600 + " > - <font-awesome-icon - size="lg" - icon="fa-solid fa-circle-arrow-left" - /> Back + Create Account + </button> + + <div> + <h4 class="mt-6">Already have an account? <a class="text-blue-500 hover:text-blue-600 hover:no-underline" @click="$router.go(-1)">Sign in</a></h4> </div> </form> </div> @@ -165,75 +430,270 @@ </div> </template> <script> -import BaseToaster from '../components/UI/BaseToaster.vue'; -let host = require('@/components/js/const').apiHost; +import BaseSpinner from "../components/UI/BaseSpinner.vue"; +import BaseToaster from "../components/UI/BaseToaster.vue"; +let host = require("@/components/js/const").apiHost; export default { - name: 'Register', - components: { BaseToaster }, + name: "Register", + components: { BaseToaster, BaseSpinner }, data() { return { - name: '', - email: '', - password: '', - username: '', - motivation: '', - current_role: '', - scientific_discipline: [], + name: { + val: "", + isValid: true, + }, + email: { + val: "", + isValid: true, + }, + password: { + val: "", + isValid: true, + }, + username: { + val: "", + isValid: true, + }, + motivation: { + val: "", + isValid: true, + }, + affiliation: { + val: "", + isValid: true, + }, + current_role: { + val: "", + isValid: true, + }, + profileLink: { + val: "", + isValid: true, + }, + scientific_discipline: { + val: [], + isValid: true, + }, + formIsValid: true, error: false, - errorMsg: `An Error occurred, please try again`, - isDev: process.env.NODE_ENV === 'development', - toasterIsOpen: false - } - }, - computed:{ - + errorMsg: ``, + isDev: process.env.NODE_ENV === "development", + toasterIsOpen: false, + scientificDisciplineOptions: [ + { id: "1", discipline: "Physics" }, + { id: "2", discipline: "Chemistry" }, + { id: "3", discipline: "Biology" }, + { id: "4", discipline: "Computer Science" }, + { id: "5", discipline: "Mathematics" }, + ], + profileLinkError: false, + profileLinkErrMsg: "", + selected: [], + isLoading: false, + }; }, + computed: {}, methods: { - showDialog() { + clearValidity(input) { + this[input].isValid = true; + }, + validateForm() { + this.formIsValid = true; + if (this.name.val === "") { + this.name.isValid = false; + this.formIsValid = false; + } + if (this.email.val === "") { + this.email.isValid = false; + this.formIsValid = false; + } + if (this.password.val === "" || this.password.val.length < 6) { + + this.password.isValid = false; + this.formIsValid = false; + } + if (this.username.val === "" || this.username.val.length < 3) { + this.username.isValid = false; + this.formIsValid = false; + } + if (this.current_role.val === "") { + this.current_role.isValid = false; + this.formIsValid = false; + } + if (this.scientific_discipline.val.length === 0) { + this.scientific_discipline.isValid = false; + this.formIsValid = false; + } + if (this.affiliation.val === "") { + this.affiliation.isValid = false; + this.formIsValid = false; + } + if (this.isValidHttpUrl() === false) { + this.profileLink.isValid = false; + this.formIsValid = false; + } + if (this.motivation.val === "") { + this.motivation.isValid = false; + this.formIsValid = false; + } + }, + isValidHttpUrl() { + let timer = null; + let pattern = new RegExp( + "^(https?:\\/\\/)?" + // protocol + "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name + "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address + "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path + "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string + "(\\#[-a-z\\d_]*)?$", + "i" + ); // fragment locator + let isValid; + if (this.profileLink.val === "") { + console.log("profile link empty"); + return true; + } else { + isValid = pattern.test(this.profileLink.val); + console.log("lint is there", isValid); + } + return isValid; + // if(!isValid){ + // console.log("triggered"); + // this.profileLinkError= true; + // this.profileLinkErrMsg="The url entered is invalid. Please check it again!" + // } else{ + // this.profileLinkError=false; + // this.profileLinkErrMsg="" + // } + // clearTimeout(timer); + // timer= setTimeout(()=>{ + // let isValid= pattern.test(this.profileLink.val); + // if(!isValid){ + // console.log("triggered"); + // this.profileLinkError= true; + // this.profileLinkErrMsg="The url entered is invalid. Please check it again!" + // } else{ + // this.profileLinkError=false; + // this.profileLinkErrMsg="" + // } + + // }, 1000) + }, + showDialog() { this.toasterIsOpen = true; }, hideDialog() { this.toasterIsOpen = false; - this.$router.push('login') - + this.$router.push("login"); }, async register(e) { - const vm = this - - - e.preventDefault() + const vm = this; - + vm.validateForm(); + if (!vm.formIsValid) { + return; + } - if(vm.isDev) { - host = require('@/components/js/const').devApiHost; - } -try { - await vm.axios.post(`${host}/api/auth/local/register`, { - full_name: vm.name, - password: vm.password, - email: vm.email, - username: vm.username, - motivation_text: vm.motivation, - current_role: vm.current_role, - scientific_discipline: vm.scientific_discipline - }) - vm.toasterIsOpen= true; - //vm.$router.push('login') - } catch(e) { - // console.error(e) - vm.error = true - vm.email = '' + if (vm.isDev) { + host = require("@/components/js/const").devApiHost; } - } - } -} + this.isLoading = true; + const res = await fetch(`${host}/api/auth/local/register`, { + method: "POST", + mode: "cors", // no-cors, *cors, same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, + headers: { + "Content-Type": "application/json", + // 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: JSON.stringify({ + full_name: vm.name.val, + password: vm.password.val, + email: vm.email.val, + username: vm.username.val, + motivation_text: vm.motivation.val, + current_role: vm.current_role.val, + scientific_discipline: vm.scientific_discipline.val, + current_affiliation: vm.affiliation.val, + profile_link: vm.profileLink.val, + }), + }); + if (!res.ok && res.status === 500) { + this.isLoading = false; + this.error = true; + this.errorMsg = `${res.status} Internal Error, please write a mail to DDCode Admin`; + + return; + } + if (!res.ok && res.status === 405) { + this.isLoading = false; + this.error = true; + this.errorMsg = `${res.status} Internal Error, please write a mail to DDCode Admin`; + + return; + } + const response = await res.json(); + if (!res.ok && response.error.status === 400 && response.error.message === "Email is already taken") { + this.isLoading = false; + this.error = true; + this.errorMsg = "Email Address already registered with an existing account. You can reset your password by clicking forget password from sign up page."; + this.password.val = ""; + return; + } + this.isLoading = false; + vm.toasterIsOpen = true; + this.error = false; + this.errorMsg = ""; + // try { + // await vm.axios.post(`${host}/api/auth/local/register`, { + // full_name: vm.name.val, + // password: vm.password.val, + // email: vm.email.val, + // username: vm.username.val, + // motivation_text: vm.motivation.val, + // current_role: vm.current_role.val, + // scientific_discipline: vm.scientific_discipline.val, + // current_affiliation: vm.affiliation.val, + // profile_link: vm.profileLink.val, + // }); + + // this.isLoading = false; + // vm.toasterIsOpen = true; + // this.error = false; + // this.errorMsg = ""; + // //vm.$router.push('login') + // } catch (e) { + // console.error(e); + // this.isLoading = false; + // if (e.message === "Request failed with status code 405") { + // console.log(e.message); + // } + // if (e.message === "Request failed with status code 400") { + // vm.error = true; + // vm.errorMsg = + // "Email Address already registered with an existing account. You can reset your password by clicking forget password from sign up page."; + // } + + + // } + }, + }, +}; </script> <style> - @import url('~@/assets/bootstrap.css'); +@import url("~@/assets/bootstrap.css"); - a { - color: #42b983; - } +a { + color: #42b983; +} +.invalid h1, +.invalid label { + color: red; +} +.invalid input, +.invalid textarea, +.invalid select { + border: 1px solid red; +} </style> \ No newline at end of file diff --git a/web/src/views/UpdateItem.vue b/web/src/views/UpdateItem.vue index efa5dd07bd73ccc40eadf608eac30f4fbf827593..7c1e0a7c913ab80e2e3abbb7f1e7a40deb5270db 100644 --- a/web/src/views/UpdateItem.vue +++ b/web/src/views/UpdateItem.vue @@ -32,7 +32,7 @@ </h3> <div v-if="item === 'new' || loaded" - class="md:flex w-3/5 card p-2 mt-5" + class="md:flex w-3/5 border border-gray-300 rounded-lg p-2 mt-5" > <div class="p-5 mx-auto text-left font-raleway container max-w-screen-md" @@ -332,6 +332,30 @@ {{ item === "new" ? "Submit" : "Update" }} <span class="fa fa-arrow-right" /> </button> --> + <div class="md:flex md:items-center mx-3 mb-6"> + <div class="md:w-1/3"> + <label class="text-left font-bold md:text-right mb-1 md:mb-0 pr-4" for="inline-reviewer-comment"> + Sync Response + </label> + </div> + <div class="md:w-2/3"> + <p class="mt-3"> + {{syncResponse}} + </p> + </div> + </div> + <div class="md:flex md:items-center mx-3 mb-6"> + <div class="md:w-1/3"> + <label class="text-left font-bold md:text-right mb-1 md:mb-0 pr-4" for="inline-reviewer-comment"> + Synced At + </label> + </div> + <div class="md:w-2/3"> + <p class="mt-3"> + {{syncedAt}} + </p> + </div> + </div> <div v-if=" @@ -466,33 +490,47 @@ item !== 'new' && status!='synced'" type="submit" :disabled="entityId.length < 3" - class="bg-green-400 font-bold rounded-lg p-5 text-white" + class="text-white + bg-blue-600 + hover:bg-blue-700 + focus:ring-2 focus:ring-blue-300 + rounded-lg + inline-flex + items-center + px-5 + py-2.5 + text-center + font-bold" > - {{ "Update Review" }} <font-awesome-icon icon="fa-solid fa-pen-to-square" /> + {{ "Update Review" }} </button> - <button + <div class="group"> +<button type="button" class=" - text-white - font-bold - rounded-lg - bg-blue-600 - hover:bg-blue-800 - focus:ring-4 focus:ring-blue-300 - rounded-lg - items-center - px-5 - py-4 - text-center - mr-2 + bg-gray + + group-hover:bg-gray-400 + group-hover:text-white + + + focus:ring-2 focus:ring-gray-300 + rounded-lg + border border-gray-300 + px-5 + py-3 + hover:text-gray-900 + font-bold " @click="$router.go(-1)" > - <span class="fa fa-arrow-left" /> Back + Back </button> - </div> + + </div> + </div> </form> </div> </div> @@ -703,8 +741,8 @@ vm.isLoading= true; // ReviewedBy: // ReviewedAt: // ReviewComment: - // SyncedAt: - // SyncResponse: + vm.syncedAt=d.SyncedAt; + vm.syncResponse=d.SyncResponse } vm.isLoading=false; vm.loaded = true; diff --git a/web/src/views/UpdateItems.vue b/web/src/views/UpdateItems.vue index 62e83adedadda07ce2f1f4e240e4d092a719a9c5..4f1c3c3c56fec8b8b2c23941e5f7d988f744961a 100644 --- a/web/src/views/UpdateItems.vue +++ b/web/src/views/UpdateItems.vue @@ -3,11 +3,14 @@ id="page-content-wrapper" class="main" > - <div v-if="userData !== null && (userRole === 'Contributor' || userRole === 'Maintainer')"> + <div class="container border border-gray-300 rounded-lg mt-5" v-if="userData !== null && (userRole === 'Contributor' || userRole === 'Maintainer')"> <h2 class="round"> Update Items </h2> + <update-item-table id="update-item-table" /> + + </div> </div> </template> diff --git a/web/tailwindcss.config.js b/web/tailwindcss.config.js index 675511e66db483ea772d6d1ae55901fc1ea45d00..3ff2898c1a995f3771468e84ac328b8e68795267 100644 --- a/web/tailwindcss.config.js +++ b/web/tailwindcss.config.js @@ -12,6 +12,10 @@ module.exports = { 'raleway': ['Raleway'], } }, + colors: { + // Configure your color palette here + + } }, variants: { extend: {},