diff --git a/frontend/src/ui/pages/Dashboard.tsx b/frontend/src/ui/pages/Dashboard.tsx index 128f4a0..19056ec 100644 --- a/frontend/src/ui/pages/Dashboard.tsx +++ b/frontend/src/ui/pages/Dashboard.tsx @@ -8,7 +8,14 @@ import { import { useLiveKey, useModule } from '~/lib/react'; import { useAppDispatch, useAppSelector } from '~/store'; import { modules } from '~/store/api/reducer'; -import { PageContainer, SectionHeader, styled, TextBlock } from '../theme'; +import * as HoverCard from '@radix-ui/react-hover-card'; +import { + PageContainer, + SectionHeader, + styled, + TextBlock, + TooltipContent, +} from '../theme'; import BrowserLink from '../components/BrowserLink'; import Scrollbar from '../components/utils/Scrollbar'; import RevealLink from '../components/utils/RevealLink'; @@ -345,15 +352,21 @@ function TwitchEventLog({ events }: { events: EventSubNotification[] }) { const { t } = useTranslation(); return ( <> - - {t('pages.dashboard.twitch-events.header')} - - - - + + + + {t('pages.dashboard.twitch-events.header')} + + + + + + + + {t('pages.dashboard.twitch-events.warning')} + + + {events @@ -427,10 +440,15 @@ function TwitchSection() { ); } +function ProblemList() { + return <>; +} + export default function Dashboard(): React.ReactElement { const { t } = useTranslation(); return ( + {t('pages.dashboard.quick-links')} diff --git a/frontend/src/ui/pages/Extensions.tsx b/frontend/src/ui/pages/Extensions.tsx index 113c123..d377005 100644 --- a/frontend/src/ui/pages/Extensions.tsx +++ b/frontend/src/ui/pages/Extensions.tsx @@ -26,6 +26,8 @@ import extensionsReducer, { startExtension, stopExtension, } from '~/store/extensions/reducer'; +import { useModule } from '~/lib/react'; +import { modules } from '~/store/api/reducer'; import AlertContent from '../components/AlertContent'; import DialogContent from '../components/DialogContent'; import Loading from '../components/Loading'; @@ -37,6 +39,7 @@ import { DialogActions, Field, FlexRow, + getTheme, InputBox, Label, MultiButton, @@ -220,7 +223,7 @@ function ExtensionListItem(props: ExtensionListItemProps) { }} showCancel={false} > - {props.error.toString()} + {props.error.message} ) : null} @@ -583,6 +586,7 @@ function ExtensionEditor() { } export default function ExtensionsPage(): React.ReactElement { + const [uiConfig] = useModule(modules.uiConfig); const { t } = useTranslation(); const extensions = useAppSelector((state) => state.extensions); const dispatch = useAppDispatch(); @@ -619,12 +623,20 @@ export default function ExtensionsPage(): React.ReactElement { }; if (!extensions.ready) { + const theme = getTheme( + uiConfig?.theme ?? localStorage.getItem('theme') ?? 'dark', + ); + return ( {t('pages.extensions.title')} - + ); } diff --git a/frontend/src/ui/theme/utils.ts b/frontend/src/ui/theme/utils.ts index 6241c55..b90f029 100644 --- a/frontend/src/ui/theme/utils.ts +++ b/frontend/src/ui/theme/utils.ts @@ -1,5 +1,6 @@ /* eslint-disable import/prefer-default-export */ +import { Content as HoverCardContent } from '@radix-ui/react-hover-card'; import { styled, theme } from './theme'; export const FlexRow = styled('div', { @@ -26,3 +27,15 @@ export const FlexRow = styled('div', { }, }, }); + +export const TooltipContent = styled(HoverCardContent, { + borderRadius: 6, + display: 'flex', + padding: '0.5rem', + gap: '0.5rem', + flexDirection: 'column', + border: '2px solid $gray6', + backgroundColor: '$gray2', + alignItems: 'flex-start', + boxShadow: '0px 5px 20px rgba(0,0,0,0.4)', +}); diff --git a/twitch/client.auth.go b/twitch/client.auth.go index 562035b..ac27e97 100644 --- a/twitch/client.auth.go +++ b/twitch/client.auth.go @@ -3,6 +3,7 @@ package twitch import ( "fmt" "net/http" + "slices" "time" "github.com/nicklaw5/helix/v2" @@ -16,22 +17,60 @@ type AuthResponse struct { Time time.Time } +var scopes = []string{ + "bits:read", + "channel:bot", + "channel:moderate", + "channel:read:hype_train", + "channel:read:polls", + "channel:read:predictions", + "channel:read:redemptions", + "channel:read:subscriptions", + "chat:edit", + "chat:read", + "moderator:manage:announcements", + "moderator:read:chatters", + "moderator:read:followers", + "user_read", + "user:bot", + "user:manage:whispers", + "user:read:chat", + "whispers:edit", + "whispers:read", +} + func (c *Client) GetAuthorizationURL() string { if c.API == nil { return "twitch-not-configured" } return c.API.GetAuthorizationURL(&helix.AuthorizationURLParams{ ResponseType: "code", - Scopes: []string{"bits:read channel:read:subscriptions channel:read:redemptions channel:read:polls channel:read:predictions channel:read:hype_train user_read chat:read chat:edit channel:moderate whispers:read whispers:edit moderator:read:chatters moderator:read:followers user:manage:whispers moderator:manage:announcements"}, + Scopes: scopes, }) } +// CheckScopes checks if the user has authorized all required scopes +// Normally this would be the case but between versions strimertul has changed +// the required scopes, and it's possible that some users have not re-authorized +// the application with the new scopes. +func (c *Client) CheckScopes() (bool, error) { + var authResp AuthResponse + if err := c.db.GetJSON(AuthKey, &authResp); err != nil { + return false, err + } + + // Sort scopes for comparison + slices.Sort(authResp.Scope) + + return slices.Equal(scopes, authResp.Scope), nil +} + func (c *Client) GetUserClient(forceRefresh bool) (*helix.Client, error) { var authResp AuthResponse - err := c.db.GetJSON(AuthKey, &authResp) - if err != nil { + if err := c.db.GetJSON(AuthKey, &authResp); err != nil { return nil, err } + // Handle token expiration if forceRefresh || time.Now().After(authResp.Time.Add(time.Duration(authResp.ExpiresIn)*time.Second)) { // Refresh tokens @@ -108,7 +147,7 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } w.Header().Add("Content-Type", "text/html") - fmt.Fprintf(w, `

All done, you can close me now!

`) + _, _ = fmt.Fprintf(w, `

All done, you can close me now!

`) } type RefreshResponse struct {