From b04b2a1e95c84e2b9db6c701b34a622c406d9583 Mon Sep 17 00:00:00 2001 From: Flacial Date: Mon, 23 Jan 2023 10:15:18 +0400 Subject: [PATCH] feat(setting): Create linked accounts section --- .../pages/settings/account/index.test.js | 199 +++++++++++++++--- pages/settings/account/index.tsx | 97 ++++++++- scss/accountSettings.module.scss | 22 ++ 3 files changed, 289 insertions(+), 29 deletions(-) diff --git a/__tests__/pages/settings/account/index.test.js b/__tests__/pages/settings/account/index.test.js index d0b11640d..ffe6fbdb3 100644 --- a/__tests__/pages/settings/account/index.test.js +++ b/__tests__/pages/settings/account/index.test.js @@ -1,20 +1,26 @@ jest.mock('@sentry/browser') import React from 'react' -import { render, screen } from '@testing-library/react' +import { + render, + screen, + waitForElementToBeRemoved +} from '@testing-library/react' import userEvent from '@testing-library/user-event' import AccountSettings from '../../../../pages/settings/account/index' import { MockedProvider } from '@apollo/client/testing' -import GetApp from '../../../../graphql/queries/getApp.ts' +import USER_INFO from '../../../../graphql/queries/userInfo' import UPDATE_USER_NAMES from '../../../../graphql/queries/updateUserNames' import UPDATE_USER_PASSWORD from '../../../../graphql/queries/updateUserPassword' import dummyLessonData from '../../../../__dummy__/lessonData' import dummySessionData from '../../../../__dummy__/sessionData' -import dummyAlertData from '../../../../__dummy__/alertData' +import dummyStarsData from '../../../../__dummy__/starsData' +import { signIn, useSession } from 'next-auth/react' import * as Sentry from '@sentry/browser' // Imported to be able to use expect(...).toBeInTheDocument() import '@testing-library/jest-dom' +import { UNLINK_DISCORD } from '../../../../graphql/queries/unlinkDiscord' const UpdateUserNamesMock = { request: { @@ -112,14 +118,92 @@ const UpdateUserPasswordMockWithError = { }) } +const unlinkDiscordMock = { + request: { + query: UNLINK_DISCORD + }, + result: jest.fn(() => ({ + data: { + unlinkDiscord: { + id: 1 + } + } + })) +} + +const unlinkDiscordMockWithError = { + request: { + query: UNLINK_DISCORD + }, + newData: jest.fn(() => { + throw new Error('Error') + }) +} + +const session = { + ...dummySessionData, + lessonStatus: [ + { + id: 1, + userId: '1', + lessonId: 5, + passedAt: `'true'`, + starGiven: null, + starsReceived: [dummyStarsData[0]] + }, + { + id: 2, + userId: '1', + lessonId: 2, + passedAt: Date.now().toString(), + starGiven: null, + starsReceived: [dummyStarsData[1]] + }, + { + id: 3, + userId: '1', + lessonId: 1, + passedAt: Date.now().toString(), + starGiven: null, + starsReceived: [dummyStarsData[2], dummyStarsData[9]] + } + ] +} + +const UserInfoMock = { + request: { + query: USER_INFO, + variables: { + username: 'fakeusername' + } + }, + result: { + data: { + lessons: dummyLessonData, + userInfo: session + } + } +} + const mocks = [ + unlinkDiscordMock, + UserInfoMock, + UpdateUserNamesMock, + UpdateUserPasswordMock +] + +const mocksWithDiscordData = [ + unlinkDiscordMock, { - request: { query: GetApp }, + ...UserInfoMock, result: { + ...UserInfoMock.result, data: { - session: dummySessionData, - lessons: dummyLessonData, - alerts: dummyAlertData + ...UserInfoMock.result.data, + userInfo: { + ...session, + user: { ...session.user, discordUsername: 'floppityflob' } + } } } }, @@ -128,16 +212,17 @@ const mocks = [ ] const mocksWithError = [ + unlinkDiscordMockWithError, { - request: { query: GetApp }, + ...UserInfoMock, result: { + ...UserInfoMock.result, data: { - session: { - ...dummySessionData, - user: { ...dummySessionData.user, name: null } - }, - lessons: dummyLessonData, - alerts: dummyAlertData + ...UserInfoMock.result.data, + userInfo: { + ...session, + user: { ...session.user, name: null, discordUsername: 'floppityflob' } + } } } }, @@ -145,16 +230,8 @@ const mocksWithError = [ ] const updateUserPasswordMocksWithError = [ - { - request: { query: GetApp }, - result: { - data: { - session: dummySessionData, - lessons: dummyLessonData, - alerts: dummyAlertData - } - } - }, + unlinkDiscordMock, + UserInfoMock, UpdateUserPasswordMockWithError ] @@ -327,7 +404,7 @@ describe('Account settings page', () => { }) }) - describe('Password settings', () => { + describe('Password setting', () => { it('Should update inputs and submit', async () => { expect.assertions(3) @@ -457,4 +534,76 @@ describe('Account settings page', () => { expect(screen.queryByRole('alert')).not.toBeInTheDocument() }) }) + + describe('Linked account setting', () => { + it('Should start linking Discord flow', async () => { + expect.assertions(1) + + render( + + + + ) + + await waitForElementToBeRemoved(() => screen.queryByText('Loading...')) + + const connectBtn = await screen.findByTestId('connect-to-discord') + + await userEvent.click(connectBtn) + + expect(signIn).toBeCalled() + }) + + it('Should start unlinking Discord flow', async () => { + expect.assertions(1) + + render( + + + + ) + + await waitForElementToBeRemoved(() => screen.queryByText('Loading...')) + + const unlinkButton = await screen.findByTestId('unlink-discord') + + await userEvent.click(unlinkButton) + + expect(unlinkDiscordMock.result).toBeCalled() + }) + + it('Should capture exception when unlinking Discord', async () => { + expect.assertions(1) + + render( + + + + ) + + await waitForElementToBeRemoved(() => screen.queryByText('Loading...')) + + const unlinkButton = await screen.findByTestId('unlink-discord') + + await userEvent.click(unlinkButton) + + expect(Sentry.captureException).toBeCalled() + }) + + it('Should not call userInfo query if session username is missing', async () => { + expect.assertions(1) + + useSession.mockReturnValue({ + status: 'unauthenticated' + }) + + render( + + + + ) + + expect(await screen.findByText('Loading...')).toBeInTheDocument() + }) + }) }) diff --git a/pages/settings/account/index.tsx b/pages/settings/account/index.tsx index 860495d48..1b6ae8a9f 100644 --- a/pages/settings/account/index.tsx +++ b/pages/settings/account/index.tsx @@ -4,9 +4,10 @@ import { FormCard, Option } from '../../../components/FormCard' import { formChange } from '../../../helpers/formChange' import { GetAppQuery, - useGetAppQuery, + useUnlinkDiscordMutation, useUpdateUserNamesMutation, - useUpdateUserPasswordMutation + useUpdateUserPasswordMutation, + useUserInfoQuery } from '../../../graphql/index' import LoadingSpinner from '../../../components/LoadingSpinner' import styles from '../../../scss/accountSettings.module.scss' @@ -17,6 +18,11 @@ import { } from '../../../helpers/formValidation' import QueryInfo from '../../../components/QueryInfo' import * as Sentry from '@sentry/browser' +import { Button } from '../../../components/theme/Button' +import Image from 'next/image' +import { signIn, useSession } from 'next-auth/react' +import { SessionContext } from '../../../@types/auth' +import { useRouter } from 'next/router' const basicValues: ( username?: string, @@ -180,9 +186,90 @@ const PasswordSettings = () => { ) } +const LinkedAccountsSetting = ({ + discordUsername +}: { + discordUsername: string | undefined +}) => { + const router = useRouter() + const [unlinkDiscordMutation, { data, loading, error }] = + useUnlinkDiscordMutation() + + const unlinkDiscord = async () => { + try { + await unlinkDiscordMutation() + router.reload() + } catch (error) { + Sentry.captureException(error) + } + } + + return ( +
+

Linked accounts

+ +
+
+
+
+ +
+ Discord +
+ {discordUsername ? ( +
+ {discordUsername} + +
+ ) : ( + + )} +
+
+
+ ) +} + const AccountSettings = () => { - const { data, loading } = useGetAppQuery() - const { username, name } = data?.session.user || {} + const { data: session, status } = useSession() as SessionContext + const { data, loading: userInfoLoading } = useUserInfoQuery({ + variables: { + username: session?.user.username || '' + } + }) + + const loading = userInfoLoading || status === 'loading' + const { username, name, discordUsername } = data?.userInfo?.user || {} if (loading || !data) { return @@ -198,6 +285,8 @@ const AccountSettings = () => {
+
+ diff --git a/scss/accountSettings.module.scss b/scss/accountSettings.module.scss index 697c65df5..ab9258abc 100644 --- a/scss/accountSettings.module.scss +++ b/scss/accountSettings.module.scss @@ -13,6 +13,28 @@ flex-direction: column; } +.linkedAccounts__container { + display: flex; + flex-direction: column; + row-gap: 20px; +} + +.linkedAccounts__container__item { + display: flex; + align-items: center; + column-gap: 24px; +} + +.linkedAccounts__container__item__icon { + display: flex; + align-items: center; + column-gap: 8px; + + & div { + line-height: 0; + } +} + @media only screen and (max-width: 700px) { .container__child { width: 100%;