exercism/website

View on GitHub
app/javascript/packs/internal.tsx

Summary

Maintainability
F
1 wk
Test Coverage
/* eslint-disable @typescript-eslint/no-explicit-any */
// Absolute, module imports
import React, { Suspense, lazy } from 'react'
import { camelizeKeys } from 'humps'
import currency from 'currency.js'
import { initReact } from '@/utils/react-bootloader'
import { camelizeKeysAs } from '@/utils/camelize-keys-as'
import 'easymde/dist/easymde.min.css'

// Type imports
import type {
  Iteration,
  MentorSessionRequest,
  MentorSessionTrack,
  MentorSessionExercise,
  MentorDiscussion,
  MentoredTrack,
  SolutionForStudent,
  CommunitySolution,
  MentoredTrackExercise,
  CommunicationPreferences,
  User,
  MentoringSessionExemplarFile,
  SharePlatform,
  CompleteRepresentationData,
  Guidance,
  MentoringSessionDonation,
  Track,
} from '@/components/types'
import type { Links as TryMentoringButtonLinks } from '@/components/mentoring/TryMentoringButton'
import type { Links as MentoringQueueLinks } from '@/components/mentoring/Queue'

import type { Student as MentoringSessionStudent } from '@/components/types'
import type {
  Links as MentoringSessionLinks,
  Scratchpad as MentoringSessionScratchpad,
} from '@/components/mentoring/Session'
import type {
  Mentor as StudentMentoringSessionMentor,
  Video as StudentMentoringSessionVideo,
  Links as StudentMentoringSessionLinks,
} from '@/components/student/MentoringSession'
import type { Links as RequestMentoringButtonLinks } from '@/components/student/RequestMentoringButton'
import type { Track as MentoringTestimonialsListTrack } from '@/components/mentoring/TestimonialsList'
import type { Category as JourneyPageCategory } from '@/components/journey/JourneyPage'
import type { Links as TrackMenuLinks } from '@/components/dropdowns/TrackMenu'
import type {
  Track as IterationsListTrack,
  Exercise as IterationsListExercise,
  Links as IterationsListLinks,
  IterationsListRequest,
} from '@/components/student/IterationsList'
import type { Links as NotificationsListLinks } from '@/components/notifications/NotificationsList'
import type { Request } from '@/hooks/request-query'
import type { Request as MentoringInboxRequest } from '@/components/mentoring/Inbox'
import type { AutomationProps } from '@/components/mentoring/automation/Representation'
import type { ThemePreferenceLinks } from '@/components/settings/ThemePreferenceForm'

import type { Props as EditorProps } from '@/components/editor/Props'
import type {
  CommentsPreferenceFormProps,
  UserPreferences,
} from '@/components/settings'
import type { FooterFormProps } from '@/components/donations/FooterForm'
import type { StripeFormLinks } from '@/components/donations/Form'
import type { ChangePublishedIterationModalButtonProps } from '@/components/student/published-solution/ChangePublishedIterationModalButton'
import type { UnpublishSolutionModalButtonProps } from '@/components/student/published-solution/UnpublishSolutionModalButton'

// Component imports
const Editor = lazy(() => import('@/components/Editor'))
const SubmissionsSummaryTable = lazy(() => import('@/components/maintaining'))
const Inbox = lazy(() => import('@/components/mentoring/Inbox'))
const Queue = lazy(() => import('@/components/mentoring/Queue'))
const Session = lazy(() => import('@/components/mentoring/Session'))
const Representation = lazy(
  () => import('@/components/mentoring/Representation')
)
const TestimonialsList = lazy(
  () => import('@/components/mentoring/TestimonialsList')
)
const TryMentoringButton = lazy(
  () => import('@/components/mentoring/TryMentoringButton')
)
const RepresentationsWithFeedback = lazy(
  () => import('@/components/mentoring/automation/RepresentationsWithFeedback')
)
const RepresentationsWithoutFeedback = lazy(
  () =>
    import('@/components/mentoring/automation/RepresentationsWithoutFeedback')
)
const RepresentationsAdmin = lazy(
  () => import('@/components/mentoring/automation/RepresentationsAdmin')
)

const PublishSolutionButton = lazy(
  () => import('@/components/student/PublishSolutionButton')
)
const RequestMentoringButton = lazy(
  () => import('@/components/student/RequestMentoringButton')
)

const UpdateExerciseNotice = lazy(
  () => import('@/components/student/UpdateExerciseNotice')
)

const StudentIterationsList = lazy(
  () => import('@/components/student/IterationsList')
)

const StudentMentoringSession = lazy(
  () => import('@/components/student/MentoringSession')
)

const JourneyPage = lazy(() => import('@/components/journey/JourneyPage'))

const ProfileForm = lazy(() => import('@/components/settings/ProfileForm'))
const PhotoForm = lazy(() => import('@/components/settings/PhotoForm'))
const DeleteProfileForm = lazy(
  () => import('@/components/settings/DeleteProfileForm')
)
const PronounsForm = lazy(() => import('@/components/settings/PronounsForm'))
const HandleForm = lazy(() => import('@/components/settings/HandleForm'))
const EmailForm = lazy(() => import('@/components/settings/EmailForm'))
const PasswordForm = lazy(() => import('@/components/settings/PasswordForm'))
const UserPreferencesForm = lazy(
  () => import('@/components/settings/UserPreferencesForm')
)
const TokenForm = lazy(() => import('@/components/settings/TokenForm'))
const ThemePreferenceForm = lazy(
  () => import('@/components/settings/ThemePreferenceForm')
)
const CommentsPreferenceForm = lazy(
  () =>
    import(
      '@/components/settings/comments-preference-form/CommentsPreferenceForm'
    )
)

const CommunicationPreferencesForm = lazy(
  () => import('@/components/settings/CommunicationPreferencesForm')
)

const DeleteAccountButton = lazy(
  () => import('@/components/settings/DeleteAccountButton')
)
const ResetAccountButton = lazy(
  () => import('@/components/settings/ResetAccountButton')
)

const ShowOnSupportersPageButton = lazy(
  () => import('@/components/settings/ShowOnSupportersPageButton')
)

const MarkdownEditor = lazy(() => import('@/components/common/MarkdownEditor'))

const NotificationsDropdown = lazy(
  () => import('@/components/dropdowns/Notifications')
)

const ReputationDropdown = lazy(
  () => import('@/components/dropdowns/Reputation')
)

const TrackMenuDropdown = lazy(() => import('@/components/dropdowns/TrackMenu'))

const IterationSummaryWithWebsockets = lazy(
  () => import('@/components/track/IterationSummary')
)

const NotificationsList = lazy(
  () => import('@/components/notifications/NotificationsList')
)
const WelcomeModal = lazy(() => import('@/components/modals/WelcomeModal'))
const WelcomeToInsidersModal = lazy(
  () => import('@/components/modals/WelcomeToInsidersModal')
)
const DonationsFooterForm = lazy(
  () => import('@/components/donations/FooterForm')
)
const DonationsFormWithModal = lazy(
  () => import('@/components/donations/FormWithModal')
)

const DonationsSubscriptionForm = lazy(
  () => import('@/components/donations/SubscriptionForm')
)

const LatestIterationLink = lazy(
  () => import('@/components/student/solution-summary/LatestIterationLink')
)

const ChangePublishedIterationModalButton = lazy(
  () =>
    import(
      '@/components/student/published-solution/ChangePublishedIterationModalButton'
    )
)
const UnpublishSolutionModalButton = lazy(
  () =>
    import(
      '@/components/student/published-solution/UnpublishSolutionModalButton'
    )
)

import { RenderLoader } from '@/components/common'
import { ScreenSizeWrapper } from '@/components/mentoring/session/ScreenSizeContext'
import { TrackMenuDropdownSkeleton } from '@/components/common/skeleton/skeletons/TrackMenuDropdownSkeleton'
import { NotificationsDropdownSkeleton } from '@/components/common/skeleton/skeletons/NotificationsDropdownSkeleton'
import { ReputationDropdownSkeleton } from '@/components/common/skeleton/skeletons/ReputationDropdownSkeleton'
import {
  TrackWelcomeModal,
  TrackWelcomeModalProps,
} from '@/components/modals/track-welcome-modal/TrackWelcomeModal'

// Add all react components here.
// Each should map 1-1 to a component in app/helpers/components
initReact({
  'common-markdown-editor': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <MarkdownEditor contextId={data.context_id} />
    </Suspense>
  ),

  editor: (data: any): JSX.Element => (
    <Suspense fallback={RenderLoader()}>
      <Editor {...camelizeKeysAs<EditorProps>(data)} />
    </Suspense>
  ),

  'modals-welcome-modal': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <WelcomeModal endpoint={data.endpoint} />
    </Suspense>
  ),

  'modals-track-welcome-modal': (data: any) => (
    <TrackWelcomeModal {...camelizeKeysAs<TrackWelcomeModalProps>(data)} />
  ),

  'modals-welcome-to-insiders-modal': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <WelcomeToInsidersModal endpoint={data.endpoint} />
    </Suspense>
  ),

  'maintaining-submissions-summary-table': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <SubmissionsSummaryTable
        submissions={data.submissions.map((s: any) => {
          return camelizeKeys(s)
        })}
      />
    </Suspense>
  ),
  'mentoring-inbox': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <Inbox
        discussionsRequest={camelizeKeysAs<MentoringInboxRequest>(
          data.discussions_request
        )}
        tracksRequest={camelizeKeysAs<MentoringInboxRequest>(
          data.tracks_request
        )}
        sortOptions={data.sort_options}
        links={data.links}
      />
    </Suspense>
  ),
  'mentoring-queue': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <Queue
        queueRequest={camelizeKeysAs<Request>(data.queue_request)}
        tracksRequest={camelizeKeysAs<Request>(data.tracks_request)}
        defaultTrack={camelizeKeysAs<MentoredTrack>(data.default_track)}
        defaultExercise={camelizeKeysAs<MentoredTrackExercise>(
          data.default_exercise
        )}
        links={camelizeKeysAs<MentoringQueueLinks>(data.links)}
        sortOptions={data.sort_options}
      />
    </Suspense>
  ),
  'mentoring-session': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <ScreenSizeWrapper>
        <Session
          userHandle={data.user_handle}
          discussion={camelizeKeysAs<MentorDiscussion>(data.discussion)}
          mentorSolution={camelizeKeysAs<CommunitySolution>(
            data.mentor_solution
          )}
          exemplarFiles={camelizeKeysAs<
            readonly MentoringSessionExemplarFile[]
          >(data.exemplar_files)}
          student={camelizeKeysAs<MentoringSessionStudent>(data.student)}
          track={camelizeKeysAs<MentorSessionTrack>(data.track)}
          exercise={camelizeKeysAs<MentorSessionExercise>(data.exercise)}
          iterations={camelizeKeysAs<Iteration[]>(data.iterations)}
          instructions={data.instructions}
          testFiles={data.test_files}
          links={camelizeKeysAs<MentoringSessionLinks>(data.links)}
          request={camelizeKeysAs<MentorSessionRequest>(data.request)}
          scratchpad={camelizeKeysAs<MentoringSessionScratchpad>(
            data.scratchpad
          )}
          guidance={camelizeKeysAs<
            Pick<Guidance, 'exercise' | 'track' | 'links'>
          >(data.guidance)}
          outOfDate={data.out_of_date}
          downloadCommand={data.download_command}
          studentSolutionUuid={data.student_solution_uuid}
        />
      </ScreenSizeWrapper>
    </Suspense>
  ),
  'mentoring-representations-with-feedback': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <RepresentationsWithFeedback
        data={camelizeKeysAs<AutomationProps>(data)}
      />
    </Suspense>
  ),
  'mentoring-representations-admin': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <RepresentationsAdmin data={camelizeKeysAs<AutomationProps>(data)} />
    </Suspense>
  ),
  'mentoring-representations-without-feedback': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <RepresentationsWithoutFeedback
        data={camelizeKeysAs<AutomationProps>(data)}
      />
    </Suspense>
  ),
  'mentoring-try-mentoring-button': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <TryMentoringButton
        text={data.text}
        size={data.size}
        links={camelizeKeysAs<TryMentoringButtonLinks>(data.links)}
      />
    </Suspense>
  ),
  'mentoring-testimonials-list': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <TestimonialsList
        request={camelizeKeysAs<Request>(data.request)}
        tracks={camelizeKeysAs<readonly MentoringTestimonialsListTrack[]>(
          data.tracks
        )}
        platforms={camelizeKeysAs<readonly SharePlatform[]>(data.platforms)}
      />
    </Suspense>
  ),
  'mentoring-representation': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <Representation data={camelizeKeysAs<CompleteRepresentationData>(data)} />
    </Suspense>
  ),
  'student-mentoring-session': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <StudentMentoringSession
        userHandle={data.user_handle}
        discussion={camelizeKeysAs<MentorDiscussion>(data.discussion)}
        iterations={camelizeKeysAs<Iteration[]>(data.iterations)}
        mentor={camelizeKeysAs<StudentMentoringSessionMentor>(data.mentor)}
        track={camelizeKeysAs<MentorSessionTrack>(data.track)}
        exercise={camelizeKeysAs<MentorSessionExercise>(data.exercise)}
        trackObjectives={data.track_objectives}
        videos={camelizeKeysAs<StudentMentoringSessionVideo[]>(data.videos)}
        request={camelizeKeysAs<MentorSessionRequest>(data.request)}
        links={camelizeKeysAs<StudentMentoringSessionLinks>(data.links)}
        outOfDate={data.out_of_date}
        donation={camelizeKeysAs<MentoringSessionDonation>(data.donation)}
      />
    </Suspense>
  ),
  'student-request-mentoring-button': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <RequestMentoringButton
        request={data.request}
        links={camelizeKeysAs<RequestMentoringButtonLinks>(data.links)}
      />
    </Suspense>
  ),

  'journey-journey-page': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <JourneyPage
        categories={camelizeKeysAs<readonly JourneyPageCategory[]>(
          data.categories
        )}
        defaultCategory={data.default_category}
      />
    </Suspense>
  ),
  'settings-profile-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <ProfileForm
        defaultUser={data.user}
        defaultProfile={data.profile}
        links={data.links}
      />
    </Suspense>
  ),
  'settings-photo-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <PhotoForm
        defaultUser={camelizeKeysAs<User>(data.user)}
        links={data.links}
      />
    </Suspense>
  ),
  'settings-delete-profile-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <DeleteProfileForm links={data.links} />
    </Suspense>
  ),
  'settings-pronouns-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <PronounsForm
        handle={data.handle}
        defaultPronounParts={data.pronoun_parts}
        links={data.links}
      />
    </Suspense>
  ),
  'settings-handle-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <HandleForm defaultHandle={data.handle} links={data.links} />
    </Suspense>
  ),
  'settings-email-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <EmailForm defaultEmail={data.email} links={data.links} />
    </Suspense>
  ),
  'settings-password-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <PasswordForm links={data.links} />
    </Suspense>
  ),
  'settings-token-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <TokenForm defaultToken={data.token} links={data.links} />
    </Suspense>
  ),
  'settings-user-preferences-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <UserPreferencesForm
        defaultPreferences={camelizeKeysAs<UserPreferences>(data.preferences)}
        links={data.links}
      />
    </Suspense>
  ),
  'settings-theme-preference-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <ThemePreferenceForm
        defaultThemePreference={data.default_theme_preference}
        insidersStatus={data.insiders_status}
        links={camelizeKeysAs<ThemePreferenceLinks>(data.links)}
      />
    </Suspense>
  ),
  'settings-comments-preference-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <CommentsPreferenceForm
        {...camelizeKeysAs<CommentsPreferenceFormProps>(data)}
      />
    </Suspense>
  ),
  'settings-communication-preferences-form': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <CommunicationPreferencesForm
        defaultPreferences={camelizeKeysAs<CommunicationPreferences>(
          data.preferences
        )}
        links={data.links}
      />
    </Suspense>
  ),
  'settings-delete-account-button': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <DeleteAccountButton handle={data.handle} links={data.links} />
    </Suspense>
  ),
  'settings-reset-account-button': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <ResetAccountButton handle={data.handle} links={data.links} />
    </Suspense>
  ),
  'settings-show-on-supporters-page-button': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <ShowOnSupportersPageButton
        defaultValue={data.value}
        links={data.links}
      />
    </Suspense>
  ),
  'dropdowns-notifications': (data: any) => (
    <Suspense fallback={<NotificationsDropdownSkeleton />}>
      <NotificationsDropdown endpoint={data.endpoint} />
    </Suspense>
  ),
  'dropdowns-reputation': (data: any) => (
    <Suspense
      fallback={<ReputationDropdownSkeleton reputation={data.reputation} />}
    >
      <div className="flex gap-8">
        <ReputationDropdown
          endpoint={data.endpoint}
          defaultIsSeen={data.is_seen}
          defaultReputation={data.reputation}
        />
      </div>
    </Suspense>
  ),
  'dropdowns-track-menu': (data: any) => (
    <Suspense fallback={<TrackMenuDropdownSkeleton />}>
      <TrackMenuDropdown
        track={data.track}
        links={camelizeKeysAs<TrackMenuLinks>(data.links)}
      />
    </Suspense>
  ),
  'notifications-notifications-list': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <NotificationsList
        request={camelizeKeysAs<Request>(data.request)}
        links={camelizeKeysAs<NotificationsListLinks>(data.links)}
      />
    </Suspense>
  ),
  'track-iteration-summary': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <IterationSummaryWithWebsockets
        iteration={camelizeKeysAs<Iteration>(data.iteration)}
        className={data.class_name}
        showSubmissionMethod={true}
        showTestsStatusAsButton={!!data.show_tests_status_as_button}
        showFeedbackIndicator={!!data.show_feedback_indicator}
      />
    </Suspense>
  ),
  'student-iterations-list': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <StudentIterationsList
        solutionUuid={data.solution_uuid}
        request={camelizeKeysAs<IterationsListRequest>(data.request)}
        exercise={camelizeKeysAs<IterationsListExercise>(data.exercise)}
        track={camelizeKeysAs<IterationsListTrack>(data.track)}
        links={camelizeKeysAs<IterationsListLinks>(data.links)}
      />
    </Suspense>
  ),
  'student-latest-iteration-link': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <LatestIterationLink
        iteration={camelizeKeysAs<Iteration>(data.latest_iteration)}
      />
    </Suspense>
  ),

  'student-publish-solution-button': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <PublishSolutionButton
        endpoint={data.endpoint}
        iterations={camelizeKeysAs<readonly Iteration[]>(data.iterations)}
      />
    </Suspense>
  ),
  'student-change-published-iteration-modal-button': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <ChangePublishedIterationModalButton
        {...camelizeKeysAs<ChangePublishedIterationModalButtonProps>(data)}
      />
    </Suspense>
  ),
  'student-unpublish-solution-modal-button': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <UnpublishSolutionModalButton
        {...camelizeKeysAs<UnpublishSolutionModalButtonProps>(data)}
      />
    </Suspense>
  ),
  'student-update-exercise-notice': (data: any) => (
    <Suspense fallback={RenderLoader()}>
      <UpdateExerciseNotice links={data.links} />
    </Suspense>
  ),

  // Slow things at the end
  'donations-footer-form': (data: any): JSX.Element => (
    <Suspense fallback={RenderLoader()}>
      <DonationsFooterForm {...camelizeKeysAs<FooterFormProps>(data)} />
    </Suspense>
  ),

  'donations-with-modal-form': (data: any): JSX.Element => (
    <Suspense fallback={RenderLoader()}>
      <DonationsFormWithModal
        request={camelizeKeysAs<Request>(data.request)}
        links={camelizeKeysAs<StripeFormLinks>(data.links)}
        userSignedIn={data.user_signed_in}
        captchaRequired={data.captcha_required}
        recaptchaSiteKey={data.recaptcha_site_key}
      />
    </Suspense>
  ),
  'donations-subscription-form': (data: any): JSX.Element => (
    <Suspense fallback={RenderLoader()}>
      <DonationsSubscriptionForm
        {...data}
        amount={currency(data.amount_in_cents, { fromCents: true })}
      />
    </Suspense>
  ),
})