mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-25 18:11:48 +01:00
commit
8b498ef036
@ -16,6 +16,7 @@
|
|||||||
"title": "Login to your account",
|
"title": "Login to your account",
|
||||||
"description": "Please enter your passphrase to login to your account",
|
"description": "Please enter your passphrase to login to your account",
|
||||||
"validationError": "Invalid or incomplete passphrase",
|
"validationError": "Invalid or incomplete passphrase",
|
||||||
|
"deviceLengthError": "Please enter a device name",
|
||||||
"submit": "Login",
|
"submit": "Login",
|
||||||
"passphraseLabel": "12-Word passphrase",
|
"passphraseLabel": "12-Word passphrase",
|
||||||
"passphrasePlaceholder": "Passphrase"
|
"passphrasePlaceholder": "Passphrase"
|
||||||
|
@ -21,7 +21,7 @@ export function Dropdown(props: DropdownProps) {
|
|||||||
<Listbox value={props.selectedItem} onChange={props.setSelectedItem}>
|
<Listbox value={props.selectedItem} onChange={props.setSelectedItem}>
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
<Listbox.Button className="relative w-full rounded-lg bg-dropdown-background py-3 pl-3 pr-10 text-left text-white shadow-md focus:outline-none tabbable cursor-pointer">
|
<Listbox.Button className="relative w-full rounded-lg bg-dropdown-background hover:bg-dropdown-hoverBackground py-3 pl-3 pr-10 text-left text-white shadow-md focus:outline-none tabbable cursor-pointer">
|
||||||
<span className="flex gap-4 items-center truncate">
|
<span className="flex gap-4 items-center truncate">
|
||||||
{props.selectedItem.leftIcon
|
{props.selectedItem.leftIcon
|
||||||
? props.selectedItem.leftIcon
|
? props.selectedItem.leftIcon
|
||||||
|
@ -4,6 +4,7 @@ import { useHistory } from "react-router-dom";
|
|||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
import { BrandPill } from "@/components/layout/BrandPill";
|
import { BrandPill } from "@/components/layout/BrandPill";
|
||||||
import { WideContainer } from "@/components/layout/WideContainer";
|
import { WideContainer } from "@/components/layout/WideContainer";
|
||||||
|
import { shouldHaveDmcaPage } from "@/pages/Dmca";
|
||||||
import { conf } from "@/setup/config";
|
import { conf } from "@/setup/config";
|
||||||
|
|
||||||
function FooterLink(props: {
|
function FooterLink(props: {
|
||||||
@ -30,6 +31,8 @@ function Dmca() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
if (!shouldHaveDmcaPage()) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FooterLink icon={Icons.DRAGON} onClick={() => history.push("/dmca")}>
|
<FooterLink icon={Icons.DRAGON} onClick={() => history.push("/dmca")}>
|
||||||
{t("footer.links.dmca")}
|
{t("footer.links.dmca")}
|
||||||
|
@ -130,10 +130,12 @@ export function CaptionsView({ id }: { id: string }) {
|
|||||||
[selectLanguage, setCurrentlyDownloading]
|
[selectLanguage, setCurrentlyDownloading]
|
||||||
);
|
);
|
||||||
|
|
||||||
const content = subtitleList.map((v) => {
|
const content = subtitleList.map((v, i) => {
|
||||||
return (
|
return (
|
||||||
<CaptionOption
|
<CaptionOption
|
||||||
key={v.language}
|
// key must use index to prevent url collisions
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
key={`${i}-${v.url}`}
|
||||||
countryCode={v.language}
|
countryCode={v.language}
|
||||||
selected={lang === v.language}
|
selected={lang === v.language}
|
||||||
loading={v.language === currentlyDownloading && downloadReq.loading}
|
loading={v.language === currentlyDownloading && downloadReq.loading}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
export function handleBuffered(time: number, buffered: TimeRanges): number {
|
export function handleBuffered(time: number, buffered: TimeRanges): number {
|
||||||
// TODO normalize the buffer sections into one section. they can be stitched together
|
|
||||||
for (let i = 0; i < buffered.length; i += 1) {
|
for (let i = 0; i < buffered.length; i += 1) {
|
||||||
if (buffered.start(buffered.length - 1 - i) < time) {
|
if (buffered.start(buffered.length - 1 - i) < time) {
|
||||||
return buffered.end(buffered.length - 1 - i);
|
return buffered.end(buffered.length - 1 - i);
|
||||||
|
@ -4,10 +4,14 @@ import { Icon, Icons } from "@/components/Icon";
|
|||||||
import { ThinContainer } from "@/components/layout/ThinContainer";
|
import { ThinContainer } from "@/components/layout/ThinContainer";
|
||||||
import { Heading1, Paragraph } from "@/components/utils/Text";
|
import { Heading1, Paragraph } from "@/components/utils/Text";
|
||||||
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
||||||
|
import { conf } from "@/setup/config";
|
||||||
|
|
||||||
import { SubPageLayout } from "./layouts/SubPageLayout";
|
import { SubPageLayout } from "./layouts/SubPageLayout";
|
||||||
|
|
||||||
// TODO make email a constant
|
export function shouldHaveDmcaPage() {
|
||||||
|
return !!conf().DMCA_EMAIL;
|
||||||
|
}
|
||||||
|
|
||||||
export function DmcaPage() {
|
export function DmcaPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -19,7 +23,7 @@ export function DmcaPage() {
|
|||||||
<Paragraph>{t("screens.dmca.text")}</Paragraph>
|
<Paragraph>{t("screens.dmca.text")}</Paragraph>
|
||||||
<Paragraph className="flex space-x-3 items-center">
|
<Paragraph className="flex space-x-3 items-center">
|
||||||
<Icon icon={Icons.MAIL} />
|
<Icon icon={Icons.MAIL} />
|
||||||
<span>dmca@movie-web.app</span>
|
<span>{conf().DMCA_EMAIL ?? ""}</span>
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</ThinContainer>
|
</ThinContainer>
|
||||||
</SubPageLayout>
|
</SubPageLayout>
|
||||||
|
@ -32,11 +32,18 @@ export function AccountCreatePart(props: AccountCreatePartProps) {
|
|||||||
const [colorB, setColorB] = useState("#2E65CF");
|
const [colorB, setColorB] = useState("#2E65CF");
|
||||||
const [userIcon, setUserIcon] = useState<UserIcons>(UserIcons.USER);
|
const [userIcon, setUserIcon] = useState<UserIcons>(UserIcons.USER);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// TODO validate device and account before next step
|
const [hasDeviceError, setHasDeviceError] = useState(false);
|
||||||
|
|
||||||
const nextStep = useCallback(() => {
|
const nextStep = useCallback(() => {
|
||||||
|
setHasDeviceError(false);
|
||||||
|
const validatedDevice = device.trim();
|
||||||
|
if (validatedDevice.length === 0) {
|
||||||
|
setHasDeviceError(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
props.onNext?.({
|
props.onNext?.({
|
||||||
device,
|
device: validatedDevice,
|
||||||
profile: {
|
profile: {
|
||||||
colorA,
|
colorA,
|
||||||
colorB,
|
colorB,
|
||||||
@ -75,6 +82,11 @@ export function AccountCreatePart(props: AccountCreatePartProps) {
|
|||||||
value={userIcon}
|
value={userIcon}
|
||||||
onInput={setUserIcon}
|
onInput={setUserIcon}
|
||||||
/>
|
/>
|
||||||
|
{hasDeviceError ? (
|
||||||
|
<p className="text-authentication-errorText">
|
||||||
|
{t("auth.login.deviceLengthError")}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<LargeCardButtons>
|
<LargeCardButtons>
|
||||||
<Button theme="purple" onClick={() => nextStep()}>
|
<Button theme="purple" onClick={() => nextStep()}>
|
||||||
|
@ -29,14 +29,17 @@ export function LoginFormPart(props: LoginFormPartProps) {
|
|||||||
|
|
||||||
const [result, execute] = useAsyncFn(
|
const [result, execute] = useAsyncFn(
|
||||||
async (inputMnemonic: string, inputdevice: string) => {
|
async (inputMnemonic: string, inputdevice: string) => {
|
||||||
// TODO verify valid device input
|
|
||||||
if (!verifyValidMnemonic(inputMnemonic))
|
if (!verifyValidMnemonic(inputMnemonic))
|
||||||
throw new Error(t("auth.login.validationError") ?? undefined);
|
throw new Error(t("auth.login.validationError") ?? undefined);
|
||||||
|
|
||||||
|
const validatedDevice = inputdevice.trim();
|
||||||
|
if (validatedDevice.length === 0)
|
||||||
|
throw new Error(t("auth.login.deviceLengthError") ?? undefined);
|
||||||
|
|
||||||
const account = await login({
|
const account = await login({
|
||||||
mnemonic: inputMnemonic,
|
mnemonic: inputMnemonic,
|
||||||
userData: {
|
userData: {
|
||||||
device: inputdevice,
|
device: validatedDevice,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,7 +49,7 @@ export function LoginFormPart(props: LoginFormPartProps) {
|
|||||||
|
|
||||||
props.onLogin?.();
|
props.onLogin?.();
|
||||||
},
|
},
|
||||||
[props, login, restore]
|
[props, login, restore, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -14,7 +14,7 @@ import { useOnlineListener } from "@/hooks/usePing";
|
|||||||
import { AboutPage } from "@/pages/About";
|
import { AboutPage } from "@/pages/About";
|
||||||
import { AdminPage } from "@/pages/admin/AdminPage";
|
import { AdminPage } from "@/pages/admin/AdminPage";
|
||||||
import VideoTesterView from "@/pages/developer/VideoTesterView";
|
import VideoTesterView from "@/pages/developer/VideoTesterView";
|
||||||
import { DmcaPage } from "@/pages/Dmca";
|
import { DmcaPage, shouldHaveDmcaPage } from "@/pages/Dmca";
|
||||||
import { NotFoundPage } from "@/pages/errors/NotFoundPage";
|
import { NotFoundPage } from "@/pages/errors/NotFoundPage";
|
||||||
import { HomePage } from "@/pages/HomePage";
|
import { HomePage } from "@/pages/HomePage";
|
||||||
import { LoginPage } from "@/pages/Login";
|
import { LoginPage } from "@/pages/Login";
|
||||||
@ -93,7 +93,10 @@ function App() {
|
|||||||
<Route exact path="/register" component={RegisterPage} />
|
<Route exact path="/register" component={RegisterPage} />
|
||||||
<Route exact path="/login" component={LoginPage} />
|
<Route exact path="/login" component={LoginPage} />
|
||||||
<Route exact path="/faq" component={AboutPage} />
|
<Route exact path="/faq" component={AboutPage} />
|
||||||
<Route exact path="/dmca" component={DmcaPage} />
|
|
||||||
|
{shouldHaveDmcaPage() ? (
|
||||||
|
<Route exact path="/dmca" component={DmcaPage} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
{/* Settings page */}
|
{/* Settings page */}
|
||||||
<Route exact path="/settings" component={SettingsPage} />
|
<Route exact path="/settings" component={SettingsPage} />
|
||||||
|
@ -10,6 +10,7 @@ interface Config {
|
|||||||
GITHUB_LINK: string;
|
GITHUB_LINK: string;
|
||||||
DONATION_LINK: string;
|
DONATION_LINK: string;
|
||||||
DISCORD_LINK: string;
|
DISCORD_LINK: string;
|
||||||
|
DMCA_EMAIL: string;
|
||||||
TMDB_READ_API_KEY: string;
|
TMDB_READ_API_KEY: string;
|
||||||
CORS_PROXY_URL: string;
|
CORS_PROXY_URL: string;
|
||||||
NORMAL_ROUTER: boolean;
|
NORMAL_ROUTER: boolean;
|
||||||
@ -22,6 +23,7 @@ export interface RuntimeConfig {
|
|||||||
GITHUB_LINK: string;
|
GITHUB_LINK: string;
|
||||||
DONATION_LINK: string;
|
DONATION_LINK: string;
|
||||||
DISCORD_LINK: string;
|
DISCORD_LINK: string;
|
||||||
|
DMCA_EMAIL: string | null;
|
||||||
TMDB_READ_API_KEY: string;
|
TMDB_READ_API_KEY: string;
|
||||||
NORMAL_ROUTER: boolean;
|
NORMAL_ROUTER: boolean;
|
||||||
PROXY_URLS: string[];
|
PROXY_URLS: string[];
|
||||||
@ -35,6 +37,7 @@ const env: Record<keyof Config, undefined | string> = {
|
|||||||
GITHUB_LINK: undefined,
|
GITHUB_LINK: undefined,
|
||||||
DONATION_LINK: undefined,
|
DONATION_LINK: undefined,
|
||||||
DISCORD_LINK: undefined,
|
DISCORD_LINK: undefined,
|
||||||
|
DMCA_EMAIL: import.meta.env.VITE_DMCA_EMAIL,
|
||||||
CORS_PROXY_URL: import.meta.env.VITE_CORS_PROXY_URL,
|
CORS_PROXY_URL: import.meta.env.VITE_CORS_PROXY_URL,
|
||||||
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
|
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
|
||||||
BACKEND_URL: import.meta.env.VITE_BACKEND_URL,
|
BACKEND_URL: import.meta.env.VITE_BACKEND_URL,
|
||||||
@ -54,11 +57,13 @@ function getKey(key: keyof Config, defaultString?: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function conf(): RuntimeConfig {
|
export function conf(): RuntimeConfig {
|
||||||
|
const dmcaEmail = getKey("DMCA_EMAIL");
|
||||||
return {
|
return {
|
||||||
APP_VERSION,
|
APP_VERSION,
|
||||||
GITHUB_LINK,
|
GITHUB_LINK,
|
||||||
DONATION_LINK,
|
DONATION_LINK,
|
||||||
DISCORD_LINK,
|
DISCORD_LINK,
|
||||||
|
DMCA_EMAIL: dmcaEmail.length > 0 ? dmcaEmail : null,
|
||||||
BACKEND_URL: getKey("BACKEND_URL"),
|
BACKEND_URL: getKey("BACKEND_URL"),
|
||||||
TMDB_READ_API_KEY: getKey("TMDB_READ_API_KEY"),
|
TMDB_READ_API_KEY: getKey("TMDB_READ_API_KEY"),
|
||||||
PROXY_URLS: getKey("CORS_PROXY_URL")
|
PROXY_URLS: getKey("CORS_PROXY_URL")
|
||||||
|
@ -97,6 +97,7 @@ export const defaultTheme = {
|
|||||||
dropdown: {
|
dropdown: {
|
||||||
background: "#171728",
|
background: "#171728",
|
||||||
altBackground: "#151525",
|
altBackground: "#151525",
|
||||||
|
hoverBackground: "#202036",
|
||||||
highlight: "#afa349",
|
highlight: "#afa349",
|
||||||
highlightHover: "#FCEC61",
|
highlightHover: "#FCEC61",
|
||||||
text: "#846D95",
|
text: "#846D95",
|
||||||
|
Loading…
Reference in New Issue
Block a user