Skip to content

Commit

Permalink
feat: show chips and submit/reset buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
swouf committed May 27, 2024
1 parent 6cf2b87 commit 62d4e1b
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 160 deletions.
5 changes: 5 additions & 0 deletions src/config/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export const makeUserAnswerCorrectCellCy = (index: number | string): string =>
export const QUESTION_CY = 'question';
export const ANSWER_CY = 'answer';
export const ANSWER_SUBMIT_BUTTON_CY = 'answer-submit-button';
export const RESET_BTN_CY = 'reset-button';

export const REQUIRED_CHIP_CY = 'required-chip';
export const SAVED_CHIP_CY = 'saved-chip';
export const SUBMITTED_CHIP_CY = 'submitted-chip';

export const buildDataCy = (selector: string): string =>
`[data-cy=${selector}]`;
Expand Down
35 changes: 23 additions & 12 deletions src/config/sentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,37 @@ type SentryConfigType = {
};

export const generateSentryConfig = (): SentryConfigType => {
let SENTRY_ENVIRONMENT = SENTRY_ENV || 'development';
let SENTRY_TRACE_SAMPLE_RATE = 1.0;
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
const DEV_TRACE_SAMPLE_RATE = 1.0;
const DEV_REPLAY_SAMPLE_RATE = 0.1;
const PROD_TRACE_SAMPLE_RATE = 0.1;
const PROD_REPLAY_SAMPLE_RATE = 0.1;
let SENTRY_REPLAY_SAMPLE_RATE = 0.1;
switch (process.env.NODE_ENV) {
case 'production':
SENTRY_ENVIRONMENT = 'production';
SENTRY_TRACE_SAMPLE_RATE = 0.1;
SENTRY_REPLAY_SAMPLE_RATE = 0.1;
break;
case 'test':
SENTRY_TRACE_SAMPLE_RATE = 0.0;
SENTRY_REPLAY_SAMPLE_RATE = 0.0;
break;
case 'development':
SENTRY_TRACE_SAMPLE_RATE = 1.0;
SENTRY_REPLAY_SAMPLE_RATE = 1.0;
break;
default:
}

return {
// dsn is set only when not running inside cypress
dsn: (!window.Cypress && SENTRY_DSN) || '',
environment: SENTRY_ENV,
tracesSampleRate: import.meta.env.PROD
? PROD_TRACE_SAMPLE_RATE
: DEV_TRACE_SAMPLE_RATE,
environment: SENTRY_ENV || SENTRY_ENVIRONMENT,
tracesSampleRate: SENTRY_TRACE_SAMPLE_RATE,
// release is set only when building for production
release: VERSION,
release: SENTRY_ENVIRONMENT === 'production' ? VERSION : '',

replaysSessionSampleRate: import.meta.env.PROD
? PROD_REPLAY_SAMPLE_RATE
: DEV_REPLAY_SAMPLE_RATE,
replaysSessionSampleRate: SENTRY_REPLAY_SAMPLE_RATE,
// If the entire session is not sampled, use the below sample rate to sample
// sessions when an error occurs.
replaysOnErrorSampleRate: 1.0,
Expand Down
6 changes: 6 additions & 0 deletions src/interfaces/userAnswer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export enum UserAnswerStatus {
Saved = 'saved',
Submitted = 'submitted',
}

export type UserAnswer = {
answer?: string;
status?: UserAnswerStatus;
};
11 changes: 10 additions & 1 deletion src/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,16 @@
},
"PLAYER": {
"SAVED_MESSAGE": "Saved",
"SAVE_BUTTON": "Save"
"SAVE_BUTTON": "Save",
"SUBMIT_OK_TOOLTIP": "Your answer has been submitted.",
"SUBMIT_OK_HELPER": "Submitted",
"SAVED_TOOLTIP": "Your answer has been saved.",
"SAVED_HELPER": "Saved",
"RESET_ANSWER": "Reset your answer.",
"REQUIRED_CHIP": "Required",
"REQUIRED_TOOLTIP": "This question requires an answer.",
"SUBMIT": "Submit",
"RESET": "Reset"
}
}
}
9 changes: 4 additions & 5 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import buildDatabase, { defaultMockContext, mockMembers } from './mocks/db';
import Root from './modules/Root';

Sentry.init({
integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()],

// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
integrations: [
Sentry.replayIntegration(),
Sentry.browserTracingIntegration(),
],
...generateSentryConfig(),
});

Expand Down
39 changes: 11 additions & 28 deletions src/mocks/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,16 @@ import type { Database, LocalContext } from '@graasp/apps-query-client';
import {
AppItemFactory,
CompleteMember,
Context,
MemberFactory,
PermissionLevel,
} from '@graasp/sdk';

import { API_HOST } from '@/config/env';

export const defaultMockContext: LocalContext = {
apiHost: API_HOST,
permission: PermissionLevel.Admin,
context: 'builder',
itemId: '1234-1234-123456-8123-123456',
memberId: 'mock-member-id',
};

export const mockMembers: CompleteMember[] = [
MemberFactory({
id: defaultMockContext.memberId || '',
id: '1',
name: 'current-member',
email: '[email protected]',
type: 'individual',
Expand All @@ -36,33 +29,23 @@ export const mockMembers: CompleteMember[] = [
];

export const mockItem = AppItemFactory({
id: defaultMockContext.itemId,
name: 'app-short-answer',
creator: mockMembers[0],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});

export const defaultMockContext: LocalContext = {
apiHost: API_HOST,
permission: PermissionLevel.Admin,
context: Context.Builder,
itemId: mockItem.id,
memberId: mockMembers[0].id,
};

const buildDatabase = (members?: CompleteMember[]): Database => ({
appData: [],
appActions: [
{
id: 'cecc1671-6c9d-4604-a3a2-6d7fad4a5996',
type: 'admin-action',
member: mockMembers[0],
createdAt: new Date().toISOString(),
item: mockItem,
data: { content: 'hello' },
},
{
id: '0c11a63a-f333-47e1-8572-b8f99fe883b0',
type: 'other-action',
member: mockMembers[1],
createdAt: new Date().toISOString(),
item: mockItem,
data: { content: 'other member' },
},
],
appActions: [],
members: members ?? mockMembers,
appSettings: [],
items: [mockItem],
Expand Down
157 changes: 157 additions & 0 deletions src/modules/context/UserAnswersContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {
FC,
ReactElement,
createContext,
useContext,
useEffect,
useMemo,
useState,
} from 'react';

import { useLocalContext } from '@graasp/apps-query-client';
import { AppData, PermissionLevel, PermissionLevelCompare } from '@graasp/sdk';

import sortBy from 'lodash.sortby';

import {
AppDataType,
UserAnswerAppData,
getDefaultUserAnswerAppData,
} from '@/config/appData';
import { hooks, mutations } from '@/config/queryClient';
import { UserAnswer, UserAnswerStatus } from '@/interfaces/userAnswer';

import { useSettings } from './SettingsContext';

type UserAnswersContextType = {
userAnswer?: UserAnswer;
setAnswer: (answer: string) => void;
submitAnswer: () => void;
deleteAnswer: (id?: UserAnswerAppData['id']) => void;
allAnswersAppData?: UserAnswerAppData[];
};

const defaultContextValue: UserAnswersContextType = {
setAnswer: () => null,
submitAnswer: () => null,
deleteAnswer: () => null,
};

const UserAnswersContext =
createContext<UserAnswersContextType>(defaultContextValue);

export const UserAnswersProvider: FC<{
children: ReactElement | ReactElement[];
}> = ({ children }) => {
const { data, isSuccess } = hooks.useAppData<UserAnswer>({
type: AppDataType.UserAnswer,
});
const [userAnswerAppData, setUserAnswerAppData] =
useState<UserAnswerAppData>();
const [allAnswersAppData, setAllAnswersAppData] =
useState<UserAnswerAppData[]>();
const { mutate: postAppData } = mutations.usePostAppData();
const { mutate: patchAppData } = mutations.usePatchAppData();
const { mutate: deleteAppData } = mutations.useDeleteAppData();
const { permission, memberId } = useLocalContext();

const { general } = useSettings();
const { autosubmit } = general;

const isAdmin = useMemo(
() => PermissionLevelCompare.gte(permission, PermissionLevel.Admin),
[permission],
);
useEffect(() => {
if (isSuccess) {
const allAns = data.filter(
(d: AppData) => d.type === AppDataType.UserAnswer,
) as UserAnswerAppData[];
setAllAnswersAppData(allAns);
setUserAnswerAppData(
sortBy(allAns, ['createdAt'])
.reverse()
.find((d) => d.member.id === memberId),
);
}
}, [isSuccess, data, memberId]);

const setAnswer = useMemo(
() =>
(answer: string): void => {
const payloadData = {
answer,
status: autosubmit
? UserAnswerStatus.Submitted
: UserAnswerStatus.Saved,
};
if (userAnswerAppData?.id) {
patchAppData({
...userAnswerAppData,
data: payloadData,
});
} else {
postAppData(getDefaultUserAnswerAppData(payloadData));
}
},
[autosubmit, patchAppData, postAppData, userAnswerAppData],
);

const submitAnswer = useMemo(
() => (): void => {
if (userAnswerAppData?.id) {
const payloadData = {
...userAnswerAppData.data,
status: UserAnswerStatus.Submitted,
};
patchAppData({
...userAnswerAppData,
data: payloadData,
});
} else {
throw new Error('No answer to submit.');
}
},
[patchAppData, userAnswerAppData],
);

const deleteAnswer = useMemo(
() =>
(id?: UserAnswerAppData['id']): void => {
if (id) {
deleteAppData({ id });
} else if (userAnswerAppData) {
deleteAppData({ id: userAnswerAppData?.id });
}
},
[deleteAppData, userAnswerAppData],
);
const contextValue = useMemo(
() => ({
userAnswer: userAnswerAppData?.data,
setAnswer,
submitAnswer,
allAnswersAppData: isAdmin ? allAnswersAppData : undefined,
deleteAnswer,
}),
[
allAnswersAppData,
deleteAnswer,
isAdmin,
setAnswer,
submitAnswer,
userAnswerAppData?.data,
],
);

return (
<UserAnswersContext.Provider value={contextValue}>
{children}
</UserAnswersContext.Provider>
);
};

const useUserAnswers = (): UserAnswersContextType =>
useContext(UserAnswersContext);

export default useUserAnswers;
Loading

0 comments on commit 62d4e1b

Please sign in to comment.