diff --git a/package-lock.json b/package-lock.json index ae7cf659..7e16630b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "axios": "^1.6.0", "bcrypt": "^5.1.0", "clsx": "^1.1.1", + "colord": "^2.9.3", "date-fns": "^2.28.0", "emoji-mart": "^5.5.2", "formik": "^2.2.9", @@ -5232,6 +5233,11 @@ "color-support": "bin.js" } }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -17460,6 +17466,11 @@ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" }, + "colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, "colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", diff --git a/package.json b/package.json index ab5fd76f..aa0ac061 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "axios": "^1.6.0", "bcrypt": "^5.1.0", "clsx": "^1.1.1", + "colord": "^2.9.3", "date-fns": "^2.28.0", "emoji-mart": "^5.5.2", "formik": "^2.2.9", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9dedcb60..fc16d041 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -73,6 +73,7 @@ model Survey { answers Answer[] oneQuestionPerStep Boolean displayTitle Boolean + accentColor String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) } diff --git a/src/features/surveys/components/SurveyOptionsModal/SurveyOptionsModal.tsx b/src/features/surveys/components/SurveyOptionsModal/SurveyOptionsModal.tsx index 0a518472..8e129553 100644 --- a/src/features/surveys/components/SurveyOptionsModal/SurveyOptionsModal.tsx +++ b/src/features/surveys/components/SurveyOptionsModal/SurveyOptionsModal.tsx @@ -8,7 +8,7 @@ type SurveyOptionsModalProps = { isOpened: boolean; closeModal: () => void; surveyOptions: SurveyOptions; - updateOptions: (option: keyof SurveyOptions, value: boolean) => void; + updateOptions: (option: keyof SurveyOptions, value: boolean | string) => void; }; export default function SurveyOptionsModalModal({ @@ -47,6 +47,21 @@ export default function SurveyOptionsModalModal({ }} label={t('surveyOptionsModal.DisplayTitle')} /> + +
+
+
Accent color
+ { + updateOptions('accentColor', e.target.value); + }} + title="Choose your color" + /> +
} /> diff --git a/src/features/surveys/features/SurveyCreator/components/PreviewPanel/PreviewPanel.tsx b/src/features/surveys/features/SurveyCreator/components/PreviewPanel/PreviewPanel.tsx index 3c4c80ce..ea6b617a 100644 --- a/src/features/surveys/features/SurveyCreator/components/PreviewPanel/PreviewPanel.tsx +++ b/src/features/surveys/features/SurveyCreator/components/PreviewPanel/PreviewPanel.tsx @@ -43,6 +43,7 @@ export default function PreviewPanel() { oneQuestionPerStep: surveyOptions.oneQuestionPerStep, userId: '', title, + accentColor: surveyOptions.accentColor, createdAt: new Date(), questions: questions.map((question, index) => ({ surveyId: '', diff --git a/src/features/surveys/features/SurveyCreator/managers/createSurveyManager/createSurveyManager.ts b/src/features/surveys/features/SurveyCreator/managers/createSurveyManager/createSurveyManager.ts index ea7985f2..6cd95eac 100644 --- a/src/features/surveys/features/SurveyCreator/managers/createSurveyManager/createSurveyManager.ts +++ b/src/features/surveys/features/SurveyCreator/managers/createSurveyManager/createSurveyManager.ts @@ -23,6 +23,7 @@ export interface Question { export interface SurveyOptions { oneQuestionPerStep: boolean; displayTitle: boolean; + accentColor: string; } export const useCreateSurveyManager = (initialData?: SurveyWithQuestions) => { @@ -43,6 +44,7 @@ export const useCreateSurveyManager = (initialData?: SurveyWithQuestions) => { const [surveyOptions, setSurveyOptions] = useState({ oneQuestionPerStep: initialData?.oneQuestionPerStep ?? true, displayTitle: initialData?.displayTitle ?? true, + accentColor: initialData?.accentColor ?? '#C7D2FE', }); const [error, setError] = useState(''); @@ -76,7 +78,10 @@ export const useCreateSurveyManager = (initialData?: SurveyWithQuestions) => { sessionStorage.removeItem(DRAFT_SURVEY_SESSION_STORAGE); }, []); - const updateSurveyOptions = (option: keyof SurveyOptions, value: boolean) => { + const updateSurveyOptions = ( + option: keyof SurveyOptions, + value: boolean | string + ) => { setSurveyOptions((oldOptions) => ({ ...oldOptions, [option]: value })); }; @@ -240,6 +245,7 @@ export const useCreateSurveyManager = (initialData?: SurveyWithQuestions) => { title, oneQuestionPerStep: surveyOptions.oneQuestionPerStep, displayTitle: surveyOptions.displayTitle, + accentColor: surveyOptions.accentColor, questions: questions.map((question) => ({ title: question.title, options: question.options, @@ -275,6 +281,7 @@ export const useCreateSurveyManager = (initialData?: SurveyWithQuestions) => { title, oneQuestionPerStep: surveyOptions.oneQuestionPerStep, displayTitle: surveyOptions.displayTitle, + accentColor: surveyOptions.accentColor, questions: questions.map((question) => ({ id: question.id, title: question.title, diff --git a/src/features/surveys/features/SurveyDisplay/components/AllQuestionsView/AllQuestionsView.tsx b/src/features/surveys/features/SurveyDisplay/components/AllQuestionsView/AllQuestionsView.tsx index abe85329..18d688f0 100644 --- a/src/features/surveys/features/SurveyDisplay/components/AllQuestionsView/AllQuestionsView.tsx +++ b/src/features/surveys/features/SurveyDisplay/components/AllQuestionsView/AllQuestionsView.tsx @@ -7,6 +7,7 @@ import Button, { import useTranslation from 'next-translate/useTranslation'; import { AnswersComponentFactory } from 'features/surveys/features/SurveyDisplay/components/AnswersComponent/AnswersComponentFactory'; import { useSurveyDisplayContext } from 'features/surveys/features/SurveyDisplay/context'; +import { getFontColor } from 'features/surveys/features/SurveyDisplay/utils/getFontColor'; export default function AllQuestionView() { const { t } = useTranslation('survey'); @@ -32,6 +33,10 @@ export default function AllQuestionView() { variant={ButtonVariant.PRIMARY} sizeType={ButtonSize.FULL} isLoading={isAnswering} + style={{ + backgroundColor: formData.accentColor ?? undefined, + color: getFontColor(formData?.accentColor), + }} > {t('sendButton')} diff --git a/src/features/surveys/features/SurveyDisplay/components/AnswersComponent/AnswersComponentFactory.tsx b/src/features/surveys/features/SurveyDisplay/components/AnswersComponent/AnswersComponentFactory.tsx index e6354812..53241676 100644 --- a/src/features/surveys/features/SurveyDisplay/components/AnswersComponent/AnswersComponentFactory.tsx +++ b/src/features/surveys/features/SurveyDisplay/components/AnswersComponent/AnswersComponentFactory.tsx @@ -10,6 +10,7 @@ import ListAnswersComponent from 'features/surveys/features/SurveyDisplay/compon import RateAnswersComponent from 'features/surveys/features/SurveyDisplay/components/AnswersComponent/RateComponent/RateComponent'; import TextAnswersComponent from 'features/surveys/features/SurveyDisplay/components/AnswersComponent/TextAnswersComponent'; import { useSurveyDisplayContext } from 'features/surveys/features/SurveyDisplay/context'; +import { getFontColor } from 'features/surveys/features/SurveyDisplay/utils/getFontColor'; interface AnswersComponentFactoryProps { questionIndex: number; @@ -63,6 +64,7 @@ export const AnswersComponentFactory = ( sizeType={ButtonSize.FULL} onClick={handlePreviousQuestion} disabled={isAnswering} + className="text-black" > {t('back')} @@ -74,6 +76,10 @@ export const AnswersComponentFactory = ( sizeType={ButtonSize.FULL} onClick={handleNextQuestion} isLoading={isAnswering} + style={{ + backgroundColor: formData.accentColor ?? undefined, + color: getFontColor(formData.accentColor), + }} > {isLastQuestion ? t('sendButton') : t('next')} diff --git a/src/features/surveys/features/SurveyDisplay/components/OneQuestionView/OneQuestionView.tsx b/src/features/surveys/features/SurveyDisplay/components/OneQuestionView/OneQuestionView.tsx index 2584b3de..8b6c7bc5 100644 --- a/src/features/surveys/features/SurveyDisplay/components/OneQuestionView/OneQuestionView.tsx +++ b/src/features/surveys/features/SurveyDisplay/components/OneQuestionView/OneQuestionView.tsx @@ -21,6 +21,7 @@ export default function OneQuestionView() { currentStep={activeQuestionIndex + 1} totalSteps={formData?.questions.length} isSubmitted={isAnswering} + accentColor={formData?.accentColor} /> )} diff --git a/src/features/surveys/features/SurveyDisplay/utils/getFontColor.ts b/src/features/surveys/features/SurveyDisplay/utils/getFontColor.ts new file mode 100644 index 00000000..15764e38 --- /dev/null +++ b/src/features/surveys/features/SurveyDisplay/utils/getFontColor.ts @@ -0,0 +1,10 @@ +export const getFontColor = (color?: string | null) => { + if (!color) return '#000000'; + + const hex = color.replace('#', ''); + const r = parseInt(hex.substr(0, 2), 16); + const g = parseInt(hex.substr(2, 2), 16); + const b = parseInt(hex.substr(4, 2), 16); + + return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? '#000000' : '#ffffff'; +}; diff --git a/src/pages/api/survey/[id].ts b/src/pages/api/survey/[id].ts index 36384ca6..a849a325 100644 --- a/src/pages/api/survey/[id].ts +++ b/src/pages/api/survey/[id].ts @@ -135,6 +135,7 @@ export default async function handler( questions, oneQuestionPerStep, displayTitle, + accentColor, } = req.body as SurveyData; if (!isSurveyValid(req.body)) { return res.status(400).end(); @@ -189,6 +190,7 @@ export default async function handler( description, oneQuestionPerStep, displayTitle, + accentColor, }, }); diff --git a/src/pages/api/survey/index.ts b/src/pages/api/survey/index.ts index 6dd765df..1185e130 100644 --- a/src/pages/api/survey/index.ts +++ b/src/pages/api/survey/index.ts @@ -17,6 +17,7 @@ export interface SurveyData { questions: Question[]; oneQuestionPerStep: boolean; displayTitle: boolean; + accentColor: string; } export async function getAllUserSurveys(userId: string) { @@ -76,6 +77,7 @@ export default async function handler( questions, oneQuestionPerStep, displayTitle, + accentColor, } = req.body as SurveyData; if (!isSurveyValid(req.body)) { @@ -87,6 +89,7 @@ export default async function handler( user: { connect: { id: session.currentUser.id } }, title, description, + accentColor, isActive: true, oneQuestionPerStep, displayTitle, diff --git a/src/shared/components/Button/Button.tsx b/src/shared/components/Button/Button.tsx index ae97ffcc..4df62bb2 100644 --- a/src/shared/components/Button/Button.tsx +++ b/src/shared/components/Button/Button.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { CSSProperties } from 'react'; import clsx from 'clsx'; import Loader from 'shared/components/Loader/Loader'; @@ -25,6 +25,7 @@ export interface ButtonProps { className?: string | undefined; type?: 'button' | 'submit' | 'reset' | undefined; icon?: React.ReactNode; + style?: CSSProperties; } const Button = ({ @@ -36,6 +37,7 @@ const Button = ({ disabled = false, type = 'button', icon, + style, ...props }: ButtonProps & React.HTMLProps) => (