Set region information for static text, one example

This commit is contained in:
jetli 2024-06-18 17:13:12 +08:00
commit 22597b552f
53 changed files with 2988 additions and 1650 deletions

View File

@ -11,13 +11,11 @@
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"@vueup/vue-quill": "^1.2.0",
"axios": "^1.4.0",
"bootstrap": "^5.3.1",
"buffer": "^6.0.3",
"pdfjs-dist": "^4.2.67",
"pdfjs-dist": "^4.3.136",
"pinia": "^2.1.6",
"quill": "^1.3.7",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"vuex": "^4.1.0"
@ -28,12 +26,12 @@
"@vue/eslint-config-prettier": "^8.0.0",
"eslint": "^8.45.0",
"eslint-plugin-vue": "^9.15.1",
"fast-glob": "^3.3.2",
"prettier": "^3.0.0",
"sass": "^1.66.1",
"sass-loader": "^13.3.2",
"vite": "^4.4.6",
"vite-plugin-svg-icons": "^2.0.1",
"fast-glob": "^3.3.2",
"webpack": "^5.88.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -1,40 +1,48 @@
.stop-button {
@extend .btn;
@extend .btn-secondary;
@extend .btn-outline-secondary;
@extend .btn-sm;
}
.back-button {
@extend .btn;
@extend .btn-secondary;
@extend .btn-outline-secondary;
@extend .btn-sm;
}
.proceed-button {
@extend .btn;
@extend .btn-primary;
@extend .btn-sm;
}
.initiate-button {
@extend .btn;
@extend .btn-primary;
@extend .btn-sm;
}
.proceed-button {
@extend .btn;
@extend .btn-primary;
@extend .btn-sm;
}
.option-button {
@extend .btn;
@extend .btn-primary;
@extend .btn-sm;
}
.close-button {
@extend .btn-close;
@extend .btn-sm;
}
.light-button {
@extend .btn;
@extend .btn-light;
@extend .btn-sm;
}
.inplace-proceed-button {
@ -44,3 +52,7 @@
.inplace-back-button {
@extend .light-button;
}
.min-btn {
min-width: 115px;
}

View File

@ -104,3 +104,131 @@ p {
height: 100%;
margin: 0 auto;
}
.btn-link {
text-decoration: none;
color: $primary;
}
.dropdown-menu {
border: none;
box-shadow: 0px 0px 24px 0px #d4d3e380;
padding: 0;
overflow: hidden;
.btn-link {
text-decoration: none;
height: 33px;
width: 100%;
color: #0d1637;
font-size: 14px;
text-align: left;
border-radius: 0;
&:hover {
background-color: #f3f6ff;
}
&.active {
background-color: #1748f8;
color: white;
}
}
.dropdown-new {
color: #1748f8;
&:hover {
background-color: white;
}
}
.dropdown-new-input-container {
padding: 6px 12px;
position: relative;
input {
border: 1px solid #e7e8eb;
box-shadow: none;
border-radius: 3px;
padding: 0 20px 0 5px;
height: 22px;
font-size: 14px;
}
.dropdown-new-input-icon {
width: 16px;
height: 16px;
padding: 3px;
color: #3d455f;
background-color: #eeeff1;
border-radius: 3px;
position: absolute;
top: 10px;
right: 15px;
}
}
}
.container {
@media (min-width: 1200px) {
max-width: $body-width;
}
}
.accordion-list {
box-shadow: 0px 0px 24px 0px #d4d3e380;
border: none;
border-radius: 12px;
margin-bottom: 16px;
.accordion-item {
border: none;
}
}
.accordion-button {
padding: 12px 28px 12px 12px;
outline: none;
box-shadow: none !important;
.dashed-container {
flex: 1;
margin-right: 28px;
padding: 8px 12px;
font-weight: bold;
border: 1px dashed #aebffd;
border-radius: 3px;
}
&:not(.collapsed) {
color: black;
background-color: transparent;
box-shadow: none;
border-bottom: 1px solid #dee2e6;
.dashed-container {
background-color: #f3f6ff;
}
}
}
.submission-result-container {
@extend .flex-row-container;
align-items: center;
justify-content: center;
min-height: $body-height;
}
.submission-result-card {
width: fit-content;
min-width: 641px;
padding: 45px 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 26px;
line-height: 36px;
box-shadow: 0px 0px 24px 0px #d4d3e380;
img {
width: 86px;
margin-bottom: 20px;
}
.btn-link {
text-decoration: underline;
padding: 0;
font-size: inherit;
}
}
.upload-contianer {
display: flex;
align-items: center;
}

View File

@ -0,0 +1,520 @@
<template>
<div class="freeleaps-editor">
<div
class="editor-body"
:contenteditable="!disabled"
spellcheck="false"
ref="editor"
v-html="content"
@blur="updateAction"
@mouseup.stop="selectionChange"
/>
<div v-if="!disabled" class="editor-control" :style="editorCtrlStyle">
<div v-for="(item, index) in iconList" :key="index" class="editor-item">
<button
class="item-icon"
:class="{
activity: commandStates.indexOf(item.type) !== -1,
last: index === iconList.length - 1
}"
:data-info="item.name"
@click="iconClick($event, item.type)"
:data-bs-toggle="item.drop ? 'dropdown' : ''"
data-bs-auto-close="outside"
aria-expanded="false"
>
<svg-icon :icon="item.icon" class-name="icon" />
</button>
<div class="dropmenu drop-style" v-if="item.type === 'style'">
<ul>
<li>
<a href="#" @click="iconClick($event, 'p', 'style')"><p>Text</p></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'pre', 'style')"><pre>code</pre></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'blockquote', 'style')"
><blockquote>引用</blockquote></a
>
</li>
<li>
<a href="#" @click="iconClick($event, 'h1', 'style')"><h1>Caption 1</h1></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'h2', 'style')"><h2>Caption 2</h2></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'h3', 'style')"><h3>Caption 3</h3></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'h4', 'style')"><h4>Caption 4</h4></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'h5', 'style')"><h5>Caption 5</h5></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'h6', 'style')"><h6>Caption 6</h6></a>
</li>
</ul>
</div>
<div class="dropmenu drop-style" v-if="item.type === 'alignjustify'">
<ul>
<li>
<a href="#" @click="iconClick($event, 'justifyCenter', 'alignjustify')">
<i class="iconfont icon-aligncenter"></i>
<span>middle</span>
</a>
</li>
<li>
<a href="#" @click="iconClick($event, 'justifyLeft', 'alignjustify')">
<i class="iconfont icon-alignleft"></i>
<span>left aligned</span>
</a>
</li>
<li>
<a href="#" @click="iconClick($event, 'justifyRight', 'alignjustify')">
<i class="iconfont icon-alignright"></i>
<span>right aligned</span>
</a>
</li>
<li>
<a href="#" @click="iconClick($event, 'justifyFull', 'alignjustify')">
<i class="iconfont icon-alignjustify"></i>
<span>default aligned</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import SvgIcon from '@/components/SvgIcon.vue'
export default {
name: 'FreeleapsEditor',
props: {
content: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
}
},
emits: ['update:content'],
components: { SvgIcon },
data() {
return {
selectedRange: '',
editorCtrlStyle: {},
commandStates: [],
iconList: [
// {
// name: 'lable', // hover name
// type: 'style', // click event handler
// icon: 'fe-paragraph', // icon style
// drop: true, // If there is drop menu
// canChoose: true, // chosen or not
// },
{
name: 'bold',
type: 'bold',
icon: 'fe-bold',
drop: false,
canChoose: true
},
{
name: 'italic',
type: 'italic',
icon: 'fe-italic',
drop: false,
canChoose: true
},
{
name: 'underline',
type: 'underline',
icon: 'fe-underline',
drop: false,
canChoose: true
},
// {
// name: 'strike',
// type: 'strike',
// icon: 'fe-strike',
// drop: false,
// canChoose: true,
// },
// {
// name: 'clear-format',
// type: 'clear',
// icon: 'fe-clear',
// drop: false,
// canChoose: false,
// },
// {
// name: 'font-color',
// type: 'fontFamily',
// icon: 'fe-char',
// drop: false,
// canChoose: true,
// },
{
name: 'unordered-list',
type: 'unorderedlist',
icon: 'fe-unorderedlist',
drop: false,
canChoose: true
},
{
name: 'ordered list',
type: 'orderedlist',
icon: 'fe-orderedlist',
drop: false,
canChoose: true
}
// {
// name: 'align-justify',
// type: 'alignjustify',
// icon: 'fe-alignjustify',
// drop: true,
// canChoose: true,
// }
]
}
},
methods: {
selectionChange(e) {
const sel = window.getSelection()
if (sel && sel.type === 'Range') {
// this.selectedRange = sel.getRangeAt(0)
// this.restoreSelection()
// console.log('window.getSelection',this.selectedRange)
// console.log('is bold ? ', this.queryCommandState(sel.getRangeAt(0), 'bold'))
// this.iconClick(null, 'bold')
this.editorCtrlStyle = { display: 'flex', top: `${e.layerY}px`, left: `${e.layerX}px` }
} else {
this.selectedRange = null
this.editorCtrlStyle = { display: 'none' }
// this.commandStates = []
}
},
queryCommandState(range, command) {
const container = document.createElement('span')
container.style.display = 'none'
container.appendChild(range.cloneContents())
console.log('queryCommandState', container)
// range
document.body.appendChild(container)
const state = document.queryCommandState(command)
console.log('queryCommandIndeterm ', document.queryCommandIndeterm('bold'))
console.log('queryCommandState ', document.queryCommandState('bold'))
// range.collapse(true);
container.remove()
return state
},
iconClick(event, type, dropType) {
event.preventDefault()
this.$refs.editor.focus()
this.selectedRange = this.getSelect()
this.restoreSelection()
this.changeStyle(type)
this.$nextTick(() => {
if (dropType) {
type = dropType
}
const i = this.commandStates.indexOf(type)
if (i === -1) {
this.commandStates.push(type)
} else {
this.commandStates.splice(i, 1)
}
})
},
getSelect() {
if (window.getSelection) {
let sel = window.getSelection()
console.log('this.selectedRange', sel)
if (sel.rangeCount > 0) {
return sel.getRangeAt(0)
}
} else if (document.selection) {
return document.selection.createRange()
}
return null
},
// change the selection's style
changeStyle(type) {
switch (type) {
case 'bold':
document.execCommand('bold', false)
break
case 'underline':
document.execCommand('underline', false)
break
case 'strike':
document.execCommand('strikeThrough', false)
break
case 'italic':
document.execCommand('italic', false)
break
case 'clear':
document.execCommand('removeFormat', false)
break
case 'unorderedlist':
document.execCommand('insertUnorderedList', false)
break
case 'orderedlist':
document.execCommand('insertorderedList', false)
break
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
case 'p':
case 'pre':
case 'blockquote':
document.execCommand('formatBlock', false, type)
break
case 'justifyCenter':
case 'justifyFull':
case 'justifyLeft':
case 'justifyRight':
document.execCommand(type, false)
break
default:
console.log('none')
}
// window.getSelection().removeAllRanges()
},
restoreSelection() {
let selection = window.getSelection()
if (this.selectedRange) {
try {
selection.removeAllRanges()
} catch (ex) {
document.body.createTextRange().select()
document.selection.empty()
}
selection.addRange(this.selectedRange)
}
},
updateAction($event) {
let html = $event.target.innerHTML || ''
this.$emit('update:content', html)
}
}
}
</script>
<style lang="scss" scoped>
.freeleaps-editor {
position: relative;
min-width: 100px;
background-color: #fff;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
h1,
h2,
h3,
h4,
h5,
h6,
.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
p,
a {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
margin: 0;
text-decoration: none;
}
code,
kbd,
pre,
samp {
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
}
.editor-control {
display: none;
width: fit-content;
height: 26px;
background-color: #f8f8f9;
position: absolute;
}
.dropmenu {
display: none;
position: absolute;
top: 32px;
left: 0;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 4px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
ul {
padding: 10px;
margin-bottom: 0;
}
ul li {
text-align: left;
list-style: none;
a {
display: block;
padding: 5px 10px;
white-space: nowrap;
}
}
}
.drop-align {
min-width: 100px;
}
.editor-body {
height: 300px;
padding: 10px;
color: #000;
background-color: #fff;
overflow: auto;
outline: none;
text-align: left;
border-radius: 4px;
position: relative;
p {
font-size: 14px;
color: #68747f;
margin: 0 0 10px;
}
}
}
.editor-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
cursor: pointer;
.item-icon {
position: relative;
width: 50px;
height: 16px;
font-size: 16px;
color: #9ea2af;
font-weight: normal;
white-space: nowrap;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: none;
transition: all 0.1s ease-out;
border: none;
line-height: 16px;
background: transparent;
border-right: 1px solid #e7e8eb;
&.last {
border-right: 0;
}
&::after {
position: absolute;
top: 0;
content: attr(data-info);
top: 40px;
left: 20px;
padding: 5px 8px;
border-radius: 4px;
white-space: nowrap;
line-height: 1.5;
font-size: 13px;
color: #fff;
background: rgba(0, 0, 0, 0.8);
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
visibility: hidden;
opacity: 0.9;
letter-spacing: 1px;
z-index: 9999;
}
&::before {
position: absolute;
content: '';
top: 35px;
left: 20px;
width: 0;
height: 0;
margin: 0 0 0 -6px;
font-size: 0;
color: rgba(0, 0, 0, 0.8);
border-bottom: 6px solid currentColor;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
visibility: hidden;
opacity: 0.9;
z-index: 9999;
}
&:hover,
&.activity {
color: black;
}
&:hover:after,
&:hover:before {
visibility: visible;
}
&.activity + .dropmenu {
display: block;
}
}
}
</style>
<style>
.freeleaps-editor blockquote {
padding: 10px 20px;
font-size: 17.5px;
border-left: 5px solid #f86466;
background: white;
}
.freeleaps-editor pre {
display: block;
padding: 9.5px;
font-size: 13px;
line-height: 1.42857143;
color: #333;
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
}
.editor-body blockquote {
margin-bottom: 30px;
}
.editor-body pre {
margin-bottom: 10px;
}
.editor-body ul {
padding-left: 40px;
list-style-type: disc;
}
.editor-body ol {
padding-left: 40px;
}
</style>

View File

@ -0,0 +1,105 @@
<template>
<div class="input-selector-container">
<div
class="input-selector-btn"
data-bs-toggle="dropdown"
aria-expanded="false"
id="input-selector-btn"
>
<span>{{ selected || '' }}</span>
<svg-icon icon="dropdown" class-name="selector-dropdown-icon" />
</div>
<ul class="dropdown-menu" aria-labelledby="input-selector-btn">
<li>
<button v-if="!inputing" class="btn btn-link dropdown-new" @click="newAction">+ NEW</button>
<div v-if="inputing" class="dropdown-new-input-container">
<input type="text" v-model="newval" @keyup.enter="newItemAction" />
<svg-icon v-if="newval" icon="msg-enter" class-name="dropdown-new-input-icon" />
</div>
</li>
<li v-for="item in selectList" :key="item">
<button
class="btn btn-link"
@click="selectItem(item)"
:class="item == selected ? 'active' : ''"
>
{{ item }}
</button>
</li>
</ul>
</div>
</template>
<script>
import SvgIcon from '@/components/SvgIcon.vue'
export default {
name: 'InputSelector',
props: {
selectList: {
type: Array,
default: () => []
},
selected: {
type: String,
default: ''
}
},
data() {
return {
inputing: true,
newval: ''
}
},
mounted() {},
components: { SvgIcon },
methods: {
newAction($event) {
$event.stopPropagation()
this.newval = ''
this.inputing = true
},
selectItem(item) {
this.$emit('selectedChange', { selected: item, isNew: false })
},
newItemAction() {
if (!this.newval) return
this.$emit('selectedChange', {
selected: this.newval,
isNew: this.selectList.indexOf(this.newval) === -1
})
this.newval = ''
this.inputing = false
}
}
}
</script>
<style lang="scss">
.input-selector-container {
width: fit-content;
height: fit-content;
position: relative;
.input-selector-btn {
width: 153px;
height: 30px;
display: flex;
align-items: center;
overflow: hidden;
cursor: pointer;
border: 1px solid #e7e8eb;
border-radius: 3px;
padding: 0 3px 0 5px;
> span {
font-size: 18px;
font-weight: 500;
color: #242424;
flex: 1;
text-align: left;
}
.selector-dropdown-icon {
color: #242424;
padding: 3px;
}
}
}
</style>

View File

@ -1,81 +1,91 @@
<template>
<div class="pdf-reader">
<canvas ref="canvas"></canvas>
<div id="pdf-container" class="pdf-container">
<canvas id="theCanvas"></canvas>
<div class="oprate">
<button class="btn btn-link" @click="prev">prev</button>
<span> {{ currentPage }} / {{ numPages }} </span>
<button class="btn btn-link" @click="next">next</button>
</div>
</div>
</template>
<script>
// import { getDocument } from 'pdfjs-dist'
import * as PDFJS from 'pdfjs-dist'
// import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
import { ContentApi } from '@/utils/index'
// PDFJS.GlobalWorkerOptions.workerSrc = import('pdfjs-dist/build/pdf.worker')
import * as PDFJS from 'pdfjs-dist/build/pdf'
PDFJS.GlobalWorkerOptions.workerPort = new Worker(
new URL('pdfjs-dist/build/pdf.worker.mjs', import.meta.url),
{ type: 'module' }
)
// import * as PDFJS from 'pdfjs-dist/webpack.mjs'
export default {
name: 'PDFReader',
props: {
document: { type: String, default: '' }
doc: { type: Object, default: () => {} }
},
mounted() {
this.renderPDF()
data() {
return {
loadingTask: null,
currentPage: 1,
numPages: 0,
selectedPage: 1
}
},
watch: {
doc() {
if (this.doc.url || this.doc.data) {
this.renderPDF()
}
}
},
methods: {
async renderPDF() {
// const response = await DocumentApi.download(this.document)
const response = await ContentApi.retrieve_blog_content(this.document)
console.log('response', response)
// var fr = new FileReader();
// fr.onload = async function(){
// const doc = await getDocument({url: this.result}).promise
// const page = await doc.getPage(1)
// const canvas = this.$refs.canvas
// const context = canvas.getContext('2d')
// const viewport = page.getViewport({ scale: 1 })
// canvas.width = viewport.width
// canvas.height = viewport.height
// await page.render({
// canvasContext: context,
// viewport
// })
// }
// fr.readAsDataURL(response.data);
const doc = await PDFJS.getDocument({ data: response.data }).promise
const page = await doc.getPage(1)
const canvas = this.$refs.canvas
renderPage(num) {
const canvas = document.getElementById('theCanvas')
const context = canvas.getContext('2d')
const viewport = page.getViewport({ scale: 1 })
canvas.width = viewport.width
canvas.height = viewport.height
await page.render({
canvasContext: context,
viewport
const scale = 1.5
this.loadingTask.promise.then((pdf) => {
pdf.getPage(num).then((page) => {
const viewport = page.getViewport({ scale })
canvas.height = viewport.height
canvas.width = viewport.width
const renderContext = {
canvasContext: context,
viewport: viewport
}
page.render(renderContext)
})
})
},
base64StringToUint8Array(base64String) {
var padding = '='.repeat((4 - (base64String.length % 4)) % 4)
var base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
var rawData = atob(base64)
var outputArray = new Uint8Array(rawData.length)
for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i)
renderPDF() {
this.loadingTask = PDFJS.getDocument(this.doc)
this.loadingTask.promise.then((pdf) => {
this.numPages = pdf.numPages
this.renderPage(1)
})
},
prev() {
if (this.currentPage > 1) {
this.currentPage--
this.renderPage(this.currentPage)
}
},
next() {
if (this.currentPage < this.numPages) {
this.currentPage++
this.renderPage(this.currentPage)
}
return outputArray
}
}
}
</script>
<style scoped>
.pdf-reader {
width: 100%;
height: 100%;
<style lang="scss" scoped>
.pdf-container {
position: relative;
.operate {
position: absolute;
right: 0;
top: 0;
}
}
</style>

View File

@ -1,70 +0,0 @@
<template>
<div class="vue-quill-container">
<QuillEditor
ref="quillRef"
v-model:content="quillData"
:options="quillOptions"
content-type="html"
@text-change="quillChange()"
/>
</div>
</template>
<script>
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ direction: 'rtl' }],
[{ size: ['small', false, 'large', 'huge'] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ font: [] }],
[{ align: [] }],
['clean']
]
import { QuillEditor } from '@vueup/vue-quill'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
export default {
name: 'VueQuill',
props: {
content: {
type: String,
default: ''
}
},
emits: ['update:content'],
components: { QuillEditor },
data() {
return {
quillData: this.content,
quillOptions: {
theme: 'snow',
placeholder: '请输入',
modules: {
toolbar: {
container: toolbarOptions
}
}
}
}
},
methods: {
quillChange() {
this.$emit('update:content', this.quillData)
}
}
}
</script>
<style>
.vue-quill-container {
width: 100%;
height: 100%;
min-height: 100px;
}
</style>

View File

@ -1,26 +1,61 @@
<template>
<div class="header-container">
<div class="header-content">
<div class="information-bar" @click="gotoMessages">
<div
class="information-bar"
@click="gotoMessages"
:class="activePath == 'message' ? 'active' : ''"
>
<img alt="freeleaps logo" src="@/assets/message.png" />
</div>
<div class="navigation-container" role="navigation">
<button class="navigation-item active" @click="gotoWorkspace">
<button
class="navigation-item"
@click="gotoWorkspace"
:class="activePath == 'Workspace' ? 'active' : ''"
>
<svg-icon icon="workspace" class-name="icon" />
Workspace
</button>
<button class="navigation-item" @click="gotoRequests">
<button
class="navigation-item"
@click="gotoRequests"
:class="activePath == 'Requests' ? 'active' : ''"
>
<svg-icon icon="requests" class-name="icon" />
Requests
</button>
<button class="navigation-item" @click="gotoProviders">
<button
class="navigation-item"
@click="gotoProviders"
:class="activePath == 'Providers' ? 'active' : ''"
>
<svg-icon icon="providers" class-name="icon" />
Providers
</button>
<button class="navigation-item" @click="gotoIssueRequest">
<button
class="navigation-item"
@click="gotoIssueRequest"
:class="activePath == 'Post' ? 'active' : ''"
>
<svg-icon icon="post" class-name="icon" />
Post
</button>
<div class="form-check form-switch header-switch-container">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="personal-earning-now-checkbox"
disabled
/>
<label class="form-check-label" for="personal-earning-now-checkbox">
<span>Providing service</span>
</label>
<div class="header-switch-desc">
Please go to profile page to add money receiving method
</div>
</div>
</div>
<div class="profile-container">
<img
@ -64,7 +99,13 @@ export default {
},
data() {
return {
userIdentityNote: this.mnx_getUserIdentity()
userIdentityNote: this.mnx_getUserIdentity(),
activePath: this.$route.meta.activePath
}
},
watch: {
$route(to) {
this.activePath = to.meta.activePath
}
},
methods: {
@ -156,6 +197,9 @@ export default {
top: 0;
background-color: #f44837;
}
&.active {
border: 1px solid $primary;
}
}
.profile-container {
@ -185,6 +229,21 @@ export default {
}
}
.header-switch-container {
position: relative;
.header-switch-desc {
position: absolute;
background-color: white;
left: 0;
bottom: -20px;
font-size: 12px;
color: #3D455f;
white-space: nowrap;
padding: 2px 5px;
}
}
.company-logo {
max-width: 100%;
max-height: 100%;

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1362" width="64" height="64"><path d="M512 81.465C274.223 81.465 81.465 274.222 81.465 512S274.221 942.535 512 942.535c237.776 0 430.535-192.756 430.535-430.535S749.777 81.465 512 81.465z m0 804.279c-206.413 0-373.743-167.331-373.743-373.743S305.588 138.257 512 138.257s373.743 167.331 373.743 373.744S718.412 885.744 512 885.744z m58.193-364.989c4.018-8.407 6.272-17.821 6.272-27.76 0-24.174-13.313-45.226-33-56.26V248.949c0-6.781-5.498-12.28-12.28-12.28h-38.372c-6.781 0-12.28 5.498-12.28 12.28v187.785c-19.686 11.034-33 32.088-33 56.26 0 35.603 28.862 64.465 64.465 64.465a64.251 64.251 0 0 0 24.82-4.959l177.381 177.382c4.797 4.797 12.571 4.797 17.367 0l15.194-15.194c4.797-4.795 4.797-12.569 0-17.367L570.192 520.755z"></path></svg>

After

Width:  |  Height:  |  Size: 821 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1523" width="64" height="64"><path d="M758.94993735 933.875H265.05006265c-76.72843323 0-144.04975104-72.15625991-144.04975103-154.40283381V223.87422138C121.00031161 147.61877182 182.9085958 90.125 265.05006265 90.125H581.37091262l5.78090947 4.88749603c92.07411987 78.67292102 153.14154456 140.68631304 230.39551537 219.25412622 24.43748016 24.75280288 50.3990192 51.18732477 79.4612266 80.249533l5.99112432 6.04367824v378.91233271c-0.05255393 82.24657388-67.32131699 154.40283381-144.04975103 154.4028338zM265.05006265 131.2745639c-59.64847366 0-102.90018713 38.94230732-102.90018714 92.59965748v555.59794482c0 59.28059783 49.0326221 113.20071597 102.90018714 113.20071598h493.8998747c53.81501111 0 102.90018713-53.92011814 102.90018714-113.20071598V417.58723914c-26.80239771-26.74984461-50.87200286-51.29243262-73.62776404-74.46862354-74.9416072-76.15034253-134.53752695-136.74478354-222.14458055-211.89660562H265.05006265z" fill="#4D4D4D" p-id="1524"></path><path d="M861.85012449 460.52362989h-174.95133921c-82.19402078 0-154.35027988-67.32131699-154.35027988-144.04975105V131.2745639a20.6010585 20.6010585 0 0 1 41.14956391 1e-8v185.19931493c0 53.81501111 53.97267205 102.90018713 113.20071597 102.90018715h174.95133921a20.6010585 20.6010585 0 1 1 0 41.1495639z"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5138" width="64" height="64"><path d="M993.310029 179.7115H892.968039l-45.464399 813.855781c-0.938917 16.951554-14.971621 30.209501-31.940191 30.209501H209.743828c-16.970573 0-30.998272-13.247937-31.936188-30.17747l-46.19311-813.887812H32.346588c-17.689275 0-32.002252-14.322987-32.002252-31.996247 0-17.666252 14.312978-31.989239 32.002252-31.989239h120.931097c1.457423 0 2.888821 0.106104 4.294194 0.294287a32.138385 32.138385 0 0 1 4.266166-0.294287h167.931996V31.99024c0-17.666252 14.312978-31.989239 31.998248-31.989239h301.08603c17.685271 0 31.995245 14.322987 31.995245 31.989239v83.735774H862.722502c1.656618 0 3.301224 0.129126 4.919805 0.382373a32.19444 32.19444 0 0 1 4.953838-0.382373H993.310029c17.685271 0 31.999249 14.322987 31.99925 31.989239 0 17.673259-14.313978 31.996246-31.99925 31.996247zM630.852066 63.986487H393.767539v51.739527h237.084527V63.986487zM195.710123 179.7115l44.280243 780.079796H785.316911l43.558538-780.079796H195.710123zM701.535093 869.624242c-0.964942 17.024626-15.060708 30.178471-31.903156 30.178472-0.625611 0-1.21819-0.014014-1.840797-0.046045-17.659245-1.000978-31.126397-16.119742-30.12542-33.757967l33.652865-592.997099c1.00398-17.666252 16.37399-30.883159 33.750959-30.132426 17.655241 0.997975 31.122393 16.120743 30.124419 33.758968L701.535093 869.624242z m-189.207773 30.178472c-17.692278 0-31.998248-14.319984-31.998249-31.993244V274.816375c0-17.673259 14.305971-31.992242 31.998249-31.992242 17.683269 0 31.996246 14.318983 31.996246 31.992242v592.993095c0 17.673259-14.312978 31.993243-31.996246 31.993244z m-156.621951-0.046045a35.334506 35.334506 0 0 1-1.843801 0.046045c-16.843449 0-30.93521-13.153846-31.904156-30.178472L288.301544 276.628145c-1.000978-17.638225 12.469177-32.760993 30.122417-33.758968 17.220817-0.79878 32.749982 12.466174 33.753963 30.132426l33.64886 592.997099c1.001978 17.638225-12.465173 32.756989-30.121415 33.757967z" p-id="5139"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1331 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4137" width="64" height="64"><path d="M552.5504 896.512L45.1584 244.1216A102.4 102.4 0 0 1 125.952 78.848h1014.784a102.4 102.4 0 0 1 80.896 165.2736l-507.4944 652.288a102.4 102.4 0 0 1-161.6896 0z" p-id="4138"></path></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1066 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3055" width="64" height="64"><path d="M170.666667 298.666667h768a42.666667 42.666667 0 1 0 0-85.333334H170.666667a42.666667 42.666667 0 1 0 0 85.333334z m768 426.666666H170.666667a42.666667 42.666667 0 0 0 0 85.333334h768a42.666667 42.666667 0 1 0 0-85.333334z m0-170.666666H170.666667a42.666667 42.666667 0 0 0 0 85.333333h768a42.666667 42.666667 0 1 0 0-85.333333z m0-170.666667H170.666667a42.666667 42.666667 0 1 0 0 85.333333h768a42.666667 42.666667 0 0 0 0-85.333333z" p-id="3056"></path></svg>

After

Width:  |  Height:  |  Size: 583 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1304" width="64" height="64"><path d="M697.8 481.4c33.6-35 54.2-82.3 54.2-134.3v-10.2C752 229.3 663.9 142 555.3 142H259.4c-15.1 0-27.4 12.3-27.4 27.4v679.1c0 16.3 13.2 29.5 29.5 29.5h318.7c117 0 211.8-94.2 211.8-210.5v-11c0-73-37.4-137.3-94.2-175.1zM328 238h224.7c57.1 0 103.3 44.4 103.3 99.3v9.5c0 54.8-46.3 99.3-103.3 99.3H328V238z m366.6 429.4c0 62.9-51.7 113.9-115.5 113.9H328V542.7h251.1c63.8 0 115.5 51 115.5 113.9v10.8z" p-id="1305"></path></svg>

After

Width:  |  Height:  |  Size: 537 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2418" width="64" height="64"><path d="M792.864 922.112l103.584-2.176L572.576 110.24h-89.184L161.696 919.936H264l66.944-167.936h394.112l67.808 170.112zM369.216 656L528 257.632 686.784 656h-317.568z" p-id="2419"></path></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2256" width="64" height="64"><path d="M311.769043 997.487304h653.668174v-66.782608H311.769043z" p-id="2257"></path><path d="M638.447304 760.186435l-152.665043-153.355131-152.665044-153.35513L650.245565 134.92313 955.575652 441.633391 638.425043 760.186435z m-173.723826 174.458435h-165.487304L70.77287 717.000348l215.129043-216.086261 18.031304 18.120348L591.187478 807.602087l-126.441739 127.042783z m540.91687-537.6L694.633739 84.658087c-23.752348-23.863652-65.113043-23.774609-88.776348 0L262.28313 429.768348 13.94087 679.201391c-21.147826 21.25913-17.986783 57.677913 7.791304 83.500522L262.906435 992.52313a33.391304 33.391304 0 0 0 22.995478 9.216h192.645565c8.859826 0 17.341217-3.539478 23.596522-9.839304l159.877565-160.567652 343.574261-345.110261A62.73113 62.73113 0 0 0 1024.005565 441.633391c0-16.851478-6.522435-32.678957-18.365217-44.588521z" p-id="2258"></path></svg>

After

Width:  |  Height:  |  Size: 968 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1619" width="64" height="64"><path d="M798 160H366c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h181.2l-156 544H229c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h432c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8H474.4l156-544H798c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z" p-id="1620"></path></svg>

After

Width:  |  Height:  |  Size: 347 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2894" width="64" height="64"><path d="M920 760H336c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z m0-568H336c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z m0 284H336c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM216 712H100c-2.2 0-4 1.8-4 4v34c0 2.2 1.8 4 4 4h72.4v20.5h-35.7c-2.2 0-4 1.8-4 4v34c0 2.2 1.8 4 4 4h35.7V838H100c-2.2 0-4 1.8-4 4v34c0 2.2 1.8 4 4 4h116c2.2 0 4-1.8 4-4V716c0-2.2-1.8-4-4-4zM100 188h38v120c0 2.2 1.8 4 4 4h40c2.2 0 4-1.8 4-4V152c0-4.4-3.6-8-8-8h-78c-2.2 0-4 1.8-4 4v36c0 2.2 1.8 4 4 4z m116 240H100c-2.2 0-4 1.8-4 4v36c0 2.2 1.8 4 4 4h68.4l-70.3 77.7c-1.3 1.5-2.1 3.4-2.1 5.4V592c0 2.2 1.8 4 4 4h116c2.2 0 4-1.8 4-4v-36c0-2.2-1.8-4-4-4h-68.4l70.3-77.7c1.3-1.5 2.1-3.4 2.1-5.4V432c0-2.2-1.8-4-4-4z" p-id="2895"></path></svg>

After

Width:  |  Height:  |  Size: 926 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1147" width="64" height="64"><path d="M149.333333 149.333333h448a42.666667 42.666667 0 1 1 0 85.333334H149.333333a42.666667 42.666667 0 1 1 0-85.333334z m0 640h448a42.666667 42.666667 0 1 1 0 85.333334H149.333333a42.666667 42.666667 0 1 1 0-85.333334z m0-320h725.333334a42.666667 42.666667 0 1 1 0 85.333334H149.333333a42.666667 42.666667 0 1 1 0-85.333334z" p-id="1148"></path></svg>

After

Width:  |  Height:  |  Size: 468 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2095" width="64" height="64"><path d="M952 474H569.9c-10-2-20.5-4-31.6-6-15.9-2.9-22.2-4.1-30.8-5.8-51.3-10-82.2-20-106.8-34.2-35.1-20.5-52.2-48.3-52.2-85.1 0-37 15.2-67.7 44-89 28.4-21 68.8-32.1 116.8-32.1 54.8 0 97.1 14.4 125.8 42.8 14.6 14.4 25.3 32.1 31.8 52.6 1.3 4.1 2.8 10 4.3 17.8 0.9 4.8 5.2 8.2 9.9 8.2h72.8c5.6 0 10.1-4.6 10.1-10.1v-1c-0.7-6.8-1.3-12.1-2-16-7.3-43.5-28-81.7-59.7-110.3-44.4-40.5-109.7-61.8-188.7-61.8-72.3 0-137.4 18.1-183.3 50.9-25.6 18.4-45.4 41.2-58.6 67.7-13.5 27.1-20.3 58.4-20.3 92.9 0 29.5 5.7 54.5 17.3 76.5 8.3 15.7 19.6 29.5 34.1 42H72c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h433.2c2.1 0.4 3.9 0.8 5.9 1.2 30.9 6.2 49.5 10.4 66.6 15.2 23 6.5 40.6 13.3 55.2 21.5 35.8 20.2 53.3 49.2 53.3 89 0 35.3-15.5 66.8-43.6 88.8-30.5 23.9-75.6 36.4-130.5 36.4-43.7 0-80.7-8.5-110.2-25-29.1-16.3-49.1-39.8-59.7-69.5-0.8-2.2-1.7-5.2-2.7-9-1.2-4.4-5.3-7.5-9.7-7.5h-79.7c-5.6 0-10.1 4.6-10.1 10.1v1c0.2 2.3 0.4 4.2 0.6 5.7 6.5 48.8 30.3 88.8 70.7 118.8 47.1 34.8 113.4 53.2 191.8 53.2 84.2 0 154.8-19.8 204.2-57.3 25-18.9 44.2-42.2 57.1-69 13-27.1 19.7-57.9 19.7-91.5 0-31.8-5.8-58.4-17.8-81.4-5.8-11.2-13.1-21.5-21.8-30.8H952c4.4 0 8-3.6 8-8v-60c0-4.3-3.6-7.9-8-7.9z" p-id="2096"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1934" width="64" height="64"><path d="M824 804H200c-4.4 0-8 3.4-8 7.6v60.8c0 4.2 3.6 7.6 8 7.6h624c4.4 0 8-3.4 8-7.6v-60.8c0-4.2-3.6-7.6-8-7.6zM512 728c69.4 0 134.6-27.1 183.8-76.2C745 602.7 772 537.4 772 468V156c0-6.6-5.4-12-12-12h-60c-6.6 0-12 5.4-12 12v312c0 97-79 176-176 176s-176-79-176-176V156c0-6.6-5.4-12-12-12h-60c-6.6 0-12 5.4-12 12v312c0 69.4 27.1 134.6 76.2 183.8C377.3 701 442.6 728 512 728z" p-id="1935"></path></svg>

After

Width:  |  Height:  |  Size: 515 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2733" width="64" height="64"><path d="M192 277.333333H106.666667V192h85.333333v85.333333z m725.333333 0H277.333333V192h640v85.333333zM192 554.666667H106.666667v-85.333334h85.333333v85.333334z m725.333333 0H277.333333v-85.333334h640v85.333334zM192 832H106.666667v-85.333333h85.333333v85.333333z m725.333333 0H277.333333v-85.333333h640v85.333333z" p-id="2734"></path></svg>

After

Width:  |  Height:  |  Size: 455 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3264" width="64" height="64"><path d="M789.333333 128a192 192 0 0 1 191.786667 182.954667L981.333333 320v213.333333a192 192 0 0 1-182.954666 191.786667L789.333333 725.333333H110.976l118.314667 135.253334a21.333333 21.333333 0 0 1 0.682666 27.349333l-2.688 2.773333a21.333333 21.333333 0 0 1-27.306666 0.682667l-2.773334-2.688-149.248-170.624a21.333333 21.333333 0 0 1-3.84-21.76l1.536-3.2 2.304-3.114667 149.333334-170.709333a21.333333 21.333333 0 0 1 34.346666 24.96l-2.261333 3.114667L111.018667 682.666667H789.333333a149.333333 149.333333 0 0 0 149.12-141.141334L938.666667 533.333333v-213.333333a149.333333 149.333333 0 0 0-141.141334-149.12L789.333333 170.666667H512a21.333333 21.333333 0 0 1-3.84-42.325334L512 128h277.333333z" p-id="3265"></path></svg>

After

Width:  |  Height:  |  Size: 843 B

View File

@ -14,8 +14,8 @@ export default {
mnx_navToContact() {
this.$router.push('/contact')
},
mnx_navToPdfContentViewer(content_id) {
this.$router.push('/pdf-content-viewer/' + content_id)
mnx_navToPdfContentViewer(document_id) {
this.$router.push('/pdf-content-viewer/' + document_id)
},
mnx_navToLinkContentViewer(content_link_based64) {
this.$router.push('/link-content-viewer/' + content_link_based64)
@ -122,6 +122,9 @@ export default {
},
//common
mnx_navToLink(path) {
this.$router.push('/' + path.replace(/^(\/)+/, ''))
},
mnx_navAfterSignedin() {
if (this.mnx_isUserAuthenticated()) {
switch (this.mnx_getUserRole()) {

View File

@ -12,13 +12,7 @@
<div class="form-group">
<div class="input-group-container">
<div class="form-floating">
<input
class="input-email"
id="inputEmail"
type="text"
placeholder="name@example.com"
v-model="email"
/>
<input class="input-email" id="inputEmail" type="text" placeholder="name@example.com" v-model="email" />
<label for="inputEmail">Email address</label>
</div>
<button class="btn-start" ref="submitButton" @click="trySigninWithEmail()">
@ -33,7 +27,8 @@
<script>
import {
UserAuthApi
UserAuthApi,
applicantValidator
// userProfileValidator,
} from '../../utils/index'
import { signinActionEnum } from '../../types/index'
@ -54,11 +49,8 @@ export default {
this.message = 'Please type in your email'
return
}
console.log('This is the region information', window.location.href)
// this.message = userProfileValidator.emailValidator.validate(
// this.email
// );
this.message = applicantValidator.emailValidator.validate(this.email)
if (this.message != null) return
@ -91,6 +83,7 @@ export default {
<style scoped lang="scss">
.front-container {
padding: 20px 0;
.slogen {
font-size: 20px;
font-weight: 400;
@ -99,6 +92,7 @@ export default {
margin-bottom: 24px;
line-height: 22px;
}
.poster {
font-size: 48px;
font-weight: 500;
@ -106,6 +100,7 @@ export default {
text-align: center;
margin-bottom: 40px;
line-height: 52px;
.blue {
color: $primary;
}

View File

@ -31,7 +31,7 @@
</div>
<button type="submit" class="btn-start">SIGN UP</button>
</div>
<p class="errorInput" v-if="message != null">{{ message }}</p>
<p class="error-msg" v-if="message != null">{{ message }}</p>
</div>
</form>
</div>

View File

@ -0,0 +1,55 @@
<template>
<div class="directories_containter">
<div
class="directory_container"
v-for="(directory, index) in directories"
:key="index"
@click="view_link(directory)"
>
<p>{{ directory.title_text }}</p>
<img class="directory_cover_image" :src="directory.cover_picture" />
<p>{{ directory.summary_text }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'LabHome',
components: {},
computed: {},
mounted() {},
methods: {
view_link(directory) {
this.mnx_navToLink(directory.path)
}
},
data() {
return {
directories: [
{
path: 'machine-translation',
title_text: 'Machine Translation',
summary_text: 'Translate lanuages leverage AI power',
icon_picture: '',
cover_picture: 'src/assets/images/lab-translation.png'
}
]
}
}
}
</script>
<style scoped lang="scss">
.directories_containter {
@extend .container;
}
.directory_container {
@extend .container;
cursor: pointer;
}
.directory_cover_image {
height: 20vh;
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<div class="input_containter">
<input class="input_text" type="text" v-model="input_text" @keyup.enter="translate($event)" />
<p class="translated_text">{{ translated_text }}</p>
</div>
</template>
<script>
import { LabApi } from '../../../utils/index'
export default {
name: 'TranslationHome',
components: {},
computed: {},
mounted() {},
methods: {
translate($event) {
LabApi.translate_text(this.input_text)
.then((response) => {
this.translated_text = response.data
})
.catch((error) => {
this.mnx_backendErrorHandler(error)
})
}
},
data() {
return {
input_text: null,
translated_text: null
}
}
}
</script>
<style scoped lang="scss">
.input_containter {
@extend .container;
@extend .m-3;
}
.input_text {
@extend .w-100;
}
.translated_text {
@extend .w-100;
}
</style>

View File

@ -1,12 +1,13 @@
<template>
<div style="width: 100vw; height: 100vh">
<PDFReader document="6662431ee312d5389c612020" />
<div class="pdf-viewer">
<PDFReader :doc="content_media_data" />
</div>
</template>
<script>
// import { pdfjsLib } from '../../plugins/index'
// import { ContentApi } from '../../utils/index'
// import { Buffer } from 'buffer'
import { ContentApi } from '@/utils/index'
import PDFReader from '@/components/PDFReader.vue'
export default {
name: 'PdfContentViewer',
@ -19,29 +20,14 @@ export default {
components: { PDFReader },
computed: {},
mounted() {
// this.retrieve_blog_content()
this.retrieve_blog_content(this.content_id)
},
methods: {
// retrieve_blog_content() {
// ContentApi.retrieve_blog_content(this.content_id)
// .then((response) => {
// this.content_media_data = response.data
// var loadingTask = pdfjsLib.getDocument({
// data: Buffer.from(this.content_media_data, 'base64')
// })
// loadingTask.promise.then(function (pdf) {
// //
// // Fetch the first page
// //
// pdf.getPage(1).then(function (page) {
// //rendering
// })
// })
// })
// .catch((error) => {
// this.mnx_backendErrorHandler(error)
// })
// }
retrieve_blog_content(document_id) {
ContentApi.retrieve_blog_content(document_id).then((response) => {
this.content_media_data = { url: response.data }
})
}
},
data() {
return {
@ -50,3 +36,6 @@ export default {
}
}
</script>
<style lang="scss" scoped>
// .pdf-viewer {height: $body-height;}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,81 +1,87 @@
<template>
<div class="message-hub-conainter">
<div class="conversation-list-container">
<div
v-for="(conversation, index) in conversations"
:key="index"
class="conversation-container"
@click="selectConversation(conversation)"
>
<div class="participant-portrait-container">
<img
class="participant-portrait"
alt="user portrait"
src="@/assets/images/default-user-portrait.png"
/>
</div>
<div class="conversation-summary-container">
<div class="conversation-summary-header-container">
<span class="participant-fullname">
{{ conversation.summary.contact.first_name }}
{{ conversation.summary.contact.last_name }}
</span>
<span class="conversation-last-update-date">
{{ getDateFromFulltimeString(conversation.summary.last_message.create_time) }}</span
>
</div>
<div class="conversation-summary-highlight-container">
<span class="conversation-last-message-summary">{{
conversation.summary.last_message.message_body
}}</span>
<div class="message-container">
<div class="message-hub-conainter">
<div v-if="conversations && conversations.length > 0" class="conversation-list-container">
<div
v-for="(conversation, index) in conversations"
:key="index"
class="conversation-container"
:class="{
selected:
current_thread?.conversation?.information?.conversation_id ===
conversation.summary.last_message.conversation_id
}"
@click="selectConversation(conversation)"
>
<img class="participant-portrait" alt="user portrait" src="@/assets/profile.png" />
<div class="conversation-summary-container">
<div class="conversation-summary-header-container">
<span class="participant-fullname">
{{ conversation.summary.contact.first_name }}
{{ conversation.summary.contact.last_name }}
</span>
<span class="conversation-last-update-date">{{
getDateFromFulltimeString(conversation.summary.last_message.create_time)
}}</span>
</div>
<div class="conversation-summary-highlight-container">
{{ conversation.summary.last_message.message_body }}
</div>
</div>
</div>
</div>
</div>
<div class="message-panel-container">
<div v-if="current_thread" class="message-thread-container">
<div
v-for="(item, index) in current_thread.conversation.messages"
:key="index"
class="message-item-container"
>
<div class="message-item-header-container">
<div class="message-item-sender-info">
<div
v-if="!conversations || conversations.length == 0"
class="conversation-list-empty-container"
>
Empty conversation
</div>
<div class="message-panel-container">
<div v-if="current_thread" class="message-thread-container">
<div
v-for="(item, index) in current_thread.conversation.messages"
:key="index"
class="message-item-container"
:class="item.raw_data.sender_id == userIdentityNote ? 'me' : ''"
>
<div class="message-item-header-container">
<img
class="message-item-sender-portrait"
alt="user portrait"
src="@/assets/images/default-user-portrait.png"
src="@/assets/profile.png"
/>
<span class="message-item-sender-fullname">
{{ item.sender_profile.first_name }}
{{ item.sender_profile.last_name }}
</span>
</div>
<div class="message-item-additional-info-container">
<span class="message-item-create-time">
{{ getDateFromFulltimeString(item.raw_data.create_time) }}</span
>
</div>
</div>
<div class="message-item-message-body-container">
<p class="message-item-message-body-span">{{ item.raw_data.message_body }}</p>
<div class="message-item-message-body">{{ item.raw_data.message_body }}</div>
</div>
</div>
</div>
<div class="message-writing-panel-container">
<input
class="writing-message-input"
type="text"
v-model="writtenMessage"
@keypress.enter="sendMessage(current_thread.conversation.information.conversation_id)"
/>
<div v-if="!current_thread" class="message-thread-empty-container">
Please choose conversation
</div>
<div class="message-writing-panel-container">
<svg-icon icon="msg-enter" class-name="writing-message-enter" />
<input
class="writing-message-input"
type="text"
v-model="writtenMessage"
@keypress.enter="sendMessage(current_thread.conversation.information.conversation_id)"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import SvgIcon from '../../../components/SvgIcon.vue'
import { MessageHubApi, DateUtils } from '../../../utils/index'
export default {
components: { SvgIcon },
name: 'MessageHub',
props: {},
mounted() {
@ -83,7 +89,7 @@ export default {
},
watch: {
conversations: {
handler: function (val, oldVal) {
handler: function (val) {
if (val && val.length > 0) {
this.current_thread = val[0]
}
@ -93,6 +99,7 @@ export default {
},
data() {
return {
userIdentityNote: this.mnx_getUserIdentity(),
conversations: [],
current_thread: null,
writtenMessage: null
@ -124,145 +131,204 @@ export default {
})
},
getDateFromFulltimeString(fulltime) {
return DateUtils.FromJsonToDatetimeString(fulltime)
return DateUtils.FromJsonToHMDateString(fulltime)
}
}
}
</script>
<style lang="scss" scoped>
.message-hub-conainter {
@extend .flex-row-container;
@extend .justify-content-between;
@extend .border;
@extend .w-100;
}
.message-container {
display: flex;
align-items: center;
width: 100%;
height: $body-height;
padding: 24px 24px 0 24px;
justify-content: center;
.message-hub-conainter {
display: flex;
box-shadow: 0px 0px 24px 0px #d4d3e380;
border-radius: 12px;
width: 100%;
max-width: $body-width;
height: 100%;
overflow: hidden;
.conversation-list-container {
height: 100%;
overflow: auto;
width: 300px;
.conversation-container {
background-color: #f3f3f5;
display: flex;
cursor: pointer;
height: 98px;
padding: 18px;
.participant-portrait {
width: 36px;
height: 36px;
border-radius: 18px;
margin-right: 16px;
}
.conversation-summary-container {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
.conversation-summary-header-container {
display: flex;
align-items: center;
height: 22px;
color: #0d1637;
.participant-fullname {
font-weight: 500;
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.conversation-last-update-date {
white-space: nowrap;
flex-shrink: 0;
}
}
.conversation-summary-highlight-container {
font-size: 14px;
color: #6e7387;
white-space: normal;
overflow: hidden;
flex: 1;
text-align: left;
word-break: break-word;
}
}
&:hover {
opacity: 0.78;
}
&.selected {
background-color: transparent;
}
}
}
.conversation-list-empty-container {
height: 100%;
overflow: auto;
width: 300px;
background-color: #f3f3f5;
display: flex;
align-items: center;
justify-content: center;
}
.message-panel-container {
height: 100%;
overflow: auto;
flex: 1;
padding-bottom: 98px;
position: relative;
.message-thread-container {
height: 100%;
overflow: auto;
padding: 20px 32px;
display: flex;
flex-direction: column;
.message-item-container {
max-width: 70%;
margin-bottom: 15px;
width: fit-content;
display: flex;
flex-direction: column;
.message-item-header-container {
display: flex;
align-items: center;
margin-bottom: 6px;
width: 100%;
overflow: hidden;
font-size: 18px;
width: fit-content;
max-width: 100%;
.message-item-sender-portrait {
width: 36px;
height: 36px;
border-radius: 18px;
}
.message-item-sender-fullname {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #16181e;
font-weight: 500;
margin: 0 7px;
}
.message-item-create-time {
color: #737478;
white-space: nowrap;
flex-shrink: 0;
font-size: 16px;
}
}
.message-item-message-body {
font-size: 14px;
line-height: 17px;
color: #0d1637;
background-color: #f3f3f5;
padding: 12px;
border-radius: 12px;
overflow: hidden;
width: fit-content;
max-width: 100%;
text-align: left;
word-break: break-word;
}
.conversation-list-container {
@extend .flex-colum-container;
@extend .justify-content-start;
@extend .w-40;
@extend .m-1;
@extend .border;
min-height: 80vh;
}
.conversation-container {
@extend .flex-row-container;
@extend .justify-content-between;
@extend .border;
@extend .w-100;
@extend .my-3;
@extend .focus-ring;
cursor: pointer;
}
.participant-portrait-container {
@extend .float-start;
}
.participant-portrait {
@extend .img-fluid;
}
.conversation-summary-container {
@extend .flex-colum-container;
@extend .w-90;
}
.conversation-summary-header-container {
@extend .flex-row-container;
@extend .justify-content-between;
@extend .w-100;
}
.participant-fullname {
@extend .text-start;
@extend .label-text-light;
}
.conversation-last-update-date {
@extend .text-start;
@extend .label-text-light;
}
.conversation-summary-highlight-container {
@extend .container;
}
.conversation-last-message-summary {
@extend .d-inline-block;
@extend .text-truncate;
@extend .w-100;
}
.message-panel-container {
@extend .flex-colum-container;
@extend .w-60;
@extend .m-1;
@extend .border;
@extend .justify-content-between;
min-height: 80vh;
}
.message-thread-container {
@extend .flex-colum-container;
@extend .m-1;
@extend .border;
@extend .align-items-start;
@extend .flex-grow-1;
}
.message-item-container {
@extend .flex-colum-container;
@extend .m-1;
}
.message-item-header-container {
@extend .flex-row-container;
@extend .justify-content-between;
@extend .w-100;
}
.message-item-sender-info {
@extend .m-1;
}
.message-item-additional-info-container {
@extend .m-1;
}
.message-item-sender-portrait {
@extend .img-fluid;
height: 2vh;
}
.message-item-sender-fullname {
@extend .label-text;
@extend .mx-1;
}
.message-item-create-time {
@extend .label-text;
@extend .mx-1;
}
.message-item-message-body-container {
@extend .container;
}
.message-item-message-body-span {
@extend .text-start;
}
.message-writing-panel-container {
@extend .flex-colum-container;
@extend .border;
min-height: 10vh;
}
.writing-message-input {
@extend .text-start;
@extend .w-100;
@extend .flex-grow-1;
&.me {
align-self: flex-end;
.message-item-header-container {
flex-direction: row-reverse;
align-self: flex-end;
}
.message-item-message-body {
background-color: $primary;
color: white;
align-self: flex-end;
}
}
}
}
.message-thread-empty-container {
height: 100%;
overflow: auto;
padding: 20px 32px;
display: flex;
align-items: center;
justify-content: center;
}
.message-writing-panel-container {
height: 98px;
padding: 24px;
background-color: white;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
.writing-message-enter {
position: absolute;
height: 24px;
width: 24px;
background-color: #f3f3f5;
padding: 7px;
color: #9ea2af;
top: 37px;
right: 37px;
border-radius: 7px;
}
.writing-message-input {
height: 50px;
border: 1px solid #e1e1e1;
box-shadow: none;
width: 100%;
padding: 0 48px 0 12px;
border-radius: 12px;
}
}
}
}
}
</style>

View File

@ -1,267 +1,254 @@
<template>
<div class="provider-hub-container">
<div v-if="recommendedProviders" class="accordion" id="provider-accordion-container">
<div class="provider-hub-container" id="provider-accordion-container">
<template v-if="recommendedProviders">
<div
class="accordion accordion-list"
v-for="(provider, index) in recommendedProviders"
:key="index"
class="accordion-item my-3"
>
<h2 class="accordion-header">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#collapse' + index"
aria-expanded="false"
:aria-controls="'collapse' + index"
<div class="accordion-item my-3">
<h2 class="accordion-header">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#collapse' + index"
aria-expanded="false"
:aria-controls="'collapse' + index"
>
<div class="provider-summary-containter dashed-container">
<div class="provider-portrait-containter">
<img class="provider-portrait" alt="user portrait" src="@/assets/profile.png" />
</div>
<div class="provider-name-container">
<label class="provider-name-label" for="provider-name">Name</label>
<span class="provider-name-span" id="provider-name">
{{ provider.user_profile.first_name }}
{{ provider.user_profile.last_name }}</span
>
</div>
<div class="provider-stay-on-freeleaps-container">
<label class="provider-stay-on-freeleaps-label" for="provider-stay-on-freeleaps"
>Stay on Freeleaps</label
>
<span class="provider-stay-on-freeleaps-span" id="provider-stay-on-freeleaps">
{{ provider.activeness_achievement.days_of_staying_on }} day(s)</span
>
</div>
<div class="provider-delivered-projects-container">
<label class="provider-delivered-projects-label" for="provider-delivered-projects"
>Delivered projects</label
>
<span class="provider-delivered-projects-span" id="provider-delivered-projects">
{{ provider.provider_achievement.delivered_projects }}
</span>
</div>
<div class="provider-responding-time-container">
<label class="provider-responding-time-label" for="provider-responding-time"
>Responding time</label
>
<span class="provider-responding-time-span" id="provider-responding-time">
{{ provider.provider_achievement.responding_time_in_minutes }} min(s)
</span>
</div>
<div class="provider-credit-score-container">
<label class="provider-credit-score-label" for="provider-credit-score"
>Credit score</label
>
<span class="provider-credit-score-span" id="provider-credit-score">
{{ provider.provider_achievement.credit }}</span
>
</div>
</div>
</button>
</h2>
<div
:id="'collapse' + index"
class="accordion-collapse collapse"
data-bs-parent="#provider-accordion-container"
>
<div class="provider-summary-containter">
<div class="provider-portrait-containter">
<img
class="provider-portrait"
alt="user portrait"
src="@/assets/images/default-user-portrait.png"
/>
<div class="accordion-body">
<div class="self-intro-container">
<label class="self-intro-content-label" for="self-intro-content">Self intro</label>
<div
class="self-intro-content-container"
id="self-intro-content"
v-html="provider.user_profile.self_intro.content_html"
></div>
</div>
<div class="provider-name-container">
<label class="provider-name-label" for="provider-name">Name</label>
<span class="provider-name-span" id="provider-name">
{{ provider.user_profile.first_name }} {{ provider.user_profile.last_name }}</span
>
</div>
<div class="provider-stay-on-freeleaps-container">
<label class="provider-stay-on-freeleaps-label" for="provider-stay-on-freeleaps"
>Stay on Freeleaps</label
>
<span class="provider-stay-on-freeleaps-span" id="provider-stay-on-freeleaps">
{{ provider.activeness_achievement.days_of_staying_on }} day(s)</span
>
</div>
<div class="provider-delivered-projects-container">
<label class="provider-delivered-projects-label" for="provider-delivered-projects"
>Delivered projects</label
>
<span class="provider-delivered-projects-span" id="provider-delivered-projects">
{{ provider.provider_achievement.delivered_projects }}
</span>
</div>
<div class="provider-responding-time-container">
<label class="provider-responding-time-label" for="provider-responding-time"
>Responding time</label
>
<span class="provider-responding-time-span" id="provider-responding-time">
{{ provider.provider_achievement.responding_time_in_minutes }} min(s)
</span>
</div>
<div class="provider-credit-score-container">
<label class="provider-credit-score-label" for="provider-credit-score"
>Credit score</label
>
<span class="provider-credit-score-span" id="provider-credit-score">
{{ provider.provider_achievement.credit }}</span
>
</div>
</div>
</button>
</h2>
<div
:id="'collapse' + index"
class="accordion-collapse collapse"
data-bs-parent="#provider-accordion-container"
>
<div class="accordion-body">
<div class="self-intro-container">
<label class="self-intro-content-label" for="self-intro-content">Self intro</label>
<div
class="self-intro-content-container"
id="self-intro-content"
v-html="provider.user_profile.self_intro.content_html"
></div>
</div>
<div class="statistics-container">
<label class="statistics-content-label" for="statistics-content">Profile</label>
<div class="statistics-content-container" id="statistics-content">
<div class="delivery-container">
<div class="delivery-delivered-project-container">
<label
class="delivery-delivered-projects-label"
for="delivery-delivered-projects"
>Delivered projects</label
>
<span class="delivery-delivered-projects-span" id="delivery-delivered-projects">
{{ provider.provider_achievement.delivered_projects }}
</span>
</div>
<div class="delivery-time-per-project-container">
<label class="delivery-time-per-project-label" for="delivery-time-per-project"
>Project delivering time</label
>
<span class="delivery-time-per-project-span" id="delivery-time-per-project">
{{ provider.provider_deliveries.delivering_time_per_project_in_day }} day(s)
</span>
</div>
<div class="delivery-top-programming-language-container">
<label
class="delivery-top-programming-language-label"
for="delivery-top-programming-language"
>Top programming languages</label
>
<div
class="delivery-top-programming-language-content-container"
id="delivery-top-programming-language"
>
<span
v-for="(lang, index) in provider.provider_deliveries
.top_programming_languages"
:key="index"
class="delivery-top-programming-language-span"
<div class="statistics-container">
<label class="self-intro-content-label" for="statistics-content">Profile</label>
<div class="statistics-content-container" id="statistics-content">
<div class="delivery-container">
<div class="dd-project-container">
<label class="dd-project-label" for="delivery-delivered-projects"
>Delivered projects</label
>
#{{ lang }}
<span class="dd-project-span" id="delivery-delivered-projects">
{{ provider.provider_achievement.delivered_projects }}
</span>
</div>
<div class="dd-project-container">
<label class="dd-project-label" for="delivery-time-per-project"
>Project delivering time</label
>
<span class="dd-project-span" id="delivery-time-per-project">
{{ provider.provider_deliveries.delivering_time_per_project_in_day }} day(s)
</span>
</div>
<div class="dd-project-container">
<label class="dd-project-label" for="delivery-top-programming-language"
>Top programming languages</label
>
<div
class="delivery-top-programming-language-content-container"
id="delivery-top-programming-language"
>
<span
v-for="(lang, index) in provider.provider_deliveries
.top_programming_languages"
:key="index"
class="dd-project-span"
>
#{{ lang }}
</span>
</div>
</div>
<div class="dd-project-container">
<label class="dd-project-label" for="delivery-weekly-produced-code"
>Weekly produced code</label
>
<span class="dd-project-span" id="delivery-weekly-produced-code">
{{ provider.provider_deliveries.lines_of_code_per_week }} line(s)
</span>
</div>
</div>
<div class="delivery-weekly-produced-code-container">
<label
class="delivery-weekly-produced-code-label"
for="delivery-weekly-produced-code"
>Weekly produced code</label
>
<span
class="delivery-weekly-produced-code-span"
id="delivery-weekly-produced-code"
>
{{ provider.provider_deliveries.lines_of_code_per_week }} line(s)
</span>
<div class="delivery-container">
<div class="dd-project-container">
<label class="dd-project-label" for="activity-ongoing-projects"
>Ongoing projects</label
>
<span class="dd-project-span" id="activity-ongoing-projects">
{{ provider.provider_activities.ongoing_projects }}
</span>
</div>
<div class="dd-project-container">
<label class="dd-project-label" for="activity-invitation-to-requests"
>Invitations to requests</label
>
<span class="dd-project-span" id="activity-invitation-to-requests">
{{ provider.provider_activities.invitations_to_open_requests }}
</span>
</div>
<div class="dd-project-container">
<label class="dd-project-label" for="activity-active-proposals"
>Active proposals</label
>
<span class="dd-project-span" id="activity-active-proposals">
{{ provider.provider_activities.active_proposals }}
</span>
</div>
<div class="dd-project-container">
<label class="dd-project-label" for="activity-hourly-rate"
>Expected hourly rate</label
>
<span class="dd-project-span" id="activity-hourly-rate">
{{ provider.provider_profile.expected_salary.hourly }}
{{ provider.provider_profile.expected_salary.currency }}
</span>
</div>
</div>
</div>
<div class="activity-container">
<div class="activity-ongoing-projects-container">
<label class="activity-ongoing-projects-label" for="activity-ongoing-projects"
>Ongoing projects</label
>
<span class="activity-ongoing-projects-span" id="activity-ongoing-projects">
{{ provider.provider_activities.ongoing_projects }}
</span>
</div>
<div class="activity-invitation-to-requests-container">
<label
class="activity-invitation-to-requests-label"
for="activity-invitation-to-requests"
>Invitations to requests</label
>
<span
class="activity-invitation-to-requests-span"
id="activity-invitation-to-requests"
>
{{ provider.provider_activities.invitations_to_open_requests }}
</span>
</div>
<div class="activity-active-proposals-container">
<label class="activity-active-proposals-label" for="activity-active-proposals"
>Active proposals</label
>
<span class="activity-active-proposals-span" id="activity-active-proposals">
{{ provider.provider_activities.active_proposals }}
</span>
</div>
<div class="delivery-hourly-rate-container">
<label class="delivery-hourly-rate-label" for="activity-hourly-rate"
>Expected hourly rate</label
>
<span class="delivery-hourly-rate-span" id="activity-hourly-rate">
{{ provider.provider_profile.expected_salary.hourly }}
{{ provider.provider_profile.expected_salary.currency }}
</span>
</div>
</div>
<div class="work-quality-container">
<div class="quality-code-issue-rate-container">
<label class="quality-code-issue-rate-label" for="quality-issue-rate"
>Code issue rate</label
>
<span class="quality-code-issue-rate-span" id="quality-issue-rate">
{{ provider.provider_work_quality.issues_per_thousand_lines_of_codes }}
</span>
</div>
<div class="quality-online-issue-rate-container">
<label class="quality-online-issue-rate-label" for="quality-online-issue-rate"
>Online issue rate</label
>
<span class="quality-online-issue-rate-span" id="quality-online-issue-rate">
{{ provider.provider_work_quality.issues_after_delivery_per_project }}
</span>
</div>
<div class="quality-issue-fixing-rate-container">
<label class="quality-issue-fixing-rate-label" for="quality-issue-fixing-rate"
>Issue fixing rate</label
>
<span class="quality-issue-fixing-rate-span" id="quality-issue-fixing-rate">
{{ provider.provider_work_quality.issue_fixing_rate_pencentage }}%
</span>
</div>
<div class="quality-issue-fixing-time-container">
<label class="quality-issue-fixing-time-label" for="quality-issue-fixing-time"
>Issue fixing time</label
>
<span class="quality-issue-fixing-time-span" id="quality-issue-fixing-time">
{{ provider.provider_work_quality.issue_fixing_time_minutes }} min(s)
</span>
<div class="delivery-container">
<div class="dd-project-container">
<label class="dd-project-label" for="quality-issue-rate"
>Code issue rate</label
>
<span class="dd-project-span" id="quality-issue-rate">
{{ provider.provider_work_quality.issues_per_thousand_lines_of_codes }}
</span>
</div>
<div class="dd-project-container">
<label class="dd-project-label" for="quality-online-issue-rate"
>Online issue rate</label
>
<span class="dd-project-span" id="quality-online-issue-rate">
{{ provider.provider_work_quality.issues_after_delivery_per_project }}
</span>
</div>
<div class="dd-project-container">
<label class="dd-project-label" for="quality-issue-fixing-rate"
>Issue fixing rate</label
>
<span class="dd-project-span" id="quality-issue-fixing-rate">
{{ provider.provider_work_quality.issue_fixing_rate_pencentage }}%
</span>
</div>
<div class="dd-project-container">
<label class="dd-project-label" for="quality-issue-fixing-time"
>Issue fixing time</label
>
<span class="dd-project-span" id="quality-issue-fixing-time">
{{ provider.provider_work_quality.issue_fixing_time_minutes }} min(s)
</span>
</div>
</div>
</div>
</div>
</div>
<div v-if="requests" class="accordion" id="accordion-action-panel">
<div class="accordion-item">
<h2 class="accordion-header">
<button
class="accordion-button"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse-action-panel"
aria-expanded="false"
aria-controls="collapse-action-panel"
<div v-if="requests" class="accordion" id="accordion-action-panel">
<div class="accordion-item">
<h2 class="accordion-header">
<button
class="accordion-button"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse-action-panel"
aria-expanded="false"
aria-controls="collapse-action-panel"
>
Action panel
</button>
</h2>
<div
id="collapse-action-panel"
class="accordion-collapse collapse"
data-bs-parent="#accordion-action-panel"
>
Action panel
</button>
</h2>
<div
id="collapse-action-panel"
class="accordion-collapse collapse"
data-bs-parent="#accordion-action-panel"
>
<div class="accordion-body">
<div class="invite-to-request-container">
<label class="invite-to-request-content-label" for="invite-to-request-content"
>Invite
<span class="invite-to-request-name-span"
>{{ provider.user_profile.first_name }}
{{ provider.user_profile.last_name }}</span
<div class="accordion-body">
<div class="invite-to-request-container">
<label
class="invite-to-request-content-label"
for="invite-to-request-content"
>Invite
<span class="invite-to-request-name-span"
>{{ provider.user_profile.first_name }}
{{ provider.user_profile.last_name }}</span
>
to my open requests</label
>
to my open requests</label
>
<div
class="invite-to-request-content-container"
id="invite-to-request-content"
>
<div class="form-check" v-for="(request, index) in requests" :key="index">
<input
class="form-check-input"
type="checkbox"
:value="request.id"
:id="'check' + request.id"
v-model="checkedRequests"
@change="
checkRequest($event, request.id, provider.user_profile.user_id)
"
/>
<label class="form-check-label" :for="'check' + request.id">{{
request.title
}}</label>
<div
class="invite-to-request-content-container"
id="invite-to-request-content"
>
<div class="form-check" v-for="(request, index) in requests" :key="index">
<input
class="form-check-input"
type="checkbox"
:value="request.id"
:id="'check' + request.id"
v-model="checkedRequests"
@change="
checkRequest($event, request.id, provider.user_profile.user_id)
"
/>
<label class="form-check-label" :for="'check' + request.id">{{
request.title
}}</label>
</div>
<span class="invite-to-request-note-text"
>*Once the request is selected, the provider will be invited to see the
request.</span
>
</div>
<span class="invite-to-request-note-text"
>*Once the request is selected, the provider will be invited to see the
request.</span
>
</div>
</div>
</div>
@ -271,7 +258,7 @@
</div>
</div>
</div>
</div>
</template>
</div>
</template>
@ -422,13 +409,16 @@ export default {
.self-intro-container {
@extend .flex-colum-container;
@extend .m-1;
@extend .border;
margin-bottom: 20px;
// @extend .m-1;
// @extend .border;
}
.self-intro-content-label {
@extend .label-text-light;
@extend .w-100;
font-size: 14px;
font-weight: bold !important;
}
.self-intro-content-container {
@ -439,43 +429,45 @@ export default {
.statistics-container {
@extend .flex-colum-container;
@extend .m-1;
@extend .border;
margin-bottom: 20px;
padding-top: 20px;
border-top: 1px solid #dee2e6;
}
.statistics-content-label {
@extend .label-text-light;
@extend .w-100;
}
// .statistics-content-label {
// @extend .label-text-light;
// @extend .w-100;
// }
.statistics-content-container {
@extend .flex-colum-container;
@extend .m-1;
padding: 0;
margin-top: 12px;
}
.delivery-container {
@extend .flex-row-container;
@extend .justify-content-around;
@extend .m-1;
@extend .border;
@extend .w-100;
background-color: rgba(32, 90, 239, 0.03);
margin-bottom: 12px;
padding: 12px 0;
}
.delivery-delivered-project-container {
.dd-project-container {
@extend .flex-colum-container;
@extend .w-20;
@extend .m-1;
}
.delivery-delivered-projects-label {
.dd-project-label {
@extend .label-text-light;
@extend .w-100;
}
.delivery-delivered-projects-span {
.dd-project-span {
@extend .text-start;
@extend .w-90;
@extend .m-1;
@extend .w-100;
font-weight: bold;
}
.delivery-time-per-project-container {
@ -526,7 +518,7 @@ export default {
.delivery-top-programming-language-content-container {
@extend .flex-row-container;
@extend .justify-content-start;
@extend .m-1;
padding: 0;
@extend .w-100;
}

View File

@ -6,157 +6,167 @@
class="request-invitations"
:id="group.name"
>
<div v-if="group.data" class="accordion" id="request-invitation-container">
<div v-if="group.data" id="request-invitation-container">
<div
class="accordion accordion-list"
v-for="(request, index) in group.data"
:key="index"
:id="request.id"
class="accordion-item my-3"
>
<h2 class="accordion-header">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#collapse' + index"
aria-expanded="false"
:aria-controls="'collapse' + index"
>
<div class="request-content-container">
<div class="request-content-issuer-container">
<label class="request-content-label" for="request-content-issuer-box"
>Issuer</label
>
<div class="request-content-box" id="request-content-issuer-box">
<span class="request-content-issuer-text">
{{ request.issuer_profile.first_name }}
{{ request.issuer_profile.last_name }}</span
<div class="accordion-item my-3">
<h2 class="accordion-header">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#collapse' + index"
aria-expanded="false"
:aria-controls="'collapse' + index"
>
<div class="dashed-container request-content-container">
<div class="request-content-issuer-container">
<label class="request-content-label" for="request-content-issuer-box"
>Issuer</label
>
</div>
</div>
<div class="request-content-title-container">
<label class="request-content-label" for="request-content-title-box"
>Request</label
>
<div class="request-content-box" id="request-content-title-box">
<span class="request-content-title-text"> {{ request.title }}</span>
</div>
</div>
<div class="request-content-date-container">
<label class="request-content-label" for="request-content-date-box">Date</label>
<div class="request-content-box" id="request-content-date-box">
<span class="request-content-date-text">
{{ getDateFromFulltimeString(request.update_time) }}
</span>
</div>
</div>
<div class="request-content-score-container">
<label class="request-content-label" for="request-content-score-box">Score</label>
<div class="request-content-box" id="request-content-score-box">
<span class="request-content-score-text">
{{ request.score }}
</span>
</div>
</div>
</div>
</button>
</h2>
<div
:id="'collapse' + index"
class="accordion-collapse collapse"
data-bs-parent="#request-invitation-container"
>
<div class="accordion-body">
<div class="request-description-container">
<button class="make-proposal-button" type="button" @click="Propose(request)">
Propose
</button>
<div class="request-description-content" v-html="request.content"></div>
<div v-for="(document_id, index) in request.document_ids" :key="document_id">
<a :href="getDocumentDownloadLink(document_id)" download>file-{{ index }}</a>
</div>
</div>
<div class="issuer-profile-container">
<label class="issuer-profile-label" for="issuer-achievement-container"
>Issuer profile</label
>
<div class="issuer-achievement-container" id="issuer-achievement-container">
<div class="issuer-achievement-isssuer-container">
<label
class="issuer-achievement-label"
for="issuer-achievement-isssuer-content-div"
>Name</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-isssuer-content-div"
>
<span class="issuer-achievement-issuer-text">
<div class="request-content-box" id="request-content-issuer-box">
<span class="request-content-issuer-text">
{{ request.issuer_profile.first_name }}
{{ request.issuer_profile.last_name }}</span
>
</div>
</div>
<div class="issuer-achievement-stay-container">
<label
class="issuer-achievement-label"
for="issuer-achievement-stay-content-div"
>Stay on Freeleaps</label
<div class="request-content-title-container">
<label class="request-content-label" for="request-content-title-box"
>Request</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-stay-content-div"
>
<span class="issuer-achievement-stay-content-text">
{{ request.issuer_achievement.activeness.days_of_staying_on }} day(s)</span
>
<div class="request-content-box" id="request-content-title-box">
<span class="request-content-title-text"> {{ request.title }}</span>
</div>
</div>
<div class="issuer-achievement-paid-container">
<label
class="issuer-achievement-label"
for="issuer-achievement-paid-content-div"
>Total payment</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-stay-content-div"
>
<span class="issuer-achievement-paid-content-text">
{{ request.issuer_achievement.issuer.spending.total }}
{{ request.issuer_achievement.issuer.spending.currency }}</span
>
<div class="request-content-date-container">
<label class="request-content-label" for="request-content-date-box">Date</label>
<div class="request-content-box" id="request-content-date-box">
<span class="request-content-date-text">
{{ getDateFromFulltimeString(request.update_time) }}
</span>
</div>
</div>
<div class="issuer-achievement-deposit-container">
<label
class="issuer-achievement-label"
for="issuer-achievement-deposit-content-div"
>Deposit</label
<div class="request-content-score-container">
<label class="request-content-label" for="request-content-score-box"
>Score</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-deposit-content-div"
>
<span class="issuer-achievement-deposit-content-text">
{{ request.issuer_achievement.issuer.deposit.available }}
{{ request.issuer_achievement.issuer.deposit.currency }}</span
>
<div class="request-content-box" id="request-content-score-box">
<span class="request-content-score-text">
{{ request.score }}
</span>
</div>
</div>
<div class="issuer-achievement-credit-container">
<label
class="issuer-achievement-label"
for="issuer-achievement-credit-content-div"
>Credit</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-credit-content-div"
>
<span class="issuer-achievement-credit-content-text">
{{ request.issuer_achievement.activeness.credit }}</span
</div>
</button>
</h2>
<div
:id="'collapse' + index"
class="accordion-collapse collapse"
data-bs-parent="#request-invitation-container"
>
<div class="accordion-body">
<div class="request-description-container">
<button class="make-proposal-button" type="button" @click="Propose(request)">
Propose
</button>
<div class="request-description-content" v-html="request.content"></div>
<div v-for="(file, index) in request.attached_files" :key="index">
<button @click="previewAttachedFile(request.id, file.document_id)">
Preview{{ file.file_name }}
</button>
<button @click="downloadAttachedFile(request.id, file.document_id)">
Download{{ file.file_name }}
</button>
</div>
</div>
<div class="issuer-profile-container">
<!-- <label class="issuer-profile-label" for="issuer-achievement-container"
>Issuer profile</label
> -->
<div class="issuer-achievement-container" id="issuer-achievement-container">
<div class="issuer-achievement-isssuer-container">
<label
class="issuer-achievement-label"
for="issuer-achievement-isssuer-content-div"
>Name</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-isssuer-content-div"
>
<span class="issuer-achievement-issuer-text">
{{ request.issuer_profile.first_name }}
{{ request.issuer_profile.last_name }}</span
>
</div>
</div>
<div class="issuer-achievement-stay-container">
<label
class="issuer-achievement-label"
for="issuer-achievement-stay-content-div"
>Stay on Freeleaps</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-stay-content-div"
>
<span class="issuer-achievement-stay-content-text">
{{ request.issuer_achievement.activeness.days_of_staying_on }}
day(s)</span
>
</div>
</div>
<div class="issuer-achievement-paid-container">
<label
class="issuer-achievement-label"
for="issuer-achievement-paid-content-div"
>Total payment</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-stay-content-div"
>
<span class="issuer-achievement-paid-content-text">
{{ request.issuer_achievement.issuer.spending.total }}
{{ request.issuer_achievement.issuer.spending.currency }}</span
>
</div>
</div>
<div class="issuer-achievement-deposit-container">
<label
class="issuer-achievement-label"
for="issuer-achievement-deposit-content-div"
>Deposit</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-deposit-content-div"
>
<span class="issuer-achievement-deposit-content-text">
{{ request.issuer_achievement.issuer.deposit.available }}
{{ request.issuer_achievement.issuer.deposit.currency }}</span
>
</div>
</div>
<div class="issuer-achievement-credit-container">
<label
class="issuer-achievement-label"
for="issuer-achievement-credit-content-div"
>Credit</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-credit-content-div"
>
<span class="issuer-achievement-credit-content-text">
{{ request.issuer_achievement.activeness.credit }}</span
>
</div>
</div>
</div>
</div>
@ -170,7 +180,7 @@
</template>
<script>
import { RequestHubApi, DateUtils, requestHubUtils } from '../../../utils/index'
import { RequestHubApi, WorksapceApi, DateUtils, requestHubUtils } from '../../../utils/index'
import { proposingModelEnum } from '../../../types/index'
export default {
name: 'RequestHub',
@ -202,8 +212,38 @@ export default {
getDateFromFulltimeString(fulltime) {
return DateUtils.FromJsonToDateString(fulltime)
},
getDocumentDownloadLink(document_id) {
return '/api/v1/document/download/' + document_id
previewAttachedFile(request_id, document_id) {
// !!! SHOULD NOT use PdfContentViewer which is designed for unlogged in users.
// !!! Instead, should have a dedicated pdf viewer which should follow the figma design.
// WorksapceApi.fetchAttachedFileAsMediaData(request_id, document_id)
// .then((response) => {
// let media_data = response.data
// console.log(media_data)
// })
// .catch((error) => {
// this.mnx_backendErrorHandler(error)
// })
},
downloadAttachedFile(request_id, document_id) {
WorksapceApi.fetchAttachedFileAsDownload(request_id, document_id)
.then((response) => {
// create file link in browser's memory
const href = URL.createObjectURL(response.data)
// create "a" HTML element with href to file & click
const link = document.createElement('a')
link.href = href
link.setAttribute('download', 'file.pdf') //or any other extension
document.body.appendChild(link)
link.click()
// clean up "a" element & remove ObjectURL
document.body.removeChild(link)
URL.revokeObjectURL(href) //TODO: navigate to the preview page
})
.catch((error) => {
this.mnx_backendErrorHandler(error)
})
}
}
}
@ -211,7 +251,6 @@ export default {
<style lang="scss" scoped>
.request-hub {
@extend .container;
@extend .my-1;
}
.request-invitations-header-text {
@ -220,8 +259,6 @@ export default {
.request-invitations {
@extend .container;
@extend .border;
@extend .my-1;
}
.request-content-container {
@ -279,13 +316,12 @@ export default {
}
.request-description-container {
@extend .container;
@extend .border;
min-height: 4vh;
margin-bottom: 20px;
}
.make-proposal-button {
@extend .initiate-button;
@extend .btn;
@extend .btn-link;
@extend .float-end;
}
@ -294,8 +330,8 @@ export default {
}
.issuer-profile-container {
@extend .container;
@extend .border;
border-top: 1px solid #dee2e6;
padding-top: 15px;
}
.issuer-profile-label {
@ -321,26 +357,31 @@ export default {
@extend .flex-colum-container-aligned-start;
@extend .w-10;
@extend .my-1;
@extend .text-start;
}
.issuer-achievement-issuer-text {
@extend .text-start;
font-weight: bold;
}
.issuer-achievement-stay-container {
@extend .flex-colum-container-aligned-start;
@extend .w-10;
@extend .my-1;
@extend .text-start;
}
.issuer-achievement-stay-content-text {
@extend .text-start;
font-weight: bold;
}
.issuer-achievement-paid-container {
@extend .flex-colum-container-aligned-start;
@extend .w-10;
@extend .my-1;
@extend .text-start;
}
.issuer-achievement-deposit-container {
@ -351,19 +392,23 @@ export default {
.issuer-achievement-deposit-content-text {
@extend .text-start;
font-weight: bold;
}
.issuer-achievement-paid-content-text {
@extend .text-start;
font-weight: bold;
}
.issuer-achievement-credit-container {
@extend .flex-colum-container-aligned-start;
@extend .w-10;
@extend .my-1;
@extend .text-start;
}
.issuer-achievement-credit-content-text {
@extend .text-start;
font-weight: bold;
}
</style>

View File

@ -1,10 +1,13 @@
<template>
<div>
<p class="result-text">Your proposal has been submitted!</p>
<p class="result-text">
You can check the status of proposals in
<button class="goto-workspace-button" @click="gotoWorkspace">workspace</button>
</p>
<div class="submission-result-container">
<div class="submission-result-card">
<img src="@/assets/images/submited.png" alt="freeleaps" />
<span>Your proposal has been submitted!</span>
<span>
You can check the status of proposals in
<button class="btn btn-link" @click="gotoWorkspace()">My work.</button>
</span>
</div>
</div>
</template>
@ -36,12 +39,3 @@ export default {
}
}
</script>
<style lang="scss" scoped>
.result-text {
@extend .fs-2;
}
.goto-workspace-button {
@extend .proceed-button;
}
</style>

View File

@ -1,111 +1,115 @@
<template>
<div class="making-proposal-container offcanvas-parent">
<div
class="offcanvas offcanvas-end offcanvas-container"
tabindex="-1"
id="offcanvas-copy-existing"
aria-labelledby="offcanvas-copy-existing"
>
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvas-copy-existing">Copy from existing proposals</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
</div>
<div class="offcanvas-body">
<div class="accordion" id="existing-proposal-item-container">
<div
v-for="(existingProposal, index) in existingProposals"
:key="index"
:id="'existing-proposal' + index"
class="accordion-item"
>
<h2 class="accordion-header" :id="'existing-heading' + index">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#' + 'existing-proposal-collapse' + index"
aria-expanded="false"
:aria-controls="'collapse' + index"
>
{{ existingProposal.request.title }}
</button>
</h2>
<div class="making-proposal-content">
<div
class="offcanvas offcanvas-end offcanvas-container"
tabindex="-1"
id="offcanvas-copy-existing"
aria-labelledby="offcanvas-copy-existing"
>
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvas-copy-existing">Copy from existing proposals</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
</div>
<div class="offcanvas-body">
<div class="accordion" id="existing-proposal-item-container">
<div
:id="'existing-proposal-collapse' + index"
class="accordion-collapse collapse"
:aria-labelledby="'existing-heading' + index"
data-bs-parent="#existing-proposal-item-container"
v-for="(existingProposal, index) in existingProposals"
:key="index"
:id="'existing-proposal' + index"
class="accordion-item"
>
<div class="accordion-body">
<button class="copy-existing-button" @click="copyProposal(existingProposal)">
Copy
<h2 class="accordion-header" :id="'existing-heading' + index">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#' + 'existing-proposal-collapse' + index"
aria-expanded="false"
:aria-controls="'collapse' + index"
>
{{ existingProposal.request.title }}
</button>
<div class="existing-request-content-text" v-html="existingProposal.content"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="offcanvas offcanvas-end offcanvas-container"
tabindex="-1"
id="offcanvas-template"
aria-labelledby="offcanvas-template"
>
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvas-template">Apply proposal template</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
</div>
<div class="offcanvas-body">
<div class="accordion" id="template-item-container">
<div
v-for="(template, index) in templates"
:key="index"
:id="'template' + index"
class="accordion-item"
>
<h2 class="accordion-header" :id="'heading' + index">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#' + 'collapse' + index"
aria-expanded="false"
:aria-controls="'collapse' + index"
</h2>
<div
:id="'existing-proposal-collapse' + index"
class="accordion-collapse collapse"
:aria-labelledby="'existing-heading' + index"
data-bs-parent="#existing-proposal-item-container"
>
{{ template.title }}
</button>
</h2>
<div
:id="'collapse' + index"
class="accordion-collapse collapse"
:aria-labelledby="'heading' + index"
data-bs-parent="#template-item-container"
>
<div class="accordion-body">
<button class="load-template-button" @click="applyTemplate(template)">Apply</button>
<div class="template-content-text" v-html="template.proposal.content"></div>
<div class="accordion-body">
<button class="copy-existing-button" @click="copyProposal(existingProposal)">
Copy
</button>
<div
class="existing-request-content-text"
v-html="existingProposal.content"
></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="request" class="request-container">
<label class="request-label" for="request-content-container">For request:</label>
<div class="request-content-container" id="request-content-container">
<div class="accordion" id="accordion-request-container">
<div
class="offcanvas offcanvas-end offcanvas-container"
tabindex="-1"
id="offcanvas-template"
aria-labelledby="offcanvas-template"
>
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvas-template">Apply proposal template</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
</div>
<div class="offcanvas-body">
<div class="accordion" id="template-item-container">
<div
v-for="(template, index) in templates"
:key="index"
:id="'template' + index"
class="accordion-item"
>
<h2 class="accordion-header" :id="'heading' + index">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#' + 'collapse' + index"
aria-expanded="false"
:aria-controls="'collapse' + index"
>
{{ template.title }}
</button>
</h2>
<div
:id="'collapse' + index"
class="accordion-collapse collapse"
:aria-labelledby="'heading' + index"
data-bs-parent="#template-item-container"
>
<div class="accordion-body">
<button class="load-template-button" @click="applyTemplate(template)">
Apply
</button>
<div class="template-content-text" v-html="template.proposal.content"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="request" class="request-container" id="request-content-container">
<div class="accordion accordion-list" id="accordion-request-container">
<div class="accordion-item">
<h2 class="accordion-header">
<button
@ -116,7 +120,7 @@
aria-expanded="false"
aria-controls="collapse-request-content"
>
{{ request.title }}
<span class="dashed-container">{{ request.title }}</span>
</button>
</h2>
<div
@ -131,95 +135,158 @@
</div>
</div>
</div>
</div>
<div v-if="proposal" class="proposal-container">
<label class="proposal-label">Proposal:</label>
<div class="header-bar">
<button
class="load-templates-button"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#offcanvas-template"
aria-controls="offcanvas-template"
>
Templates...
</button>
<button
class="copy-proposal-button"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#offcanvas-copy-existing"
aria-controls="offcanvas-copy-existing"
>
Copy...
</button>
</div>
<div
class="cover-letter-content-container"
id="cover-letter-content"
contenteditable="true"
v-html="proposal.content"
@blur="coverLetterDone($event)"
></div>
<label class="summary-content-label" for="stage-content"> Milestones</label>
<div class="stage-content-container" id="stage-content">
<div v-for="(stage, index) in proposal.stages" :key="index" class="stage-item-container">
<div class="stage-item-content-container" id="stage-item-content">
<div class="stage-payment-container">
<label class="stage-content-label" for="stage-payment-content">Payment</label>
<div class="stage-payment-content-container" id="stage-payment-content">
<input
type="text"
class="stage-payment-content-text"
id="stage-payment-content-text"
v-model="stage.payment"
/>
<span class="stage-payment-content-span"> {{ stage.currency }}</span>
</div>
</div>
<div class="stage-duration-container">
<label class="stage-content-label" for="stage-duration-content">Duration</label>
<div class="stage-duration-content-container" id="stage-duration-content">
<input
type="text"
class="stage-duration-content-text"
v-model="stage.duration_in_days"
/>
<span class="stage-duration-content-span"> day(s)</span>
</div>
</div>
<div class="stage-note-container">
<label class="stage-content-label" for="stage-note-content">Notes</label>
<input class="stage-note-content-text" id="stage-note-content" v-model="stage.note" />
</div>
<button class="stage-item-delete-button" @click="removeStage(index)"></button>
<div v-if="proposal" class="proposal-container">
<div class="proposal-header-container">
<label class="proposal-label">Proposal:</label>
<div class="header-bar">
<button
class="btn btn-link"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#offcanvas-template"
aria-controls="offcanvas-template"
>
<svg-icon icon="btn-templates" />
Templates
</button>
<button
class="btn btn-link"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#offcanvas-copy-existing"
aria-controls="offcanvas-copy-existing"
>
<svg-icon icon="btn-history" />
Copy
</button>
</div>
</div>
<div class="stage-more-action-container">
<button class="stage-add-more-button" @click="addStage()">+ Add Milestones</button>
<div class="cover-letter-content-container" id="cover-letter-content">
<freeleaps-editor v-model:content="proposal.content" />
</div>
<div class="summary-content-container" id="summary-content">
<span class="summary-total-stages-content-span" id="summary-total-stages-content">
Total milestones:
{{ summary.total_stages }}
</span>
<span class="summary-total-payment-content-span" id="summary-total-payment-content">
Total payment:{{ summary.total_payment }} {{ summary.currency }}
</span>
<span class="summary-total-duration-content-span" id="summary-total-duration-content">
Total duration:{{ summary.total_duration_in_days }} day(s)
</span>
<label class="summary-content-label" for="stage-content"> Milestones</label>
<div class="stage-content-container" id="stage-content">
<div v-for="(stage, index) in proposal.stages" :key="index" class="stage-item-container">
<div class="input-container milestone-input-container">
<div class="form-group">
<div class="input-group-container">
<div class="form-floating">
<input
class="input-email"
:id="`stage-payment-content-${index}`"
type="number"
v-model="stage.payment"
/>
<label :for="`stage-payment-content-${index}`">Payment</label>
</div>
<span class="btn-start">{{ stage.currency }}</span>
</div>
</div>
</div>
<div class="input-container milestone-input-container">
<div class="form-group">
<div class="input-group-container">
<div class="form-floating">
<input
class="input-email"
:id="`stage-duration-content-${index}`"
type="number"
v-model="stage.duration_in_days"
/>
<label :for="`stage-duration-content-${index}`">Duration</label>
</div>
<span class="btn-start">day(s)</span>
</div>
</div>
</div>
<div class="input-container milestone-input-container">
<div class="form-group">
<div class="input-group-container">
<div class="form-floating">
<input
class="input-email"
:id="`stage-note-content-${index}`"
type="text"
v-model="stage.note"
/>
<label :for="`stage-note-content-${index}`">Notes</label>
</div>
</div>
</div>
</div>
<button class="stage-item-delete-button">
<svg-icon v-if="index != 0" icon="delete" @click="removeStage(index)" />
</button>
<!-- <div class="stage-item-content-container" id="stage-item-content">
<div class="stage-payment-container">
<label class="stage-content-label" for="stage-payment-content">Payment</label>
<div class="stage-payment-content-container" id="stage-payment-content">
<input
type="text"
class="stage-payment-content-text"
id="stage-payment-content-text"
v-model="stage.payment"
/>
<span class="stage-payment-content-span"> {{ stage.currency }}</span>
</div>
</div>
<div class="stage-duration-container">
<label class="stage-content-label" for="stage-duration-content">Duration</label>
<div class="stage-duration-content-container" id="stage-duration-content">
<input
type="text"
class="stage-duration-content-text"
v-model="stage.duration_in_days"
/>
<span class="stage-duration-content-span"> day(s)</span>
</div>
</div>
<div class="stage-note-container">
<label class="stage-content-label" for="stage-note-content">Notes</label>
<input class="stage-note-content-text" id="stage-note-content" v-model="stage.note" />
</div>
</div> -->
</div>
<div class="stage-more-action-container">
<button class="stage-add-more-button" @click="addStage()">+ Add Milestones</button>
</div>
<div class="summary-content-container" id="summary-content">
<span id="summary-total-stages-content">
Total milestones:
{{ summary.total_stages }}
</span>
<span id="summary-total-payment-content">
Total payment:{{ summary.total_payment }} {{ summary.currency }}
</span>
<span id="summary-total-duration-content">
Total duration:{{ summary.total_duration_in_days }} day(s)
</span>
</div>
</div>
<div class="file-upload-container">
<div class="upload-contianer">
<label class="btn btn-link">
<span v-if="!uploadFile">Upload file</span>
<span v-if="uploadFile">{{ uploadFile.name }}</span>
<input type="file" hidden @change="handleFileUpload" />
</label>
<svg-icon
v-if="uploadFile"
icon="delete"
class-name="delete-icon"
@click.stop="clearFile"
/>
</div>
<!-- <label for="file-upload" class="file-upload-label">Upload File:</label>
<input type="file" id="file-upload" class="file-upload-input" @change="handleFileUpload" /> -->
</div>
</div>
<div class="file-upload-container">
<label for="file-upload" class="file-upload-label">Upload File:</label>
<input type="file" id="file-upload" class="file-upload-input" @change="handleFileUpload" />
<div class="action-footer">
<button class="cancel-button" @click="back">Back</button>
<button class="submit-button" @click="submit">Submit</button>
</div>
</div>
<div class="action-footer">
<button class="cancel-button" @click="back">Back</button>
<button class="submit-button" @click="submit">Submit</button>
</div>
</div>
</template>
@ -232,8 +299,11 @@ import {
preparedProposalSummary,
proposalUtils
} from '../../../types/index'
import SvgIcon from '@/components/SvgIcon.vue'
import FreeleapsEditor from '@/components/FreeleapsEditor.vue'
export default {
name: 'MakeProposal',
components: { SvgIcon, FreeleapsEditor },
props: {
requestId: {
required: true,
@ -252,7 +322,7 @@ export default {
},
watch: {
'proposal.stages': {
handler(val, oldVal) {
handler() {
this.caculateSummary()
},
deep: true
@ -281,6 +351,9 @@ export default {
handleFileUpload(event) {
this.uploadFile = event.target.files[0]
},
clearFile() {
this.uploadFile = null
},
back() {
this.mnx_goBack()
},
@ -357,7 +430,7 @@ export default {
}
},
addStage() {
this.proposal.stages.push(this.emptyStage)
this.proposal.stages.push({ ...this.emptyStage })
},
removeStage(index) {
@ -399,6 +472,13 @@ export default {
<style lang="scss" scoped>
.making-proposal-container {
@extend .container;
padding-top: 24px;
padding-bottom: 24px;
}
.making-proposal-content {
border-radius: 12px;
box-shadow: 0px 0px 24px 0px #d4d3e380;
}
.request-label {
@ -408,8 +488,11 @@ export default {
.request-container {
@extend .container;
@extend .my-3;
@extend .border;
padding: 0;
.accordion-list {
margin: 0;
box-shadow: none;
}
}
.request-content-container {
@ -423,17 +506,32 @@ export default {
.proposal-container {
@extend .flex-colum-container;
@extend .border;
}
.proposal-header-container {
display: flex;
padding: 15px 24px;
align-items: center;
@extend .w-100;
justify-content: space-between;
border-bottom: 1px solid #e7e8eb;
}
.header-bar {
@extend .flex-row-container;
@extend .justify-content-end;
align-items: center;
.btn {
white-space: nowrap;
padding: 0;
margin-left: 15px;
}
}
.proposal-label {
@extend .label-text;
@extend .w-100;
font-size: 18px;
font-weight: bold;
color: #0d1637;
}
.load-templates-button {
@ -459,9 +557,8 @@ export default {
.cover-letter-content-container {
@extend .container;
min-height: 40vh;
overflow: hidden;
@extend .text-start;
@extend .border;
padding: 0;
margin-bottom: 20px;
}
.stage-content-label {
@ -471,13 +568,17 @@ export default {
.stage-content-container {
@extend .container;
min-height: 10vh;
@extend .border;
padding: 0;
}
.stage-item-container {
@extend .flex-colum-container;
@extend .m-1;
// @extend .flex-colum-container;
// @extend .m-1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #e7e8eb;
}
.stage-item-content-label {
@ -554,18 +655,20 @@ export default {
}
.stage-more-action-container {
@extend .flex-row-container;
@extend .justify-content-around;
@extend .my-1;
padding: 12px 0;
border-bottom: 1px solid #e7e8eb;
}
.stage-item-delete-button {
@extend .close-button;
width: 42px;
@extend .btn;
@extend .btn-link;
}
.stage-add-more-button {
@extend .proceed-button;
@extend .w-70;
@extend .btn;
@extend .btn-outline-primary;
@extend .w-100;
}
.summary-container {
@ -575,14 +678,26 @@ export default {
}
.summary-content-label {
@extend .label-text;
@extend .proposal-label;
@extend .w-100;
@extend .text-start;
padding: 12px 0;
border-bottom: 1px solid #e7e8eb;
}
.summary-content-container {
@extend .flex-row-container;
@extend .justify-content-around;
@extend .my-1;
padding: 12px 0;
border-bottom: 1px solid #e7e8eb;
span {
@extend .text-start;
display: block;
flex: 1;
font-size: 12px;
font-weight: 600;
color: #0d1637;
}
}
.summary-total-stages-content-span {
@ -601,16 +716,20 @@ export default {
}
.action-footer {
@extend .flex-row-container;
@extend .justify-content-around;
padding: 12px 0;
text-align: left;
}
.cancel-button {
@extend .stop-button;
@extend .mx-3;
@extend .min-btn;
}
.submit-button {
@extend .proceed-button;
@extend .mx-3;
@extend .min-btn;
}
.offcanvas-parent {
@ -643,9 +762,13 @@ export default {
}
.file-upload-container {
@extend .flex-row-container;
@extend .justify-content-start;
@extend .my-3;
padding: 12px 0;
width: 100%;
text-align: left;
border-bottom: 1px solid #e7e8eb;
.btn-link {
padding: 0;
}
}
.file-upload-label {
@ -659,4 +782,25 @@ export default {
@extend .form-control;
@extend .w-30;
}
.milestone-input-container {
width: calc(33.3% - 30px);
border: 2px dashed #9cb0f6;
.btn-start {
background: transparent;
color: #0d1637;
border: none;
display: flex;
align-items: center;
pointer-events: none;
}
}
.delete-icon {
color: $primary;
cursor: pointer;
&:hover {
opacity: 0.78;
}
}
</style>

View File

@ -1,9 +1,9 @@
<template>
<div class="workspace-container">
<div class="workspace-header"></div>
<!-- <div class="workspace-header"></div> -->
<div class="workspace-body">
<div
class="accordion"
class="accordion accordion-list"
v-for="(project, project_index) in projects"
:key="project_index"
:id="project.id"
@ -18,7 +18,7 @@
aria-expanded="false"
:aria-controls="'collapse-' + project_index"
>
<div class="workspace-item-bar">
<div class="workspace-item-bar dashed-container">
<div class="workspace-item-bar-left">
<div class="project-item-title-container">
<label class="project-item-label">Summary</label>
@ -912,7 +912,10 @@ export default {
<style scoped lang="scss">
.workspace-container {
@extend .flex-colum-container;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.workspace-header {
@ -921,24 +924,32 @@ export default {
}
.workspace-body {
@extend .container;
width: 100%;
max-width: $body-width;
padding: 24px 0;
}
.workspace-item-bar {
@extend .flex-row-container;
display: flex;
align-items: center;
// margin: 0 24px 0 0;
// border: 1px dashed #aebffd;
// border-radius: 3px;
// flex: 1;
}
.workspace-item-bar-left {
@extend .flex-row-container;
@extend .flex-grow-1;
@extend .border;
@extend .me-3;
padding: 0;
// @extend .border;
// @extend .me-3;
}
.workspace-item-bar-right {
@extend .flex-row-container;
@extend .w-20;
@extend .border;
// @extend .border;
}
.project-item-title-container {
@ -968,11 +979,11 @@ export default {
.project-item-text {
@extend .text-start;
@extend .mx-1;
margin-bottom: 0;
}
.project-request-container {
@extend .container;
@extend .border;
// padding: 32px;
}
.float-action-container {
@ -980,7 +991,9 @@ export default {
}
.request-action-withdraw {
@extend .initiate-button;
@extend .btn;
@extend .btn-link;
padding: 0;
}
.project-request-content {
@ -988,8 +1001,8 @@ export default {
}
.project-request-proposals-container {
@extend .container;
@extend .border;
// @extend .container;
// @extend .border;
}
.project-request-proposal-bar-container {
@ -1071,7 +1084,8 @@ export default {
}
.proposal-action-withdraw {
@extend .initiate-button;
@extend .btn;
@extend .btn-link;
}
.project-milestone-bar-container {

View File

@ -42,9 +42,7 @@
data-bs-parent="#existing-request-item-container"
>
<div class="accordion-body">
<button class="copy-existing-button" @click="copyRequest(existingRequest)">
Copy
</button>
<button class="btn btn-link" @click="copyRequest(existingRequest)">Copy</button>
<div class="existing-request-content-text" v-html="existingRequest.content"></div>
</div>
</div>
@ -84,7 +82,7 @@
aria-expanded="false"
:aria-controls="'collapse' + index"
>
{{ template.title }}
<span class="dashed-container">{{ template.title }}</span>
</button>
</h2>
<div
@ -94,7 +92,10 @@
data-bs-parent="#template-item-container"
>
<div class="accordion-body">
<button class="select-template-button" @click="selectTemplate(template)">
<button
class="select-template-button btn btn-link"
@click="selectTemplate(template)"
>
Apply
</button>
<div class="template-content-textarea" v-html="template.content"></div>
@ -105,39 +106,44 @@
</div>
</div>
<div class="action-bar">
<div class="product-dropdown-container">
<label>Product Name:</label>
<select class="product-dropdown" v-model="selectedProduct">
<option value="new">New ...</option>
<option v-for="product in products" :value="product.name" :key="product">
{{ product.name }}
</option>
</select>
<input
v-if="selectedProduct === 'new'"
type="text"
class="product-input-box"
v-model="newProduct"
placeholder="Enter new product name"
<input-selector
:select-list="products"
:selected="selectedProduct"
@selectedChange="selectedChange"
/>
<div class="upload-contianer">
<label class="btn btn-link">
<span v-if="!uploadFile">Upload file</span>
<span v-if="uploadFile">{{ uploadFile.name }}</span>
<input type="file" hidden @change="handleFileUpload" />
</label>
<svg-icon
v-if="uploadFile"
icon="delete"
class-name="delete-icon"
@click.stop="clearFile"
/>
</div>
<div class="flex-1" />
<button
class="action-button"
class="action-button btn btn-link"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#offcanvas-template"
aria-controls="offcanvas-template"
>
Templates...
<svg-icon icon="btn-templates" />
Templates
</button>
<button
class="action-button"
class="action-button btn btn-link"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#offcanvas-copy-existing"
aria-controls="offcanvas-copy-existing"
>
Copy...
<svg-icon icon="btn-history" />
Copy
</button>
</div>
<div class="description-container">
@ -150,15 +156,15 @@
@blur="descriptionDone($event)"
contenteditable="true"
/> -->
<VueQuill v-model:content="content" />
<freeleaps-editor v-model:content="content" />
</div>
<div class="file-upload-container">
<!-- <div class="file-upload-container">
<label for="file-upload" class="file-upload-label">Upload File:</label>
<input type="file" id="file-upload" class="file-upload-input" @change="handleFileUpload" />
</div>
</div> -->
<div class="action-footer">
<button class="cancel-button" @click="back">Back</button>
<button class="submit-button" @click="submit">Submit</button>
<button class="cancel-button" @click="back">Cancel</button>
<button class="submit-button" @click="submit">(Re)Submit</button>
</div>
</div>
</template>
@ -166,7 +172,9 @@
<script>
import { WorksapceApi, textAreaAujuster, requestIssueUtils } from '../../../../utils/index'
import { requestIssuingModelEnum } from '../../../../types/index'
import VueQuill from '@/components/VueQuill.vue'
import FreeleapsEditor from '@/components/FreeleapsEditor.vue'
import InputSelector from '@/components/InputSelector.vue'
import SvgIcon from '@/components/SvgIcon.vue'
export default {
name: 'IssueRequest',
@ -180,7 +188,7 @@ export default {
type: String
}
},
components: { VueQuill },
components: { FreeleapsEditor, InputSelector, SvgIcon },
mounted() {
this.initProducts()
this.initiateFromInput()
@ -194,26 +202,36 @@ export default {
request_id: null,
availableTemplates: null,
existingRequests: null,
products: null,
products: [],
selectedProduct: null,
newProduct: null,
uploadFile: null
}
},
methods: {
handleFileUpload(event) {
this.uploadFile = event.target.files[0]
if (event.target.files[0]) {
this.uploadFile = event.target.files[0]
}
},
clearFile() {
this.uploadFile = null
},
initProducts() {
WorksapceApi.fetchProducts()
.then((response) => {
this.products = response.data
this.products = (response.data || []).map((p) => p.name)
})
.catch((error) => {
this.mnx_backendErrorHandler(error)
})
},
selectedChange(item) {
this.selectedProduct = item.selected
if (item.isNew) {
this.products.push(item.selected)
}
},
fetchExisting() {
WorksapceApi.fetchMyExistingRequests('')
.then((response) => {
@ -290,9 +308,6 @@ export default {
},
submit() {
var product = this.selectedProduct
if (this.selectedProduct === 'new') {
product = this.newProduct.trim()
}
if (!product) {
alert('Please input a product name or select a product')
@ -322,19 +337,32 @@ export default {
}
</script>
<style lang="scss" scoped>
.delete-icon {
color: $primary;
cursor: pointer;
&:hover {
opacity: 0.78;
}
}
.request-issue-container {
@extend .flex-colum-container;
box-shadow: 0px 0px 24px 0px rgba(212, 211, 227, 0.5);
border-top-left-radius: 12px;
border-top-right-radius: 12px;
overflow: hidden;
}
.action-bar {
@extend .flex-row-container;
@extend .justify-content-end;
border-bottom: 1px solid #e7e8eb;
padding-top: 6px;
padding-bottom: 6px;
}
.action-button {
@extend .initiate-button;
@extend .mx-3;
@extend .my-1;
margin-left: 16px;
// .action-btn-icon {color: $primary}
}
.title-container {
@ -350,6 +378,7 @@ export default {
.description-container {
@extend .w-100;
@extend .my-3;
flex: 1;
}
.input-description {
@ -485,11 +514,13 @@ export default {
.cancel-button {
@extend .stop-button;
@extend .mx-3;
@extend .min-btn;
}
.submit-button {
@extend .proceed-button;
@extend .mx-3;
@extend .min-btn;
}
.offcanvas-parent {
@ -507,7 +538,7 @@ export default {
}
.select-template-button {
@extend .initiate-button;
// @extend .initiate-button;
@extend .float-end;
}

View File

@ -1,10 +1,13 @@
<template>
<div class="submission-result-container">
<p class="submission-result-text">The request has been submitted!</p>
<p class="submission-result-text">
You can find it in
<button class="workspace-button" @click="gotoWorkspace()">workspace</button>
</p>
<div class="submission-result-card">
<img src="@/assets/images/submited.png" alt="freeleaps" />
<span>The request has been submitted!</span>
<span>
You can find it in
<button class="btn btn-link" @click="gotoWorkspace()">My work.</button>
</span>
</div>
</div>
</template>
@ -36,18 +39,3 @@ export default {
}
}
</script>
<style lang="scss" scoped>
.submission-result-container {
@extend .m-3;
@extend .p-3;
@extend .container;
}
.submission-result-text {
@extend .fs-2;
}
.workspace-button {
@extend .inplace-proceed-button;
}
</style>

View File

@ -1,3 +1,2 @@
export { store } from './store/index'
export { router } from './router/index'
export { pdfjsLib } from './pdfjs/index'

View File

@ -1 +0,0 @@
export { pdfjsLib } from './pdfjs.js'

View File

@ -1,7 +0,0 @@
import * as pdfjsLib from 'pdfjs-dist'
//need to set worker before the pdfjs can be used.
// pdfjsLib.GlobalWorkerOptions.workerPort = new Worker(
// new URL('pdfjs-dist/build/pdf.worker.mjs', import.meta.url)
// )
export { pdfjsLib }

View File

@ -58,10 +58,15 @@ import UserHistory from '../../pages/user/account/UserHistory.vue'
import Workspace from '../../pages/user/workspace/Home.vue'
import FooterGuest from '../../footers/FooterGuest.vue'
import FooterUser from '../../footers/FooterUser.vue'
import HeaderGuest from '../../headers/HeaderGuest.vue'
import HeaderUser from '../../headers/HeaderUser.vue'
//Lab
import LabHome from '../../pages/lab/Home.vue'
import TranslationHome from '../../pages/lab/translation/Home.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
@ -176,7 +181,7 @@ const router = createRouter({
name: 'my-workspace-projects',
path: '/my-workspace-projects',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: MyWorkspaceProjects, footer: FooterGuest, header: HeaderUser },
components: { default: MyWorkspaceProjects, footer: FooterUser, header: HeaderUser },
props: false
},
@ -184,7 +189,7 @@ const router = createRouter({
name: 'project-manage',
path: '/project-manage/:projectId',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: ProjectManage, footer: FooterGuest, header: HeaderUser },
components: { default: ProjectManage, footer: FooterUser, header: HeaderUser },
props: true,
children: [
{
@ -229,14 +234,14 @@ const router = createRouter({
name: 'my-workspace-requests',
path: '/my-workspace-requests',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: MyWorkspaceRequests, footer: FooterGuest, header: HeaderUser },
components: { default: MyWorkspaceRequests, footer: FooterUser, header: HeaderUser },
props: false
},
{
name: 'request-issue',
path: '/request-issue/:loadFrom',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: IssueRequest, footer: FooterGuest, header: HeaderUser },
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'Post' },
components: { default: IssueRequest, footer: FooterUser, header: HeaderUser },
props: (route) => {
/**
* This would preserve the other route.params object properties overriding only
@ -253,7 +258,7 @@ const router = createRouter({
name: 'request-issue2',
path: '/request-issue/:loadFrom/:requestId',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: IssueRequest, footer: FooterGuest, header: HeaderUser },
components: { default: IssueRequest, footer: FooterUser, header: HeaderUser },
props: (route) => {
/**
* This would preserve the other route.params object properties overriding only
@ -269,64 +274,64 @@ const router = createRouter({
{
name: 'request-submitted',
path: '/request-submitted/:requestId',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: RequestSubmitted, footer: FooterGuest, header: HeaderUser },
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'Post' },
components: { default: RequestSubmitted, footer: FooterUser, header: HeaderUser },
props: true
},
{
name: 'request-deposit',
path: '/request-deposit/:requestId',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: RequestDeposit, footer: FooterGuest, header: HeaderUser },
components: { default: RequestDeposit, footer: FooterUser, header: HeaderUser },
props: true
},
{
name: 'request-deposited',
path: '/request-deposited/:requestId',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: RequestDeposited, footer: FooterGuest, header: HeaderUser },
components: { default: RequestDeposited, footer: FooterUser, header: HeaderUser },
props: true
},
{
name: 'my-workspace-proposals',
path: '/my-workspace-proposals',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: MyWorkspaceProposals, footer: FooterGuest, header: HeaderUser },
components: { default: MyWorkspaceProposals, footer: FooterUser, header: HeaderUser },
props: false
},
//message hub
{
name: 'message-hub',
path: '/message-hub',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: MessageHub, footer: FooterGuest, header: HeaderUser }
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'message' },
components: { default: MessageHub, footer: FooterUser, header: HeaderUser }
},
// provider hub
{
name: 'provider-hub',
path: '/provider-hub',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: ProviderHub, footer: FooterGuest, header: HeaderUser }
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'Providers' },
components: { default: ProviderHub, footer: FooterUser, header: HeaderUser }
},
//request hub
{
name: 'request-hub',
path: '/request-hub',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: RequestHub, footer: FooterGuest, header: HeaderUser }
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'Requests' },
components: { default: RequestHub, footer: FooterUser, header: HeaderUser }
},
{
name: 'make-proposal',
path: '/make-proposal/:requestId/:loadingMode',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: MakeProposal, footer: FooterGuest, header: HeaderUser },
components: { default: MakeProposal, footer: FooterUser, header: HeaderUser },
props: true
},
{
name: 'proposal-submitted',
path: '/proposal-submitted/:proposalId',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: ProposalSubmitted, footer: FooterGuest, header: HeaderUser },
components: { default: ProposalSubmitted, footer: FooterUser, header: HeaderUser },
props: true
},
@ -334,7 +339,7 @@ const router = createRouter({
name: 'request-manage',
path: '/request-manage/:requestId',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: RequestManage, footer: FooterGuest, header: HeaderUser },
components: { default: RequestManage, footer: FooterUser, header: HeaderUser },
props: true
},
{
@ -343,7 +348,7 @@ const router = createRouter({
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: {
default: ReviewProposalBeforeAccetance,
footer: FooterGuest,
footer: FooterUser,
header: HeaderUser
},
props: true
@ -353,25 +358,37 @@ const router = createRouter({
name: 'user-profile',
path: '/user-profile',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: UserProfile, footer: FooterGuest, header: HeaderUser }
components: { default: UserProfile, footer: FooterUser, header: HeaderUser }
},
{
name: 'user-finance',
path: '/user-finance',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: UserFinance, footer: FooterGuest, header: HeaderUser }
components: { default: UserFinance, footer: FooterUser, header: HeaderUser }
},
{
name: 'user-history',
path: '/user-history',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: UserHistory, footer: FooterGuest, header: HeaderUser }
components: { default: UserHistory, footer: FooterUser, header: HeaderUser }
},
{
name: 'work-space',
path: '/work-space',
meta: { requiredRoles: [userRoleEnum.PERSONAL], active: 'workspace' },
components: { default: Workspace, footer: FooterGuest, header: HeaderUser }
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'Workspace' },
components: { default: Workspace, footer: FooterUser, header: HeaderUser }
},
{
name: 'lab-home',
path: '/lab-home',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: LabHome, footer: FooterUser, header: HeaderUser }
},
{
name: 'machine-translation',
path: '/machine-translation',
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
components: { default: TranslationHome, footer: FooterUser, header: HeaderUser }
}
],

View File

@ -35,8 +35,8 @@ class ProposalUtils {
if (stages)
stages.forEach((element) => {
summary.total_stages++
summary.total_payment += element.payment
summary.total_duration_in_days += element.duration_in_days
summary.total_payment += Number(element.payment)
summary.total_duration_in_days += Number(element.duration_in_days)
summary.currency = element.currency
})
}

View File

@ -6,3 +6,4 @@ export { ProviderHubApi } from './providerHub'
export { MessageHubApi } from './messageHub'
export { HistoryApi } from './history'
export { ContentApi } from './content'
export { LabApi } from './lab'

View File

@ -0,0 +1,18 @@
import { backendAxios } from './axios'
import { userUtils } from '../store/index'
class LabApi {
static translate_text(input_text) {
let jwt = userUtils.getJwtToken()
const request = backendAxios.post(
'/api/lab/translate_text',
{
input_text: input_text
},
{
headers: { Authorization: `Bearer ${jwt}` }
}
)
return request
}
}
export { LabApi }

View File

@ -319,6 +319,32 @@ class WorksapceApi {
})
return request
}
static fetchAttachedFileAsMediaData(request_id, document_id) {
let jwt = userUtils.getJwtToken()
const request = backendAxios.post(
'/api/workspace/request/fetch-attached-file-as-media-data',
{
request_id: request_id,
document_id: document_id
},
{
headers: { Authorization: `Bearer ${jwt}` }
}
)
return request
}
static fetchAttachedFileAsDownload(request_id, document_id) {
let jwt = userUtils.getJwtToken()
const request = backendAxios.get('/api/workspace/request/fetch-attached-file-as-download', {
params: {
request_id: request_id,
document_id: document_id
},
responseType: 'blob',
headers: { Authorization: `Bearer ${jwt}` }
})
return request
}
}
export { WorksapceApi }

View File

@ -38,6 +38,15 @@ class DateUtils {
}
}
static FromJsonToHMDateString(o) {
let date = DateUtils.FromJson(o)
if (date) {
return `${date.getUTCHours().toString().padStart(2, '0')}:${date.getUTCMinutes().toString().padStart(2, '0')}`
} else {
return null
}
}
static GetDeltaInDays(o) {
let date = o instanceof Date ? o : DateUtils.FromJson(o)
return Math.abs(Math.floor((date - Date.now()) / (1000 * 60 * 60 * 24)))

View File

@ -6,13 +6,14 @@ export {
ProviderHubApi,
MessageHubApi,
HistoryApi,
ContentApi
ContentApi,
LabApi
} from './backend/index'
export { userUtils, userProfileUtils, requestIssueUtils, requestHubUtils } from './store/index'
export { ValueChecker, DateUtils, OIDUtils } from './common/index'
export { passwordValidator } from './validator/index'
export { passwordValidator, applicantValidator } from './validator/index'
export { textAreaAujuster, tooltip, elementHandler, mediaDataHandler } from './html/index'

View File

@ -1 +1,2 @@
export { passwordValidator } from './passwordValidator'
export { applicantValidator } from './applicantValidator'

View File

@ -1,5 +1,5 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import { defineConfig, loadEnv } from 'vite'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import vue from '@vitejs/plugin-vue'
@ -9,51 +9,71 @@ function resolve(dir) {
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
createSvgIconsPlugin({
iconDirs: [resolve('src/icons')],
symbolId: 'icon-[name]'
}),
],
resolve: {
root: resolve('src'),
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'~bootstrap': resolve('node_modules/bootstrap'),
'~quill': resolve('node_modules/quill'),
}
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/assets/styles/main.scss";`
},
styl: {
additionalData: ` @import '~quill/dist/quill.core.css';'~quill/dist/quill.bubble.css';'~quill/dist/quill.snow.css';`
}
}
},
server: {
proxy: {
'^/api/': {
target: 'http://localhost:8001/',
ws: true,
changeOrigin: true
}
}
},
optimizeDeps: {
esbuildOptions: {
supported: {
"top-level-await": true
},
},
},
esbuild: {
supported: {
'top-level-await': true //browsers can handle top-level-await features
},
export default defineConfig(({ command, mode }) => {
let API_URL = 'http://127.0.0.1:8001'
let PORT = '5173'
if (command === 'serve') {
// dev specific config
console.log('loading environment variables from .env and .env.' + mode + ' under ' + process.cwd())
const env = loadEnv(mode, process.cwd());
API_URL = `${env.VITE_API_URL ?? 'http://127.0.0.1:8001'}`;
PORT = `${env.VITE_PORT ?? '5173'}`;
} else {
// command === 'build'
// build specific config
}
})
console.log('backend api url: ' + API_URL)
console.log('server port: ' + PORT)
return {
plugins: [
vue(),
createSvgIconsPlugin({
iconDirs: [resolve('src/icons')],
symbolId: 'icon-[name]'
}),
],
resolve: {
root: resolve('src'),
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'~bootstrap': resolve('node_modules/bootstrap'),
'~quill': resolve('node_modules/quill'),
}
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/assets/styles/main.scss";`
},
styl: {
additionalData: ` @import '~quill/dist/quill.core.css';'~quill/dist/quill.bubble.css';'~quill/dist/quill.snow.css';`
}
}
},
server: {
proxy: {
'^/api/': {
target: API_URL,
ws: true,
changeOrigin: true
}
},
port: PORT,
},
optimizeDeps: {
esbuildOptions: {
supported: {
"top-level-await": true
},
},
},
esbuild: {
supported: {
'top-level-await': true //browsers can handle top-level-await features
},
}
}
}
)