Vue
by joplomacedo
HTML
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css">
<div id="app">
<div class="sms_edit__box l-stack">
<div class="sms_edit__info_row l-stack">
<div class="sms_edit__character_count_info">
<span>Character count</span> <span>{{ smsBodyCharCount }}/{{ CHARS_COUNT_PER_SEGMENT * smsSegmentCount}}</span>
</div>
<accordion-item v-if="smsSegmentCount > 1">
<template v-slot:header>
<i class="sms_edit__info_warning_icon fas fa-exclamation-triangle"></i>
Counts as {{smsSegmentCount}} texts because your text exceeds {{CHARS_COUNT_PER_SEGMENT}} characters
</template>
<template v-slot:body>
Clear detailed explanation goes here...
</template>
</accordion-item>
<accordion-item v-if="smsContainsCustomVars">
<template v-slot:header>
<i class="sms_edit__info_warning_icon fas fa-exclamation-triangle"></i>
You are using dynamic variables in your text
(<span>
<span class="sms_edit__info_custom_var" v-for="(smsCustomVar, i) in smsCustomVars" :key="i">#{{smsCustomVar}}</span>
</span>). The real cost can be different than the one presented here.
</template>
<template v-slot:body>
Because the text's length will depend on the actual value of the variables you're using (likely different for each appointment) Reoodle cannot determine the actual size of the text and the corresponding text count. The displayed text count is just an estimation (each dynamic variable is counted as 10 characters)
</template>
</accordion-item>
</div>
<div class="sms_edit__form_row">
<textarea class="sms_edit__sms_body_field field field--texarea" v-model="smsBody"></textarea>
</div>
</div>
</div>
CSS
*,
*:before,
*:after {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0;
font: inherit;
color: inherit;
}
html {
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
word-spacing: 1px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
line-height: 1.5;
}
ol,
ul {
list-style: none;
}
a,
button,
input[type="button"],
input[type="submit"] {
cursor: pointer;
}
a {
text-decoration: none;
}
textarea {
display: block;
width: 100%;
}
body {
padding: 2em;
}
.l-stack > * + * {
margin-top: 1em;
}
.accordion__item {
--border-radius: 4px;
background-color: #eee;
font-size: .9em;
margin-bottom: .4em;
border-radius: var(--border-radius);
}
.accordion__header {
padding: .7em 1em .8em;
cursor: pointer;
background: #ebebeb;
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
.accordion__header_content {
margin-right: .5em;
}
.accordion__toggle_indicator {
text-decoration: underline;
color: blue;
}
.accordion__body {
padding: 1em 1em 1.2em;
background: #f7f7f7;
color: #6d6d6d;
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.accordion__item.is-open .accordion__body {
display: block;
}
.accordion__item.is-closed .accordion__body {
display: none;
}
.field {
border: 1px solid #ccc;
border-radius: 2px;
padding: 1em;
}
.field--texarea {
min-height: 17em;
}
.sms_edit__info_warning_icon {
color: #e8b030;
}
.sms_edit__info_custom_var {
display: inline-block;
line-height: 1.2;
padding: .1em .3em;
border-radius: 2px;
background-color: #fff;
margin-right: .3em;
}
.sms_edit__info_custom_var:last-child {
margin-right: 0;
}
Vue
//utils
function removeWhitespace(str) {
return str.trim().replace(/^ +| +$/gm, "");
}
function removeWhitespaceAndNewLines(str) {
return str.replace(/(\r\n|\n|\r)/gm, " ").replace(/\s+/g, " ");
}
function isAlphaNumeric(str) {
var code, i, len;
for (i = 0, len = str.length; i < len; i++) {
code = str.charCodeAt(i);
if (
!(code > 47 && code < 58) && // numeric (0-9)
!(code > 64 && code < 91) && // upper alpha (A-Z)
!(code > 96 && code < 123)
) {
// lower alpha (a-z)
return false;
}
}
return true;
}
Vue.component('accordion-item', {
template: `
<div class="accordion__item l-stack" :class="isOpen ? 'is-open' : 'is-closed'">
<div class="accordion__header" @click="handleHeaderClick">
<span class="accordion__header_content">
<slot name="header">
</slot>
</span>
<span class="accordion__toggle_indicator">
Learn more.
</span>
</div>
<div class="accordion__body">
<slot name="body">
</slot>
</div>
</div>
`,
data() {
return {
isOpen: false
}
},
methods: {
handleHeaderClick() {
this.isOpen = !this.isOpen;
}
}
});
new Vue({
el: "#app",
data: {
REOODLE_VARS: ["dt", "datetime", "date", "time"],
CHARS_COUNT_PER_CUSTOM_VAR_ESTIMATATION: 10,
CHARS_COUNT_PER_SEGMENT: 160,
smsBody: removeWhitespace(`
Hello Rita,
This is just a reminder of your hairstyling appointment at 15:00 at 31/12/2020.
If you cannot attend please call us today at 888-808-505.
Thank you,
Sandra's Hair Magic
`),
},
computed: {
cleanSmsBody() {
return removeWhitespace(this.smsBody);
},
smsBodyCharCount() {
const smsCustomVarsKeysCharCount = this.smsCustomVars.reduce(
(acc, smsCustomVars) => {
//1 is to count with the '#'
return acc + 1 + smsCustomVars.length;
},
0
);
const smsCustomVarsCharCountEstimation...