-
-
Notifications
You must be signed in to change notification settings - Fork 126
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add seniority survey modal * Update copy * Correctly import WelcomeModal * Fix wrong type in `internal.tsx` * Set max width * Add links + mutations * Add logic, and different views * Refactor * Add BootcampAdvertisment view * Set default view to `initial` * Update copy * Remove ThanksView, close if person is not a beginner * Guard against showing both modals quickly after each other * Show seniority modal on track page too * Tweak wording * Fix dropdown_test * Fix profile_dropdown_test * Fix user_loads_reputation_test * Fix views_track_documentation_test * Update user factory * Undo changes to application_system_test_case * Fix weird refute_text failure * Fix test * Add more multi-modal guards --------- Co-authored-by: Jeremy Walker <jez.walker@gmail.com>
- Loading branch information
Showing
19 changed files
with
512 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
app/helpers/react_components/modals/seniority_survey_modal.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
module ReactComponents | ||
module Modals | ||
class SenioritySurveyModal < ReactComponent | ||
SHOWN_AT_FLAG = "shown_seniority_modal_at".freeze | ||
|
||
def to_s | ||
return if showing_modal? | ||
return if current_user.seniority | ||
|
||
showing_modal! | ||
session[SHOWN_AT_FLAG] = Time.current | ||
|
||
super( | ||
"modals-seniority-survey-modal", | ||
{ | ||
links: { | ||
hide_modal_endpoint: Exercism::Routes.hide_api_settings_introducer_path(slug), | ||
api_user_endpoint: Exercism::Routes.api_user_url | ||
} | ||
} | ||
) | ||
end | ||
|
||
private | ||
def slug = "seniority-survey-modal" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
app/javascript/components/modals/seniority-survey-modal/BootcampAdvertismentView.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
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 ( | ||
<> | ||
<div className="lhs"> | ||
<header> | ||
<h1>Have you heard about the Bootcamp?</h1> | ||
|
||
<p className="mb-8"> | ||
In January, we're running a one-off{' '} | ||
<strong className="font-semibold text-softEmphasis"> | ||
part-time, remote bootcamp for beginners! 🎉 | ||
</strong> | ||
</p> | ||
<p className="mb-8"> | ||
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! | ||
</p> | ||
<p className="mb-8"> | ||
It's super affordable, with discounts if you're a student, | ||
unemployed, or live in a country with an emerging economy. | ||
</p> | ||
<p className="mb-20">Watch our intro video to learn more 👉</p> | ||
</header> | ||
<div className="flex gap-8"> | ||
<a | ||
href="https://bootcamp.exercism.org" | ||
className="btn-primary btn-l cursor-pointer" | ||
> | ||
Go to the Bootcamp ✨ | ||
</a> | ||
|
||
<FormButton | ||
status={patchCloseModal.status} | ||
className="btn-secondary btn-l" | ||
type="button" | ||
onClick={patchCloseModal.mutate} | ||
> | ||
Skip & Close | ||
</FormButton> | ||
</div> | ||
<ErrorBoundary resetKeys={[patchCloseModal.status]}> | ||
<ErrorMessage | ||
error={patchCloseModal.error} | ||
defaultError={DEFAULT_ERROR} | ||
/> | ||
</ErrorBoundary> | ||
</div> | ||
<div className="rhs pt-72"> | ||
<div className="flex flex-row gap-8 items-center justify-center text-16 text-textColor1 mb-16"> | ||
<Icon | ||
icon="exercism-face" | ||
className="filter-textColor1" | ||
alt="exercism-face" | ||
height={16} | ||
width={16} | ||
/> | ||
<div> | ||
<strong className="font-semibold"> Exercism </strong> | ||
Bootcamp | ||
</div> | ||
</div> | ||
<div | ||
className="video relative rounded-8 overflow-hidden !mb-16" | ||
style={{ padding: '56.25% 0 0 0', position: 'relative' }} | ||
> | ||
<iframe | ||
src="https://player.vimeo.com/video/1024390839?h=c2b3bdce14&badge=0&autopause=0&player_id=0&app_id=58479" | ||
title="Introducing the Exercism Bootcamp" | ||
frameBorder="0" | ||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" | ||
allowFullScreen | ||
/> | ||
</div> | ||
<div className="bubbles"> | ||
<div className="bubble"> | ||
<Icon category="bootcamp" alt="wave-icon" icon="wave" /> | ||
<div className="text"> | ||
<strong>Live</strong> teaching | ||
</div> | ||
</div> | ||
<div className="bubble"> | ||
<Icon category="bootcamp" alt="fun-icon" icon="fun" /> | ||
<div className="text"> | ||
<strong>Fun</strong> projects | ||
</div> | ||
</div> | ||
<div className="bubble"> | ||
<Icon category="bootcamp" alt="price-icon" icon="price" /> | ||
<div className="text"> | ||
Priced <strong>fairly</strong>{' '} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</> | ||
) | ||
} |
130 changes: 130 additions & 0 deletions
130
app/javascript/components/modals/seniority-survey-modal/InitialView.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
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' | ||
import { ErrorFallback } from '@/components/common/ErrorFallback' | ||
|
||
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, patchCloseModal } = useContext( | ||
SenioritySurveyModalContext | ||
) | ||
const [selected, setSelected] = useState<SeniorityLevel | ''>('') | ||
|
||
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: () => { | ||
if (selected.includes('beginner')) { | ||
setCurrentView('bootcamp-advertisment') | ||
return | ||
} | ||
|
||
patchCloseModal.mutate() | ||
}, | ||
} | ||
) | ||
|
||
const handleSaveSeniorityLevel = useCallback(() => { | ||
if (selected === '') return | ||
setSeniorityMutation(selected) | ||
}, [selected, setSeniorityMutation]) | ||
|
||
return ( | ||
<div className="lhs"> | ||
<header> | ||
<h1>Hey there 👋</h1> | ||
<p className="mb-16"> | ||
We're expanding Exercism to add content relevant to a wide range of | ||
abilities. To ensure Exercism shows you the right content, please tell | ||
us how experienced you are. | ||
</p> | ||
<h2>How experienced a developer are you?</h2> | ||
</header> | ||
<div className="flex flex-col flex-wrap gap-8 mb-16 text-18"> | ||
{SENIORITIES.map((seniority) => ( | ||
<button | ||
key={seniority.value} | ||
className={assembleClassNames( | ||
'btn-m btn-slightly-enhanced', | ||
selected === seniority.value | ||
? 'border-prominentLinkColor text-prominentLinkColor' | ||
: '' | ||
)} | ||
onClick={() => setSelected(seniority.value)} | ||
> | ||
{seniority.label} | ||
</button> | ||
))} | ||
</div> | ||
|
||
<FormButton | ||
status={setSeniorityMutationStatus} | ||
disabled={selected === ''} | ||
className="btn-primary btn-l w-100" | ||
type="button" | ||
onClick={handleSaveSeniorityLevel} | ||
> | ||
Save my choice | ||
</FormButton> | ||
<p className="!text-14 text-center mt-12"> | ||
(This can be updated at any time in your settings) | ||
</p> | ||
<ErrorBoundary | ||
FallbackComponent={ErrorFallback} | ||
resetKeys={[setSeniorityMutationStatus]} | ||
> | ||
<ErrorMessage | ||
error={setSeniorityMutationError} | ||
defaultError={DEFAULT_ERROR} | ||
/> | ||
</ErrorBoundary> | ||
<ErrorBoundary resetKeys={[patchCloseModal.status]}> | ||
<ErrorMessage | ||
error={patchCloseModal.status} | ||
defaultError={DEFAULT_ERROR} | ||
/> | ||
</ErrorBoundary> | ||
</div> | ||
) | ||
} |
Oops, something went wrong.