@taruvi/navkit 0.0.37 → 0.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm run dev:*)",
5
+ "WebSearch",
6
+ "Bash(npm run build:*)",
7
+ "Bash(npm install:*)"
8
+ ],
9
+ "deny": [],
10
+ "ask": []
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taruvi/navkit",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
4
4
  "main": "src/App.tsx",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -13,10 +13,10 @@
13
13
  "peerDependencies": {
14
14
  "@emotion/react": ">=11 <12",
15
15
  "@emotion/styled": ">=11 <12",
16
- "@fortawesome/fontawesome-svg-core": ">=6 <7",
17
- "@fortawesome/free-regular-svg-icons": ">=6 <7",
18
- "@fortawesome/free-solid-svg-icons": ">=6 <7",
19
- "@fortawesome/react-fontawesome": ">=0.2 <1",
16
+ "@fortawesome/fontawesome-svg-core": ">=6 <8",
17
+ "@fortawesome/free-regular-svg-icons": ">=6 <8",
18
+ "@fortawesome/free-solid-svg-icons": ">=6 <8",
19
+ "@fortawesome/react-fontawesome": ">=0.2 <4",
20
20
  "@mui/icons-material": ">=5 <8",
21
21
  "@mui/material": ">=5 <8",
22
22
  "react": ">=18 <20"
@@ -48,6 +48,6 @@
48
48
  "vitest": "^4.0.18"
49
49
  },
50
50
  "dependencies": {
51
- "@taruvi/sdk": "^1.3.4-beta.0"
51
+ "@taruvi/sdk": "latest"
52
52
  }
53
53
  }
package/src/App.styles.ts CHANGED
@@ -77,7 +77,7 @@ export const getAppStyles = (mode: ThemeMode) => {
77
77
  overflow: 'hidden',
78
78
  },
79
79
  toolbar: {
80
- backgroundColor: '#004369',
80
+ backgroundColor: '#2B97FF',
81
81
  boxShadow: ' 0px 0px 10px 2px rgba(0, 0, 0, 0.3)',
82
82
  justifyContent: 'space-between',
83
83
  minHeight: dimensions.navHeight,
@@ -106,6 +106,9 @@ export const getAppStyles = (mode: ThemeMode) => {
106
106
  fontWeight: 600,
107
107
  cursor: 'pointer',
108
108
  },
109
+ taruviSpaceLogo: {
110
+ height: '22px',
111
+ },
109
112
  rightSection: {
110
113
  display: 'flex',
111
114
  alignItems: 'center',
package/src/App.tsx CHANGED
@@ -9,10 +9,12 @@ import DraggableResizable from "./components/DraggableResizable";
9
9
  import { getAppStyles } from "./App.styles"
10
10
  import { NavkitProvider, useNavigation } from "./NavkitContext"
11
11
  import taruviLogo from "./assets/logo.svg";
12
+ import taruviLogoWhite from "./assets/taruvi-logo-white.png";
12
13
  import type { Client } from "@taruvi/sdk"
13
14
 
14
15
  const NavkitContent = () => {
15
16
  const { isUserAuthenticated, siteSettings, appSettings, appName, userData, jwtToken, themeMode } = useNavigation();
17
+ const showTaruviLogo = !appName
16
18
  const styles = getAppStyles(themeMode)
17
19
  const [showAppLauncher, setShowAppLauncher] = useState<boolean>(false)
18
20
  const [showProfileMenu, setShowProfileMenu] = useState<boolean>(false)
@@ -35,12 +37,18 @@ const NavkitContent = () => {
35
37
  <AppBar position="static" sx={styles.appBar}>
36
38
  <Toolbar sx={styles.toolbar}>
37
39
  <Box component={"a"} href="/" sx={styles.leftSection}>
38
- {appSettings?.icon ? <Box component="img" src={appSettings?.icon} sx={styles.logo} /> : <></>}
39
- {(appName || appSettings?.displayName) &&
40
- <Typography sx={styles.appName} onClick={() => (window.location.href = "/")}>
41
- {(() => { const name = appName || appSettings!.displayName!; return name.length > 40 ? `${name.substring(0, 40)}...` : name })()}
42
- </Typography>
43
- }
40
+ {showTaruviLogo ? (
41
+ <Box component="img" src={taruviLogoWhite} sx={styles.taruviSpaceLogo} />
42
+ ) : (
43
+ <>
44
+ {appSettings?.icon ? <Box component="img" src={appSettings?.icon} sx={styles.logo} /> : <></>}
45
+ {(appName || appSettings?.displayName) &&
46
+ <Typography sx={styles.appName} onClick={() => (window.location.href = "/")}>
47
+ {(() => { const name = appName || appSettings!.displayName!; return name.length > 40 ? `${name.substring(0, 40)}...` : name })()}
48
+ </Typography>
49
+ }
50
+ </>
51
+ )}
44
52
  </Box>
45
53
  <Box component={"div"} sx={styles.rightSection}>
46
54
  <Shortcuts showChat={setShowChat} onMenuToggle={() => setShowShortcutsMenu(!showShortcutsMenu)} />
@@ -70,7 +78,7 @@ const NavkitContent = () => {
70
78
  <Box component="img" src={taruviLogo} sx={styles.appLauncherLogo} />
71
79
  )}
72
80
  </Box>
73
- {isUserAuthenticated && userData && <Profile userData={userData} onClick={() => setShowProfileMenu(!showProfileMenu)}/>}
81
+ {isUserAuthenticated && userData && <Profile userData={userData} onClick={() => setShowProfileMenu(!showProfileMenu)} />}
74
82
  </Box>
75
83
  </Toolbar>
76
84
  </AppBar>
@@ -101,7 +109,7 @@ const NavkitContent = () => {
101
109
  <FontAwesomeIcon icon={["fas", "external-link-alt"]} style={{ fontSize: "10px" }} />
102
110
  </IconButton>
103
111
  </Box>
104
- <MattermostChat jwtToken={jwtToken} mattermostUrl={siteSettings['chat-url']} width="100%" height="100%" />
112
+ <MattermostChat jwtToken={jwtToken} mattermostUrl={siteSettings['chat-url']} loginId={userData?.email || ''} width="100%" height="100%" />
105
113
  </DraggableResizable>
106
114
  )}
107
115
  </>
@@ -12,7 +12,6 @@ const getInitialTheme = (): ThemeMode => {
12
12
  if (typeof window !== 'undefined') {
13
13
  const stored = localStorage.getItem(THEME_STORAGE_KEY)
14
14
  if (stored === 'dark' || stored === 'light') return stored
15
- if (window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark'
16
15
  }
17
16
  return 'light'
18
17
  }
@@ -30,6 +29,7 @@ export const NavkitProvider = ({ children, client, onThemeChange, appName }: Nav
30
29
  'show-chat': false,
31
30
  'chat-url': '',
32
31
  frontendUrl: '',
32
+ enableDarkMode: false,
33
33
  })
34
34
  const appSettings = useRef<AppSettings>({})
35
35
 
@@ -104,6 +104,7 @@ export const NavkitProvider = ({ children, client, onThemeChange, appName }: Nav
104
104
  'chat-url': rawSettings['navkit.chat-url'] ?? '',
105
105
  frontendUrl: rawSettings['navkit.frontend-url'] ?? '',
106
106
  logo: rawSettings['navkit.logo'] ?? '',
107
+ enableDarkMode: rawSettings['navkit.enable-dark-mode'] ?? false,
107
108
  }
108
109
  if (isUserAuthenticated) {
109
110
  const userDataResponse = await auth.getCurrentUser()
@@ -6,6 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
6
6
  import { findIconDefinition, type IconName } from "@fortawesome/fontawesome-svg-core"
7
7
  import { useNavigation } from "../../NavkitContext"
8
8
  import { getAppLauncherStyles } from "./AppLauncher.styles"
9
+ import { typography } from "../../styles/variables"
9
10
 
10
11
 
11
12
  const isIconUrl = (icon: string): boolean => {
@@ -37,7 +38,11 @@ const AppLauncher = () => {
37
38
  return (
38
39
  <Card sx={styles.container}>
39
40
  <Search appsList={appsList} onSearchChange={handleSearchChange} />
40
- {filteredApps && filteredApps.length > 0 && (
41
+ {filteredApps.length === 0 ? (
42
+ <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', py: 6 }}>
43
+ <Typography sx={{ color: styles.appName.color, fontSize: typography.sizes.sm }}>No apps available</Typography>
44
+ </Box>
45
+ ) : (
41
46
  <Box sx={styles.scrollContainer}>
42
47
  <Grid container spacing={0}>
43
48
  {filteredApps.map((app) => {
@@ -41,7 +41,7 @@ const DraggableResizable = ({
41
41
  y: Math.max(0, Math.min(e.clientY - dragOffset.current.y, window.innerHeight - 50)),
42
42
  })
43
43
  } else if (action.current === 'resize') {
44
- setSize(prev => ({
44
+ setSize(() => ({
45
45
  w: Math.max(minWidth, e.clientX - dragOffset.current.x),
46
46
  h: Math.max(minHeight, e.clientY - dragOffset.current.y),
47
47
  }))
@@ -1,9 +1,10 @@
1
1
  import { useEffect, useRef, useState } from 'react'
2
- import { Box, Badge } from '@mui/material'
2
+ import { Box, Badge, CircularProgress, Typography } from '@mui/material'
3
3
 
4
4
  interface MattermostChatProps {
5
5
  jwtToken: string
6
- mattermostUrl: string // Mattermost server URL (must be passed from parent app)
6
+ mattermostUrl: string
7
+ loginId: string
7
8
  onNotification?: (count: number) => void
8
9
  onUrlClick?: (url: string) => void
9
10
  width?: string | number
@@ -24,6 +25,7 @@ interface NotifyMessage {
24
25
  const MattermostChat = ({
25
26
  jwtToken,
26
27
  mattermostUrl,
28
+ loginId,
27
29
  onNotification,
28
30
  onUrlClick,
29
31
  width = '100%',
@@ -32,17 +34,65 @@ const MattermostChat = ({
32
34
  const iframeRef = useRef<HTMLIFrameElement>(null)
33
35
  const [unreadCount, setUnreadCount] = useState(0)
34
36
  const [isWindowFocused, setIsWindowFocused] = useState(true)
37
+ const [authToken, setAuthToken] = useState<string | null>(null)
38
+ const [authError, setAuthError] = useState<string | null>(null)
39
+ const [isLoading, setIsLoading] = useState(true)
40
+
41
+ // Authenticate with Mattermost on mount
42
+ useEffect(() => {
43
+ const login = async () => {
44
+ try {
45
+ const res = await fetch(`${mattermostUrl.replace(/\/$/, '')}/api/v4/users/login`, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
48
+ credentials: 'omit',
49
+ body: JSON.stringify({ login_id: loginId, password: jwtToken, token: '', deviceId: '' })
50
+ })
51
+
52
+ if (!res.ok) {
53
+ setAuthError(`Login failed (${res.status})`)
54
+ setIsLoading(false)
55
+ return
56
+ }
57
+
58
+ const token = res.headers.get('Token')
59
+ if (token) {
60
+ setAuthToken(token)
61
+ } else {
62
+ setAuthError('No token in response')
63
+ }
64
+ } catch (e: any) {
65
+ setAuthError(e.message || 'Login failed')
66
+ }
67
+ setIsLoading(false)
68
+ }
69
+
70
+ if (loginId && jwtToken) login()
71
+ else { setAuthError('Missing credentials'); setIsLoading(false) }
72
+ }, [mattermostUrl, loginId, jwtToken])
73
+
74
+ // Inject auth token into iframe via postMessage once loaded
75
+ useEffect(() => {
76
+ if (!authToken || !iframeRef.current) return
77
+
78
+ const handleIframeLoad = () => {
79
+ // Set the auth cookie/token in the iframe context
80
+ iframeRef.current?.contentWindow?.postMessage(
81
+ { type: 'token', data: authToken },
82
+ new URL(mattermostUrl).origin
83
+ )
84
+ }
85
+
86
+ const iframe = iframeRef.current
87
+ iframe.addEventListener('load', handleIframeLoad)
88
+ return () => iframe.removeEventListener('load', handleIframeLoad)
89
+ }, [authToken, mattermostUrl])
90
+
35
91
  useEffect(() => {
36
- // Handle postMessage events from Mattermost iframe
37
92
  const handleMessage = (event: MessageEvent<NotifyMessage>) => {
38
- // Validate origin for security
39
93
  const mattermostOrigin = new URL(mattermostUrl).origin
40
- if (event.origin !== mattermostOrigin) {
41
- console.warn('Received message from untrusted origin:', event.origin)
42
- return
43
- }
94
+ if (event.origin !== mattermostOrigin) return
44
95
 
45
- // Handle Notify events
46
96
  if (event.data?.event === 'Notify') {
47
97
  if (!isWindowFocused) {
48
98
  setUnreadCount(prev => {
@@ -51,31 +101,16 @@ const MattermostChat = ({
51
101
  return newCount
52
102
  })
53
103
  }
54
-
55
- // Handle URL clicks if provided
56
- if (event.data.data?.url && onUrlClick) {
57
- onUrlClick(event.data.data.url)
58
- }
104
+ if (event.data.data?.url && onUrlClick) onUrlClick(event.data.data.url)
59
105
  }
60
106
  }
61
107
 
62
- // Handle window focus/blur to track unread messages
63
- const handleFocus = () => {
64
- setIsWindowFocused(true)
65
- setUnreadCount(0)
66
- onNotification?.(0)
67
- }
108
+ const handleFocus = () => { setIsWindowFocused(true); setUnreadCount(0); onNotification?.(0) }
109
+ const handleBlur = () => setIsWindowFocused(false)
68
110
 
69
- const handleBlur = () => {
70
- setIsWindowFocused(false)
71
- }
72
-
73
- // Add event listeners
74
111
  window.addEventListener('message', handleMessage)
75
112
  window.addEventListener('focus', handleFocus)
76
113
  window.addEventListener('blur', handleBlur)
77
-
78
- // Cleanup event listeners on unmount
79
114
  return () => {
80
115
  window.removeEventListener('message', handleMessage)
81
116
  window.removeEventListener('focus', handleFocus)
@@ -83,33 +118,34 @@ const MattermostChat = ({
83
118
  }
84
119
  }, [mattermostUrl, isWindowFocused, onNotification, onUrlClick])
85
120
 
86
- // Construct iframe URL with JWT authentication
87
- const iframeUrl = `${mattermostUrl.replace(/\/$/, '')}/login`
121
+ if (isLoading) {
122
+ return (
123
+ <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width, height }}>
124
+ <CircularProgress size={32} />
125
+ </Box>
126
+ )
127
+ }
128
+
129
+ if (authError) {
130
+ return (
131
+ <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width, height }}>
132
+ <Typography color="error">{authError}</Typography>
133
+ </Box>
134
+ )
135
+ }
136
+
137
+ // Load Mattermost with the auth token as a cookie header via the access_token param
138
+ const iframeUrl = `${mattermostUrl.replace(/\/$/, '')}?access_token=${authToken}`
88
139
 
89
140
  return (
90
- <Box
91
- sx={{
92
- position: 'relative',
93
- width,
94
- height,
95
- overflow: 'hidden'
96
- }}
97
- >
141
+ <Box sx={{ position: 'relative', width, height, overflow: 'hidden' }}>
98
142
  {unreadCount > 0 && (
99
143
  <Badge
100
144
  badgeContent={unreadCount}
101
145
  color="error"
102
146
  sx={{
103
- position: 'absolute',
104
- top: 16,
105
- right: 16,
106
- zIndex: 1000,
107
- '& .MuiBadge-badge': {
108
- fontSize: '1rem',
109
- height: '28px',
110
- minWidth: '28px',
111
- borderRadius: '14px'
112
- }
147
+ position: 'absolute', top: 16, right: 16, zIndex: 1000,
148
+ '& .MuiBadge-badge': { fontSize: '1rem', height: '28px', minWidth: '28px', borderRadius: '14px' }
113
149
  }}
114
150
  />
115
151
  )}
@@ -118,12 +154,7 @@ const MattermostChat = ({
118
154
  src={iframeUrl}
119
155
  title="Mattermost Chat"
120
156
  allow="clipboard-read; clipboard-write"
121
- style={{
122
- width: '100%',
123
- height: '100%',
124
- border: 'none',
125
- display: 'block'
126
- }}
157
+ style={{ width: '100%', height: '100%', border: 'none', display: 'block' }}
127
158
  />
128
159
  </Box>
129
160
  )
@@ -2,39 +2,33 @@ import { Box, SvgIcon } from '@mui/material'
2
2
  import { getProfileStyles } from './Profile.styles'
3
3
  import { useNavigation } from '../../NavkitContext'
4
4
 
5
- const ProfileIcon = () => (
5
+ const ProfileIcon = ({ color }: { color?: string }) => (
6
6
  <SvgIcon viewBox="0 0 22 22" sx={{ fontSize: 23, display: 'block' }}>
7
- <path d="M10.9921 20.9849C16.511 20.9849 20.9849 16.511 20.9849 10.9921C20.9849 5.4732 16.511 0.999268 10.9921 0.999268C5.47322 0.999268 0.999283 5.4732 0.999283 10.9921C0.999283 16.511 5.47322 20.9849 10.9921 20.9849Z" stroke="#9DE5FD" fill="none" strokeWidth="1.99856" strokeLinecap="round" strokeLinejoin="round"/>
8
- <path d="M10.9921 11.9914C12.6478 11.9914 13.9899 10.6492 13.9899 8.99357C13.9899 7.33791 12.6478 5.99573 10.9921 5.99573C9.33644 5.99573 7.99426 7.33791 7.99426 8.99357C7.99426 10.6492 9.33644 11.9914 10.9921 11.9914Z" stroke="#9DE5FD" fill="none" strokeWidth="1.99856" strokeLinecap="round" strokeLinejoin="round"/>
9
- <path d="M5.99569 19.6479V17.9871C5.99569 17.457 6.20625 16.9487 6.58106 16.5739C6.95586 16.1991 7.4642 15.9885 7.99425 15.9885H13.9899C14.52 15.9885 15.0283 16.1991 15.4031 16.5739C15.7779 16.9487 15.9885 17.457 15.9885 17.9871V19.6479" stroke="#9DE5FD" fill="none" strokeWidth="1.99856" strokeLinecap="round" strokeLinejoin="round"/>
7
+ <path d="M10.9921 20.9849C16.511 20.9849 20.9849 16.511 20.9849 10.9921C20.9849 5.4732 16.511 0.999268 10.9921 0.999268C5.47322 0.999268 0.999283 5.4732 0.999283 10.9921C0.999283 16.511 5.47322 20.9849 10.9921 20.9849Z" stroke={color || "#ffffff"} fill="none" strokeWidth="1.99856" strokeLinecap="round" strokeLinejoin="round"/>
8
+ <path d="M10.9921 11.9914C12.6478 11.9914 13.9899 10.6492 13.9899 8.99357C13.9899 7.33791 12.6478 5.99573 10.9921 5.99573C9.33644 5.99573 7.99426 7.33791 7.99426 8.99357C7.99426 10.6492 9.33644 11.9914 10.9921 11.9914Z" stroke={color || "#ffffff"} fill="none" strokeWidth="1.99856" strokeLinecap="round" strokeLinejoin="round"/>
9
+ <path d="M5.99569 19.6479V17.9871C5.99569 17.457 6.20625 16.9487 6.58106 16.5739C6.95586 16.1991 7.4642 15.9885 7.99425 15.9885H13.9899C14.52 15.9885 15.0283 16.1991 15.4031 16.5739C15.7779 16.9487 15.9885 17.457 15.9885 17.9871V19.6479" stroke={color || "#ffffff"} fill="none" strokeWidth="1.99856" strokeLinecap="round" strokeLinejoin="round"/>
10
10
  </SvgIcon>
11
11
  )
12
12
 
13
- interface UserData {
14
- full_name?: string
15
- first_name?: string
16
- last_name?: string
17
- pfp?: string
18
- username?: string
19
- email?: string
20
- }
13
+ import type { UserData } from '../../types'
21
14
 
22
15
  interface ProfileProps {
23
16
  userData: UserData
24
17
  onClick: () => void
18
+ iconColor?: string
25
19
  }
26
20
 
27
21
  const Profile = (props: ProfileProps) => {
28
- const { userData, onClick } = props
22
+ const { userData, onClick, iconColor } = props
29
23
  const { themeMode } = useNavigation()
30
24
  const styles = getProfileStyles(themeMode)
31
25
 
32
26
  return (
33
27
  <Box sx={styles.trigger} onClick={onClick}>
34
- {userData.pfp ? (
35
- <Box component="img" src={userData.pfp} sx={{ width: 23, height: 23, borderRadius: '50%' }} />
28
+ {userData.icon_url ? (
29
+ <Box component="img" src={userData.icon_url} sx={{ width: 23, height: 23, borderRadius: '50%' }} />
36
30
  ) : (
37
- <ProfileIcon />
31
+ <ProfileIcon color={iconColor} />
38
32
  )}
39
33
  </Box>
40
34
  )
@@ -6,7 +6,7 @@ import { getProfileStyles } from './Profile.styles'
6
6
  import { useNavigation } from '../../NavkitContext'
7
7
 
8
8
  const ProfileMenu = () => {
9
- const { auth, themeMode, toggleTheme, userData } = useNavigation()
9
+ const { auth, themeMode, toggleTheme, userData, siteSettings } = useNavigation()
10
10
  const styles = getProfileStyles(themeMode)
11
11
 
12
12
  // const handlePreferences = () => {
@@ -14,6 +14,7 @@ const ProfileMenu = () => {
14
14
  // }
15
15
 
16
16
  const handleLogout = async () => {
17
+ localStorage.removeItem('navkit-theme-mode')
17
18
  await auth.logout()
18
19
  }
19
20
 
@@ -35,6 +36,7 @@ const ProfileMenu = () => {
35
36
  </Typography>
36
37
  </MenuItem> */}
37
38
 
39
+ {siteSettings.enableDarkMode && (
38
40
  <MenuItem onClick={toggleTheme} sx={styles.menuItem}>
39
41
  {themeMode === 'dark' ? (
40
42
  <LightModeOutlined sx={styles.iconStyle} />
@@ -45,6 +47,7 @@ const ProfileMenu = () => {
45
47
  {themeMode === 'dark' ? 'Light Mode' : 'Dark Mode'}
46
48
  </Typography>
47
49
  </MenuItem>
50
+ )}
48
51
 
49
52
  <MenuItem onClick={handleLogout} sx={styles.menuItem}>
50
53
  <FontAwesomeIcon
@@ -10,7 +10,7 @@ export const shortcutsStyles = {
10
10
  mobileTrigger: {
11
11
  display: { xs: 'flex', md: 'none' },
12
12
  ml: spacing.sm,
13
- color: '#9DE5FD',
13
+ color: '#ffffff',
14
14
  },
15
15
  shortcut: {
16
16
  display: 'flex',
@@ -25,6 +25,6 @@ export const shortcutsStyles = {
25
25
  },
26
26
  iconStyle: {
27
27
  fontSize: '23px',
28
- color: colours.text.secondary,
28
+ color: '#ffffff',
29
29
  },
30
30
  }
@@ -4,9 +4,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
4
4
  import { shortcutsStyles } from './Shortcuts.styles'
5
5
  import { useNavigation } from '../../NavkitContext'
6
6
 
7
- const ChatIcon = () => (
7
+ const ChatIcon = ({ color }: { color?: string }) => (
8
8
  <SvgIcon viewBox="-2 -2 24 24" sx={{ fontSize: 23, display: 'block', position: 'relative', top: 2 }}>
9
- <path d="M18 0H2C0.9 0 0 0.9 0 2V20L4 16H18C19.1 16 20 15.1 20 14V2C20 0.9 19.1 0 18 0Z" fill="#9DE5FD"/>
9
+ <path d="M18 0H2C0.9 0 0 0.9 0 2V20L4 16H18C19.1 16 20 15.1 20 14V2C20 0.9 19.1 0 18 0Z" fill={color || "#ffffff"}/>
10
10
  </SvgIcon>
11
11
  )
12
12
 
@@ -14,11 +14,12 @@ interface ShortcutsProps {
14
14
  showChat?: (showChat: boolean) => void
15
15
  onMenuToggle?: () => void
16
16
  triggerRef?: React.RefObject<HTMLButtonElement | null>
17
+ iconColor?: string
17
18
  }
18
19
 
19
20
  const Shortcuts = (props: ShortcutsProps) => {
20
21
 
21
- const { showChat, onMenuToggle, triggerRef } = props
22
+ const { showChat, onMenuToggle, triggerRef, iconColor } = props
22
23
  const { navigateToUrl, siteSettings, isUserAuthenticated } = useNavigation()
23
24
  const shortcuts = siteSettings.shortcuts
24
25
 
@@ -40,7 +41,7 @@ const Shortcuts = (props: ShortcutsProps) => {
40
41
  >
41
42
  <FontAwesomeIcon
42
43
  icon={["far", shortcut.icon] as any}
43
- style={shortcutsStyles.iconStyle}
44
+ style={{ ...shortcutsStyles.iconStyle, ...(iconColor && { color: iconColor }) }}
44
45
  />
45
46
  </Link>
46
47
  ))}
@@ -50,7 +51,7 @@ const Shortcuts = (props: ShortcutsProps) => {
50
51
  onClick={() => showChat?.(true)}
51
52
  sx={shortcutsStyles.shortcut}
52
53
  >
53
- <ChatIcon />
54
+ <ChatIcon color={iconColor} />
54
55
  </Link>
55
56
  }
56
57
  </Box>
@@ -60,11 +61,11 @@ const Shortcuts = (props: ShortcutsProps) => {
60
61
  <IconButton
61
62
  ref={triggerRef}
62
63
  onClick={onMenuToggle}
63
- sx={shortcutsStyles.mobileTrigger}
64
+ sx={{ ...shortcutsStyles.mobileTrigger, ...(iconColor && { color: iconColor }) }}
64
65
  >
65
66
  <FontAwesomeIcon
66
67
  icon={["fas", "ellipsis-v"]}
67
- style={{ ...shortcutsStyles.iconStyle, color: '#9DE5FD' }}
68
+ style={{ ...shortcutsStyles.iconStyle, color: iconColor || '#ffffff' }}
68
69
  />
69
70
  </IconButton>
70
71
  )}
package/src/types.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type { ReactNode } from "react"
2
- import type { Client, Auth } from "@taruvi/sdk"
2
+ import type { Client, Auth, UserData, UserRole } from "@taruvi/sdk"
3
3
  import type { ThemeMode } from "./styles/variables"
4
4
 
5
+ export type { UserData, UserRole }
6
+
5
7
  export interface AppData {
6
8
  appname: string,
7
9
  icon: string,
@@ -9,41 +11,13 @@ export interface AppData {
9
11
  id: string
10
12
  }
11
13
 
12
- export interface UserRole {
13
- name: string
14
- slug: string
15
- type: string
16
- app_slug?: string
17
- source?: string
18
- }
19
-
20
- export interface UserData {
21
- id?: number
22
- username?: string
23
- email?: string
24
- first_name?: string
25
- last_name?: string
26
- full_name?: string
27
- pfp?: string
28
- is_active?: boolean
29
- is_staff?: boolean
30
- is_superuser?: boolean
31
- is_deleted?: boolean
32
- date_joined?: string
33
- last_login?: string
34
- groups?: { id: number; name: string }[]
35
- user_permissions?: { id: number; name: string; codename: string; content_type: string }[]
36
- attributes?: Record<string, unknown>
37
- missing_attributes?: string[]
38
- roles?: UserRole[]
39
- }
40
-
41
14
  export interface SiteSettings {
42
15
  shortcuts?: AppData[]
43
16
  logo?: string
44
17
  'show-chat': boolean
45
18
  'chat-url': string
46
19
  frontendUrl: string
20
+ enableDarkMode: boolean
47
21
  }
48
22
 
49
23
  export interface AppSettings {