mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Merge branch 'dev' of https://github.com/umami-software/umami into dev
This commit is contained in:
commit
5bbc1a94b3
16 changed files with 116 additions and 107 deletions
|
|
@ -82,7 +82,7 @@
|
|||
"@react-spring/web": "^10.0.1",
|
||||
"@svgr/cli": "^8.1.0",
|
||||
"@tanstack/react-query": "^5.85.5",
|
||||
"@umami/react-zen": "^0.184.0",
|
||||
"@umami/react-zen": "^0.186.0",
|
||||
"@umami/redis-client": "^0.29.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"chalk": "^5.6.0",
|
||||
|
|
@ -130,6 +130,7 @@
|
|||
"semver": "^7.5.4",
|
||||
"serialize-error": "^12.0.0",
|
||||
"thenby": "^1.3.4",
|
||||
"ua-parser-js": "^2.0.5",
|
||||
"uuid": "^11.1.0",
|
||||
"zod": "^4.1.5",
|
||||
"zustand": "^5.0.8"
|
||||
|
|
|
|||
47
pnpm-lock.yaml
generated
47
pnpm-lock.yaml
generated
|
|
@ -45,8 +45,8 @@ importers:
|
|||
specifier: ^5.85.5
|
||||
version: 5.85.5(react@19.1.1)
|
||||
'@umami/react-zen':
|
||||
specifier: ^0.184.0
|
||||
version: 0.184.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
|
||||
specifier: ^0.186.0
|
||||
version: 0.186.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1))
|
||||
'@umami/redis-client':
|
||||
specifier: ^0.29.0
|
||||
version: 0.29.0
|
||||
|
|
@ -188,6 +188,9 @@ importers:
|
|||
thenby:
|
||||
specifier: ^1.3.4
|
||||
version: 1.3.4
|
||||
ua-parser-js:
|
||||
specifier: ^2.0.5
|
||||
version: 2.0.5
|
||||
uuid:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
|
|
@ -364,6 +367,8 @@ importers:
|
|||
specifier: ^5.9.2
|
||||
version: 5.9.2
|
||||
|
||||
dist: {}
|
||||
|
||||
packages:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
|
|
@ -2733,8 +2738,8 @@ packages:
|
|||
'@prisma/client': ^6.1.0
|
||||
'@prisma/extension-read-replicas': ^0.4.1
|
||||
|
||||
'@umami/react-zen@0.184.0':
|
||||
resolution: {integrity: sha512-XfxTiP4ljumflx02ymDMXLnhcJW+mOxxKCPEVEjuDrQfR6VUlbHg0EdH04S4gvCJZJC/WnP6guyO2eabhJL88Q==}
|
||||
'@umami/react-zen@0.186.0':
|
||||
resolution: {integrity: sha512-s+x4cJK5UTHQ0l2TTUb3zX8P2U6bMw35NRjIqG+OJvljJf5NNdRo6WChZOvnh/08XxGI30jntFhUYdup255rFg==}
|
||||
|
||||
'@umami/redis-client@0.29.0':
|
||||
resolution: {integrity: sha512-Jaqh++jskqDB7ny75pfC02OvKp1JTS4asGDsFrRL3qy8sxL3PAl9+/mybCJe4/6vWrXDJKqpgkSfUDJq2bFjyw==}
|
||||
|
|
@ -3695,6 +3700,9 @@ packages:
|
|||
detect-browser@5.3.0:
|
||||
resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==}
|
||||
|
||||
detect-europe-js@0.1.2:
|
||||
resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==}
|
||||
|
||||
detect-indent@6.1.0:
|
||||
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -4719,6 +4727,9 @@ packages:
|
|||
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-standalone-pwa@0.1.1:
|
||||
resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==}
|
||||
|
||||
is-stream@2.0.1:
|
||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -7202,6 +7213,13 @@ packages:
|
|||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ua-is-frozen@0.1.2:
|
||||
resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==}
|
||||
|
||||
ua-parser-js@2.0.5:
|
||||
resolution: {integrity: sha512-sZErtx3rhpvZQanWW5umau4o/snfoLqRcQwQIZ54377WtRzIecnIKvjpkd5JwPcSUMglGnbIgcsQBGAbdi3S9Q==}
|
||||
hasBin: true
|
||||
|
||||
ufo@1.6.1:
|
||||
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
|
||||
|
||||
|
|
@ -7217,6 +7235,10 @@ packages:
|
|||
undici-types@7.10.0:
|
||||
resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
|
||||
|
||||
undici@7.16.0:
|
||||
resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
unicorn-magic@0.3.0:
|
||||
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -10321,7 +10343,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@umami/react-zen@0.184.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1))':
|
||||
'@umami/react-zen@0.186.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1))':
|
||||
dependencies:
|
||||
'@fontsource/jetbrains-mono': 5.2.8
|
||||
'@internationalized/date': 3.9.0
|
||||
|
|
@ -11398,6 +11420,8 @@ snapshots:
|
|||
|
||||
detect-browser@5.3.0: {}
|
||||
|
||||
detect-europe-js@0.1.2: {}
|
||||
|
||||
detect-indent@6.1.0: {}
|
||||
|
||||
detect-libc@2.0.4:
|
||||
|
|
@ -12606,6 +12630,8 @@ snapshots:
|
|||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
|
||||
is-standalone-pwa@0.1.1: {}
|
||||
|
||||
is-stream@2.0.1: {}
|
||||
|
||||
is-string@1.1.1:
|
||||
|
|
@ -15546,6 +15572,15 @@ snapshots:
|
|||
|
||||
typescript@5.9.2: {}
|
||||
|
||||
ua-is-frozen@0.1.2: {}
|
||||
|
||||
ua-parser-js@2.0.5:
|
||||
dependencies:
|
||||
detect-europe-js: 0.1.2
|
||||
is-standalone-pwa: 0.1.1
|
||||
ua-is-frozen: 0.1.2
|
||||
undici: 7.16.0
|
||||
|
||||
ufo@1.6.1: {}
|
||||
|
||||
uglify-js@3.19.3:
|
||||
|
|
@ -15560,6 +15595,8 @@ snapshots:
|
|||
|
||||
undici-types@7.10.0: {}
|
||||
|
||||
undici@7.16.0: {}
|
||||
|
||||
unicorn-magic@0.3.0: {}
|
||||
|
||||
universalify@0.1.2: {}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from '@umami/react-zen';
|
||||
import { Globe, LinkIcon, LogoSvg, Grid2x2, PanelLeft } from '@/components/icons';
|
||||
import { useMessages, useNavigation, useGlobalState } from '@/components/hooks';
|
||||
import { TeamsButton } from '@/components/input/TeamsButton';
|
||||
import { NavButton } from '@/components/input/NavButton';
|
||||
import { PanelButton } from '@/components/input/PanelButton';
|
||||
import { Key } from 'react';
|
||||
import { SettingsButton } from '@/components/input/SettingsButton';
|
||||
|
|
@ -48,8 +48,8 @@ export function SideNav(props: SidebarProps) {
|
|||
};
|
||||
|
||||
return (
|
||||
<Row height="100%" backgroundColor border="right">
|
||||
<Sidebar {...props} isCollapsed={isCollapsed || hasNav} muteItems={false} showBorder={false}>
|
||||
<Row height="100%" backgroundColor>
|
||||
<Sidebar {...props} isCollapsed={isCollapsed || hasNav}>
|
||||
<SidebarSection onClick={() => setIsCollapsed(false)}>
|
||||
<SidebarHeader
|
||||
label="umami"
|
||||
|
|
@ -60,7 +60,7 @@ export function SideNav(props: SidebarProps) {
|
|||
</SidebarHeader>
|
||||
</SidebarSection>
|
||||
<SidebarSection paddingTop="0" paddingBottom="0" justifyContent="center">
|
||||
<TeamsButton showText={!hasNav && !isCollapsed} onAction={handleSelect} />
|
||||
<NavButton showText={!hasNav && !isCollapsed} onAction={handleSelect} />
|
||||
</SidebarSection>
|
||||
<SidebarSection flexGrow={1}>
|
||||
{links.map(({ id, path, label, icon }) => {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
|
|||
title={formatMessage(labels.settings)}
|
||||
selectedKey={selectedKey}
|
||||
allowMinimize={false}
|
||||
muteItems={false}
|
||||
/>
|
||||
</Column>
|
||||
<Column gap="6" margin="2">
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ export function WebsiteExpandedView({
|
|||
return (
|
||||
<Grid columns="auto 1fr" gap="6" height="100%" overflow="hidden">
|
||||
<Column gap="6" border="right" paddingRight="3" overflowY="auto">
|
||||
<SideMenu items={items} selectedKey={view} muteItems={false} />
|
||||
<SideMenu items={items} selectedKey={view} />
|
||||
</Column>
|
||||
<Column overflow="hidden">
|
||||
<MetricsExpandedTable
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) {
|
|||
.find(({ path }) => path && pathname.endsWith(path.split('?')[0]))?.id;
|
||||
|
||||
return (
|
||||
<SideMenu items={items} selectedKey={selectedKey} allowMinimize={false} muteItems={false}>
|
||||
<SideMenu items={items} selectedKey={selectedKey} allowMinimize={false}>
|
||||
<WebsiteSelect
|
||||
websiteId={websiteId}
|
||||
teamId={teamId}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { parseRequest } from '@/lib/request';
|
||||
import { json } from '@/lib/response';
|
||||
import { getAllUserTeams } from '@/queries';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const { auth, error } = await parseRequest(request);
|
||||
|
|
@ -8,5 +9,7 @@ export async function POST(request: Request) {
|
|||
return error();
|
||||
}
|
||||
|
||||
return json(auth.user);
|
||||
const teams = await getAllUserTeams(auth.user.id);
|
||||
|
||||
return json({ ...auth.user, teams });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { CURRENT_VERSION, HOMEPAGE_URL } from '@/lib/constants';
|
|||
|
||||
export function Footer() {
|
||||
return (
|
||||
<Row as="footer">
|
||||
<Row as="footer" paddingY="6" justifyContent="flex-end">
|
||||
<a href={HOMEPAGE_URL} target="_blank">
|
||||
<Text weight="bold">umami</Text> {`v${CURRENT_VERSION}`}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
import { Row, Icon, Text, ThemeButton } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import { LanguageButton } from '@/components/input/LanguageButton';
|
||||
import { PreferencesButton } from '@/components/input/PreferencesButton';
|
||||
import { LogoSvg } from '@/components/icons';
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
<Row as="header">
|
||||
<Row gap>
|
||||
<Link href="https://umami.is" target="_blank">
|
||||
<Icon size="lg">
|
||||
<Row as="header" justifyContent="space-between" alignItems="center" paddingY="3">
|
||||
<a href="https://umami.is" target="_blank">
|
||||
<Row alignItems="center" gap>
|
||||
<Icon>
|
||||
<LogoSvg />
|
||||
</Icon>
|
||||
<Text>umami</Text>
|
||||
</Link>
|
||||
</Row>
|
||||
<Text weight="bold">umami</Text>
|
||||
</Row>
|
||||
</a>
|
||||
<Row alignItems="center" gap>
|
||||
<ThemeButton />
|
||||
<LanguageButton />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
'use client';
|
||||
import { Column } from '@umami/react-zen';
|
||||
import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
|
||||
import { WebsitePage } from '@/app/(main)/websites/[websiteId]/WebsitePage';
|
||||
import { useShareTokenQuery } from '@/components/hooks';
|
||||
|
|
@ -14,12 +15,14 @@ export function SharePage({ shareId }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<PageBody>
|
||||
<Header />
|
||||
<WebsiteProvider websiteId={shareToken.websiteId}>
|
||||
<WebsitePage websiteId={shareToken.websiteId} />
|
||||
</WebsiteProvider>
|
||||
<Footer />
|
||||
</PageBody>
|
||||
<Column backgroundColor="2">
|
||||
<PageBody gap>
|
||||
<Header />
|
||||
<WebsiteProvider websiteId={shareToken.websiteId}>
|
||||
<WebsitePage websiteId={shareToken.websiteId} />
|
||||
</WebsiteProvider>
|
||||
<Footer />
|
||||
</PageBody>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ import {
|
|||
Row,
|
||||
Column,
|
||||
Pressable,
|
||||
Loading,
|
||||
} from '@umami/react-zen';
|
||||
import { useLoginQuery, useMessages, useUserTeamsQuery, useNavigation } from '@/components/hooks';
|
||||
import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { ChevronRight, User, Users } from '@/components/icons';
|
||||
|
||||
export interface TeamsButtonProps {
|
||||
|
|
@ -20,19 +19,14 @@ export interface TeamsButtonProps {
|
|||
onAction?: (id: any) => void;
|
||||
}
|
||||
|
||||
export function TeamsButton({ showText = true, onAction }: TeamsButtonProps) {
|
||||
export function NavButton({ showText = true, onAction }: TeamsButtonProps) {
|
||||
const { user } = useLoginQuery();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { data, isLoading } = useUserTeamsQuery(user.id);
|
||||
const { teamId } = useNavigation();
|
||||
const team = data?.data?.find(({ id }) => id === teamId);
|
||||
const team = user?.teams?.find(({ id }) => id === teamId);
|
||||
const selectedKeys = new Set([teamId || 'user']);
|
||||
const label = teamId ? team?.name : user.username;
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading icon="dots" size="sm" placement="center" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuTrigger>
|
||||
<Pressable>
|
||||
|
|
@ -41,10 +35,13 @@ export function TeamsButton({ showText = true, onAction }: TeamsButtonProps) {
|
|||
justifyContent="space-between"
|
||||
flexGrow={1}
|
||||
padding
|
||||
backgroundColor="2"
|
||||
border
|
||||
borderRadius
|
||||
shadow="1"
|
||||
maxHeight="40px"
|
||||
style={{ cursor: 'pointer', textWrap: 'nowrap', outline: 'none' }}
|
||||
>
|
||||
<Row alignItems="center" gap>
|
||||
<Row alignItems="center" position="relative" gap maxHeight="40px">
|
||||
<Icon>{teamId ? <Users /> : <User />}</Icon>
|
||||
{showText && <Text>{label}</Text>}
|
||||
</Row>
|
||||
|
|
@ -75,7 +72,7 @@ export function TeamsButton({ showText = true, onAction }: TeamsButtonProps) {
|
|||
</MenuSection>
|
||||
<MenuSeparator />
|
||||
<MenuSection title={formatMessage(labels.teams)}>
|
||||
{data?.data?.map(({ id, name }) => (
|
||||
{user?.teams?.map(({ id, name }) => (
|
||||
<MenuItem key={id} id={id}>
|
||||
<Row alignItems="center" gap>
|
||||
<Icon size="sm">
|
||||
|
|
@ -3,6 +3,7 @@ import { TimezoneSetting } from '@/app/(main)/settings/preferences/TimezoneSetti
|
|||
import { DateRangeSetting } from '@/app/(main)/settings/preferences/DateRangeSetting';
|
||||
import { Settings } from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
export function PreferencesButton() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
@ -15,12 +16,16 @@ export function PreferencesButton() {
|
|||
</Icon>
|
||||
</Button>
|
||||
<Popover placement="bottom end">
|
||||
<Column gap="3">
|
||||
<Label>{formatMessage(labels.timezone)}</Label>
|
||||
<TimezoneSetting />
|
||||
<Label>{formatMessage(labels.defaultDateRange)}</Label>
|
||||
<DateRangeSetting />
|
||||
</Column>
|
||||
<Panel gap="3">
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.timezone)}</Label>
|
||||
<TimezoneSetting />
|
||||
</Column>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.defaultDateRange)}</Label>
|
||||
<DateRangeSetting />
|
||||
</Column>
|
||||
</Panel>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export * from '@/components/input/DateFilter';
|
|||
export * from '@/components/input/DownloadButton';
|
||||
export * from '@/components/input/ExportButton';
|
||||
export * from '@/components/input/FilterButtons';
|
||||
export * from '@/components/input/TeamsButton';
|
||||
export * from '@/components/input/NavButton';
|
||||
export * from '@/components/input/ProfileButton';
|
||||
export * from '@/components/input/WebsiteSelect';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import path from 'path';
|
||||
import path from 'node:path';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { browserName, detectOS } from 'detect-browser';
|
||||
import isLocalhost from 'is-localhost-ip';
|
||||
import ipaddr from 'ipaddr.js';
|
||||
|
|
@ -7,35 +8,6 @@ import { safeDecodeURIComponent } from '@/lib/url';
|
|||
|
||||
const MAXMIND = 'maxmind';
|
||||
|
||||
export const DESKTOP_OS = [
|
||||
'BeOS',
|
||||
'Chrome OS',
|
||||
'Linux',
|
||||
'Mac OS',
|
||||
'Open BSD',
|
||||
'OS/2',
|
||||
'QNX',
|
||||
'Sun OS',
|
||||
'Windows 10',
|
||||
'Windows 2000',
|
||||
'Windows 3.11',
|
||||
'Windows 7',
|
||||
'Windows 8',
|
||||
'Windows 8.1',
|
||||
'Windows 95',
|
||||
'Windows 98',
|
||||
'Windows ME',
|
||||
'Windows Server 2003',
|
||||
'Windows Vista',
|
||||
'Windows XP',
|
||||
];
|
||||
|
||||
export const MOBILE_OS = ['Amazon OS', 'Android OS', 'BlackBerry OS', 'iOS', 'Windows Mobile'];
|
||||
|
||||
export const DESKTOP_SCREEN_WIDTH = 1920;
|
||||
export const LAPTOP_SCREEN_WIDTH = 1024;
|
||||
export const MOBILE_SCREEN_WIDTH = 479;
|
||||
|
||||
// The order here is important and influences how IPs are detected by lib/detect.ts
|
||||
// Please do not change the order unless you know exactly what you're doing - read https://developers.cloudflare.com/fundamentals/reference/http-headers/
|
||||
export const IP_ADDRESS_HEADERS = [
|
||||
|
|
@ -121,32 +93,10 @@ export function getIpAddress(headers: Headers) {
|
|||
return ip;
|
||||
}
|
||||
|
||||
export function getDevice(screen: string, os: string) {
|
||||
if (!screen) return;
|
||||
export function getDevice(userAgent: string) {
|
||||
const { device } = UAParser(userAgent);
|
||||
|
||||
const [width] = screen.split('x');
|
||||
|
||||
if (DESKTOP_OS.includes(os)) {
|
||||
if (os === 'Chrome OS' || +width < DESKTOP_SCREEN_WIDTH) {
|
||||
return 'laptop';
|
||||
}
|
||||
return 'desktop';
|
||||
} else if (MOBILE_OS.includes(os)) {
|
||||
if (os === 'Amazon OS' || +width > MOBILE_SCREEN_WIDTH) {
|
||||
return 'tablet';
|
||||
}
|
||||
return 'mobile';
|
||||
}
|
||||
|
||||
if (+width >= DESKTOP_SCREEN_WIDTH) {
|
||||
return 'desktop';
|
||||
} else if (+width >= LAPTOP_SCREEN_WIDTH) {
|
||||
return 'laptop';
|
||||
} else if (+width >= MOBILE_SCREEN_WIDTH) {
|
||||
return 'tablet';
|
||||
} else {
|
||||
return 'mobile';
|
||||
}
|
||||
return device?.type || 'desktop';
|
||||
}
|
||||
|
||||
function getRegionCode(country: string, region: string) {
|
||||
|
|
@ -221,7 +171,7 @@ export async function getClientInfo(request: Request, payload: Record<string, an
|
|||
const city = safeDecodeURIComponent(location?.city);
|
||||
const browser = browserName(userAgent);
|
||||
const os = detectOS(userAgent) as string;
|
||||
const device = getDevice(payload?.screen, os);
|
||||
const device = getDevice(userAgent);
|
||||
|
||||
return { userAgent, browser, os, ip, country, region, city, device };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export async function getTeams(
|
|||
);
|
||||
}
|
||||
|
||||
export async function getUserTeams(userId: string, filters: QueryFilters) {
|
||||
export async function getUserTeams(userId: string, filters: QueryFilters = {}) {
|
||||
return getTeams(
|
||||
{
|
||||
where: {
|
||||
|
|
@ -80,6 +80,22 @@ export async function getUserTeams(userId: string, filters: QueryFilters) {
|
|||
);
|
||||
}
|
||||
|
||||
export async function getAllUserTeams(userId: string) {
|
||||
return prisma.client.team.findMany({
|
||||
where: {
|
||||
deletedAt: null,
|
||||
members: {
|
||||
some: { userId },
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
logoUrl: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function createTeam(data: Prisma.TeamCreateInput, userId: string): Promise<any> {
|
||||
const { id } = data;
|
||||
const { client, transaction } = prisma;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ body {
|
|||
background-color: var(--background-color);
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
html[style*='padding-right'] {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue