Onboarding screen (not working) added

This commit is contained in:
Fergal Moran
2022-04-11 19:45:08 +01:00
parent 565f00c8d3
commit 81991a22b6
17 changed files with 199 additions and 44 deletions

View File

@@ -8,6 +8,7 @@
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.2.1",
"autoprefixer": "^10.4.4",
"axios": "^0.26.1",
"classnames": "^2.3.1",
"hls.js": "^1.1.5",
"postcss": "^8.4.12",

View File

@@ -1,14 +1,17 @@
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { ToastContainer } from "react-toastify";
import { Layout } from "./containers";
import { OnboardingPage } from "./pages";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/*" element={<Layout />} />
{localStorage.getItem("server") ? (
<Route path="/*" element={<OnboardingPage />} />
) : (
<Route path="/*" element={<Layout />} />
)}
</Routes>
</BrowserRouter>
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -1,5 +1,4 @@
import React from "react";
interface IEPGComponentProps {
channelId: string;
}

View File

@@ -50,7 +50,7 @@ const Header = () => {
<BsSearch className="w-4 h-4" aria-hidden="true" />
</div>
<Input
className="h-8 pl-8 text-gray-700"
className="pl-8 text-gray-700"
placeholder="Search for channels"
aria-label="Search"
/>

View File

@@ -1,16 +1,18 @@
import React from "react";
import { NavLink, Route } from "react-router-dom";
import { Channel } from "../../models/channel";
import { ApiService } from "../../services";
const SidebarContent = () => {
const [channels, setChannels] = React.useState<Channel[]>([]);
const [filteredChannels, setFilteredChannels] = React.useState<Channel[]>([]);
React.useEffect(() => {
const fetchChannels = async () => {
const res = await fetch(`${process.env.REACT_APP_API_URL}/channels`);
const data = await res.json();
setChannels(data);
setFilteredChannels(data);
const res = await ApiService.getChannels();
if (res) {
setChannels(res);
setFilteredChannels(res);
}
};
fetchChannels().catch(console.error);

View File

@@ -3,5 +3,6 @@ import Badge from "./badge.component";
import Button from "./button.component";
import { Dropdown, DropdownItem } from "./dropdown.component";
import Input from "./input.component";
import Label from "./label.component";
export { Avatar, Badge, Button, Input, Dropdown, DropdownItem };
export { Avatar, Badge, Button, Input, Dropdown, DropdownItem, Label };

View File

@@ -0,0 +1,36 @@
import React from "react";
import classNames from "classnames";
import { defaultTheme } from "../../constants";
export interface LabelProps extends React.HTMLAttributes<HTMLLabelElement> {
check?: boolean;
radio?: boolean;
disabled?: boolean;
}
const Label = React.forwardRef<HTMLLabelElement, LabelProps>(function Label(
props,
ref
) {
const { children, check, radio, disabled, className, ...other } = props;
const { label } = defaultTheme;
const baseStyle = label.base;
const checkStyle = label.check;
const disabledStyle = label.disabled;
const cls = classNames(
baseStyle,
// check and radio are interchangeable
(check || radio) && checkStyle,
disabled && disabledStyle,
className
);
return (
<label className={cls} ref={ref} {...other}>
{children}
</label>
);
});
export default Label;

View File

@@ -130,7 +130,7 @@ const defaultTheme = {
},
// Input
input: {
base: "block w-full text-sm focus:outline-none dark:text-gray-300 leading-5 rounded-md",
base: "block w-full text-sm focus:outline-none dark:text-gray-300 leading-5 rounded-md h-8 p-2",
active:
"focus:border-purple-400 border-gray-300 dark:border-gray-600 focus:ring focus:ring-purple-300 dark:focus:border-gray-600 dark:focus:ring-gray-300 dark:bg-gray-700",
disabled: "cursor-not-allowed opacity-50 bg-gray-300 dark:bg-gray-800",

View File

@@ -11,8 +11,16 @@ import {
useMedia,
} from "../utils/chromecast";
import { toast } from "react-toastify";
import { Table, TableBody, TableCell, TableContainer, TableHeader, TableRow } from "../components/widgets/table";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHeader,
TableRow,
} from "../components/widgets/table";
import { Avatar, Badge, Button } from "../components/widgets";
import { ApiService } from "../services";
const ChannelPage = () => {
let params = useParams();
@@ -24,31 +32,20 @@ const ChannelPage = () => {
React.useEffect(() => {
const fetchChannels = async () => {
const res = await fetch(
`${process.env.REACT_APP_API_URL}/streams/${params.channelId}`
);
const data = await res.json();
setStreams(data);
if (params.channelId) {
const data = await ApiService.getStreams(params.channelId);
setStreams(data);
}
};
fetchChannels().catch(console.error);
}, [params.channelId]);
const _getStreamUrl = async (streamId: number) => {
const res = await fetch(
`${process.env.REACT_APP_API_URL}/live/stream/url/${streamId}`
);
if (res.status !== 200) {
alert("Failed to get stream url");
return;
}
const data = await res.json();
return data?.url;
};
const _cast = React.useCallback(
async (streamId: number) => {
const streamUrl = await _getStreamUrl(streamId);
const streamUrl = await ApiService.getStreamUrl(streamId);
if (streamUrl) {
await media.playMedia(streamUrl);
}
@@ -60,7 +57,7 @@ const ChannelPage = () => {
console.log("channel.page", "handleXHR", args);
};
const playStream = async (streamId: number) => {
const url = await _getStreamUrl(streamId);
const url = await ApiService.getStreamUrl(streamId);
if (url) {
const mpv_args =
"--keep-open=yes\n--geometry=1024x768-0-0\n--ontop\n--screen=2\n--ytdl-format=bestvideo[ext=mp4][height<=?720]+bestaudio[ext=m4a]\n--border=no".split(

View File

@@ -1,5 +1,6 @@
import ChannelPage from "./channel.page";
import HomePage from "./home.page";
import OnboardingPage from "./onboarding.page";
import PlayerPage from "./player.page";
export { HomePage, ChannelPage, PlayerPage };
export { HomePage, ChannelPage, PlayerPage, OnboardingPage };

View File

@@ -0,0 +1,60 @@
import React from "react";
import ImageLight from "../assets/images/love-tv.jpg";
import { Button, Input, Label } from "../components/widgets";
const OnboardingPage = () => {
const _setupOnboarding = () => {};
return (
<div className="flex items-center min-h-screen p-6 bg-gray-50 dark:bg-gray-900">
<div className="flex-1 h-full max-w-4xl mx-auto overflow-hidden bg-white rounded-lg shadow-xl dark:bg-gray-800">
<div className="flex flex-col overflow-y-auto md:flex-row">
<div className="h-32 md:h-auto md:w-1/2">
<img
aria-hidden="true"
className="object-cover w-full h-full dark:hidden"
src={ImageLight}
alt="Office"
/>
<img
aria-hidden="true"
className="hidden object-cover w-full h-full dark:block"
src={ImageLight}
alt="Office"
/>
</div>
<main className="flex items-center justify-center p-6 sm:p-12 md:w-1/2">
<div className="w-full">
<h1 className="mb-4 text-xl font-semibold text-gray-700 dark:text-gray-200">
Login
</h1>
<Label>
<span>Email</span>
<Input
className="mt-1"
type="email"
placeholder="john@doe.com"
/>
</Label>
<Label className="mt-4">
<span>Password</span>
<Input
className="mt-1"
type="password"
placeholder="***************"
/>
</Label>
<Button className="mt-4" block onClick={_setupOnboarding}>
Log in
</Button>
<hr className="my-8" />
</div>
</main>
</div>
</div>
</div>
);
};
export default OnboardingPage;

View File

@@ -0,0 +1,28 @@
import http from "./http.service";
import { Channel } from "../models/channel";
import { Stream } from "../models/stream";
class ApiService {
public getChannels = async (): Promise<Channel[]> => {
const response = await http.get("/channels");
return response.data;
};
public getStreams = async (channelId: string): Promise<Stream[]> => {
const response = await http.get(`/streams/${channelId}`);
return response.data;
};
public getStreamUrl = async (
streamId: number
): Promise<string | undefined> => {
const res = await http.get(`/live/stream/url/${streamId}`);
if (res.status !== 200) {
alert("Failed to get stream url");
return;
}
return res?.data.url;
};
}
export default new ApiService();

View File

@@ -0,0 +1,16 @@
import axios from "axios";
export default axios.create({
baseURL: process.env.REACT_APP_API_URL,
headers: {
"Content-type": "application/json",
"x-xtream-server": JSON.parse(localStorage.getItem("server") || "{}")[
"server"
],
"x-xtream-username": JSON.parse(localStorage.getItem("server") || "{}")[
"username"
],
"x-xtream-password": JSON.parse(localStorage.getItem("server") || "{}")[
"password"
],
},
});

View File

@@ -0,0 +1,3 @@
import ApiService from "./api.service";
export { ApiService };

View File

@@ -2495,6 +2495,13 @@ axe-core@^4.3.5:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413"
integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
axios@^0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
dependencies:
follow-redirects "^1.14.8"
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -4300,7 +4307,7 @@ focus-lock@^0.10.2:
dependencies:
tslib "^2.0.3"
follow-redirects@^1.0.0:
follow-redirects@^1.0.0, follow-redirects@^1.14.8:
version "1.14.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==