embed screen style adjustments (#4063)
* restyle and relayout embed screen to account for smaller screen displays. - address https://github.com/owncast/owncast/issues/3683 to address overflow issues - address https://github.com/owncast/owncast/issues/4051 to move the name of the stream * Javascript formatting autofixes * clean up; restore package lock * accommodate cases when there's no follow option; put follow form on one line, but wrap if need * clean up * separate out follow form into separate standalone component to be used in multiple places * improve follow error styling; rm defaultProps for Modal to get rid of warning * improve styling of follow form and components for legibility * prettyify scss * prettyify scss again * one more time * prettify ant file * simplify layout, center everything * just use gap * tweak and lint * lint, again --------- Co-authored-by: Owncast <owncast@owncast.online>
This commit is contained in:
parent
4b1a89bb31
commit
87c7571d5c
103
web/components/modals/FollowModal/FollowForm.tsx
Normal file
103
web/components/modals/FollowModal/FollowForm.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/* eslint-disable react/no-unescaped-entities */
|
||||||
|
import { Input, Button, Alert, Spin, Space } from 'antd';
|
||||||
|
import { FC, useState } from 'react';
|
||||||
|
import styles from './FollowModal.module.scss';
|
||||||
|
import { isValidFediverseAccount } from '../../../utils/validators';
|
||||||
|
|
||||||
|
const ENDPOINT = '/api/remotefollow';
|
||||||
|
|
||||||
|
export type FollowFormProps = {
|
||||||
|
handleClose?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FollowForm: FC<FollowFormProps> = ({ handleClose }: FollowFormProps) => {
|
||||||
|
const [remoteAccount, setRemoteAccount] = useState(null);
|
||||||
|
const [valid, setValid] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [errorMessage, setErrorMessage] = useState(null);
|
||||||
|
|
||||||
|
const handleAccountChange = a => {
|
||||||
|
setRemoteAccount(a);
|
||||||
|
if (isValidFediverseAccount(a)) {
|
||||||
|
setValid(true);
|
||||||
|
} else {
|
||||||
|
setValid(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const joinButtonPressed = () => {
|
||||||
|
window.open('https://owncast.online/join-fediverse', '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
const remoteFollowButtonPressed = async () => {
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sanitizedAccount = remoteAccount.replace(/^@+/, '');
|
||||||
|
const request = { account: sanitizedAccount };
|
||||||
|
const rawResponse = await fetch(ENDPOINT, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
});
|
||||||
|
const result = await rawResponse.json();
|
||||||
|
|
||||||
|
if (result.redirectUrl) {
|
||||||
|
window.open(result.redirectUrl, '_blank');
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
if (!result.success) {
|
||||||
|
setErrorMessage(result.message);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!result.redirectUrl) {
|
||||||
|
setErrorMessage('Unable to follow.');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setErrorMessage(e.message);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
{errorMessage && (
|
||||||
|
<Alert
|
||||||
|
message="Follow Error"
|
||||||
|
description={errorMessage}
|
||||||
|
type="error"
|
||||||
|
closable
|
||||||
|
className={styles.errorAlert}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.inputContainer}>
|
||||||
|
<div className={styles.instructions}>Enter your username @server to follow</div>
|
||||||
|
<Input
|
||||||
|
value={remoteAccount}
|
||||||
|
size="large"
|
||||||
|
onChange={e => handleAccountChange(e.target.value)}
|
||||||
|
placeholder="Your fediverse account @account@server"
|
||||||
|
defaultValue={remoteAccount}
|
||||||
|
/>
|
||||||
|
<div className={styles.footer}>
|
||||||
|
You'll be redirected to your Fediverse server and asked to confirm the action.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Space className={styles.buttons}>
|
||||||
|
<Button onClick={joinButtonPressed} type="text">
|
||||||
|
Join the Fediverse
|
||||||
|
</Button>
|
||||||
|
<Button disabled={!valid} type="primary" onClick={remoteFollowButtonPressed}>
|
||||||
|
Follow
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Spin>
|
||||||
|
);
|
||||||
|
};
|
@ -9,16 +9,21 @@
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.instructions {
|
.inputContainer {
|
||||||
font-family: var(--theme-text-display-font-family);
|
font-family: var(--theme-text-display-font-family);
|
||||||
font-size: 0.7rem;
|
margin-bottom: 10px;
|
||||||
font-weight: 600;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
.instructions {
|
||||||
font-size: 0.5rem;
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 5px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
font-size: 10px;
|
||||||
|
margin: 2px;
|
||||||
|
color: var(--theme-color-components-primary-button-text-disabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.account {
|
.account {
|
||||||
@ -50,3 +55,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.errorAlert {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
font-family: var(--theme-text-display-font-family);
|
||||||
|
|
||||||
|
:global(.ant-alert-message) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-alert-description) {
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
/* eslint-disable react/no-unescaped-entities */
|
/* eslint-disable react/no-unescaped-entities */
|
||||||
import { Input, Button, Alert, Spin, Space } from 'antd';
|
import { Space } from 'antd';
|
||||||
import { FC, useState } from 'react';
|
import { FC } from 'react';
|
||||||
import styles from './FollowModal.module.scss';
|
import styles from './FollowModal.module.scss';
|
||||||
import { isValidFediverseAccount } from '../../../utils/validators';
|
import { FollowForm } from './FollowForm';
|
||||||
|
|
||||||
const ENDPOINT = '/api/remotefollow';
|
|
||||||
|
|
||||||
export type FollowModalProps = {
|
export type FollowModalProps = {
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
@ -12,106 +10,24 @@ export type FollowModalProps = {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FollowModal: FC<FollowModalProps> = ({ handleClose, account, name }) => {
|
export const FollowModal: FC<FollowModalProps> = ({ handleClose, account, name }) => (
|
||||||
const [remoteAccount, setRemoteAccount] = useState(null);
|
<Space direction="vertical" id="follow-modal">
|
||||||
const [valid, setValid] = useState(false);
|
<div className={styles.header}>
|
||||||
const [loading, setLoading] = useState(false);
|
By following this stream you'll get notified on the Fediverse when it goes live. Now is a
|
||||||
const [errorMessage, setErrorMessage] = useState(null);
|
great time to
|
||||||
|
<a href="https://owncast.online/join-fediverse" target="_blank" rel="noreferrer">
|
||||||
const handleAccountChange = a => {
|
learn about the Fediverse
|
||||||
setRemoteAccount(a);
|
</a>
|
||||||
if (isValidFediverseAccount(a)) {
|
if it's new to you.
|
||||||
setValid(true);
|
</div>
|
||||||
} else {
|
<div className={styles.account}>
|
||||||
setValid(false);
|
<img src="/logo" alt="logo" className={styles.logo} />
|
||||||
}
|
<div className={styles.username}>
|
||||||
};
|
<div className={styles.name}>{name}</div>
|
||||||
|
<div>{account}</div>
|
||||||
const joinButtonPressed = () => {
|
|
||||||
window.open('https://owncast.online/join-fediverse', '_blank');
|
|
||||||
};
|
|
||||||
|
|
||||||
const remoteFollowButtonPressed = async () => {
|
|
||||||
if (!valid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const sanitizedAccount = remoteAccount.replace(/^@+/, '');
|
|
||||||
const request = { account: sanitizedAccount };
|
|
||||||
const rawResponse = await fetch(ENDPOINT, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
});
|
|
||||||
const result = await rawResponse.json();
|
|
||||||
|
|
||||||
if (result.redirectUrl) {
|
|
||||||
window.open(result.redirectUrl, '_blank');
|
|
||||||
handleClose();
|
|
||||||
}
|
|
||||||
if (!result.success) {
|
|
||||||
setErrorMessage(result.message);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!result.redirectUrl) {
|
|
||||||
setErrorMessage('Unable to follow.');
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setErrorMessage(e.message);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Space direction="vertical" id="follow-modal">
|
|
||||||
<div className={styles.header}>
|
|
||||||
By following this stream you'll get notified on the Fediverse when it goes live. Now is a
|
|
||||||
great time to
|
|
||||||
<a href="https://owncast.online/join-fediverse" target="_blank" rel="noreferrer">
|
|
||||||
learn about the Fediverse
|
|
||||||
</a>
|
|
||||||
if it's new to you.
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Spin spinning={loading}>
|
<FollowForm handleClose={handleClose} />
|
||||||
{errorMessage && (
|
</Space>
|
||||||
<Alert message="Follow Error" description={errorMessage} type="error" showIcon />
|
);
|
||||||
)}
|
|
||||||
<div className={styles.account}>
|
|
||||||
<img src="/logo" alt="logo" className={styles.logo} />
|
|
||||||
<div className={styles.username}>
|
|
||||||
<div className={styles.name}>{name}</div>
|
|
||||||
<div>{account}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className={styles.instructions}>Enter your username @server to follow</div>
|
|
||||||
<Input
|
|
||||||
value={remoteAccount}
|
|
||||||
size="large"
|
|
||||||
onChange={e => handleAccountChange(e.target.value)}
|
|
||||||
placeholder="Your fediverse account @account@server"
|
|
||||||
defaultValue={remoteAccount}
|
|
||||||
/>
|
|
||||||
<div className={styles.footer}>
|
|
||||||
You'll be redirected to your Fediverse server and asked to confirm the action.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Space className={styles.buttons}>
|
|
||||||
<Button disabled={!valid} type="primary" onClick={remoteFollowButtonPressed}>
|
|
||||||
Follow
|
|
||||||
</Button>
|
|
||||||
<Button onClick={joinButtonPressed} type="primary">
|
|
||||||
Join the Fediverse
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Spin>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -95,11 +95,3 @@ export const Modal: FC<ModalProps> = ({
|
|||||||
</AntModal>
|
</AntModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Modal.defaultProps = {
|
|
||||||
url: undefined,
|
|
||||||
children: undefined,
|
|
||||||
handleOk: undefined,
|
|
||||||
handleCancel: undefined,
|
|
||||||
afterClose: undefined,
|
|
||||||
};
|
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
@import '../../../styles/mixins';
|
@import '../../../styles/mixins';
|
||||||
|
|
||||||
|
$short-container-max-height: 480px;
|
||||||
|
$short-container-min-height: 320px;
|
||||||
|
$follow-modal-width: 300px;
|
||||||
|
|
||||||
.offlineContainer {
|
.offlineContainer {
|
||||||
|
--text-color: rgb(255 255 255 / 100%);
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -13,56 +19,108 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
padding: 24px;
|
font-size: 16px;
|
||||||
|
|
||||||
|
@include screen(mobile) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (height <= $short-container-max-height) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Content */
|
/* Content */
|
||||||
.content {
|
.content {
|
||||||
|
color: var(--text-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 2rem;
|
||||||
gap: 8px;
|
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
max-width: 1024px;
|
||||||
|
max-height: 576px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.headerContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageContainer {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
--description-height: auto;
|
||||||
|
|
||||||
|
@media (height <= $short-container-min-height) {
|
||||||
|
--description-height: 30vh;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
min-height: var(--description-height);
|
||||||
|
|
||||||
|
--gradient-mask: linear-gradient(to top, rgb(0 0 0 / 0%), rgb(0 0 0 / 100%) 17%);
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: var(--description-height);
|
||||||
|
mask-image: var(--gradient-mask);
|
||||||
|
mask-repeat: repeat-x;
|
||||||
|
mask-size: auto var(--description-height);
|
||||||
|
mask-position: top;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Message */
|
/* Message */
|
||||||
.message {
|
.message {
|
||||||
color: rgb(255 255 255 / 100%);
|
color: var(--text-color);
|
||||||
font-family: var(--theme-text-body-font-family);
|
font-family: var(--theme-text-body-font-family);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 16px;
|
font-size: 1em;
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.375;
|
line-height: 1.375;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
@include screen(desktop) {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Heading */
|
/* Heading */
|
||||||
.heading {
|
.offlineTitle {
|
||||||
color: rgb(255 255 255 / 100%);
|
color: var(--text-color);
|
||||||
font-family: var(--theme-text-display-font-family);
|
font-family: var(--theme-text-display-font-family);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 24px;
|
font-size: 1.375em;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
line-height: 1.125;
|
line-height: 1.125;
|
||||||
letter-spacing: -0.125px;
|
letter-spacing: -0.125px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Page Logo */
|
/* Page Logo */
|
||||||
.pageLogo {
|
.pageLogo {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 10vw;
|
min-height: 3em;
|
||||||
height: 10vw;
|
min-width: 3em;
|
||||||
min-height: 64px;
|
|
||||||
min-width: 64px;
|
|
||||||
max-height: 100px;
|
max-height: 100px;
|
||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
border-radius: 96px;
|
border-radius: 96px;
|
||||||
background-color: rgb(255 255 255 / 100%);
|
background-color: rgb(255 255 255 / 100%);
|
||||||
border: 5px solid rgb(18 22 29 / 100%);
|
border: 2px solid rgb(18 22 29 / 100%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -73,26 +131,26 @@
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Page Name */
|
/* Stream Name */
|
||||||
.pageName {
|
.streamName {
|
||||||
color: rgb(255 255 255 / 100%);
|
color: var(--text-color);
|
||||||
font-family: var(--theme-text-display-font-family);
|
font-family: var(--theme-text-display-font-family);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 20px;
|
font-size: 1.25em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.1875;
|
line-height: 1.1875;
|
||||||
letter-spacing: -0.0625px;
|
letter-spacing: -0.0625px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.followButton {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media (width > $follow-modal-width) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.submitButton {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
color: white;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { Button, Input, Space, Spin, Alert, Typography } from 'antd';
|
import { Button, Typography } from 'antd';
|
||||||
import styles from './OfflineEmbed.module.scss';
|
import styles from './OfflineEmbed.module.scss';
|
||||||
import { isValidFediverseAccount } from '../../../utils/validators';
|
import { Modal } from '../Modal/Modal';
|
||||||
|
import { FollowForm } from '../../modals/FollowModal/FollowForm';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
const ENDPOINT = '/api/remotefollow';
|
|
||||||
|
|
||||||
export type OfflineEmbedProps = {
|
export type OfflineEmbedProps = {
|
||||||
streamName: string;
|
streamName: string;
|
||||||
@ -20,8 +20,6 @@ export type OfflineEmbedProps = {
|
|||||||
enum EmbedMode {
|
enum EmbedMode {
|
||||||
CannotFollow = 1,
|
CannotFollow = 1,
|
||||||
CanFollow,
|
CanFollow,
|
||||||
FollowPrompt,
|
|
||||||
InProgress,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OfflineEmbed: FC<OfflineEmbedProps> = ({
|
export const OfflineEmbed: FC<OfflineEmbedProps> = ({
|
||||||
@ -31,10 +29,7 @@ export const OfflineEmbed: FC<OfflineEmbedProps> = ({
|
|||||||
supportsFollows,
|
supportsFollows,
|
||||||
}) => {
|
}) => {
|
||||||
const [currentMode, setCurrentMode] = useState(EmbedMode.CanFollow);
|
const [currentMode, setCurrentMode] = useState(EmbedMode.CanFollow);
|
||||||
const [remoteAccount, setRemoteAccount] = useState(null);
|
const [showFollowModal, setShowFollowModal] = useState(false);
|
||||||
const [valid, setValid] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [errorMessage, setErrorMessage] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!supportsFollows) {
|
if (!supportsFollows) {
|
||||||
@ -45,53 +40,7 @@ export const OfflineEmbed: FC<OfflineEmbedProps> = ({
|
|||||||
}, [supportsFollows]);
|
}, [supportsFollows]);
|
||||||
|
|
||||||
const followButtonPressed = async () => {
|
const followButtonPressed = async () => {
|
||||||
setCurrentMode(EmbedMode.FollowPrompt);
|
setShowFollowModal(true);
|
||||||
};
|
|
||||||
|
|
||||||
const remoteFollowButtonPressed = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
setCurrentMode(EmbedMode.CannotFollow);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const sanitizedAccount = remoteAccount.replace(/^@+/, '');
|
|
||||||
const request = { account: sanitizedAccount };
|
|
||||||
const rawResponse = await fetch(ENDPOINT, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
});
|
|
||||||
const result = await rawResponse.json();
|
|
||||||
|
|
||||||
if (result.redirectUrl) {
|
|
||||||
window.open(result.redirectUrl, '_blank');
|
|
||||||
}
|
|
||||||
if (!result.success) {
|
|
||||||
setErrorMessage(result.message);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!result.redirectUrl) {
|
|
||||||
setErrorMessage('Unable to follow.');
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setErrorMessage(e.message);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleErrorClose = () => {
|
|
||||||
setErrorMessage('');
|
|
||||||
setCurrentMode(EmbedMode.FollowPrompt);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAccountChange = a => {
|
|
||||||
setRemoteAccount(a);
|
|
||||||
if (isValidFediverseAccount(a)) {
|
|
||||||
setValid(true);
|
|
||||||
} else {
|
|
||||||
setValid(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -100,64 +49,34 @@ export const OfflineEmbed: FC<OfflineEmbedProps> = ({
|
|||||||
<title>{streamName}</title>
|
<title>{streamName}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className={classNames(styles.offlineContainer)}>
|
<div className={classNames(styles.offlineContainer)}>
|
||||||
<Spin spinning={loading}>
|
<div className={styles.content}>
|
||||||
<div className={styles.content}>
|
<Title level={1} className={styles.headerContainer}>
|
||||||
<div className={styles.heading}>This stream is not currently live.</div>
|
|
||||||
<div className={styles.message} dangerouslySetInnerHTML={{ __html: subtitle }} />
|
|
||||||
|
|
||||||
<div className={styles.pageLogo} style={{ backgroundImage: `url(${image})` }} />
|
<div className={styles.pageLogo} style={{ backgroundImage: `url(${image})` }} />
|
||||||
<div className={styles.pageName}>{streamName}</div>
|
<div className={styles.streamName}>{streamName}</div>
|
||||||
|
</Title>
|
||||||
|
|
||||||
{errorMessage && (
|
<div className={styles.messageContainer}>
|
||||||
<Alert
|
<Title level={2} className={styles.offlineTitle}>
|
||||||
message="Follow Error"
|
This stream is not currently live.
|
||||||
description={errorMessage}
|
</Title>
|
||||||
type="error"
|
<div className={styles.message} dangerouslySetInnerHTML={{ __html: subtitle }} />
|
||||||
showIcon
|
</div>
|
||||||
closable
|
|
||||||
onClose={handleErrorClose}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentMode === EmbedMode.CanFollow && (
|
{currentMode === EmbedMode.CanFollow && (
|
||||||
<Button className={styles.submitButton} type="primary" onClick={followButtonPressed}>
|
<>
|
||||||
|
<Button className={styles.followButton} type="primary" onClick={followButtonPressed}>
|
||||||
Follow Server
|
Follow Server
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
<Modal
|
||||||
|
title={`Follow ${streamName}`}
|
||||||
{currentMode === EmbedMode.InProgress && (
|
open={showFollowModal}
|
||||||
<Title level={4} className={styles.heading}>
|
handleCancel={() => setShowFollowModal(false)}
|
||||||
Follow the instructions on your Fediverse server to complete the follow.
|
>
|
||||||
</Title>
|
<FollowForm />
|
||||||
)}
|
</Modal>
|
||||||
|
</>
|
||||||
{currentMode === EmbedMode.FollowPrompt && (
|
)}
|
||||||
<div>
|
</div>
|
||||||
<Input
|
|
||||||
value={remoteAccount}
|
|
||||||
size="large"
|
|
||||||
onChange={e => handleAccountChange(e.target.value)}
|
|
||||||
placeholder="Your fediverse account @account@server"
|
|
||||||
defaultValue={remoteAccount}
|
|
||||||
/>
|
|
||||||
<div className={styles.footer}>
|
|
||||||
You'll be redirected to your Fediverse server and asked to confirm the
|
|
||||||
action.
|
|
||||||
</div>
|
|
||||||
<Space className={styles.buttons}>
|
|
||||||
<Button
|
|
||||||
className={styles.submitButton}
|
|
||||||
disabled={!valid}
|
|
||||||
type="primary"
|
|
||||||
onClick={remoteFollowButtonPressed}
|
|
||||||
>
|
|
||||||
Submit and Follow
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Spin>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,10 @@ HEADER
|
|||||||
BUTTONS
|
BUTTONS
|
||||||
// ------------------------- */
|
// ------------------------- */
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-btn[disabled] {
|
.ant-btn[disabled] {
|
||||||
background-color: var(--theme-color-components-secondary-button-background-disabled);
|
background-color: var(--theme-color-components-secondary-button-background-disabled);
|
||||||
color: var(--theme-color-components-secondary-button-text-disabled);
|
color: var(--theme-color-components-secondary-button-text-disabled);
|
||||||
@ -40,8 +44,6 @@ BUTTONS
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn-primary {
|
.ant-btn-primary {
|
||||||
height: 2rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
border-radius: var(--theme-rounded-corners);
|
border-radius: var(--theme-rounded-corners);
|
||||||
border-color: var(--theme-color-components-primary-button-border);
|
border-color: var(--theme-color-components-primary-button-border);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user