From 113835138812808388980ca99ee89e81ebac74d6 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Sun, 10 Dec 2023 11:07:10 +0100 Subject: [PATCH 01/48] Drafting email sending --- config.ts | 8 +- package-lock.json | 121 ++++++++++++++++++++- package.json | 1 + src/pages/api/client/is-client-verified.ts | 4 + src/pages/api/experiments/send-email.ts | 35 ++++++ src/pages/api/publish.ts | 2 + 6 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 src/pages/api/experiments/send-email.ts diff --git a/config.ts b/config.ts index ba97c062a..9419b0eee 100644 --- a/config.ts +++ b/config.ts @@ -100,8 +100,6 @@ export const PHOTOBANK_SEARCH_IMAGES_COUNT = 4; */ export const OPTIMIZE_PHOTOBANK_MAX_SEARCH_DEPTH = 5; - - export const IS_VERIFIED_EMAIL_REQUIRED = { CREATE: false, EDIT: false, @@ -109,7 +107,6 @@ export const IS_VERIFIED_EMAIL_REQUIRED = { PUBLISH: true, } as const; - export const NEXT_PUBLIC_SUPABASE_URL = config.get('NEXT_PUBLIC_SUPABASE_URL').url().required().value; export const NEXT_PUBLIC_SUPABASE_ANON_KEY = config.get('NEXT_PUBLIC_SUPABASE_ANON_KEY').required().value; export const SUPABASE_SERVICE_ROLE_KEY = config.get('SUPABASE_SERVICE_ROLE_KEY').value; @@ -125,6 +122,8 @@ export const LIMIT_WALLPAPERS_EXCLUDE = config.get('LIMIT_WALLPAPERS_EXCLUDE').l export const OPENAI_API_KEY = config.get('OPENAI_API_KEY').value; +export const SENDGRID_API_KEY = config.get('SENDGRID_API_KEY').value; + export const AZURE_COMPUTER_VISION_ENDPOINT = config.get('AZURE_COMPUTER_VISION_ENDPOINT').url().value; export const AZURE_COMPUTER_VISION_KEY = config.get('AZURE_COMPUTER_VISION_KEY').value; @@ -1078,8 +1077,7 @@ export const PUBLISH_TO_GITHUB_ORGANIZATION = config.get( ).value; export const GITHUB_TOKEN = config.get('GITHUB_TOKEN', `@see https://github.com/settings/tokens`).value; - /** * TODO: !! Annotate all * TODO: [📙] Every dictionary should look like LikedStatus - */ \ No newline at end of file + */ diff --git a/package-lock.json b/package-lock.json index 0ac284ff5..845546475 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@promptbook/remote-server": "0.18.0", "@promptbook/types": "0.18.0", "@promptbook/utils": "0.18.0", + "@sendgrid/mail": "^8.1.0", "@supabase/supabase-js": "2.26.0", "@types/file-saver": "2.0.5", "@vercel/og": "0.5.8", @@ -3283,6 +3284,69 @@ "integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==", "dev": true }, + "node_modules/@sendgrid/client": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.0.tgz", + "integrity": "sha512-Kp2kKLr307v/HnR3uGuySt0AbCkeG7naDVOzfPOtWvKHVZIEHmKidQjJjzytVZNYWtoRdYgNfBw6GyUznGqa6w==", + "dependencies": { + "@sendgrid/helpers": "^8.0.0", + "axios": "^1.6.0" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/@sendgrid/client/node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/@sendgrid/client/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@sendgrid/client/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "dependencies": { + "deepmerge": "^4.2.2" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/@sendgrid/mail": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.0.tgz", + "integrity": "sha512-WkE0qwOrJMX9oQ+Xvtl3CdmucD6/iKw6go0VPoPieVlfXc43rbIf91wvtO6m7sKPnzxw3G+8rekBgXibmP4S8Q==", + "dependencies": { + "@sendgrid/client": "^8.1.0", + "@sendgrid/helpers": "^8.0.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/@shuding/opentype.js": { "version": "1.4.0-beta.0", "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", @@ -7145,7 +7209,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -19611,6 +19674,59 @@ "integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==", "dev": true }, + "@sendgrid/client": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.0.tgz", + "integrity": "sha512-Kp2kKLr307v/HnR3uGuySt0AbCkeG7naDVOzfPOtWvKHVZIEHmKidQjJjzytVZNYWtoRdYgNfBw6GyUznGqa6w==", + "requires": { + "@sendgrid/helpers": "^8.0.0", + "axios": "^1.6.0" + }, + "dependencies": { + "axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + } + } + }, + "@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "requires": { + "deepmerge": "^4.2.2" + } + }, + "@sendgrid/mail": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.0.tgz", + "integrity": "sha512-WkE0qwOrJMX9oQ+Xvtl3CdmucD6/iKw6go0VPoPieVlfXc43rbIf91wvtO6m7sKPnzxw3G+8rekBgXibmP4S8Q==", + "requires": { + "@sendgrid/client": "^8.1.0", + "@sendgrid/helpers": "^8.0.0" + } + }, "@shuding/opentype.js": { "version": "1.4.0-beta.0", "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", @@ -22682,8 +22798,7 @@ "deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, "define-data-property": { "version": "1.1.1", diff --git a/package.json b/package.json index 51a9cd58b..a939c6ec8 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "@promptbook/remote-server": "0.18.0", "@promptbook/types": "0.18.0", "@promptbook/utils": "0.18.0", + "@sendgrid/mail": "^8.1.0", "@supabase/supabase-js": "2.26.0", "@types/file-saver": "2.0.5", "@vercel/og": "0.5.8", diff --git a/src/pages/api/client/is-client-verified.ts b/src/pages/api/client/is-client-verified.ts index 3f0c34621..4b429dd64 100644 --- a/src/pages/api/client/is-client-verified.ts +++ b/src/pages/api/client/is-client-verified.ts @@ -42,3 +42,7 @@ export default async function isClientVerifiedHandler( .status(200) .json({ isClientInserted: false, isClientVerified: false } satisfies IsClientVerifiedResponse); } + +/** + * TODO: !!! Email client verification + */ diff --git a/src/pages/api/experiments/send-email.ts b/src/pages/api/experiments/send-email.ts new file mode 100644 index 000000000..1b6ba3a83 --- /dev/null +++ b/src/pages/api/experiments/send-email.ts @@ -0,0 +1,35 @@ +import sgMail from '@sendgrid/mail'; +import type { NextApiRequest, NextApiResponse } from 'next'; +import { SENDGRID_API_KEY } from '../../../../config'; + +export default async function sendEmailExperimentHandler(request: NextApiRequest, response: NextApiResponse) { + sgMail.setApiKey(SENDGRID_API_KEY! /* <- TODO: !!! Check config */); + const email = { + to: 'me@pavolhejny.com', + from: 'pavol@webgpt.cz', + subject: 'Sending with SendGrid is Fun', + text: 'and easy to do anywhere, even with Node.js', + html: 'and easy to do anywhere, even with Node.js', + }; + + try { + const sendingResult = await sgMail.send(email); + + console.info(sendingResult); + + return response.status(201).send({ + message: 'Email sent', + }); + } catch (error) { + console.error(error); + + if (!(error instanceof Error)) { + throw error; + } + + return response.status(500).send({ + message: 'Email sending failed', + error: { message: error.message }, + }); + } +} diff --git a/src/pages/api/publish.ts b/src/pages/api/publish.ts index bc8d012dd..d8420a5a5 100644 --- a/src/pages/api/publish.ts +++ b/src/pages/api/publish.ts @@ -73,6 +73,8 @@ export default async function publishWebsiteHandler( files, }); + // TODO: !!! Send email to user with link to website + return response.status(201).json({ websiteUrl: `https://${CNAME}/`, } satisfies PublishWebsiteResponse); From 87a6d62c98892cb8fd6516a56552fdd9a140b685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20Hejn=C3=BD?= Date: Sun, 10 Dec 2023 12:34:23 +0100 Subject: [PATCH 02/48] Update send-email.ts --- src/pages/api/experiments/send-email.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/api/experiments/send-email.ts b/src/pages/api/experiments/send-email.ts index 1b6ba3a83..a921aa992 100644 --- a/src/pages/api/experiments/send-email.ts +++ b/src/pages/api/experiments/send-email.ts @@ -7,9 +7,9 @@ export default async function sendEmailExperimentHandler(request: NextApiRequest const email = { to: 'me@pavolhejny.com', from: 'pavol@webgpt.cz', - subject: 'Sending with SendGrid is Fun', - text: 'and easy to do anywhere, even with Node.js', - html: 'and easy to do anywhere, even with Node.js', + subject:❄️', + text: 'Click here https://webgpt.cz/', + html: 'click here', }; try { From 6344c7d4ad01811fcdff30024d07aebe3d7430ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20Hejn=C3=BD?= Date: Sun, 10 Dec 2023 12:34:47 +0100 Subject: [PATCH 03/48] Update send-email.ts --- src/pages/api/experiments/send-email.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/api/experiments/send-email.ts b/src/pages/api/experiments/send-email.ts index a921aa992..eb2a6381c 100644 --- a/src/pages/api/experiments/send-email.ts +++ b/src/pages/api/experiments/send-email.ts @@ -7,7 +7,7 @@ export default async function sendEmailExperimentHandler(request: NextApiRequest const email = { to: 'me@pavolhejny.com', from: 'pavol@webgpt.cz', - subject:❄️', + subject: '❄️ WebGPT notification', text: 'Click here https://webgpt.cz/', html: 'click here', }; From a080783933fae7be8f4baf10cba37a9a79e38c99 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Sun, 10 Dec 2023 16:11:25 +0100 Subject: [PATCH 04/48] Email object --- config.ts | 27 +++++---- .../Content/Maxdown/validateMaxdown.ts | 1 + src/pages/api/experiments/send-email.ts | 28 +++++----- src/pages/api/publish.ts | 20 ++++++- .../content/removeMarkdownFormatting.test.ts | 2 +- src/utils/content/removeMarkdownFormatting.ts | 2 + src/utils/emails/Email.ts | 36 ++++++++++++ src/utils/emails/sendEmailForServer.ts | 55 +++++++++++++++++++ 8 files changed, 141 insertions(+), 30 deletions(-) create mode 100644 src/utils/emails/Email.ts create mode 100644 src/utils/emails/sendEmailForServer.ts diff --git a/config.ts b/config.ts index 9419b0eee..92a923cab 100644 --- a/config.ts +++ b/config.ts @@ -3,6 +3,7 @@ import spaceTrim from 'spacetrim'; import { Vector } from 'xyzt'; import packageJson from './package.json'; import type { DallePrompt } from './src/ai/text-to-image/dalle/interfaces/DallePrompt'; +import { validateMaxdown } from './src/components/Content/Maxdown/validateMaxdown'; import { FULLHD } from './src/constants'; import type { AspectRatioRange } from './src/utils/aspect-ratio/AspectRatioRange'; import { expectAspectRatioInRange } from './src/utils/aspect-ratio/expectAspectRatioInRange'; @@ -10,21 +11,10 @@ import { DigitalOceanSpaces } from './src/utils/cdn/classes/DigitalOceanSpaces'; import { createColorfulComputeImageColorStats15 } from './src/utils/image/palette/15/createColorfulComputeImageColorStats15'; import type { IComputeImageColorStats } from './src/utils/image/utils/IImageColorStats'; import { isRunningInBrowser } from './src/utils/isRunningInWhatever'; -import { string_font_family } from './src/utils/typeAliases'; +import { string_font_family, string_maxdown } from './src/utils/typeAliases'; import { isUrlOnPrivateNetwork } from './src/utils/validators/isUrlOnPrivateNetwork'; import { validateUuid } from './src/utils/validators/validateUuid'; -export const APP_VERSION = packageJson.version; -export const APP_NAME = 'WebGPT'; - -export const USE_DALLE_VERSION: 2 | 3 = 3; - -export const USE_DALLE_MODEL_SETTINGS: DallePrompt['modelSettings'] = { - style: 'vivid', - quality: `standard`, - // <- TODO: !! Play with theeese to achieve best results -}; - const config = ConfigChecker.from({ ...process.env, @@ -39,6 +29,19 @@ const config = ConfigChecker.from({ }); export const NEXT_PUBLIC_URL = config.get('NEXT_PUBLIC_URL').url().required().value; + +export const APP_VERSION = packageJson.version; +export const APP_NAME = 'WebGPT'; +export const APP_SIGNATURE: string_maxdown = validateMaxdown(`[⏣ ${APP_NAME}](${NEXT_PUBLIC_URL.href})`); + +export const USE_DALLE_VERSION: 2 | 3 = 3; + +export const USE_DALLE_MODEL_SETTINGS: DallePrompt['modelSettings'] = { + style: 'vivid', + quality: `standard`, + // <- TODO: !! Play with theeese to achieve best results +}; + export const NEXT_PUBLIC_PROMPTBOOK_SERVER_URL = config.get('NEXT_PUBLIC_PROMPTBOOK_SERVER_URL').url().required().value; export const NEXT_PUBLIC_IMAGE_SERVER_URL = config.get('NEXT_PUBLIC_IMAGE_SERVER_URL').url().required().value; diff --git a/src/components/Content/Maxdown/validateMaxdown.ts b/src/components/Content/Maxdown/validateMaxdown.ts index 3135b7c1f..f88f20e2a 100644 --- a/src/components/Content/Maxdown/validateMaxdown.ts +++ b/src/components/Content/Maxdown/validateMaxdown.ts @@ -15,6 +15,7 @@ export function validateMaxdown(content: unknown): string_maxdown { } /** + * TODO: !!! TODO: Use ACRY maxdown`...` instead * TODO: !! Do here a real validation / sanitization * TODO: [🧠][🚓] Is/which combination it better to use asserts/check, validate or is utility function? */ diff --git a/src/pages/api/experiments/send-email.ts b/src/pages/api/experiments/send-email.ts index eb2a6381c..5aa7395a8 100644 --- a/src/pages/api/experiments/send-email.ts +++ b/src/pages/api/experiments/send-email.ts @@ -1,23 +1,21 @@ -import sgMail from '@sendgrid/mail'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { SENDGRID_API_KEY } from '../../../../config'; +import spaceTrim from 'spacetrim'; +import { validateMaxdown } from '../../../components/Content/Maxdown/validateMaxdown'; +import { sendEmailForServer } from '../../../utils/emails/sendEmailForServer'; export default async function sendEmailExperimentHandler(request: NextApiRequest, response: NextApiResponse) { - sgMail.setApiKey(SENDGRID_API_KEY! /* <- TODO: !!! Check config */); - const email = { - to: 'me@pavolhejny.com', - from: 'pavol@webgpt.cz', - subject: '❄️ WebGPT notification', - text: 'Click here https://webgpt.cz/', - html: 'click here', - }; - try { - const sendingResult = await sgMail.send(email); - - console.info(sendingResult); + await sendEmailForServer({ + to: 'me@pavolhejny.com', + subject: '⏣ WebGPT notification', + content: validateMaxdown( + spaceTrim(` + Look on [WebGPT](https://webgpt.cz/) page! + `), + ), + }); - return response.status(201).send({ + return response.status(202).send({ message: 'Email sent', }); } catch (error) { diff --git a/src/pages/api/publish.ts b/src/pages/api/publish.ts index d8420a5a5..0992035fe 100644 --- a/src/pages/api/publish.ts +++ b/src/pages/api/publish.ts @@ -2,7 +2,10 @@ import formidable from 'formidable'; import { readFile } from 'fs/promises'; import JSZip from 'jszip'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { PUBLISH_TO_GITHUB_ORGANIZATION } from '../../../config'; +import spaceTrim from 'spacetrim'; +import { APP_SIGNATURE, PUBLISH_TO_GITHUB_ORGANIZATION } from '../../../config'; +import { validateMaxdown } from '../../components/Content/Maxdown/validateMaxdown'; +import { sendEmailForServer } from '../../utils/emails/sendEmailForServer'; import type { IFileToPublish } from '../../utils/publishing/github/interfaces/IFileToPublish'; import { publishToRepository } from '../../utils/publishing/github/publishToRepository'; import { string_url } from '../../utils/typeAliases'; @@ -73,7 +76,20 @@ export default async function publishWebsiteHandler( files, }); - // TODO: !!! Send email to user with link to website + // TODO: !!! Check email to user with link to website + + await sendEmailForServer({ + to: 'me@pavolhejny.com', + subject: `⏣ Website ${CNAME} is published!`, + + // TODO: !!! use maxdown`` pattern + content: validateMaxdown( + // TODO: !!! Translations + spaceTrim(` + Your website [${CNAME}](https://${CNAME}/) was successfully published by ${APP_SIGNATURE}! + `), + ), + }); return response.status(201).json({ websiteUrl: `https://${CNAME}/`, diff --git a/src/utils/content/removeMarkdownFormatting.test.ts b/src/utils/content/removeMarkdownFormatting.test.ts index 34b819af0..ccff20369 100644 --- a/src/utils/content/removeMarkdownFormatting.test.ts +++ b/src/utils/content/removeMarkdownFormatting.test.ts @@ -52,7 +52,7 @@ describe('removeMarkdownFormatting', () => { }); /* - TODO: + TODO: !!! it('should convert headings to texts', () => { const str = spaceTrim(` # Heading 1 diff --git a/src/utils/content/removeMarkdownFormatting.ts b/src/utils/content/removeMarkdownFormatting.ts index facbd21b6..d4e391110 100644 --- a/src/utils/content/removeMarkdownFormatting.ts +++ b/src/utils/content/removeMarkdownFormatting.ts @@ -18,3 +18,5 @@ export function removeMarkdownFormatting(str: string_markdown_text): string { return str; } + + diff --git a/src/utils/emails/Email.ts b/src/utils/emails/Email.ts new file mode 100644 index 000000000..77c9fbd55 --- /dev/null +++ b/src/utils/emails/Email.ts @@ -0,0 +1,36 @@ +import { string_email } from '@promptbook/types'; +import { string_maxdown } from '../typeAliases'; + +/** + * One email + */ +export interface Email { + /** + * Email address of sender + * + * Note: If not set, default value !!! from config is used + */ + readonly from?: string_email; + + /** + * Email address of recipient + */ + readonly to: string_email; + + /** + * Email subject + */ + readonly subject: string; + + /** + * Email content + * + * Note: The content is in maxdown format, it will be automatically converted to plain text + html before sending + */ + readonly content: string_maxdown; +} + + +/** + * TODO: !!! Translations + */ \ No newline at end of file diff --git a/src/utils/emails/sendEmailForServer.ts b/src/utils/emails/sendEmailForServer.ts new file mode 100644 index 000000000..64dee8984 --- /dev/null +++ b/src/utils/emails/sendEmailForServer.ts @@ -0,0 +1,55 @@ +import sendgridEmailClient from '@sendgrid/mail'; +import { SENDGRID_API_KEY } from '../../../config'; +import { maxdownToHtml } from '../../components/Content/Maxdown/maxdownToHtml'; +import { removeMarkdownFormatting } from '../content/removeMarkdownFormatting'; +import { isRunningInNode } from '../isRunningInWhatever'; +import { Email } from './Email'; + +/** + * Send an email + * + * Note: This function is available ONLY in server/node + * + * @returns instance of supabase client + */ +export async function sendEmailForServer(email: Email): Promise { + if (!isRunningInNode()) { + throw new Error( + 'Function `getSupabaseForServer` can not be used in browser, use `getSupabaseForBrowser` instead.', + ); + } + + if (SENDGRID_API_KEY === undefined) { + throw new Error('SENDGRID_API_KEY is not set in config'); + } + + sendgridEmailClient.setApiKey(SENDGRID_API_KEY); + + const { from = 'pavol@webgpt.cz' /* <- TODO: !!! Put to config */, to, subject, content } = email; + + const text = removeMarkdownFormatting(content); /* <- TODO: !!! What about links */ + const html = maxdownToHtml(content); + + const sendgridEmail = { + from, + to, + subject, + text, + html, + }; + + await sendgridEmailClient.send(sendgridEmail); + + /* + TODO: Look at what returned sendgridEmailClient.send + > const sendingResult =await sendgridEmailClient.send(sendgridEmail); + > + > if (sendingResult.statusCode !== 202) { + > throw new Error(`Email sending failed: ${JSON.stringify(sendingResult)}`); + > } + */ +} + +/** + * TODO: !!! Translations + */ From c26e1922db8b756a671347cba8edf525a40c2372 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Sun, 10 Dec 2023 16:12:38 +0100 Subject: [PATCH 05/48] TODOs --- src/utils/supabase/provideClientId.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/supabase/provideClientId.ts b/src/utils/supabase/provideClientId.ts index edd8888e2..df36e78d0 100644 --- a/src/utils/supabase/provideClientId.ts +++ b/src/utils/supabase/provideClientId.ts @@ -42,6 +42,7 @@ export async function provideClientId(options: IProvideClientIdOptions): Promise return clientId; } + // TODO: !!! validateEmailDialogue const { answer: email } = await simpleTextDialogue({ message: `Please write your email`, placeholder: `john.smith@gmail.com`, From 8e67b271440543d74aa170241708ce2be3883021 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Sun, 10 Dec 2023 16:46:57 +0100 Subject: [PATCH 06/48] Validate email dialogue (not finished) --- src/utils/supabase/provideClientId.ts | 22 ++--- src/workers/dialogues/index.ts | 3 +- .../ValidateEmailDialogueComponent.module.css | 94 +++++++++++++++++++ .../ValidateEmailDialogueComponent.tsx | 69 ++++++++++++++ .../types/ValidateEmailDialogueRequest.ts | 14 +++ .../types/ValidateEmailDialogueResponse.ts | 14 +++ .../validate-email/validateEmailDialogue.tsx | 15 +++ 7 files changed, 214 insertions(+), 17 deletions(-) create mode 100644 src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.module.css create mode 100644 src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx create mode 100644 src/workers/dialogues/validate-email/types/ValidateEmailDialogueRequest.ts create mode 100644 src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts create mode 100644 src/workers/dialogues/validate-email/validateEmailDialogue.tsx diff --git a/src/utils/supabase/provideClientId.ts b/src/utils/supabase/provideClientId.ts index df36e78d0..5fd855a47 100644 --- a/src/utils/supabase/provideClientId.ts +++ b/src/utils/supabase/provideClientId.ts @@ -1,5 +1,5 @@ import { IsClientVerifiedResponse } from '../../pages/api/client/is-client-verified'; -import { simpleTextDialogue } from '../../workers/dialogues/simple-text/simpleTextDialogue'; +import { validateEmailDialogue } from '../../workers/dialogues/validate-email/validateEmailDialogue'; import { string_email, uuid } from '../typeAliases'; import { isValidEmail } from '../validators/isValidEmail'; import { getSupabaseForBrowser } from './getSupabaseForBrowser'; @@ -10,8 +10,6 @@ export interface IProvideClientIdOptions { * Is required to have verified email * - If `false`, just putting in the email will be enough * - If `true`, user will must verify the email - * - * Note: [0] Not implemented yet - it will be ignored */ readonly isVerifiedEmailRequired?: boolean; } @@ -24,12 +22,7 @@ export interface IProvideClientIdOptions { * @returns clientId */ export async function provideClientId(options: IProvideClientIdOptions): Promise { - const { isVerifiedEmailRequired } = options; - - if (isVerifiedEmailRequired) { - // [0] - console.warn(`isVerifiedEmailRequired is not implemented yet`); - } + const { isVerifiedEmailRequired = false } = options; const clientId = provideClientIdWithoutVerification(); @@ -43,12 +36,9 @@ export async function provideClientId(options: IProvideClientIdOptions): Promise } // TODO: !!! validateEmailDialogue - const { answer: email } = await simpleTextDialogue({ - message: `Please write your email`, - placeholder: `john.smith@gmail.com`, - defaultValue: `@`, - isFeedbackCollected: false, - // TODO: !!!main Add GDPR consent + const { email, isEmailVerified } = await validateEmailDialogue({ + // [🍀] Maybe allow to pass default value for email + isVerifiedEmailRequired, }); if (!isValidEmail(email)) { @@ -62,6 +52,6 @@ export async function provideClientId(options: IProvideClientIdOptions): Promise } /** - * TODO: [0] Implement isVerifiedEmailRequired + * TODO: [0] !!! Implement isVerifiedEmailRequired * TODO: [🧠] This should be probbably in some other folder than supabase */ diff --git a/src/workers/dialogues/index.ts b/src/workers/dialogues/index.ts index e130477a9..3a45f997b 100644 --- a/src/workers/dialogues/index.ts +++ b/src/workers/dialogues/index.ts @@ -3,6 +3,7 @@ import { confirmDialogue } from './confirm/confirmDialogue'; import { feedbackDialogue } from './feedback/feedbackDialogue'; import { imageGeneratorDialogue } from './image-generator/imageGeneratorDialogue'; import { simpleTextDialogue } from './simple-text/simpleTextDialogue'; +import { validateEmailDialogue } from './validate-email/validateEmailDialogue'; /** * All dialogues that are used in WebGPT app @@ -11,4 +12,4 @@ import { simpleTextDialogue } from './simple-text/simpleTextDialogue'; */ export const supportDialogues: Array< DialogueFunction -> = [simpleTextDialogue, confirmDialogue, imageGeneratorDialogue, feedbackDialogue]; +> = [simpleTextDialogue, confirmDialogue, imageGeneratorDialogue, feedbackDialogue, validateEmailDialogue]; diff --git a/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.module.css b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.module.css new file mode 100644 index 000000000..e630dbb3e --- /dev/null +++ b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.module.css @@ -0,0 +1,94 @@ +.inner { + width: 100%; + height: 100%; + + display: grid; + grid-template: '🟦'; +} + +.inner .inputLayer, +.inner .feedbackLayer { + /*/ + outline: 1px dotted rgb(255 63 38); + /**/ + + grid-area: 🟦; + width: 100%; + height: 100%; +} + +.inner .inputLayer { + z-index: 1 /* <- Note: Local order in .inner */; +} + +.inner .feedbackLayer { + z-index: 2 /* <- Note: Local order in .inner */; +} + +/* ---------------------- */ + +.inner .inputLayer { + display: grid; + grid-template: + '🟢' 1fr + '🔵' 50px; +} + +.inner .inputLayer .answer { + /*/ + outline: 1px dotted rgb(81, 255, 38); + /**/ + + height: 100%; + width: 100%; + grid-area: 🟢; + + resize: none; + + border-radius: 0; + background-color: #151515; + color: white; + outline: none; + border: none; + padding: 10px; + + /* Note: This is not really a good solution, but it works for now + Text of answer should not be covered by feedback button + */ + padding-right: 55px; +} + +.inner .inputLayer .submit { + /*/ + outline: 1px dotted rgb(81, 255, 38); + /**/ + + grid-area: 🔵; + + width: 100%; + height: 100%; + cursor: pointer; +} + +/* ---------------------- */ + +.inner .feedbackLayer { + /*/ + outline: 1px dotted rgb(38 114 255); + /**/ + + display: flex; + align-items: flex-start; + justify-content: flex-end; + + padding: 10px; + padding-bottom: 60px; + + pointer-events: none; +} + +.inner .feedbackLayer button.triggerFeedback { + z-index: 3 /* <- Note: Local order in dialogue */; + + pointer-events: all; +} diff --git a/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx new file mode 100644 index 000000000..04b97d241 --- /dev/null +++ b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx @@ -0,0 +1,69 @@ +import { useCallback, useRef, useState } from 'react'; +import type { Feedback } from '../../../../ai/recommendation/Feedback'; +import { Modal } from '../../../../components/Modal/00-Modal'; +import { useStyleModule } from '../../../../utils/hooks/useStyleModule'; +import type { DialogueComponentProps } from '../../../lib/dialogues/interfaces/DialogueComponentProps'; +import type { ValidateEmailDialogueRequest } from '../types/ValidateEmailDialogueRequest'; +import type { ValidateEmailDialogueResponse } from '../types/ValidateEmailDialogueResponse'; + +/** + * Simple text dialogue offers a modal to the user to enter a (multiline) text. + * + * @private use only within validateEmailDialogue function + */ +export function ValidateEmailDialogueComponent( + props: DialogueComponentProps, +) { + const { + request: { isVerifiedEmailRequired, priority = 0 }, + respond, + } = props; + + const styles = useStyleModule(import('./ValidateEmailDialogueComponent.module.css')); + + const emailInputRef = useRef(null); + + const [feedback, setFeedback] = useState(); + const [isInFeedbackCollection, setInFeedbackCollection] = useState(false); + + const submit = useCallback(() => { + respond({ answer: emailInputRef.current!.value, feedback }); + }, [respond, emailInputRef, feedback]); + + return ( + + {/* TODO: Maybe create some
component to make this type of layouts */} +
+
+ { + if (!(event.key === 'Enter' && event.shiftKey === false && event.ctrlKey === false)) { + return; + } + + submit(); + }} + /> + +
+
+ + ); +} + +ValidateEmailDialogueComponent.dialogueTypeName = 'VALIDATE_EMAIL'; diff --git a/src/workers/dialogues/validate-email/types/ValidateEmailDialogueRequest.ts b/src/workers/dialogues/validate-email/types/ValidateEmailDialogueRequest.ts new file mode 100644 index 000000000..8f74d4c01 --- /dev/null +++ b/src/workers/dialogues/validate-email/types/ValidateEmailDialogueRequest.ts @@ -0,0 +1,14 @@ +import { AbstractDialogueRequest } from '../../../lib/dialogues/interfaces/AbstractDialogueRequest'; + +export type ValidateEmailDialogueRequest = AbstractDialogueRequest & { + /** + * Is required to have verified email + * - If `false`, just putting in the email will be enough + * - If `true`, user will must verify the email + */ + readonly isVerifiedEmailRequired: boolean; +}; + +/** + * TODO: [🍀] Maybe allow to pass default value for email + */ diff --git a/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts b/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts new file mode 100644 index 000000000..f0ed766fc --- /dev/null +++ b/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts @@ -0,0 +1,14 @@ +import { string_email } from '../../../../utils/typeAliases'; +import type { AbstractDialogueResponse } from '../../../lib/dialogues/interfaces/AbstractDialogueResponse'; + +export type ValidateEmailDialogueResponse = AbstractDialogueResponse & { + /** + * The validated/entered email + */ + readonly email: string_email; + + /** + * Is the email verified + */ + readonly isEmailVerified: boolean; +}; diff --git a/src/workers/dialogues/validate-email/validateEmailDialogue.tsx b/src/workers/dialogues/validate-email/validateEmailDialogue.tsx new file mode 100644 index 000000000..ab4c2039d --- /dev/null +++ b/src/workers/dialogues/validate-email/validateEmailDialogue.tsx @@ -0,0 +1,15 @@ +import { makeDialogueFunction } from '../../lib/dialogues/makeDialogueFunction'; +import { ValidateEmailDialogueComponent } from './component/ValidateEmailDialogueComponent'; +import type { ValidateEmailDialogueRequest } from './types/ValidateEmailDialogueRequest'; +import type { ValidateEmailDialogueResponse } from './types/ValidateEmailDialogueResponse'; + +/** + * Simple text dialogue offers a modal to the user to enter a (multiline) text. + */ +export const validateEmailDialogue = makeDialogueFunction( + ValidateEmailDialogueComponent, +); + +/** + * TODO: [🧠][👨‍⚕️] The problem with feedback returned together with answer is that when user cancels the dialogue, the feedback is not recorded + */ From 2805452dbffc56d858b95e03a0817dc7993e0b34 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Sun, 10 Dec 2023 19:26:52 +0100 Subject: [PATCH 07/48] TODOs --- .../ClientVerificationComponent.module.css | 7 ++++ .../ClientVerificationComponent.tsx | 33 +++++++++++++++++++ src/components/ControlPanel/ControlPanel.tsx | 2 +- .../ControlPanel/ControlPanelLikeButtons.tsx | 2 +- .../RandomWallpaper/RandomWallpaperManager.ts | 2 +- src/components/CopilotPanel/CopilotPanel.tsx | 2 +- src/components/ExportModal/ExportModal.tsx | 2 +- src/components/ExportModal/exportWebsite.ts | 2 +- src/components/PromptCook/PromptCook.tsx | 2 +- src/components/PublishModal/PublishModal.tsx | 2 +- src/components/PublishModal/publishWebsite.ts | 2 +- .../UploadNewWallpaper/UploadNewWallpaper.tsx | 2 +- src/pages/api/publish.ts | 4 +++ src/pages/new/from-idea.tsx | 2 +- src/pages/new/from-instagram.tsx | 2 +- src/pages/verify.tsx | 1 + src/utils/client/TODO.txt | 5 +++ src/utils/client/isClientVerified.ts | 6 ++++ .../provideClientEmail.ts | 0 .../{supabase => client}/provideClientId.ts | 6 +++- .../provideClientIdWithoutVerification.ts | 0 src/utils/client/sendEmailToVerifyClient.ts | 6 ++++ src/utils/client/verifyEmailCode.ts | 7 ++++ src/utils/hooks/useClientId.ts | 2 +- .../hooks/useLikedStatusOfCurrentWallpaper.ts | 2 +- src/utils/scraping/fetchImage.ts | 2 +- 26 files changed, 89 insertions(+), 16 deletions(-) create mode 100644 src/components/ClientVerificationComponent/ClientVerificationComponent.module.css create mode 100644 src/components/ClientVerificationComponent/ClientVerificationComponent.tsx create mode 100644 src/pages/verify.tsx create mode 100644 src/utils/client/TODO.txt create mode 100644 src/utils/client/isClientVerified.ts rename src/utils/{supabase => client}/provideClientEmail.ts (100%) rename src/utils/{supabase => client}/provideClientId.ts (92%) rename src/utils/{supabase => client}/provideClientIdWithoutVerification.ts (100%) create mode 100644 src/utils/client/sendEmailToVerifyClient.ts create mode 100644 src/utils/client/verifyEmailCode.ts diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.module.css b/src/components/ClientVerificationComponent/ClientVerificationComponent.module.css new file mode 100644 index 000000000..7b2dc16ba --- /dev/null +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.module.css @@ -0,0 +1,7 @@ +.ClientVerificationComponent { + /*/ + outline: 1px dotted rgb(255, 38, 38); + /**/ + + /* TODO: Use or remove empty css */ +} diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx new file mode 100644 index 000000000..a84f731c7 --- /dev/null +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -0,0 +1,33 @@ +import type { ReactNode } from 'react'; +import { classNames } from '../../utils/classNames'; +import type { string_css_class } from '../../utils/typeAliases'; +import styles from './ClientVerificationComponent.module.css'; + +interface ClientVerificationComponentProps { + /** + * Content of @@ + */ + readonly children?: ReactNode; + + /** + * Optional CSS class name which will be added to root element + */ + readonly className?: string_css_class; +} + +/** + * Renders a @@ + */ +export function ClientVerificationComponent(props: ClientVerificationComponentProps) { + const { children, className } = props; + + // Use or remove: + //> const styles = useStyleModule(import('./ClientVerificationComponent.module.css')); + + return
{children}
; +} + +/** + * TODO: !!! Implement + * TODO: + */ diff --git a/src/components/ControlPanel/ControlPanel.tsx b/src/components/ControlPanel/ControlPanel.tsx index b3b8d49de..7f58d5c3b 100644 --- a/src/components/ControlPanel/ControlPanel.tsx +++ b/src/components/ControlPanel/ControlPanel.tsx @@ -3,11 +3,11 @@ import { useRouter } from 'next/router'; import { IS_VERIFIED_EMAIL_REQUIRED } from '../../../config'; import type { LikedStatus } from '../../ai/recommendation/LikedStatus'; import { classNames } from '../../utils/classNames'; +import { provideClientId } from '../../utils/client/provideClientId'; import { computeWallpaperUriid } from '../../utils/computeWallpaperUriid'; import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper'; import { serializeWallpaper } from '../../utils/hydrateWallpaper'; import { getSupabaseForBrowser } from '../../utils/supabase/getSupabaseForBrowser'; -import { provideClientId } from '../../utils/supabase/provideClientId'; import { parseKeywordsFromWallpaper } from '../Gallery/GalleryFilter/utils/parseKeywordsFromWallpaper'; import { Hint } from '../Hint/Hint'; import { WallpaperLink } from '../WallpaperLink/WallpaperLink'; diff --git a/src/components/ControlPanel/ControlPanelLikeButtons.tsx b/src/components/ControlPanel/ControlPanelLikeButtons.tsx index 4f4f0922d..d91ccaaf4 100644 --- a/src/components/ControlPanel/ControlPanelLikeButtons.tsx +++ b/src/components/ControlPanel/ControlPanelLikeButtons.tsx @@ -1,9 +1,9 @@ import Image from 'next/image'; import { useRouter } from 'next/router'; import { classNames } from '../../utils/classNames'; +import { provideClientIdWithoutVerification } from '../../utils/client/provideClientIdWithoutVerification'; import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper'; import { useLikedStatusOfCurrentWallpaper } from '../../utils/hooks/useLikedStatusOfCurrentWallpaper'; -import { provideClientIdWithoutVerification } from '../../utils/supabase/provideClientIdWithoutVerification'; import { Hint } from '../Hint/Hint'; import { PublishLink } from '../PublishModal/PublishLink'; import styles from './ControlPanel.module.css'; diff --git a/src/components/ControlPanel/RandomWallpaper/RandomWallpaperManager.ts b/src/components/ControlPanel/RandomWallpaper/RandomWallpaperManager.ts index 57d4e68c1..4ff01790c 100644 --- a/src/components/ControlPanel/RandomWallpaper/RandomWallpaperManager.ts +++ b/src/components/ControlPanel/RandomWallpaper/RandomWallpaperManager.ts @@ -2,9 +2,9 @@ import { Promisable } from 'type-fest'; import { forAnimationFrame, forImmediate } from 'waitasecond'; import { IS_DEVELOPMENT, NEXT_PUBLIC_URL } from '../../../../config'; import type { RecommendWallpaperResponse } from '../../../pages/api/recommend-wallpaper'; +import { provideClientIdWithoutVerification } from '../../../utils/client/provideClientIdWithoutVerification'; import { IWallpaperSerialized } from '../../../utils/IWallpaper'; import { randomItem } from '../../../utils/randomItem'; -import { provideClientIdWithoutVerification } from '../../../utils/supabase/provideClientIdWithoutVerification'; import { string_color } from '../../../utils/typeAliases'; export type IWallpaperInStorage = Pick; diff --git a/src/components/CopilotPanel/CopilotPanel.tsx b/src/components/CopilotPanel/CopilotPanel.tsx index 188352f5b..a79a0d07c 100644 --- a/src/components/CopilotPanel/CopilotPanel.tsx +++ b/src/components/CopilotPanel/CopilotPanel.tsx @@ -9,6 +9,7 @@ import { getExecutionTools } from '../../ai/prompt-templates/getExecutionTools'; import { webgptPtpLibrary } from '../../ai/prompt-templates/webgptPtpLibrary'; import type { LikedStatus } from '../../ai/recommendation/LikedStatus'; import { classNames } from '../../utils/classNames'; +import { provideClientId } from '../../utils/client/provideClientId'; import { computeWallpaperUriid } from '../../utils/computeWallpaperUriid'; import { removeContentComments } from '../../utils/content/removeContentComments'; import { focusRef } from '../../utils/focusRef'; @@ -19,7 +20,6 @@ import { serializeWallpaper } from '../../utils/hydrateWallpaper'; import { randomItem } from '../../utils/randomItem'; import { shuffleItems } from '../../utils/shuffleItems'; import { getSupabaseForBrowser } from '../../utils/supabase/getSupabaseForBrowser'; -import { provideClientId } from '../../utils/supabase/provideClientId'; import { validateMaxdown } from '../Content/Maxdown/validateMaxdown'; import { FeedbackButton } from '../FeedbackButton/FeedbackButton'; import { parseKeywordsFromWallpaper } from '../Gallery/GalleryFilter/utils/parseKeywordsFromWallpaper'; diff --git a/src/components/ExportModal/ExportModal.tsx b/src/components/ExportModal/ExportModal.tsx index 284fb89fd..64a1534ea 100644 --- a/src/components/ExportModal/ExportModal.tsx +++ b/src/components/ExportModal/ExportModal.tsx @@ -1,8 +1,8 @@ import { FormEvent, useCallback, useState } from 'react'; import spaceTrim from 'spacetrim'; import { classNames } from '../../utils/classNames'; +import { provideClientEmail } from '../../utils/client/provideClientEmail'; import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper'; -import { provideClientEmail } from '../../utils/supabase/provideClientEmail'; import { string_email } from '../../utils/typeAliases'; import { isValidUrl } from '../../utils/validators/isValidUrl'; import { MarkdownContent } from '../Content/MarkdownContent'; diff --git a/src/components/ExportModal/exportWebsite.ts b/src/components/ExportModal/exportWebsite.ts index ac1410db3..a51bcc17b 100644 --- a/src/components/ExportModal/exportWebsite.ts +++ b/src/components/ExportModal/exportWebsite.ts @@ -2,9 +2,9 @@ import spaceTrim from 'spacetrim'; import { IS_VERIFIED_EMAIL_REQUIRED } from '../../../config'; import { exportAsZip } from '../../export/exportAsZip'; import { induceFileDownload } from '../../export/utils/induceFileDownload'; +import { provideClientId } from '../../utils/client/provideClientId'; import { IWallpaper } from '../../utils/IWallpaper'; import { getSupabaseForBrowser } from '../../utils/supabase/getSupabaseForBrowser'; -import { provideClientId } from '../../utils/supabase/provideClientId'; import { string_email } from '../../utils/typeAliases'; import { isValidEmail } from '../../utils/validators/isValidEmail'; import { PricingPlan } from '../PricingTable/plans'; diff --git a/src/components/PromptCook/PromptCook.tsx b/src/components/PromptCook/PromptCook.tsx index 44e55da57..3044eb7f7 100644 --- a/src/components/PromptCook/PromptCook.tsx +++ b/src/components/PromptCook/PromptCook.tsx @@ -11,9 +11,9 @@ import { getExecutionTools } from '../../ai/prompt-templates/getExecutionTools'; import { TasksInProgress } from '../../components/TaskInProgress/TasksInProgress'; import { induceFileDownload } from '../../export/utils/induceFileDownload'; import { classNames } from '../../utils/classNames'; +import { provideClientId } from '../../utils/client/provideClientId'; import { useJsonStateInLocalstorage } from '../../utils/hooks/useJsonStateInLocalstorage'; import { useStateInLocalstorage } from '../../utils/hooks/useStateInLocalstorage'; -import { provideClientId } from '../../utils/supabase/provideClientId'; import type { string_name } from '../../utils/typeAliases'; import { CodeEditor } from '../CodeEditor/CodeEditor'; import { Select } from '../Select/Select'; diff --git a/src/components/PublishModal/PublishModal.tsx b/src/components/PublishModal/PublishModal.tsx index 536fa955f..1df21de22 100644 --- a/src/components/PublishModal/PublishModal.tsx +++ b/src/components/PublishModal/PublishModal.tsx @@ -2,9 +2,9 @@ import { useRouter } from 'next/router'; import { FormEvent, useCallback, useMemo, useState } from 'react'; import spaceTrim from 'spacetrim'; import { classNames } from '../../utils/classNames'; +import { provideClientEmail } from '../../utils/client/provideClientEmail'; import { computeWallpaperDomainPart } from '../../utils/computeWallpaperDomainPart'; import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper'; -import { provideClientEmail } from '../../utils/supabase/provideClientEmail'; import { string_domain, string_email } from '../../utils/typeAliases'; import { MarkdownContent } from '../Content/MarkdownContent'; import { DomainStatusText } from '../Domains/DomainStatusText/DomainStatusText'; diff --git a/src/components/PublishModal/publishWebsite.ts b/src/components/PublishModal/publishWebsite.ts index 62963312f..863e9b001 100644 --- a/src/components/PublishModal/publishWebsite.ts +++ b/src/components/PublishModal/publishWebsite.ts @@ -2,10 +2,10 @@ import spaceTrim from 'spacetrim'; import { IS_VERIFIED_EMAIL_REQUIRED, NEXT_PUBLIC_URL } from '../../../config'; import { exportAsZip } from '../../export/exportAsZip'; import { PublishWebsiteResponse } from '../../pages/api/publish'; +import { provideClientId } from '../../utils/client/provideClientId'; import { isValidDomain } from '../../utils/domains/isValidDomain'; import { IWallpaper } from '../../utils/IWallpaper'; import { getSupabaseForBrowser } from '../../utils/supabase/getSupabaseForBrowser'; -import { provideClientId } from '../../utils/supabase/provideClientId'; import { string_domain, string_email } from '../../utils/typeAliases'; import { isValidEmail } from '../../utils/validators/isValidEmail'; diff --git a/src/components/UploadNewWallpaper/UploadNewWallpaper.tsx b/src/components/UploadNewWallpaper/UploadNewWallpaper.tsx index 70fd9719c..3bd550dd0 100644 --- a/src/components/UploadNewWallpaper/UploadNewWallpaper.tsx +++ b/src/components/UploadNewWallpaper/UploadNewWallpaper.tsx @@ -3,8 +3,8 @@ import { ReactNode, useState } from 'react'; import spaceTrim from 'spacetrim'; import { IS_VERIFIED_EMAIL_REQUIRED } from '../../../config'; import { classNames } from '../../utils/classNames'; +import { provideClientId } from '../../utils/client/provideClientId'; import { useLocale } from '../../utils/hooks/useLocale'; -import { provideClientId } from '../../utils/supabase/provideClientId'; import { string_css_class } from '../../utils/typeAliases'; import { createNewWallpaperForBrowser } from '../../workers/functions/createNewWallpaper/workerify/createNewWallpaperForBrowser'; import { joinTasksProgress } from '../TaskInProgress/task/joinTasksProgress'; diff --git a/src/pages/api/publish.ts b/src/pages/api/publish.ts index 0992035fe..8d6693c42 100644 --- a/src/pages/api/publish.ts +++ b/src/pages/api/publish.ts @@ -87,6 +87,10 @@ export default async function publishWebsiteHandler( // TODO: !!! Translations spaceTrim(` Your website [${CNAME}](https://${CNAME}/) was successfully published by ${APP_SIGNATURE}! + + --- + + !!! add wait dislaimer to publish email !!! `), ), }); diff --git a/src/pages/new/from-idea.tsx b/src/pages/new/from-idea.tsx index e21ce85eb..1917638ce 100644 --- a/src/pages/new/from-idea.tsx +++ b/src/pages/new/from-idea.tsx @@ -14,9 +14,9 @@ import { WebgptTaskProgress } from '../../components/TaskInProgress/task/WebgptT import { TasksInProgress } from '../../components/TaskInProgress/TasksInProgress'; import { Translate } from '../../components/Translate/Translate'; import styles from '../../styles/static.module.css' /* <- TODO: [🤶] Get rid of page css and only use components (as ) */; +import { provideClientId } from '../../utils/client/provideClientId'; import { useLocale } from '../../utils/hooks/useLocale'; import { shuffleItems } from '../../utils/shuffleItems'; -import { provideClientId } from '../../utils/supabase/provideClientId'; import { createNewWallpaperForBrowser } from '../../workers/functions/createNewWallpaper/workerify/createNewWallpaperForBrowser'; export default function NewWallpaperFromIdeaPage() { diff --git a/src/pages/new/from-instagram.tsx b/src/pages/new/from-instagram.tsx index bd1273e4a..81aefdff0 100644 --- a/src/pages/new/from-instagram.tsx +++ b/src/pages/new/from-instagram.tsx @@ -14,12 +14,12 @@ import { WebgptTaskProgress } from '../../components/TaskInProgress/task/WebgptT import { TasksInProgress } from '../../components/TaskInProgress/TasksInProgress'; import { Translate } from '../../components/Translate/Translate'; import styles from '../../styles/static.module.css' /* <- TODO: [🤶] Get rid of page css and only use components (as ) */; +import { provideClientId } from '../../utils/client/provideClientId'; import { useLocale } from '../../utils/hooks/useLocale'; import { normalizeInstagramName } from '../../utils/normalizeInstagramName'; import { randomItem } from '../../utils/randomItem'; import { fetchImage } from '../../utils/scraping/fetchImage'; import { shuffleItems } from '../../utils/shuffleItems'; -import { provideClientId } from '../../utils/supabase/provideClientId'; import { string_business_category_name } from '../../utils/typeAliases'; import { createNewWallpaperForBrowser } from '../../workers/functions/createNewWallpaper/workerify/createNewWallpaperForBrowser'; import type { ScrapeInstagramUserResponse } from '../api/scrape/scrape-instagram-user'; diff --git a/src/pages/verify.tsx b/src/pages/verify.tsx new file mode 100644 index 000000000..724e1981b --- /dev/null +++ b/src/pages/verify.tsx @@ -0,0 +1 @@ +// TODO: !!! Implement \ No newline at end of file diff --git a/src/utils/client/TODO.txt b/src/utils/client/TODO.txt new file mode 100644 index 000000000..55188fb3c --- /dev/null +++ b/src/utils/client/TODO.txt @@ -0,0 +1,5 @@ +TODO: !!! Backup old tables +TODO: !!! Remove old tables +TODO: !!! Create table ClientEmailVerificationRequest +TODO: !!! Create table ClientEmailVerification +TODO: !!! Sync new tables ClientEmailVerificationRequest and ClientEmailVerification \ No newline at end of file diff --git a/src/utils/client/isClientVerified.ts b/src/utils/client/isClientVerified.ts new file mode 100644 index 000000000..657d7318e --- /dev/null +++ b/src/utils/client/isClientVerified.ts @@ -0,0 +1,6 @@ +isClientVerified; + +/** + * TODO: !!! Implement + * TODO: !!!last Annotate + */ diff --git a/src/utils/supabase/provideClientEmail.ts b/src/utils/client/provideClientEmail.ts similarity index 100% rename from src/utils/supabase/provideClientEmail.ts rename to src/utils/client/provideClientEmail.ts diff --git a/src/utils/supabase/provideClientId.ts b/src/utils/client/provideClientId.ts similarity index 92% rename from src/utils/supabase/provideClientId.ts rename to src/utils/client/provideClientId.ts index 5fd855a47..6f333ac8f 100644 --- a/src/utils/supabase/provideClientId.ts +++ b/src/utils/client/provideClientId.ts @@ -1,10 +1,14 @@ import { IsClientVerifiedResponse } from '../../pages/api/client/is-client-verified'; import { validateEmailDialogue } from '../../workers/dialogues/validate-email/validateEmailDialogue'; +import { getSupabaseForBrowser } from '../supabase/getSupabaseForBrowser'; import { string_email, uuid } from '../typeAliases'; import { isValidEmail } from '../validators/isValidEmail'; -import { getSupabaseForBrowser } from './getSupabaseForBrowser'; import { provideClientIdWithoutVerification } from './provideClientIdWithoutVerification'; +/** + * TODO: !!! Remove ACRY isVerifiedEmailRequired, IS_VERIFIED_EMAIL_REQUIRED, [Vv]erif[iy] + */ + export interface IProvideClientIdOptions { /** * Is required to have verified email diff --git a/src/utils/supabase/provideClientIdWithoutVerification.ts b/src/utils/client/provideClientIdWithoutVerification.ts similarity index 100% rename from src/utils/supabase/provideClientIdWithoutVerification.ts rename to src/utils/client/provideClientIdWithoutVerification.ts diff --git a/src/utils/client/sendEmailToVerifyClient.ts b/src/utils/client/sendEmailToVerifyClient.ts new file mode 100644 index 000000000..1ddfee733 --- /dev/null +++ b/src/utils/client/sendEmailToVerifyClient.ts @@ -0,0 +1,6 @@ +sendEmailToVerifyClient + +/** + * TODO: !!! Implement + * TODO: !!!last Annotate + */ \ No newline at end of file diff --git a/src/utils/client/verifyEmailCode.ts b/src/utils/client/verifyEmailCode.ts new file mode 100644 index 000000000..d00268e5a --- /dev/null +++ b/src/utils/client/verifyEmailCode.ts @@ -0,0 +1,7 @@ +verifyEmailCode + + +/** + * TODO: !!! Implement + * TODO: !!!last Annotate + */ \ No newline at end of file diff --git a/src/utils/hooks/useClientId.ts b/src/utils/hooks/useClientId.ts index 09c66f54f..107bb36a4 100644 --- a/src/utils/hooks/useClientId.ts +++ b/src/utils/hooks/useClientId.ts @@ -1,7 +1,7 @@ import { uuid } from '@promptbook/types'; import { useMemo } from 'react'; -import { IProvideClientIdOptions, provideClientId } from '../supabase/provideClientId'; +import { IProvideClientIdOptions, provideClientId } from '../client/provideClientId'; import { usePromise } from './usePromise'; import { useSsrDetection } from './useSsrDetection'; diff --git a/src/utils/hooks/useLikedStatusOfCurrentWallpaper.ts b/src/utils/hooks/useLikedStatusOfCurrentWallpaper.ts index e02807de7..30f864da9 100644 --- a/src/utils/hooks/useLikedStatusOfCurrentWallpaper.ts +++ b/src/utils/hooks/useLikedStatusOfCurrentWallpaper.ts @@ -1,7 +1,7 @@ import { IS_VERIFIED_EMAIL_REQUIRED } from '../../../config'; import type { LikedStatus } from '../../ai/recommendation/LikedStatus'; +import { provideClientId } from '../client/provideClientId'; import { getSupabaseForBrowser } from '../supabase/getSupabaseForBrowser'; -import { provideClientId } from '../supabase/provideClientId'; import { useCurrentWallpaperId } from './useCurrentWallpaperId'; import { useStateInLocalstorage } from './useStateInLocalstorage'; diff --git a/src/utils/scraping/fetchImage.ts b/src/utils/scraping/fetchImage.ts index 7253542dc..ef26b044d 100644 --- a/src/utils/scraping/fetchImage.ts +++ b/src/utils/scraping/fetchImage.ts @@ -1,6 +1,6 @@ import { IS_VERIFIED_EMAIL_REQUIRED } from '../../../config'; +import { provideClientId } from '../client/provideClientId'; import { isRunningInBrowser } from '../isRunningInWhatever'; -import { provideClientId } from '../supabase/provideClientId'; import { string_url_image } from '../typeAliases'; /** From 6780e97e38738e790b30077a41405ccd5e067810 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 13:01:10 +0100 Subject: [PATCH 08/48] TODOs --- src/utils/client/sendEmailToVerifyClient.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/utils/client/sendEmailToVerifyClient.ts b/src/utils/client/sendEmailToVerifyClient.ts index 1ddfee733..7226f6b74 100644 --- a/src/utils/client/sendEmailToVerifyClient.ts +++ b/src/utils/client/sendEmailToVerifyClient.ts @@ -1,6 +1,18 @@ -sendEmailToVerifyClient +sendEmailToVerifyClient; /** * TODO: !!! Implement * TODO: !!!last Annotate - */ \ No newline at end of file + * TODO: !!! Create DB view for jirka to be DB costs, feedbac etc visible for him + * TODO: !!! Referral system + * TODO: [🧠] Some unification method how to attribute costs to unique people + * - franta.novak@gmail.com -> franta.novak@gmail.com + * - franta.novak+alias@gmail.com -> franta.novak@gmail.com + * - me@pavolhejny.com -> pavolhejny.com + * - pavol@pavolhejny.com -> pavolhejny.com + * - voxisik917@mcenb.com -> anonymous + * - !! [🍏] What about Apple anonymous emails? + * TODO: [🧠] Some method to detect one-time emails and do not allow them OR strictly warn them + * - Like voxisik917@mcenb.com + * - !! [🍏] What about Apple anonymous emails? + */ From baa16891c814729469d8ccaca84103dbf5642785 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 13:10:21 +0100 Subject: [PATCH 09/48] Update code snippets --- .vscode/hejny.code-snippets | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.vscode/hejny.code-snippets b/.vscode/hejny.code-snippets index da5d89d8f..b7f9ce421 100644 --- a/.vscode/hejny.code-snippets +++ b/.vscode/hejny.code-snippets @@ -25,8 +25,8 @@ }, "test": { "scope": "typescript", - "prefix": "AI+TDD", - "description": "Test+Implementation ready to AI development", + "prefix": "Function with unit test", + "description": "Test+Implementation of one function ready to AI development", "body": [ "import { describe, expect, it } from '@jest/globals';", "import spaceTrim from 'spacetrim';", @@ -53,7 +53,10 @@ " });", "});", "", - "function $1(value: string): boolean {", + "/**", + "* $1 @@@", + "*/" + "export function $1(value: string): boolean {", " return value === 'Foo';", "}", "" From 63027266b7a1812c7abc0fd54ea49b15a68958a7 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 13:10:46 +0100 Subject: [PATCH 10/48] Use snippets in function to do --- src/utils/client/isClientVerified.test.ts | 34 +++++++++++++++++++++ src/utils/client/isClientVerified.ts | 14 ++++++++- src/utils/client/sendEmailToVerifyClient.ts | 7 ++++- src/utils/client/verifyEmailCode.ts | 10 ++++-- 4 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 src/utils/client/isClientVerified.test.ts diff --git a/src/utils/client/isClientVerified.test.ts b/src/utils/client/isClientVerified.test.ts new file mode 100644 index 000000000..7c007dfc0 --- /dev/null +++ b/src/utils/client/isClientVerified.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from '@jest/globals'; +import spaceTrim from 'spacetrim'; +import { isClientVerified } from './isClientVerified'; + +describe('how isClientVerified works', () => { + it('should work with foo', () => { + expect( + isClientVerified( + spaceTrim(` + Foo + `), + ), + ).resolves.toBe(true); + }); + + it('should NOT work with bar', () => { + expect( + isClientVerified( + spaceTrim(` + bar + `), + ), + ).resolves.toBe(false); + }); +}); + + + + + +/** + * TODO: !!! Implement + * TODO: !!!last Annotate + */ diff --git a/src/utils/client/isClientVerified.ts b/src/utils/client/isClientVerified.ts index 657d7318e..530a34f89 100644 --- a/src/utils/client/isClientVerified.ts +++ b/src/utils/client/isClientVerified.ts @@ -1,4 +1,16 @@ -isClientVerified; +import { describe, expect, it } from '@jest/globals'; +import spaceTrim from 'spacetrim'; + + +/** +* isClientVerified @@@ +*/ +export function isClientVerified(value: string): boolean { + return value === 'Foo'; +} + + + /** * TODO: !!! Implement diff --git a/src/utils/client/sendEmailToVerifyClient.ts b/src/utils/client/sendEmailToVerifyClient.ts index 7226f6b74..8932202d6 100644 --- a/src/utils/client/sendEmailToVerifyClient.ts +++ b/src/utils/client/sendEmailToVerifyClient.ts @@ -1,4 +1,9 @@ -sendEmailToVerifyClient; +/** + * sendEmailToVerifyClient @@@ + */ +export function sendEmailToVerifyClient(value: string): boolean { + return value === 'Foo'; +} /** * TODO: !!! Implement diff --git a/src/utils/client/verifyEmailCode.ts b/src/utils/client/verifyEmailCode.ts index d00268e5a..07f5f1f39 100644 --- a/src/utils/client/verifyEmailCode.ts +++ b/src/utils/client/verifyEmailCode.ts @@ -1,7 +1,11 @@ -verifyEmailCode - +/** + * verifyEmailCode @@@ + */ +export async function verifyEmailCode(value: string): boolean { + return value === 'Foo'; +} /** * TODO: !!! Implement * TODO: !!!last Annotate - */ \ No newline at end of file + */ From 2710d1e74b229d321b079997151ff096afa561f9 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 13:16:23 +0100 Subject: [PATCH 11/48] Branded type for client_id --- src/ai/prompt-templates/getExecutionTools.ts | 4 ++-- ...seLoggerWrapperOfNaturalExecutionToolsOptions.ts | 4 ++-- src/ai/text-to-image/getImageGenerator.ts | 4 ++-- src/ai/text-to-image/getPhotobank.ts | 4 ++-- .../SupabaseLoggerWrapperOfImageGeneratorOptions.ts | 4 ++-- .../interfaces/PregeneratedPhotobankOptions.ts | 4 ++-- .../remote/delete/RemoteImageGenerator.ts.delete | 4 ++-- ...createRemoteImageGeneratorRouteHandler.ts.delete | 4 ++-- .../CreateRemoteImageGeneratorServerOptions.ts | 5 +++-- .../text-to-image/remote/interfaces/Imgs_Request.ts | 6 +++--- .../RemoteNaturalExecutionToolsOptions.ts | 4 ++-- src/utils/IWallpaper.ts | 4 ++-- src/utils/client/isClientVerified.ts | 3 ++- .../client/provideClientIdWithoutVerification.ts | 4 ++-- src/utils/client/sendEmailToVerifyClient.ts | 4 +++- src/utils/client/verifyEmailCode.ts | 4 +++- src/utils/typeAliases.ts | 13 +++++++++++-- .../createNewWallpaper/createNewWallpaper_image.ts | 4 ++-- .../createNewWallpaper/createNewWallpaper_text.ts | 4 ++-- .../interfaces/CreateNewWallpaperRequest.ts | 5 ++--- 20 files changed, 53 insertions(+), 39 deletions(-) diff --git a/src/ai/prompt-templates/getExecutionTools.ts b/src/ai/prompt-templates/getExecutionTools.ts index 3281b0c7d..cbb4abe2d 100644 --- a/src/ai/prompt-templates/getExecutionTools.ts +++ b/src/ai/prompt-templates/getExecutionTools.ts @@ -7,7 +7,7 @@ import { IS_DEVELOPMENT, NEXT_PUBLIC_PROMPTBOOK_SERVER_URL } from '../../../conf import { isRunningInBrowser, isRunningInWebWorker } from '../../utils/isRunningInWhatever'; import { getSupabaseForWorker } from '../../utils/supabase/getSupabaseForWorker'; import { Database } from '../../utils/supabase/types'; -import { uuid } from '../../utils/typeAliases'; +import { client_id } from '../../utils/typeAliases'; import { simpleTextDialogue } from '../../workers/dialogues/simple-text/simpleTextDialogue'; /** @@ -27,7 +27,7 @@ let executionTools: ExecutionTools; * * @returns ExecutionTools */ -export function getExecutionTools(clientId: uuid): ExecutionTools { +export function getExecutionTools(clientId: client_id): ExecutionTools { if (!isRunningInWebWorker() && !isRunningInBrowser()) { throw new Error('This function is available ONLY in browser or worker'); } diff --git a/src/ai/prompt-templates/logger/SupabaseLoggerWrapperOfNaturalExecutionToolsOptions.ts b/src/ai/prompt-templates/logger/SupabaseLoggerWrapperOfNaturalExecutionToolsOptions.ts index f0957217e..b8de8596f 100644 --- a/src/ai/prompt-templates/logger/SupabaseLoggerWrapperOfNaturalExecutionToolsOptions.ts +++ b/src/ai/prompt-templates/logger/SupabaseLoggerWrapperOfNaturalExecutionToolsOptions.ts @@ -1,5 +1,5 @@ import type { CommonExecutionToolsOptions, NaturalExecutionTools } from '@promptbook/types'; -import type { uuid } from '../../../utils/typeAliases'; +import { client_id } from '../../../utils/typeAliases'; /** * Options for SupabaseLoggerWrapperOfNaturalExecutionTools @@ -14,5 +14,5 @@ export interface SupabaseLoggerWrapperOfNaturalExecutionToolsOptions extends Com /** * Client responsible for the requests */ - readonly clientId: uuid; + readonly clientId: client_id; } diff --git a/src/ai/text-to-image/getImageGenerator.ts b/src/ai/text-to-image/getImageGenerator.ts index 4736c7d88..9491824d8 100644 --- a/src/ai/text-to-image/getImageGenerator.ts +++ b/src/ai/text-to-image/getImageGenerator.ts @@ -1,6 +1,6 @@ import { IS_DEVELOPMENT, NEXT_PUBLIC_IMAGE_SERVER_URL } from '../../../config'; import { isRunningInBrowser, isRunningInWebWorker } from '../../utils/isRunningInWhatever'; -import { uuid } from '../../utils/typeAliases'; +import { client_id } from '../../utils/typeAliases'; import { ImageGenerator } from './0-interfaces/ImageGenerator'; import { RemoteImageGenerator } from './remote/RemoteImageGenerator'; @@ -21,7 +21,7 @@ let imageGenerator: ImageGenerator; * * @returns ImageGenerator based on OpenAI Dalle */ -export function getImageGenerator(clientId: uuid): ImageGenerator { +export function getImageGenerator(clientId: client_id): ImageGenerator { if (!isRunningInWebWorker() && !isRunningInBrowser()) { throw new Error('This function is available ONLY in browser or worker'); } diff --git a/src/ai/text-to-image/getPhotobank.ts b/src/ai/text-to-image/getPhotobank.ts index b8bf3dd1a..9a2d9d392 100644 --- a/src/ai/text-to-image/getPhotobank.ts +++ b/src/ai/text-to-image/getPhotobank.ts @@ -1,6 +1,6 @@ -import { uuid } from '@promptbook/types'; import { IS_DEVELOPMENT } from '../../../config'; import { isRunningInBrowser, isRunningInWebWorker } from '../../utils/isRunningInWhatever'; +import { client_id } from '../../utils/typeAliases'; import { ImageGenerator } from './0-interfaces/ImageGenerator'; import { PregeneratedPhotobank } from './photobank/PregeneratedPhotobank'; @@ -21,7 +21,7 @@ let photobank: PregeneratedPhotobank; * * @returns ImageGenerator based on pregenerated MidJourney images */ -export function getPhotobank(clientId: uuid): ImageGenerator { +export function getPhotobank(clientId: client_id): ImageGenerator { if (!isRunningInWebWorker() && !isRunningInBrowser()) { throw new Error('This function is available ONLY in browser or worker'); } diff --git a/src/ai/text-to-image/logger/SupabaseLoggerWrapperOfImageGeneratorOptions.ts b/src/ai/text-to-image/logger/SupabaseLoggerWrapperOfImageGeneratorOptions.ts index bd9af18b8..82d68d923 100644 --- a/src/ai/text-to-image/logger/SupabaseLoggerWrapperOfImageGeneratorOptions.ts +++ b/src/ai/text-to-image/logger/SupabaseLoggerWrapperOfImageGeneratorOptions.ts @@ -1,5 +1,5 @@ import type { CommonExecutionToolsOptions } from '@promptbook/types'; -import type { uuid } from '../../../utils/typeAliases'; +import type { client_id } from '../../../utils/typeAliases'; import { ImageGenerator } from '../0-interfaces/ImageGenerator'; /** @@ -15,5 +15,5 @@ export interface SupabaseLoggerWrapperOfImageGeneratorOptions extends CommonExec /** * Client responsible for the requests */ - readonly clientId: uuid; + readonly clientId: client_id; } diff --git a/src/ai/text-to-image/photobank/interfaces/PregeneratedPhotobankOptions.ts b/src/ai/text-to-image/photobank/interfaces/PregeneratedPhotobankOptions.ts index 6d1ac90c4..a1befc1b0 100644 --- a/src/ai/text-to-image/photobank/interfaces/PregeneratedPhotobankOptions.ts +++ b/src/ai/text-to-image/photobank/interfaces/PregeneratedPhotobankOptions.ts @@ -1,5 +1,5 @@ import { CommonExecutionToolsOptions } from '@promptbook/types'; -import { uuid } from '../../../../utils/typeAliases'; +import { client_id } from '../../../../utils/typeAliases'; /** * Search options for the pregenerated photobank @@ -8,5 +8,5 @@ export interface PregeneratedPhotobankOptions extends CommonExecutionToolsOption /** * Client ID for the GPT usage */ - clientId: uuid; + clientId: client_id; } diff --git a/src/ai/text-to-image/remote/delete/RemoteImageGenerator.ts.delete b/src/ai/text-to-image/remote/delete/RemoteImageGenerator.ts.delete index 6a8a9b6ab..3f687ad6e 100644 --- a/src/ai/text-to-image/remote/delete/RemoteImageGenerator.ts.delete +++ b/src/ai/text-to-image/remote/delete/RemoteImageGenerator.ts.delete @@ -1,4 +1,4 @@ -import { uuid } from '@promptbook/types'; +import { client_id } from '@promptbook/types'; import { ImageGenerator } from '../../0-interfaces/ImageGenerator'; import { ImagePrompt } from '../../0-interfaces/ImagePrompt'; import { ImagePromptResult } from '../../0-interfaces/ImagePromptResult'; @@ -8,7 +8,7 @@ import { RemoteImageGeneratorResponse } from './createRemoteImageGeneratorRouteH * Image generator called remotely */ export class RemoteImageGenerator implements ImageGenerator { - public constructor(private readonly clientId: uuid, private readonly subroute: string) {} + public constructor(private readonly clientId: client_id, private readonly subroute: string) {} public async generate(prompt: ImagePrompt): Promise> { const response = await fetch( diff --git a/src/ai/text-to-image/remote/delete/createRemoteImageGeneratorRouteHandler.ts.delete b/src/ai/text-to-image/remote/delete/createRemoteImageGeneratorRouteHandler.ts.delete index 001de3783..51da59f84 100644 --- a/src/ai/text-to-image/remote/delete/createRemoteImageGeneratorRouteHandler.ts.delete +++ b/src/ai/text-to-image/remote/delete/createRemoteImageGeneratorRouteHandler.ts.delete @@ -1,5 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import type { uuid } from '../../../utils/typeAliases'; +import type { client_id } from '../../../utils/typeAliases'; import { isValidClientId } from '../../../utils/validators/isValidClientId'; import type { ImageGenerator } from '../0-interfaces/ImageGenerator'; import type { ImagePrompt } from '../0-interfaces/ImagePrompt'; @@ -14,7 +14,7 @@ interface CreateRemoteImageGeneratorRouteHandlerOptions { /** * Provides an image generator for a given client */ - createImageGenerator(clientId: uuid): ImageGenerator; + createImageGenerator(clientId: client_id): ImageGenerator; } /** diff --git a/src/ai/text-to-image/remote/interfaces/CreateRemoteImageGeneratorServerOptions.ts b/src/ai/text-to-image/remote/interfaces/CreateRemoteImageGeneratorServerOptions.ts index 712014580..059ae72a4 100644 --- a/src/ai/text-to-image/remote/interfaces/CreateRemoteImageGeneratorServerOptions.ts +++ b/src/ai/text-to-image/remote/interfaces/CreateRemoteImageGeneratorServerOptions.ts @@ -1,4 +1,5 @@ -import type { CommonExecutionToolsOptions, string_uri, uuid } from '@promptbook/types'; +import type { CommonExecutionToolsOptions, string_uri } from '@promptbook/types'; +import { client_id } from '../../../../utils/typeAliases'; import type { ImageGenerator } from '../../0-interfaces/ImageGenerator'; export interface CreateRemoteImageGeneratorServerOptions extends CommonExecutionToolsOptions { @@ -18,5 +19,5 @@ export interface CreateRemoteImageGeneratorServerOptions extends CommonExecution /** * Provides an image generator for a given client */ - createImageGenerator(clientId: uuid): ImageGenerator; + createImageGenerator(clientId: client_id): ImageGenerator; } diff --git a/src/ai/text-to-image/remote/interfaces/Imgs_Request.ts b/src/ai/text-to-image/remote/interfaces/Imgs_Request.ts index f40e7cf99..d5dd3a731 100644 --- a/src/ai/text-to-image/remote/interfaces/Imgs_Request.ts +++ b/src/ai/text-to-image/remote/interfaces/Imgs_Request.ts @@ -1,16 +1,16 @@ -import type { uuid } from '../../../../utils/typeAliases'; +import { client_id } from '../../../../utils/typeAliases'; import type { ImagePrompt } from '../../0-interfaces/ImagePrompt'; /** * Socket.io progress for remote image generation - * + * * This is a request from client to server */ export interface Imgs_Request { /** * Client responsible for the requests */ - readonly clientId: uuid; + readonly clientId: client_id; /** * The Prompt to execute diff --git a/src/ai/text-to-image/remote/interfaces/RemoteNaturalExecutionToolsOptions.ts b/src/ai/text-to-image/remote/interfaces/RemoteNaturalExecutionToolsOptions.ts index 2e9c8a1cf..738122e0e 100644 --- a/src/ai/text-to-image/remote/interfaces/RemoteNaturalExecutionToolsOptions.ts +++ b/src/ai/text-to-image/remote/interfaces/RemoteNaturalExecutionToolsOptions.ts @@ -1,5 +1,5 @@ import type { CommonExecutionToolsOptions } from '@promptbook/types'; -import type { uuid,string_uri } from '../../../../utils/typeAliases'; +import type { client_id, string_uri } from '../../../../utils/typeAliases'; /** * Options for RemoteNaturalExecutionTools @@ -22,5 +22,5 @@ export interface RemoteNaturalExecutionToolsOptions extends CommonExecutionTools /** * Your client ID */ - readonly clientId: uuid; + readonly clientId: client_id; } diff --git a/src/utils/IWallpaper.ts b/src/utils/IWallpaper.ts index d6613af2d..4904b71d0 100644 --- a/src/utils/IWallpaper.ts +++ b/src/utils/IWallpaper.ts @@ -3,7 +3,7 @@ import { Vector } from 'xyzt'; import { Json } from '../utils/supabase/types'; import { IImageColorStats } from './image/utils/IImageColorStats'; import { IMidjourneyJob } from './IMidjourneyJob'; -import { string_maxdown, string_midjourney_prompt, string_url, string_wallpaper_id, title, uuid } from './typeAliases'; +import { client_id, string_maxdown, string_midjourney_prompt, string_url, string_wallpaper_id, title } from './typeAliases'; export interface IWallpaper { /** @@ -27,7 +27,7 @@ export interface IWallpaper { * - If user edit and save public wallpaper, he becomes the author of the derived version * Information about the original author is stored in parent */ - readonly author: uuid; + readonly author: client_id; /** * Is the wallpaper public or private diff --git a/src/utils/client/isClientVerified.ts b/src/utils/client/isClientVerified.ts index 530a34f89..9559b950f 100644 --- a/src/utils/client/isClientVerified.ts +++ b/src/utils/client/isClientVerified.ts @@ -1,11 +1,12 @@ import { describe, expect, it } from '@jest/globals'; import spaceTrim from 'spacetrim'; +import { client_id } from '../typeAliases'; /** * isClientVerified @@@ */ -export function isClientVerified(value: string): boolean { +export function isClientVerified(clientId: client_id): boolean { return value === 'Foo'; } diff --git a/src/utils/client/provideClientIdWithoutVerification.ts b/src/utils/client/provideClientIdWithoutVerification.ts index c8496c5d3..85a45a663 100644 --- a/src/utils/client/provideClientIdWithoutVerification.ts +++ b/src/utils/client/provideClientIdWithoutVerification.ts @@ -1,6 +1,6 @@ import { isRunningInBrowser } from '../isRunningInWhatever'; import { randomUuid } from '../randomUuid'; -import { uuid } from '../typeAliases'; +import { client_id, uuid } from '../typeAliases'; import { isValidClientId } from '../validators/isValidClientId'; /** @@ -8,7 +8,7 @@ import { isValidClientId } from '../validators/isValidClientId'; * @private * @singleton */ -let clientId: uuid | null = null; +let clientId: client_id | null = null; /** * Gets clientId from localStorage or generates new one diff --git a/src/utils/client/sendEmailToVerifyClient.ts b/src/utils/client/sendEmailToVerifyClient.ts index 8932202d6..80bf7c965 100644 --- a/src/utils/client/sendEmailToVerifyClient.ts +++ b/src/utils/client/sendEmailToVerifyClient.ts @@ -1,7 +1,9 @@ +import { client_id, string_email } from "../typeAliases"; + /** * sendEmailToVerifyClient @@@ */ -export function sendEmailToVerifyClient(value: string): boolean { +export function sendEmailToVerifyClient(clientId: client_id, email: string_email): boolean { return value === 'Foo'; } diff --git a/src/utils/client/verifyEmailCode.ts b/src/utils/client/verifyEmailCode.ts index 07f5f1f39..d67e098c7 100644 --- a/src/utils/client/verifyEmailCode.ts +++ b/src/utils/client/verifyEmailCode.ts @@ -1,7 +1,9 @@ +import { client_id, string_token } from '../typeAliases'; + /** * verifyEmailCode @@@ */ -export async function verifyEmailCode(value: string): boolean { +export async function verifyEmailCode(clientId: client_id, email: string_token): boolean { return value === 'Foo'; } diff --git a/src/utils/typeAliases.ts b/src/utils/typeAliases.ts index 388185d39..b97d75e9b 100644 --- a/src/utils/typeAliases.ts +++ b/src/utils/typeAliases.ts @@ -193,7 +193,9 @@ export type string_markdown_text = string_markdown; * * For example `"**Hello** World!"` */ -export type string_maxdown = string & { _type: 'maxdown' }; +export type string_maxdown = string & { + _type: 'maxdown' /* <- TODO: [🏟] What is the best shape of the additional object in branded types */; +}; /** * Semantic helper @@ -351,7 +353,14 @@ export type string_email = string; * For example `"5a0a153d-7be9-4018-9eda-e0e2e2b89bd9"` */ export type uuid = string & { - readonly __type: 'UUID' /* <- TODO: [0] What is the best shape of the additional object in branded types */; + readonly _type: 'uuid' /* <- TODO: [🏟] What is the best shape of the additional object in branded types */; +}; + +/** + * Branded type client id + */ +export type client_id = uuid & { + readonly _type: 'client_id' /* <- TODO: [🏟] What is the best shape of the additional object in branded types */; }; /** diff --git a/src/workers/functions/createNewWallpaper/createNewWallpaper_image.ts b/src/workers/functions/createNewWallpaper/createNewWallpaper_image.ts index 216f9cb63..2b51faee5 100644 --- a/src/workers/functions/createNewWallpaper/createNewWallpaper_image.ts +++ b/src/workers/functions/createNewWallpaper/createNewWallpaper_image.ts @@ -14,7 +14,7 @@ import { createImageInWorker } from '../../../utils/image/createImageInWorker'; import { measureImageBlob } from '../../../utils/image/measureImageBlob'; import { resizeImageBlob } from '../../../utils/image/resizeImageBlob'; import { IImageColorStats } from '../../../utils/image/utils/IImageColorStats'; -import { string_image_prompt, string_url_image, uuid } from '../../../utils/typeAliases'; +import { string_image_prompt, string_url_image } from '../../../utils/typeAliases'; import { imageGeneratorDialogue } from '../../dialogues/image-generator/imageGeneratorDialogue'; interface CreateNewWallpaperImageRequest { @@ -22,7 +22,7 @@ interface CreateNewWallpaperImageRequest { * Author of the wallpaper * Note: It must be valid client ID and same as identity of the user */ - readonly author: uuid; + readonly author: client_id; /** * Image of the wallpaper diff --git a/src/workers/functions/createNewWallpaper/createNewWallpaper_text.ts b/src/workers/functions/createNewWallpaper/createNewWallpaper_text.ts index 166d3690d..e69cc8118 100644 --- a/src/workers/functions/createNewWallpaper/createNewWallpaper_text.ts +++ b/src/workers/functions/createNewWallpaper/createNewWallpaper_text.ts @@ -7,6 +7,7 @@ import { addFontToContent } from '../../../components/ImportFonts/addFontToConte import type { WriteWallpaperPromptResponse } from '../../../pages/api/image-to-text'; import { randomItem } from '../../../utils/randomItem'; import { + client_id, description, string_image_prompt, string_maxdown, @@ -15,7 +16,6 @@ import { string_url, string_url_image, title, - uuid, } from '../../../utils/typeAliases'; export interface CreateNewWallpaperTextRequest { @@ -39,7 +39,7 @@ export interface CreateNewWallpaperTextRequest { * Author of the wallpaper * Note: It must be valid client ID and same as identity of the user */ - readonly author: uuid; + readonly author: client_id; /** * URL of the wallpaper in our CDN diff --git a/src/workers/functions/createNewWallpaper/interfaces/CreateNewWallpaperRequest.ts b/src/workers/functions/createNewWallpaper/interfaces/CreateNewWallpaperRequest.ts index 4717a9e87..138d0f717 100644 --- a/src/workers/functions/createNewWallpaper/interfaces/CreateNewWallpaperRequest.ts +++ b/src/workers/functions/createNewWallpaper/interfaces/CreateNewWallpaperRequest.ts @@ -1,12 +1,11 @@ import { + client_id, description, - string_markdown, string_maxdown, string_name, string_translate_language, string_url, title, - uuid, } from '../../../../utils/typeAliases'; export interface CreateNewWallpaperRequest { @@ -30,7 +29,7 @@ export interface CreateNewWallpaperRequest { * Author of the wallpaper * Note: It must be valid client ID and same as identity of the user */ - readonly author: uuid; + readonly author: client_id; /** * Image of the wallpaper From b7f026e5e1b431e81272fc1b39018b46e3d26022 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 13:34:11 +0100 Subject: [PATCH 12/48] Add $ prefix to non-pure function + TODOs --- TODO.md | 7 +--- .../ClientVerificationComponent.tsx | 40 ++++++++++++++----- src/components/ControlPanel/ControlPanel.tsx | 4 +- .../ControlPanel/ControlPanelLikeButtons.tsx | 4 +- .../RandomWallpaper/RandomWallpaperManager.ts | 4 +- src/components/CopilotPanel/CopilotPanel.tsx | 8 ++-- src/components/ExportModal/ExportModal.tsx | 4 +- src/components/ExportModal/exportWebsite.ts | 6 +-- src/components/PromptCook/PromptCook.tsx | 4 +- src/components/PublishModal/PublishModal.tsx | 4 +- src/components/PublishModal/publishWebsite.ts | 4 +- .../UploadNewWallpaper/UploadNewWallpaper.tsx | 4 +- src/pages/new/from-idea.tsx | 4 +- src/pages/new/from-instagram.tsx | 6 +-- src/utils/client/generateClientId.ts | 13 ++++++ src/utils/client/isClientVerified.test.ts | 10 ++--- src/utils/client/isClientVerified.ts | 16 +++----- src/utils/client/provideClientEmail.ts | 7 +++- src/utils/client/provideClientId.ts | 9 ++--- .../provideClientIdWithoutVerification.ts | 14 +++---- src/utils/client/sendEmailToVerifyClient.ts | 5 ++- src/utils/client/verifyEmailCode.ts | 5 ++- src/utils/hooks/useClientId.ts | 4 +- .../hooks/useLikedStatusOfCurrentWallpaper.ts | 4 +- src/utils/scraping/fetchImage.ts | 4 +- .../ValidateEmailDialogueComponent.tsx | 2 +- 26 files changed, 110 insertions(+), 86 deletions(-) create mode 100644 src/utils/client/generateClientId.ts diff --git a/TODO.md b/TODO.md index 3a2cc1fe4..9b7618701 100644 --- a/TODO.md +++ b/TODO.md @@ -10,14 +10,9 @@ - [ ] When importing type USE always `import type { ... } from '...'` automatically Search & replace `import\s+(\{.*\/interfaces\/)` -> `import type $1` works - [ ] Script for auto-generating fresh unused tags with emojis [👩‍🦱][👩‍🦲][👳‍♂️] - - [ ] Go through all .push(...) and decide to change to [...x,y] where better - - [ ] Do not use useEffect sooo often https://youtu.be/bGzanfKVFeU?si=CSebLURTV3RKqOsn - - - -p +- [ ] Mark all non-pure functions/classes with $ prefix OR `/* @impure */` annotation ## Misc diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index a84f731c7..b93395e55 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -1,14 +1,9 @@ -import type { ReactNode } from 'react'; +import { useCallback, useRef } from 'react'; import { classNames } from '../../utils/classNames'; +import { useStyleModule } from '../../utils/hooks/useStyleModule'; import type { string_css_class } from '../../utils/typeAliases'; -import styles from './ClientVerificationComponent.module.css'; interface ClientVerificationComponentProps { - /** - * Content of @@ - */ - readonly children?: ReactNode; - /** * Optional CSS class name which will be added to root element */ @@ -19,12 +14,35 @@ interface ClientVerificationComponentProps { * Renders a @@ */ export function ClientVerificationComponent(props: ClientVerificationComponentProps) { - const { children, className } = props; + const { className } = props; + + const styles = useStyleModule(import('./ClientVerificationComponent.module.css')); + + const emailInputRef = useRef(null); + const submit = useCallback(() => { + respond({ answer: emailInputRef.current!.value, feedback }); + }, [respond, emailInputRef, feedback]); + - // Use or remove: - //> const styles = useStyleModule(import('./ClientVerificationComponent.module.css')); + return ( +
+ { + if (!(event.key === 'Enter' && event.shiftKey === false && event.ctrlKey === false)) { + return; + } - return
{children}
; + submit(); + }} + /> +
+ ); } /** diff --git a/src/components/ControlPanel/ControlPanel.tsx b/src/components/ControlPanel/ControlPanel.tsx index 7f58d5c3b..758ccc179 100644 --- a/src/components/ControlPanel/ControlPanel.tsx +++ b/src/components/ControlPanel/ControlPanel.tsx @@ -3,7 +3,7 @@ import { useRouter } from 'next/router'; import { IS_VERIFIED_EMAIL_REQUIRED } from '../../../config'; import type { LikedStatus } from '../../ai/recommendation/LikedStatus'; import { classNames } from '../../utils/classNames'; -import { provideClientId } from '../../utils/client/provideClientId'; +import { $provideClientId } from '../../utils/client/provideClientId'; import { computeWallpaperUriid } from '../../utils/computeWallpaperUriid'; import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper'; import { serializeWallpaper } from '../../utils/hydrateWallpaper'; @@ -34,7 +34,7 @@ export function ControlPanel() {
); } /** + * TODO: !!! Implement ClientVerificationComponent with using onVerificationSuccess * TODO: !!! Implement * TODO: */ diff --git a/src/components/Modal/00-Modal.tsx b/src/components/Modal/00-Modal.tsx index e4342b37b..939255f42 100644 --- a/src/components/Modal/00-Modal.tsx +++ b/src/components/Modal/00-Modal.tsx @@ -5,7 +5,7 @@ import { string_css_class } from '../../utils/typeAliases'; import { MarkdownContent } from '../Content/MarkdownContent'; import { CloseModalLink } from './10-CloseModalLink'; -interface ModalProps { +type ModalProps = { /** * Title of the modal */ @@ -21,28 +21,6 @@ interface ModalProps { */ readonly isDisabled?: boolean; - /** - * Whether the modal can be closed by clicking on the overlay - * - * If `true` then you need to be in wallpaper page to close the modal - */ - readonly isCloseable?: boolean; - - /** - * The close icon - * - * @default "✖" - */ - readonly closeIcon?: '✖' | '✔'; - - /** - * Callback which will be called when the modal is requested to be closed - * - * If NOT set it will use as default - * Warning: YOU SHOULD set it when you are using modal with isCloseable outside of wallpaper page - */ - readonly onClose?: () => void; - /** * Size of the modal * @@ -54,7 +32,39 @@ interface ModalProps { * Optional CSS class name which will be added to content part of the modal */ readonly className?: string_css_class; -} +} & ( + | { + /** + * Whether the modal can be closed by clicking on the overlay + * + * If `false` then you need to be in wallpaper page to close the modal + */ + readonly isCloseable?: false; + } + | { + /** + * Whether the modal can be closed by clicking on the overlay + * + * If `true` then you need to be in wallpaper page to close the modal + */ + readonly isCloseable: true; + + /** + * The close icon + * + * @default "✖" + */ + readonly closeIcon?: '✖' | '✔'; + + /** + * Callback which will be called when the modal is requested to be closed + * + * If NOT set it will use as default + * Warning: YOU SHOULD set it when you are using modal with isCloseable outside of wallpaper page + */ + readonly onClose?: () => void; + } +); /** * Renders a modal above the wallpaper page diff --git a/src/pages/verify.tsx b/src/pages/verify.tsx index 724e1981b..93d4a7ceb 100644 --- a/src/pages/verify.tsx +++ b/src/pages/verify.tsx @@ -1 +1,23 @@ -// TODO: !!! Implement \ No newline at end of file +import { StaticAppHead } from '../components/AppHead/StaticAppHead'; +import { ClientVerificationComponent } from '../components/ClientVerificationComponent/ClientVerificationComponent'; +import { ClientEmailVerification } from '../utils/client/ClientVerification'; +import { WallpapersContext } from '../utils/hooks/WallpapersContext'; + +export default function VerifyPage() { + return ( + + + + { + // TODO: !!! Do domething + // TODO: !!! Redirect to ?ref=... (if exists) + }} + /> + + ); +} + +/** + * TODO: !!! Design of the page + */ diff --git a/src/utils/client/ClientVerification.ts b/src/utils/client/ClientVerification.ts new file mode 100644 index 000000000..88ca7972b --- /dev/null +++ b/src/utils/client/ClientVerification.ts @@ -0,0 +1,18 @@ +import type { client_id, string_email } from '../typeAliases'; + +export type ClientEmailVerification = { + /** + * The client id under which the email is validated + */ + readonly clientId: client_id; + + /** + * The validated/entered email + */ + readonly email: string_email; + + /** + * Is the email verified + */ + readonly isEmailVerified: boolean; +}; diff --git a/src/utils/client/CodeValidationError.ts b/src/utils/client/CodeValidationError.ts index 44715ca9f..f9267d3e3 100644 --- a/src/utils/client/CodeValidationError.ts +++ b/src/utils/client/CodeValidationError.ts @@ -1,4 +1,4 @@ /** - * TODO: !!! Implement + * TODO: !!! Implement or remove * TODO: !!!last Annotate */ diff --git a/src/utils/client/generateVerificationCode.ts b/src/utils/client/generateVerificationCode.ts new file mode 100644 index 000000000..0ffefea9c --- /dev/null +++ b/src/utils/client/generateVerificationCode.ts @@ -0,0 +1,13 @@ +import { $randomString } from '../randomString'; +import type { string_token } from '../typeAliases'; + +/** + * Generates random verification code + */ +export function $generateVerificationCode(): string_token { + return $randomString(10, '0123456789'); +} + +/** + * TODO: !!! Use common util for generateVerificationCode, generating wallpaper URI + */ diff --git a/src/utils/client/isClientVerifiedForServer.ts b/src/utils/client/isClientVerifiedForServer.ts index b1fe0d031..9ead72cc1 100644 --- a/src/utils/client/isClientVerifiedForServer.ts +++ b/src/utils/client/isClientVerifiedForServer.ts @@ -13,6 +13,8 @@ export async function $isClientVerifiedForServer(options: IsClientVerifiedReques ); } + const {clientId} = options; + /* const selectResult = await getSupabaseForServer().from('Client').select('email').eq('clientId', clientId).limit(1); diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index 2de117f9c..ee9f07a4a 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -1,4 +1,11 @@ +import spaceTrim from 'spacetrim'; +import { APP_NAME, APP_SIGNATURE } from '../../../config'; +import { validateMaxdown } from '../../components/Content/Maxdown/validateMaxdown'; +import { sendEmailForServer } from '../emails/sendEmailForServer'; import { isRunningInNode } from '../isRunningInWhatever'; +import { getSupabaseForServer } from '../supabase/getSupabaseForServer'; +import { isValidEmail } from '../validators/isValidEmail'; +import { $generateVerificationCode } from './generateVerificationCode'; import type { SendEmailToVerifyClientRequest, SendEmailToVerifyClientResult } from './sendEmailToVerifyClient.types'; /** @@ -15,7 +22,42 @@ export async function $sendEmailToVerifyClientForServer( ); } + const { clientId, email } = options; + if (!isValidEmail(email)) { + // TODO: !!! [🧠] Use here CodeValidationError OR return false + throw new Error('!!!'); + } + + const code = $generateVerificationCode(); + + // TODO: Maybe do theese things (insert+email sending) in parallel OR transaction but not indipedently at series + await getSupabaseForServer().from('ClientEmailVerificationRequest').insert({ clientId, email, code }); + await sendEmailForServer({ + to: email, + subject: `⏣ ${APP_NAME} verification code`, + + // TODO: !!! use maxdown`` pattern + content: validateMaxdown( + // TODO: !!! Better text + // TODO: !!! Translations + // TODO: !!! Add verification link alongsite the code + spaceTrim(` + Your code to sign-in into ${APP_SIGNATURE} is: + + **${code}** + + --- + + !!! Add wait dislaimer for people whos email this isnt !!! + `), + ), + }); + + return { + // TODO: !!! Handle errors and report false + isSendingEmailSuccessful: true, + }; } /** diff --git a/src/utils/client/verifyEmailCodeForServer.ts b/src/utils/client/verifyEmailCodeForServer.ts index 88b3d6b86..7c33111f0 100644 --- a/src/utils/client/verifyEmailCodeForServer.ts +++ b/src/utils/client/verifyEmailCodeForServer.ts @@ -15,6 +15,8 @@ export async function $verifyEmailCodeForServer(options: VerifyEmailCodeRequest) 'Function `$verifyEmailCodeForBrowser` can not be used in browser or worker, use browser version instead.', ); } + + const {clientId,email} = options; } /** diff --git a/src/utils/emails/sendEmailForServer.ts b/src/utils/emails/sendEmailForServer.ts index 424dd00d9..ceb9ac415 100644 --- a/src/utils/emails/sendEmailForServer.ts +++ b/src/utils/emails/sendEmailForServer.ts @@ -1,4 +1,6 @@ import sendgridEmailClient from '@sendgrid/mail'; +import chalk from 'chalk'; +import spaceTrim from 'spacetrim'; import { SENDGRID_API_KEY } from '../../../config'; import { maxdownToHtml } from '../../components/Content/Maxdown/maxdownToHtml'; import { removeMarkdownFormatting } from '../content/removeMarkdownFormatting'; @@ -38,6 +40,21 @@ export async function sendEmailForServer(email: Email): Promise { html, }; + console.info( + chalk.bgBlueBright(`Sending email\n\n`) + + chalk.blueBright( + spaceTrim( + (block) => ` + From: ${block(from)} + To: ${block(to)} + Subject: ${block(subject)} + Text: + ${block(text)} + `, + ), + ), + ); + await sendgridEmailClient.send(sendgridEmail); /* diff --git a/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.module.css b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.module.css deleted file mode 100644 index e630dbb3e..000000000 --- a/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.module.css +++ /dev/null @@ -1,94 +0,0 @@ -.inner { - width: 100%; - height: 100%; - - display: grid; - grid-template: '🟦'; -} - -.inner .inputLayer, -.inner .feedbackLayer { - /*/ - outline: 1px dotted rgb(255 63 38); - /**/ - - grid-area: 🟦; - width: 100%; - height: 100%; -} - -.inner .inputLayer { - z-index: 1 /* <- Note: Local order in .inner */; -} - -.inner .feedbackLayer { - z-index: 2 /* <- Note: Local order in .inner */; -} - -/* ---------------------- */ - -.inner .inputLayer { - display: grid; - grid-template: - '🟢' 1fr - '🔵' 50px; -} - -.inner .inputLayer .answer { - /*/ - outline: 1px dotted rgb(81, 255, 38); - /**/ - - height: 100%; - width: 100%; - grid-area: 🟢; - - resize: none; - - border-radius: 0; - background-color: #151515; - color: white; - outline: none; - border: none; - padding: 10px; - - /* Note: This is not really a good solution, but it works for now - Text of answer should not be covered by feedback button - */ - padding-right: 55px; -} - -.inner .inputLayer .submit { - /*/ - outline: 1px dotted rgb(81, 255, 38); - /**/ - - grid-area: 🔵; - - width: 100%; - height: 100%; - cursor: pointer; -} - -/* ---------------------- */ - -.inner .feedbackLayer { - /*/ - outline: 1px dotted rgb(38 114 255); - /**/ - - display: flex; - align-items: flex-start; - justify-content: flex-end; - - padding: 10px; - padding-bottom: 60px; - - pointer-events: none; -} - -.inner .feedbackLayer button.triggerFeedback { - z-index: 3 /* <- Note: Local order in dialogue */; - - pointer-events: all; -} diff --git a/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx index 16d527c98..9800c92e7 100644 --- a/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx +++ b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx @@ -1,7 +1,5 @@ -import { useCallback, useRef, useState } from 'react'; -import type { Feedback } from '../../../../ai/recommendation/Feedback'; +import { ClientVerificationComponent } from '../../../../components/ClientVerificationComponent/ClientVerificationComponent'; import { Modal } from '../../../../components/Modal/00-Modal'; -import { useStyleModule } from '../../../../utils/hooks/useStyleModule'; import type { DialogueComponentProps } from '../../../lib/dialogues/interfaces/DialogueComponentProps'; import type { ValidateEmailDialogueRequest } from '../types/ValidateEmailDialogueRequest'; import type { ValidateEmailDialogueResponse } from '../types/ValidateEmailDialogueResponse'; @@ -15,53 +13,15 @@ export function ValidateEmailDialogueComponent( props: DialogueComponentProps, ) { const { - request: { isVerifiedEmailRequired, priority = 0 }, + request: { + /* TODO: !!! Maybe take default email and pass into as prop */ + }, respond, } = props; - const styles = useStyleModule(import('./ValidateEmailDialogueComponent.module.css')); - - - - const [feedback, setFeedback] = useState(); - const [isInFeedbackCollection, setInFeedbackCollection] = useState(false); - - const submit = useCallback(() => { - respond({ answer: emailInputRef.current!.value, feedback }); - }, [respond, emailInputRef, feedback]); - return ( - - {/* TODO: Maybe create some
component to make this type of layouts */} -
-
- { - if (!(event.key === 'Enter' && event.shiftKey === false && event.ctrlKey === false)) { - return; - } - - submit(); - }} - /> - -
-
+ + ); } diff --git a/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts b/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts index f0ed766fc..dbdb90c0e 100644 --- a/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts +++ b/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts @@ -1,14 +1,4 @@ -import { string_email } from '../../../../utils/typeAliases'; +import type { ClientVerification } from '../../../../utils/client/ClientVerification'; import type { AbstractDialogueResponse } from '../../../lib/dialogues/interfaces/AbstractDialogueResponse'; -export type ValidateEmailDialogueResponse = AbstractDialogueResponse & { - /** - * The validated/entered email - */ - readonly email: string_email; - - /** - * Is the email verified - */ - readonly isEmailVerified: boolean; -}; +export type ValidateEmailDialogueResponse = AbstractDialogueResponse & ClientVerification; From 172bfd728b17f871c9431acf93145b2feb37e90c Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 15:53:28 +0100 Subject: [PATCH 19/48] Update sendEmailForServer function --- src/utils/emails/sendEmailForServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/emails/sendEmailForServer.ts b/src/utils/emails/sendEmailForServer.ts index ceb9ac415..0a6b46bb7 100644 --- a/src/utils/emails/sendEmailForServer.ts +++ b/src/utils/emails/sendEmailForServer.ts @@ -41,7 +41,7 @@ export async function sendEmailForServer(email: Email): Promise { }; console.info( - chalk.bgBlueBright(`Sending email\n\n`) + + chalk.bgBlueBright(`Sending email\n`) + chalk.blueBright( spaceTrim( (block) => ` From d02580e74303f1f2a014d564bd081a3497d71046 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:21:14 +0100 Subject: [PATCH 20/48] Implementing verification utils --- src/pages/{verify.tsx => verify-email.tsx} | 3 +- src/utils/client/isClientVerified.types.ts | 2 +- .../client/isClientVerifiedForBrowser.ts | 2 +- src/utils/client/isClientVerifiedForServer.ts | 10 ++++- .../sendEmailToVerifyClientForBrowser.ts | 2 +- .../sendEmailToVerifyClientForServer.ts | 11 ++++-- src/utils/client/verifyEmailCode.types.ts | 3 +- src/utils/client/verifyEmailCodeForBrowser.ts | 5 +-- src/utils/client/verifyEmailCodeForServer.ts | 37 ++++++++++++++++--- 9 files changed, 55 insertions(+), 20 deletions(-) rename src/pages/{verify.tsx => verify-email.tsx} (91%) diff --git a/src/pages/verify.tsx b/src/pages/verify-email.tsx similarity index 91% rename from src/pages/verify.tsx rename to src/pages/verify-email.tsx index 93d4a7ceb..ccd49a8e0 100644 --- a/src/pages/verify.tsx +++ b/src/pages/verify-email.tsx @@ -3,7 +3,7 @@ import { ClientVerificationComponent } from '../components/ClientVerificationCom import { ClientEmailVerification } from '../utils/client/ClientVerification'; import { WallpapersContext } from '../utils/hooks/WallpapersContext'; -export default function VerifyPage() { +export default function VerifyEmailPage() { return ( @@ -19,5 +19,6 @@ export default function VerifyPage() { } /** + * TODO: !!! Integrate here ?code=... * TODO: !!! Design of the page */ diff --git a/src/utils/client/isClientVerified.types.ts b/src/utils/client/isClientVerified.types.ts index 46c43edeb..bf14f7f5c 100644 --- a/src/utils/client/isClientVerified.types.ts +++ b/src/utils/client/isClientVerified.types.ts @@ -5,7 +5,7 @@ export type IsClientVerifiedRequest = { }; export type IsClientVerifiedResult = { - isClientVerified: boolean; + status: 'NOT_VERIFIED' |'EMAIL_SENT' | 'VERIFIED'; }; /** diff --git a/src/utils/client/isClientVerifiedForBrowser.ts b/src/utils/client/isClientVerifiedForBrowser.ts index b77030c9e..2a8729ff0 100644 --- a/src/utils/client/isClientVerifiedForBrowser.ts +++ b/src/utils/client/isClientVerifiedForBrowser.ts @@ -2,7 +2,7 @@ import { isRunningInBrowser, isRunningInWebWorker } from '../isRunningInWhatever import type { IsClientVerifiedRequest, IsClientVerifiedResult } from './isClientVerified.types'; /** - * Function isClientVerified checks if client has verified email + * Function isClientVerifiedForBrowser checks if client has verified email * * Note: This function has version both for browser and server */ diff --git a/src/utils/client/isClientVerifiedForServer.ts b/src/utils/client/isClientVerifiedForServer.ts index 9ead72cc1..e0079df04 100644 --- a/src/utils/client/isClientVerifiedForServer.ts +++ b/src/utils/client/isClientVerifiedForServer.ts @@ -1,8 +1,9 @@ import { isRunningInNode } from '../isRunningInWhatever'; +import { getSupabaseForServer } from '../supabase/getSupabaseForServer'; import type { IsClientVerifiedRequest, IsClientVerifiedResult } from './isClientVerified.types'; /** - * Function isClientVerified checks if client has verified email + * Function isClientVerifiedForServer checks if client has verified email * * Note: This function has version both for browser and server */ @@ -13,7 +14,12 @@ export async function $isClientVerifiedForServer(options: IsClientVerifiedReques ); } - const {clientId} = options; + const { clientId } = options; + + const { data: verificationRequests } = await getSupabaseForServer() + .from('ClientEmailVerificationRequest') + .select('email') + .eq('clientId', clientId); /* const selectResult = await getSupabaseForServer().from('Client').select('email').eq('clientId', clientId).limit(1); diff --git a/src/utils/client/sendEmailToVerifyClientForBrowser.ts b/src/utils/client/sendEmailToVerifyClientForBrowser.ts index e247770c3..8fbf51666 100644 --- a/src/utils/client/sendEmailToVerifyClientForBrowser.ts +++ b/src/utils/client/sendEmailToVerifyClientForBrowser.ts @@ -2,7 +2,7 @@ import { isRunningInBrowser, isRunningInWebWorker } from '../isRunningInWhatever import type { SendEmailToVerifyClientRequest, SendEmailToVerifyClientResult } from './sendEmailToVerifyClient.types'; /** - * Function sendEmailToVerifyClient will generate a verification code, saves it into a DB and send it to the email + * Function sendEmailToVerifyClientForBrowser sends verification request to the server * * Note: This function has version both for browser and server */ diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index ee9f07a4a..6b3f061d3 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -1,5 +1,5 @@ import spaceTrim from 'spacetrim'; -import { APP_NAME, APP_SIGNATURE } from '../../../config'; +import { APP_NAME, NEXT_PUBLIC_URL } from '../../../config'; import { validateMaxdown } from '../../components/Content/Maxdown/validateMaxdown'; import { sendEmailForServer } from '../emails/sendEmailForServer'; import { isRunningInNode } from '../isRunningInWhatever'; @@ -9,7 +9,7 @@ import { $generateVerificationCode } from './generateVerificationCode'; import type { SendEmailToVerifyClientRequest, SendEmailToVerifyClientResult } from './sendEmailToVerifyClient.types'; /** - * Function sendEmailToVerifyClient will generate a verification code, saves it into a DB and send it to the email + * Function sendEmailToVerifyClientForServer will generate a verification code, saves it into a DB and send it to the email * * Note: This function has version both for browser and server */ @@ -42,10 +42,15 @@ export async function $sendEmailToVerifyClientForServer( // TODO: !!! Better text // TODO: !!! Translations // TODO: !!! Add verification link alongsite the code + // TODO: !!! Unify greeting and signature in all emails ACRY spaceTrim(` - Your code to sign-in into ${APP_SIGNATURE} is: + Hello, + Your code to sign-in into ${APP_NAME} is: **${code}** + + Or click on this link to verify your email: + ${NEXT_PUBLIC_URL.href}/verify-email?code=${code}&email=${encodeURIComponent(email)} --- diff --git a/src/utils/client/verifyEmailCode.types.ts b/src/utils/client/verifyEmailCode.types.ts index 1fb43cc63..ba319f1a2 100644 --- a/src/utils/client/verifyEmailCode.types.ts +++ b/src/utils/client/verifyEmailCode.types.ts @@ -3,10 +3,11 @@ import type { client_id, string_token } from '../typeAliases'; export type VerifyEmailCodeRequest = { clientId: client_id; email: string_token; + code: string_token; }; export type VerifyEmailCodeResult = { - isVerificationSuccessful: boolean; + status: 'ERROR' | 'VERIFIED' /*| 'ALREADY_VERIFIED' | 'EXPIRED'*/; }; /** diff --git a/src/utils/client/verifyEmailCodeForBrowser.ts b/src/utils/client/verifyEmailCodeForBrowser.ts index 4673ce3ca..f783e149f 100644 --- a/src/utils/client/verifyEmailCodeForBrowser.ts +++ b/src/utils/client/verifyEmailCodeForBrowser.ts @@ -2,10 +2,7 @@ import { isRunningInBrowser, isRunningInWebWorker } from '../isRunningInWhatever import type { VerifyEmailCodeRequest, VerifyEmailCodeResult } from './verifyEmailCode.types'; /** - * Function verifyEmailCode @@@ - * - * @returns true if code is valid - * @throws CodeValidationError if code is invalid + * Function verifyEmailCodeForBrowser sends verification code to the server and checks if it is correct * * Note: This function has version both for browser and server */ diff --git a/src/utils/client/verifyEmailCodeForServer.ts b/src/utils/client/verifyEmailCodeForServer.ts index 7c33111f0..9254d42e6 100644 --- a/src/utils/client/verifyEmailCodeForServer.ts +++ b/src/utils/client/verifyEmailCodeForServer.ts @@ -1,11 +1,10 @@ import { isRunningInNode } from '../isRunningInWhatever'; +import { getSupabaseForServer } from '../supabase/getSupabaseForServer'; import type { VerifyEmailCodeRequest, VerifyEmailCodeResult } from './verifyEmailCode.types'; /** - * Function verifyEmailCode @@@ - * - * @returns true if code is valid - * @throws CodeValidationError if code is invalid + * Function verifyEmailCodeForServer checks if verification code is correct + * If it is correct, it marks client as verified in the database * * Note: This function has version both for browser and server */ @@ -16,12 +15,38 @@ export async function $verifyEmailCodeForServer(options: VerifyEmailCodeRequest) ); } - const {clientId,email} = options; + const { clientId, email, code } = options; + + const { data: verificationRequests } = await getSupabaseForServer() + .from('ClientEmailVerificationRequest') + .select('id,createdAt,code,email') + .eq('clientId', clientId) + .eq('email', email); + + for (const verificationRequest of verificationRequests || []) { + if (verificationRequest.code === code && verificationRequest.email === email) { + const insertVerificationResult = await getSupabaseForServer().from('ClientEmailVerification').insert({ + verificationRequestId: verificationRequest.id, + }); + + // TODO: !! Util isInsertSuccessfull (status===201) + console.info({ insertVerificationResult }); + + return { + status: 'VERIFIED', + }; + } + } + + return { + status: 'ERROR', + }; } /** * TODO: [🌯] Create some system (simmilar to Workerify) which can create server functions exposed in client through API in some DRY way - * + * TODO: !! Use or remove status ALREADY_VERIFIED + * TODO: !! Use or remove status EXPIRED * TODO: !!! Implement * TODO: !!!last Annotate */ From 966959bafdcd0fbc26510ff75d19c7491399b2c4 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:22:16 +0100 Subject: [PATCH 21/48] Add view ClientEmailVerification_withRequests --- database/dumps/structure.dump.pgsql | 25 + src/utils/supabase/types.ts | 1098 ++++++++++++++------------- 2 files changed, 586 insertions(+), 537 deletions(-) diff --git a/database/dumps/structure.dump.pgsql b/database/dumps/structure.dump.pgsql index 908f16852..c8277e04a 100644 --- a/database/dumps/structure.dump.pgsql +++ b/database/dumps/structure.dump.pgsql @@ -1772,6 +1772,22 @@ CREATE VIEW public."Wallpaper_random" AS ALTER TABLE public."Wallpaper_random" OWNER TO postgres; +-- +-- Name: clientemailverification_withrequests; Type: VIEW; Schema: public; Owner: postgres +-- + +CREATE VIEW public.clientemailverification_withrequests AS + SELECT "ClientEmailVerification"."createdAt", + "ClientEmailVerificationRequest"."clientId", + "ClientEmailVerificationRequest".email, + "ClientEmailVerification"."verificationRequestId", + "ClientEmailVerificationRequest".code + FROM (public."ClientEmailVerification" + LEFT JOIN public."ClientEmailVerificationRequest" ON (("ClientEmailVerification"."verificationRequestId" = "ClientEmailVerificationRequest".id))); + + +ALTER TABLE public.clientemailverification_withrequests OWNER TO postgres; + -- -- Name: buckets; Type: TABLE; Schema: storage; Owner: supabase_storage_admin -- @@ -3807,6 +3823,15 @@ GRANT ALL ON TABLE public."Wallpaper_random" TO authenticated; GRANT ALL ON TABLE public."Wallpaper_random" TO service_role; +-- +-- Name: TABLE clientemailverification_withrequests; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON TABLE public.clientemailverification_withrequests TO anon; +GRANT ALL ON TABLE public.clientemailverification_withrequests TO authenticated; +GRANT ALL ON TABLE public.clientemailverification_withrequests TO service_role; + + -- -- Name: TABLE buckets; Type: ACL; Schema: storage; Owner: supabase_storage_admin -- diff --git a/src/utils/supabase/types.ts b/src/utils/supabase/types.ts index 7b811b7ef..f36889442 100644 --- a/src/utils/supabase/types.ts +++ b/src/utils/supabase/types.ts @@ -1,540 +1,564 @@ -export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]; +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] export interface Database { - public: { - Tables: { - Client: { - Row: { - clientId: string; - createdAt: string | null; - email: string | null; - emailPreferences: Json | null; - }; - Insert: { - clientId: string; - createdAt?: string | null; - email?: string | null; - emailPreferences?: Json | null; - }; - Update: { - clientId?: string; - createdAt?: string | null; - email?: string | null; - emailPreferences?: Json | null; - }; - Relationships: []; - }; - ClientEmailVerification: { - Row: { - createdAt: string; - id: number; - verificationRequestId: number; - }; - Insert: { - createdAt?: string; - id?: number; - verificationRequestId: number; - }; - Update: { - createdAt?: string; - id?: number; - verificationRequestId?: number; - }; - Relationships: [ - { - foreignKeyName: 'ClientEmailVerification_verificationRequestId_fkey'; - columns: ['verificationRequestId']; - referencedRelation: 'ClientEmailVerificationRequest'; - referencedColumns: ['id']; - }, - ]; - }; - ClientEmailVerificationRequest: { - Row: { - clientId: string; - code: string; - createdAt: string; - email: string; - id: number; - }; - Insert: { - clientId?: string; - code: string; - createdAt?: string; - email: string; - id?: number; - }; - Update: { - clientId?: string; - code?: string; - createdAt?: string; - email?: string; - id?: number; - }; - Relationships: []; - }; - ImagePromptExecution: { - Row: { - clientId: string | null; - createdAt: string; - id: number; - prompt: Json | null; - promptAt: string | null; - promptContent: string | null; - ptbkUrl: string | null; - rawResponse: Json | null; - result: Json | null; - resultAt: string | null; - resultSrc: string | null; - usedModel: string | null; - }; - Insert: { - clientId?: string | null; - createdAt?: string; - id?: number; - prompt?: Json | null; - promptAt?: string | null; - promptContent?: string | null; - ptbkUrl?: string | null; - rawResponse?: Json | null; - result?: Json | null; - resultAt?: string | null; - resultSrc?: string | null; - usedModel?: string | null; - }; - Update: { - clientId?: string | null; - createdAt?: string; - id?: number; - prompt?: Json | null; - promptAt?: string | null; - promptContent?: string | null; - ptbkUrl?: string | null; - rawResponse?: Json | null; - result?: Json | null; - resultAt?: string | null; - resultSrc?: string | null; - usedModel?: string | null; - }; - Relationships: []; - }; - Prompt: { - Row: { - answer: string | null; - answerAt: string | null; - clientId: string | null; - createdAt: string; - externalId: string | null; - fullCompletion: Json | null; - id: number; - metadata: Json | null; - model: string | null; - modelSettings: Json | null; - previousExternalId: string | null; - prompt: string | null; - promptAt: string | null; - systemMessage: string | null; - type: string | null; - }; - Insert: { - answer?: string | null; - answerAt?: string | null; - clientId?: string | null; - createdAt?: string; - externalId?: string | null; - fullCompletion?: Json | null; - id?: number; - metadata?: Json | null; - model?: string | null; - modelSettings?: Json | null; - previousExternalId?: string | null; - prompt?: string | null; - promptAt?: string | null; - systemMessage?: string | null; - type?: string | null; - }; - Update: { - answer?: string | null; - answerAt?: string | null; - clientId?: string | null; - createdAt?: string; - externalId?: string | null; - fullCompletion?: Json | null; - id?: number; - metadata?: Json | null; - model?: string | null; - modelSettings?: Json | null; - previousExternalId?: string | null; - prompt?: string | null; - promptAt?: string | null; - systemMessage?: string | null; - type?: string | null; - }; - Relationships: []; - }; - PromptbookFeedback: { - Row: { - clientId: string | null; - createdAt: string; - defaultValue: string | null; - id: number; - likedStatus: string | null; - note: string | null; - value: string | null; - }; - Insert: { - clientId?: string | null; - createdAt?: string; - defaultValue?: string | null; - id?: number; - likedStatus?: string | null; - note?: string | null; - value?: string | null; - }; - Update: { - clientId?: string | null; - createdAt?: string; - defaultValue?: string | null; - id?: number; - likedStatus?: string | null; - note?: string | null; - value?: string | null; - }; - Relationships: []; - }; - PromptExecution: { - Row: { - clientId: string | null; - createdAt: string; - id: number; - promptAt: string | null; - promptContent: string | null; - promptModelRequirements: Json | null; - promptParameters: Json | null; - ptpUrl: string | null; - rawResponse: Json | null; - resultAt: string | null; - resultContent: string | null; - usedModel: string | null; - }; - Insert: { - clientId?: string | null; - createdAt?: string; - id?: number; - promptAt?: string | null; - promptContent?: string | null; - promptModelRequirements?: Json | null; - promptParameters?: Json | null; - ptpUrl?: string | null; - rawResponse?: Json | null; - resultAt?: string | null; - resultContent?: string | null; - usedModel?: string | null; - }; - Update: { - clientId?: string | null; - createdAt?: string; - id?: number; - promptAt?: string | null; - promptContent?: string | null; - promptModelRequirements?: Json | null; - promptParameters?: Json | null; - ptpUrl?: string | null; - rawResponse?: Json | null; - resultAt?: string | null; - resultContent?: string | null; - usedModel?: string | null; - }; - Relationships: []; - }; - Site: { - Row: { - author: string | null; - createdAt: string | null; - id: number; - note: string | null; - ownerEmail: string | null; - plan: string | null; - url: string | null; - wallpaperId: string | null; - }; - Insert: { - author?: string | null; - createdAt?: string | null; - id?: number; - note?: string | null; - ownerEmail?: string | null; - plan?: string | null; - url?: string | null; - wallpaperId?: string | null; - }; - Update: { - author?: string | null; - createdAt?: string | null; - id?: number; - note?: string | null; - ownerEmail?: string | null; - plan?: string | null; - url?: string | null; - wallpaperId?: string | null; - }; - Relationships: []; - }; - SupportRequest: { - Row: { - author: string | null; - createdAt: string | null; - from: string | null; - id: number; - isSolved: boolean | null; - message: string | null; - note: string | null; - }; - Insert: { - author?: string | null; - createdAt?: string | null; - from?: string | null; - id?: number; - isSolved?: boolean | null; - message?: string | null; - note?: string | null; - }; - Update: { - author?: string | null; - createdAt?: string | null; - from?: string | null; - id?: number; - isSolved?: boolean | null; - message?: string | null; - note?: string | null; - }; - Relationships: []; - }; - Value: { - Row: { - createdAt: string; - id: number; - key: string | null; - note: string | null; - validUntil: string | null; - value: Json | null; - }; - Insert: { - createdAt?: string; - id?: number; - key?: string | null; - note?: string | null; - validUntil?: string | null; - value?: Json | null; - }; - Update: { - createdAt?: string; - id?: number; - key?: string | null; - note?: string | null; - validUntil?: string | null; - value?: Json | null; - }; - Relationships: []; - }; - Wallpaper: { - Row: { - author: string; - colorStats: Json | null; - content: string; - createdAt: string; - id: string; - isPublic: boolean; - keywords: string[] | null; - naturalSize: Json | null; - parent: string | null; - prompt: string | null; - src: string; - title: string; - }; - Insert: { - author: string; - colorStats?: Json | null; - content: string; - createdAt?: string; - id: string; - isPublic?: boolean; - keywords?: string[] | null; - naturalSize?: Json | null; - parent?: string | null; - prompt?: string | null; - src: string; - title: string; - }; - Update: { - author?: string; - colorStats?: Json | null; - content?: string; - createdAt?: string; - id?: string; - isPublic?: boolean; - keywords?: string[] | null; - naturalSize?: Json | null; - parent?: string | null; - prompt?: string | null; - src?: string; - title?: string; - }; - Relationships: []; - }; - WallpaperFeedback: { - Row: { - author: string; - createdAt: string; - likedStatus: string; - note: string | null; - wallpaperId: string; - }; - Insert: { - author: string; - createdAt?: string; - likedStatus: string; - note?: string | null; - wallpaperId: string; - }; - Update: { - author?: string; - createdAt?: string; - likedStatus?: string; - note?: string | null; - wallpaperId?: string; - }; - Relationships: [ - { - foreignKeyName: 'WallpaperFeedback_wallpaperId_fkey'; - columns: ['wallpaperId']; - referencedRelation: 'Wallpaper'; - referencedColumns: ['id']; - }, - { - foreignKeyName: 'WallpaperFeedback_wallpaperId_fkey'; - columns: ['wallpaperId']; - referencedRelation: 'Wallpaper_random'; - referencedColumns: ['id']; - }, - ]; - }; - }; - Views: { - Prompt_stats: { - Row: { - answer: string | null; - answerAt: string | null; - clientId: string | null; - createdAt: string | null; - duration: unknown | null; - externalId: string | null; - fullCompletion: Json | null; - id: number | null; - metadata: Json | null; - model: string | null; - modelSettings: Json | null; - nonce: number | null; - previousExternalId: string | null; - prompt: string | null; - promptAt: string | null; - systemMessage: string | null; - type: string | null; - }; - Insert: { - answer?: string | null; - answerAt?: string | null; - clientId?: string | null; - createdAt?: string | null; - duration?: never; - externalId?: string | null; - fullCompletion?: Json | null; - id?: number | null; - metadata?: Json | null; - model?: string | null; - modelSettings?: Json | null; - nonce?: never; - previousExternalId?: string | null; - prompt?: string | null; - promptAt?: string | null; - systemMessage?: string | null; - type?: string | null; - }; - Update: { - answer?: string | null; - answerAt?: string | null; - clientId?: string | null; - createdAt?: string | null; - duration?: never; - externalId?: string | null; - fullCompletion?: Json | null; - id?: number | null; - metadata?: Json | null; - model?: string | null; - modelSettings?: Json | null; - nonce?: never; - previousExternalId?: string | null; - prompt?: string | null; - promptAt?: string | null; - systemMessage?: string | null; - type?: string | null; - }; - Relationships: []; - }; - Wallpaper_random: { - Row: { - author: string | null; - colorStats: Json | null; - content: string | null; - createdAt: string | null; - id: string | null; - isPublic: boolean | null; - keywords: string[] | null; - nonce: number | null; - parent: string | null; - prompt: string | null; - src: string | null; - title: string | null; - }; - Insert: { - author?: string | null; - colorStats?: Json | null; - content?: string | null; - createdAt?: string | null; - id?: string | null; - isPublic?: boolean | null; - keywords?: string[] | null; - nonce?: never; - parent?: string | null; - prompt?: string | null; - src?: string | null; - title?: string | null; - }; - Update: { - author?: string | null; - colorStats?: Json | null; - content?: string | null; - createdAt?: string | null; - id?: string | null; - isPublic?: boolean | null; - keywords?: string[] | null; - nonce?: never; - parent?: string | null; - prompt?: string | null; - src?: string | null; - title?: string | null; - }; - Relationships: []; - }; - }; - Functions: { - [_ in never]: never; - }; - Enums: { - [_ in never]: never; - }; - CompositeTypes: { - [_ in never]: never; - }; - }; + public: { + Tables: { + Client: { + Row: { + clientId: string + createdAt: string | null + email: string | null + emailPreferences: Json | null + } + Insert: { + clientId: string + createdAt?: string | null + email?: string | null + emailPreferences?: Json | null + } + Update: { + clientId?: string + createdAt?: string | null + email?: string | null + emailPreferences?: Json | null + } + Relationships: [] + } + ClientEmailVerification: { + Row: { + createdAt: string + id: number + verificationRequestId: number + } + Insert: { + createdAt?: string + id?: number + verificationRequestId: number + } + Update: { + createdAt?: string + id?: number + verificationRequestId?: number + } + Relationships: [ + { + foreignKeyName: "ClientEmailVerification_verificationRequestId_fkey" + columns: ["verificationRequestId"] + referencedRelation: "ClientEmailVerificationRequest" + referencedColumns: ["id"] + } + ] + } + ClientEmailVerificationRequest: { + Row: { + clientId: string + code: string + createdAt: string + email: string + id: number + } + Insert: { + clientId?: string + code: string + createdAt?: string + email: string + id?: number + } + Update: { + clientId?: string + code?: string + createdAt?: string + email?: string + id?: number + } + Relationships: [] + } + ImagePromptExecution: { + Row: { + clientId: string | null + createdAt: string + id: number + prompt: Json | null + promptAt: string | null + promptContent: string | null + ptbkUrl: string | null + rawResponse: Json | null + result: Json | null + resultAt: string | null + resultSrc: string | null + usedModel: string | null + } + Insert: { + clientId?: string | null + createdAt?: string + id?: number + prompt?: Json | null + promptAt?: string | null + promptContent?: string | null + ptbkUrl?: string | null + rawResponse?: Json | null + result?: Json | null + resultAt?: string | null + resultSrc?: string | null + usedModel?: string | null + } + Update: { + clientId?: string | null + createdAt?: string + id?: number + prompt?: Json | null + promptAt?: string | null + promptContent?: string | null + ptbkUrl?: string | null + rawResponse?: Json | null + result?: Json | null + resultAt?: string | null + resultSrc?: string | null + usedModel?: string | null + } + Relationships: [] + } + Prompt: { + Row: { + answer: string | null + answerAt: string | null + clientId: string | null + createdAt: string + externalId: string | null + fullCompletion: Json | null + id: number + metadata: Json | null + model: string | null + modelSettings: Json | null + previousExternalId: string | null + prompt: string | null + promptAt: string | null + systemMessage: string | null + type: string | null + } + Insert: { + answer?: string | null + answerAt?: string | null + clientId?: string | null + createdAt?: string + externalId?: string | null + fullCompletion?: Json | null + id?: number + metadata?: Json | null + model?: string | null + modelSettings?: Json | null + previousExternalId?: string | null + prompt?: string | null + promptAt?: string | null + systemMessage?: string | null + type?: string | null + } + Update: { + answer?: string | null + answerAt?: string | null + clientId?: string | null + createdAt?: string + externalId?: string | null + fullCompletion?: Json | null + id?: number + metadata?: Json | null + model?: string | null + modelSettings?: Json | null + previousExternalId?: string | null + prompt?: string | null + promptAt?: string | null + systemMessage?: string | null + type?: string | null + } + Relationships: [] + } + PromptbookFeedback: { + Row: { + clientId: string | null + createdAt: string + defaultValue: string | null + id: number + likedStatus: string | null + note: string | null + value: string | null + } + Insert: { + clientId?: string | null + createdAt?: string + defaultValue?: string | null + id?: number + likedStatus?: string | null + note?: string | null + value?: string | null + } + Update: { + clientId?: string | null + createdAt?: string + defaultValue?: string | null + id?: number + likedStatus?: string | null + note?: string | null + value?: string | null + } + Relationships: [] + } + PromptExecution: { + Row: { + clientId: string | null + createdAt: string + id: number + promptAt: string | null + promptContent: string | null + promptModelRequirements: Json | null + promptParameters: Json | null + ptpUrl: string | null + rawResponse: Json | null + resultAt: string | null + resultContent: string | null + usedModel: string | null + } + Insert: { + clientId?: string | null + createdAt?: string + id?: number + promptAt?: string | null + promptContent?: string | null + promptModelRequirements?: Json | null + promptParameters?: Json | null + ptpUrl?: string | null + rawResponse?: Json | null + resultAt?: string | null + resultContent?: string | null + usedModel?: string | null + } + Update: { + clientId?: string | null + createdAt?: string + id?: number + promptAt?: string | null + promptContent?: string | null + promptModelRequirements?: Json | null + promptParameters?: Json | null + ptpUrl?: string | null + rawResponse?: Json | null + resultAt?: string | null + resultContent?: string | null + usedModel?: string | null + } + Relationships: [] + } + Site: { + Row: { + author: string | null + createdAt: string | null + id: number + note: string | null + ownerEmail: string | null + plan: string | null + url: string | null + wallpaperId: string | null + } + Insert: { + author?: string | null + createdAt?: string | null + id?: number + note?: string | null + ownerEmail?: string | null + plan?: string | null + url?: string | null + wallpaperId?: string | null + } + Update: { + author?: string | null + createdAt?: string | null + id?: number + note?: string | null + ownerEmail?: string | null + plan?: string | null + url?: string | null + wallpaperId?: string | null + } + Relationships: [] + } + SupportRequest: { + Row: { + author: string | null + createdAt: string | null + from: string | null + id: number + isSolved: boolean | null + message: string | null + note: string | null + } + Insert: { + author?: string | null + createdAt?: string | null + from?: string | null + id?: number + isSolved?: boolean | null + message?: string | null + note?: string | null + } + Update: { + author?: string | null + createdAt?: string | null + from?: string | null + id?: number + isSolved?: boolean | null + message?: string | null + note?: string | null + } + Relationships: [] + } + Value: { + Row: { + createdAt: string + id: number + key: string | null + note: string | null + validUntil: string | null + value: Json | null + } + Insert: { + createdAt?: string + id?: number + key?: string | null + note?: string | null + validUntil?: string | null + value?: Json | null + } + Update: { + createdAt?: string + id?: number + key?: string | null + note?: string | null + validUntil?: string | null + value?: Json | null + } + Relationships: [] + } + Wallpaper: { + Row: { + author: string + colorStats: Json | null + content: string + createdAt: string + id: string + isPublic: boolean + keywords: string[] | null + naturalSize: Json | null + parent: string | null + prompt: string | null + src: string + title: string + } + Insert: { + author: string + colorStats?: Json | null + content: string + createdAt?: string + id: string + isPublic?: boolean + keywords?: string[] | null + naturalSize?: Json | null + parent?: string | null + prompt?: string | null + src: string + title: string + } + Update: { + author?: string + colorStats?: Json | null + content?: string + createdAt?: string + id?: string + isPublic?: boolean + keywords?: string[] | null + naturalSize?: Json | null + parent?: string | null + prompt?: string | null + src?: string + title?: string + } + Relationships: [] + } + WallpaperFeedback: { + Row: { + author: string + createdAt: string + likedStatus: string + note: string | null + wallpaperId: string + } + Insert: { + author: string + createdAt?: string + likedStatus: string + note?: string | null + wallpaperId: string + } + Update: { + author?: string + createdAt?: string + likedStatus?: string + note?: string | null + wallpaperId?: string + } + Relationships: [ + { + foreignKeyName: "WallpaperFeedback_wallpaperId_fkey" + columns: ["wallpaperId"] + referencedRelation: "Wallpaper" + referencedColumns: ["id"] + }, + { + foreignKeyName: "WallpaperFeedback_wallpaperId_fkey" + columns: ["wallpaperId"] + referencedRelation: "Wallpaper_random" + referencedColumns: ["id"] + } + ] + } + } + Views: { + clientemailverification_withrequests: { + Row: { + clientId: string | null + code: string | null + createdAt: string | null + email: string | null + verificationRequestId: number | null + } + Relationships: [ + { + foreignKeyName: "ClientEmailVerification_verificationRequestId_fkey" + columns: ["verificationRequestId"] + referencedRelation: "ClientEmailVerificationRequest" + referencedColumns: ["id"] + } + ] + } + Prompt_stats: { + Row: { + answer: string | null + answerAt: string | null + clientId: string | null + createdAt: string | null + duration: unknown | null + externalId: string | null + fullCompletion: Json | null + id: number | null + metadata: Json | null + model: string | null + modelSettings: Json | null + nonce: number | null + previousExternalId: string | null + prompt: string | null + promptAt: string | null + systemMessage: string | null + type: string | null + } + Insert: { + answer?: string | null + answerAt?: string | null + clientId?: string | null + createdAt?: string | null + duration?: never + externalId?: string | null + fullCompletion?: Json | null + id?: number | null + metadata?: Json | null + model?: string | null + modelSettings?: Json | null + nonce?: never + previousExternalId?: string | null + prompt?: string | null + promptAt?: string | null + systemMessage?: string | null + type?: string | null + } + Update: { + answer?: string | null + answerAt?: string | null + clientId?: string | null + createdAt?: string | null + duration?: never + externalId?: string | null + fullCompletion?: Json | null + id?: number | null + metadata?: Json | null + model?: string | null + modelSettings?: Json | null + nonce?: never + previousExternalId?: string | null + prompt?: string | null + promptAt?: string | null + systemMessage?: string | null + type?: string | null + } + Relationships: [] + } + Wallpaper_random: { + Row: { + author: string | null + colorStats: Json | null + content: string | null + createdAt: string | null + id: string | null + isPublic: boolean | null + keywords: string[] | null + nonce: number | null + parent: string | null + prompt: string | null + src: string | null + title: string | null + } + Insert: { + author?: string | null + colorStats?: Json | null + content?: string | null + createdAt?: string | null + id?: string | null + isPublic?: boolean | null + keywords?: string[] | null + nonce?: never + parent?: string | null + prompt?: string | null + src?: string | null + title?: string | null + } + Update: { + author?: string | null + colorStats?: Json | null + content?: string | null + createdAt?: string | null + id?: string | null + isPublic?: boolean | null + keywords?: string[] | null + nonce?: never + parent?: string | null + prompt?: string | null + src?: string | null + title?: string | null + } + Relationships: [] + } + } + Functions: { + [_ in never]: never + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } } + From bdbfdc5f29d00cb528a0846eb06822492f807d54 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:24:01 +0100 Subject: [PATCH 22/48] Working on $isClientVerifiedForServer --- src/utils/client/isClientVerifiedForServer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/client/isClientVerifiedForServer.ts b/src/utils/client/isClientVerifiedForServer.ts index e0079df04..eb714c2a0 100644 --- a/src/utils/client/isClientVerifiedForServer.ts +++ b/src/utils/client/isClientVerifiedForServer.ts @@ -17,8 +17,8 @@ export async function $isClientVerifiedForServer(options: IsClientVerifiedReques const { clientId } = options; const { data: verificationRequests } = await getSupabaseForServer() - .from('ClientEmailVerificationRequest') - .select('email') + .from('ClientEmailVerification_withRequests') + .select('id') .eq('clientId', clientId); /* From 2ebb4a3c3d5bb590b9352ce882c90b5bea7881d8 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:26:10 +0100 Subject: [PATCH 23/48] Add view ClientEmailVerification_withRequests --- database/dumps/structure.dump.pgsql | 50 +- src/utils/supabase/types.ts | 1115 +++++++++++++-------------- 2 files changed, 579 insertions(+), 586 deletions(-) diff --git a/database/dumps/structure.dump.pgsql b/database/dumps/structure.dump.pgsql index c8277e04a..3a9b83b16 100644 --- a/database/dumps/structure.dump.pgsql +++ b/database/dumps/structure.dump.pgsql @@ -1386,6 +1386,22 @@ ALTER TABLE public."ClientEmailVerification" ALTER COLUMN id ADD GENERATED BY DE ); +-- +-- Name: ClientEmailVerification_withRequests; Type: VIEW; Schema: public; Owner: postgres +-- + +CREATE VIEW public."ClientEmailVerification_withRequests" AS + SELECT "ClientEmailVerification"."createdAt", + "ClientEmailVerificationRequest"."clientId", + "ClientEmailVerificationRequest".email, + "ClientEmailVerification"."verificationRequestId", + "ClientEmailVerificationRequest".code + FROM (public."ClientEmailVerification" + LEFT JOIN public."ClientEmailVerificationRequest" ON (("ClientEmailVerification"."verificationRequestId" = "ClientEmailVerificationRequest".id))); + + +ALTER TABLE public."ClientEmailVerification_withRequests" OWNER TO postgres; + -- -- Name: PromptbookFeedback; Type: TABLE; Schema: public; Owner: postgres -- @@ -1772,22 +1788,6 @@ CREATE VIEW public."Wallpaper_random" AS ALTER TABLE public."Wallpaper_random" OWNER TO postgres; --- --- Name: clientemailverification_withrequests; Type: VIEW; Schema: public; Owner: postgres --- - -CREATE VIEW public.clientemailverification_withrequests AS - SELECT "ClientEmailVerification"."createdAt", - "ClientEmailVerificationRequest"."clientId", - "ClientEmailVerificationRequest".email, - "ClientEmailVerification"."verificationRequestId", - "ClientEmailVerificationRequest".code - FROM (public."ClientEmailVerification" - LEFT JOIN public."ClientEmailVerificationRequest" ON (("ClientEmailVerification"."verificationRequestId" = "ClientEmailVerificationRequest".id))); - - -ALTER TABLE public.clientemailverification_withrequests OWNER TO postgres; - -- -- Name: buckets; Type: TABLE; Schema: storage; Owner: supabase_storage_admin -- @@ -3661,6 +3661,15 @@ GRANT ALL ON SEQUENCE public."ClientEmailVerification_id_seq" TO authenticated; GRANT ALL ON SEQUENCE public."ClientEmailVerification_id_seq" TO service_role; +-- +-- Name: TABLE "ClientEmailVerification_withRequests"; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON TABLE public."ClientEmailVerification_withRequests" TO anon; +GRANT ALL ON TABLE public."ClientEmailVerification_withRequests" TO authenticated; +GRANT ALL ON TABLE public."ClientEmailVerification_withRequests" TO service_role; + + -- -- Name: TABLE "PromptbookFeedback"; Type: ACL; Schema: public; Owner: postgres -- @@ -3823,15 +3832,6 @@ GRANT ALL ON TABLE public."Wallpaper_random" TO authenticated; GRANT ALL ON TABLE public."Wallpaper_random" TO service_role; --- --- Name: TABLE clientemailverification_withrequests; Type: ACL; Schema: public; Owner: postgres --- - -GRANT ALL ON TABLE public.clientemailverification_withrequests TO anon; -GRANT ALL ON TABLE public.clientemailverification_withrequests TO authenticated; -GRANT ALL ON TABLE public.clientemailverification_withrequests TO service_role; - - -- -- Name: TABLE buckets; Type: ACL; Schema: storage; Owner: supabase_storage_admin -- diff --git a/src/utils/supabase/types.ts b/src/utils/supabase/types.ts index f36889442..7b2c314ee 100644 --- a/src/utils/supabase/types.ts +++ b/src/utils/supabase/types.ts @@ -1,564 +1,557 @@ -export type Json = - | string - | number - | boolean - | null - | { [key: string]: Json | undefined } - | Json[] +export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]; export interface Database { - public: { - Tables: { - Client: { - Row: { - clientId: string - createdAt: string | null - email: string | null - emailPreferences: Json | null - } - Insert: { - clientId: string - createdAt?: string | null - email?: string | null - emailPreferences?: Json | null - } - Update: { - clientId?: string - createdAt?: string | null - email?: string | null - emailPreferences?: Json | null - } - Relationships: [] - } - ClientEmailVerification: { - Row: { - createdAt: string - id: number - verificationRequestId: number - } - Insert: { - createdAt?: string - id?: number - verificationRequestId: number - } - Update: { - createdAt?: string - id?: number - verificationRequestId?: number - } - Relationships: [ - { - foreignKeyName: "ClientEmailVerification_verificationRequestId_fkey" - columns: ["verificationRequestId"] - referencedRelation: "ClientEmailVerificationRequest" - referencedColumns: ["id"] - } - ] - } - ClientEmailVerificationRequest: { - Row: { - clientId: string - code: string - createdAt: string - email: string - id: number - } - Insert: { - clientId?: string - code: string - createdAt?: string - email: string - id?: number - } - Update: { - clientId?: string - code?: string - createdAt?: string - email?: string - id?: number - } - Relationships: [] - } - ImagePromptExecution: { - Row: { - clientId: string | null - createdAt: string - id: number - prompt: Json | null - promptAt: string | null - promptContent: string | null - ptbkUrl: string | null - rawResponse: Json | null - result: Json | null - resultAt: string | null - resultSrc: string | null - usedModel: string | null - } - Insert: { - clientId?: string | null - createdAt?: string - id?: number - prompt?: Json | null - promptAt?: string | null - promptContent?: string | null - ptbkUrl?: string | null - rawResponse?: Json | null - result?: Json | null - resultAt?: string | null - resultSrc?: string | null - usedModel?: string | null - } - Update: { - clientId?: string | null - createdAt?: string - id?: number - prompt?: Json | null - promptAt?: string | null - promptContent?: string | null - ptbkUrl?: string | null - rawResponse?: Json | null - result?: Json | null - resultAt?: string | null - resultSrc?: string | null - usedModel?: string | null - } - Relationships: [] - } - Prompt: { - Row: { - answer: string | null - answerAt: string | null - clientId: string | null - createdAt: string - externalId: string | null - fullCompletion: Json | null - id: number - metadata: Json | null - model: string | null - modelSettings: Json | null - previousExternalId: string | null - prompt: string | null - promptAt: string | null - systemMessage: string | null - type: string | null - } - Insert: { - answer?: string | null - answerAt?: string | null - clientId?: string | null - createdAt?: string - externalId?: string | null - fullCompletion?: Json | null - id?: number - metadata?: Json | null - model?: string | null - modelSettings?: Json | null - previousExternalId?: string | null - prompt?: string | null - promptAt?: string | null - systemMessage?: string | null - type?: string | null - } - Update: { - answer?: string | null - answerAt?: string | null - clientId?: string | null - createdAt?: string - externalId?: string | null - fullCompletion?: Json | null - id?: number - metadata?: Json | null - model?: string | null - modelSettings?: Json | null - previousExternalId?: string | null - prompt?: string | null - promptAt?: string | null - systemMessage?: string | null - type?: string | null - } - Relationships: [] - } - PromptbookFeedback: { - Row: { - clientId: string | null - createdAt: string - defaultValue: string | null - id: number - likedStatus: string | null - note: string | null - value: string | null - } - Insert: { - clientId?: string | null - createdAt?: string - defaultValue?: string | null - id?: number - likedStatus?: string | null - note?: string | null - value?: string | null - } - Update: { - clientId?: string | null - createdAt?: string - defaultValue?: string | null - id?: number - likedStatus?: string | null - note?: string | null - value?: string | null - } - Relationships: [] - } - PromptExecution: { - Row: { - clientId: string | null - createdAt: string - id: number - promptAt: string | null - promptContent: string | null - promptModelRequirements: Json | null - promptParameters: Json | null - ptpUrl: string | null - rawResponse: Json | null - resultAt: string | null - resultContent: string | null - usedModel: string | null - } - Insert: { - clientId?: string | null - createdAt?: string - id?: number - promptAt?: string | null - promptContent?: string | null - promptModelRequirements?: Json | null - promptParameters?: Json | null - ptpUrl?: string | null - rawResponse?: Json | null - resultAt?: string | null - resultContent?: string | null - usedModel?: string | null - } - Update: { - clientId?: string | null - createdAt?: string - id?: number - promptAt?: string | null - promptContent?: string | null - promptModelRequirements?: Json | null - promptParameters?: Json | null - ptpUrl?: string | null - rawResponse?: Json | null - resultAt?: string | null - resultContent?: string | null - usedModel?: string | null - } - Relationships: [] - } - Site: { - Row: { - author: string | null - createdAt: string | null - id: number - note: string | null - ownerEmail: string | null - plan: string | null - url: string | null - wallpaperId: string | null - } - Insert: { - author?: string | null - createdAt?: string | null - id?: number - note?: string | null - ownerEmail?: string | null - plan?: string | null - url?: string | null - wallpaperId?: string | null - } - Update: { - author?: string | null - createdAt?: string | null - id?: number - note?: string | null - ownerEmail?: string | null - plan?: string | null - url?: string | null - wallpaperId?: string | null - } - Relationships: [] - } - SupportRequest: { - Row: { - author: string | null - createdAt: string | null - from: string | null - id: number - isSolved: boolean | null - message: string | null - note: string | null - } - Insert: { - author?: string | null - createdAt?: string | null - from?: string | null - id?: number - isSolved?: boolean | null - message?: string | null - note?: string | null - } - Update: { - author?: string | null - createdAt?: string | null - from?: string | null - id?: number - isSolved?: boolean | null - message?: string | null - note?: string | null - } - Relationships: [] - } - Value: { - Row: { - createdAt: string - id: number - key: string | null - note: string | null - validUntil: string | null - value: Json | null - } - Insert: { - createdAt?: string - id?: number - key?: string | null - note?: string | null - validUntil?: string | null - value?: Json | null - } - Update: { - createdAt?: string - id?: number - key?: string | null - note?: string | null - validUntil?: string | null - value?: Json | null - } - Relationships: [] - } - Wallpaper: { - Row: { - author: string - colorStats: Json | null - content: string - createdAt: string - id: string - isPublic: boolean - keywords: string[] | null - naturalSize: Json | null - parent: string | null - prompt: string | null - src: string - title: string - } - Insert: { - author: string - colorStats?: Json | null - content: string - createdAt?: string - id: string - isPublic?: boolean - keywords?: string[] | null - naturalSize?: Json | null - parent?: string | null - prompt?: string | null - src: string - title: string - } - Update: { - author?: string - colorStats?: Json | null - content?: string - createdAt?: string - id?: string - isPublic?: boolean - keywords?: string[] | null - naturalSize?: Json | null - parent?: string | null - prompt?: string | null - src?: string - title?: string - } - Relationships: [] - } - WallpaperFeedback: { - Row: { - author: string - createdAt: string - likedStatus: string - note: string | null - wallpaperId: string - } - Insert: { - author: string - createdAt?: string - likedStatus: string - note?: string | null - wallpaperId: string - } - Update: { - author?: string - createdAt?: string - likedStatus?: string - note?: string | null - wallpaperId?: string - } - Relationships: [ - { - foreignKeyName: "WallpaperFeedback_wallpaperId_fkey" - columns: ["wallpaperId"] - referencedRelation: "Wallpaper" - referencedColumns: ["id"] - }, - { - foreignKeyName: "WallpaperFeedback_wallpaperId_fkey" - columns: ["wallpaperId"] - referencedRelation: "Wallpaper_random" - referencedColumns: ["id"] - } - ] - } - } - Views: { - clientemailverification_withrequests: { - Row: { - clientId: string | null - code: string | null - createdAt: string | null - email: string | null - verificationRequestId: number | null - } - Relationships: [ - { - foreignKeyName: "ClientEmailVerification_verificationRequestId_fkey" - columns: ["verificationRequestId"] - referencedRelation: "ClientEmailVerificationRequest" - referencedColumns: ["id"] - } - ] - } - Prompt_stats: { - Row: { - answer: string | null - answerAt: string | null - clientId: string | null - createdAt: string | null - duration: unknown | null - externalId: string | null - fullCompletion: Json | null - id: number | null - metadata: Json | null - model: string | null - modelSettings: Json | null - nonce: number | null - previousExternalId: string | null - prompt: string | null - promptAt: string | null - systemMessage: string | null - type: string | null - } - Insert: { - answer?: string | null - answerAt?: string | null - clientId?: string | null - createdAt?: string | null - duration?: never - externalId?: string | null - fullCompletion?: Json | null - id?: number | null - metadata?: Json | null - model?: string | null - modelSettings?: Json | null - nonce?: never - previousExternalId?: string | null - prompt?: string | null - promptAt?: string | null - systemMessage?: string | null - type?: string | null - } - Update: { - answer?: string | null - answerAt?: string | null - clientId?: string | null - createdAt?: string | null - duration?: never - externalId?: string | null - fullCompletion?: Json | null - id?: number | null - metadata?: Json | null - model?: string | null - modelSettings?: Json | null - nonce?: never - previousExternalId?: string | null - prompt?: string | null - promptAt?: string | null - systemMessage?: string | null - type?: string | null - } - Relationships: [] - } - Wallpaper_random: { - Row: { - author: string | null - colorStats: Json | null - content: string | null - createdAt: string | null - id: string | null - isPublic: boolean | null - keywords: string[] | null - nonce: number | null - parent: string | null - prompt: string | null - src: string | null - title: string | null - } - Insert: { - author?: string | null - colorStats?: Json | null - content?: string | null - createdAt?: string | null - id?: string | null - isPublic?: boolean | null - keywords?: string[] | null - nonce?: never - parent?: string | null - prompt?: string | null - src?: string | null - title?: string | null - } - Update: { - author?: string | null - colorStats?: Json | null - content?: string | null - createdAt?: string | null - id?: string | null - isPublic?: boolean | null - keywords?: string[] | null - nonce?: never - parent?: string | null - prompt?: string | null - src?: string | null - title?: string | null - } - Relationships: [] - } - } - Functions: { - [_ in never]: never - } - Enums: { - [_ in never]: never - } - CompositeTypes: { - [_ in never]: never - } - } + public: { + Tables: { + Client: { + Row: { + clientId: string; + createdAt: string | null; + email: string | null; + emailPreferences: Json | null; + }; + Insert: { + clientId: string; + createdAt?: string | null; + email?: string | null; + emailPreferences?: Json | null; + }; + Update: { + clientId?: string; + createdAt?: string | null; + email?: string | null; + emailPreferences?: Json | null; + }; + Relationships: []; + }; + ClientEmailVerification: { + Row: { + createdAt: string; + id: number; + verificationRequestId: number; + }; + Insert: { + createdAt?: string; + id?: number; + verificationRequestId: number; + }; + Update: { + createdAt?: string; + id?: number; + verificationRequestId?: number; + }; + Relationships: [ + { + foreignKeyName: 'ClientEmailVerification_verificationRequestId_fkey'; + columns: ['verificationRequestId']; + referencedRelation: 'ClientEmailVerificationRequest'; + referencedColumns: ['id']; + }, + ]; + }; + ClientEmailVerificationRequest: { + Row: { + clientId: string; + code: string; + createdAt: string; + email: string; + id: number; + }; + Insert: { + clientId?: string; + code: string; + createdAt?: string; + email: string; + id?: number; + }; + Update: { + clientId?: string; + code?: string; + createdAt?: string; + email?: string; + id?: number; + }; + Relationships: []; + }; + ImagePromptExecution: { + Row: { + clientId: string | null; + createdAt: string; + id: number; + prompt: Json | null; + promptAt: string | null; + promptContent: string | null; + ptbkUrl: string | null; + rawResponse: Json | null; + result: Json | null; + resultAt: string | null; + resultSrc: string | null; + usedModel: string | null; + }; + Insert: { + clientId?: string | null; + createdAt?: string; + id?: number; + prompt?: Json | null; + promptAt?: string | null; + promptContent?: string | null; + ptbkUrl?: string | null; + rawResponse?: Json | null; + result?: Json | null; + resultAt?: string | null; + resultSrc?: string | null; + usedModel?: string | null; + }; + Update: { + clientId?: string | null; + createdAt?: string; + id?: number; + prompt?: Json | null; + promptAt?: string | null; + promptContent?: string | null; + ptbkUrl?: string | null; + rawResponse?: Json | null; + result?: Json | null; + resultAt?: string | null; + resultSrc?: string | null; + usedModel?: string | null; + }; + Relationships: []; + }; + Prompt: { + Row: { + answer: string | null; + answerAt: string | null; + clientId: string | null; + createdAt: string; + externalId: string | null; + fullCompletion: Json | null; + id: number; + metadata: Json | null; + model: string | null; + modelSettings: Json | null; + previousExternalId: string | null; + prompt: string | null; + promptAt: string | null; + systemMessage: string | null; + type: string | null; + }; + Insert: { + answer?: string | null; + answerAt?: string | null; + clientId?: string | null; + createdAt?: string; + externalId?: string | null; + fullCompletion?: Json | null; + id?: number; + metadata?: Json | null; + model?: string | null; + modelSettings?: Json | null; + previousExternalId?: string | null; + prompt?: string | null; + promptAt?: string | null; + systemMessage?: string | null; + type?: string | null; + }; + Update: { + answer?: string | null; + answerAt?: string | null; + clientId?: string | null; + createdAt?: string; + externalId?: string | null; + fullCompletion?: Json | null; + id?: number; + metadata?: Json | null; + model?: string | null; + modelSettings?: Json | null; + previousExternalId?: string | null; + prompt?: string | null; + promptAt?: string | null; + systemMessage?: string | null; + type?: string | null; + }; + Relationships: []; + }; + PromptbookFeedback: { + Row: { + clientId: string | null; + createdAt: string; + defaultValue: string | null; + id: number; + likedStatus: string | null; + note: string | null; + value: string | null; + }; + Insert: { + clientId?: string | null; + createdAt?: string; + defaultValue?: string | null; + id?: number; + likedStatus?: string | null; + note?: string | null; + value?: string | null; + }; + Update: { + clientId?: string | null; + createdAt?: string; + defaultValue?: string | null; + id?: number; + likedStatus?: string | null; + note?: string | null; + value?: string | null; + }; + Relationships: []; + }; + PromptExecution: { + Row: { + clientId: string | null; + createdAt: string; + id: number; + promptAt: string | null; + promptContent: string | null; + promptModelRequirements: Json | null; + promptParameters: Json | null; + ptpUrl: string | null; + rawResponse: Json | null; + resultAt: string | null; + resultContent: string | null; + usedModel: string | null; + }; + Insert: { + clientId?: string | null; + createdAt?: string; + id?: number; + promptAt?: string | null; + promptContent?: string | null; + promptModelRequirements?: Json | null; + promptParameters?: Json | null; + ptpUrl?: string | null; + rawResponse?: Json | null; + resultAt?: string | null; + resultContent?: string | null; + usedModel?: string | null; + }; + Update: { + clientId?: string | null; + createdAt?: string; + id?: number; + promptAt?: string | null; + promptContent?: string | null; + promptModelRequirements?: Json | null; + promptParameters?: Json | null; + ptpUrl?: string | null; + rawResponse?: Json | null; + resultAt?: string | null; + resultContent?: string | null; + usedModel?: string | null; + }; + Relationships: []; + }; + Site: { + Row: { + author: string | null; + createdAt: string | null; + id: number; + note: string | null; + ownerEmail: string | null; + plan: string | null; + url: string | null; + wallpaperId: string | null; + }; + Insert: { + author?: string | null; + createdAt?: string | null; + id?: number; + note?: string | null; + ownerEmail?: string | null; + plan?: string | null; + url?: string | null; + wallpaperId?: string | null; + }; + Update: { + author?: string | null; + createdAt?: string | null; + id?: number; + note?: string | null; + ownerEmail?: string | null; + plan?: string | null; + url?: string | null; + wallpaperId?: string | null; + }; + Relationships: []; + }; + SupportRequest: { + Row: { + author: string | null; + createdAt: string | null; + from: string | null; + id: number; + isSolved: boolean | null; + message: string | null; + note: string | null; + }; + Insert: { + author?: string | null; + createdAt?: string | null; + from?: string | null; + id?: number; + isSolved?: boolean | null; + message?: string | null; + note?: string | null; + }; + Update: { + author?: string | null; + createdAt?: string | null; + from?: string | null; + id?: number; + isSolved?: boolean | null; + message?: string | null; + note?: string | null; + }; + Relationships: []; + }; + Value: { + Row: { + createdAt: string; + id: number; + key: string | null; + note: string | null; + validUntil: string | null; + value: Json | null; + }; + Insert: { + createdAt?: string; + id?: number; + key?: string | null; + note?: string | null; + validUntil?: string | null; + value?: Json | null; + }; + Update: { + createdAt?: string; + id?: number; + key?: string | null; + note?: string | null; + validUntil?: string | null; + value?: Json | null; + }; + Relationships: []; + }; + Wallpaper: { + Row: { + author: string; + colorStats: Json | null; + content: string; + createdAt: string; + id: string; + isPublic: boolean; + keywords: string[] | null; + naturalSize: Json | null; + parent: string | null; + prompt: string | null; + src: string; + title: string; + }; + Insert: { + author: string; + colorStats?: Json | null; + content: string; + createdAt?: string; + id: string; + isPublic?: boolean; + keywords?: string[] | null; + naturalSize?: Json | null; + parent?: string | null; + prompt?: string | null; + src: string; + title: string; + }; + Update: { + author?: string; + colorStats?: Json | null; + content?: string; + createdAt?: string; + id?: string; + isPublic?: boolean; + keywords?: string[] | null; + naturalSize?: Json | null; + parent?: string | null; + prompt?: string | null; + src?: string; + title?: string; + }; + Relationships: []; + }; + WallpaperFeedback: { + Row: { + author: string; + createdAt: string; + likedStatus: string; + note: string | null; + wallpaperId: string; + }; + Insert: { + author: string; + createdAt?: string; + likedStatus: string; + note?: string | null; + wallpaperId: string; + }; + Update: { + author?: string; + createdAt?: string; + likedStatus?: string; + note?: string | null; + wallpaperId?: string; + }; + Relationships: [ + { + foreignKeyName: 'WallpaperFeedback_wallpaperId_fkey'; + columns: ['wallpaperId']; + referencedRelation: 'Wallpaper'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'WallpaperFeedback_wallpaperId_fkey'; + columns: ['wallpaperId']; + referencedRelation: 'Wallpaper_random'; + referencedColumns: ['id']; + }, + ]; + }; + }; + Views: { + ClientEmailVerification_withRequests: { + Row: { + clientId: string | null; + code: string | null; + createdAt: string | null; + email: string | null; + verificationRequestId: number | null; + }; + Relationships: [ + { + foreignKeyName: 'ClientEmailVerification_verificationRequestId_fkey'; + columns: ['verificationRequestId']; + referencedRelation: 'ClientEmailVerificationRequest'; + referencedColumns: ['id']; + }, + ]; + }; + Prompt_stats: { + Row: { + answer: string | null; + answerAt: string | null; + clientId: string | null; + createdAt: string | null; + duration: unknown | null; + externalId: string | null; + fullCompletion: Json | null; + id: number | null; + metadata: Json | null; + model: string | null; + modelSettings: Json | null; + nonce: number | null; + previousExternalId: string | null; + prompt: string | null; + promptAt: string | null; + systemMessage: string | null; + type: string | null; + }; + Insert: { + answer?: string | null; + answerAt?: string | null; + clientId?: string | null; + createdAt?: string | null; + duration?: never; + externalId?: string | null; + fullCompletion?: Json | null; + id?: number | null; + metadata?: Json | null; + model?: string | null; + modelSettings?: Json | null; + nonce?: never; + previousExternalId?: string | null; + prompt?: string | null; + promptAt?: string | null; + systemMessage?: string | null; + type?: string | null; + }; + Update: { + answer?: string | null; + answerAt?: string | null; + clientId?: string | null; + createdAt?: string | null; + duration?: never; + externalId?: string | null; + fullCompletion?: Json | null; + id?: number | null; + metadata?: Json | null; + model?: string | null; + modelSettings?: Json | null; + nonce?: never; + previousExternalId?: string | null; + prompt?: string | null; + promptAt?: string | null; + systemMessage?: string | null; + type?: string | null; + }; + Relationships: []; + }; + Wallpaper_random: { + Row: { + author: string | null; + colorStats: Json | null; + content: string | null; + createdAt: string | null; + id: string | null; + isPublic: boolean | null; + keywords: string[] | null; + nonce: number | null; + parent: string | null; + prompt: string | null; + src: string | null; + title: string | null; + }; + Insert: { + author?: string | null; + colorStats?: Json | null; + content?: string | null; + createdAt?: string | null; + id?: string | null; + isPublic?: boolean | null; + keywords?: string[] | null; + nonce?: never; + parent?: string | null; + prompt?: string | null; + src?: string | null; + title?: string | null; + }; + Update: { + author?: string | null; + colorStats?: Json | null; + content?: string | null; + createdAt?: string | null; + id?: string | null; + isPublic?: boolean | null; + keywords?: string[] | null; + nonce?: never; + parent?: string | null; + prompt?: string | null; + src?: string | null; + title?: string | null; + }; + Relationships: []; + }; + }; + Functions: { + [_ in never]: never; + }; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; + }; } - From 9a95afb0fccb68962ff66b858feef49463a484ab Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:37:47 +0100 Subject: [PATCH 24/48] Working on isClientVerified --- src/utils/client/isClientVerified.test.ts | 3 +++ src/utils/client/isClientVerifiedForServer.ts | 26 +++++++++++++++++-- src/utils/client/provideClientId.ts | 17 ++++++------ src/utils/client/verifyEmailCodeForServer.ts | 1 + 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/utils/client/isClientVerified.test.ts b/src/utils/client/isClientVerified.test.ts index 96079d58b..d8fa806ef 100644 --- a/src/utils/client/isClientVerified.test.ts +++ b/src/utils/client/isClientVerified.test.ts @@ -25,6 +25,9 @@ describe('how isClientVerified works', () => { }); /** + * TODO: !!! pavol+not-verified@webgpt.cz + * TODO: !!! pavol+email-sent@webgpt.cz + * TODO: !!! pavol+verified@webgpt.cz * TODO: !!! Implement * TODO: !!!last Annotate */ diff --git a/src/utils/client/isClientVerifiedForServer.ts b/src/utils/client/isClientVerifiedForServer.ts index eb714c2a0..fbd51ab22 100644 --- a/src/utils/client/isClientVerifiedForServer.ts +++ b/src/utils/client/isClientVerifiedForServer.ts @@ -16,11 +16,33 @@ export async function $isClientVerifiedForServer(options: IsClientVerifiedReques const { clientId } = options; - const { data: verificationRequests } = await getSupabaseForServer() + const { data: clientEmailVerifications } = await getSupabaseForServer() .from('ClientEmailVerification_withRequests') - .select('id') + .select('"1"') .eq('clientId', clientId); + if (clientEmailVerifications && clientEmailVerifications.length > 0) { + return { + status: 'VERIFIED', + }; + } + + const { data: clientEmailVerificationsRequests } = await getSupabaseForServer() + // TODO: [🍠] Put here some time limit + .from('ClientEmailVerificationRequest') + .select('"1"') + .eq('clientId', clientId); + + if (clientEmailVerificationsRequests && clientEmailVerificationsRequests.length > 0) { + return { + status: 'EMAIL_SENT', + }; + } + + return { + status: 'NOT_VERIFIED', + }; + /* const selectResult = await getSupabaseForServer().from('Client').select('email').eq('clientId', clientId).limit(1); diff --git a/src/utils/client/provideClientId.ts b/src/utils/client/provideClientId.ts index dd1735277..47a9e1f72 100644 --- a/src/utils/client/provideClientId.ts +++ b/src/utils/client/provideClientId.ts @@ -1,8 +1,8 @@ -import { IsClientVerifiedResponse } from '../../pages/api/client/is-client-verified'; import { validateEmailDialogue } from '../../workers/dialogues/validate-email/validateEmailDialogue'; import { getSupabaseForBrowser } from '../supabase/getSupabaseForBrowser'; import { client_id, string_email } from '../typeAliases'; import { isValidEmail } from '../validators/isValidEmail'; +import { $isClientVerifiedForBrowser } from './isClientVerifiedForBrowser'; import { $provideClientIdWithoutVerification } from './provideClientIdWithoutVerification'; /** @@ -19,7 +19,11 @@ export interface IProvideClientIdOptions { } /** - * Checks if clientId is in localStorage and verified OR generates new one and pops up the dialogue to verify email + * Provides verified clientId + * + * 1) If the `clientId` is not in localStorage, generates new one + * 2) If the `clientId` is in localStorage, checks if it is verified + * 3) If the is not verified, pops up the dialogue to verify email * * Note: This function is available only in browser * @@ -30,14 +34,9 @@ export async function $provideClientId(options: IProvideClientIdOptions): Promis const clientId = $provideClientIdWithoutVerification(); - // TODO: !!! Use isClientVerifiedForBrowser instead - // TODO: !!! Use propperly - send ONLY when user requests it - const response = await fetch( - `/api/client/is-client-verified?clientId=${/* <- TODO: [⛹️‍♂️] Send clientId through headers */ clientId}`, - ); - const { isClientInserted /* [0],isClientVerified */ } = (await response.json()) as IsClientVerifiedResponse; + const { status } = await $isClientVerifiedForBrowser({ clientId }); - if (isClientInserted) { + if (status === 'VERIFIED') { return clientId; } diff --git a/src/utils/client/verifyEmailCodeForServer.ts b/src/utils/client/verifyEmailCodeForServer.ts index 9254d42e6..59e4e9d97 100644 --- a/src/utils/client/verifyEmailCodeForServer.ts +++ b/src/utils/client/verifyEmailCodeForServer.ts @@ -18,6 +18,7 @@ export async function $verifyEmailCodeForServer(options: VerifyEmailCodeRequest) const { clientId, email, code } = options; const { data: verificationRequests } = await getSupabaseForServer() + // TODO: [🍠] Put here some time limit .from('ClientEmailVerificationRequest') .select('id,createdAt,code,email') .eq('clientId', clientId) From 16e64e7a96279a75ef18f171e9e3c4a95003500f Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:38:50 +0100 Subject: [PATCH 25/48] TODOs --- src/workers/dialogues/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/workers/dialogues/index.ts b/src/workers/dialogues/index.ts index 3a45f997b..3c2024cdd 100644 --- a/src/workers/dialogues/index.ts +++ b/src/workers/dialogues/index.ts @@ -13,3 +13,7 @@ import { validateEmailDialogue } from './validate-email/validateEmailDialogue'; export const supportDialogues: Array< DialogueFunction > = [simpleTextDialogue, confirmDialogue, imageGeneratorDialogue, feedbackDialogue, validateEmailDialogue]; + +/** + * TODO: !!!last Rename to validate -> verify + */ From bf4bbf51fdd480f6e2fbc42c90db8e9b40a34408 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:43:23 +0100 Subject: [PATCH 26/48] Fixing types --- config.ts | 3 ++- promptbook-server/server.ts | 3 ++- src/pages/[wallpaperId].tsx | 3 ++- src/pages/api/og-image.tsx | 2 +- src/utils/client/validateClientId.test.ts | 29 +++++++++++++++++++++++ src/utils/client/validateClientId.ts | 15 ++++++++++++ src/utils/computeWallpaperUriid.test.ts | 5 ++-- 7 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 src/utils/client/validateClientId.test.ts create mode 100644 src/utils/client/validateClientId.ts diff --git a/config.ts b/config.ts index 92a923cab..7eee26e28 100644 --- a/config.ts +++ b/config.ts @@ -8,6 +8,7 @@ import { FULLHD } from './src/constants'; import type { AspectRatioRange } from './src/utils/aspect-ratio/AspectRatioRange'; import { expectAspectRatioInRange } from './src/utils/aspect-ratio/expectAspectRatioInRange'; import { DigitalOceanSpaces } from './src/utils/cdn/classes/DigitalOceanSpaces'; +import { validateClientId } from './src/utils/client/validateClientId'; import { createColorfulComputeImageColorStats15 } from './src/utils/image/palette/15/createColorfulComputeImageColorStats15'; import type { IComputeImageColorStats } from './src/utils/image/utils/IImageColorStats'; import { isRunningInBrowser } from './src/utils/isRunningInWhatever'; @@ -1050,7 +1051,7 @@ export const TEXT_BACKGROUND_COLOR_DISTANCE_THEASHOLD_RATIO = 0.5; /* <- As a ra */ export const PRIMARY_TO_AVERAGE_MAX_COLOR_DISTANCE_THEASHOLD_RATIO = 0.1; /* <- As a ratio of distance between white and black */ -export const SYSTEM_AUTHOR_ID = validateUuid('000d2940-4a35-4859-83a8-3c754ea5df51'); +export const SYSTEM_AUTHOR_ID = validateClientId('000d2940-4a35-4859-83a8-3c754ea5df51'); // TODO: [🧠] How to do required only on server export const CDN_BUCKET = config.get('CDN_BUCKET') /*.required([📐])*/.value; diff --git a/promptbook-server/server.ts b/promptbook-server/server.ts index b0d7b6340..295e89457 100644 --- a/promptbook-server/server.ts +++ b/promptbook-server/server.ts @@ -7,6 +7,7 @@ import { OpenAiExecutionTools } from '@promptbook/openai'; import { runRemoteServer } from '@promptbook/remote-server'; import { IS_DEVELOPMENT, OPENAI_API_KEY } from '../config'; import { SupabaseLoggerWrapperOfNaturalExecutionTools } from '../src/ai/prompt-templates/logger/SupabaseLoggerWrapperOfNaturalExecutionTools'; +import { client_id } from '../src/utils/typeAliases'; // [🎛] import { webgptPtpLibrary } from '../src/ai/prompt-templates/webgptPtpLibrary'; runRemoteServer({ @@ -17,7 +18,7 @@ runRemoteServer({ /* <- TODO: [🎛] Use here real webgptPtpLibrary */ }), - createNaturalExecutionTools(clientId) { + createNaturalExecutionTools(clientId: client_id) { return new SupabaseLoggerWrapperOfNaturalExecutionTools({ isVerbose: false /* <- Note: [3] */, clientId, diff --git a/src/pages/[wallpaperId].tsx b/src/pages/[wallpaperId].tsx index 98213ce56..649967a48 100644 --- a/src/pages/[wallpaperId].tsx +++ b/src/pages/[wallpaperId].tsx @@ -8,6 +8,7 @@ import { SkinStyle } from '../components/SkinStyle/SkinStyle'; import { WallpaperEditing } from '../components/WallpaperEditing/WallpaperEditing'; import { WallpaperEditingLink } from '../components/WallpaperEditing/WallpaperEditingLink'; import { WallpaperLayout } from '../components/WallpaperLayout/WallpaperLayout'; +import { validateClientId } from '../utils/client/validateClientId'; import { useRole } from '../utils/hooks/useRole'; import { WallpapersContext } from '../utils/hooks/WallpapersContext'; import { hydrateWallpapersCached } from '../utils/hydrateWallpapersCached'; @@ -97,7 +98,7 @@ export async function getStaticProps({ currentWallpaper = { ...selectResult.data[0]!, content: validateMaxdown(selectResult.data[0]!.content), - author: validateUuid(selectResult.data[0]!.author), + author: validateClientId(selectResult.data[0]!.author), naturalSize: selectResult.data[0]!.naturalSize as { x: number; y: number; diff --git a/src/pages/api/og-image.tsx b/src/pages/api/og-image.tsx index 619355075..5cf5b5ed4 100644 --- a/src/pages/api/og-image.tsx +++ b/src/pages/api/og-image.tsx @@ -26,7 +26,7 @@ export default async function ogImageHandler( const wallpaper = { ...selectResult.data[0], - author: validateUuid(selectResult.data[0].author), + author: validateClientId(selectResult.data[0].author), }; /**/ diff --git a/src/utils/client/validateClientId.test.ts b/src/utils/client/validateClientId.test.ts new file mode 100644 index 000000000..65a863382 --- /dev/null +++ b/src/utils/client/validateClientId.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from '@jest/globals'; +import { validateClientId } from './validateClientId'; + +describe(`validation of UUIDs`, () => { + it(`is valid`, () => { + expect(validateClientId(`6c815d9e-b1b0-4768-86ef-8d8e3635984a`)).toBe(`6c815d9e-b1b0-4768-86ef-8d8e3635984a`); + expect(validateClientId(`9de47735-e949-41f4-9819-012af1e76aeb`)).toBe(`9de47735-e949-41f4-9819-012af1e76aeb`); + expect(validateClientId(`53b5a203-1b6a-4db2-b4df-f1ec37ce01ee`)).toBe(`53b5a203-1b6a-4db2-b4df-f1ec37ce01ee`); + expect(validateClientId(`0f3bbeb6-19d3-42f2-9436-500b8d418c8d`)).toBe(`0f3bbeb6-19d3-42f2-9436-500b8d418c8d`); + expect(validateClientId(`e1921419-74e7-40a7-b288-edbc4a06037a`)).toBe(`e1921419-74e7-40a7-b288-edbc4a06037a`); + expect(validateClientId(`a4873faf-8504-40d5-a098-482b7cd5f214`)).toBe(`a4873faf-8504-40d5-a098-482b7cd5f214`); + expect(validateClientId(`724f0e12-7390-4510-b6f5-df414f69b432`)).toBe(`724f0e12-7390-4510-b6f5-df414f69b432`); + expect(validateClientId(`30f4d3db-60a0-4100-918e-e68bd7fe585b`)).toBe(`30f4d3db-60a0-4100-918e-e68bd7fe585b`); + expect(validateClientId(`d132ff5f-48ab-4f32-bff0-533f761ee978`)).toBe(`d132ff5f-48ab-4f32-bff0-533f761ee978`); + expect(validateClientId(`b4821d3c-5c73-4048-a07d-a8ff730a6d3c`)).toBe(`b4821d3c-5c73-4048-a07d-a8ff730a6d3c`); + }); + + it(`is NOT valid`, () => { + expect(() => validateClientId(``)).toThrowError(/Invalid uuid/i); + expect(() => validateClientId(`1`)).toThrowError(/Invalid uuid/i); + expect(() => validateClientId(`1.A`)).toThrowError(/Invalid uuid/i); + expect(() => validateClientId(`Hello`)).toThrowError(/Invalid uuid/i); + expect(() => validateClientId(`d132ff5f`)).toThrowError(/Invalid uuid/i); + expect(() => validateClientId(`b4821d3c-5c73-4048-a07d`)).toThrowError(/Invalid uuid/i); + expect(() => validateClientId(`9SeSQTupmQHwuSrLi`)).toThrowError(/Invalid uuid/i); + expect(() => validateClientId(`wqgbh5qgkohkjhtetvh7`)).toThrowError(/Invalid uuid/i); + expect(() => validateClientId(`uuyrr6h4niwaqfbmlb65`)).toThrowError(/Invalid uuid/i); + }); +}); diff --git a/src/utils/client/validateClientId.ts b/src/utils/client/validateClientId.ts new file mode 100644 index 000000000..ec21e2bae --- /dev/null +++ b/src/utils/client/validateClientId.ts @@ -0,0 +1,15 @@ +import { client_id } from '../typeAliases'; +import { isValidUuid } from '../validators/isValidUuid'; + +export function validateClientId(value: unknown): client_id { + if (!isValidUuid(value)) { + throw new Error(`Invalid clientId ${value}`); + } + + return value as client_id; +} + +/** + * TODO: [🧠][🚓] Is/which combination it better to use asserts/check, validate or is utility function? + * TODO: Can there be used (asserts value is uuid) with uuid return type? + */ diff --git a/src/utils/computeWallpaperUriid.test.ts b/src/utils/computeWallpaperUriid.test.ts index 9fd4af794..cf1bdac64 100644 --- a/src/utils/computeWallpaperUriid.test.ts +++ b/src/utils/computeWallpaperUriid.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from '@jest/globals'; import spaceTrim from 'spacetrim'; import { validateMaxdown } from '../components/Content/Maxdown/validateMaxdown'; import { FULLHD } from '../constants'; +import { validateClientId } from './client/validateClientId'; import { computeWallpaperUriid } from './computeWallpaperUriid'; import { hydrateColorStats } from './image/utils/hydrateColorStats'; import { validateUuid } from './validators/validateUuid'; @@ -13,7 +14,7 @@ describe(`computeWallpaperUriid`, () => { parent: 'ocean-vibes-1kmp5dwt35su', src: 'https://cdn.midjourney.com/6be2b125-4fbb-498f-8f08-fc153998fef5/0_3.png', prompt: 'A beautiful sunset over the ocean', - author: validateUuid('8450ee88-d216-41c4-a30e-5bba49289573'), + author: validateClientId('8450ee88-d216-41c4-a30e-5bba49289573'), colorStats: hydrateColorStats({ palette: [ { @@ -308,7 +309,7 @@ describe(`computeWallpaperUriid`, () => { parent: 'ocean-vibes-1kmp5dwt35su', src: 'https://cdn.midjourney.com/6be2b125-4fbb-498f-8f08-fc153998fef5/0_3.png', prompt: 'A beautiful sunset over the ocean', - author: validateUuid('8450ee88-d216-41c4-a30e-5bba49289573'), + author: validateClientId('8450ee88-d216-41c4-a30e-5bba49289573'), colorStats: hydrateColorStats({ palette: [ { From a36a87c6297eed7c068da1cc89456154f4b967c7 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:46:27 +0100 Subject: [PATCH 27/48] Fixing types --- src/utils/hooks/useClientId.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/hooks/useClientId.ts b/src/utils/hooks/useClientId.ts index 9922aa310..166ca0326 100644 --- a/src/utils/hooks/useClientId.ts +++ b/src/utils/hooks/useClientId.ts @@ -1,7 +1,7 @@ -import { uuid } from '@promptbook/types'; import { useMemo } from 'react'; import { $provideClientId, IProvideClientIdOptions } from '../client/provideClientId'; +import { client_id } from '../typeAliases'; import { usePromise } from './usePromise'; import { useSsrDetection } from './useSsrDetection'; @@ -10,7 +10,7 @@ import { useSsrDetection } from './useSsrDetection'; * * @returns clientId */ -export function useClientId(options: IProvideClientIdOptions): uuid | null { +export function useClientId(options: IProvideClientIdOptions): client_id | null { const isServerRender = useSsrDetection(); const { isVerifiedEmailRequired } = options; From 28fafac03832527f88352e358ff98bea417356a3 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:52:30 +0100 Subject: [PATCH 28/48] Fixing types --- src/components/Modal/00-Modal.tsx | 20 +++++++++++++++++++- src/utils/client/isClientVerified.test.ts | 20 ++++++++------------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/components/Modal/00-Modal.tsx b/src/components/Modal/00-Modal.tsx index 939255f42..31a926dbe 100644 --- a/src/components/Modal/00-Modal.tsx +++ b/src/components/Modal/00-Modal.tsx @@ -66,11 +66,29 @@ type ModalProps = { } ); +type MagicType = UnionToIntersection extends infer O ? { [K in keyof O]: O[K] } : never; + +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; + /** * Renders a modal above the wallpaper page */ export function Modal(props: ModalProps) { - const { title, children, isDisabled, isCloseable, closeIcon = '✖', onClose, size = 'FULL', className } = props; + const { + title, + children, + isDisabled, + isCloseable, + closeIcon = '✖', + onClose, + size = 'FULL', + className, + } = { + closeIcon: undefined, + onClose: undefined, + // <- TODO: [🦪] Some helper type to be able to use discriminant union types with destructuring + ...props, + }; const styles = useStyleModule(import('./00-Modal.module.css')); diff --git a/src/utils/client/isClientVerified.test.ts b/src/utils/client/isClientVerified.test.ts index d8fa806ef..6ddcbe65b 100644 --- a/src/utils/client/isClientVerified.test.ts +++ b/src/utils/client/isClientVerified.test.ts @@ -1,25 +1,21 @@ import { describe, expect, it } from '@jest/globals'; -import spaceTrim from 'spacetrim'; -import { $isClientVerified } from './isClientVerifiedForServer'; +import { $isClientVerifiedForServer } from './isClientVerifiedForServer'; +import { validateClientId } from './validateClientId'; describe('how isClientVerified works', () => { it('should work with foo', () => { expect( - $isClientVerified( - spaceTrim(` - Foo - `), - ), + $isClientVerifiedForServer({ + clientId: validateClientId('!!!'), + }), ).resolves.toBe(true); }); it('should NOT work with bar', () => { expect( - $isClientVerified( - spaceTrim(` - bar - `), - ), + $isClientVerifiedForServer({ + clientId: validateClientId('!!!'), + }), ).resolves.toBe(false); }); }); From cb3b41f838fd902c68caf4f19c5f0917d3f2f35c Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:53:09 +0100 Subject: [PATCH 29/48] Remove CodeValidationError --- src/utils/client/CodeValidationError.ts | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 src/utils/client/CodeValidationError.ts diff --git a/src/utils/client/CodeValidationError.ts b/src/utils/client/CodeValidationError.ts deleted file mode 100644 index f9267d3e3..000000000 --- a/src/utils/client/CodeValidationError.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * TODO: !!! Implement or remove - * TODO: !!!last Annotate - */ From 1d228d3f799f9608e4666c628269925c367a3423 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 18:56:16 +0100 Subject: [PATCH 30/48] Fixing types --- src/utils/client/provideClientIdWithoutVerification.ts | 7 +++---- src/utils/typeAliases.ts | 2 +- .../validate-email/types/ValidateEmailDialogueResponse.ts | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/utils/client/provideClientIdWithoutVerification.ts b/src/utils/client/provideClientIdWithoutVerification.ts index f89e8b2fb..c3fad8aef 100644 --- a/src/utils/client/provideClientIdWithoutVerification.ts +++ b/src/utils/client/provideClientIdWithoutVerification.ts @@ -1,7 +1,8 @@ import { isRunningInBrowser } from '../isRunningInWhatever'; -import { client_id, uuid } from '../typeAliases'; +import { client_id } from '../typeAliases'; import { isValidClientId } from '../validators/isValidClientId'; import { $generateClientId } from './generateClientId'; +import { validateClientId } from './validateClientId'; /** * Internal cache for provideClientIdWithoutVerification @@ -27,7 +28,7 @@ export function $provideClientIdWithoutVerification(): client_id { return clientId; } - clientId = window.localStorage.getItem(`clientId`) as uuid; + clientId = validateClientId(window.localStorage.getItem(`clientId`)); if (!isValidClientId(clientId)) { // Note: It make sense to log this error because it is captured by Sentry @@ -43,5 +44,3 @@ export function $provideClientIdWithoutVerification(): client_id { return clientId; } - - diff --git a/src/utils/typeAliases.ts b/src/utils/typeAliases.ts index b97d75e9b..a500191b2 100644 --- a/src/utils/typeAliases.ts +++ b/src/utils/typeAliases.ts @@ -359,7 +359,7 @@ export type uuid = string & { /** * Branded type client id */ -export type client_id = uuid & { +export type client_id = string & { readonly _type: 'client_id' /* <- TODO: [🏟] What is the best shape of the additional object in branded types */; }; diff --git a/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts b/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts index dbdb90c0e..17c057cde 100644 --- a/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts +++ b/src/workers/dialogues/validate-email/types/ValidateEmailDialogueResponse.ts @@ -1,4 +1,4 @@ -import type { ClientVerification } from '../../../../utils/client/ClientVerification'; +import type { ClientEmailVerification } from '../../../../utils/client/ClientVerification'; import type { AbstractDialogueResponse } from '../../../lib/dialogues/interfaces/AbstractDialogueResponse'; -export type ValidateEmailDialogueResponse = AbstractDialogueResponse & ClientVerification; +export type ValidateEmailDialogueResponse = AbstractDialogueResponse & ClientEmailVerification; From f9b8e5c751100f9cbdeba9655baf8dfbc974c965 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 19:18:58 +0100 Subject: [PATCH 31/48] Fixing types --- package-lock.json | 110 +++++++++--------- package.json | 14 +-- src/utils/client/generateClientId.ts | 2 +- src/utils/client/validateClientId.ts | 2 +- .../createNewWallpaper_image.ts | 4 +- 5 files changed, 66 insertions(+), 66 deletions(-) diff --git a/package-lock.json b/package-lock.json index 845546475..e3015d7e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,13 +16,13 @@ "@monaco-editor/react": "4.5.1", "@octokit/rest": "18.12.0", "@preact/signals-react": "1.3.6", - "@promptbook/core": "0.18.0", - "@promptbook/execute-javascript": "0.18.0", - "@promptbook/openai": "0.18.0", - "@promptbook/remote-client": "0.18.0", - "@promptbook/remote-server": "0.18.0", - "@promptbook/types": "0.18.0", - "@promptbook/utils": "0.18.0", + "@promptbook/core": "0.19.0", + "@promptbook/execute-javascript": "0.19.0", + "@promptbook/openai": "0.19.0", + "@promptbook/remote-client": "0.19.0", + "@promptbook/remote-server": "0.19.0", + "@promptbook/types": "0.19.0", + "@promptbook/utils": "0.19.0", "@sendgrid/mail": "^8.1.0", "@supabase/supabase-js": "2.26.0", "@types/file-saver": "2.0.5", @@ -3059,9 +3059,9 @@ } }, "node_modules/@promptbook/core": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/core/-/core-0.18.0.tgz", - "integrity": "sha512-My8CdfZ9q0S8fG4zptyoEv1ifOZ7skUh/SNDv5U+Gj77cbVzOD07umIQ6CSg2sLlIBHlgKAEa0PvjAALXQO5yg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/core/-/core-0.19.0.tgz", + "integrity": "sha512-PEsD6TAXXj9flpcb8geRYktIPwyswm/4IAMK5kdjGtf5+VLr6l5IbuFizxAWNpVdpOdN/qsr4fgwog4V8NhElA==", "dependencies": { "n12": "1.6.0", "spacetrim": "0.9.2" @@ -3073,16 +3073,16 @@ "integrity": "sha512-1yc++1RbULLNqyxxqOKfrJEdjWGAG4tyQgJKi9QRjYPmyiAOqpsnphJuDOGdZWtsLg3DH2roAYjz7S74CP6nYw==" }, "node_modules/@promptbook/execute-javascript": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/execute-javascript/-/execute-javascript-0.18.0.tgz", - "integrity": "sha512-txoytirdeTwXQurE/Za32+r489hpTmjQ89x6SXhhldPussh2uip1WajPdXxTZXctsdvk5pb29fVkBlWSdihxLQ==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/execute-javascript/-/execute-javascript-0.19.0.tgz", + "integrity": "sha512-gu5bvMmtCwx9YbOAyFzv+Ew78w78UdymKqPY9Zx9IlOD8mUWurs1I3J9cRCNIQHEOebuZJQA07yubiSwJ/suQQ==", "dependencies": { "n12": "1.6.0", "spacetrim": "0.9.2", "waitasecond": "1.11.1" }, "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@promptbook/execute-javascript/node_modules/n12": { @@ -3091,14 +3091,14 @@ "integrity": "sha512-1yc++1RbULLNqyxxqOKfrJEdjWGAG4tyQgJKi9QRjYPmyiAOqpsnphJuDOGdZWtsLg3DH2roAYjz7S74CP6nYw==" }, "node_modules/@promptbook/openai": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/openai/-/openai-0.18.0.tgz", - "integrity": "sha512-R6uP1Ay1oF1AHMbnV4M1xPC4IcsBP96iC5ySMhy8Eps5Z2y72UACsMWealEMGvyrATeZnBN56824B0qvdLI6FA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/openai/-/openai-0.19.0.tgz", + "integrity": "sha512-NVWCeNc0lSpSPF6H6k+pH0tbMA7FU3s2jZA0aXQcze33guzg2NrU7MF2Bc3nz9wpDpvIdDvyYkMrc6LZxMnoLw==", "dependencies": { "openai": "4.2.0" }, "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@promptbook/openai/node_modules/openai": { @@ -3120,45 +3120,45 @@ } }, "node_modules/@promptbook/remote-client": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/remote-client/-/remote-client-0.18.0.tgz", - "integrity": "sha512-9fFT2eYiKgpanbRCV9CPz6kZ3SK84PHcCDDM8Z8ARkD/C7L5ulVEvKaX2nN4oxvvs8oUpKP2f1reayX9Zt/Vqg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/remote-client/-/remote-client-0.19.0.tgz", + "integrity": "sha512-NcBURR0qYoAOAACIGHD/HP+GaJ5vnpTbtSVpj1wJgro9lIuY9xcckEz2jMkptW3b1tunYSPTtQsPx8tQuMEkTQ==", "dependencies": { "socket.io-client": "4.7.2" }, "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@promptbook/remote-server": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/remote-server/-/remote-server-0.18.0.tgz", - "integrity": "sha512-CBbi/8JX2gK7CNYDOZV56UgGyBgXnSFJ3J8WF01YFBoKiKtzcEuHbKiK/d8un8cVsF+xBimOZ/xDzHHvxOsjAw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/remote-server/-/remote-server-0.19.0.tgz", + "integrity": "sha512-Y/Nm45q18rJAP0tAJPSk530cFhpAVGV/5rDq031TTdV4lNn4/p/MpWRjP0wMJeyomGJVs8POy9KxtPRmDP1Nnw==", "dependencies": { "socket.io": "4.7.2", "spacetrim": "0.9.2" }, "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@promptbook/types": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/types/-/types-0.18.0.tgz", - "integrity": "sha512-Ij8asg9y909jlaffXoa0ir3oI0JTNjwNJftqK5ZiB06G7Hu8N1YpJpvZX+FNDQ0IwrsQzWDCPifpuCcdF2dPsw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/types/-/types-0.19.0.tgz", + "integrity": "sha512-R0FyDWfRtObc47oyC+jVXfcgsdY9USnonPDxauDgHYNoqzVT3O578h92+CYprMr8itfMaftAJY8WqiA4xMtUSA==", "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@promptbook/utils": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.18.0.tgz", - "integrity": "sha512-ATuzOBLOZ67JB775WWyN52EKhmvz+6n54YQHenjK0uzMMNYf2O+2V8NxYlvXYxeH/uxbrOoKSJAK1NouoSKLnA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-M46wano6loWD9mqk8XTXynTSUmxlX5teh8BR418r+k0fhldX23vdJH/cjS9ECUQVzsThY+aDZNhEkEtMFS7KXg==", "dependencies": { "spacetrim": "0.9.2" }, "peerDependencies": { - "@promptbook/core": "0.18.0" + "@promptbook/core": "0.19.0" } }, "node_modules/@puppeteer/browsers": { @@ -19496,9 +19496,9 @@ } }, "@promptbook/core": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/core/-/core-0.18.0.tgz", - "integrity": "sha512-My8CdfZ9q0S8fG4zptyoEv1ifOZ7skUh/SNDv5U+Gj77cbVzOD07umIQ6CSg2sLlIBHlgKAEa0PvjAALXQO5yg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/core/-/core-0.19.0.tgz", + "integrity": "sha512-PEsD6TAXXj9flpcb8geRYktIPwyswm/4IAMK5kdjGtf5+VLr6l5IbuFizxAWNpVdpOdN/qsr4fgwog4V8NhElA==", "requires": { "n12": "1.6.0", "spacetrim": "0.9.2" @@ -19512,9 +19512,9 @@ } }, "@promptbook/execute-javascript": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/execute-javascript/-/execute-javascript-0.18.0.tgz", - "integrity": "sha512-txoytirdeTwXQurE/Za32+r489hpTmjQ89x6SXhhldPussh2uip1WajPdXxTZXctsdvk5pb29fVkBlWSdihxLQ==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/execute-javascript/-/execute-javascript-0.19.0.tgz", + "integrity": "sha512-gu5bvMmtCwx9YbOAyFzv+Ew78w78UdymKqPY9Zx9IlOD8mUWurs1I3J9cRCNIQHEOebuZJQA07yubiSwJ/suQQ==", "requires": { "n12": "1.6.0", "spacetrim": "0.9.2", @@ -19529,9 +19529,9 @@ } }, "@promptbook/openai": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/openai/-/openai-0.18.0.tgz", - "integrity": "sha512-R6uP1Ay1oF1AHMbnV4M1xPC4IcsBP96iC5ySMhy8Eps5Z2y72UACsMWealEMGvyrATeZnBN56824B0qvdLI6FA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/openai/-/openai-0.19.0.tgz", + "integrity": "sha512-NVWCeNc0lSpSPF6H6k+pH0tbMA7FU3s2jZA0aXQcze33guzg2NrU7MF2Bc3nz9wpDpvIdDvyYkMrc6LZxMnoLw==", "requires": { "openai": "4.2.0" }, @@ -19554,32 +19554,32 @@ } }, "@promptbook/remote-client": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/remote-client/-/remote-client-0.18.0.tgz", - "integrity": "sha512-9fFT2eYiKgpanbRCV9CPz6kZ3SK84PHcCDDM8Z8ARkD/C7L5ulVEvKaX2nN4oxvvs8oUpKP2f1reayX9Zt/Vqg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/remote-client/-/remote-client-0.19.0.tgz", + "integrity": "sha512-NcBURR0qYoAOAACIGHD/HP+GaJ5vnpTbtSVpj1wJgro9lIuY9xcckEz2jMkptW3b1tunYSPTtQsPx8tQuMEkTQ==", "requires": { "socket.io-client": "4.7.2" } }, "@promptbook/remote-server": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/remote-server/-/remote-server-0.18.0.tgz", - "integrity": "sha512-CBbi/8JX2gK7CNYDOZV56UgGyBgXnSFJ3J8WF01YFBoKiKtzcEuHbKiK/d8un8cVsF+xBimOZ/xDzHHvxOsjAw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/remote-server/-/remote-server-0.19.0.tgz", + "integrity": "sha512-Y/Nm45q18rJAP0tAJPSk530cFhpAVGV/5rDq031TTdV4lNn4/p/MpWRjP0wMJeyomGJVs8POy9KxtPRmDP1Nnw==", "requires": { "socket.io": "4.7.2", "spacetrim": "0.9.2" } }, "@promptbook/types": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/types/-/types-0.18.0.tgz", - "integrity": "sha512-Ij8asg9y909jlaffXoa0ir3oI0JTNjwNJftqK5ZiB06G7Hu8N1YpJpvZX+FNDQ0IwrsQzWDCPifpuCcdF2dPsw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/types/-/types-0.19.0.tgz", + "integrity": "sha512-R0FyDWfRtObc47oyC+jVXfcgsdY9USnonPDxauDgHYNoqzVT3O578h92+CYprMr8itfMaftAJY8WqiA4xMtUSA==", "requires": {} }, "@promptbook/utils": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.18.0.tgz", - "integrity": "sha512-ATuzOBLOZ67JB775WWyN52EKhmvz+6n54YQHenjK0uzMMNYf2O+2V8NxYlvXYxeH/uxbrOoKSJAK1NouoSKLnA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-M46wano6loWD9mqk8XTXynTSUmxlX5teh8BR418r+k0fhldX23vdJH/cjS9ECUQVzsThY+aDZNhEkEtMFS7KXg==", "requires": { "spacetrim": "0.9.2" } diff --git a/package.json b/package.json index a939c6ec8..9f0f02c65 100644 --- a/package.json +++ b/package.json @@ -64,13 +64,13 @@ "@monaco-editor/react": "4.5.1", "@octokit/rest": "18.12.0", "@preact/signals-react": "1.3.6", - "@promptbook/core": "0.18.0", - "@promptbook/execute-javascript": "0.18.0", - "@promptbook/openai": "0.18.0", - "@promptbook/remote-client": "0.18.0", - "@promptbook/remote-server": "0.18.0", - "@promptbook/types": "0.18.0", - "@promptbook/utils": "0.18.0", + "@promptbook/core": "0.19.0", + "@promptbook/execute-javascript": "0.19.0", + "@promptbook/openai": "0.19.0", + "@promptbook/remote-client": "0.19.0", + "@promptbook/remote-server": "0.19.0", + "@promptbook/types": "0.19.0", + "@promptbook/utils": "0.19.0", "@sendgrid/mail": "^8.1.0", "@supabase/supabase-js": "2.26.0", "@types/file-saver": "2.0.5", diff --git a/src/utils/client/generateClientId.ts b/src/utils/client/generateClientId.ts index 1780e9ea2..35f68c5b5 100644 --- a/src/utils/client/generateClientId.ts +++ b/src/utils/client/generateClientId.ts @@ -9,5 +9,5 @@ import { client_id } from '../typeAliases'; * @returns clientId */ export function $generateClientId(): client_id { - return $randomUuid() as client_id; + return $randomUuid() as any as client_id; } diff --git a/src/utils/client/validateClientId.ts b/src/utils/client/validateClientId.ts index ec21e2bae..d3b57a17e 100644 --- a/src/utils/client/validateClientId.ts +++ b/src/utils/client/validateClientId.ts @@ -6,7 +6,7 @@ export function validateClientId(value: unknown): client_id { throw new Error(`Invalid clientId ${value}`); } - return value as client_id; + return value as any as client_id; } /** diff --git a/src/workers/functions/createNewWallpaper/createNewWallpaper_image.ts b/src/workers/functions/createNewWallpaper/createNewWallpaper_image.ts index 2b51faee5..ad97894db 100644 --- a/src/workers/functions/createNewWallpaper/createNewWallpaper_image.ts +++ b/src/workers/functions/createNewWallpaper/createNewWallpaper_image.ts @@ -13,8 +13,8 @@ import { isInAspectRatioRange } from '../../../utils/aspect-ratio/isInAspectRati import { createImageInWorker } from '../../../utils/image/createImageInWorker'; import { measureImageBlob } from '../../../utils/image/measureImageBlob'; import { resizeImageBlob } from '../../../utils/image/resizeImageBlob'; -import { IImageColorStats } from '../../../utils/image/utils/IImageColorStats'; -import { string_image_prompt, string_url_image } from '../../../utils/typeAliases'; +import type { IImageColorStats } from '../../../utils/image/utils/IImageColorStats'; +import type { client_id, string_image_prompt, string_url_image } from '../../../utils/typeAliases'; import { imageGeneratorDialogue } from '../../dialogues/image-generator/imageGeneratorDialogue'; interface CreateNewWallpaperImageRequest { From aa8991459d1abbe91b6b785cc3710daacafe3656 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 20:57:32 +0100 Subject: [PATCH 32/48] Working on --- .../ClientVerificationComponent.tsx | 96 +++++++++++++++++-- .../VerificationCodeInput.module.css | 7 ++ .../VerificationCodeInput.tsx | 61 ++++++++++++ src/components/_Sample/Sample.tsx | 8 +- src/utils/client/ClientVerification.ts | 4 + .../client/sendEmailToVerifyClient.types.ts | 11 ++- .../sendEmailToVerifyClientForBrowser.ts | 1 + .../sendEmailToVerifyClientForServer.ts | 15 ++- src/utils/client/verifyEmailCode.types.ts | 11 ++- 9 files changed, 191 insertions(+), 23 deletions(-) create mode 100644 src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.module.css create mode 100644 src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index cd3da853c..669e4f763 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -1,10 +1,12 @@ -import { useCallback, useRef } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { classNames } from '../../utils/classNames'; import { ClientEmailVerification } from '../../utils/client/ClientVerification'; import { $provideClientIdWithoutVerification } from '../../utils/client/provideClientIdWithoutVerification'; import { $sendEmailToVerifyClientForBrowser } from '../../utils/client/sendEmailToVerifyClientForBrowser'; +import { $verifyEmailCodeForBrowser } from '../../utils/client/verifyEmailCodeForBrowser'; import { useStyleModule } from '../../utils/hooks/useStyleModule'; -import type { string_css_class } from '../../utils/typeAliases'; +import type { string_css_class, string_token } from '../../utils/typeAliases'; +import { VerificationCodeInput } from './VerificationCodeInput/VerificationCodeInput'; interface ClientVerificationComponentProps { /** @@ -27,16 +29,88 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr const styles = useStyleModule(import('./ClientVerificationComponent.module.css')); const emailInputRef = useRef(null); - const submit = useCallback(async () => { - // TODO: !!! Lock for some time + const [status, setStatus] = useState< + 'BEFORE' | 'PENDING_EMAIL_SENDING' | 'EMAIL_SENT' | 'PENDING_CODE_SUBMITTING' | 'VERIFIED' + >('BEFORE'); - const { isSendingEmailSuccessful } = await $sendEmailToVerifyClientForBrowser({ - clientId: $provideClientIdWithoutVerification(), + const handleSuccess = useCallback( + (verification: ClientEmailVerification) => { + onVerificationSuccess(verification); + }, + [onVerificationSuccess], + ); + + const submitEmail = useCallback(async () => { + if (status !== 'BEFORE') { + // TODO: Better then alert + alert( + { + PENDING_EMAIL_SENDING: `The email is now sending`, + EMAIL_SENT: `Email already sent`, + PENDING_CODE_SUBMITTING: `The code is now submitting`, + VERIFIED: `You are already verified`, + }[status], + ); + return; + } + + setStatus('PENDING_EMAIL_SENDING'); + + const clientId = $provideClientIdWithoutVerification(); + const sendEmailResult = await $sendEmailToVerifyClientForBrowser({ + clientId, email: emailInputRef.current!.value!, }); - // TODO: !!! Use isSendingEmailSuccessful - }, [emailInputRef]); + if (sendEmailResult.status === 'EMAIL_SENT') { + setStatus('EMAIL_SENT'); + } else if (sendEmailResult.status === 'ERROR') { + // TODO: Better then alert + alert(sendEmailResult.message); + } else if (sendEmailResult.status === 'ALREADY_VERIFIED') { + // TODO: Better then alert + alert('You are already verified'); + handleSuccess({ + clientId, + email: emailInputRef.current!.value!, + isEmailVerified: true, + }); + } else if (sendEmailResult.status === 'LIMIT_REACHED') { + // TODO: Better then alert + // TODO: !!! Lock for some time + alert('!!!'); + } + }, [status, emailInputRef, handleSuccess]); + + const submitCode = useCallback( + async (code: string_token) => { + if (status !== 'EMAIL_SENT') { + throw new Error(`Code can be submitted only when status is "${status}"`); + // <- TODO: ShouldNeverHappenError + } + + setStatus('PENDING_CODE_SUBMITTING'); + + const clientId = $provideClientIdWithoutVerification(); + const codeVerifyResult = await $verifyEmailCodeForBrowser({ + clientId, + email: emailInputRef.current!.value!, + code, + }); + + if (codeVerifyResult.status === 'VERIFIED') { + handleSuccess({ + email: emailInputRef.current!.value!, + clientId, + isEmailVerified: true, + }); + } else if (codeVerifyResult.status === 'ERROR') { + // TODO: Better then alert + alert(codeVerifyResult.message); + } + }, + [status, handleSuccess], + ); return (
@@ -52,12 +126,14 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr return; } - submit(); + submitEmail(); }} /> - + + {status === 'EMAIL_SENT' && }
); } diff --git a/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.module.css b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.module.css new file mode 100644 index 000000000..747a9e888 --- /dev/null +++ b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.module.css @@ -0,0 +1,7 @@ +.VerificationCodeInput { + /*/ + outline: 1px dotted rgb(255, 38, 38); + /**/ + + /* TODO: Use or remove empty css */ +} diff --git a/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx new file mode 100644 index 000000000..290f68627 --- /dev/null +++ b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx @@ -0,0 +1,61 @@ +import { string_token } from '@promptbook/types'; +import { useCallback, useRef } from 'react'; +import { classNames } from '../../../utils/classNames'; +import { useStyleModule } from '../../../utils/hooks/useStyleModule'; +import type { string_css_class } from '../../../utils/typeAliases'; + +interface VerificationCodeInputProps { + /** + * Callback when code is entered and submitted + */ + onSubmit(code: string_token): void; + + /** + * Optional CSS class name which will be added to root element + */ + readonly className?: string_css_class; +} + +/** + * Renders a @@ + */ +export function VerificationCodeInput(props: VerificationCodeInputProps) { + const { onSubmit, className } = props; + + const styles = useStyleModule(import('./VerificationCodeInput.module.css')); + const codeInputRef = useRef(null); + + const submitCode = useCallback(() => { + if (codeInputRef.current === null) { + return; + } + + const code = codeInputRef.current.value; + if (code === '') { + return; + } + + onSubmit(code); + }, [onSubmit, codeInputRef]); + + return ( +
+ { + if (!(event.key === 'Enter' && event.shiftKey === false && event.ctrlKey === false)) { + return; + } + + submitCode(); + }} + /> + +
+ ); +} diff --git a/src/components/_Sample/Sample.tsx b/src/components/_Sample/Sample.tsx index 12b45884f..44f57caf5 100644 --- a/src/components/_Sample/Sample.tsx +++ b/src/components/_Sample/Sample.tsx @@ -7,12 +7,12 @@ interface SampleProps { /** * Content of @@ */ - children?: ReactNode; + readonly children?: ReactNode; /** * Optional CSS class name which will be added to root element */ - className?: string_css_class; + readonly className?: string_css_class; } /** @@ -26,7 +26,3 @@ export function Sample(props: SampleProps) { return
{children}
; } - -/** - * TODO: [🧠] Should be the props readonly (for all react components)? - */ diff --git a/src/utils/client/ClientVerification.ts b/src/utils/client/ClientVerification.ts index 88ca7972b..801af07ea 100644 --- a/src/utils/client/ClientVerification.ts +++ b/src/utils/client/ClientVerification.ts @@ -16,3 +16,7 @@ export type ClientEmailVerification = { */ readonly isEmailVerified: boolean; }; + +/** + * TODO: [🧠] Maybe isEmailVerified is redundant - ClientEmailVerification by definition is verified + */ diff --git a/src/utils/client/sendEmailToVerifyClient.types.ts b/src/utils/client/sendEmailToVerifyClient.types.ts index d387c16bf..dc4b5831e 100644 --- a/src/utils/client/sendEmailToVerifyClient.types.ts +++ b/src/utils/client/sendEmailToVerifyClient.types.ts @@ -5,9 +5,14 @@ export type SendEmailToVerifyClientRequest = { email: string_email; }; -export type SendEmailToVerifyClientResult = { - isSendingEmailSuccessful: boolean; -}; +export type SendEmailToVerifyClientResult = + | { + status: 'EMAIL_SENT' | 'ALREADY_VERIFIED' | 'LIMIT_REACHED'; + } + | { + status: 'ERROR'; + message: string; + }; /** * TODO: !!!last Annotate all diff --git a/src/utils/client/sendEmailToVerifyClientForBrowser.ts b/src/utils/client/sendEmailToVerifyClientForBrowser.ts index 8fbf51666..cce90ecc2 100644 --- a/src/utils/client/sendEmailToVerifyClientForBrowser.ts +++ b/src/utils/client/sendEmailToVerifyClientForBrowser.ts @@ -4,6 +4,7 @@ import type { SendEmailToVerifyClientRequest, SendEmailToVerifyClientResult } fr /** * Function sendEmailToVerifyClientForBrowser sends verification request to the server * + * Note: This function internally checks if client is already verified, if yes, it will return ALREADY_VERIFIED * Note: This function has version both for browser and server */ export async function $sendEmailToVerifyClientForBrowser( diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index 6b3f061d3..9578d9650 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -6,11 +6,13 @@ import { isRunningInNode } from '../isRunningInWhatever'; import { getSupabaseForServer } from '../supabase/getSupabaseForServer'; import { isValidEmail } from '../validators/isValidEmail'; import { $generateVerificationCode } from './generateVerificationCode'; +import { $isClientVerifiedForServer } from './isClientVerifiedForServer'; import type { SendEmailToVerifyClientRequest, SendEmailToVerifyClientResult } from './sendEmailToVerifyClient.types'; /** * Function sendEmailToVerifyClientForServer will generate a verification code, saves it into a DB and send it to the email * + * Note: This function internally checks if client is already verified, if yes, it will return ALREADY_VERIFIED * Note: This function has version both for browser and server */ export async function $sendEmailToVerifyClientForServer( @@ -24,6 +26,17 @@ export async function $sendEmailToVerifyClientForServer( const { clientId, email } = options; + const { status: currentStatus } = await $isClientVerifiedForServer({ + clientId /* TODO: Check combination with email */, + }); + + // TODO: If EMAIL_SENT, send email again BUT only after one minute, else return LIMIT_REACHED + if (currentStatus === 'VERIFIED') { + return { + status: 'ALREADY_VERIFIED', + }; + } + if (!isValidEmail(email)) { // TODO: !!! [🧠] Use here CodeValidationError OR return false throw new Error('!!!'); @@ -61,7 +74,7 @@ export async function $sendEmailToVerifyClientForServer( return { // TODO: !!! Handle errors and report false - isSendingEmailSuccessful: true, + status: 'EMAIL_SENT', }; } diff --git a/src/utils/client/verifyEmailCode.types.ts b/src/utils/client/verifyEmailCode.types.ts index ba319f1a2..8406d2d96 100644 --- a/src/utils/client/verifyEmailCode.types.ts +++ b/src/utils/client/verifyEmailCode.types.ts @@ -6,9 +6,14 @@ export type VerifyEmailCodeRequest = { code: string_token; }; -export type VerifyEmailCodeResult = { - status: 'ERROR' | 'VERIFIED' /*| 'ALREADY_VERIFIED' | 'EXPIRED'*/; -}; +export type VerifyEmailCodeResult = + | { + status: 'VERIFIED' /*| 'ALREADY_VERIFIED' | 'EXPIRED'*/; + } + | { + status: 'ERROR'; + message: string; + }; /** * TODO: !!!last Annotate all From 3b049bb39f4c2f9f95d96d01496a8b91e69cccec Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 21:00:11 +0100 Subject: [PATCH 33/48] Fixing types --- config.ts | 2 +- src/pages/verify-email.tsx | 2 +- src/utils/client/verifyEmailCodeForServer.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config.ts b/config.ts index 581b77b13..7fb2ead8a 100644 --- a/config.ts +++ b/config.ts @@ -6,7 +6,7 @@ import type { DallePrompt } from './src/ai/text-to-image/dalle/interfaces/DalleP import { validateMaxdown } from './src/components/Content/Maxdown/validateMaxdown'; import { FULLHD } from './src/constants'; import type { AspectRatioRange } from './src/utils/aspect-ratio/AspectRatioRange'; -import { expectAspectRatioInRange } from './src/utils/aspect-ratio/expe ctAspectRatioInRange'; +import { expectAspectRatioInRange } from './src/utils/aspect-ratio/expectAspectRatioInRange'; import { DigitalOceanSpaces } from './src/utils/cdn/classes/DigitalOceanSpaces'; import { validateClientId } from './src/utils/client/validateClientId'; import { createColorfulComputeImageColorStats15 } from './src/utils/image/palette/15/createColorfulComputeImageColorStats15'; diff --git a/src/pages/verify-email.tsx b/src/pages/verify-email.tsx index ccd49a8e0..223d33be4 100644 --- a/src/pages/verify-email.tsx +++ b/src/pages/verify-email.tsx @@ -10,7 +10,7 @@ export default function VerifyEmailPage() { { - // TODO: !!! Do domething + // TODO: !!! Do something // TODO: !!! Redirect to ?ref=... (if exists) }} /> diff --git a/src/utils/client/verifyEmailCodeForServer.ts b/src/utils/client/verifyEmailCodeForServer.ts index 59e4e9d97..843eb2430 100644 --- a/src/utils/client/verifyEmailCodeForServer.ts +++ b/src/utils/client/verifyEmailCodeForServer.ts @@ -41,6 +41,7 @@ export async function $verifyEmailCodeForServer(options: VerifyEmailCodeRequest) return { status: 'ERROR', + message: 'Verification code is not correct', // <- TODO: [🧠] Translations in server messages }; } From b9670146043263c91a027b6e35f081989e38d658 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 21:02:30 +0100 Subject: [PATCH 34/48] Manage TODOs --- .../ClientVerificationComponent.tsx | 6 ++---- src/utils/client/generateVerificationCode.ts | 2 +- src/utils/client/sendEmailToVerifyClientForServer.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index 669e4f763..e3f3bce07 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -76,9 +76,9 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr isEmailVerified: true, }); } else if (sendEmailResult.status === 'LIMIT_REACHED') { + // TODO: [📮] Lock for some time // TODO: Better then alert - // TODO: !!! Lock for some time - alert('!!!'); + alert('Limit reached'); } }, [status, emailInputRef, handleSuccess]); @@ -139,7 +139,5 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr } /** - * TODO: !!! Implement ClientVerificationComponent with using onVerificationSuccess - * TODO: !!! Implement * TODO: */ diff --git a/src/utils/client/generateVerificationCode.ts b/src/utils/client/generateVerificationCode.ts index 0ffefea9c..7873e7a2d 100644 --- a/src/utils/client/generateVerificationCode.ts +++ b/src/utils/client/generateVerificationCode.ts @@ -9,5 +9,5 @@ export function $generateVerificationCode(): string_token { } /** - * TODO: !!! Use common util for generateVerificationCode, generating wallpaper URI + * TODO: [🧠] Use common util for generateVerificationCode, generating wallpaper URI */ diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index 9578d9650..da323282a 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -30,7 +30,7 @@ export async function $sendEmailToVerifyClientForServer( clientId /* TODO: Check combination with email */, }); - // TODO: If EMAIL_SENT, send email again BUT only after one minute, else return LIMIT_REACHED + // TODO: [📮] If EMAIL_SENT, send email again BUT only after one minute, else return LIMIT_REACHED if (currentStatus === 'VERIFIED') { return { status: 'ALREADY_VERIFIED', From 9b35b1c93da7ca307699a59da06dff8ca0b0de8d Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 21:02:48 +0100 Subject: [PATCH 35/48] Manage TODOs --- .../ClientVerificationComponent.tsx | 2 +- .../VerificationCodeInput/VerificationCodeInput.tsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index e3f3bce07..4b155128e 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -139,5 +139,5 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr } /** - * TODO: + * TODO: !!! Design */ diff --git a/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx index 290f68627..f0fe66d51 100644 --- a/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx +++ b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx @@ -59,3 +59,7 @@ export function VerificationCodeInput(props: VerificationCodeInputProps) {
); } + +/** + * TODO: !!! Design + */ From be8b608a1e371404727624a1f499b57f306ab277 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 21:03:27 +0100 Subject: [PATCH 36/48] Manage TODOs --- .../ClientVerificationComponent/ClientVerificationComponent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index 4b155128e..a4582a5dd 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -139,5 +139,6 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr } /** + * TODO: !!! Show loading indicator * TODO: !!! Design */ From 8aff330e27096a05fcfda2a8ba7bc234f2b0d968 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 21:28:09 +0100 Subject: [PATCH 37/48] Working on verification process --- .../ClientVerificationComponent.tsx | 73 +++++++++++++------ .../VerificationCodeInput.tsx | 12 ++- .../sendEmailToVerifyClientForServer.ts | 6 +- src/utils/client/verifyEmailCodeForBrowser.ts | 2 +- src/utils/client/verifyEmailCodeForServer.ts | 2 + 5 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index a4582a5dd..95ffe22b6 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -67,9 +67,11 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr } else if (sendEmailResult.status === 'ERROR') { // TODO: Better then alert alert(sendEmailResult.message); + setStatus('BEFORE'); } else if (sendEmailResult.status === 'ALREADY_VERIFIED') { // TODO: Better then alert alert('You are already verified'); + setStatus('VERIFIED'); handleSuccess({ clientId, email: emailInputRef.current!.value!, @@ -79,6 +81,7 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr // TODO: [📮] Lock for some time // TODO: Better then alert alert('Limit reached'); + setStatus('BEFORE'); } }, [status, emailInputRef, handleSuccess]); @@ -99,6 +102,7 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr }); if (codeVerifyResult.status === 'VERIFIED') { + setStatus('VERIFIED'); handleSuccess({ email: emailInputRef.current!.value!, clientId, @@ -106,6 +110,7 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr }); } else if (codeVerifyResult.status === 'ERROR') { // TODO: Better then alert + setStatus('EMAIL_SENT'); alert(codeVerifyResult.message); } }, @@ -114,31 +119,57 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr return (
- { - if (!(event.key === 'Enter' && event.shiftKey === false && event.ctrlKey === false)) { - return; - } - - submitEmail(); - }} - /> - - - {status === 'EMAIL_SENT' && } +

+ { + { + BEFORE: <>Verify your email, + PENDING_EMAIL_SENDING: <>Sending the email, + EMAIL_SENT: ( + <> + Verify code received in your email +
+ (Look in spam folder if not in inbox) + + ), + PENDING_CODE_SUBMITTING: <>Verifying the code, + VERIFIED: <>You are verified!, + }[status] + } +

+ +
+ + +
+ + {['EMAIL_SENT', 'PENDING_CODE_SUBMITTING', 'VERIFIED'].includes(status) && ( + + )}
); } /** - * TODO: !!! Show loading indicator + * TODO: !!! Show indicators for'PENDING_EMAIL_SENDING', 'EMAIL_SENT' and 'PENDING_CODE_SUBMITTING' * TODO: !!! Design */ diff --git a/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx index f0fe66d51..467e392a6 100644 --- a/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx +++ b/src/components/ClientVerificationComponent/VerificationCodeInput/VerificationCodeInput.tsx @@ -10,6 +10,11 @@ interface VerificationCodeInputProps { */ onSubmit(code: string_token): void; + /** + * If true, the input is disabled + */ + readonly isDisabled?: boolean; + /** * Optional CSS class name which will be added to root element */ @@ -17,10 +22,10 @@ interface VerificationCodeInputProps { } /** - * Renders a @@ + * Renders a verification code input */ export function VerificationCodeInput(props: VerificationCodeInputProps) { - const { onSubmit, className } = props; + const { onSubmit, isDisabled, className } = props; const styles = useStyleModule(import('./VerificationCodeInput.module.css')); const codeInputRef = useRef(null); @@ -44,6 +49,7 @@ export function VerificationCodeInput(props: VerificationCodeInputProps) { autoFocus ref={codeInputRef} type="string" + disabled={isDisabled} className={styles.answer} onKeyDown={(event) => { if (!(event.key === 'Enter' && event.shiftKey === false && event.ctrlKey === false)) { @@ -53,7 +59,7 @@ export function VerificationCodeInput(props: VerificationCodeInputProps) { submitCode(); }} /> -
diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index da323282a..954e1f992 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -38,8 +38,10 @@ export async function $sendEmailToVerifyClientForServer( } if (!isValidEmail(email)) { - // TODO: !!! [🧠] Use here CodeValidationError OR return false - throw new Error('!!!'); + return { + status: 'ERROR', + message: 'You have entered invalid email', // <- TODO: [🧠] Translations in server messages + }; } const code = $generateVerificationCode(); diff --git a/src/utils/client/verifyEmailCodeForBrowser.ts b/src/utils/client/verifyEmailCodeForBrowser.ts index f783e149f..c19983d5b 100644 --- a/src/utils/client/verifyEmailCodeForBrowser.ts +++ b/src/utils/client/verifyEmailCodeForBrowser.ts @@ -11,7 +11,7 @@ export async function $verifyEmailCodeForBrowser(options: VerifyEmailCodeRequest throw new Error('Function `$verifyEmailCodeForBrowser` can not be used on server, use server version instead.'); } - const response = await fetch(`/api/client/send-email-to-verify-client`, { + const response = await fetch(`/api/client/verify-email-code`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/utils/client/verifyEmailCodeForServer.ts b/src/utils/client/verifyEmailCodeForServer.ts index 843eb2430..332425c38 100644 --- a/src/utils/client/verifyEmailCodeForServer.ts +++ b/src/utils/client/verifyEmailCodeForServer.ts @@ -33,6 +33,8 @@ export async function $verifyEmailCodeForServer(options: VerifyEmailCodeRequest) // TODO: !! Util isInsertSuccessfull (status===201) console.info({ insertVerificationResult }); + // TODO: [🧠] Should we send another email to the user that he is verified? + return { status: 'VERIFIED', }; From 0b0d8533cf9f08bd65ddb9fb0a3e2ac5e155a2f7 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 21:43:20 +0100 Subject: [PATCH 38/48] Automatic verification --- .../ClientVerificationComponent.tsx | 20 +++++++++++++++++-- src/utils/client/backupClientEmail.ts | 19 ++++++++++++++++++ src/utils/client/isClientVerifiedForServer.ts | 8 ++++++-- src/utils/client/provideClientId.ts | 14 +++---------- .../sendEmailToVerifyClientForServer.ts | 14 ++++++------- 5 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 src/utils/client/backupClientEmail.ts diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index 95ffe22b6..df27a35ef 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -1,9 +1,12 @@ import { useCallback, useRef, useState } from 'react'; import { classNames } from '../../utils/classNames'; +import { $backupClientEmail } from '../../utils/client/backupClientEmail'; import { ClientEmailVerification } from '../../utils/client/ClientVerification'; +import { $provideClientEmail } from '../../utils/client/provideClientEmail'; import { $provideClientIdWithoutVerification } from '../../utils/client/provideClientIdWithoutVerification'; import { $sendEmailToVerifyClientForBrowser } from '../../utils/client/sendEmailToVerifyClientForBrowser'; import { $verifyEmailCodeForBrowser } from '../../utils/client/verifyEmailCodeForBrowser'; +import { useInitialAction } from '../../utils/hooks/useInitialAction'; import { useStyleModule } from '../../utils/hooks/useStyleModule'; import type { string_css_class, string_token } from '../../utils/typeAliases'; import { VerificationCodeInput } from './VerificationCodeInput/VerificationCodeInput'; @@ -35,6 +38,7 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr const handleSuccess = useCallback( (verification: ClientEmailVerification) => { + $backupClientEmail(verification.email); onVerificationSuccess(verification); }, [onVerificationSuccess], @@ -69,8 +73,6 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr alert(sendEmailResult.message); setStatus('BEFORE'); } else if (sendEmailResult.status === 'ALREADY_VERIFIED') { - // TODO: Better then alert - alert('You are already verified'); setStatus('VERIFIED'); handleSuccess({ clientId, @@ -117,6 +119,20 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr [status, handleSuccess], ); + useInitialAction( + () => true, + () => { + const email = $provideClientEmail(); + + if (email === null) { + return; + } + + emailInputRef.current!.value = email; + submitEmail(); + }, + ); + return (

diff --git a/src/utils/client/backupClientEmail.ts b/src/utils/client/backupClientEmail.ts new file mode 100644 index 000000000..f48f3ca61 --- /dev/null +++ b/src/utils/client/backupClientEmail.ts @@ -0,0 +1,19 @@ +import { string_email } from '../typeAliases'; + +/** + * Backup client email if it's in localStorage + * - It is redundant place to prefill the forms and to be compliant with GDPR + * - It also allows to quickresolve + * + * Note: This function is available only in browser + * + * @returns clientId + */ +export function $backupClientEmail(email: string_email) { + window.localStorage.setItem(`clientEmail`, email); +} + +/** + * TODO: !!! Rename to ForBrowser + * TODO: [🌯] Maybe read email from the the server + */ diff --git a/src/utils/client/isClientVerifiedForServer.ts b/src/utils/client/isClientVerifiedForServer.ts index fbd51ab22..9ec15d61d 100644 --- a/src/utils/client/isClientVerifiedForServer.ts +++ b/src/utils/client/isClientVerifiedForServer.ts @@ -18,7 +18,9 @@ export async function $isClientVerifiedForServer(options: IsClientVerifiedReques const { data: clientEmailVerifications } = await getSupabaseForServer() .from('ClientEmailVerification_withRequests') - .select('"1"') + .select( + 'verificationRequestId' /* <- Note: This is not used BUT it needs to be set on some existing column to return some result not null */, + ) .eq('clientId', clientId); if (clientEmailVerifications && clientEmailVerifications.length > 0) { @@ -30,7 +32,9 @@ export async function $isClientVerifiedForServer(options: IsClientVerifiedReques const { data: clientEmailVerificationsRequests } = await getSupabaseForServer() // TODO: [🍠] Put here some time limit .from('ClientEmailVerificationRequest') - .select('"1"') + .select( + 'verificationRequestId' /* <- Note: This is not used BUT it needs to be set on some existing column to return some result not null */, + ) .eq('clientId', clientId); if (clientEmailVerificationsRequests && clientEmailVerificationsRequests.length > 0) { diff --git a/src/utils/client/provideClientId.ts b/src/utils/client/provideClientId.ts index 47a9e1f72..c1b1a54f5 100644 --- a/src/utils/client/provideClientId.ts +++ b/src/utils/client/provideClientId.ts @@ -1,7 +1,5 @@ import { validateEmailDialogue } from '../../workers/dialogues/validate-email/validateEmailDialogue'; -import { getSupabaseForBrowser } from '../supabase/getSupabaseForBrowser'; -import { client_id, string_email } from '../typeAliases'; -import { isValidEmail } from '../validators/isValidEmail'; +import { client_id } from '../typeAliases'; import { $isClientVerifiedForBrowser } from './isClientVerifiedForBrowser'; import { $provideClientIdWithoutVerification } from './provideClientIdWithoutVerification'; @@ -40,18 +38,12 @@ export async function $provideClientId(options: IProvideClientIdOptions): Promis return clientId; } - // TODO: !!! validateEmailDialogue - const { email, isEmailVerified } = await validateEmailDialogue({ + await validateEmailDialogue({ // [🍀] Maybe allow to pass default value for email isVerifiedEmailRequired, }); - if (!isValidEmail(email)) { - throw new Error(`Invalid email`); - } - - window.localStorage.setItem(`clientEmail`, email as string_email); - await getSupabaseForBrowser().from('Client').insert({ clientId, email }); + // Note: Dialogue validateEmailDialogue will never resolve if email is not verified return clientId; } diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index 954e1f992..19e38f848 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -26,6 +26,13 @@ export async function $sendEmailToVerifyClientForServer( const { clientId, email } = options; + if (!isValidEmail(email)) { + return { + status: 'ERROR', + message: 'You have entered invalid email', // <- TODO: [🧠] Translations in server messages + }; + } + const { status: currentStatus } = await $isClientVerifiedForServer({ clientId /* TODO: Check combination with email */, }); @@ -37,13 +44,6 @@ export async function $sendEmailToVerifyClientForServer( }; } - if (!isValidEmail(email)) { - return { - status: 'ERROR', - message: 'You have entered invalid email', // <- TODO: [🧠] Translations in server messages - }; - } - const code = $generateVerificationCode(); // TODO: Maybe do theese things (insert+email sending) in parallel OR transaction but not indipedently at series From 26ec4c49b5a7a27125056970ec35c9c6137e7b49 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 21:56:34 +0100 Subject: [PATCH 39/48] Maxdown as a template literal tag function --- config.ts | 4 +- .../Content/Maxdown/maxdown.test.ts | 38 ++++++ src/components/Content/Maxdown/maxdown.ts | 17 +++ .../Content/Maxdown/validateMaxdown.ts | 6 +- src/pages/api/experiments/send-email.ts | 11 +- src/pages/api/publish.ts | 11 +- src/pages/gallery.tsx | 4 +- .../sendEmailToVerifyClientForServer.ts | 12 +- src/utils/computeWallpaperDomainPart.test.ts | 123 +++++++++--------- src/utils/computeWallpaperUriid.test.ts | 10 +- 10 files changed, 139 insertions(+), 97 deletions(-) create mode 100644 src/components/Content/Maxdown/maxdown.test.ts create mode 100644 src/components/Content/Maxdown/maxdown.ts diff --git a/config.ts b/config.ts index 7fb2ead8a..0c37c99ba 100644 --- a/config.ts +++ b/config.ts @@ -3,7 +3,7 @@ import spaceTrim from 'spacetrim'; import { Vector } from 'xyzt'; import packageJson from './package.json'; import type { DallePrompt } from './src/ai/text-to-image/dalle/interfaces/DallePrompt'; -import { validateMaxdown } from './src/components/Content/Maxdown/validateMaxdown'; +import { maxdown } from './src/components/Content/Maxdown/maxdown'; import { FULLHD } from './src/constants'; import type { AspectRatioRange } from './src/utils/aspect-ratio/AspectRatioRange'; import { expectAspectRatioInRange } from './src/utils/aspect-ratio/expectAspectRatioInRange'; @@ -33,7 +33,7 @@ export const NEXT_PUBLIC_URL = config.get('NEXT_PUBLIC_URL').url().required().va export const APP_VERSION = packageJson.version; export const APP_NAME = 'WebGPT'; export const ADMIN_EMAIL: string_email = 'pavol@webgpt.cz'; -export const APP_SIGNATURE: string_maxdown = validateMaxdown(`[⏣ ${APP_NAME}](${NEXT_PUBLIC_URL.href})`); +export const APP_SIGNATURE: string_maxdown = maxdown`[⏣ ${APP_NAME}](${NEXT_PUBLIC_URL.href})`; export const USE_DALLE_VERSION: 2 | 3 = 3; diff --git a/src/components/Content/Maxdown/maxdown.test.ts b/src/components/Content/Maxdown/maxdown.test.ts new file mode 100644 index 000000000..4c6a1f3d9 --- /dev/null +++ b/src/components/Content/Maxdown/maxdown.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from '@jest/globals'; +import { maxdown } from './maxdown'; + +describe(`maxdown string template literal tag function`, () => { + it(`should work with simple maxdown`, () => { + expect( + maxdown` + + # Hello world + + `, + ).toBe('# Hello world'); + }); + + it(`should work with multiline maxdown`, () => { + expect( + maxdown` + + # Hello world + + I am multiline **maxdown** + + `, + ).toBe('# Hello world\n\nI am multiline **maxdown**'); + }); + + it(`should work with templated maxdown`, () => { + expect( + maxdown` + + # Hello world + + I am multiline **${'maxdown'}** + + `, + ).toBe('# Hello world\n\nI am multiline **maxdown**'); + }); +}); diff --git a/src/components/Content/Maxdown/maxdown.ts b/src/components/Content/Maxdown/maxdown.ts new file mode 100644 index 000000000..7750ac528 --- /dev/null +++ b/src/components/Content/Maxdown/maxdown.ts @@ -0,0 +1,17 @@ +import spaceTrim from 'spacetrim'; +import { validateMaxdown } from './validateMaxdown'; + +/** + * Use maxdown as a template literal tag function + * + * If content is not a valid maxdown, throws an error + * If content is a valid maxdown: + * - It will be spaceTrimmed + */ +export function maxdown(strings: TemplateStringsArray, ...values: any[]) { + const content = strings.reduce((result, string, index) => { + return result + string + (values[index] ?? ''); + }, ''); + + return validateMaxdown(spaceTrim(content)); +} diff --git a/src/components/Content/Maxdown/validateMaxdown.ts b/src/components/Content/Maxdown/validateMaxdown.ts index f88f20e2a..336b748eb 100644 --- a/src/components/Content/Maxdown/validateMaxdown.ts +++ b/src/components/Content/Maxdown/validateMaxdown.ts @@ -4,7 +4,7 @@ import { string_maxdown } from '../../../utils/typeAliases'; * Validate maxdown content * * If content is not a valid maxdown, throws an error - * If content is a valid maxdown, + * If content is a valid maxdown, returnes it */ export function validateMaxdown(content: unknown): string_maxdown { if (typeof content !== 'string') { @@ -14,8 +14,10 @@ export function validateMaxdown(content: unknown): string_maxdown { return content as string_maxdown; } + + + /** - * TODO: !!! TODO: Use ACRY maxdown`...` instead * TODO: !! Do here a real validation / sanitization * TODO: [🧠][🚓] Is/which combination it better to use asserts/check, validate or is utility function? */ diff --git a/src/pages/api/experiments/send-email.ts b/src/pages/api/experiments/send-email.ts index 5aa7395a8..776dba689 100644 --- a/src/pages/api/experiments/send-email.ts +++ b/src/pages/api/experiments/send-email.ts @@ -1,6 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import spaceTrim from 'spacetrim'; -import { validateMaxdown } from '../../../components/Content/Maxdown/validateMaxdown'; +import { maxdown } from '../../../components/Content/Maxdown/maxdown'; import { sendEmailForServer } from '../../../utils/emails/sendEmailForServer'; export default async function sendEmailExperimentHandler(request: NextApiRequest, response: NextApiResponse) { @@ -8,11 +7,9 @@ export default async function sendEmailExperimentHandler(request: NextApiRequest await sendEmailForServer({ to: 'me@pavolhejny.com', subject: '⏣ WebGPT notification', - content: validateMaxdown( - spaceTrim(` - Look on [WebGPT](https://webgpt.cz/) page! - `), - ), + content: maxdown` + Look on [WebGPT](https://webgpt.cz/) page! + `, }); return response.status(202).send({ diff --git a/src/pages/api/publish.ts b/src/pages/api/publish.ts index 8d6693c42..f868af4f1 100644 --- a/src/pages/api/publish.ts +++ b/src/pages/api/publish.ts @@ -2,9 +2,8 @@ import formidable from 'formidable'; import { readFile } from 'fs/promises'; import JSZip from 'jszip'; import type { NextApiRequest, NextApiResponse } from 'next'; -import spaceTrim from 'spacetrim'; import { APP_SIGNATURE, PUBLISH_TO_GITHUB_ORGANIZATION } from '../../../config'; -import { validateMaxdown } from '../../components/Content/Maxdown/validateMaxdown'; +import { maxdown } from '../../components/Content/Maxdown/maxdown'; import { sendEmailForServer } from '../../utils/emails/sendEmailForServer'; import type { IFileToPublish } from '../../utils/publishing/github/interfaces/IFileToPublish'; import { publishToRepository } from '../../utils/publishing/github/publishToRepository'; @@ -82,17 +81,15 @@ export default async function publishWebsiteHandler( to: 'me@pavolhejny.com', subject: `⏣ Website ${CNAME} is published!`, - // TODO: !!! use maxdown`` pattern - content: validateMaxdown( + content: // TODO: !!! Translations - spaceTrim(` + maxdown` Your website [${CNAME}](https://${CNAME}/) was successfully published by ${APP_SIGNATURE}! --- !!! add wait dislaimer to publish email !!! - `), - ), + `, }); return response.status(201).json({ diff --git a/src/pages/gallery.tsx b/src/pages/gallery.tsx index 3089582f2..05fbda2aa 100644 --- a/src/pages/gallery.tsx +++ b/src/pages/gallery.tsx @@ -3,7 +3,7 @@ import Link from 'next/link'; import { useRouter } from 'next/router'; import { getHardcodedWallpapers } from '../../scripts/utils/hardcoded-wallpaper/getHardcodedWallpapers'; import { StaticAppHead } from '../components/AppHead/StaticAppHead'; -import { validateMaxdown } from '../components/Content/Maxdown/validateMaxdown'; +import { maxdown } from '../components/Content/Maxdown/maxdown'; import { GallerySection } from '../components/Gallery/Gallery'; import styles from '../styles/static.module.css' /* <- TODO: [🤶] Get rid of page css and only use components (as ) */; import { classNames } from '../utils/classNames'; @@ -70,7 +70,7 @@ export async function getStaticProps({ locale }: { locale: string }) { colorStats /* <- TODO: !! Also reduce colorStats */, naturalSize, title, - content: validateMaxdown('[🟥]' /* <- Note: [🟥] No need to pass everything into index page */), + content: maxdown`[🟥]` /* <- Note: [🟥] No need to pass everything into index page */, keywords, isPublic, author, diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index 19e38f848..350998aa2 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -1,6 +1,5 @@ -import spaceTrim from 'spacetrim'; import { APP_NAME, NEXT_PUBLIC_URL } from '../../../config'; -import { validateMaxdown } from '../../components/Content/Maxdown/validateMaxdown'; +import { maxdown } from '../../components/Content/Maxdown/maxdown'; import { sendEmailForServer } from '../emails/sendEmailForServer'; import { isRunningInNode } from '../isRunningInWhatever'; import { getSupabaseForServer } from '../supabase/getSupabaseForServer'; @@ -51,14 +50,12 @@ export async function $sendEmailToVerifyClientForServer( await sendEmailForServer({ to: email, subject: `⏣ ${APP_NAME} verification code`, - - // TODO: !!! use maxdown`` pattern - content: validateMaxdown( + content: // TODO: !!! Better text // TODO: !!! Translations // TODO: !!! Add verification link alongsite the code // TODO: !!! Unify greeting and signature in all emails ACRY - spaceTrim(` + maxdown` Hello, Your code to sign-in into ${APP_NAME} is: @@ -70,8 +67,7 @@ export async function $sendEmailToVerifyClientForServer( --- !!! Add wait dislaimer for people whos email this isnt !!! - `), - ), + `, }); return { diff --git a/src/utils/computeWallpaperDomainPart.test.ts b/src/utils/computeWallpaperDomainPart.test.ts index b485b4d38..218188989 100644 --- a/src/utils/computeWallpaperDomainPart.test.ts +++ b/src/utils/computeWallpaperDomainPart.test.ts @@ -1,77 +1,74 @@ import { describe, expect, it } from '@jest/globals'; -import spaceTrim from 'spacetrim'; -import { validateMaxdown } from '../components/Content/Maxdown/validateMaxdown'; +import { maxdown } from '../components/Content/Maxdown/maxdown'; import { computeWallpaperDomainPart } from './computeWallpaperDomainPart'; describe(`computeWallpaperDomainPart`, () => { it(`is compute deterministic uriid`, () => { expect( computeWallpaperDomainPart( - validateMaxdown( - spaceTrim(` -

-

Ocean Vibes

-

Welcome to Ocean Vibes, a website dedicated to exploring the wonders of the ocean. Our stunning wallpaper featuring a beautiful sunset over the ocean sets the tone for our passion for all things related to the sea.

-

About Us

-

At Ocean Vibes, we are committed to sharing our love and knowledge of the ocean with others. We offer a variety of resources, including articles on marine life, conservation efforts, travel destinations, and much more.

-

User Stories

-

Our community is made up of people from all walks of life who share a common love for the ocean. Here are some stories from our users:

-
    -
  • "I\'ve always been fascinated by the ocean, but never knew where to start. Ocean Vibes has given me a wealth of information and inspiration to explore this amazing world." - Emily, 28
  • -
  • "The articles on Ocean Vibes have helped me plan my dream trip to Hawaii. I can\'t wait to explore the coral reefs and see all the incredible marine life." - Mark, 35
  • -
  • "As an avid scuba diver, I appreciate the dedication Ocean Vibes has to promoting responsible tourism and conservation efforts. It\'s important to protect the ocean so future generations can enjoy it too." - Sarah, 42
  • -
-

Resources

-

Check out some of our most popular articles and resources:

- -

Contact Us

-

Do you have questions or suggestions? We\'d love to hear from you! Reach out to us at oceanvibes@example.com.

-

Thank you for visiting Ocean Vibes. Let\'s work together to preserve the beauty of the ocean for generations to come 🐬🌊.

-
-

Best Beaches

-
    -
  1. Anse Source d\'Argent, Seychelles
  2. -
  3. Whitehaven Beach, Australia
  4. -
  5. Navagio Beach, Greece
  6. -
  7. Pink Sands Beach, Bahamas
  8. -
  9. Maya Bay, Thailand
  10. -
  11. Tulum Beach, Mexico
  12. -
  13. Matira Beach, French Polynesia
  14. -
  15. Seven Mile Beach, Jamaica
  16. -
  17. Horseshoe Bay, Bermuda
  18. -
  19. Lanikai Beach, Hawaii
  20. -
-

Responsible Tourism

-
-

"We do not inherit the earth from our ancestors, we borrow it from our children." - Native American Proverb

-
-

As travelers, it\'s important to be mindful of our impact on the environment. Here are some tips for responsible tourism:

-
    -
  • Choose eco-friendly accommodations and tours.
  • -
  • Respect local customs and traditions.
  • -
  • Reduce your plastic use by bringing a reusable water bottle and shopping bag.
  • -
  • Support local businesses and communities.
  • -
-

Coral Reefs

-

Coral reefs are one of the most biodiverse ecosystems on the planet. Here are some Fascinating facts about these underwater wonderlands:

-
    -
  • Coral reefs cover less than 1% of the ocean floor but support over 25% of marine life.
  • -
  • The Great Barrier Reef in Australia is the largest coral reef system in the world.
  • -
  • Coral reefs are made up of tiny animals called polyps that secrete calcium carbonate to form a hard skeleton.
  • -
  • Coral reefs are threatened by climate change, overfishing, and pollution.
  • -
-
- `), - ), + maxdown` +
+

Ocean Vibes

+

Welcome to Ocean Vibes, a website dedicated to exploring the wonders of the ocean. Our stunning wallpaper featuring a beautiful sunset over the ocean sets the tone for our passion for all things related to the sea.

+

About Us

+

At Ocean Vibes, we are committed to sharing our love and knowledge of the ocean with others. We offer a variety of resources, including articles on marine life, conservation efforts, travel destinations, and much more.

+

User Stories

+

Our community is made up of people from all walks of life who share a common love for the ocean. Here are some stories from our users:

+
    +
  • "I\'ve always been fascinated by the ocean, but never knew where to start. Ocean Vibes has given me a wealth of information and inspiration to explore this amazing world." - Emily, 28
  • +
  • "The articles on Ocean Vibes have helped me plan my dream trip to Hawaii. I can\'t wait to explore the coral reefs and see all the incredible marine life." - Mark, 35
  • +
  • "As an avid scuba diver, I appreciate the dedication Ocean Vibes has to promoting responsible tourism and conservation efforts. It\'s important to protect the ocean so future generations can enjoy it too." - Sarah, 42
  • +
+

Resources

+

Check out some of our most popular articles and resources:

+ +

Contact Us

+

Do you have questions or suggestions? We\'d love to hear from you! Reach out to us at oceanvibes@example.com.

+

Thank you for visiting Ocean Vibes. Let\'s work together to preserve the beauty of the ocean for generations to come 🐬🌊.

+
+

Best Beaches

+
    +
  1. Anse Source d\'Argent, Seychelles
  2. +
  3. Whitehaven Beach, Australia
  4. +
  5. Navagio Beach, Greece
  6. +
  7. Pink Sands Beach, Bahamas
  8. +
  9. Maya Bay, Thailand
  10. +
  11. Tulum Beach, Mexico
  12. +
  13. Matira Beach, French Polynesia
  14. +
  15. Seven Mile Beach, Jamaica
  16. +
  17. Horseshoe Bay, Bermuda
  18. +
  19. Lanikai Beach, Hawaii
  20. +
+

Responsible Tourism

+
+

"We do not inherit the earth from our ancestors, we borrow it from our children." - Native American Proverb

+
+

As travelers, it\'s important to be mindful of our impact on the environment. Here are some tips for responsible tourism:

+
    +
  • Choose eco-friendly accommodations and tours.
  • +
  • Respect local customs and traditions.
  • +
  • Reduce your plastic use by bringing a reusable water bottle and shopping bag.
  • +
  • Support local businesses and communities.
  • +
+

Coral Reefs

+

Coral reefs are one of the most biodiverse ecosystems on the planet. Here are some Fascinating facts about these underwater wonderlands:

+
    +
  • Coral reefs cover less than 1% of the ocean floor but support over 25% of marine life.
  • +
  • The Great Barrier Reef in Australia is the largest coral reef system in the world.
  • +
  • Coral reefs are made up of tiny animals called polyps that secrete calcium carbonate to form a hard skeleton.
  • +
  • Coral reefs are threatened by climate change, overfishing, and pollution.
  • +
+
+ `, ), ).toBe(`ocean-vibes`); }); it(`works without title in content`, () => { - expect(computeWallpaperDomainPart(validateMaxdown('This content has no title.'))).toBe(`untitled`); + expect(computeWallpaperDomainPart(maxdown`This content has no title.`)).toBe(`untitled`); }); }); diff --git a/src/utils/computeWallpaperUriid.test.ts b/src/utils/computeWallpaperUriid.test.ts index cf1bdac64..08c505e80 100644 --- a/src/utils/computeWallpaperUriid.test.ts +++ b/src/utils/computeWallpaperUriid.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from '@jest/globals'; import spaceTrim from 'spacetrim'; +import { maxdown } from '../components/Content/Maxdown/maxdown'; import { validateMaxdown } from '../components/Content/Maxdown/validateMaxdown'; import { FULLHD } from '../constants'; import { validateClientId } from './client/validateClientId'; import { computeWallpaperUriid } from './computeWallpaperUriid'; import { hydrateColorStats } from './image/utils/hydrateColorStats'; -import { validateUuid } from './validators/validateUuid'; describe(`computeWallpaperUriid`, () => { it(`is compute deterministic uriid`, () => { @@ -336,11 +336,9 @@ describe(`computeWallpaperUriid`, () => { version: 'colorful-192x108-16bit-v14palette', }), naturalSize: FULLHD, - content: validateMaxdown( - spaceTrim(` - This content has no title. - `), - ), + content: maxdown` + This content has no title. + `, }), ).toBe(`2rrgwx5bdwod`); }); From 88bd93f7d3e842b0776508b3e59a69550e23f110 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 22:19:37 +0100 Subject: [PATCH 40/48] UX of Verification process --- .../ClientVerificationComponent.tsx | 30 +++++++++++++++---- .../PreviewGallery/PreviewGallery.tsx | 2 +- src/utils/client/backupClientEmail.ts | 8 +++-- src/utils/client/isClientVerifiedForServer.ts | 2 +- src/utils/client/provideClientEmail.ts | 9 ++++++ .../provideClientIdWithoutVerification.ts | 12 ++++---- .../client/sendEmailToVerifyClient.types.ts | 2 +- .../sendEmailToVerifyClientForServer.ts | 12 ++++++-- 8 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index df27a35ef..b1d7f911f 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -33,7 +33,12 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr const emailInputRef = useRef(null); const [status, setStatus] = useState< - 'BEFORE' | 'PENDING_EMAIL_SENDING' | 'EMAIL_SENT' | 'PENDING_CODE_SUBMITTING' | 'VERIFIED' + | 'BEFORE' + | 'PENDING_EMAIL_SENDING' + | 'EMAIL_SENT' + | 'ALREADY_EMAIL_SENT' + | 'PENDING_CODE_SUBMITTING' + | 'VERIFIED' >('BEFORE'); const handleSuccess = useCallback( @@ -50,7 +55,8 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr alert( { PENDING_EMAIL_SENDING: `The email is now sending`, - EMAIL_SENT: `Email already sent`, + EMAIL_SENT: `Email was sent`, + ALREADY_EMAIL_SENT: `Email was sent`, PENDING_CODE_SUBMITTING: `The code is now submitting`, VERIFIED: `You are already verified`, }[status], @@ -68,6 +74,8 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr if (sendEmailResult.status === 'EMAIL_SENT') { setStatus('EMAIL_SENT'); + } else if (sendEmailResult.status === 'ALREADY_EMAIL_SENT') { + setStatus('ALREADY_EMAIL_SENT'); } else if (sendEmailResult.status === 'ERROR') { // TODO: Better then alert alert(sendEmailResult.message); @@ -89,8 +97,8 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr const submitCode = useCallback( async (code: string_token) => { - if (status !== 'EMAIL_SENT') { - throw new Error(`Code can be submitted only when status is "${status}"`); + if (!['EMAIL_SENT', 'ALREADY_EMAIL_SENT'].includes(status)) { + throw new Error(`Code can be submitted only when status is "EMAIL_SENT" but it is "${status}"`); // <- TODO: ShouldNeverHappenError } @@ -147,6 +155,13 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr (Look in spam folder if not in inbox) ), + ALREADY_EMAIL_SENT: ( + <> + Verify code was sent into your email +
+ (Look in spam folder if not in inbox) + + ), PENDING_CODE_SUBMITTING: <>Verifying the code, VERIFIED: <>You are verified!, }[status] @@ -178,8 +193,11 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr
- {['EMAIL_SENT', 'PENDING_CODE_SUBMITTING', 'VERIFIED'].includes(status) && ( - + {['EMAIL_SENT', 'ALREADY_EMAIL_SENT', 'PENDING_CODE_SUBMITTING', 'VERIFIED'].includes(status) && ( + )}
); diff --git a/src/components/PreviewGallery/PreviewGallery.tsx b/src/components/PreviewGallery/PreviewGallery.tsx index c62827d29..606b432e0 100644 --- a/src/components/PreviewGallery/PreviewGallery.tsx +++ b/src/components/PreviewGallery/PreviewGallery.tsx @@ -35,7 +35,7 @@ export function PreviewGallery(props: PreviewGalleryProps) { let wallpapers = welcomeWallpapers.map(({ id, primaryColor }) => ({ id, - src: `${NEXT_PUBLIC_URL.href}/${id}?role=visitor`, + src: `${NEXT_PUBLIC_URL.href}${id}?role=visitor`, primaryColor: Color.fromString(primaryColor), })); diff --git a/src/utils/client/backupClientEmail.ts b/src/utils/client/backupClientEmail.ts index f48f3ca61..c1891fefb 100644 --- a/src/utils/client/backupClientEmail.ts +++ b/src/utils/client/backupClientEmail.ts @@ -1,4 +1,5 @@ import { string_email } from '../typeAliases'; +import { isValidEmail } from '../validators/isValidEmail'; /** * Backup client email if it's in localStorage @@ -9,8 +10,11 @@ import { string_email } from '../typeAliases'; * * @returns clientId */ -export function $backupClientEmail(email: string_email) { - window.localStorage.setItem(`clientEmail`, email); +export function $backupClientEmail(clientEmail: string_email) { + if (!isValidEmail(clientEmail)) { + throw new Error(`Can not backup invalid email "${clientEmail}"`); + } + window.localStorage.setItem(`clientEmail`, clientEmail); } /** diff --git a/src/utils/client/isClientVerifiedForServer.ts b/src/utils/client/isClientVerifiedForServer.ts index 9ec15d61d..d0c3944bc 100644 --- a/src/utils/client/isClientVerifiedForServer.ts +++ b/src/utils/client/isClientVerifiedForServer.ts @@ -33,7 +33,7 @@ export async function $isClientVerifiedForServer(options: IsClientVerifiedReques // TODO: [🍠] Put here some time limit .from('ClientEmailVerificationRequest') .select( - 'verificationRequestId' /* <- Note: This is not used BUT it needs to be set on some existing column to return some result not null */, + 'id' /* <- Note: This is not used BUT it needs to be set on some existing column to return some result not null */, ) .eq('clientId', clientId); diff --git a/src/utils/client/provideClientEmail.ts b/src/utils/client/provideClientEmail.ts index 9f1f16457..4bba7a90d 100644 --- a/src/utils/client/provideClientEmail.ts +++ b/src/utils/client/provideClientEmail.ts @@ -1,4 +1,5 @@ import { string_email } from '../typeAliases'; +import { isValidEmail } from '../validators/isValidEmail'; /** * Provides client email if it's in localStorage @@ -9,6 +10,14 @@ import { string_email } from '../typeAliases'; */ export function $provideClientEmail(): string_email | null { const clientEmail = window.localStorage.getItem(`clientEmail`); + + if (clientEmail && !isValidEmail(clientEmail)) { + window.localStorage.removeItem(`clientEmail`); + // Note: It make sense to log this error because it is captured by Sentry + throw new Error(`Invalid clientEmail in localStorage "${clientEmail}"`); + // <- TODO: ShouldNeverHappenError + } + return clientEmail; } diff --git a/src/utils/client/provideClientIdWithoutVerification.ts b/src/utils/client/provideClientIdWithoutVerification.ts index c3fad8aef..105f0230e 100644 --- a/src/utils/client/provideClientIdWithoutVerification.ts +++ b/src/utils/client/provideClientIdWithoutVerification.ts @@ -24,15 +24,15 @@ export function $provideClientIdWithoutVerification(): client_id { throw new Error(`provideClientId is available only in browser`); } - if (clientId) { - return clientId; - } + let clientIdInLocalStorage = window.localStorage.getItem(`clientId`); - clientId = validateClientId(window.localStorage.getItem(`clientId`)); + clientId = clientIdInLocalStorage === null ? null : validateClientId(clientIdInLocalStorage); - if (!isValidClientId(clientId)) { + if (clientId && !isValidClientId(clientId)) { + window.localStorage.removeItem(`clientId`); // Note: It make sense to log this error because it is captured by Sentry - console.error(`Invalid clientId in localStorage "${clientId}"`); + throw new Error(`Invalid clientId in localStorage "${clientId}"`); + // <- TODO: ShouldNeverHappenError } if (clientId) { diff --git a/src/utils/client/sendEmailToVerifyClient.types.ts b/src/utils/client/sendEmailToVerifyClient.types.ts index dc4b5831e..ce78032cc 100644 --- a/src/utils/client/sendEmailToVerifyClient.types.ts +++ b/src/utils/client/sendEmailToVerifyClient.types.ts @@ -7,7 +7,7 @@ export type SendEmailToVerifyClientRequest = { export type SendEmailToVerifyClientResult = | { - status: 'EMAIL_SENT' | 'ALREADY_VERIFIED' | 'LIMIT_REACHED'; + status: 'EMAIL_SENT' | 'ALREADY_VERIFIED' | 'ALREADY_EMAIL_SENT' | 'LIMIT_REACHED'; } | { status: 'ERROR'; diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index 350998aa2..faf179e8e 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -36,8 +36,12 @@ export async function $sendEmailToVerifyClientForServer( clientId /* TODO: Check combination with email */, }); - // TODO: [📮] If EMAIL_SENT, send email again BUT only after one minute, else return LIMIT_REACHED - if (currentStatus === 'VERIFIED') { + if (currentStatus === 'EMAIL_SENT') { + // TODO: [📮] When tryng after longer time, send another email + return { + status: 'ALREADY_EMAIL_SENT', + }; + } else if (currentStatus === 'VERIFIED') { return { status: 'ALREADY_VERIFIED', }; @@ -62,7 +66,9 @@ export async function $sendEmailToVerifyClientForServer( **${code}** Or click on this link to verify your email: - ${NEXT_PUBLIC_URL.href}/verify-email?code=${code}&email=${encodeURIComponent(email)} + ${NEXT_PUBLIC_URL.href}verify-email?code=${code}&email=${encodeURIComponent( + email, + )}&clientId=${encodeURIComponent(clientId)} --- From 99c5c41d97358c22dd7cfd9e7dc7a038357e6acb Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 22:51:38 +0100 Subject: [PATCH 41/48] Clicking on link in verification email | AutomaticVerification --- .../AutomaticVerification.ts | 11 ++++ .../ClientVerificationComponent.tsx | 60 ++++++++++++++----- src/pages/api/scrape/scrape-image.ts | 2 +- src/pages/api/scrape/scrape-instagram-user.ts | 2 +- src/pages/verify-email.tsx | 37 ++++++++++++ src/utils/client/isValidClientId.test.ts | 29 +++++++++ src/utils/client/isValidClientId.ts | 9 +++ .../provideClientIdWithoutVerification.ts | 2 +- .../sendEmailToVerifyClientForServer.ts | 1 + src/utils/hooks/useInitialDelayedAction.ts | 25 ++++++++ src/utils/validators/isValidClientId.ts | 4 -- 11 files changed, 160 insertions(+), 22 deletions(-) create mode 100644 src/components/ClientVerificationComponent/AutomaticVerification.ts create mode 100644 src/utils/client/isValidClientId.test.ts create mode 100644 src/utils/client/isValidClientId.ts create mode 100644 src/utils/hooks/useInitialDelayedAction.ts delete mode 100644 src/utils/validators/isValidClientId.ts diff --git a/src/components/ClientVerificationComponent/AutomaticVerification.ts b/src/components/ClientVerificationComponent/AutomaticVerification.ts new file mode 100644 index 000000000..198e8a9ff --- /dev/null +++ b/src/components/ClientVerificationComponent/AutomaticVerification.ts @@ -0,0 +1,11 @@ +import { client_id, string_email, string_token } from '../../utils/typeAliases'; + +export type AutomaticVerification = { + code: string_token; + email: string_email; + clientId: client_id; +}; + +/** + * TODO: !!!last Annotate all + */ diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index b1d7f911f..ae1af7ee8 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -1,4 +1,5 @@ import { useCallback, useRef, useState } from 'react'; +import spaceTrim from 'spacetrim'; import { classNames } from '../../utils/classNames'; import { $backupClientEmail } from '../../utils/client/backupClientEmail'; import { ClientEmailVerification } from '../../utils/client/ClientVerification'; @@ -6,9 +7,10 @@ import { $provideClientEmail } from '../../utils/client/provideClientEmail'; import { $provideClientIdWithoutVerification } from '../../utils/client/provideClientIdWithoutVerification'; import { $sendEmailToVerifyClientForBrowser } from '../../utils/client/sendEmailToVerifyClientForBrowser'; import { $verifyEmailCodeForBrowser } from '../../utils/client/verifyEmailCodeForBrowser'; -import { useInitialAction } from '../../utils/hooks/useInitialAction'; +import { useInitialDelayedAction } from '../../utils/hooks/useInitialDelayedAction'; import { useStyleModule } from '../../utils/hooks/useStyleModule'; import type { string_css_class, string_token } from '../../utils/typeAliases'; +import type { AutomaticVerification } from './AutomaticVerification'; import { VerificationCodeInput } from './VerificationCodeInput/VerificationCodeInput'; interface ClientVerificationComponentProps { @@ -17,6 +19,11 @@ interface ClientVerificationComponentProps { */ readonly className?: string_css_class; + /** + * If provided, the verification will be automatic + */ + readonly automaticVerification?: AutomaticVerification; + /** * Called when user successfully verifies his email */ @@ -27,7 +34,9 @@ interface ClientVerificationComponentProps { * Renders a @@ */ export function ClientVerificationComponent(props: ClientVerificationComponentProps) { - const { onVerificationSuccess, className } = props; + const { onVerificationSuccess, automaticVerification, className } = props; + + console.log('!!!', { automaticVerification }); const styles = useStyleModule(import('./ClientVerificationComponent.module.css')); @@ -50,6 +59,8 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr ); const submitEmail = useCallback(async () => { + console.info(`Submitting email`); + if (status !== 'BEFORE') { // TODO: Better then alert alert( @@ -96,8 +107,10 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr }, [status, emailInputRef, handleSuccess]); const submitCode = useCallback( - async (code: string_token) => { - if (!['EMAIL_SENT', 'ALREADY_EMAIL_SENT'].includes(status)) { + async (code: string_token, isStatusBypassed = false) => { + console.info(`Submitting code`); + + if (!['EMAIL_SENT', 'ALREADY_EMAIL_SENT'].includes(status) && !isStatusBypassed) { throw new Error(`Code can be submitted only when status is "EMAIL_SENT" but it is "${status}"`); // <- TODO: ShouldNeverHappenError } @@ -127,19 +140,36 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr [status, handleSuccess], ); - useInitialAction( - () => true, - () => { - const email = $provideClientEmail(); + useInitialDelayedAction(async () => { + const email = $provideClientEmail() || automaticVerification?.email || null; - if (email === null) { - return; - } + if (email === null) { + return; + } - emailInputRef.current!.value = email; - submitEmail(); - }, - ); + emailInputRef.current!.value = email; + + await submitEmail(); + + if (!automaticVerification) { + return; + } + + const clientId = $provideClientIdWithoutVerification(); + if (automaticVerification.clientId !== clientId) { + console.error( + spaceTrim(` + Can not automatically verify email because clientId is not the same + + Local storage: ${clientId} + Automatic verification: ${automaticVerification.clientId} + `), + ); + return; + } + + await submitCode(automaticVerification.code, true); + }); return (
diff --git a/src/pages/api/scrape/scrape-image.ts b/src/pages/api/scrape/scrape-image.ts index 8c627a6e9..f1d3b4238 100644 --- a/src/pages/api/scrape/scrape-image.ts +++ b/src/pages/api/scrape/scrape-image.ts @@ -1,7 +1,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; +import { isValidClientId } from '../../../utils/client/isValidClientId'; import { string_url } from '../../../utils/typeAliases'; import { isUrlOnPrivateNetwork } from '../../../utils/validators/isUrlOnPrivateNetwork'; -import { isValidClientId } from '../../../utils/validators/isValidClientId'; import { isValidUrl } from '../../../utils/validators/isValidUrl'; export const config = { diff --git a/src/pages/api/scrape/scrape-instagram-user.ts b/src/pages/api/scrape/scrape-instagram-user.ts index e9dd4540d..14aff7939 100644 --- a/src/pages/api/scrape/scrape-instagram-user.ts +++ b/src/pages/api/scrape/scrape-instagram-user.ts @@ -1,12 +1,12 @@ import type { GraphqlUser } from 'insta-fetcher/dist/types'; import type { NextApiRequest, NextApiResponse } from 'next'; import spaceTrim from 'spacetrim'; +import { isValidClientId } from '../../../utils/client/isValidClientId'; import { explainError } from '../../../utils/extraMessage'; import { getInstagramApiForServer, resetInstagramApiForServer, } from '../../../utils/scraping/getInstagramApiForServer/getInstagramApiForServer'; -import { isValidClientId } from '../../../utils/validators/isValidClientId'; export interface ScrapeInstagramUserResponse { // TODO: [🌋] ErrorableResponse diff --git a/src/pages/verify-email.tsx b/src/pages/verify-email.tsx index 223d33be4..af5ebe512 100644 --- a/src/pages/verify-email.tsx +++ b/src/pages/verify-email.tsx @@ -1,9 +1,45 @@ +import { useRouter } from 'next/router'; +import { useMemo } from 'react'; import { StaticAppHead } from '../components/AppHead/StaticAppHead'; +import type { AutomaticVerification } from '../components/ClientVerificationComponent/AutomaticVerification'; import { ClientVerificationComponent } from '../components/ClientVerificationComponent/ClientVerificationComponent'; import { ClientEmailVerification } from '../utils/client/ClientVerification'; +import { isValidClientId } from '../utils/client/isValidClientId'; import { WallpapersContext } from '../utils/hooks/WallpapersContext'; +import { isValidEmail } from '../utils/validators/isValidEmail'; export default function VerifyEmailPage() { + const router = useRouter(); + + const code = router.query.code; + const email = router.query.email; + const clientId = router.query.clientId; + + const automaticVerification = useMemo(() => { + if (!code && !email && !clientId) { + return undefined; + } + + if (typeof code !== 'string') { + console.error(`In GET params, "code" is not a string`); + return undefined; + } + if (!isValidEmail(email)) { + console.error(`In GET params, "email" is not a valid email but "${email}"`); + return undefined; + } + if (!isValidClientId(clientId)) { + console.error(`In GET params, "clientId" is not a valid clientId but "${clientId}"`); + return undefined; + } + + return { + code, + email, + clientId, + }; + }, [code, email, clientId]); + return ( @@ -13,6 +49,7 @@ export default function VerifyEmailPage() { // TODO: !!! Do something // TODO: !!! Redirect to ?ref=... (if exists) }} + {...{ automaticVerification }} /> ); diff --git a/src/utils/client/isValidClientId.test.ts b/src/utils/client/isValidClientId.test.ts new file mode 100644 index 000000000..c64758e7e --- /dev/null +++ b/src/utils/client/isValidClientId.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from '@jest/globals'; +import { isValidClientId } from './isValidClientId'; + +describe(`validation of UUIDs`, () => { + it(`is valid`, () => { + expect(isValidClientId(`6c815d9e-b1b0-4768-86ef-8d8e3635984a`)).toBe(true); + expect(isValidClientId(`9de47735-e949-41f4-9819-012af1e76aeb`)).toBe(true); + expect(isValidClientId(`53b5a203-1b6a-4db2-b4df-f1ec37ce01ee`)).toBe(true); + expect(isValidClientId(`0f3bbeb6-19d3-42f2-9436-500b8d418c8d`)).toBe(true); + expect(isValidClientId(`e1921419-74e7-40a7-b288-edbc4a06037a`)).toBe(true); + expect(isValidClientId(`a4873faf-8504-40d5-a098-482b7cd5f214`)).toBe(true); + expect(isValidClientId(`724f0e12-7390-4510-b6f5-df414f69b432`)).toBe(true); + expect(isValidClientId(`30f4d3db-60a0-4100-918e-e68bd7fe585b`)).toBe(true); + expect(isValidClientId(`d132ff5f-48ab-4f32-bff0-533f761ee978`)).toBe(true); + expect(isValidClientId(`b4821d3c-5c73-4048-a07d-a8ff730a6d3c`)).toBe(true); + }); + + it(`is NOT valid`, () => { + expect(isValidClientId(``)).toBe(false); + expect(isValidClientId(`1`)).toBe(false); + expect(isValidClientId(`1.A`)).toBe(false); + expect(isValidClientId(`Hello`)).toBe(false); + expect(isValidClientId(`d132ff5f`)).toBe(false); + expect(isValidClientId(`b4821d3c-5c73-4048-a07d`)).toBe(false); + expect(isValidClientId(`9SeSQTupmQHwuSrLi`)).toBe(false); + expect(isValidClientId(`wqgbh5qgkohkjhtetvh7`)).toBe(false); + expect(isValidClientId(`uuyrr6h4niwaqfbmlb65`)).toBe(false); + }); +}); diff --git a/src/utils/client/isValidClientId.ts b/src/utils/client/isValidClientId.ts new file mode 100644 index 000000000..930f2a16d --- /dev/null +++ b/src/utils/client/isValidClientId.ts @@ -0,0 +1,9 @@ +import type { client_id } from '../typeAliases'; +import { isValidUuid } from '../validators/isValidUuid'; + +/** + * Checks if value is valid client id + */ +export function isValidClientId(value: unknown): value is client_id { + return isValidUuid(value); +} diff --git a/src/utils/client/provideClientIdWithoutVerification.ts b/src/utils/client/provideClientIdWithoutVerification.ts index 105f0230e..6b96c80d3 100644 --- a/src/utils/client/provideClientIdWithoutVerification.ts +++ b/src/utils/client/provideClientIdWithoutVerification.ts @@ -1,7 +1,7 @@ import { isRunningInBrowser } from '../isRunningInWhatever'; import { client_id } from '../typeAliases'; -import { isValidClientId } from '../validators/isValidClientId'; import { $generateClientId } from './generateClientId'; +import { isValidClientId } from './isValidClientId'; import { validateClientId } from './validateClientId'; /** diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index faf179e8e..dbc1917dd 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -66,6 +66,7 @@ export async function $sendEmailToVerifyClientForServer( **${code}** Or click on this link to verify your email: + ${NEXT_PUBLIC_URL.href}verify-email?code=${code}&email=${encodeURIComponent( email, )}&clientId=${encodeURIComponent(clientId)} diff --git a/src/utils/hooks/useInitialDelayedAction.ts b/src/utils/hooks/useInitialDelayedAction.ts new file mode 100644 index 000000000..1fda0ba99 --- /dev/null +++ b/src/utils/hooks/useInitialDelayedAction.ts @@ -0,0 +1,25 @@ +import { useEffect, useRef } from 'react'; + +/** + * This hook runs a callback exactly once after some small delay after component is mounted. + */ +export function useInitialDelayedAction(action: () => void) { + const isPerformed = useRef(false); + + useEffect(() => { + if (isPerformed.current) { + return; + } + + const timeout = setTimeout(() => { + isPerformed.current = true; + action(); + }, 300); + + return () => clearTimeout(timeout); + }, [isPerformed, action]); +} + +/** + * TODO: [🍭] Probbably useInitialDelayedAction should be default + */ diff --git a/src/utils/validators/isValidClientId.ts b/src/utils/validators/isValidClientId.ts deleted file mode 100644 index b8bcbab57..000000000 --- a/src/utils/validators/isValidClientId.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { uuid } from '../typeAliases'; -import { isValidUuid } from './isValidUuid'; - -export const isValidClientId = isValidUuid \ No newline at end of file From fce82b6ca51bc68d13f69d0a548f47000466be09 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 22:56:34 +0100 Subject: [PATCH 42/48] Redirects back after verification --- .../ClientVerificationComponent.tsx | 1 + src/pages/verify-email.tsx | 7 ++++--- src/utils/client/sendEmailToVerifyClientForServer.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index ae1af7ee8..0581db0d1 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -236,4 +236,5 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr /** * TODO: !!! Show indicators for'PENDING_EMAIL_SENDING', 'EMAIL_SENT' and 'PENDING_CODE_SUBMITTING' * TODO: !!! Design + * TODO: [🧠] What if user clicks on email link and veryfy on background with opened ? */ diff --git a/src/pages/verify-email.tsx b/src/pages/verify-email.tsx index af5ebe512..45bf690c1 100644 --- a/src/pages/verify-email.tsx +++ b/src/pages/verify-email.tsx @@ -14,6 +14,7 @@ export default function VerifyEmailPage() { const code = router.query.code; const email = router.query.email; const clientId = router.query.clientId; + const ref = router.query.ref; const automaticVerification = useMemo(() => { if (!code && !email && !clientId) { @@ -46,8 +47,9 @@ export default function VerifyEmailPage() { { - // TODO: !!! Do something - // TODO: !!! Redirect to ?ref=... (if exists) + if (ref) { + router.push(ref as string); + } }} {...{ automaticVerification }} /> @@ -56,6 +58,5 @@ export default function VerifyEmailPage() { } /** - * TODO: !!! Integrate here ?code=... * TODO: !!! Design of the page */ diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index dbc1917dd..d5510bd56 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -69,7 +69,7 @@ export async function $sendEmailToVerifyClientForServer( ${NEXT_PUBLIC_URL.href}verify-email?code=${code}&email=${encodeURIComponent( email, - )}&clientId=${encodeURIComponent(clientId)} + )}&clientId=${encodeURIComponent(clientId)}&ref=${encodeURIComponent('/')} --- From b61712fcacfcebd1dd7e7e8e92a233928260c847 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 22:58:23 +0100 Subject: [PATCH 43/48] TODOs --- .../AutomaticVerification.ts | 20 +++++++++++++++---- .../ClientVerificationComponent.tsx | 2 -- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/ClientVerificationComponent/AutomaticVerification.ts b/src/components/ClientVerificationComponent/AutomaticVerification.ts index 198e8a9ff..fb963a90c 100644 --- a/src/components/ClientVerificationComponent/AutomaticVerification.ts +++ b/src/components/ClientVerificationComponent/AutomaticVerification.ts @@ -1,11 +1,23 @@ import { client_id, string_email, string_token } from '../../utils/typeAliases'; +/** + * Data for automatic verification, this data will be in the link in verification email + */ export type AutomaticVerification = { + /** + * Verification code + */ code: string_token; + + /** + * Client email + * Note: This is double check + */ email: string_email; + + /** + * Client id + * Note: This is double check, because user can open the link on different device + */ clientId: client_id; }; - -/** - * TODO: !!!last Annotate all - */ diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index 0581db0d1..aefd9b286 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -36,8 +36,6 @@ interface ClientVerificationComponentProps { export function ClientVerificationComponent(props: ClientVerificationComponentProps) { const { onVerificationSuccess, automaticVerification, className } = props; - console.log('!!!', { automaticVerification }); - const styles = useStyleModule(import('./ClientVerificationComponent.module.css')); const emailInputRef = useRef(null); From 90ae93dc8f394e1393a9a628fcd31fde90083402 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 23:08:29 +0100 Subject: [PATCH 44/48] TODOs --- .../ClientVerificationComponent.tsx | 2 +- src/pages/api/client/is-client-verified.ts | 26 ------------------- src/utils/client/backupClientEmail.ts | 2 +- ....test.ts => isClientVerified.test.ts.todo} | 9 +++---- src/utils/client/provideClientEmail.ts | 2 +- 5 files changed, 7 insertions(+), 34 deletions(-) rename src/utils/client/{isClientVerified.test.ts => isClientVerified.test.ts.todo} (79%) diff --git a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx index aefd9b286..82a0a0aae 100644 --- a/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx +++ b/src/components/ClientVerificationComponent/ClientVerificationComponent.tsx @@ -232,7 +232,7 @@ export function ClientVerificationComponent(props: ClientVerificationComponentPr } /** - * TODO: !!! Show indicators for'PENDING_EMAIL_SENDING', 'EMAIL_SENT' and 'PENDING_CODE_SUBMITTING' * TODO: !!! Design + * TODO: [🧠] Show animations for'PENDING_EMAIL_SENDING', 'EMAIL_SENT' and 'PENDING_CODE_SUBMITTING' * TODO: [🧠] What if user clicks on email link and veryfy on background with opened ? */ diff --git a/src/pages/api/client/is-client-verified.ts b/src/pages/api/client/is-client-verified.ts index f4b6bf3be..60553aab5 100644 --- a/src/pages/api/client/is-client-verified.ts +++ b/src/pages/api/client/is-client-verified.ts @@ -13,32 +13,6 @@ export default async function isClientVerifiedHandler( const result = await $isClientVerifiedForServer(request.body); return response.status(202).json(result); - - /* - TODO: !!!last Remove - const clientId = request.query.clientId; - - if (!isValidClientId(clientId)) { - return response.status(400).json( - { - message: 'GET param clientId is not valid client ID' /* <- TODO: [🌻] Unite wrong GET param message * /, - } as any /* <-[🌋] * /, - ); - } - - const selectResult = await getSupabaseForServer().from('Client').select('email').eq('clientId', clientId).limit(1); - - if ((selectResult.data?.length || 0) > 0) { - return response - .status(200) - .json({ isClientInserted: true, isClientVerified: false } satisfies IsClientVerifiedResponse); - } - - return response - .status(200) - .json({ isClientInserted: false, isClientVerified: false } satisfies IsClientVerifiedResponse); - - */ } /** diff --git a/src/utils/client/backupClientEmail.ts b/src/utils/client/backupClientEmail.ts index c1891fefb..89499d3a4 100644 --- a/src/utils/client/backupClientEmail.ts +++ b/src/utils/client/backupClientEmail.ts @@ -18,6 +18,6 @@ export function $backupClientEmail(clientEmail: string_email) { } /** - * TODO: !!! Rename to ForBrowser + * TODO: [🧠] Maybe suffix "ForBrowser" * TODO: [🌯] Maybe read email from the the server */ diff --git a/src/utils/client/isClientVerified.test.ts b/src/utils/client/isClientVerified.test.ts.todo similarity index 79% rename from src/utils/client/isClientVerified.test.ts rename to src/utils/client/isClientVerified.test.ts.todo index 6ddcbe65b..337829bf1 100644 --- a/src/utils/client/isClientVerified.test.ts +++ b/src/utils/client/isClientVerified.test.ts.todo @@ -21,9 +21,8 @@ describe('how isClientVerified works', () => { }); /** - * TODO: !!! pavol+not-verified@webgpt.cz - * TODO: !!! pavol+email-sent@webgpt.cz - * TODO: !!! pavol+verified@webgpt.cz - * TODO: !!! Implement - * TODO: !!!last Annotate + * TODO: !! pavol+not-verified@webgpt.cz + * TODO: !! pavol+email-sent@webgpt.cz + * TODO: !! pavol+verified@webgpt.cz + */ diff --git a/src/utils/client/provideClientEmail.ts b/src/utils/client/provideClientEmail.ts index 4bba7a90d..24b9d5ccb 100644 --- a/src/utils/client/provideClientEmail.ts +++ b/src/utils/client/provideClientEmail.ts @@ -22,6 +22,6 @@ export function $provideClientEmail(): string_email | null { } /** - * TODO: !!! Rename to ForBrowser + * TODO: [🧠] Maybe suffix "ForBrowser" * TODO: [🌯] Maybe read email from the the server */ From d568c0e8607d784c3e7d09f7d673523661e28955 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 23:11:04 +0100 Subject: [PATCH 45/48] Annotate types --- src/utils/client/isClientVerified.types.ts | 14 +++++++++---- .../client/sendEmailToVerifyClient.types.ts | 20 +++++++++++++++---- src/utils/client/verifyEmailCode.types.ts | 20 +++++++++++++++---- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/utils/client/isClientVerified.types.ts b/src/utils/client/isClientVerified.types.ts index bf14f7f5c..5dcc92f5a 100644 --- a/src/utils/client/isClientVerified.types.ts +++ b/src/utils/client/isClientVerified.types.ts @@ -1,13 +1,19 @@ import type { client_id } from '../typeAliases'; +/** + * Arguments for $isClientVerified function + */ export type IsClientVerifiedRequest = { clientId: client_id; }; +/** + * Result of $isClientVerified function + */ export type IsClientVerifiedResult = { + + /** + * Indicates the status of client verification + */ status: 'NOT_VERIFIED' |'EMAIL_SENT' | 'VERIFIED'; }; - -/** - * TODO: !!!last Annotate all - */ diff --git a/src/utils/client/sendEmailToVerifyClient.types.ts b/src/utils/client/sendEmailToVerifyClient.types.ts index ce78032cc..22b8d723c 100644 --- a/src/utils/client/sendEmailToVerifyClient.types.ts +++ b/src/utils/client/sendEmailToVerifyClient.types.ts @@ -1,19 +1,31 @@ import type { client_id, string_email } from '../typeAliases'; +/** + * Arguments for $sendEmailToVerify function + */ export type SendEmailToVerifyClientRequest = { clientId: client_id; email: string_email; }; +/** + * Result of $sendEmailToVerify function + */ export type SendEmailToVerifyClientResult = | { + /** + * Indicates the status of client verification + */ status: 'EMAIL_SENT' | 'ALREADY_VERIFIED' | 'ALREADY_EMAIL_SENT' | 'LIMIT_REACHED'; } | { + /** + * Indicates the failed status of client verification + */ status: 'ERROR'; + + /** + * Indicates the reason of failure + */ message: string; }; - -/** - * TODO: !!!last Annotate all - */ diff --git a/src/utils/client/verifyEmailCode.types.ts b/src/utils/client/verifyEmailCode.types.ts index 8406d2d96..2a15e0e6c 100644 --- a/src/utils/client/verifyEmailCode.types.ts +++ b/src/utils/client/verifyEmailCode.types.ts @@ -1,20 +1,32 @@ import type { client_id, string_token } from '../typeAliases'; +/** + * Arguments for $verifyEmailCode function + */ export type VerifyEmailCodeRequest = { clientId: client_id; email: string_token; code: string_token; }; +/** + * Result of $verifyEmailCode function + */ export type VerifyEmailCodeResult = | { + /** + * Indicates the status of client verification + */ status: 'VERIFIED' /*| 'ALREADY_VERIFIED' | 'EXPIRED'*/; } | { + /** + * Indicates the failed status of client verification + */ status: 'ERROR'; + + /** + * Indicates the reason of failure + */ message: string; }; - -/** - * TODO: !!!last Annotate all - */ From afd60b2e6aa0efcea861fb76f52cc5fbe479f4d0 Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 23:16:14 +0100 Subject: [PATCH 46/48] TODOs --- src/utils/client/isClientVerified.test.ts.todo | 4 ++-- src/utils/client/isClientVerifiedForServer.ts | 12 ------------ src/utils/client/provideClientId.ts | 6 +----- src/utils/client/sendEmailToVerifyClientForServer.ts | 10 ++++------ src/utils/client/verifyEmailCodeForServer.ts | 2 -- src/utils/emails/Email.ts | 7 +++---- src/utils/emails/sendEmailForServer.ts | 2 +- .../component/ValidateEmailDialogueComponent.tsx | 2 +- 8 files changed, 12 insertions(+), 33 deletions(-) diff --git a/src/utils/client/isClientVerified.test.ts.todo b/src/utils/client/isClientVerified.test.ts.todo index 337829bf1..316bdafc5 100644 --- a/src/utils/client/isClientVerified.test.ts.todo +++ b/src/utils/client/isClientVerified.test.ts.todo @@ -6,7 +6,7 @@ describe('how isClientVerified works', () => { it('should work with foo', () => { expect( $isClientVerifiedForServer({ - clientId: validateClientId('!!!'), + clientId: validateClientId('!!'), }), ).resolves.toBe(true); }); @@ -14,7 +14,7 @@ describe('how isClientVerified works', () => { it('should NOT work with bar', () => { expect( $isClientVerifiedForServer({ - clientId: validateClientId('!!!'), + clientId: validateClientId('!!'), }), ).resolves.toBe(false); }); diff --git a/src/utils/client/isClientVerifiedForServer.ts b/src/utils/client/isClientVerifiedForServer.ts index d0c3944bc..eeb010cc1 100644 --- a/src/utils/client/isClientVerifiedForServer.ts +++ b/src/utils/client/isClientVerifiedForServer.ts @@ -46,20 +46,8 @@ export async function $isClientVerifiedForServer(options: IsClientVerifiedReques return { status: 'NOT_VERIFIED', }; - - /* - const selectResult = await getSupabaseForServer().from('Client').select('email').eq('clientId', clientId).limit(1); - - if ((selectResult.data?.length || 0) > 0) { - return response - .status(200) - .json({ isClientInserted: true, isClientVerified: false } satisfies IsClientVerifiedResponse); - } - */ } /** * TODO: [🌯] Create some system (simmilar to Workerify) which can create server functions exposed in client through API in some DRY way - * TODO: !!! Implement - * TODO: !!!last Annotate */ diff --git a/src/utils/client/provideClientId.ts b/src/utils/client/provideClientId.ts index c1b1a54f5..80f3c8254 100644 --- a/src/utils/client/provideClientId.ts +++ b/src/utils/client/provideClientId.ts @@ -4,7 +4,7 @@ import { $isClientVerifiedForBrowser } from './isClientVerifiedForBrowser'; import { $provideClientIdWithoutVerification } from './provideClientIdWithoutVerification'; /** - * TODO: !!! Remove ACRY isVerifiedEmailRequired, IS_VERIFIED_EMAIL_REQUIRED, [Vv]erif[iy] + * TODO: !!!main Remove ACRY isVerifiedEmailRequired, IS_VERIFIED_EMAIL_REQUIRED, [Vv]erif[iy] */ export interface IProvideClientIdOptions { @@ -47,7 +47,3 @@ export async function $provideClientId(options: IProvideClientIdOptions): Promis return clientId; } - -/** - * TODO: [0] !!! Implement isVerifiedEmailRequired - */ diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index d5510bd56..70d02d446 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -57,7 +57,6 @@ export async function $sendEmailToVerifyClientForServer( content: // TODO: !!! Better text // TODO: !!! Translations - // TODO: !!! Add verification link alongsite the code // TODO: !!! Unify greeting and signature in all emails ACRY maxdown` Hello, @@ -77,8 +76,9 @@ export async function $sendEmailToVerifyClientForServer( `, }); + // TODO: !! Handle errors and report failures + return { - // TODO: !!! Handle errors and report false status: 'EMAIL_SENT', }; } @@ -86,10 +86,8 @@ export async function $sendEmailToVerifyClientForServer( /** * TODO: [🌯] Create some system (simmilar to Workerify) which can create server functions exposed in client through API in some DRY way * - * TODO: !!! Implement - * TODO: !!!last Annotate - * TODO: !!! Create DB view for jirka to be DB costs, feedbac etc visible for him - * TODO: !!! Referral system + * TODO: !!!main Create DB view for jirka to be DB costs, feedbac etc visible for him + * TODO: !!!main Referral system * TODO: [🧠] Some unification method how to attribute costs to unique people * - franta.novak@gmail.com -> franta.novak@gmail.com * - franta.novak+alias@gmail.com -> franta.novak@gmail.com diff --git a/src/utils/client/verifyEmailCodeForServer.ts b/src/utils/client/verifyEmailCodeForServer.ts index 332425c38..d06e9a307 100644 --- a/src/utils/client/verifyEmailCodeForServer.ts +++ b/src/utils/client/verifyEmailCodeForServer.ts @@ -51,6 +51,4 @@ export async function $verifyEmailCodeForServer(options: VerifyEmailCodeRequest) * TODO: [🌯] Create some system (simmilar to Workerify) which can create server functions exposed in client through API in some DRY way * TODO: !! Use or remove status ALREADY_VERIFIED * TODO: !! Use or remove status EXPIRED - * TODO: !!! Implement - * TODO: !!!last Annotate */ diff --git a/src/utils/emails/Email.ts b/src/utils/emails/Email.ts index 77c9fbd55..64399a109 100644 --- a/src/utils/emails/Email.ts +++ b/src/utils/emails/Email.ts @@ -7,7 +7,7 @@ import { string_maxdown } from '../typeAliases'; export interface Email { /** * Email address of sender - * + * * Note: If not set, default value !!! from config is used */ readonly from?: string_email; @@ -30,7 +30,6 @@ export interface Email { readonly content: string_maxdown; } - /** - * TODO: !!! Translations - */ \ No newline at end of file + * TODO: !!! [🧠] Translations + */ diff --git a/src/utils/emails/sendEmailForServer.ts b/src/utils/emails/sendEmailForServer.ts index 0a6b46bb7..27d713cfa 100644 --- a/src/utils/emails/sendEmailForServer.ts +++ b/src/utils/emails/sendEmailForServer.ts @@ -68,5 +68,5 @@ export async function sendEmailForServer(email: Email): Promise { } /** - * TODO: !!! Translations + * TODO: !!! [🧠] Translations */ diff --git a/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx index 9800c92e7..201ab1580 100644 --- a/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx +++ b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx @@ -14,7 +14,7 @@ export function ValidateEmailDialogueComponent( ) { const { request: { - /* TODO: !!! Maybe take default email and pass into as prop */ + /* Note: !!!+++ */ }, respond, } = props; From 58dbd0eb8aa9ce010ed7d0d14959e64f6a021f3e Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 23:39:52 +0100 Subject: [PATCH 47/48] Notes --- src/utils/client/provideClientId.ts | 1 - .../component/ValidateEmailDialogueComponent.tsx | 7 +------ .../validate-email/types/ValidateEmailDialogueRequest.ts | 6 ++---- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/utils/client/provideClientId.ts b/src/utils/client/provideClientId.ts index 80f3c8254..efc5fcdf2 100644 --- a/src/utils/client/provideClientId.ts +++ b/src/utils/client/provideClientId.ts @@ -39,7 +39,6 @@ export async function $provideClientId(options: IProvideClientIdOptions): Promis } await validateEmailDialogue({ - // [🍀] Maybe allow to pass default value for email isVerifiedEmailRequired, }); diff --git a/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx index 201ab1580..9c7134349 100644 --- a/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx +++ b/src/workers/dialogues/validate-email/component/ValidateEmailDialogueComponent.tsx @@ -12,12 +12,7 @@ import type { ValidateEmailDialogueResponse } from '../types/ValidateEmailDialog export function ValidateEmailDialogueComponent( props: DialogueComponentProps, ) { - const { - request: { - /* Note: !!!+++ */ - }, - respond, - } = props; + const { respond } = props; return ( diff --git a/src/workers/dialogues/validate-email/types/ValidateEmailDialogueRequest.ts b/src/workers/dialogues/validate-email/types/ValidateEmailDialogueRequest.ts index 8f74d4c01..45429a44f 100644 --- a/src/workers/dialogues/validate-email/types/ValidateEmailDialogueRequest.ts +++ b/src/workers/dialogues/validate-email/types/ValidateEmailDialogueRequest.ts @@ -7,8 +7,6 @@ export type ValidateEmailDialogueRequest = AbstractDialogueRequest & { * - If `true`, user will must verify the email */ readonly isVerifiedEmailRequired: boolean; -}; -/** - * TODO: [🍀] Maybe allow to pass default value for email - */ + // Note: ValidateEmailDialogue is a special dialogue, it has no input +}; From 6aff6ecc69beac0e5c040b3a1d90f37a44ed1c9d Mon Sep 17 00:00:00 2001 From: Pavol Hejny Date: Mon, 11 Dec 2023 23:40:32 +0100 Subject: [PATCH 48/48] TODOs --- src/utils/client/sendEmailToVerifyClientForServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/client/sendEmailToVerifyClientForServer.ts b/src/utils/client/sendEmailToVerifyClientForServer.ts index 70d02d446..56133425f 100644 --- a/src/utils/client/sendEmailToVerifyClientForServer.ts +++ b/src/utils/client/sendEmailToVerifyClientForServer.ts @@ -57,7 +57,7 @@ export async function $sendEmailToVerifyClientForServer( content: // TODO: !!! Better text // TODO: !!! Translations - // TODO: !!! Unify greeting and signature in all emails ACRY + // TODO: !!! [🧠] Unify greeting and signature in all emails ACRY maxdown` Hello, Your code to sign-in into ${APP_NAME} is: