Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Option to enable/disable survey #245

Merged
merged 8 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/.pnp
.pnp.js


# testing
/coverage

Expand Down
8 changes: 7 additions & 1 deletion lib/axiosConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ export const postFetch = (url: string, data = {}) => {
data,
}).then((response) => response.data);
};

export const patchFetch = (url: string, data = {}) => {
return instance({
method: 'PATCH',
url,
data,
}).then((response) => response.data);
};
export const deleteFetch = (url: string) => {
return instance({
method: 'DELETE',
Expand Down
4 changes: 3 additions & 1 deletion locales/en/surveyAnswer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@
"copyLinkButtonTitle": "Copy link to clipboard",
"deleteSurveyButtonTitle": "Delete survey",
"tooltipValue": "value:",
"buttonShare": "Share"
"buttonShare": "Share",
"isActive": "Active",
"updateStatusFailure": "Failed to update survey's status"
}
682 changes: 386 additions & 296 deletions package-lock.json

Large diffs are not rendered by default.

21 changes: 9 additions & 12 deletions src/features/surveys/managers/surveyAnswerManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect } from 'react';
import toast from 'react-hot-toast';
import { LocalStorageKeys } from 'features/surveys/constants/types';
import useLocalStorage from 'features/surveys/hooks/useLocalStorage';
Expand Down Expand Up @@ -61,18 +61,9 @@ export const useSurveyAnswerManager = (initialData: SurveyWithQuestions) => {
return true;
};

const getSurveyData = useCallback(async () => {
if (!initialData.isActive) {
router.replace('/');
return;
} else {
setFormData(initialData);
}
}, [router, initialData]);

useEffect(() => {
if (surveyId) {
getSurveyData();
setFormData(initialData);
}
if (
process.env.NEXT_PUBLIC_BLOCK_MULTIPLE_ANSWERS &&
Expand Down Expand Up @@ -152,7 +143,13 @@ export const useSurveyAnswerManager = (initialData: SurveyWithQuestions) => {
await router.replace(`/survey/${surveyId}/thank-you`);
toast.success(t('successfullSubmit'));
} else {
await router.replace('/');
setFormData((prev) => {
if (!prev) return prev;
return {
...prev,
isActive: false,
};
});
toast.error(t('surveyInactive'));
}
} catch (error) {
Expand Down
21 changes: 20 additions & 1 deletion src/features/surveys/managers/surveyResultsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import toast from 'react-hot-toast';
import useCopyToClipboard from 'shared/hooks/useCopyToClipboard';

import useTranslation from 'next-translate/useTranslation';
import { getFetch } from '../../../../lib/axiosConfig';
import { getFetch, patchFetch } from '../../../../lib/axiosConfig';
import { SurveyWithAnswers } from 'types/SurveyWithAnswers';
import { QuestionType } from '@prisma/client';
import { MappedAnswers } from 'types/MappedAnswers';
Expand All @@ -16,6 +16,7 @@ export const useSurveyResultsManager = (initialData: SurveyWithAnswers) => {
const { surveyId } = router.query as { surveyId: string };

const [isDataLoading, setIsDataLoading] = useState<boolean>(false);
const [isStatusLoading, setIsStatusLoading] = useState<boolean>(false);
const [surveyData, setSurveyData] = useState<SurveyWithAnswers>();
const [mappedAnswersData, setMappedAnswersData] = useState<MappedAnswers>({});

Expand Down Expand Up @@ -74,6 +75,22 @@ export const useSurveyResultsManager = (initialData: SurveyWithAnswers) => {
toast.success(t('refreshSuccess'));
}, [surveyId, router, t, fillSurveyData]);

const updateSurveyStatus = useCallback(async () => {
setIsStatusLoading(true);
try {
const surveyResult = await patchFetch(`/api/survey/${surveyId}`, {
actionType: 'UPDATE_ACTIVE',
isActive: !surveyData?.isActive,
});
setSurveyData((prev) =>
prev ? { ...prev, isActive: !!surveyResult?.isActive } : prev
);
} catch (_err) {
toast.error(t('updateStatusFailure'));
}
setIsStatusLoading(false);
}, [setIsStatusLoading, setSurveyData, surveyData, surveyId, t]);

useEffect(() => {
if (!surveyId) {
router.replace('/');
Expand Down Expand Up @@ -101,6 +118,8 @@ export const useSurveyResultsManager = (initialData: SurveyWithAnswers) => {
surveyData,
mappedAnswersData,
isDataLoading,
isStatusLoading,
onRemoveSuccess,
updateSurveyStatus,
};
};
5 changes: 3 additions & 2 deletions src/pages/api/answer/[id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,16 @@ export default async function handler(

const { id } = req.query;

const survey = await getSurveyData(id as string);

switch (requestMethod) {
case 'GET': {
const survey = await getSurveyData(id as string);
return res.status(200).json(survey);
}
case 'POST': {
const { answersData } = req.body as AnswerData;

if (!isAnswerDataValid(req.body)) {
if (!isAnswerDataValid(req.body) || !survey?.isActive) {
return res.status(400).end();
}

Expand Down
55 changes: 53 additions & 2 deletions src/pages/api/survey/[id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { NextApiRequest, NextApiResponse } from 'next';
import prismadb from '../../../../lib/prismadb';
import serverAuth from '../../../../lib/serverAuth';

export enum SurveyActionTypes {
UPDATE_ACTIVE = 'UPDATE_ACTIVE',
}
interface SurveyPatchPayloadI {
actionType: SurveyActionTypes;
}

export async function getSurveyWithAnswers(surveyId: string, userId: string) {
const survey = await prismadb.survey.findFirst({
where: {
Expand All @@ -24,7 +31,49 @@ export async function getSurveyWithAnswers(surveyId: string, userId: string) {

return survey;
}

export async function updateSurveyActiveStatus({
surveyId,
isActive,
}: {
surveyId: string;
isActive: boolean;
}) {
const survey = await prismadb.survey.update({
data: { isActive },
where: {
id: surveyId,
},
});
return survey;
}
export async function handlePatch(req: NextApiRequest, res: NextApiResponse) {
const surveyId = String(req.query.id);
const { actionType } = req.body as SurveyPatchPayloadI;
const session = await serverAuth(req, res);
const userId = session.currentUser.id;
const surveyFound = await prismadb.survey.findFirst({
where: { id: surveyId, userId },
});
if (!surveyFound?.id) {
return res.status(404).end();
}
switch (actionType) {
case SurveyActionTypes.UPDATE_ACTIVE: {
const isActive = !!req.body.isActive;
const survey = await updateSurveyActiveStatus({
surveyId,
isActive,
});
if (survey?.id) {
return res.status(200).json(survey);
}
return res.status(500).json({ message: 'Failed to change status' });
}
default: {
return res.status(400).json({ message: 'actionType is invalid' });
}
}
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
Expand Down Expand Up @@ -64,7 +113,9 @@ export default async function handler(

return res.status(200).end();
}

case 'PATCH': {
return handlePatch(req, res);
}
default:
return res.status(405).end();
}
Expand Down
2 changes: 2 additions & 0 deletions src/pages/login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import { getSession } from 'next-auth/react';
import { NextPageContext } from 'next';

export async function getServerSideProps(context: NextPageContext) {

const session = await getSession(context);

if (session) {
return {
redirect: {
Expand Down
1 change: 1 addition & 0 deletions src/pages/signup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { NextPageContext } from 'next';

export async function getServerSideProps(context: NextPageContext) {
const session = await getSession(context);

if (session) {
return {
redirect: {
Expand Down
13 changes: 2 additions & 11 deletions src/pages/survey/[surveyId]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import Head from 'next/head';
import { ButtonVariant } from 'shared/components/Button/Button';
import { useSurveyAnswerManager } from 'features/surveys/managers/surveyAnswerManager';
import ButtonLink from 'shared/components/ButtonLink/ButtonLink';
import useTranslation from 'next-translate/useTranslation';
import { InferGetServerSidePropsType, NextPageContext } from 'next';
import { getSurveyData } from 'pages/api/answer/[id]';
Expand Down Expand Up @@ -62,15 +60,8 @@ function AnswerPage({
)
) : (
<>
<h1 className="text-5xl">🙁</h1>
<h1 className="my-5 text-xl">{t('surveyNoLongerActive')}</h1>
<ButtonLink
href={'/'}
variant={ButtonVariant.PRIMARY}
className="w-full sm:w-auto"
>
{t('backHomeButton')}
</ButtonLink>
<div className="text-5xl">🙁</div>
<div className="my-5 text-xl">{t('surveyNoLongerActive')}</div>
</>
)}
</div>
Expand Down
21 changes: 16 additions & 5 deletions src/pages/survey/answer/[surveyId]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { RefreshIcon, ShareIcon, TrashIcon } from '@heroicons/react/outline';

import Toggle from 'shared/components/Toggle/Toggle';
import Head from 'next/head';
import withAnimation from 'shared/HOC/withAnimation';
import withProtectedRoute from 'shared/HOC/withProtectedRoute';
import AnswerHeader from 'features/surveys/components/AnswerHeader/AnswerHeader';
import { useSurveyResultsManager } from 'features/surveys/managers/surveyResultsManager';
import Button, { ButtonVariant } from 'shared/components/Button/Button';

import useTranslation from 'next-translate/useTranslation';
import { InferGetServerSidePropsType, NextPageContext } from 'next';
import { getSession } from 'next-auth/react';
Expand All @@ -31,7 +30,7 @@ export async function getServerSideProps(context: NextPageContext) {

const surveyData = (await getSurveyWithAnswers(
context.query.surveyId as string,
session.user.id
session.user?.id
)) as SurveyWithAnswers;

if (!surveyData) {
Expand Down Expand Up @@ -60,6 +59,8 @@ function SurveyResultsPage({
mappedAnswersData,
isDataLoading,
onRemoveSuccess,
updateSurveyStatus,
isStatusLoading,
} = useSurveyResultsManager(initialData);

const {
Expand Down Expand Up @@ -88,7 +89,17 @@ function SurveyResultsPage({
<h1 className="flex min-h-[38px] items-center border-indigo-200 pb-4 text-xl font-semibold sm:border-l-4 sm:pb-0 sm:pl-4 sm:text-left">
{surveyData?.title}
</h1>
<div className="flex w-full justify-center gap-2 sm:w-auto">
<div className="flex w-full flex-wrap justify-center gap-2 sm:w-auto sm:flex-nowrap">
<div className="flex w-full items-center justify-start">
<Toggle
classNames="mx-auto my-2 sm:my-0 sm:ml-0 sm:mr-2"
isEnabled={!!surveyData?.isActive}
onToggle={updateSurveyStatus}
label={t('isActive')}
isLoading={isStatusLoading}
/>
</div>

<Button
title={t('buttonCopyLinkTitle')}
onClick={openShareSurveyModal}
Expand Down Expand Up @@ -122,7 +133,7 @@ function SurveyResultsPage({
createDate={surveyData?.createdAt ?? ''}
/>

{surveyData?.answers.length === 0 && (
{surveyData?.answers?.length === 0 && (
<div className="mt-6">{t('noAnswers')}</div>
)}

Expand Down
11 changes: 9 additions & 2 deletions src/shared/components/Toggle/Toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Switch } from '@headlessui/react';
import clsx from 'clsx';
import Loader from 'shared/components/Loader/Loader';

interface ToggleProps {
isEnabled: boolean;
onToggle: (isEnabled: boolean) => void;
label?: string;
classNames?: string;
testId?: string;
isLoading?: boolean;
}

function Toggle({
Expand All @@ -15,17 +17,20 @@ function Toggle({
onToggle,
label,
testId,
isLoading,
}: ToggleProps) {
return (
<Switch.Group>
<div
className={clsx('flex items-center', classNames)}
className={clsx('relative flex items-center', classNames)}
data-test-id="toggle-wrapper"
>
<Switch.Label className="mr-2 text-sm">{label}</Switch.Label>

<Switch
checked={isEnabled}
data-test-id={testId}
disabled={isLoading}
onChange={onToggle}
className={`${
isEnabled ? 'bg-indigo-300' : 'bg-zinc-300'
Expand All @@ -35,7 +40,9 @@ function Toggle({
className={`${
isEnabled ? 'translate-x-5' : 'translate-x-1'
} inline-block h-3 w-3 transform rounded-full bg-white transition-transform`}
/>
>
<Loader className="h-3 w-3" isLoading={!!isLoading} />
</span>
</Switch>
</div>
</Switch.Group>
Expand Down
Loading