Skip to content

Commit 26c8cf1

Browse files
committed
feat(footer): add FAQ modal (@openvaibhav)
1 parent 999b58c commit 26c8cf1

4 files changed

Lines changed: 327 additions & 1 deletion

File tree

frontend/src/ts/components/layout/footer/Footer.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ export function Footer(): JSXElement {
7171
}}
7272
href="https://x.com/monkeytype"
7373
/>
74+
<Button
75+
variant="text"
76+
text="faq"
77+
fa={{
78+
icon: "fa-question-circle",
79+
fixedWidth: true,
80+
}}
81+
onClick={() => showModal("Faq")}
82+
/>
7483
<Button
7584
variant="text"
7685
text="terms"
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
import { createMemo, createSignal, For, JSXElement, Show } from "solid-js";
2+
3+
import { AnimatedModal } from "../common/AnimatedModal";
4+
5+
type FaqTopic = {
6+
title: string;
7+
content: JSXElement;
8+
searchText: string;
9+
};
10+
11+
const faqTopics: FaqTopic[] = [
12+
{
13+
title: "How do I type coding symbols?",
14+
searchText:
15+
"coding symbols punctuation quotes custom text code special characters",
16+
content: (
17+
<div class="flex flex-col gap-4">
18+
<p>There are two ways to type coding symbols in Monkeytype:</p>
19+
<ul class="flex list-disc flex-col gap-2 pl-4">
20+
<li>
21+
<span class="text-text">Punctuation mode</span> - Enable it by
22+
clicking the <span class="text-text">@ punctuation</span> button in
23+
the test config bar. This adds common symbols like{" "}
24+
<span class="text-text">! , . ; : &apos; &quot; ( )</span>
25+
</li>
26+
<li>
27+
<span class="text-text">Quote mode</span> - Switch to{" "}
28+
<span class="text-text">quote</span> mode in the test config bar.
29+
Quotes often contain real code-like punctuation and symbols.
30+
</li>
31+
<li>
32+
<span class="text-text">Custom text</span> - Use the command line{" "}
33+
<span class="text-text">(Ctrl + Shift + P)</span> and select
34+
&quot;custom text&quot; to type any text you want, including code.
35+
</li>
36+
</ul>
37+
</div>
38+
),
39+
},
40+
{
41+
title: "How do I restart the test quickly?",
42+
searchText: "restart test quickly tab enter esc shortcut keyboard",
43+
content: (
44+
<div class="flex flex-col gap-4">
45+
<p>You can restart the test without touching the mouse:</p>
46+
<ul class="flex list-disc flex-col gap-2 pl-4">
47+
<li>
48+
Press <span class="text-text">Tab + Enter</span> - the default
49+
shortcut to restart.
50+
</li>
51+
<li>
52+
Enable <span class="text-text">Quick Restart</span> mode in settings
53+
- restart with <span class="text-text">Tab or Esc or Enter</span>.
54+
</li>
55+
</ul>
56+
</div>
57+
),
58+
},
59+
{
60+
title: "How do I change the language?",
61+
searchText: "language change switch foreign english spanish french german",
62+
content: (
63+
<div class="flex flex-col gap-4">
64+
<p>
65+
Search <span class="text-text">language</span> in command line. A list
66+
of all available languages will appear - select the one you want.
67+
</p>
68+
<p>Monkeytype supports multiple languages.</p>
69+
</div>
70+
),
71+
},
72+
{
73+
title: "How do I change the theme?",
74+
searchText: "theme color appearance dark light custom random palette",
75+
content: (
76+
<div class="flex flex-col gap-4">
77+
<p>There are several ways to change the theme:</p>
78+
<ul class="flex list-disc flex-col gap-2 pl-4">
79+
<li>
80+
Open the <span class="text-text">command line</span> with{" "}
81+
<span class="text-text">Ctrl + Shift + P</span> and search for a
82+
theme name.
83+
</li>
84+
<li>
85+
Go to <span class="text-text">Settings → Theme</span> and browse the
86+
full list.
87+
</li>
88+
<li>
89+
Enable <span class="text-text">random theme</span> in settings to
90+
get a new theme on every test. After completing a test, the theme
91+
will be set to a random one. The random themes are not saved to your
92+
config. If set to &apos;favorite&apos; only favorite themes will be
93+
randomized. If set to &apos;light&apos; or &apos;dark&apos;, only
94+
presets with light or dark background colors will be randomized. If
95+
set to &apos;auto&apos;, dark or light themes are used depending on
96+
your system theme. If set to &apos;custom&apos;, custom themes will
97+
be randomized.
98+
</li>
99+
<li>
100+
Click the <span class="text-text">palette icon</span> in the footer
101+
to quickly switch themes.
102+
</li>
103+
</ul>
104+
</div>
105+
),
106+
},
107+
{
108+
title: "How do I save my progress?",
109+
searchText: "save progress account history personal best login signup",
110+
content: (
111+
<div class="flex flex-col gap-4">
112+
<p>
113+
To save your typing history and personal bests, create a free account.
114+
</p>
115+
<p>
116+
Click the <span class="text-text">person icon</span> in the top right
117+
to sign up or log in. Once logged in, all your results are
118+
automatically saved.
119+
</p>
120+
<p>
121+
Without an account, results are only stored temporarily in your
122+
browser session.
123+
</p>
124+
</div>
125+
),
126+
},
127+
{
128+
title: "How do the leaderboards work?",
129+
searchText:
130+
"leaderboard rank top fastest qualify anticheat english 15 60 seconds",
131+
content: (
132+
<div class="flex flex-col gap-4">
133+
<p>
134+
The global leaderboards track the fastest typists for{" "}
135+
<span class="text-text">15 second</span> and{" "}
136+
<span class="text-text">60 second</span> English tests.
137+
</p>
138+
<p>To qualify, your result must:</p>
139+
<ul class="flex list-disc flex-col gap-2 pl-4">
140+
<li>Be completed while logged in</li>
141+
<li>
142+
Use the <span class="text-text">English</span> language
143+
</li>
144+
<li>Have no funbox modifiers active</li>
145+
<li>Pass the anticheat verification</li>
146+
</ul>
147+
</div>
148+
),
149+
},
150+
{
151+
title: "What is Blind Mode?",
152+
searchText: "blind mode errors hide mistakes accuracy training",
153+
content: (
154+
<div class="flex flex-col gap-4">
155+
<p>
156+
Blind mode hides your errors while typing - you won&apos;t see red
157+
characters or highlights during the test.
158+
</p>
159+
<p>
160+
This is useful for training yourself to keep typing without fixating
161+
on mistakes. Accuracy is still tracked and shown at the end.
162+
</p>
163+
<p>
164+
Enable it in{" "}
165+
<span class="text-text">Settings → Behavior → Blind Mode</span>.
166+
</p>
167+
</div>
168+
),
169+
},
170+
{
171+
title: "What is Pace Caret?",
172+
searchText: "pace caret target speed race wpm goal second caret",
173+
content: (
174+
<div class="flex flex-col gap-4">
175+
<p>
176+
The pace caret is a second caret that moves at a target speed so you
177+
can race against it.
178+
</p>
179+
<p>
180+
Set it to your personal best, average, or a custom WPM target in{" "}
181+
<span class="text-text">Settings → Caret → Pace Caret</span>.
182+
</p>
183+
</div>
184+
),
185+
},
186+
{
187+
title: "What are Funbox modes?",
188+
searchText: "funbox fun modes gibberish numbers challenge special modifier",
189+
content: (
190+
<div class="flex flex-col gap-4">
191+
<p>
192+
Funbox modes are special modifiers that change how the test works.
193+
Examples:
194+
</p>
195+
<ul class="flex list-disc flex-col gap-2 pl-4">
196+
<li>
197+
<span class="text-text">gibberish</span> - random nonsense words
198+
</li>
199+
<li>
200+
<span class="text-text">58008</span> - only numbers
201+
</li>
202+
<li>
203+
<span class="text-text">read ahead</span> - hides the current word
204+
</li>
205+
<li>
206+
<span class="text-text">no quit</span> - forces you to finish
207+
</li>
208+
</ul>
209+
<p>
210+
Access via <span class="text-text">Ctrl + Shift + P</span> → funbox.
211+
</p>
212+
</div>
213+
),
214+
},
215+
{
216+
title: "How do I use Custom Text?",
217+
searchText: "custom text paste own code lyrics repeat randomize",
218+
content: (
219+
<div class="flex flex-col gap-4">
220+
<p>
221+
Custom text lets you type any text you want - code, prose, lyrics.
222+
</p>
223+
<p>
224+
Open the command line with{" "}
225+
<span class="text-text">Ctrl + Shift + P</span>, search for{" "}
226+
<span class="text-text">custom text</span>, and paste your content.
227+
You can also set it to repeat or randomize word order.
228+
</p>
229+
</div>
230+
),
231+
},
232+
];
233+
234+
export function FaqModal(): JSXElement {
235+
const [selectedIndex, setSelectedIndex] = createSignal(0);
236+
const [search, setSearch] = createSignal("");
237+
238+
const filteredTopics = createMemo(() => {
239+
const q = search().toLowerCase().trim();
240+
if (q === "") return faqTopics.map((t, i) => ({ ...t, originalIndex: i }));
241+
return faqTopics
242+
.map((t, i) => ({ ...t, originalIndex: i }))
243+
.filter(
244+
(t) =>
245+
t.title.toLowerCase().includes(q) ||
246+
t.searchText.toLowerCase().includes(q),
247+
);
248+
});
249+
250+
const effectiveIndex = createMemo(() => {
251+
const topics = filteredTopics();
252+
if (topics.length === 0) return -1;
253+
const found = topics.find((t) => t.originalIndex === selectedIndex());
254+
if (found !== undefined) return selectedIndex();
255+
setSelectedIndex(topics[0]?.originalIndex ?? 0);
256+
return topics[0]?.originalIndex ?? 0;
257+
});
258+
259+
return (
260+
<AnimatedModal
261+
id="Faq"
262+
title="FAQ"
263+
modalClass="max-w-4xl h-[36rem] grid-rows-[auto_1fr]"
264+
>
265+
<div
266+
class="-mt-1 grid h-full gap-3 overflow-hidden"
267+
style={{ "grid-template-rows": "auto 1fr" }}
268+
>
269+
<input
270+
type="text"
271+
placeholder="Search..."
272+
class="w-full rounded bg-sub-alt px-3 py-2 text-sm text-text placeholder-sub outline-none focus-visible:shadow-none"
273+
value={search()}
274+
onInput={(e) => {
275+
setSearch(e.currentTarget.value);
276+
}}
277+
/>
278+
<div class="grid min-h-0 flex-1 grid-cols-[12rem_1fr] gap-4 overflow-hidden">
279+
<div class="flex min-h-0 flex-col gap-1 overflow-y-auto pr-2">
280+
<Show
281+
when={filteredTopics().length > 0}
282+
fallback={
283+
<div class="p-2 text-sm text-sub">No results found.</div>
284+
}
285+
>
286+
<For each={filteredTopics()}>
287+
{(topic) => (
288+
<button
289+
type="button"
290+
class="rounded p-2 text-left text-sm transition-colors"
291+
classList={{
292+
"bg-text text-bg":
293+
effectiveIndex() === topic.originalIndex,
294+
"text-sub hover:text-bg hover:bg-text":
295+
effectiveIndex() !== topic.originalIndex,
296+
}}
297+
onClick={() => setSelectedIndex(topic.originalIndex)}
298+
>
299+
{topic.title}
300+
</button>
301+
)}
302+
</For>
303+
</Show>
304+
</div>
305+
<div class="overflow-y-auto text-sm text-sub">
306+
<Show when={effectiveIndex() !== -1}>
307+
{faqTopics[effectiveIndex()]?.content}
308+
</Show>
309+
</div>
310+
</div>
311+
</div>
312+
</AnimatedModal>
313+
);
314+
}

frontend/src/ts/components/modals/Modals.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { JSXElement, Show, Suspense, lazy } from "solid-js";
22

33
import { isDevEnvironment } from "../../utils/misc";
44
import { ContactModal } from "./ContactModal";
5+
import { FaqModal } from "./FaqModal";
56
import { RegisterCaptchaModal } from "./RegisterCaptchaModal";
67
import { SupportModal } from "./SupportModal";
78
import { VersionHistoryModal } from "./VersionHistoryModal";
@@ -17,6 +18,7 @@ export function Modals(): JSXElement {
1718
<ContactModal />
1819
<RegisterCaptchaModal />
1920
<SupportModal />
21+
<FaqModal />
2022
<Show when={isDevEnvironment()}>
2123
<Suspense fallback={null}>
2224
<DevOptionsModal />

frontend/src/ts/stores/modals.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ export type ModalId =
88
| "DevOptions"
99
| "DevInboxPicker"
1010
| "RegisterCaptcha"
11-
| "Alerts";
11+
| "Alerts"
12+
| "Faq";
1213

1314
export type ModalVisibility = {
1415
visible: boolean;

0 commit comments

Comments
 (0)