tymonial
Version:
A intuitive UI library which ease the process of collecting and viewing users feedbacks within yourt web applications.
394 lines (331 loc) • 12.4 kB
JavaScript
// Tymonial
// Easily collect feedbacks from your clients, control feedback, embedded feedback within your site with ease.
// COPYRIGHT MIT LICENSED
// Creator: Benaiah Alumona
// Github: https://github.com/benrobo
// Main App: https://github.com/Benrobo/tymonial-lib
const $ = (el) => document.querySelector(el);
const $all = (el) => document.querySelectorAll(el);
const log = (el) => console.log(el);
const ratings = (count = 5, color) => {
let stars = [];
for (let i = 0; i < count; i++) {
let star = `
<svg class="h-5 w-5 icons rating-icon" viewBox="0 0 20 20" fill="currentColor" style="color:${color}">
<path
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
`;
stars.push(star);
}
return stars.join("");
};
class TymonialParamsError extends Error {
constructor(message) {
super(message);
this.name = "TymonialParamsError";
}
}
class Tymonial {
// https://tymonial.tk/api
#BASE_URL = `https://tymonial.tk/api`;
#error = null;
constructor(params) {
this.tymonialParams = params;
this.#validateParams(params);
this.containerElement = $(params?.element);
this.containerElement.classList.add("tymonial-container-element");
}
async #isTemplateAndUserIdExist(template_id, user_id) {
const { req, result, loading } = await this.#fetch("/user", {
method: "POST",
body: JSON.stringify({
userId: user_id,
}),
});
if (result.error) {
throw new TymonialParamsError(result.message);
}
const users = result.data;
if (users.length === 0) {
throw new TymonialParamsError(
`No user was found with this ID "${user_id}" `
);
}
const templates = users[0].templates;
if (templates.length === 0) {
throw new TymonialParamsError(`You dont have any template, create one. `);
}
// if theres template, filter it based on templateId
const filteredTemplate = templates.filter(
(temp) => temp.id === template_id
);
if (filteredTemplate.length === 0) {
throw new TymonialParamsError(
`No template was found with this ID "${template_id}" `
);
}
return true;
}
#validateParams(params) {
const isValid = typeof params !== "object" ? false : true;
// validate parameters passed in
if (!isValid) {
this.#error = `Expected a valid Tymonial parameter but got ${params}`;
throw new TymonialParamsError(this.#error);
}
if (Object.entries(params).length === 0) {
this.#error = "Expected a valid parameter, but got none.";
throw new TymonialParamsError(this.#error);
}
if (params.element === undefined) {
this.#error = `Expected a valid HTML "element" parameter, but got none.`;
throw new TymonialParamsError(this.#error);
}
if (params.element === undefined || params.element === "") {
this.#error = `Expected a valid HTML "element" parameter, but got none.`;
throw new TymonialParamsError(this.#error);
}
// validate HTML element
if ($(params?.element) === null) {
this.#error = `Expected a valid HTML "element" parameter, but got '${params?.element}'.`;
throw new TymonialParamsError(this.#error);
}
// validate userId & templateId
if (params?.user_id === undefined || params?.user_id === "") {
this.#error = `Expected a valid "user_id" parameter, but got none.`;
throw new TymonialParamsError(this.#error);
}
if (params?.template_id === undefined || params?.template_id === "") {
this.#error = `Expected a valid "template_id" parameter, but got none.`;
throw new TymonialParamsError(this.#error);
}
}
async #fetch(route, config = {}) {
const validRoute = route.replace("/", "");
config["headers"] = {
"content-type": "application/json",
};
let fetchLoading = true;
const req = await fetch(`${this.#BASE_URL}/${validRoute}`, config);
const result = await req.json();
fetchLoading = false;
return { req, result, loading: fetchLoading };
}
async #getFeedback(params) {
const { req, result, loading } = await this.#fetch("/feedbacks/get", {
method: "POST",
body: JSON.stringify({
userId: params?.user_id,
type: "non-user",
templateId: params?.template_id,
}),
});
if (result.error) {
throw new TymonialParamsError(result.message);
}
const feedbacks = result.data;
if (feedbacks.length === 0) {
console.error(
`It either you dont have a feedback for this template ID "${params?.template_id}"`
);
console.error("OR");
console.error("No feedbacks found yet.");
return feedbacks;
}
return feedbacks;
}
async init() {
// validate userId and templateId
const params = this.tymonialParams;
const isValidUserData = await this.#isTemplateAndUserIdExist(
params?.template_id,
params?.user_id
);
if (isValidUserData) {
this.containerElement.innerHTML = `
<div class="tymonial-loader"></div>
`;
const userFeedbacks = await this.#getFeedback(params);
const filteredFeedbacks =
userFeedbacks.length > 0
? userFeedbacks.filter((feed) => feed.published === true)
: [];
if (filteredFeedbacks.length > 0) {
const montageType = Montage(params, filteredFeedbacks);
this.containerElement.classList.remove("tymonial-container-element");
this.containerElement.innerHTML = montageType;
return;
}
log("===============================================");
log("No Published Feedbacks Yet.");
log("===============================================");
}
return true;
}
}
// TEMPLATES
const slideLeft = () => {
let slider = $("#tymonial-slider");
slider.scrollLeft -= 450;
};
const slideRight = () => {
let slider = $("#tymonial-slider");
slider.scrollLeft += 450;
};
function UiColors(tymonialParams) {
const tymonialBgColor =
tymonialParams?.tymonialBgColor === undefined
? ""
: tymonialParams?.tymonialBgColor;
const cardBgColor =
tymonialParams?.cardBgColor === undefined ? "" : tymonialParams.cardBgColor;
const headingColor =
tymonialParams?.headingColor === undefined
? ""
: tymonialParams.headingColor;
const subheadingColor =
tymonialParams?.subheadingColor === undefined
? ""
: tymonialParams.subheadingColor;
const cardBodyTextColor =
tymonialParams?.cardBodyTextColor === undefined
? ""
: tymonialParams.cardBodyTextColor;
const cardUsernameTextColor =
tymonialParams?.cardUsernameTextColor === undefined
? ""
: tymonialParams.cardUsernameTextColor;
const cardCareerTextColor =
tymonialParams?.cardCareerTextColor === undefined
? ""
: tymonialParams.cardCareerTextColor;
const cardRatingColor =
tymonialParams?.cardRatingColor === undefined
? ""
: tymonialParams.cardRatingColor;
const controlsColor =
tymonialParams?.controlsColor === undefined
? ""
: tymonialParams.controlsColor;
const controlsBgColor =
tymonialParams?.controlsBgColor === undefined
? ""
: tymonialParams.controlsBgColor;
return {
tymonialBgColor,
cardBgColor,
headingColor,
subheadingColor,
cardBodyTextColor,
cardUsernameTextColor,
cardCareerTextColor,
cardRatingColor,
controlsBgColor,
controlsColor,
};
}
const Montage = (tymonialParams, feedbacks) => {
const validHeading =
tymonialParams?.heading === "" || tymonialParams?.heading === undefined
? false
: true;
const validSubHeading =
tymonialParams?.sub_heading === "" ||
tymonialParams?.sub_heading === undefined
? false
: true;
const {
tymonialBgColor,
cardBgColor,
headingColor,
subheadingColor,
cardBodyTextColor,
cardUsernameTextColor,
cardCareerTextColor,
cardRatingColor,
controlsBgColor,
controlsColor,
} = UiColors(tymonialParams);
const tymonialElements = [];
feedbacks.map((list) => {
const box = document.createElement("div");
const profileImgStyle = `
width: 60px;
height: 60px;
background: url('${list.profileImg}');
background-position:center;
background-repeat: no-repeat;
background-size: cover;
border-radius: 50%;
`;
box.innerHTML = `
<div class="tymonial-box" style="background: ${cardBgColor};">
<svg viewBox="0 0 512 512" class="tymonial-quote" ><path d="M464 256h-80v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8c-88.4 0-160 71.6-160 160v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48zm-288 0H96v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8C71.6 32 0 103.6 0 192v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48z"/></svg>
<br />
<div class="top">
<div class="tymonial-userImage" style="${profileImgStyle}"></div>
<p class="body" style="color: ${cardBodyTextColor};">${
list.message
}</p>
</div>
<br>
<br>
<div class="bottom">
<div class="info">
<p class="name" style="color: ${cardUsernameTextColor};">${
list.name
}</p>
<p class="career" style="color: ${cardCareerTextColor};">
${list.userCareer}
</p>
</div>
<span id="ratings" style="color: ${cardRatingColor};">
${ratings(parseInt(list.ratings), cardRatingColor)}
</span>
</div>
</div>
`;
tymonialElements.push(box);
});
return `
<div id="tymonial-cont" style="background: ${tymonialBgColor};">
<br/>
<div id="tymonial-head">
<p id="heading" style="color:${headingColor};">
${validHeading ? tymonialParams?.heading : "Testimonial"}
</p>
<br>
<p id="sub-heading" style="color:${subheadingColor};">
${
validSubHeading
? tymonialParams?.sub_heading
: "What People Think About Us."
}
</p>
</div>
<br>
<br>
<div id="tymonial-slider">
${tymonialElements.map((elm) => elm.innerHTML).join("")}
</div>
<br>
<div id="tymonial-control">
<button id="prev" class="tymonial-btn" onclick="slideLeft()" style="color:${controlsColor}; background: ${controlsBgColor};">
<svg class="h-5 w-5 icons arrow-icon" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z"
clip-rule="evenodd" />
</svg>
</button>
<button id="next" class="tymonial-btn" onclick="slideRight()" style="color:${controlsColor}; background: ${controlsBgColor};">
<svg class="h-5 w-5 icons arrow-icon" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
clip-rule="evenodd" />
</svg>
</button>
</div>
</div>
`;
};