pstream is dead; long live pstream
taciturnaxolotl.github.io/pstream-ng/
1import classNames from "classnames";
2import { useRef, useState } from "react";
3import { Trans, useTranslation } from "react-i18next";
4import { useNavigate } from "react-router-dom";
5
6import { SearchBarInput } from "@/components/form/SearchBar";
7import { ThinContainer } from "@/components/layout/ThinContainer";
8import { MwLink } from "@/components/text/Link";
9import { Ol } from "@/components/utils/Ol";
10import { Heading1, Heading2, Paragraph } from "@/components/utils/Text";
11import { PageTitle } from "@/pages/parts/util/PageTitle";
12
13import { SubPageLayout } from "./layouts/SubPageLayout";
14
15function Question(props: { title: string; children: React.ReactNode }) {
16 return (
17 <>
18 <p className="text-white mb-2 font-medium">{props.title}</p>
19 <div className="text-type-text">{props.children}</div>
20 </>
21 );
22}
23
24export function Button(props: {
25 className: string;
26 onClick?: () => void;
27 children: React.ReactNode;
28 disabled?: boolean;
29}) {
30 return (
31 <button
32 className={classNames(
33 "font-bold rounded h-10 w-40 scale-90 hover:scale-95 transition-all duration-200",
34 props.className,
35 )}
36 type="button"
37 onClick={props.onClick}
38 disabled={props.disabled}
39 >
40 {props.children}
41 </button>
42 );
43}
44
45function SectionHeading(props: { title: string }) {
46 return (
47 <h3 className="text-white font-medium text-lg my-8 pt-4">{props.title}</h3>
48 );
49}
50
51type SectionKey =
52 | "general"
53 | "search"
54 | "playback"
55 | "connections"
56 | "language";
57
58interface Sections {
59 general: JSX.Element[];
60 search: JSX.Element[];
61 playback: JSX.Element[];
62 connections: JSX.Element[];
63 language: JSX.Element[];
64}
65
66export function AboutPage() {
67 const { t } = useTranslation();
68 const navigate = useNavigate();
69 const [searchQuery, setSearchQuery] = useState("");
70 const searchRef = useRef<HTMLInputElement>(null);
71
72 const questionKeys = Object.keys(t("about", { returnObjects: true }))
73 .filter((key) => key.startsWith("q"))
74 .sort((a, b) => parseInt(a.slice(1), 10) - parseInt(b.slice(1), 10));
75
76 const sections: Sections = {
77 general: [],
78 search: [],
79 playback: [],
80 connections: [],
81 language: [],
82 };
83
84 questionKeys.forEach((key) => {
85 const section = t(`about.${key}.section`) as SectionKey;
86 if (section && sections[section]) {
87 sections[section].push(
88 <Question title={t(`about.${key}.title`)}>
89 {t(`about.${key}.body`)}
90 </Question>,
91 );
92 }
93 });
94
95 const allFaqItems = [
96 ...sections.general,
97 ...sections.search,
98 ...sections.playback,
99 ...sections.connections,
100 ...sections.language,
101 ];
102
103 const filteredItems = allFaqItems.filter((item: JSX.Element) => {
104 try {
105 const title = item?.props?.title?.toLowerCase() || "";
106 const body = item?.props?.children?.toLowerCase() || "";
107 const query = searchQuery.toLowerCase();
108 return title.includes(query) || body.includes(query);
109 } catch (e) {
110 return false;
111 }
112 });
113
114 const showFilteredItems = searchQuery.length > 0;
115
116 return (
117 <SubPageLayout>
118 <PageTitle subpage k="global.pages.about" />
119 <ThinContainer>
120 <Heading1>{t("about.title")}</Heading1>
121 <Paragraph>{t("about.description")}</Paragraph>
122
123 <div>
124 <SearchBarInput
125 ref={searchRef}
126 value={searchQuery}
127 onChange={(value) => setSearchQuery(value)}
128 onUnFocus={() => {}}
129 placeholder={t("about.searchPlaceholder")}
130 hideTooltip
131 />
132 </div>
133 <div className="pt-4">
134 <Trans i18nKey="about.help">
135 <MwLink url="/support" />
136 </Trans>
137 </div>
138
139 <Heading2 className="mt-10">{t("about.faqTitle")}</Heading2>
140
141 {showFilteredItems ? (
142 <Ol items={filteredItems} />
143 ) : (
144 <>
145 {/* General Section */}
146 {sections.general.length > 0 && (
147 <>
148 <SectionHeading title={t("about.sections.general")} />
149 <Ol items={sections.general} />
150 </>
151 )}
152
153 {/* Search Section */}
154 {sections.search.length > 0 && (
155 <>
156 <SectionHeading title={t("about.sections.search")} />
157 <Ol items={sections.search} />
158 </>
159 )}
160
161 {/* Playback Section */}
162 {sections.playback.length > 0 && (
163 <>
164 <SectionHeading title={t("about.sections.playback")} />
165 <Ol items={sections.playback} />
166 </>
167 )}
168
169 {/* FED API Section */}
170 {sections.connections.length > 0 && (
171 <>
172 <SectionHeading title={t("about.sections.connections")} />
173 <Ol items={sections.connections} />
174 </>
175 )}
176
177 {/* Language Section */}
178 {sections.language.length > 0 && (
179 <>
180 <SectionHeading title={t("about.sections.language")} />
181 <Ol items={sections.language} />
182 </>
183 )}
184 </>
185 )}
186
187 <div
188 style={{ display: "flex", justifyContent: "space-between" }}
189 className="pt-2 w-full"
190 >
191 <Button
192 className="py-px mt-8 box-content bg-buttons-secondary hover:bg-buttons-secondaryHover bg-opacity-90 text-buttons-secondaryText justify-center items-center"
193 onClick={() => navigate("/discover")}
194 >
195 Discover
196 </Button>
197 <Button
198 className="py-px mt-8 box-content bg-buttons-secondary hover:bg-buttons-secondaryHover bg-opacity-90 text-buttons-secondaryText justify-center items-center"
199 onClick={() => navigate("/support")}
200 >
201 Support
202 </Button>
203 </div>
204 </ThinContainer>
205 </SubPageLayout>
206 );
207}