From 85f28919501f7d86f102f52aa94df738ec4ee990 Mon Sep 17 00:00:00 2001 From: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Date: Thu, 21 Sep 2023 14:06:23 +0200 Subject: [PATCH] Add Donation modal after testimonial step (#2972) * Add donation modal * Make further styling on Donation step modal, make it accessible, wider, lazyload donation form * Replace button classes, add inline drop-shadow * Fix Graphical icon types * Add links to student mentoring session * Wire in donatins links * Refreshing tests - WIP * Minor changes * Fix ruby test * Fix other ruby test * Populate captcha information * Remove unused code * Fix tests * Add correct links and boolean (#6202) * Reorganise MentoringSession's donation data (#6206) * Reorganise MentoringSession's donation data * Update mentoring_session_test * Pass down camelized data * Pass donation data down to sidepanel modal initiator * Improve layout and copy * Clean up DonationStep.tsx * Add logic around when to show modal * Prevent the modal from closing when the overlay is clicked * Further donation modal tweaks (#6214) * Change button text, remove static booleans * Change default values * Update celebration text * Update celebration step * Update urls * Use confirmParamsReturnUrl everywhere * Add successfulDonationStep * Update xstate * Change width of SuccessfulDonationStep's container modal * Change nyan cat height * Change continue text * Apply suggestions from code review * Update test * Remove recaptcha sitekey * Change text in test * Fix more tests * Update app/models/user.rb Co-authored-by: Erik Schierboom * Update initialData types, Remove unnecessary else * Remove wizard-hat * Remove unused filter, undo negating boolean * Update user_test * Update app/helpers/react_components/student/mentoring_session.rb Co-authored-by: Erik Schierboom * Update .github/labels.yml * Update CODE_OF_CONDUCT.md * Sync yarn.lock and package.json * Update link name, simplify link types, unify same types --------- Co-authored-by: Erik Schierboom Co-authored-by: Jeremy Walker --- .../finish-student-mentor-discussion.css | 21 +++- .../react_components/donations/footer_form.rb | 2 +- .../donations/form_with_modal.rb | 2 +- .../student/mentoring_session.rb | 59 +++++++--- app/helpers/view_components/site_footer.rb | 2 +- .../components/donations/FooterForm.tsx | 5 +- app/javascript/components/donations/Form.tsx | 18 ++- .../components/donations/FormWithModal.tsx | 14 +-- .../components/donations/StripeForm.tsx | 6 +- .../donations/footer-form/FormModal.tsx | 6 +- .../mentoring/discussion/FinishButton.tsx | 13 +-- .../student/FinishMentorDiscussionModal.tsx | 93 ++++++++++----- .../AddTestimonialStep.tsx | 6 - .../CelebrationStep.tsx | 21 ++-- .../DonationStep.tsx | 96 ++++++++++++++++ .../RateMentorStep.tsx | 6 +- .../SuccessfulDonationStep.tsx | 52 +++++++++ .../finish-mentor-discussion-modal/index.ts | 9 ++ .../components/student/MentoringSession.tsx | 31 ++++- app/javascript/components/student/Nudge.tsx | 2 +- .../mentoring-session/DiscussionActions.tsx | 25 ++-- .../mentoring-session/DiscussionInfo.tsx | 16 ++- .../mentoring-session/FinishButton.tsx | 15 ++- .../mentoring-session/MentoringRequest.tsx | 12 +- .../MentoringRequestForm.tsx | 8 +- app/javascript/components/types.ts | 26 +++++ app/javascript/packs/internal.tsx | 5 +- app/models/user.rb | 5 + package.json | 4 +- .../student/mentoring_session_test.rb | 107 +++++++++++++++++- test/models/user_test.rb | 15 +++ .../finish_mentor_discussion/happy_test.rb | 6 +- .../satisfied_test.rb | 2 +- .../mentor_finished_discussion_test.rb | 4 +- yarn.lock | 38 +++---- 35 files changed, 575 insertions(+), 177 deletions(-) create mode 100644 app/javascript/components/modals/student/finish-mentor-discussion-modal/DonationStep.tsx create mode 100644 app/javascript/components/modals/student/finish-mentor-discussion-modal/SuccessfulDonationStep.tsx create mode 100644 app/javascript/components/modals/student/finish-mentor-discussion-modal/index.ts diff --git a/app/css/modals/finish-student-mentor-discussion.css b/app/css/modals/finish-student-mentor-discussion.css index d150b56252..36699d43ed 100644 --- a/app/css/modals/finish-student-mentor-discussion.css +++ b/app/css/modals/finish-student-mentor-discussion.css @@ -209,17 +209,28 @@ @apply mt-24; } } + .successful-donation-step { + .badge-container { + @apply flex items-center rounded-5 shadow-xsZ1 py-6 px-24 mb-32 border-1 border-lightGold; + background: var(--backgroundColorCAlert); + } + .c-badge-medallion { + height: 40px; + width: 40px; + @apply mr-8; + } + } & section.celebrate-step { - @apply p-64; + @apply p-64 rounded-8; margin: -40px -48px; @apply flex flex-col items-center justify-start; &.neon-cat { - background: rgb(15, 68, 113); + background: rgb(17, 49, 97); & .gif { - height: 160px; + height: 80px; @apply mb-24; } & h2 { @@ -227,9 +238,9 @@ } & p { @apply text-20 leading-150 text-white text-center; - @apply mb-48; + @apply mb-20; & strong { - @apply block mb-8; + @apply block mb-4; @apply font-semibold; } } diff --git a/app/helpers/react_components/donations/footer_form.rb b/app/helpers/react_components/donations/footer_form.rb index 0c79267b3b..e7f8fcea30 100644 --- a/app/helpers/react_components/donations/footer_form.rb +++ b/app/helpers/react_components/donations/footer_form.rb @@ -17,7 +17,7 @@ def to_s 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 + success: Exercism::Routes.donate_url } } ) diff --git a/app/helpers/react_components/donations/form_with_modal.rb b/app/helpers/react_components/donations/form_with_modal.rb index 86ca938d68..68b6147730 100644 --- a/app/helpers/react_components/donations/form_with_modal.rb +++ b/app/helpers/react_components/donations/form_with_modal.rb @@ -16,7 +16,7 @@ def to_s 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 + success: Exercism::Routes.donate_url } } ) diff --git a/app/helpers/react_components/student/mentoring_session.rb b/app/helpers/react_components/student/mentoring_session.rb index c8b199d347..2022d4c680 100644 --- a/app/helpers/react_components/student/mentoring_session.rb +++ b/app/helpers/react_components/student/mentoring_session.rb @@ -6,26 +6,41 @@ 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") + 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:, + donation: { + show_donation_modal:, + request: { + endpoint: Exercism::Routes.current_api_payments_subscriptions_url, + options: { + initial_data: AssembleCurrentSubscription.(current_user) + } } + }, + 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"), + donations_settings: Exercism::Routes.donations_settings_url, + donate: Exercism::Routes.donate_url } - ) + } end private @@ -95,6 +110,14 @@ def iterations SerializeIterations.(solution.iterations, comment_counts:) end + + def show_donation_modal + return false if current_user.insider? + return false if current_user.donated_in_last_35_days? + + num_testimonials = current_user.provided_testimonials.count + (num_testimonials % 3).zero? + end end end end diff --git a/app/helpers/view_components/site_footer.rb b/app/helpers/view_components/site_footer.rb index 196fa3856c..47931c387d 100644 --- a/app/helpers/view_components/site_footer.rb +++ b/app/helpers/view_components/site_footer.rb @@ -19,7 +19,7 @@ def cache_key parts << ::Track.active.count parts << user_part parts << stripe_version - parts << "v1" + parts << "v2" parts.join(':') end diff --git a/app/javascript/components/donations/FooterForm.tsx b/app/javascript/components/donations/FooterForm.tsx index b5006d70b9..7f677978cd 100644 --- a/app/javascript/components/donations/FooterForm.tsx +++ b/app/javascript/components/donations/FooterForm.tsx @@ -5,15 +5,14 @@ import { CustomAmountInput } from './donation-form/CustomAmountInput' import { FormModal } from './footer-form/FormModal' import { GraphicalIcon } from '../common' import { Request } from '../../hooks/request-query' - -type Links = Record<'settings' | 'donate', string> +import { StripeFormLinks } from './Form' const PRESET_AMOUNTS = [currency(16), currency(32), currency(64), currency(128)] const DEFAULT_AMOUNT = currency(16) export type FooterFormProps = { request: Request - links: Links + links: StripeFormLinks userSignedIn: boolean captchaRequired: boolean recaptchaSiteKey: string diff --git a/app/javascript/components/donations/Form.tsx b/app/javascript/components/donations/Form.tsx index 16ae999f00..16a26ca3ff 100644 --- a/app/javascript/components/donations/Form.tsx +++ b/app/javascript/components/donations/Form.tsx @@ -10,14 +10,18 @@ import currency from 'currency.js' import { Request, useRequestQuery } from '../../hooks/request-query' import { FetchingBoundary } from '../FetchingBoundary' import { useQueryCache } from 'react-query' -import { FormWithModalLinks } from './FormWithModal' + +export type StripeFormLinks = { + success: string + settings: string +} const TabsContext = createContext({ current: 'subscription', switchToTab: () => null, }) -type FormAmount = { +export type FormAmount = { subscription: currency payment: currency } @@ -38,10 +42,10 @@ type Props = { onSuccess: (type: PaymentIntentType, amount: currency) => void userSignedIn: boolean captchaRequired: boolean - recaptchaSiteKey: string + recaptchaSiteKey?: string onProcessing?: () => void onSettled?: () => void - links: FormWithModalLinks + links: StripeFormLinks id?: string } @@ -185,7 +189,7 @@ export const Form = ({ } > ) } + +export default Form diff --git a/app/javascript/components/donations/FormWithModal.tsx b/app/javascript/components/donations/FormWithModal.tsx index 83167feaf6..46ffcde4d4 100644 --- a/app/javascript/components/donations/FormWithModal.tsx +++ b/app/javascript/components/donations/FormWithModal.tsx @@ -1,21 +1,17 @@ import React, { useState, useCallback } from 'react' import currency from 'currency.js' import { Request } from '@/hooks/request-query' -import { Form } from './Form' +import { Form, FormAmount, StripeFormLinks } from './Form' import SuccessModal from './SuccessModal' import { PaymentIntentType } from './stripe-form/useStripeForm' -export type FormWithModalLinks = { - donate: string - settings: string -} - type FormWithModalProps = { request: Request userSignedIn: boolean - links: FormWithModalLinks + links: StripeFormLinks captchaRequired: boolean recaptchaSiteKey: string + defaultAmount?: Partial } export default function FormWithModal({ @@ -24,6 +20,7 @@ export default function FormWithModal({ links, captchaRequired, recaptchaSiteKey, + defaultAmount, }: FormWithModalProps): JSX.Element { const [paymentMade, setPaymentMade] = useState(false) @@ -49,11 +46,12 @@ export default function FormWithModal({ links={links} recaptchaSiteKey={recaptchaSiteKey} captchaRequired={captchaRequired} + defaultAmount={defaultAmount} /> ) diff --git a/app/javascript/components/donations/StripeForm.tsx b/app/javascript/components/donations/StripeForm.tsx index e31f1ab9ce..1f9c29cb7c 100644 --- a/app/javascript/components/donations/StripeForm.tsx +++ b/app/javascript/components/donations/StripeForm.tsx @@ -13,7 +13,7 @@ export type StripeFormProps = { onSettled?: () => void userSignedIn: boolean captchaRequired: boolean - recaptchaSiteKey: string + recaptchaSiteKey?: string amount: currency submitButtonDisabled?: boolean confirmParamsReturnUrl: string @@ -75,7 +75,7 @@ export function StripeForm({ /> ) : null} - {captchaRequired ? ( + {captchaRequired && recaptchaSiteKey ? (