From 4bd5db33a8a46d4c564a98d3d2522c35cf135323 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 25 Oct 2024 12:30:03 +0200 Subject: [PATCH 01/10] Add seniority survey modal --- .../modals/seniority_survey_modal.rb | 21 ++++ .../modals/SenioritySurveyModal.tsx | 103 ++++++++++++++++++ app/javascript/packs/internal.tsx | 10 +- app/views/dashboard/show.html.haml | 1 + 4 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 app/helpers/react_components/modals/seniority_survey_modal.rb create mode 100644 app/javascript/components/modals/SenioritySurveyModal.tsx diff --git a/app/helpers/react_components/modals/seniority_survey_modal.rb b/app/helpers/react_components/modals/seniority_survey_modal.rb new file mode 100644 index 0000000000..c7fdffbf02 --- /dev/null +++ b/app/helpers/react_components/modals/seniority_survey_modal.rb @@ -0,0 +1,21 @@ +module ReactComponents + module Modals + class SenioritySurveyModal < ReactComponent + def to_s + return if current_user.introducer_dismissed?(slug) + + super( + "modals-seniority-survey-modal", + { + endpoint: Exercism::Routes.hide_api_settings_introducer_path(slug) + } + ) + end + + private + def slug + "seniority-survey-modal" + end + end + end +end diff --git a/app/javascript/components/modals/SenioritySurveyModal.tsx b/app/javascript/components/modals/SenioritySurveyModal.tsx new file mode 100644 index 0000000000..777e3ec53f --- /dev/null +++ b/app/javascript/components/modals/SenioritySurveyModal.tsx @@ -0,0 +1,103 @@ +import React, { useCallback, useState } from 'react' +import { useMutation } from '@tanstack/react-query' +import { sendRequest } from '@/utils/send-request' +import { FormButton } from '@/components/common/FormButton' +import { ErrorBoundary, ErrorMessage } from '@/components/ErrorBoundary' +import { Modal, ModalProps } from './Modal' +import { assembleClassNames } from '@/utils/assemble-classnames' + +const DEFAULT_ERROR = new Error('Unable to dismiss modal') + +const SENIORITIES = [ + 'beginner', + 'junior', + 'medior', + 'senior', + 'staff', + 'lead', +] as const +type Seniority = typeof SENIORITIES[number] | '' + +export default function SenioritySurveyModal({ + endpoint, + ...props +}: Omit & { + endpoint: string +}): JSX.Element { + const [open, setOpen] = useState(true) + const [selected, setSelected] = useState('') + const { + mutate: mutation, + status, + error, + } = useMutation( + () => { + const { fetch } = sendRequest({ + endpoint: endpoint, + method: 'PATCH', + body: null, + }) + + return fetch + }, + { + onSuccess: () => { + setOpen(false) + }, + } + ) + + const handleClick = useCallback(() => { + mutation() + }, [mutation]) + + return ( + null} + className="m-welcome" + > + {' '} +
+ {' '} +
+ {' '} +

Hey!

{' '} +

+ We're starting to add more features and nudges and want to ensure + they're relevant to you. +

+

What seniority are you?

+
+
+ {SENIORITIES.map((seniority) => ( + + ))} +
+ + Save my choice + + + + +
+
+ ) +} diff --git a/app/javascript/packs/internal.tsx b/app/javascript/packs/internal.tsx index a0c22bf5f0..2a3f052c1d 100644 --- a/app/javascript/packs/internal.tsx +++ b/app/javascript/packs/internal.tsx @@ -171,8 +171,9 @@ const IterationSummaryWithWebsockets = lazy( const NotificationsList = lazy( () => import('@/components/notifications/NotificationsList') ) -const WelcomeModal = lazy( - () => import('@/components/modals/welcome-modal/WelcomeModal') +const WelcomeModal = lazy(() => import('@/components/modals/WelcomeModal')) +const SenioritySurveyModal = lazy( + () => import('@/components/modals/SenioritySurveyModal') ) const WelcomeToInsidersModal = lazy( () => import('@/components/modals/WelcomeToInsidersModal') @@ -248,6 +249,11 @@ initReact({ /> ), + 'modals-seniority-survey-modal': (data: any) => ( + + + + ), 'modals-track-welcome-modal': (data: any) => ( (data)} /> diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index 983d51bb24..fb5da5a677 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -2,6 +2,7 @@ #page-dashboard = render ReactComponents::Modals::WelcomeModal.new + = render ReactComponents::Modals::SenioritySurveyModal.new = render ReactComponents::Modals::BegModal.new .summary-bar From 022138d8abfeeb7463e1e583bb5b5997876b07a2 Mon Sep 17 00:00:00 2001 From: Jeremy Walker Date: Thu, 31 Oct 2024 21:39:34 +0900 Subject: [PATCH 02/10] Update copy --- .../modals/SenioritySurveyModal.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/javascript/components/modals/SenioritySurveyModal.tsx b/app/javascript/components/modals/SenioritySurveyModal.tsx index 777e3ec53f..2297df14b7 100644 --- a/app/javascript/components/modals/SenioritySurveyModal.tsx +++ b/app/javascript/components/modals/SenioritySurveyModal.tsx @@ -59,23 +59,21 @@ export default function SenioritySurveyModal({ onClose={() => null} className="m-welcome" > - {' '}
- {' '}
- {' '} -

Hey!

{' '} +

Hey there 👋

- We're starting to add more features and nudges and want to ensure - they're relevant to you. + As Exercism grows, certain features are becoming more relevant than + others based on your experience coding. So we're starting to filter + what we show by your seniority.

-

What seniority are you?

+

How experienced a developer are you?

-
+
{SENIORITIES.map((seniority) => (
+ +

+ (This can be updated at any time in your settings) +

Date: Thu, 31 Oct 2024 16:45:32 +0100 Subject: [PATCH 03/10] Correctly import WelcomeModal --- app/javascript/packs/internal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/packs/internal.tsx b/app/javascript/packs/internal.tsx index 2a3f052c1d..48b71eb02b 100644 --- a/app/javascript/packs/internal.tsx +++ b/app/javascript/packs/internal.tsx @@ -171,7 +171,9 @@ const IterationSummaryWithWebsockets = lazy( const NotificationsList = lazy( () => import('@/components/notifications/NotificationsList') ) -const WelcomeModal = lazy(() => import('@/components/modals/WelcomeModal')) +const WelcomeModal = lazy( + () => import('@/components/modals/welcome-modal/WelcomeModal') +) const SenioritySurveyModal = lazy( () => import('@/components/modals/SenioritySurveyModal') ) From e981a9d58f354d724570c44f2647138fc0db3eb9 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 31 Oct 2024 16:46:38 +0100 Subject: [PATCH 04/10] Fix wrong type in `internal.tsx` --- app/javascript/packs/internal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/packs/internal.tsx b/app/javascript/packs/internal.tsx index 48b71eb02b..6770df95dd 100644 --- a/app/javascript/packs/internal.tsx +++ b/app/javascript/packs/internal.tsx @@ -224,7 +224,9 @@ initReact({ (data.request)} - links={camelizeKeysAs(data.links)} + links={camelizeKeysAs( + data.links + )} /> ), From 5faf09e3ffa4a4777ca61480851be51d60bcc18f Mon Sep 17 00:00:00 2001 From: dem4ron Date: Mon, 4 Nov 2024 09:35:47 +0100 Subject: [PATCH 05/10] Set max width --- app/javascript/components/modals/SenioritySurveyModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/components/modals/SenioritySurveyModal.tsx b/app/javascript/components/modals/SenioritySurveyModal.tsx index 2297df14b7..0de13fca7c 100644 --- a/app/javascript/components/modals/SenioritySurveyModal.tsx +++ b/app/javascript/components/modals/SenioritySurveyModal.tsx @@ -56,6 +56,7 @@ export default function SenioritySurveyModal({ cover={true} open={open} {...props} + style={{ content: { maxWidth: '620px' } }} onClose={() => null} className="m-welcome" > From 02fa92103be1bb7a6c0a68b28df8e138d682cb8b Mon Sep 17 00:00:00 2001 From: dem4ron Date: Mon, 4 Nov 2024 15:58:52 +0100 Subject: [PATCH 06/10] Add links + mutations --- .../modals/seniority_survey_modal.rb | 5 +- .../modals/SenioritySurveyModal.tsx | 87 +++++++++++++------ .../modals/welcome-modal/WelcomeModal.tsx | 4 +- 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/app/helpers/react_components/modals/seniority_survey_modal.rb b/app/helpers/react_components/modals/seniority_survey_modal.rb index c7fdffbf02..2f1cc93201 100644 --- a/app/helpers/react_components/modals/seniority_survey_modal.rb +++ b/app/helpers/react_components/modals/seniority_survey_modal.rb @@ -7,7 +7,10 @@ def to_s super( "modals-seniority-survey-modal", { - endpoint: Exercism::Routes.hide_api_settings_introducer_path(slug) + links: { + hide_modal_endpoint: Exercism::Routes.hide_api_settings_introducer_path(slug), + api_user_endpoint: Exercism::Routes.api_user_url + } } ) end diff --git a/app/javascript/components/modals/SenioritySurveyModal.tsx b/app/javascript/components/modals/SenioritySurveyModal.tsx index 0de13fca7c..c03207972a 100644 --- a/app/javascript/components/modals/SenioritySurveyModal.tsx +++ b/app/javascript/components/modals/SenioritySurveyModal.tsx @@ -5,35 +5,50 @@ import { FormButton } from '@/components/common/FormButton' import { ErrorBoundary, ErrorMessage } from '@/components/ErrorBoundary' import { Modal, ModalProps } from './Modal' import { assembleClassNames } from '@/utils/assemble-classnames' +import { SeniorityLevel } from './welcome-modal/WelcomeModal' const DEFAULT_ERROR = new Error('Unable to dismiss modal') -const SENIORITIES = [ - 'beginner', - 'junior', - 'medior', - 'senior', - 'staff', - 'lead', -] as const -type Seniority = typeof SENIORITIES[number] | '' +const SENIORITIES: { label: string; value: SeniorityLevel }[] = [ + { + label: 'Absolute Beginner', + value: 'absolute_beginner', + }, + { + label: 'Beginner', + value: 'beginner', + }, + { + label: 'Junior Developer', + value: 'junior', + }, + { + label: 'Mid-level Developer', + value: 'mid', + }, + { + label: 'Senior Developer', + value: 'senior', + }, +] export default function SenioritySurveyModal({ - endpoint, + links, ...props }: Omit & { - endpoint: string + links: { hideModalEndpoint: string; apiUserEndpoint: string } }): JSX.Element { const [open, setOpen] = useState(true) - const [selected, setSelected] = useState('') + const [selected, setSelected] = useState('') const { - mutate: mutation, - status, - error, + mutate: hideModalMutation, + status: hideModalMutationStatus, + error: hideModalMutationError, } = useMutation( () => { const { fetch } = sendRequest({ - endpoint: endpoint, + // close modal endpoint + endpoint: links.hideModalEndpoint, method: 'PATCH', body: null, }) @@ -47,9 +62,23 @@ export default function SenioritySurveyModal({ } ) - const handleClick = useCallback(() => { - mutation() - }, [mutation]) + const { mutate: setSeniorityMutation } = useMutation( + (seniority: SeniorityLevel) => { + const { fetch } = sendRequest({ + endpoint: links.apiUserEndpoint + `?user[seniority]=${seniority}`, + method: 'PATCH', + body: null, + }) + + return fetch + } + ) + + const handleSave = useCallback(() => { + if (selected === '') return + setSeniorityMutation(selected) + hideModalMutation() + }, [selected, setSeniorityMutation, hideModalMutation]) return ( setSelected(seniority)} + onClick={() => setSelected(seniority.value)} > - {seniority} + {seniority.label} ))}
-

+

(This can be updated at any time in your settings)

Save my choice - - + +
diff --git a/app/javascript/components/modals/welcome-modal/WelcomeModal.tsx b/app/javascript/components/modals/welcome-modal/WelcomeModal.tsx index 52cb5d886a..61c2c1de2a 100644 --- a/app/javascript/components/modals/welcome-modal/WelcomeModal.tsx +++ b/app/javascript/components/modals/welcome-modal/WelcomeModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useState } from 'react' +import React, { useContext, useState } from 'react' import { useMutation } from '@tanstack/react-query' import { sendRequest } from '@/utils/send-request' import { Modal, ModalProps } from '../Modal' @@ -22,7 +22,7 @@ type WelcomeModalContextProps = { setCurrentView: React.Dispatch> } -type SeniorityLevel = +export type SeniorityLevel = | 'absolute_beginner' | 'beginner' | 'junior' From db84ea88ea9adf9014bdf49522ebd99af52989c2 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Mon, 4 Nov 2024 16:22:36 +0100 Subject: [PATCH 07/10] Add logic, and different views --- .../modals/SenioritySurveyModal.tsx | 205 +++++++++++++----- 1 file changed, 146 insertions(+), 59 deletions(-) diff --git a/app/javascript/components/modals/SenioritySurveyModal.tsx b/app/javascript/components/modals/SenioritySurveyModal.tsx index c03207972a..aa185aea7c 100644 --- a/app/javascript/components/modals/SenioritySurveyModal.tsx +++ b/app/javascript/components/modals/SenioritySurveyModal.tsx @@ -1,4 +1,11 @@ -import React, { useCallback, useState } from 'react' +import React, { + createContext, + Dispatch, + SetStateAction, + useCallback, + useContext, + useState, +} from 'react' import { useMutation } from '@tanstack/react-query' import { sendRequest } from '@/utils/send-request' import { FormButton } from '@/components/common/FormButton' @@ -32,14 +39,62 @@ const SENIORITIES: { label: string; value: SeniorityLevel }[] = [ }, ] +type ViewVariant = 'initial' | 'thanks' + +type Links = { hideModalEndpoint: string; apiUserEndpoint: string } + +type SenioritySurveyModalContextProps = { + currentView: ViewVariant + setCurrentView: Dispatch> + setOpen: Dispatch> + links: Links +} + +const DEFAULT_VIEW = 'thanks' + +const SenioritySurveyModalContext = + createContext({ + currentView: DEFAULT_VIEW, + setCurrentView: () => {}, + setOpen: () => {}, + links: { apiUserEndpoint: '', hideModalEndpoint: '' }, + }) + export default function SenioritySurveyModal({ links, ...props }: Omit & { - links: { hideModalEndpoint: string; apiUserEndpoint: string } + links: Links }): JSX.Element { const [open, setOpen] = useState(true) - const [selected, setSelected] = useState('') + + const [currentView, setCurrentView] = useState(DEFAULT_VIEW) + + return ( + + null} + className="m-welcome" + > + + + + ) +} + +function Inner({ currentView }: { currentView: ViewVariant }) { + return currentView === 'initial' ? : +} + +function ThanksView() { + const { links, setOpen } = useContext(SenioritySurveyModalContext) + const { mutate: hideModalMutation, status: hideModalMutationStatus, @@ -62,7 +117,46 @@ export default function SenioritySurveyModal({ } ) - const { mutate: setSeniorityMutation } = useMutation( + const handleSave = useCallback(() => { + hideModalMutation() + }, [hideModalMutation]) + return ( +
+
+

Thanks for letting us know!

+

+ We'll use this information to make sure you're seeing the most + relevant content. +

+
+ + + Close this modal + + + + +
+ ) +} + +function SenioritySelectorView() { + const { links, setCurrentView } = useContext(SenioritySurveyModalContext) + const [selected, setSelected] = useState('') + + const { + mutate: setSeniorityMutation, + status: setSeniorityMutationStatus, + error: setSeniorityMutationError, + } = useMutation( (seniority: SeniorityLevel) => { const { fetch } = sendRequest({ endpoint: links.apiUserEndpoint + `?user[seniority]=${seniority}`, @@ -71,69 +165,62 @@ export default function SenioritySurveyModal({ }) return fetch + }, + { + onSuccess: () => setCurrentView('thanks'), } ) - const handleSave = useCallback(() => { + const handleSaveSeniorityLevel = useCallback(() => { if (selected === '') return setSeniorityMutation(selected) - hideModalMutation() - }, [selected, setSeniorityMutation, hideModalMutation]) + }, [selected, setSeniorityMutation]) return ( - null} - className="m-welcome" - > -
-
-

Hey there 👋

-

- As Exercism grows, certain features are becoming more relevant than - others based on your experience coding. So we're starting to filter - what we show by your seniority. -

-

How experienced a developer are you?

-
-
- {SENIORITIES.map((seniority) => ( - - ))} -
- -

- (This can be updated at any time in your settings) +

+
+

Hey there 👋

+

+ As Exercism grows, certain features are becoming more relevant than + others based on your experience coding. So we're starting to filter + what we show by your seniority.

- - Save my choice - - - - +

How experienced a developer are you?

+
+
+ {SENIORITIES.map((seniority) => ( + + ))}
- + +

+ (This can be updated at any time in your settings) +

+ + Save my choice + + + + +
) } From 668189ddfa4d3d790dbeb1dd544020486e5b0edc Mon Sep 17 00:00:00 2001 From: dem4ron Date: Mon, 4 Nov 2024 16:29:44 +0100 Subject: [PATCH 08/10] Refactor --- .../modals/SenioritySurveyModal.tsx | 226 ------------------ .../seniority-survey-modal/InitialView.tsx | 110 +++++++++ .../SenioritySurveyModal.tsx | 69 ++++++ .../seniority-survey-modal/ThanksView.tsx | 63 +++++ app/javascript/packs/internal.tsx | 3 +- 5 files changed, 244 insertions(+), 227 deletions(-) delete mode 100644 app/javascript/components/modals/SenioritySurveyModal.tsx create mode 100644 app/javascript/components/modals/seniority-survey-modal/InitialView.tsx create mode 100644 app/javascript/components/modals/seniority-survey-modal/SenioritySurveyModal.tsx create mode 100644 app/javascript/components/modals/seniority-survey-modal/ThanksView.tsx diff --git a/app/javascript/components/modals/SenioritySurveyModal.tsx b/app/javascript/components/modals/SenioritySurveyModal.tsx deleted file mode 100644 index aa185aea7c..0000000000 --- a/app/javascript/components/modals/SenioritySurveyModal.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import React, { - createContext, - Dispatch, - SetStateAction, - useCallback, - useContext, - useState, -} from 'react' -import { useMutation } from '@tanstack/react-query' -import { sendRequest } from '@/utils/send-request' -import { FormButton } from '@/components/common/FormButton' -import { ErrorBoundary, ErrorMessage } from '@/components/ErrorBoundary' -import { Modal, ModalProps } from './Modal' -import { assembleClassNames } from '@/utils/assemble-classnames' -import { SeniorityLevel } from './welcome-modal/WelcomeModal' - -const DEFAULT_ERROR = new Error('Unable to dismiss modal') - -const SENIORITIES: { label: string; value: SeniorityLevel }[] = [ - { - label: 'Absolute Beginner', - value: 'absolute_beginner', - }, - { - label: 'Beginner', - value: 'beginner', - }, - { - label: 'Junior Developer', - value: 'junior', - }, - { - label: 'Mid-level Developer', - value: 'mid', - }, - { - label: 'Senior Developer', - value: 'senior', - }, -] - -type ViewVariant = 'initial' | 'thanks' - -type Links = { hideModalEndpoint: string; apiUserEndpoint: string } - -type SenioritySurveyModalContextProps = { - currentView: ViewVariant - setCurrentView: Dispatch> - setOpen: Dispatch> - links: Links -} - -const DEFAULT_VIEW = 'thanks' - -const SenioritySurveyModalContext = - createContext({ - currentView: DEFAULT_VIEW, - setCurrentView: () => {}, - setOpen: () => {}, - links: { apiUserEndpoint: '', hideModalEndpoint: '' }, - }) - -export default function SenioritySurveyModal({ - links, - ...props -}: Omit & { - links: Links -}): JSX.Element { - const [open, setOpen] = useState(true) - - const [currentView, setCurrentView] = useState(DEFAULT_VIEW) - - return ( - - null} - className="m-welcome" - > - - - - ) -} - -function Inner({ currentView }: { currentView: ViewVariant }) { - return currentView === 'initial' ? : -} - -function ThanksView() { - const { links, setOpen } = useContext(SenioritySurveyModalContext) - - const { - mutate: hideModalMutation, - status: hideModalMutationStatus, - error: hideModalMutationError, - } = useMutation( - () => { - const { fetch } = sendRequest({ - // close modal endpoint - endpoint: links.hideModalEndpoint, - method: 'PATCH', - body: null, - }) - - return fetch - }, - { - onSuccess: () => { - setOpen(false) - }, - } - ) - - const handleSave = useCallback(() => { - hideModalMutation() - }, [hideModalMutation]) - return ( -
-
-

Thanks for letting us know!

-

- We'll use this information to make sure you're seeing the most - relevant content. -

-
- - - Close this modal - - - - -
- ) -} - -function SenioritySelectorView() { - const { links, setCurrentView } = useContext(SenioritySurveyModalContext) - const [selected, setSelected] = useState('') - - const { - mutate: setSeniorityMutation, - status: setSeniorityMutationStatus, - error: setSeniorityMutationError, - } = useMutation( - (seniority: SeniorityLevel) => { - const { fetch } = sendRequest({ - endpoint: links.apiUserEndpoint + `?user[seniority]=${seniority}`, - method: 'PATCH', - body: null, - }) - - return fetch - }, - { - onSuccess: () => setCurrentView('thanks'), - } - ) - - const handleSaveSeniorityLevel = useCallback(() => { - if (selected === '') return - setSeniorityMutation(selected) - }, [selected, setSeniorityMutation]) - - return ( -
-
-

Hey there 👋

-

- As Exercism grows, certain features are becoming more relevant than - others based on your experience coding. So we're starting to filter - what we show by your seniority. -

-

How experienced a developer are you?

-
-
- {SENIORITIES.map((seniority) => ( - - ))} -
- -

- (This can be updated at any time in your settings) -

- - Save my choice - - - - -
- ) -} diff --git a/app/javascript/components/modals/seniority-survey-modal/InitialView.tsx b/app/javascript/components/modals/seniority-survey-modal/InitialView.tsx new file mode 100644 index 0000000000..72ee79c135 --- /dev/null +++ b/app/javascript/components/modals/seniority-survey-modal/InitialView.tsx @@ -0,0 +1,110 @@ +import React, { useContext, useState, useCallback } from 'react' +import { useMutation } from '@tanstack/react-query' +import { sendRequest } from '@/utils/send-request' +import { assembleClassNames } from '@/utils/assemble-classnames' +import { FormButton } from '@/components/common/FormButton' +import { ErrorBoundary, ErrorMessage } from '@/components/ErrorBoundary' +import { SenioritySurveyModalContext } from './SenioritySurveyModal' +import type { SeniorityLevel } from '../welcome-modal/WelcomeModal' + +const DEFAULT_ERROR = new Error('Unable to save seniority level.') + +const SENIORITIES: { label: string; value: SeniorityLevel }[] = [ + { + label: 'Absolute Beginner', + value: 'absolute_beginner', + }, + { + label: 'Beginner', + value: 'beginner', + }, + { + label: 'Junior Developer', + value: 'junior', + }, + { + label: 'Mid-level Developer', + value: 'mid', + }, + { + label: 'Senior Developer', + value: 'senior', + }, +] + +export function InitialView() { + const { links, setCurrentView } = useContext(SenioritySurveyModalContext) + const [selected, setSelected] = useState('') + + const { + mutate: setSeniorityMutation, + status: setSeniorityMutationStatus, + error: setSeniorityMutationError, + } = useMutation( + (seniority: SeniorityLevel) => { + const { fetch } = sendRequest({ + endpoint: links.apiUserEndpoint + `?user[seniority]=${seniority}`, + method: 'PATCH', + body: null, + }) + + return fetch + }, + { + onSuccess: () => setCurrentView('thanks'), + } + ) + + const handleSaveSeniorityLevel = useCallback(() => { + if (selected === '') return + setSeniorityMutation(selected) + }, [selected, setSeniorityMutation]) + + return ( +
+
+

Hey there 👋

+

+ As Exercism grows, certain features are becoming more relevant than + others based on your experience coding. So we're starting to filter + what we show by your seniority. +

+

How experienced a developer are you?

+
+
+ {SENIORITIES.map((seniority) => ( + + ))} +
+ +

+ (This can be updated at any time in your settings) +

+ + Save my choice + + + + +
+ ) +} diff --git a/app/javascript/components/modals/seniority-survey-modal/SenioritySurveyModal.tsx b/app/javascript/components/modals/seniority-survey-modal/SenioritySurveyModal.tsx new file mode 100644 index 0000000000..68e28280c4 --- /dev/null +++ b/app/javascript/components/modals/seniority-survey-modal/SenioritySurveyModal.tsx @@ -0,0 +1,69 @@ +import React, { + createContext, + Dispatch, + SetStateAction, + useContext, + useState, +} from 'react' +import { Modal, ModalProps } from '../Modal' +import { ThanksView } from './ThanksView' +import { InitialView } from './InitialView' + +type ViewVariant = 'initial' | 'thanks' + +type Links = { hideModalEndpoint: string; apiUserEndpoint: string } + +type SenioritySurveyModalContextProps = { + currentView: ViewVariant + setCurrentView: Dispatch> + setOpen: Dispatch> + links: Links +} + +const DEFAULT_VIEW = 'thanks' + +export const SenioritySurveyModalContext = + createContext({ + currentView: DEFAULT_VIEW, + setCurrentView: () => {}, + setOpen: () => {}, + links: { apiUserEndpoint: '', hideModalEndpoint: '' }, + }) + +export default function SenioritySurveyModal({ + links, + ...props +}: Omit & { + links: Links +}): JSX.Element { + const [open, setOpen] = useState(true) + + const [currentView, setCurrentView] = useState(DEFAULT_VIEW) + + return ( + + null} + className="m-welcome" + > + + + + ) +} + +function Inner() { + const { currentView } = useContext(SenioritySurveyModalContext) + switch (currentView) { + case 'initial': + return + case 'thanks': + return + } +} diff --git a/app/javascript/components/modals/seniority-survey-modal/ThanksView.tsx b/app/javascript/components/modals/seniority-survey-modal/ThanksView.tsx new file mode 100644 index 0000000000..39ece425f5 --- /dev/null +++ b/app/javascript/components/modals/seniority-survey-modal/ThanksView.tsx @@ -0,0 +1,63 @@ +import React, { useContext, useCallback } from 'react' +import { useMutation } from '@tanstack/react-query' +import { FormButton } from '@/components/common/FormButton' +import { ErrorBoundary, ErrorMessage } from '@/components/ErrorBoundary' +import { sendRequest } from '@/utils/send-request' +import { SenioritySurveyModalContext } from './SenioritySurveyModal' + +const DEFAULT_ERROR = new Error('Unable to dismiss modal') + +export function ThanksView() { + const { links, setOpen } = useContext(SenioritySurveyModalContext) + + const { + mutate: hideModalMutation, + status: hideModalMutationStatus, + error: hideModalMutationError, + } = useMutation( + () => { + const { fetch } = sendRequest({ + endpoint: links.hideModalEndpoint, + method: 'PATCH', + body: null, + }) + + return fetch + }, + { + onSuccess: () => { + setOpen(false) + }, + } + ) + + const handleSave = useCallback(() => { + hideModalMutation() + }, [hideModalMutation]) + return ( +
+
+

Thanks for letting us know!

+

+ We'll use this information to make sure you're seeing the most + relevant content. +

+
+ + + Close this modal + + + + +
+ ) +} diff --git a/app/javascript/packs/internal.tsx b/app/javascript/packs/internal.tsx index 6770df95dd..3632c14ae0 100644 --- a/app/javascript/packs/internal.tsx +++ b/app/javascript/packs/internal.tsx @@ -175,7 +175,8 @@ const WelcomeModal = lazy( () => import('@/components/modals/welcome-modal/WelcomeModal') ) const SenioritySurveyModal = lazy( - () => import('@/components/modals/SenioritySurveyModal') + () => + import('@/components/modals/seniority-survey-modal/SenioritySurveyModal') ) const WelcomeToInsidersModal = lazy( () => import('@/components/modals/WelcomeToInsidersModal') From f2527af3305b110839d78d831630a6a66dfcb8bb Mon Sep 17 00:00:00 2001 From: dem4ron Date: Mon, 4 Nov 2024 17:11:27 +0100 Subject: [PATCH 09/10] Add BootcampAdvertisment view --- .../modals/seniority_survey_modal.rb | 2 +- .../BootcampAdvertismentView.tsx | 108 ++++++++++++++++++ .../seniority-survey-modal/InitialView.tsx | 15 ++- .../SenioritySurveyModal.tsx | 58 +++++++++- .../seniority-survey-modal/ThanksView.tsx | 38 +----- app/javascript/packs/internal.tsx | 7 +- 6 files changed, 187 insertions(+), 41 deletions(-) create mode 100644 app/javascript/components/modals/seniority-survey-modal/BootcampAdvertismentView.tsx diff --git a/app/helpers/react_components/modals/seniority_survey_modal.rb b/app/helpers/react_components/modals/seniority_survey_modal.rb index 2f1cc93201..1be5d55f98 100644 --- a/app/helpers/react_components/modals/seniority_survey_modal.rb +++ b/app/helpers/react_components/modals/seniority_survey_modal.rb @@ -2,7 +2,7 @@ module ReactComponents module Modals class SenioritySurveyModal < ReactComponent def to_s - return if current_user.introducer_dismissed?(slug) + # return if current_user.introducer_dismissed?(slug) super( "modals-seniority-survey-modal", diff --git a/app/javascript/components/modals/seniority-survey-modal/BootcampAdvertismentView.tsx b/app/javascript/components/modals/seniority-survey-modal/BootcampAdvertismentView.tsx new file mode 100644 index 0000000000..d771e91b98 --- /dev/null +++ b/app/javascript/components/modals/seniority-survey-modal/BootcampAdvertismentView.tsx @@ -0,0 +1,108 @@ +import React, { useContext } from 'react' +import { Icon } from '@/components/common' +import { FormButton } from '@/components/common/FormButton' +import { ErrorBoundary, ErrorMessage } from '@/components/ErrorBoundary' +import { SenioritySurveyModalContext } from './SenioritySurveyModal' + +const DEFAULT_ERROR = new Error('Unable to dismiss modal') + +export function BootcampAdvertismentView() { + const { patchCloseModal } = useContext(SenioritySurveyModalContext) + return ( + <> +
+
+

Oooh, good timing! 👏

+ +

+ You've signed up at a great time! In January, we're running a + one-off{' '} + + part-time, remote bootcamp for beginners! + +

+

+ We're going to teach you all the fundamentals with a hands-on + project-based approach. No stuffy videos, no heavy theory, just lots + of fun coding! +

+

+ It's super affordable, with discounts if you're a student, + unemployed, or live in a country with an emerging economy. +

+

Watch our intro video to learn more 👉

+
+
+ + Go to the Bootcamp ✨ + + + + Skip & Close + +
+ + + +
+
+
+ +
+ Exercism + Bootcamp +
+
+
+