manually merge in Phil's code

This commit is contained in:
mantikoros 2022-08-26 11:35:32 -05:00
parent d710a9b669
commit 050c80609b
4 changed files with 168 additions and 3 deletions

View File

@ -0,0 +1,122 @@
import clsx from 'clsx'
import React, { useState } from 'react'
import toast from 'react-hot-toast'
import { copyToClipboard } from 'web/lib/util/copy'
import { initLinkTwitchAccount } from 'web/lib/twitch/link-twitch-account'
import { LinkIcon } from '@heroicons/react/solid'
import { User, PrivateUser } from 'common/user'
export function TwitchPanel(props: {
auth: { user: User; privateUser: PrivateUser }
}) {
const { user, privateUser } = props.auth
const [twitchToken, setTwitchToken] = useState('')
const [twitchLoading, setTwitchLoading] = useState(false)
const [twitchLinkError, setTwitchLinkError] = useState('')
const [controlToken, setControlToken] = useState<string | undefined>(
undefined
)
const linkTwitchAccount = async () => {
if (!privateUser.apiKey) return // TODO: handle missing API key
try {
setTwitchLoading(true)
const [twitchAuthURL, linkSuccessPromise] = await initLinkTwitchAccount(
privateUser.id,
privateUser.apiKey
)
window.open(twitchAuthURL)
const data = await linkSuccessPromise
setTwitchToken(data.twitchName)
setControlToken(data.controlToken)
} catch (e) {
console.error(e)
toast.error('Failed to link Twitch account: ' + (e as Object).toString())
} finally {
setTwitchLoading(false)
}
}
const linkIcon = <LinkIcon className="mr-2 h-6 w-6" aria-hidden="true" />
const copyOverlayLink = async () => {
copyToClipboard(`http://localhost:1000/overlay?t=${controlToken}`)
toast.success('Overlay link copied!', {
icon: linkIcon,
})
}
const copyDockLink = async () => {
copyToClipboard(`http://localhost:1000/dock?t=${controlToken}`)
toast.success('Dock link copied!', {
icon: linkIcon,
})
}
return (
<>
<div>
<label className="label">Twitch</label>
<div className="relative flex w-full justify-items-stretch">
<input
type="text"
placeholder="Click link to connect your Twitch account"
className="input input-bordered w-full"
value={twitchToken}
readOnly
style={{
borderTopRightRadius: '0',
borderBottomRightRadius: '0',
}}
/>
<button
className={clsx(
'btn btn-secondary btn-square p-2',
twitchLoading ? 'loading' : ''
)}
onClick={linkTwitchAccount}
style={{
borderTopLeftRadius: '0',
borderBottomLeftRadius: '0',
}}
>
{!twitchLoading && <LinkIcon />}
</button>
</div>
</div>
<span className="text-sm text-red-400">Not editable for now</span>
<div>
<div className="flex w-full">
<div
className={clsx(
'flex grow gap-4',
twitchToken ? '' : 'tooltip tooltip-top'
)}
data-tip="You must link your Twitch account first"
>
<button
className={clsx(
'btn grow',
twitchToken ? 'btn-primary' : 'btn-disabled'
)}
onClick={copyOverlayLink}
>
Copy overlay link
</button>
<button
className={clsx(
'btn grow',
twitchToken ? 'btn-primary' : 'btn-disabled'
)}
onClick={copyDockLink}
>
Copy dock link
</button>
</div>
</div>
</div>
</>
)
}

View File

@ -1,6 +1,8 @@
import { User } from 'common/user' import { User } from 'common/user'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useEffect } from 'react' import { useEffect } from 'react'
import { getUserAndPrivateUser } from 'web/lib/firebase/users'
import { initLinkTwitchAccount } from 'web/lib/twitch/link-twitch-account'
import { safeLocalStorage } from 'web/lib/util/local' import { safeLocalStorage } from 'web/lib/util/local'
@ -14,17 +16,30 @@ export const useRedirectAfterSignup = (page: page_redirects) => {
}, [page]) }, [page])
} }
export const handleRedirectAfterSignup = (user: User | null) => { export const handleRedirectAfterSignup = async (user: User | null) => {
const redirect = safeLocalStorage()?.getItem(key) const redirect = safeLocalStorage()?.getItem(key)
safeLocalStorage()?.removeItem(key)
if (!user || !redirect) return if (!user || !redirect) return
safeLocalStorage()?.removeItem(key)
const now = dayjs().utc() const now = dayjs().utc()
const userCreatedTime = dayjs(user.createdTime) const userCreatedTime = dayjs(user.createdTime)
if (now.diff(userCreatedTime, 'minute') > 5) return if (now.diff(userCreatedTime, 'minute') > 5) return
if (redirect === 'twitch') { if (redirect === 'twitch') {
// TODO: actual Twitch redirect const { privateUser } = await getUserAndPrivateUser(user.id)
if (!privateUser.apiKey) return // TODO: handle missing API key
try {
const [twitchAuthURL, linkSuccessPromise] = await initLinkTwitchAccount(
privateUser.id,
privateUser.apiKey
)
window.open(twitchAuthURL) // TODO: Handle browser pop-up block
const data = await linkSuccessPromise // TODO: Do something with result?
console.debug(`Successfully linked Twitch account '${data.twitchName}'`)
} catch (e) {
console.error(e)
}
} }
} }

View File

@ -0,0 +1,25 @@
const TWITCH_BOT_PUBLIC_URL = 'https://king-prawn-app-5btyw.ondigitalocean.app'
export async function initLinkTwitchAccount(
manifoldUserID: string,
manifoldUserAPIKey: string
): Promise<[string, Promise<{ twitchName: string; controlToken: string }>]> {
const response = await fetch(`${TWITCH_BOT_PUBLIC_URL}/api/linkInit`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
manifoldID: manifoldUserID,
apiKey: manifoldUserAPIKey,
}),
})
const responseData = await response.json()
if (!response.ok) {
throw new Error(responseData.message)
}
const responseFetch = fetch(
`${TWITCH_BOT_PUBLIC_URL}/api/linkResult?userID=${manifoldUserID}`
)
return [responseData.twitchAuthURL, responseFetch.then((r) => r.json())]
}

View File

@ -21,6 +21,7 @@ import { defaultBannerUrl } from 'web/components/user-page'
import { SiteLink } from 'web/components/site-link' import { SiteLink } from 'web/components/site-link'
import Textarea from 'react-expanding-textarea' import Textarea from 'react-expanding-textarea'
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth' import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
import { TwitchPanel } from 'web/components/twitch-panel'
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => { export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
return { props: { auth: await getUserAndPrivateUser(creds.user.uid) } } return { props: { auth: await getUserAndPrivateUser(creds.user.uid) } }
@ -242,6 +243,8 @@ export default function ProfilePage(props: {
</button> </button>
</div> </div>
</div> </div>
<TwitchPanel auth={props.auth} />
</Col> </Col>
</Col> </Col>
</Page> </Page>