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 {