[web] qns/quiz: persist quiz scroll mode between questions navigation (#1671)
This commit is contained in:
parent
9a1bba26e7
commit
9d7210480b
|
|
@ -60,6 +60,7 @@ export function questionHrefWithListType(
|
|||
href: string,
|
||||
listType?: QuestionListTypeData | null,
|
||||
questionMetadata?: QuestionMetadata,
|
||||
currentPathname?: string | null, // We need this prop and cannot use window.location instead because Next.js doesn't update location when we do the navigation
|
||||
): string {
|
||||
if (listType == null) {
|
||||
return href;
|
||||
|
|
@ -96,33 +97,38 @@ export function questionHrefWithListType(
|
|||
case 'framework': {
|
||||
switch (listType.value) {
|
||||
case 'react':
|
||||
return (
|
||||
`/questions/quiz/${QuestionFrameworkRawToSEOMapping.react}` +
|
||||
urlObject.search +
|
||||
`#${questionMetadata.slug}`
|
||||
return getQuizQuestionUrl(
|
||||
`/questions/quiz/${QuestionFrameworkRawToSEOMapping.react}`,
|
||||
questionMetadata.slug,
|
||||
urlObject,
|
||||
currentPathname,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'language': {
|
||||
switch (listType.value) {
|
||||
case 'js':
|
||||
return (
|
||||
`/questions/quiz/${QuestionLanguageRawToSEOMapping.js}` +
|
||||
urlObject.search +
|
||||
`#${questionMetadata.slug}`
|
||||
case 'js': {
|
||||
return getQuizQuestionUrl(
|
||||
`/questions/quiz/${QuestionLanguageRawToSEOMapping.js}`,
|
||||
questionMetadata.slug,
|
||||
urlObject,
|
||||
currentPathname,
|
||||
);
|
||||
}
|
||||
case 'html':
|
||||
return (
|
||||
`/questions/quiz/${QuestionLanguageRawToSEOMapping.html}` +
|
||||
urlObject.search +
|
||||
`#${questionMetadata.slug}`
|
||||
return getQuizQuestionUrl(
|
||||
`/questions/quiz/${QuestionLanguageRawToSEOMapping.html}`,
|
||||
questionMetadata.slug,
|
||||
urlObject,
|
||||
currentPathname,
|
||||
);
|
||||
case 'css':
|
||||
return (
|
||||
`/questions/quiz/${QuestionLanguageRawToSEOMapping.css}` +
|
||||
urlObject.search +
|
||||
`#${questionMetadata.slug}`
|
||||
return getQuizQuestionUrl(
|
||||
`/questions/quiz/${QuestionLanguageRawToSEOMapping.css}`,
|
||||
questionMetadata.slug,
|
||||
urlObject,
|
||||
currentPathname,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -136,6 +142,7 @@ export function questionHrefFrameworkSpecificAndListType(
|
|||
questionMetadata: QuestionMetadata,
|
||||
listType?: QuestionListTypeData | null,
|
||||
framework?: QuestionFramework,
|
||||
currentPathname?: string | null,
|
||||
): string {
|
||||
const hrefWithMaybeFramework = questionHrefFrameworkSpecific(
|
||||
questionMetadata,
|
||||
|
|
@ -147,12 +154,19 @@ export function questionHrefFrameworkSpecificAndListType(
|
|||
hrefWithMaybeFramework,
|
||||
listType,
|
||||
questionMetadata,
|
||||
currentPathname,
|
||||
);
|
||||
|
||||
return questionHrefStripSamePathnameAndSearch(hrefWithListType);
|
||||
return questionHrefStripSamePathnameAndSearch(
|
||||
hrefWithListType,
|
||||
currentPathname,
|
||||
);
|
||||
}
|
||||
|
||||
function questionHrefStripSamePathnameAndSearch(href: string): string {
|
||||
function questionHrefStripSamePathnameAndSearch(
|
||||
href: string,
|
||||
currentPathname?: string | null,
|
||||
): string {
|
||||
if (typeof window === 'undefined') {
|
||||
return href;
|
||||
}
|
||||
|
|
@ -162,7 +176,7 @@ function questionHrefStripSamePathnameAndSearch(href: string): string {
|
|||
// Leave only the hash if the current URL is the same as the href
|
||||
// Next.js has problems pushing to the same URL with a hash
|
||||
if (
|
||||
window.location.pathname === urlObj.pathname &&
|
||||
currentPathname === urlObj.pathname &&
|
||||
window.location.search === urlObj.search
|
||||
) {
|
||||
return urlObj.hash || '#';
|
||||
|
|
@ -170,3 +184,33 @@ function questionHrefStripSamePathnameAndSearch(href: string): string {
|
|||
|
||||
return href;
|
||||
}
|
||||
|
||||
function getQuizQuestionUrl(
|
||||
base: string,
|
||||
slug: string,
|
||||
urlObject: URL,
|
||||
currentPathname?: string | null,
|
||||
) {
|
||||
const scrollModeUrls = [
|
||||
`/questions/quiz/${QuestionFrameworkRawToSEOMapping.react}`,
|
||||
`/questions/quiz/${QuestionLanguageRawToSEOMapping.js}`,
|
||||
`/questions/quiz/${QuestionLanguageRawToSEOMapping.html}`,
|
||||
`/questions/quiz/${QuestionLanguageRawToSEOMapping.css}`,
|
||||
];
|
||||
const scrollModeUrl = base + urlObject.search + `#${slug}`;
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
return scrollModeUrl;
|
||||
}
|
||||
|
||||
const isQuizPage = currentPathname?.includes('/questions/quiz');
|
||||
// Check if the current URL is scroll mode url type
|
||||
const isScrollMode = scrollModeUrls.includes(currentPathname ?? '');
|
||||
|
||||
if (!isQuizPage || isScrollMode) {
|
||||
return scrollModeUrl;
|
||||
}
|
||||
|
||||
// Return quiz page by page url if the current page is page by page mode
|
||||
return `/questions/quiz/${slug}` + urlObject.search;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { Suspense } from 'react';
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
|
||||
|
||||
|
|
@ -142,17 +144,8 @@ function InterviewsQuestionsListSlideOutButtonImpl({
|
|||
|
||||
return (
|
||||
<div className="flex h-7 gap-x-2">
|
||||
<Button
|
||||
<NavigationButton
|
||||
addonPosition="start"
|
||||
href={
|
||||
prevQuestion
|
||||
? questionHrefFrameworkSpecificAndListType(
|
||||
prevQuestion,
|
||||
listType,
|
||||
framework,
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
icon={RiArrowLeftSLine}
|
||||
isDisabled={prevQuestion == null}
|
||||
isLabelHidden={true}
|
||||
|
|
@ -161,6 +154,17 @@ function InterviewsQuestionsListSlideOutButtonImpl({
|
|||
description: 'Previous question',
|
||||
id: 'WPfIhl',
|
||||
})}
|
||||
questionHref={
|
||||
prevQuestion
|
||||
? (pathname: string | null) =>
|
||||
questionHrefFrameworkSpecificAndListType(
|
||||
prevQuestion,
|
||||
listType,
|
||||
framework,
|
||||
pathname,
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
size="xs"
|
||||
tooltip={prevQuestion ? prevQuestion?.title : undefined}
|
||||
variant="tertiary"
|
||||
|
|
@ -178,17 +182,8 @@ function InterviewsQuestionsListSlideOutButtonImpl({
|
|||
slideOutSearchParam_MUST_BE_UNIQUE_ON_PAGE
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
<NavigationButton
|
||||
addonPosition="start"
|
||||
href={
|
||||
nextQuestion
|
||||
? questionHrefFrameworkSpecificAndListType(
|
||||
nextQuestion,
|
||||
listType,
|
||||
framework,
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
icon={RiArrowRightSLine}
|
||||
isDisabled={nextQuestion == null}
|
||||
isLabelHidden={true}
|
||||
|
|
@ -197,6 +192,17 @@ function InterviewsQuestionsListSlideOutButtonImpl({
|
|||
description: 'Next question',
|
||||
id: 'DqvEKB',
|
||||
})}
|
||||
questionHref={
|
||||
nextQuestion
|
||||
? (pathname: string | null) =>
|
||||
questionHrefFrameworkSpecificAndListType(
|
||||
nextQuestion,
|
||||
listType,
|
||||
framework,
|
||||
pathname,
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
size="xs"
|
||||
tooltip={nextQuestion ? nextQuestion?.title : undefined}
|
||||
variant="tertiary"
|
||||
|
|
@ -204,3 +210,17 @@ function InterviewsQuestionsListSlideOutButtonImpl({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NavigationButton({
|
||||
questionHref,
|
||||
...props
|
||||
}: ComponentProps<typeof Button> &
|
||||
Readonly<{
|
||||
questionHref?: (pathname: string | null) => string;
|
||||
}>) {
|
||||
const pathname = usePathname();
|
||||
|
||||
const href = questionHref?.(pathname);
|
||||
|
||||
return <Button {...props} href={href} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import clsx from 'clsx';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
|
|
@ -220,6 +220,7 @@ export default function InterviewsQuestionsListSlideOutContents({
|
|||
}: Props) {
|
||||
const intl = useIntl();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { userProfile } = useUserProfile();
|
||||
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
|
|
@ -379,6 +380,7 @@ export default function InterviewsQuestionsListSlideOutContents({
|
|||
processedQuestions[0],
|
||||
listType,
|
||||
framework,
|
||||
pathname,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -388,6 +390,7 @@ export default function InterviewsQuestionsListSlideOutContents({
|
|||
processedQuestions,
|
||||
setFirstQuestionHref,
|
||||
showCompanyPaywall,
|
||||
pathname,
|
||||
]);
|
||||
|
||||
const label = intl.formatMessage({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import clsx from 'clsx';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { questionHrefFrameworkSpecificAndListType } from '~/components/interviews/questions/common/QuestionHrefUtils';
|
||||
import QuestionAuthor from '~/components/interviews/questions/metadata/QuestionAuthor';
|
||||
|
|
@ -27,6 +28,8 @@ export default function InterviewsQuestionsListSlideOutHovercardContents({
|
|||
question,
|
||||
size = 'body3',
|
||||
}: Props) {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
|
@ -45,11 +48,12 @@ export default function InterviewsQuestionsListSlideOutHovercardContents({
|
|||
question,
|
||||
listType,
|
||||
framework,
|
||||
pathname,
|
||||
)}
|
||||
variant="flat">
|
||||
{question.title}
|
||||
</Anchor>
|
||||
<Text className="text-pretty grow" color="secondary" size="body2">
|
||||
<Text className="grow text-pretty" color="secondary" size="body2">
|
||||
{question.excerpt}
|
||||
</Text>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import clsx from 'clsx';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import VignetteOverlay from '~/components/common/VignetteOverlay';
|
||||
|
|
@ -49,6 +50,7 @@ export default function InterviewsQuestionsListSlideOutQuestionList<
|
|||
showCompanyPaywall,
|
||||
}: Props<Q>) {
|
||||
const intl = useIntl();
|
||||
const pathname = usePathname();
|
||||
|
||||
if (questions.length === 0) {
|
||||
return (
|
||||
|
|
@ -138,6 +140,7 @@ export default function InterviewsQuestionsListSlideOutQuestionList<
|
|||
questionMetadata,
|
||||
listType,
|
||||
framework,
|
||||
pathname,
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Reference in New Issue