diff --git a/package.json b/package.json index d1b8aecc..c3b3b910 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.13.0", "@vitejs/plugin-react": "^3.1.0", + "type-fest": "^4.3.3", "autoprefixer": "^10.4.13", "cross-env": "^7.0.3", "eslint": "^8.10.0", @@ -103,7 +104,6 @@ "tailwind-scrollbar": "^2.0.1", "tailwindcss": "^3.2.4", "tailwindcss-themer": "^3.1.0", - "type-fest": "^4.3.3", "typescript": "^4.6.4", "vite": "^4.4.12", "vite-plugin-checker": "^0.5.6", diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index 4d098125..9e923ee0 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -3,7 +3,7 @@ "deviceNameLabel": "Device name", "deviceNamePlaceholder": "Personal phone", "hasAccount": "Already have an account? <0>Login here.", - "createAccount": "Dont have an account yet? <0>Create an account.", + "createAccount": "Don't have an account yet? <0>Create an account.", "register": { "information": { "title": "Account information", @@ -26,7 +26,7 @@ "generate": { "title": "Your passphrase", "next": "I have saved my passphrase", - "description": "Your passphase acts as your username and password. Make sure to keep it safe as you will need to enter it to login to your account" + "description": "Your passphrase acts as your username and password. Make sure to keep it safe as you will need to enter it to login to your account" }, "trust": { "title": "Do you trust this server?", @@ -91,7 +91,7 @@ "items": { "pending": "Checking for videos...", "notFound": "Doesn't have the video", - "failure": "Error occured" + "failure": "Error occurred" } }, "playbackError": { @@ -104,7 +104,7 @@ "errorNetwork": "Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.", "errorDecode": "Despite having previously been determined to be usable, an error occurred while trying to decode the media resource, resulting in an error.", "errorNotSupported": "The media or media provider object is not supported.", - "errorGenericMedia": "Unknown media error occured." + "errorGenericMedia": "Unknown media error occurred." } }, "metadata": { @@ -230,7 +230,7 @@ }, "night": { "default": "What would you like to watch tonight?", - "extra": ["Tired? I hear The Excorcist is good."] + "extra": ["Tired? I hear The Exorcist is good."] } }, "search": { @@ -276,6 +276,7 @@ "register": "Sync to cloud", "settings": "Settings", "about": "About us", + "donation": "Donate", "support": "Support", "logout": "Log out" } @@ -398,7 +399,7 @@ } }, "footer": { - "tagline": "Watch your favorite shows and movies with this open source streaming app.", + "tagline": "Watch your favourite shows and movies with this open source streaming app.", "links": { "github": "GitHub", "dmca": "DMCA", diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index babdd327..71e6b556 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -59,6 +59,8 @@ export enum Icons { MENU = "menu", LOCK = "lock", UNLOCK = "unlock", + DONATION = "donation", + CIRCLE_QUESTION = "circle_question", } export interface IconProps { @@ -125,6 +127,8 @@ const iconList: Record = { menu: ``, lock: ``, unlock: ``, + donation: ``, + circle_question: ``, }; function ChromeCastButton() { diff --git a/src/components/LinksDropdown.tsx b/src/components/LinksDropdown.tsx index 3063ddb4..24115d21 100644 --- a/src/components/LinksDropdown.tsx +++ b/src/components/LinksDropdown.tsx @@ -139,9 +139,12 @@ export function LinksDropdown(props: { children: React.ReactNode }) { {t("navigation.menu.settings")} - + {t("navigation.menu.about")} + + {t("navigation.menu.donation")} + {deviceName ? ( diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index ec4951a6..e51c08cf 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -1,5 +1,7 @@ +import { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { useHistory } from "react-router-dom"; +import type { RequireExactlyOne } from "type-fest"; import { Icon, Icons } from "@/components/Icon"; import { BrandPill } from "@/components/layout/BrandPill"; @@ -7,19 +9,33 @@ import { WideContainer } from "@/components/layout/WideContainer"; import { shouldHaveDmcaPage } from "@/pages/Dmca"; import { conf } from "@/setup/config"; -function FooterLink(props: { - href?: string; - onClick?: () => void; - children: React.ReactNode; - icon: Icons; -}) { +// to and href are mutually exclusive +type FooterLinkProps = RequireExactlyOne< + { + children: React.ReactNode; + icon: Icons; + to: string; + href: string; + }, + "to" | "href" +>; + +function FooterLink(props: FooterLinkProps) { + const history = useHistory(); + + const navigateTo = useCallback(() => { + if (!props.to) return; + + history.push(props.to); + }, [history, props.to]); + return ( {props.children} @@ -29,12 +45,11 @@ function FooterLink(props: { function Dmca() { const { t } = useTranslation(); - const history = useHistory(); if (!shouldHaveDmcaPage()) return null; return ( - history.push("/dmca")}> + {t("footer.links.dmca")} ); diff --git a/src/pages/parts/errors/ErrorCard.tsx b/src/pages/parts/errors/ErrorCard.tsx index 3930326b..9c975596 100644 --- a/src/pages/parts/errors/ErrorCard.tsx +++ b/src/pages/parts/errors/ErrorCard.tsx @@ -37,24 +37,28 @@ export function ErrorCard(props: { return ( // I didn't put a here because it'd fade out, then jump height weirdly -
-
- {t("errors.details")} -
+
+
+ {t("errors.details")} +
@@ -67,7 +71,7 @@ export function ErrorCard(props: {
-
+
{errorMessage}
@@ -82,8 +86,8 @@ export function ErrorCardInPlainModal(props: { }) { if (!props.show || !props.error) return null; return ( -
-
+
+
@@ -99,7 +103,7 @@ export function ErrorCardInModal(props: { return ( -
+
diff --git a/src/pages/parts/errors/ErrorPart.tsx b/src/pages/parts/errors/ErrorPart.tsx index 9d62b3dc..2d39af3a 100644 --- a/src/pages/parts/errors/ErrorPart.tsx +++ b/src/pages/parts/errors/ErrorPart.tsx @@ -21,10 +21,10 @@ export function ErrorPart(props: { error: any; errorInfo: any }) { const error = `${props.error.toString()}\n${errorLines.join("\n")}`; return ( -
+
- + {t("errors.badge")} {t("errors.title")} @@ -38,14 +38,14 @@ export function ErrorPart(props: { error: any; errorInfo: any }) {
window.location.reload()} > {t("errors.reloadPage")} setShowErrorCard(true)} > {t("errors.showError")} diff --git a/src/pages/parts/home/HeroPart.tsx b/src/pages/parts/home/HeroPart.tsx index e40a1aa0..17a1172c 100644 --- a/src/pages/parts/home/HeroPart.tsx +++ b/src/pages/parts/home/HeroPart.tsx @@ -1,6 +1,7 @@ -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import Sticky from "react-sticky-el"; +import { useWindowSize } from "react-use"; import { SearchBarInput } from "@/components/form/SearchBar"; import { ThinContainer } from "@/components/layout/ThinContainer"; @@ -29,6 +30,20 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) { [setShowBg, setIsSticky] ); + const { width: windowWidth } = useWindowSize(); + + const topSpacing = 16; + const [stickyOffset, setStickyOffset] = useState(topSpacing); + useEffect(() => { + if (windowWidth > 1200) { + // On large screens the bar goes inline with the nav elements + setStickyOffset(topSpacing); + } else { + // On smaller screens the bar goes below the nav elements + setStickyOffset(topSpacing + 60); + } + }, [windowWidth]); + let time = "night"; const hour = new Date().getHours(); if (hour < 12) time = "morning"; @@ -47,9 +62,9 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
diff --git a/vite.config.ts b/vite.config.ts index 4fbb2776..f8934b21 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -39,7 +39,7 @@ export default defineConfig(({ mode }) => { } }), VitePWA({ - disable: process.env.VITE_PWA_ENABLED !== "yes", + disable: env.VITE_PWA_ENABLED !== "yes", registerType: "autoUpdate", workbox: { maximumFileSizeToCacheInBytes: 4000000, // 4mb @@ -57,7 +57,6 @@ export default defineConfig(({ mode }) => { theme_color: "#120f1d", background_color: "#120f1d", display: "standalone", - orientation: "portrait-primary", start_url: "/", icons: [ {