From 017232900c60f6ecf353602ca94f3bde6dfe7d99 Mon Sep 17 00:00:00 2001 From: Szymon Nowicki Date: Fri, 5 Jan 2024 22:42:36 +0100 Subject: [PATCH] feat: feedback forms --- dist/static/layout.css | 56 +++++++++++++++++--- functions/api/feedback/enhance/index.js | 28 ++++++++++ functions/api/feedback/enhance/template.html | 14 +++++ functions/api/feedback/index.js | 30 +++++++++++ functions/api/feedback/template.html | 21 ++++++++ functions/template.html | 14 ++++- lib/mongo.js | 53 ++++++++++++++++++ 7 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 functions/api/feedback/enhance/index.js create mode 100644 functions/api/feedback/enhance/template.html create mode 100644 functions/api/feedback/index.js create mode 100644 functions/api/feedback/template.html diff --git a/dist/static/layout.css b/dist/static/layout.css index c632057..5a9ffd8 100644 --- a/dist/static/layout.css +++ b/dist/static/layout.css @@ -88,7 +88,7 @@ main { align-items: center; justify-content: center; - & form { + & form.search-form { display: flex; width: min(70ch, 100%); height: 100%; @@ -117,7 +117,7 @@ body.body--has-query { display: grid; height: auto; - & > main > form { + & > main > form.search-form { width: 100%; display: grid; grid-template-areas: @@ -149,9 +149,10 @@ body.body--has-query { transform: scale(1.005); } - & input { - box-shadow: 0 0 0 2px var(--color-base); - } + } + + & input[type="search"] { + box-shadow: 0 0 0 2px var(--color-base); } & .results { @@ -197,7 +198,9 @@ body.body--has-query { } } -input[type="search"] { +input[type="search"], +input[type="text"], +textarea { font-family: var(--font-family-base); padding: .75rem 0.5rem; width: 100%; @@ -231,6 +234,19 @@ button { } } +.button--secondary { + box-shadow: none; + background: var(--color-background); + color: var(--color-base); + border: 2px solid var(--color-base); + + &:hover, + &:focus, + &:active { + box-shadow: 0 0 0 4px var(--color-contrast-bg); + } +} + blockquote { @@ -246,6 +262,10 @@ blockquote { display: block; } + .result-item-second-level { + margin-bottom: 2rem; + } + & .result-actions { display: flex; & label { @@ -359,3 +379,27 @@ history-chart { height: max(50vh, 400px); display: block; } + +.feedback-widget { + max-width: var(--width-max-content); + display: grid; + align-self: start; + + & .feedback-widget__buttons { + display: flex; + align-items: start; + gap: 1rem; + } +} + +.feedback-enhance-form { + display: grid; + width: var(--width-max-content); + gap: 2rem; + margin: 2rem 0 0; + + & .feedback-enhance-form__group { + display: grid; + gap: .5rem; + } +} diff --git a/functions/api/feedback/enhance/index.js b/functions/api/feedback/enhance/index.js new file mode 100644 index 0000000..4310404 --- /dev/null +++ b/functions/api/feedback/enhance/index.js @@ -0,0 +1,28 @@ +import { enhanceFeedbackVote } from '../../../../lib/mongo.js'; +import template from './template.html'; +import {renderHtml} from '../../../../lib/sso-render.js'; + +export const onRequestPost = async (context) => { + const { env, request } = context; + const data = await request.formData(); + + const q = data.get('q'); + const token = data.get('token'); + const contact = data.get('contact'); + const comment = data.get('comment'); + + const success = await enhanceFeedbackVote(env, {token, comment, contact}); + + const view = { + success, + noSuccess: !success, + q, + }; + + const html = await renderHtml(template, view); + return new Response(html, { + headers: { + 'content-type': 'text/html', + }, + }); +}; diff --git a/functions/api/feedback/enhance/template.html b/functions/api/feedback/enhance/template.html new file mode 100644 index 0000000..abd6e75 --- /dev/null +++ b/functions/api/feedback/enhance/template.html @@ -0,0 +1,14 @@ +{{{ before }}} +
+ {{#success}} +

Thanks!

+

Many thanks for your time. It means a lot to me!

+ Now, back to your business! + {{/success}} + {{#noSuccess}} +

Ufff

+

Something went wrong with saving your feedback. Try CMD(ctrl) + R, maybe it will work out second time.

+ {{/noSuccess}} +
+ +{{{ after }}} diff --git a/functions/api/feedback/index.js b/functions/api/feedback/index.js new file mode 100644 index 0000000..86b113e --- /dev/null +++ b/functions/api/feedback/index.js @@ -0,0 +1,30 @@ +import { createHash, randomBytes } from 'node:crypto'; +import { registerFeedbackVote } from '../../../lib/mongo.js'; +import template from './template.html'; +import {renderHtml} from '../../../lib/sso-render.js'; + +export const onRequestPost = async (context) => { + const { env, request } = context; + const data = await request.formData(); + + const pseudoRandom = randomBytes(8).toString('hex'); + const token = createHash('sha256').update(pseudoRandom).digest('hex'); + + const vote = parseInt(data.get('vote'), 10); + const q = data.get('q'); + + const insertedId = await registerFeedbackVote(env, {q, vote, token}); + + const view = { + q, + token, + success: !!insertedId, + }; + + const html = await renderHtml(template, view); + return new Response(html, { + headers: { + 'content-type': 'text/html', + }, + }); +}; diff --git a/functions/api/feedback/template.html b/functions/api/feedback/template.html new file mode 100644 index 0000000..c016d48 --- /dev/null +++ b/functions/api/feedback/template.html @@ -0,0 +1,21 @@ +{{{ before }}} +
+

Thank you

+

Many thanks for your feedback. If you want you can add some more!

+

Once you send this form, it would be saved in kukei.eu database and removed once your feedback is processed.
I might contact you later if you provide your contact info.

+ +
+{{{ after }}} diff --git a/functions/template.html b/functions/template.html index 0aea83d..eeaec2c 100644 --- a/functions/template.html +++ b/functions/template.html @@ -1,6 +1,6 @@ {{{ before }}}
-
+

Kukei.eu @@ -74,5 +74,17 @@

{{ title }}

{{/hasQuery}}
+ {{#hasQuery}} + + {{/hasQuery}}
{{{ after }}} diff --git a/lib/mongo.js b/lib/mongo.js index 49934ac..75eb0b5 100644 --- a/lib/mongo.js +++ b/lib/mongo.js @@ -103,3 +103,56 @@ export const trackQuery = async (envs, {q, hasResults}) => { }); console.log(`Analytics via mongo took ${Date.now() - d}ms`); }; + +export const registerFeedbackVote = async (envs, {q, vote, token}) => { + const response = await callMongo(envs, 'insertOne', { + collection: 'feedback', + database: envs.ATLAS_DB, + dataSource: envs.ATLAS_SOURCE, + document: { + q, + vote, + token, + created: (new Date()).toISOString(), + }, + }); + + const { insertedId } = response; + + return insertedId; +}; + +export const enhanceFeedbackVote = async (envs, {token, comment, contact }) => { + const { document } = await callMongo(envs, 'findOne', { + collection: 'feedback', + database: envs.ATLAS_DB, + dataSource: envs.ATLAS_SOURCE, + filter: { + token, + }, + projection: { + _id: 1, + }, + }); + + if (!document?._id) { + return false; + } + + await callMongo(envs, 'updateOne', { + collection: 'feedback', + database: envs.ATLAS_DB, + dataSource: envs.ATLAS_SOURCE, + filter: { + token, + }, + update: { + $set: { + comment, + contact, + }, + }, + }); + + return true; +};