manually merge in Phil's code
This commit is contained in:
parent
d710a9b669
commit
050c80609b
122
web/components/twitch-panel.tsx
Normal file
122
web/components/twitch-panel.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
web/lib/twitch/link-twitch-account.ts
Normal file
25
web/lib/twitch/link-twitch-account.ts
Normal 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())]
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user