Componentize-ing, styling tweaks, README updates, typos
This commit is contained in:
parent
770f95bd7c
commit
b235b9a211
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## How the demo works
|
||||
|
||||
This demo embeds [Daily Prebuilt](https://www.daily.co/prebuilt), a ready-to-use video chat interface, into a Next.js site. It makes use of [Next API routes](https://nextjs.org/docs/api-routes/introduction) to create a Daily room server-side.
|
||||
This demo embeds [Daily Prebuilt](https://www.daily.co/prebuilt), a ready-to-use video chat interface, into a Next.js site. It makes use of [Next API routes](https://nextjs.org/docs/api-routes/introduction) to create Daily rooms server-side.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
|
@ -15,7 +15,8 @@ You can also paste an existing Daily room into the input. The room URL should be
|
|||
# Running locally
|
||||
1. Copy .env.example and change it to an .env.local with your own DAILY_API_KEY and DAILY_DOMAIN
|
||||
2. `cd basic-embed`
|
||||
3. yarn dev
|
||||
3. yarn
|
||||
4. yarn workspace @prebuilt-ui/basic-embed dev
|
||||
|
||||
Or...
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
import DailyIframe from '@daily-co/daily-js';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { writeText } from 'clipboard-polyfill';
|
||||
import { Button } from '@dailyjs/shared/components/Button';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
} from '@dailyjs/shared/components/Card';
|
||||
import { ExpiryTimer } from '@dailyjs/shared/components/ExpiryTimer';
|
||||
import { TextInput } from '@dailyjs/shared/components/Input';
|
||||
|
||||
const CALL_OPTIONS = {
|
||||
showLeaveButton: true,
|
||||
iframeStyle: {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
aspectRatio: 16 / 9,
|
||||
minwidth: '400px',
|
||||
maxWidth: '920px',
|
||||
border: '0',
|
||||
borderRadius: '12px',
|
||||
},
|
||||
};
|
||||
|
||||
export default function Call({
|
||||
room,
|
||||
setRoom,
|
||||
callFrame,
|
||||
setCallFrame,
|
||||
expiry,
|
||||
}) {
|
||||
const callRef = useRef(null);
|
||||
const [isLinkCopied, setIsLinkCopied] = useState(false);
|
||||
|
||||
const handleCopyClick = useCallback(() => {
|
||||
writeText(room);
|
||||
setIsLinkCopied(true);
|
||||
setTimeout(() => setIsLinkCopied(false), 5000);
|
||||
}, [room, isLinkCopied]);
|
||||
|
||||
const createAndJoinCall = useCallback(() => {
|
||||
const newCallFrame = DailyIframe.createFrame(
|
||||
callRef?.current,
|
||||
CALL_OPTIONS
|
||||
);
|
||||
|
||||
setCallFrame(newCallFrame);
|
||||
|
||||
newCallFrame.join({ url: room });
|
||||
|
||||
const leaveCall = () => {
|
||||
setRoom(null);
|
||||
setCallFrame(null);
|
||||
callFrame.destroy();
|
||||
};
|
||||
|
||||
newCallFrame.on('left-meeting', leaveCall);
|
||||
});
|
||||
|
||||
/**
|
||||
* Initiate Daily iframe creation on component render if it doesn't already exist
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (callFrame) return;
|
||||
|
||||
createAndJoinCall();
|
||||
}, [callFrame, createAndJoinCall]);
|
||||
|
||||
return (
|
||||
<div className="call-container">
|
||||
{/* Daily iframe container */}
|
||||
<div ref={callRef} className="call" />
|
||||
<Card>
|
||||
<CardHeader>Copy and share the URL to invite others</CardHeader>
|
||||
<CardBody>
|
||||
<label htmlFor="copy-url"></label>
|
||||
<TextInput
|
||||
type="text"
|
||||
id="copy-url"
|
||||
placeholder="Copy this room URL"
|
||||
value={room}
|
||||
pattern="^(https:\/\/)?[\w.-]+(\.(daily\.(co)))+[\/\/]+[\w.-]+$"
|
||||
/>
|
||||
<Button onClick={handleCopyClick}>
|
||||
{isLinkCopied ? 'Copied!' : `Copy room URL`}
|
||||
</Button>
|
||||
</CardBody>
|
||||
<CardFooter>{expiry && <ExpiryTimer expiry={expiry} />}</CardFooter>
|
||||
</Card>
|
||||
<style jsx>{`
|
||||
.call-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
.call-container :global(.call) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.call-container :global(.button) {
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.call-container :global(.card) {
|
||||
max-width: 300px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 750px) {
|
||||
.call-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Well } from '@dailyjs/shared/components/Well';
|
||||
import { Button } from '@dailyjs/shared/components/Button';
|
||||
|
||||
export function CreateRoom({ isConfigured, isValidRoom, setRoom, setExpiry }) {
|
||||
const [isError, setIsError] = useState(false);
|
||||
|
||||
/**
|
||||
* Send a request to create a Daily room server-side via Next API routes, then set the returned url in local state to trigger Daily iframe creation in <Call />
|
||||
*/
|
||||
const createRoom = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/room', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const resJson = await res.json();
|
||||
setExpiry(resJson.config?.exp);
|
||||
setRoom(resJson.url);
|
||||
} catch (e) {
|
||||
setIsError(true);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{!isConfigured && (
|
||||
<Well variant="error">
|
||||
You must configure env variables to create rooms (see README
|
||||
instructions).
|
||||
</Well>
|
||||
)}
|
||||
{isError && (
|
||||
<Well variant="error">Error creating the room. Please try again.</Well>
|
||||
)}
|
||||
<Button onClick={createRoom} disabled={isValidRoom}>
|
||||
Create room and start
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
export const Header = () => (
|
||||
<header className="header">
|
||||
<div className="row">
|
||||
<img src="daily-logo.svg" alt="Daily" className="logo" />
|
||||
<div className="capsule">Prebuilt demo</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="capsule">
|
||||
<a href="https://docs.daily.co/docs/">API docs</a>
|
||||
</div>
|
||||
<span className="divider"></span>
|
||||
<a href="https://github.com/daily-demos/examples/tree/main/prebuilt-ui/basic-embed">
|
||||
<img src="github-logo.png" alt="Ocotocat" className="logo octocat" />
|
||||
</a>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.header {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
padding: var(--spacing-sm);
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-right: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.octocat {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: var(--spacing-xxs);
|
||||
}
|
||||
|
||||
.capsule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xxxs);
|
||||
background-color: var(--blue-dark);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: var(--spacing-xxs) var(--spacing-xs);
|
||||
box-sizing: border-box;
|
||||
line-height: 1;
|
||||
font-weight: var(--weight-medium);
|
||||
user-select: none;
|
||||
margin-right: var(--spacing-xxs);
|
||||
color: var(--text-reverse);
|
||||
}
|
||||
|
||||
.capsule a {
|
||||
text-decoration: none;
|
||||
color: var(--text-reverse);
|
||||
}
|
||||
|
||||
.divider {
|
||||
background: var(--gray-light);
|
||||
margin: 0 var(--spacing-xxs);
|
||||
height: 32px;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 750px) {
|
||||
.header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</header>
|
||||
);
|
||||
|
||||
export default Header;
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { Button } from '@dailyjs/shared/components/Button';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
} from '@dailyjs/shared/components/Card';
|
||||
import { CreateRoom } from '../components/CreateRoom';
|
||||
import { Field } from '@dailyjs/shared/components/Field';
|
||||
import { TextInput } from '@dailyjs/shared/components/Input';
|
||||
|
||||
export default function Home({ setRoom, setExpiry, isConfigured }) {
|
||||
const roomRef = useRef(null);
|
||||
const [isValidRoom, setIsValidRoom] = useState(false);
|
||||
|
||||
/**
|
||||
* If the room is valid, setIsValidRoom and enable the join button
|
||||
*/
|
||||
const checkValidity = useCallback(
|
||||
(e) => {
|
||||
if (e?.target?.checkValidity()) {
|
||||
setIsValidRoom(true);
|
||||
}
|
||||
},
|
||||
[isValidRoom]
|
||||
);
|
||||
|
||||
/**
|
||||
* Set the roomUrl in local state to trigger Daily iframe creation in <Call />
|
||||
*/
|
||||
const joinCall = useCallback(() => {
|
||||
const roomUrl = roomRef?.current?.value;
|
||||
setRoom(roomUrl);
|
||||
}, [roomRef]);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
Start demo with a new unique room, or paste in your own room URL
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<CreateRoom
|
||||
isConfigured={isConfigured}
|
||||
isValidRoom={isValidRoom}
|
||||
setRoom={setRoom}
|
||||
setExpiry={setExpiry}
|
||||
/>
|
||||
<Field label="Or enter room to join">
|
||||
<TextInput
|
||||
ref={roomRef}
|
||||
type="text"
|
||||
placeholder="Enter room URL..."
|
||||
pattern="^(https:\/\/)?[\w.-]+(\.(daily\.(co)))+[\/\/]+[\w.-]+$"
|
||||
onChange={checkValidity}
|
||||
/>
|
||||
</Field>
|
||||
<CardFooter>
|
||||
<Button onClick={joinCall} disabled={!isValidRoom}>
|
||||
Join room
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
import DailyIframe from '@daily-co/daily-js';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { writeText } from 'clipboard-polyfill';
|
||||
import { Button } from '@dailyjs/shared/components/Button';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
} from '@dailyjs/shared/components/Card';
|
||||
import Field from '@dailyjs/shared/components/Field';
|
||||
import { TextInput } from '@dailyjs/shared/components/Input';
|
||||
import { Well } from '@dailyjs/shared/components/Well';
|
||||
import { Header } from '../components/Header';
|
||||
|
||||
export default function PrebuiltCall(props) {
|
||||
const [demoState, setDemoState] = useState('home');
|
||||
const [isError, setIsError] = useState(false);
|
||||
const [roomURL, setRoomURL] = useState('');
|
||||
const [exp, setExp] = useState();
|
||||
const [secs, setSecs] = useState();
|
||||
const [roomValidity, setRoomValidity] = useState(false);
|
||||
const roomURLRef = useRef(null);
|
||||
const iframeRef = useRef(null);
|
||||
const callFrame = useRef(null);
|
||||
const [linkCopied, setLinkCopied] = useState(false);
|
||||
|
||||
// Updates the time left that displays in the UI
|
||||
useEffect(() => {
|
||||
if (!exp) {
|
||||
return false;
|
||||
}
|
||||
const i = setInterval(() => {
|
||||
const timeLeft = Math.floor((new Date(exp * 1000) - Date.now()) / 1000);
|
||||
setSecs(`${Math.floor(timeLeft / 60)}:${`0${timeLeft % 60}`.slice(-2)}`);
|
||||
}, 1000);
|
||||
return () => clearInterval(i);
|
||||
}, [exp]);
|
||||
|
||||
// Listens for a "call" demo state, and creates then joins a callFrame as soon as that happens
|
||||
useEffect(() => {
|
||||
if (!iframeRef?.current || demoState !== 'call') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
callFrame.current = DailyIframe.createFrame(iframeRef.current, {
|
||||
showLeaveButton: true,
|
||||
iframeStyle: {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
aspectRatio: 16 / 9,
|
||||
minwidth: '400px',
|
||||
maxWidth: '920px',
|
||||
border: '0',
|
||||
borderRadius: '12px',
|
||||
},
|
||||
});
|
||||
callFrame.current.join({
|
||||
url: roomURL,
|
||||
});
|
||||
} catch (e) {
|
||||
setDemoState('home');
|
||||
setIsError(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const handleLeftMeeting = () => {
|
||||
setDemoState('home');
|
||||
setRoomValidity(false);
|
||||
callFrame.current.destroy();
|
||||
};
|
||||
|
||||
callFrame.current.on('left-meeting', handleLeftMeeting);
|
||||
}, [demoState, iframeRef, roomURL]);
|
||||
|
||||
const createRoom = useCallback(
|
||||
async (e) => {
|
||||
if (!roomURLRef?.current.value) {
|
||||
const roomExp = Math.round(Date.now() / 1000) + 60 * 5;
|
||||
try {
|
||||
const res = await fetch('/api/room', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
properties: {
|
||||
roomExp,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const roomJson = await res.json();
|
||||
const { url } = roomJson;
|
||||
setRoomURL(url);
|
||||
setDemoState('call');
|
||||
setExp(roomExp);
|
||||
setIsError(false);
|
||||
} catch (e) {
|
||||
setDemoState('home');
|
||||
setIsError(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
[roomURL, exp]
|
||||
);
|
||||
|
||||
// Updates state if a room is provided
|
||||
const handleRoomInput = useCallback(
|
||||
(e) => {
|
||||
setRoomURL(e?.target?.value);
|
||||
if (e?.target?.checkValidity()) {
|
||||
setRoomValidity(true);
|
||||
console.log(roomValidity);
|
||||
}
|
||||
},
|
||||
[roomValidity]
|
||||
);
|
||||
|
||||
const submitJoinRoom = useCallback(() => {
|
||||
setDemoState('call');
|
||||
});
|
||||
|
||||
const handleCopyClick = useCallback(() => {
|
||||
console.log('click');
|
||||
writeText(roomURL);
|
||||
setLinkCopied(true);
|
||||
setTimeout(() => setLinkCopied(false), 5000);
|
||||
}, [roomURL, linkCopied]);
|
||||
|
||||
const content = useMemo(() => {
|
||||
switch (demoState) {
|
||||
case 'home':
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
Start demo with a new unique room, or paste in your own room URL.
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
{!props.configured && (
|
||||
<Well variant="error">
|
||||
You must configure env variables to create rooms (see README
|
||||
instructions).
|
||||
</Well>
|
||||
)}
|
||||
{isError && (
|
||||
<Well variant="error">
|
||||
Error creating the room. Please try again.
|
||||
</Well>
|
||||
)}
|
||||
<Button onClick={() => createRoom()} disabled={roomValidity}>
|
||||
Create room and start
|
||||
</Button>
|
||||
<Field label="Or enter room to join" className="roomField">
|
||||
<TextInput
|
||||
ref={roomURLRef}
|
||||
type="text"
|
||||
placeholder="Enter room URL..."
|
||||
pattern="^(https:\/\/)?[\w.-]+(\.(daily\.(co)))+[\/\/]+[\w.-]+$"
|
||||
onChange={handleRoomInput}
|
||||
/>
|
||||
</Field>
|
||||
<CardFooter>
|
||||
<Button
|
||||
onClick={() => submitJoinRoom()}
|
||||
disabled={!roomValidity}
|
||||
>
|
||||
Join room
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
case 'call':
|
||||
return (
|
||||
<>
|
||||
<div ref={iframeRef} className="call" />
|
||||
<Card>
|
||||
<CardHeader>Copy and share the URL to invite others.</CardHeader>
|
||||
<CardBody>
|
||||
<label for="copy-url"></label>
|
||||
<TextInput
|
||||
type="text"
|
||||
class="url-input"
|
||||
id="copy-url"
|
||||
placeholder="Copy this room URL"
|
||||
value={roomURL}
|
||||
pattern="^(https:\/\/)?[\w.-]+(\.(daily\.(co)))+[\/\/]+[\w.-]+$"
|
||||
/>
|
||||
<Button onClick={handleCopyClick}>
|
||||
{linkCopied ? 'Copied!' : `Copy room URL`}
|
||||
</Button>
|
||||
{exp && (
|
||||
<h3>
|
||||
This room expires in:{' '}
|
||||
<span className="countdown">{secs}</span>
|
||||
</h3>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}, [demoState, roomValidity, roomURLRef, exp, secs]);
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<Header />
|
||||
{content}
|
||||
<style jsx>{`
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.container :global(.call) {
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
.container :global(.countdown) {
|
||||
padding: 4px 0;
|
||||
font-size: 1rem;
|
||||
font-weight: var(--weight-medium);
|
||||
border-radius: 0 0 0 var(--radius-sm);
|
||||
color: var(--blue-dark);
|
||||
}
|
||||
|
||||
:global(.field) {
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
:global(.card) {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 750px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@
|
|||
*/
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { roomExp } = req.body.properties;
|
||||
|
||||
if (req.method === 'POST') {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
|
|
@ -19,7 +17,7 @@ export default async function handler(req, res) {
|
|||
enable_network_ui: true,
|
||||
enable_screenshare: true,
|
||||
enable_chat: true,
|
||||
exp: roomExp,
|
||||
exp: Math.round(Date.now() / 1000) + 5 * 60,
|
||||
eject_at_room_exp: true,
|
||||
},
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,17 +1,64 @@
|
|||
import PrebuiltCall from '../components/PrebuiltCall';
|
||||
// import PrebuiltCall from '../components/PrebuiltCall';
|
||||
import React, { useState } from 'react';
|
||||
import Call from '../components/Call';
|
||||
import Home from '../components/Home';
|
||||
import Header from '@dailyjs/shared/components/Header';
|
||||
import getDemoProps from '@dailyjs/shared/lib/demoProps';
|
||||
|
||||
export default function Index({ isConfigured = false }) {
|
||||
const [room, setRoom] = useState(null);
|
||||
const [expiry, setExpiry] = useState(null);
|
||||
const [callFrame, setCallFrame] = useState(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PrebuiltCall configured={isConfigured} />
|
||||
<div className="index-container">
|
||||
<Header
|
||||
demoTitle={'Daily Prebuilt demo'}
|
||||
repoLink={
|
||||
'https://github.com/daily-demos/examples/tree/main/prebuilt-ui/basic-embed'
|
||||
}
|
||||
/>
|
||||
<main>
|
||||
{room ? (
|
||||
<Call
|
||||
room={room}
|
||||
expiry={expiry}
|
||||
setRoom={setRoom}
|
||||
setCallFrame={setCallFrame}
|
||||
callFrame={callFrame}
|
||||
/>
|
||||
) : (
|
||||
<Home
|
||||
setRoom={setRoom}
|
||||
setExpiry={setExpiry}
|
||||
isConfigured={isConfigured}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
<style jsx>{`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
.index-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
overflow: auto;
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 2rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:global(.field) {
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
:global(.card) {
|
||||
margin: 8px;
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue