diff --git a/.github/labels.yml b/.github/labels.yml index ed4c3df056..3e67b22ab5 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -175,7 +175,7 @@ - name: "bot/bugsnag" description: "" color: "A32E92" - + - name: "bot/bugsnag-clientside" description: "" color: "A32E92" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index df8e36761c..d009601428 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -90,4 +90,4 @@ This policy was initially adopted from the Front-end London Slack community and A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md). _This policy is a "living" document, and subject to refinement and expansion in the future. -This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Slack, Twitter, email) and any other Exercism entity or event._ +This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Slack, Twitter, email) and any other Exercism entity or event._ diff --git a/app/css/ui-kit/filters.css b/app/css/ui-kit/filters.css index a1fb1453ce..6c0ae20aae 100644 --- a/app/css/ui-kit/filters.css +++ b/app/css/ui-kit/filters.css @@ -17,6 +17,11 @@ filter: var(--lightGold-filter); } + .filter-shadow-huge { + -webkit-filter: drop-shadow(0px 4px 128px rgba(79, 114, 205, 0.8)); + filter: drop-shadow(0px 4px 128px rgba(79, 114, 205, 0.8)); + } + .filter-orange { filter: var(--orange-filter); } @@ -112,8 +117,9 @@ .filter-prominentLinkColor { filter: var(--c-prominent-link-icon-filter); } - + .filter-shadow-huge { -webkit-filter: drop-shadow(0px 4px 128px rgba(79, 114, 205, 0.8)); filter: drop-shadow(0px 4px 128px rgba(79, 114, 205, 0.8)); - } \ No newline at end of file + } +} diff --git a/app/helpers/react_components/student/mentoring_session.rb b/app/helpers/react_components/student/mentoring_session.rb index 35f56ab3d5..145ed4bf31 100644 --- a/app/helpers/react_components/student/mentoring_session.rb +++ b/app/helpers/react_components/student/mentoring_session.rb @@ -6,42 +6,48 @@ class MentoringSession < ReactComponent def to_s super( "student-mentoring-session", - { - user_handle: student.handle, - request: SerializeMentorSessionRequest.(request, student), - discussion: discussion ? SerializeMentorDiscussionForStudent.(discussion) : nil, - track: SerializeMentorSessionTrack.(track), - exercise: SerializeMentorSessionExercise.(exercise), - iterations:, - mentor: mentor_data, - track_objectives: user_track&.objectives.to_s, - out_of_date: solution.out_of_date?, - videos:, - - links: { - exercise: Exercism::Routes.track_exercise_mentor_discussions_url(track, exercise), - create_mentor_request: Exercism::Routes.api_solution_mentor_requests_path(solution.uuid), - learn_more_about_private_mentoring: Exercism::Routes.doc_path(:using, "feedback/private"), - private_mentoring: solution.external_mentoring_request_url, - mentoring_guide: Exercism::Routes.doc_path(:using, "feedback/guide-to-being-mentored"), - donation_links: { - request: { - endpoint: Exercism::Routes.api_donations_active_subscription_url, - options: { - initial_data: AssembleActiveSubscription.(current_user) - } - }, - user_signed_in: user_signed_in?, - captcha_required: !current_user || current_user.captcha_required?, - recaptcha_site_key: ENV.fetch('RECAPTCHA_SITE_KEY', Exercism.secrets.recaptcha_site_key), - links: { - settings: Exercism::Routes.donations_settings_url, - donate: Exercism::Routes.donate_url + to_h + ) + end + + def to_h + { + user_handle: student.handle, + request: SerializeMentorSessionRequest.(request, student), + discussion: discussion ? SerializeMentorDiscussionForStudent.(discussion) : nil, + track: SerializeMentorSessionTrack.(track), + exercise: SerializeMentorSessionExercise.(exercise), + iterations:, + mentor: mentor_data, + track_objectives: user_track&.objectives.to_s, + out_of_date: solution.out_of_date?, + videos:, + + links: { + exercise: Exercism::Routes.track_exercise_mentor_discussions_url(track, exercise), + create_mentor_request: Exercism::Routes.api_solution_mentor_requests_path(solution.uuid), + learn_more_about_private_mentoring: Exercism::Routes.doc_path(:using, "feedback/private"), + private_mentoring: solution.external_mentoring_request_url, + mentoring_guide: Exercism::Routes.doc_path(:using, "feedback/guide-to-being-mentored"), + donation_links: { + show_donation_modal:, + request: { + endpoint: Exercism::Routes.current_api_payments_subscriptions_url, + options: { + initial_data: AssembleCurrentSubscription.(current_user) } + }, + # TODO: add correct endpoint and initial data + user_signed_in: user_signed_in?, + captcha_required: !current_user || current_user.captcha_required?, + recaptcha_site_key: ENV.fetch('RECAPTCHA_SITE_KEY', Exercism.secrets.recaptcha_site_key), + links: { + settings: Exercism::Routes.donations_settings_url, + donate: Exercism::Routes.donate_url } } } - ) + } end private @@ -111,6 +117,11 @@ def iterations SerializeIterations.(solution.iterations, comment_counts:) end + + def show_donation_modal + num_testimonials = current_user.provided_testimonials.count + num_testimonials.zero? || ((num_testimonials + 1) % 5).zero? + end end end end diff --git a/app/javascript/components/mentoring/discussion/FinishButton.tsx b/app/javascript/components/mentoring/discussion/FinishButton.tsx index a1469cc9e6..a64d2f1531 100644 --- a/app/javascript/components/mentoring/discussion/FinishButton.tsx +++ b/app/javascript/components/mentoring/discussion/FinishButton.tsx @@ -1,10 +1,10 @@ import React, { useState, useCallback } from 'react' -import { FinishMentorDiscussionModal } from '../../modals/mentor/FinishMentorDiscussionModal' -import { ModalProps } from '../../modals/Modal' -import { MentorDiscussion as Discussion } from '../../types' import { useMutation } from 'react-query' -import { sendRequest } from '../../../utils/send-request' -import { typecheck } from '../../../utils/typecheck' +import { sendRequest } from '@/utils/send-request' +import { typecheck } from '@/utils/typecheck' +import { FinishMentorDiscussionModal } from '@/components/modals/mentor/FinishMentorDiscussionModal' +import { ModalProps } from '@/components/modals/Modal' +import type { MentorDiscussion as Discussion } from '@/components/types' export const FinishButton = ({ endpoint, diff --git a/app/javascript/components/modals/student/FinishMentorDiscussionModal.tsx b/app/javascript/components/modals/student/FinishMentorDiscussionModal.tsx index 09fad7348a..6151529550 100644 --- a/app/javascript/components/modals/student/FinishMentorDiscussionModal.tsx +++ b/app/javascript/components/modals/student/FinishMentorDiscussionModal.tsx @@ -1,22 +1,10 @@ import React, { useState } from 'react' -import { MentorDiscussion } from '../../types' -import { Modal, ModalProps } from '../Modal' -import { RateMentorStep } from './finish-mentor-discussion-modal/RateMentorStep' -import { AddTestimonialStep } from './finish-mentor-discussion-modal/AddTestimonialStep' -import { CelebrationStep } from './finish-mentor-discussion-modal/CelebrationStep' -import { UnhappyStep } from './finish-mentor-discussion-modal/UnhappyStep' -import { RequeuedStep } from './finish-mentor-discussion-modal/RequeuedStep' -import { SatisfiedStep } from './finish-mentor-discussion-modal/SatisfiedStep' -import { ReportStep } from './finish-mentor-discussion-modal/ReportStep' import { useMachine } from '@xstate/react' import { createMachine } from 'xstate' -import { redirectTo } from '../../../utils/redirect-to' -import { DonationStep } from './finish-mentor-discussion-modal/DonationStep' -import { DiscussionLinks } from '../../student/mentoring-session/DiscussionActions' - -export type Links = { - exercise: string -} +import { redirectTo } from '@/utils/redirect-to' +import { MentorDiscussion, DiscussionLinks } from '@/components/types' +import { Modal, ModalProps } from '../Modal' +import * as Step from './finish-mentor-discussion-modal' export type ReportReason = 'coc' | 'incorrect' | 'other' @@ -65,11 +53,7 @@ const Inner = ({ switch (currentStep.value) { case 'rateMentor': return ( - // - send('HAPPY')} onSatisfied={() => send('SATISFIED')} @@ -78,7 +62,7 @@ const Inner = ({ ) case 'addTestimonial': return ( - send('SUBMIT')} onSkip={() => redirectTo(links.exercise)} onBack={() => send('BACK')} @@ -86,19 +70,24 @@ const Inner = ({ /> ) case 'celebration': - return ( - // - - ) + if (links.donationLinks.showDonationModal) { + return ( + + ) + } else + return ( + + ) case 'satisfied': return ( - send('REQUEUED')} onBack={() => send('BACK')} @@ -108,10 +97,10 @@ const Inner = ({ /> ) case 'requeued': - return + return case 'report': return ( - { setReport(report) @@ -125,7 +114,7 @@ const Inner = ({ throw new Error('Report should not be null') } - return + return } default: throw new Error('Unknown modal step') diff --git a/app/javascript/components/modals/student/finish-mentor-discussion-modal/AddTestimonialStep.tsx b/app/javascript/components/modals/student/finish-mentor-discussion-modal/AddTestimonialStep.tsx index af6e122442..93d4e2e139 100644 --- a/app/javascript/components/modals/student/finish-mentor-discussion-modal/AddTestimonialStep.tsx +++ b/app/javascript/components/modals/student/finish-mentor-discussion-modal/AddTestimonialStep.tsx @@ -79,12 +79,6 @@ export const AddTestimonialStep = ({ value={value} onChange={handleChange} /> - {/*

- Testimonials are a place to write what impressed you about a - mentor. -
- Mentors can choose to display them on their profiles. -

*/}

Thank you for leaving a testimonial đź’™

- You’ve helped make {mentorHandle}'s day. + You've helped make {mentorHandle}'s day. Please be sure to share your experience of Exercism with others.

diff --git a/app/javascript/components/modals/student/finish-mentor-discussion-modal/DonationStep.tsx b/app/javascript/components/modals/student/finish-mentor-discussion-modal/DonationStep.tsx index cb6822162e..74e2e15b5c 100644 --- a/app/javascript/components/modals/student/finish-mentor-discussion-modal/DonationStep.tsx +++ b/app/javascript/components/modals/student/finish-mentor-discussion-modal/DonationStep.tsx @@ -1,15 +1,17 @@ import React, { lazy, Suspense } from 'react' -import { GraphicalIcon } from '@/components/common' import { DonationLinks } from '@/components/types' const DonationsFormWithModal = lazy( () => import('@/components/donations/FormWithModal') ) + export function DonationStep({ mentorHandle, donationLinks, + exerciseLink, }: { mentorHandle: string donationLinks: DonationLinks + exerciseLink: string }): JSX.Element { return (
- -
}> ) : null} @@ -136,7 +137,7 @@ export default function MentoringSession({ userHandle={userHandle} iterations={iterations} onIterationScroll={handleIterationScroll} - links={{ exercise: exercise.links.self }} + links={links} status={status} /> ) : ( diff --git a/app/javascript/components/student/Nudge.tsx b/app/javascript/components/student/Nudge.tsx index 902d0a2013..3d55130cf0 100644 --- a/app/javascript/components/student/Nudge.tsx +++ b/app/javascript/components/student/Nudge.tsx @@ -207,7 +207,7 @@ const CompleteExerciseNudge = ({
-

Nice, it looks like you’re done here!

+

Nice, it looks like you're done here!

Complete the exercise to unlock new concepts and exercises.{' '} diff --git a/app/javascript/components/student/mentoring-session/DiscussionActions.tsx b/app/javascript/components/student/mentoring-session/DiscussionActions.tsx index 8eaa0bad19..6ff2c69a73 100644 --- a/app/javascript/components/student/mentoring-session/DiscussionActions.tsx +++ b/app/javascript/components/student/mentoring-session/DiscussionActions.tsx @@ -1,12 +1,7 @@ import React from 'react' +import { DiscussionLinks, MentorDiscussion } from '@/components/types' import { FinishButton } from './FinishButton' -import { DonationLinks, MentorDiscussion } from '../../types' -import { GraphicalIcon } from '../../common' - -export type DiscussionLinks = { - exercise: string - donation: DonationLinks -} +import GraphicalIcon from '@/components/common/GraphicalIcon' export const DiscussionActions = ({ discussion, diff --git a/app/javascript/components/student/mentoring-session/DiscussionInfo.tsx b/app/javascript/components/student/mentoring-session/DiscussionInfo.tsx index 3c539ffd11..57fe6f75e6 100644 --- a/app/javascript/components/student/mentoring-session/DiscussionInfo.tsx +++ b/app/javascript/components/student/mentoring-session/DiscussionInfo.tsx @@ -9,10 +9,7 @@ import { Mentor } from '../MentoringSession' import { GraphicalIcon } from '../../common' import { FinishButton } from './FinishButton' import { QueryStatus } from 'react-query' - -type Links = { - exercise: string -} +import { DiscussionLinks } from './DiscussionActions' export const DiscussionInfo = ({ discussion, @@ -28,7 +25,7 @@ export const DiscussionInfo = ({ userHandle: string iterations: readonly Iteration[] onIterationScroll: (iteration: Iteration) => void - links: Links + links: DiscussionLinks status: QueryStatus }): JSX.Element => { return ( diff --git a/app/javascript/components/student/mentoring-session/FinishButton.tsx b/app/javascript/components/student/mentoring-session/FinishButton.tsx index 71094b11d3..51bf47470d 100644 --- a/app/javascript/components/student/mentoring-session/FinishButton.tsx +++ b/app/javascript/components/student/mentoring-session/FinishButton.tsx @@ -1,8 +1,7 @@ import React, { useState } from 'react' import { FinishMentorDiscussionModal } from '../../modals/student/FinishMentorDiscussionModal' import { ConfirmFinishMentorDiscussionModal } from '../../modals/student/ConfirmFinishMentorDiscussionModal' -import { MentorDiscussion } from '../../types' -import { DiscussionLinks } from './DiscussionActions' +import { DiscussionLinks, MentorDiscussion } from '../../types' type Status = 'initialized' | 'confirming' | 'finishing' diff --git a/app/javascript/components/student/mentoring-session/MentoringRequest.tsx b/app/javascript/components/student/mentoring-session/MentoringRequest.tsx index fabda79c36..cc23ab552b 100644 --- a/app/javascript/components/student/mentoring-session/MentoringRequest.tsx +++ b/app/javascript/components/student/mentoring-session/MentoringRequest.tsx @@ -6,16 +6,10 @@ import { MentorSessionRequest as Request, MentorSessionTrack as Track, MentorSessionExercise as Exercise, -} from '../../types' + DiscussionLinks, +} from '@/components/types' import { Video } from '../MentoringSession' -type Links = { - learnMoreAboutPrivateMentoring: string - privateMentoring: string - mentoringGuide: string - createMentorRequest: string -} - export const MentoringRequest = ({ trackObjectives, track, @@ -32,7 +26,7 @@ export const MentoringRequest = ({ request?: Request latestIteration: Iteration videos: Video[] - links: Links + links: DiscussionLinks onCreate: (mentorRequest: Request) => void }): JSX.Element => { return request ? ( diff --git a/app/javascript/components/student/mentoring-session/mentoring-request/MentoringRequestForm.tsx b/app/javascript/components/student/mentoring-session/mentoring-request/MentoringRequestForm.tsx index c62de8467e..7fc0330173 100644 --- a/app/javascript/components/student/mentoring-session/mentoring-request/MentoringRequestForm.tsx +++ b/app/javascript/components/student/mentoring-session/mentoring-request/MentoringRequestForm.tsx @@ -4,14 +4,12 @@ import { MedianWaitTime } from '@/components/common/MedianWaitTime' import CopyToClipboardButton from '@/components/common/CopyToClipboardButton' import { FormButton } from '@/components/common/FormButton' import { FetchingBoundary } from '@/components/FetchingBoundary' -import { - type Links, - useMentoringRequest, -} from './MentoringRequestFormComponents' +import { useMentoringRequest } from './MentoringRequestFormComponents' import type { MentorSessionTrack as Track, MentorSessionExercise as Exercise, MentorSessionRequest as Request, + DiscussionLinks, } from '@/components/types' import { TrackObjectivesTextArea, @@ -30,7 +28,7 @@ export const MentoringRequestForm = ({ trackObjectives: string track: Track exercise: Exercise - links: Links + links: DiscussionLinks onSuccess: (mentorRequest: Request) => void }): JSX.Element => { const { diff --git a/app/javascript/components/types.ts b/app/javascript/components/types.ts index 1d83114d42..f879aef6c1 100644 --- a/app/javascript/components/types.ts +++ b/app/javascript/components/types.ts @@ -445,6 +445,19 @@ export type MentorDiscussion = { } } +export type DiscussionLinks = { + exercise: string + donationLinks: DonationLinks +} & MentoringRequestLinks + +export type MentoredTrackExercise = { + slug: string + title: string + iconUrl: string + count: number + completedByMentor: boolean +} + export type DonationLinks = { request: { endpoint: string @@ -452,6 +465,7 @@ export type DonationLinks = { initialData: string } } + showDonationModal: boolean userSignedIn: boolean captchaRequired: boolean recaptchaSiteKey: string @@ -460,12 +474,12 @@ export type DonationLinks = { donate: string } } -export type MentoredTrackExercise = { - slug: string - title: string - iconUrl: string - count: number - completedByMentor: boolean + +export type MentoringRequestLinks = { + learnMoreAboutPrivateMentoring: string + privateMentoring: string + mentoringGuide: string + createMentorRequest: string } export type MentoredTrack = { diff --git a/test/helpers/react_components/student/mentoring_session_test.rb b/test/helpers/react_components/student/mentoring_session_test.rb index c5113db921..7d1e3b5717 100644 --- a/test/helpers/react_components/student/mentoring_session_test.rb +++ b/test/helpers/react_components/student/mentoring_session_test.rb @@ -59,10 +59,11 @@ class ReactComponents::Student::MentoringSessionTest < ReactComponentTestCase private_mentoring: solution.external_mentoring_request_url, mentoring_guide: Exercism::Routes.doc_path(:using, "feedback/guide-to-being-mentored"), donation_links: { + show_donation_modal: true, request: { - endpoint: Exercism::Routes.api_donations_active_subscription_url, + endpoint: Exercism::Routes.current_api_payments_subscriptions_url, options: { - initial_data: AssembleActiveSubscription.(student) + initial_data: AssembleCurrentSubscription.(student) } }, user_signed_in: true, @@ -87,7 +88,7 @@ class ReactComponents::Student::MentoringSessionTest < ReactComponentTestCase comment_markdown: "Hello", updated_at: Time.utc(2016, 12, 25) - iteration = create :iteration, solution: solution + iteration = create(:iteration, solution:) component = ReactComponents::Student::MentoringSession.new(solution, mentor_request, nil) component.stubs(current_user: student) @@ -122,10 +123,11 @@ class ReactComponents::Student::MentoringSessionTest < ReactComponentTestCase private_mentoring: solution.external_mentoring_request_url, mentoring_guide: Exercism::Routes.doc_path(:using, "feedback/guide-to-being-mentored"), donation_links: { + show_donation_modal: true, request: { - endpoint: Exercism::Routes.api_donations_active_subscription_url, + endpoint: Exercism::Routes.current_api_payments_subscriptions_url, options: { - initial_data: AssembleActiveSubscription.(student) + initial_data: AssembleCurrentSubscription.(student) } }, user_signed_in: true, @@ -139,4 +141,41 @@ class ReactComponents::Student::MentoringSessionTest < ReactComponentTestCase } } end + + test "sets show_donation_modal correctly" do + student = create :user + solution = create :concept_solution, user: student + create(:iteration, solution:) + mentor_request = create(:mentor_request, solution:) + + generate_data = proc do + component = ReactComponents::Student::MentoringSession.new(solution, mentor_request, nil) + component.stubs(current_user: student) + component.stubs(user_signed_in?: true) + component.to_h + end + + # No testimonials shows model + assert generate_data.().dig(:links, :donation_links, :show_donation_modal) + + # 1/2/3 testimonials doesn't + 3.times do + create(:mentor_testimonial, student:) + refute generate_data.().dig(:links, :donation_links, :show_donation_modal) + end + + # 4 testimonials does + create(:mentor_testimonial, student:) + assert generate_data.().dig(:links, :donation_links, :show_donation_modal) + + # 5/6/7/8 testimonials doesn't + 4.times do + create(:mentor_testimonial, student:) + refute generate_data.().dig(:links, :donation_links, :show_donation_modal) + end + + # 9 testimonials does + create(:mentor_testimonial, student:) + assert generate_data.().dig(:links, :donation_links, :show_donation_modal) + end end diff --git a/test/system/flows/student/finish_mentor_discussion/happy_test.rb b/test/system/flows/student/finish_mentor_discussion/happy_test.rb index 2f29a51fdb..5ba0755346 100644 --- a/test/system/flows/student/finish_mentor_discussion/happy_test.rb +++ b/test/system/flows/student/finish_mentor_discussion/happy_test.rb @@ -30,9 +30,9 @@ class HappyTest < ApplicationSystemTestCase click_on "It was good!" fill_in "Leave #{discussion.mentor.handle} a testimonial (optional)", with: "Good mentor!" click_on "Finish" - click_on "Back to the exercise" + click_on "Continue to exercise" - assert_text "Nice, it looks like you’re done here!" + assert_text "Nice, it looks like you're done here!" end end @@ -58,7 +58,7 @@ class HappyTest < ApplicationSystemTestCase click_on "Skip" wait_for_redirect - assert_text "Nice, it looks like you’re done here!" + assert_text "Nice, it looks like you're done here!" end end end diff --git a/test/system/flows/student/finish_mentor_discussion/satisfied_test.rb b/test/system/flows/student/finish_mentor_discussion/satisfied_test.rb index 9af3bcdef3..9e555d724b 100644 --- a/test/system/flows/student/finish_mentor_discussion/satisfied_test.rb +++ b/test/system/flows/student/finish_mentor_discussion/satisfied_test.rb @@ -31,7 +31,7 @@ class SatisfiedTest < ApplicationSystemTestCase click_on "No thanks" wait_for_redirect - assert_text "Nice, it looks like you’re done here!" + assert_text "Nice, it looks like you're done here!" end end diff --git a/test/system/flows/student/mentor_finished_discussion_test.rb b/test/system/flows/student/mentor_finished_discussion_test.rb index c436a2fbc9..4d6005c33e 100644 --- a/test/system/flows/student/mentor_finished_discussion_test.rb +++ b/test/system/flows/student/mentor_finished_discussion_test.rb @@ -48,9 +48,9 @@ class MentorFinishedDiscussionTest < ApplicationSystemTestCase click_on "It was good!" fill_in "Leave #{discussion.mentor.handle} a testimonial (optional)", with: "Good mentor!" click_on "Finish" - click_on "Back to the exercise" + click_on "Continue to exercise" - assert_text "Nice, it looks like you’re done here!" + assert_text "Want to try another mentor?" end end end