mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-14 21:10:34 +01:00
Added teams button and Avatar component.
This commit is contained in:
parent
c7df1063ac
commit
992342aa72
@ -92,6 +92,7 @@
|
|||||||
"isbot": "^3.4.5",
|
"isbot": "^3.4.5",
|
||||||
"kafkajs": "^2.1.0",
|
"kafkajs": "^2.1.0",
|
||||||
"maxmind": "^4.3.6",
|
"maxmind": "^4.3.6",
|
||||||
|
"md5": "^2.3.0",
|
||||||
"moment-timezone": "^0.5.35",
|
"moment-timezone": "^0.5.35",
|
||||||
"next": "14.0.4",
|
"next": "14.0.4",
|
||||||
"next-basics": "^0.39.0",
|
"next-basics": "^0.39.0",
|
||||||
|
@ -2,18 +2,20 @@
|
|||||||
import { Icon, Text } from 'react-basics';
|
import { Icon, Text } from 'react-basics';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Icons from 'components/icons';
|
import HamburgerButton from 'components/common/HamburgerButton';
|
||||||
import ThemeButton from 'components/input/ThemeButton';
|
import ThemeButton from 'components/input/ThemeButton';
|
||||||
import LanguageButton from 'components/input/LanguageButton';
|
import LanguageButton from 'components/input/LanguageButton';
|
||||||
import ProfileButton from 'components/input/ProfileButton';
|
import ProfileButton from 'components/input/ProfileButton';
|
||||||
import { useMessages, useNavigation } from 'components/hooks';
|
import TeamsButton from 'components/input/TeamsButton';
|
||||||
import HamburgerButton from 'components/common/HamburgerButton';
|
import Icons from 'components/icons';
|
||||||
|
import { useLogin, useMessages, useNavigation } from 'components/hooks';
|
||||||
import styles from './NavBar.module.css';
|
import styles from './NavBar.module.css';
|
||||||
|
|
||||||
export function NavBar() {
|
export function NavBar() {
|
||||||
|
const { user } = useLogin();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const cloudMode = Boolean(process.env.cloudMode);
|
const cloudMode = Boolean(process.env.cloudMode);
|
||||||
const { pathname, renderTeamUrl } = useNavigation();
|
const { pathname, renderTeamUrl, teamId } = useNavigation();
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{ label: formatMessage(labels.dashboard), url: renderTeamUrl('/dashboard') },
|
{ label: formatMessage(labels.dashboard), url: renderTeamUrl('/dashboard') },
|
||||||
@ -79,6 +81,7 @@ export function NavBar() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
|
{user?.teams?.length && <TeamsButton teamId={teamId} />}
|
||||||
<ThemeButton />
|
<ThemeButton />
|
||||||
<LanguageButton />
|
<LanguageButton />
|
||||||
<ProfileButton />
|
<ProfileButton />
|
||||||
|
158
src/components/common/Avatar.tsx
Normal file
158
src/components/common/Avatar.tsx
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import md5 from 'md5';
|
||||||
|
|
||||||
|
const pallette = [
|
||||||
|
['#ff9a9e', '#fad0c4'],
|
||||||
|
['#a18cd1', '#fbc2eb'],
|
||||||
|
['#fad0c4', '#ffd1ff'],
|
||||||
|
['#ffecd2', '#fcb69f'],
|
||||||
|
['#ff9a9e', '#fecfef'],
|
||||||
|
['#f6d365', '#fda085'],
|
||||||
|
['#fbc2eb', '#a6c1ee'],
|
||||||
|
['#fdcbf1', '#e6dee9'],
|
||||||
|
['#a1c4fd', '#c2e9fb'],
|
||||||
|
['#d4fc79', '#96e6a1'],
|
||||||
|
['#84fab0', '#8fd3f4'],
|
||||||
|
['#cfd9df', '#e2ebf0'],
|
||||||
|
['#a6c0fe', '#f68084'],
|
||||||
|
['#fccb90', '#d57eeb'],
|
||||||
|
['#e0c3fc', '#8ec5fc'],
|
||||||
|
['#f093fb', '#f5576c'],
|
||||||
|
['#fdfbfb', '#ebedee'],
|
||||||
|
['#4facfe', '#00f2fe'],
|
||||||
|
['#43e97b', '#38f9d7'],
|
||||||
|
['#fa709a', '#fee140'],
|
||||||
|
['#30cfd0', '#330867'],
|
||||||
|
['#a8edea', '#fed6e3'],
|
||||||
|
['#5ee7df', '#b490ca'],
|
||||||
|
['#d299c2', '#fef9d7'],
|
||||||
|
['#f5f7fa', '#c3cfe2'],
|
||||||
|
['#667eea', '#764ba2'],
|
||||||
|
['#fdfcfb', '#e2d1c3'],
|
||||||
|
['#89f7fe', '#66a6ff'],
|
||||||
|
['#fddb92', '#d1fdff'],
|
||||||
|
['#9890e3', '#b1f4cf'],
|
||||||
|
['#ebc0fd', '#d9ded8'],
|
||||||
|
['#96fbc4', '#f9f586'],
|
||||||
|
['#2af598', '#009efd'],
|
||||||
|
['#cd9cf2', '#f6f3ff'],
|
||||||
|
['#6a11cb', '#2575fc'],
|
||||||
|
['#37ecba', '#72afd3'],
|
||||||
|
['#ebbba7', '#cfc7f8'],
|
||||||
|
['#fff1eb', '#ace0f9'],
|
||||||
|
['#c471f5', '#fa71cd'],
|
||||||
|
['#48c6ef', '#6f86d6'],
|
||||||
|
['#feada6', '#f5efef'],
|
||||||
|
['#e6e9f0', '#eef1f5'],
|
||||||
|
['#accbee', '#e7f0fd'],
|
||||||
|
['#e9defa', '#fbfcdb'],
|
||||||
|
['#c1dfc4', '#deecdd'],
|
||||||
|
['#0ba360', '#3cba92'],
|
||||||
|
['#00c6fb', '#005bea'],
|
||||||
|
['#74ebd5', '#9face6'],
|
||||||
|
['#6a85b6', '#bac8e0'],
|
||||||
|
['#a3bded', '#6991c7'],
|
||||||
|
['#9795f0', '#fbc8d4'],
|
||||||
|
['#a7a6cb', '#8989ba'],
|
||||||
|
['#f43b47', '#453a94'],
|
||||||
|
['#0250c5', '#d43f8d'],
|
||||||
|
['#88d3ce', '#6e45e2'],
|
||||||
|
['#d9afd9', '#97d9e1'],
|
||||||
|
['#7028e4', '#e5b2ca'],
|
||||||
|
['#13547a', '#80d0c7'],
|
||||||
|
['#ff0844', '#ffb199'],
|
||||||
|
['#93a5cf', '#e4efe9'],
|
||||||
|
['#434343', '#000000'],
|
||||||
|
['#93a5cf', '#e4efe9'],
|
||||||
|
['#92fe9d', '#00c9ff'],
|
||||||
|
['#ff758c', '#ff7eb3'],
|
||||||
|
['#868f96', '#596164'],
|
||||||
|
['#c79081', '#dfa579'],
|
||||||
|
['#8baaaa', '#ae8b9c'],
|
||||||
|
['#f83600', '#f9d423'],
|
||||||
|
['#b721ff', '#21d4fd'],
|
||||||
|
['#6e45e2', '#88d3ce'],
|
||||||
|
['#d558c8', '#24d292'],
|
||||||
|
['#abecd6', '#fbed96'],
|
||||||
|
['#5f72bd', '#9b23ea'],
|
||||||
|
['#09203f', '#537895'],
|
||||||
|
['#ddd6f3', '#faaca8'],
|
||||||
|
['#dcb0ed', '#99c99c'],
|
||||||
|
['#f3e7e9', '#e3eeff'],
|
||||||
|
['#c71d6f', '#d09693'],
|
||||||
|
['#96deda', '#50c9c3'],
|
||||||
|
['#f77062', '#fe5196'],
|
||||||
|
['#a8caba', '#5d4157'],
|
||||||
|
['#29323c', '#485563'],
|
||||||
|
['#16a085', '#f4d03f'],
|
||||||
|
['#ff5858', '#f09819'],
|
||||||
|
['#2b5876', '#4e4376'],
|
||||||
|
['#00cdac', '#8ddad5'],
|
||||||
|
['#4481eb', '#04befe'],
|
||||||
|
['#dad4ec', '#f3e7e9'],
|
||||||
|
['#874da2', '#c43a30'],
|
||||||
|
['#4481eb', '#04befe'],
|
||||||
|
['#e8198b', '#c7eafd'],
|
||||||
|
['#f794a4', '#fdd6bd'],
|
||||||
|
['#64b3f4', '#c2e59c'],
|
||||||
|
['#0fd850', '#f9f047'],
|
||||||
|
['#ee9ca7', '#ffdde1'],
|
||||||
|
['#209cff', '#68e0cf'],
|
||||||
|
['#bdc2e8', '#e6dee9'],
|
||||||
|
['#e6b980', '#eacda3'],
|
||||||
|
['#1e3c72', '#2a5298'],
|
||||||
|
['#9be15d', '#00e3ae'],
|
||||||
|
['#ed6ea0', '#ec8c69'],
|
||||||
|
['#ffc3a0', '#ffafbd'],
|
||||||
|
['#cc208e', '#6713d2'],
|
||||||
|
['#b3ffab', '#12fff7'],
|
||||||
|
['#243949', '#517fa4'],
|
||||||
|
['#fc6076', '#ff9a44'],
|
||||||
|
['#dfe9f3', '#ffffff'],
|
||||||
|
['#00dbde', '#fc00ff'],
|
||||||
|
['#f9d423', '#ff4e50'],
|
||||||
|
['#50cc7f', '#f5d100'],
|
||||||
|
['#0acffe', '#495aff'],
|
||||||
|
['#616161', '#9bc5c3'],
|
||||||
|
['#df89b5', '#bfd9fe'],
|
||||||
|
['#ed6ea0', '#ec8c69'],
|
||||||
|
['#d7d2cc', '#304352'],
|
||||||
|
['#e14fad', '#f9d423'],
|
||||||
|
['#b224ef', '#7579ff'],
|
||||||
|
['#c1c161', '#d4d4b1'],
|
||||||
|
['#ec77ab', '#7873f5'],
|
||||||
|
['#007adf', '#00ecbc'],
|
||||||
|
['#20E2D7', '#F9FEA5'],
|
||||||
|
['#A8BFFF', '#884D80'],
|
||||||
|
['#B6CEE8', '#F578DC'],
|
||||||
|
['#FFFEFF', '#D7FFFE'],
|
||||||
|
['#E3FDF5', '#FFE6FA'],
|
||||||
|
['#7DE2FC', '#B9B6E5'],
|
||||||
|
['#CBBACC', '#2580B3'],
|
||||||
|
['#B7F8DB', '#50A7C2'],
|
||||||
|
['#007adf', '#00ecbc'],
|
||||||
|
];
|
||||||
|
|
||||||
|
export function Avatar({ value }: { value: string }) {
|
||||||
|
const hash = md5(value);
|
||||||
|
let num = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
num += parseInt(hash.substring(i * 4, i * 4 + 4), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = num % pallette.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id={`color-${hash}`} gradientTransform={`rotate(${index % 2 ? 90 : 0})`}>
|
||||||
|
<stop offset="0%" stopColor={pallette[index][0]} />
|
||||||
|
<stop offset="100%" stopColor={pallette[index][1]} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<circle cx="50" cy="50" r="50" fill={`url(#color-${hash})`} />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Avatar;
|
17
src/components/input/TeamsButton.module.css
Normal file
17
src/components/input/TeamsButton.module.css
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
.button {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
background: var(--base50);
|
||||||
|
right: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
color: var(--base600);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 8px 16px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-bottom: 1px solid var(--base300);
|
||||||
|
}
|
51
src/components/input/TeamsButton.tsx
Normal file
51
src/components/input/TeamsButton.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Key } from 'react';
|
||||||
|
import { Text, Icon, Button, Popup, Menu, Item, PopupTrigger, Flexbox } from 'react-basics';
|
||||||
|
import Icons from 'components/icons';
|
||||||
|
import { useLogin, useMessages, useNavigation } from 'components/hooks';
|
||||||
|
import Avatar from 'components/common/Avatar';
|
||||||
|
import styles from './TeamsButton.module.css';
|
||||||
|
|
||||||
|
export function TeamsButton({ teamId }: { teamId: string }) {
|
||||||
|
const { user } = useLogin();
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { router } = useNavigation();
|
||||||
|
|
||||||
|
const handleSelect = (close: () => void, id: Key) => {
|
||||||
|
if (id !== user.id) {
|
||||||
|
router.push(`/teams/${id}`);
|
||||||
|
} else {
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PopupTrigger>
|
||||||
|
<Button className={styles.button}>
|
||||||
|
<Icon>{teamId ? <Icons.Users /> : <Icons.User />}</Icon>
|
||||||
|
<Text>{teamId ? user.teams.find(({ id }) => id === teamId)?.name : user.username}</Text>
|
||||||
|
</Button>
|
||||||
|
<Popup alignment="end">
|
||||||
|
{close => (
|
||||||
|
<Menu variant="popup" onSelect={handleSelect.bind(null, close)}>
|
||||||
|
<div className={styles.heading}>{formatMessage(labels.myAccount)}</div>
|
||||||
|
<Item key={user.id}>{user.username}</Item>
|
||||||
|
<div className={styles.heading}>{formatMessage(labels.team)}</div>
|
||||||
|
{user.teams.map(({ id, name }) => (
|
||||||
|
<Item key={id}>
|
||||||
|
<Flexbox gap={10} alignItems="center">
|
||||||
|
<Icon size="md">
|
||||||
|
<Avatar value={id} />
|
||||||
|
</Icon>
|
||||||
|
<Text>{name}</Text>
|
||||||
|
</Flexbox>
|
||||||
|
</Item>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
</Popup>
|
||||||
|
</PopupTrigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TeamsButton;
|
@ -201,6 +201,7 @@ export const labels = defineMessages({
|
|||||||
defaultMessage: '{x} {x, plural, one {record} other {records}}',
|
defaultMessage: '{x} {x, plural, one {record} other {records}}',
|
||||||
},
|
},
|
||||||
select: { id: 'label.select', defaultMessage: 'Select' },
|
select: { id: 'label.select', defaultMessage: 'Select' },
|
||||||
|
myAccount: { id: 'label.my-account', defaultMessage: 'My account' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
|
1
src/declaration.d.ts
vendored
1
src/declaration.d.ts
vendored
@ -1,3 +1,4 @@
|
|||||||
declare module 'cors';
|
declare module 'cors';
|
||||||
declare module 'debug';
|
declare module 'debug';
|
||||||
declare module 'chartjs-adapter-date-fns';
|
declare module 'chartjs-adapter-date-fns';
|
||||||
|
declare module 'md5';
|
||||||
|
24
yarn.lock
24
yarn.lock
@ -3226,6 +3226,11 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
|
|||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.1.0"
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
|
charenc@0.0.2:
|
||||||
|
version "0.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||||
|
integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
|
||||||
|
|
||||||
chart.js@^4.2.1:
|
chart.js@^4.2.1:
|
||||||
version "4.4.1"
|
version "4.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.1.tgz#ac5dc0e69a7758909158a96fe80ce43b3bb96a9f"
|
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.1.tgz#ac5dc0e69a7758909158a96fe80ce43b3bb96a9f"
|
||||||
@ -3488,6 +3493,11 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
|||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
|
crypt@0.0.2:
|
||||||
|
version "0.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
|
||||||
|
integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
|
||||||
|
|
||||||
css-blank-pseudo@^3.0.3:
|
css-blank-pseudo@^3.0.3:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561"
|
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561"
|
||||||
@ -5330,6 +5340,11 @@ is-boolean-object@^1.1.0:
|
|||||||
call-bind "^1.0.2"
|
call-bind "^1.0.2"
|
||||||
has-tostringtag "^1.0.0"
|
has-tostringtag "^1.0.0"
|
||||||
|
|
||||||
|
is-buffer@~1.1.6:
|
||||||
|
version "1.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||||
|
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||||
|
|
||||||
is-builtin-module@^3.2.1:
|
is-builtin-module@^3.2.1:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169"
|
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169"
|
||||||
@ -6107,6 +6122,15 @@ maxmind@^4.3.6:
|
|||||||
mmdb-lib "2.1.0"
|
mmdb-lib "2.1.0"
|
||||||
tiny-lru "11.2.5"
|
tiny-lru "11.2.5"
|
||||||
|
|
||||||
|
md5@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
|
||||||
|
integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
|
||||||
|
dependencies:
|
||||||
|
charenc "0.0.2"
|
||||||
|
crypt "0.0.2"
|
||||||
|
is-buffer "~1.1.6"
|
||||||
|
|
||||||
mdn-data@2.0.14:
|
mdn-data@2.0.14:
|
||||||
version "2.0.14"
|
version "2.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
|
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
|
||||||
|
Loading…
Reference in New Issue
Block a user