mirror of
https://github.com/umami-software/umami.git
synced 2026-02-10 07:37:11 +01:00
Merge 7b4699a720 into a19b92a5cb
This commit is contained in:
commit
a64df3c2bf
17 changed files with 274 additions and 17 deletions
5
prisma/migrations/15_add_user_preferences/migration.sql
Normal file
5
prisma/migrations/15_add_user_preferences/migration.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "user" ADD COLUMN "date_range" VARCHAR(50),
|
||||||
|
ADD COLUMN "timezone" VARCHAR(100),
|
||||||
|
ADD COLUMN "language" VARCHAR(10),
|
||||||
|
ADD COLUMN "theme" VARCHAR(20);
|
||||||
|
|
@ -17,6 +17,10 @@ model User {
|
||||||
role String @map("role") @db.VarChar(50)
|
role String @map("role") @db.VarChar(50)
|
||||||
logoUrl String? @map("logo_url") @db.VarChar(2183)
|
logoUrl String? @map("logo_url") @db.VarChar(2183)
|
||||||
displayName String? @map("display_name") @db.VarChar(255)
|
displayName String? @map("display_name") @db.VarChar(255)
|
||||||
|
dateRange String? @map("date_range") @db.VarChar(50)
|
||||||
|
timezone String? @db.VarChar(200)
|
||||||
|
language String? @db.VarChar(10)
|
||||||
|
theme String? @db.VarChar(20)
|
||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||||
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,25 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { DateFilter } from '@/components/input/DateFilter';
|
import { DateFilter } from '@/components/input/DateFilter';
|
||||||
import { Button, Row } from '@umami/react-zen';
|
import { Button, Row } from '@umami/react-zen';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages, usePreferences } from '@/components/hooks';
|
||||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
||||||
import { setItem, getItem } from '@/lib/storage';
|
import { setItem, getItem } from '@/lib/storage';
|
||||||
|
|
||||||
export function DateRangeSetting() {
|
export function DateRangeSetting() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { updatePreferences } = usePreferences();
|
||||||
const [date, setDate] = useState(getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE);
|
const [date, setDate] = useState(getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE);
|
||||||
|
|
||||||
const handleChange = (value: string) => {
|
const handleChange = (value: string) => {
|
||||||
setItem(DATE_RANGE_CONFIG, value);
|
setItem(DATE_RANGE_CONFIG, value);
|
||||||
setDate(value);
|
setDate(value);
|
||||||
|
updatePreferences({ dateRange: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setItem(DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE);
|
setItem(DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE);
|
||||||
setDate(DEFAULT_DATE_RANGE_VALUE);
|
setDate(DEFAULT_DATE_RANGE_VALUE);
|
||||||
|
updatePreferences({ dateRange: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Button, Select, ListItem, Row } from '@umami/react-zen';
|
import { Button, Select, ListItem, Row } from '@umami/react-zen';
|
||||||
import { useLocale, useMessages } from '@/components/hooks';
|
import { useLocale, useMessages, usePreferences } from '@/components/hooks';
|
||||||
import { DEFAULT_LOCALE } from '@/lib/constants';
|
import { DEFAULT_LOCALE } from '@/lib/constants';
|
||||||
import { languages } from '@/lib/lang';
|
import { languages } from '@/lib/lang';
|
||||||
|
|
||||||
export function LanguageSetting() {
|
export function LanguageSetting() {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { updatePreferences } = usePreferences();
|
||||||
const { locale, saveLocale } = useLocale();
|
const { locale, saveLocale } = useLocale();
|
||||||
const items = search
|
const items = search
|
||||||
? Object.keys(languages).filter(n => {
|
? Object.keys(languages).filter(n => {
|
||||||
|
|
@ -17,7 +18,15 @@ export function LanguageSetting() {
|
||||||
})
|
})
|
||||||
: Object.keys(languages);
|
: Object.keys(languages);
|
||||||
|
|
||||||
const handleReset = () => saveLocale(DEFAULT_LOCALE);
|
const handleChange = (value: string) => {
|
||||||
|
saveLocale(value);
|
||||||
|
updatePreferences({ language: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
saveLocale(DEFAULT_LOCALE);
|
||||||
|
updatePreferences({ language: null });
|
||||||
|
};
|
||||||
|
|
||||||
const handleOpen = (isOpen: boolean) => {
|
const handleOpen = (isOpen: boolean) => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
|
|
@ -29,7 +38,7 @@ export function LanguageSetting() {
|
||||||
<Row gap>
|
<Row gap>
|
||||||
<Select
|
<Select
|
||||||
value={locale}
|
value={locale}
|
||||||
onChange={val => saveLocale(val as string)}
|
onChange={handleChange}
|
||||||
allowSearch
|
allowSearch
|
||||||
onSearch={setSearch}
|
onSearch={setSearch}
|
||||||
onOpenChange={handleOpen}
|
onOpenChange={handleOpen}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,42 @@
|
||||||
import { Row, Button, Icon, useTheme } from '@umami/react-zen';
|
import { Row, Button, Icon, useTheme } from '@umami/react-zen';
|
||||||
|
import { useMessages, usePreferences } from '@/components/hooks';
|
||||||
import { Sun, Moon } from '@/components/icons';
|
import { Sun, Moon } from '@/components/icons';
|
||||||
|
import { DEFAULT_THEME } from '@/lib/constants';
|
||||||
|
|
||||||
export function ThemeSetting() {
|
export function ThemeSetting() {
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { updatePreferences } = usePreferences();
|
||||||
|
|
||||||
|
const handleChange = (value: 'light' | 'dark') => {
|
||||||
|
setTheme(value);
|
||||||
|
updatePreferences({ theme: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
setTheme(DEFAULT_THEME);
|
||||||
|
updatePreferences({ theme: null });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gap>
|
<Row gap>
|
||||||
<Button variant={theme === 'light' ? 'primary' : undefined} onPress={() => setTheme('light')}>
|
<Button
|
||||||
|
variant={theme === 'light' ? 'primary' : undefined}
|
||||||
|
onPress={() => handleChange('light')}
|
||||||
|
>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Sun />
|
<Sun />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant={theme === 'dark' ? 'primary' : undefined} onPress={() => setTheme('dark')}>
|
<Button
|
||||||
|
variant={theme === 'dark' ? 'primary' : undefined}
|
||||||
|
onPress={() => handleChange('dark')}
|
||||||
|
>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Moon />
|
<Moon />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Row, Select, ListItem, Button } from '@umami/react-zen';
|
import { Row, Select, ListItem, Button } from '@umami/react-zen';
|
||||||
import { useTimezone, useMessages } from '@/components/hooks';
|
import { useTimezone, useMessages, usePreferences } from '@/components/hooks';
|
||||||
import { getTimezone } from '@/lib/date';
|
import { getTimezone } from '@/lib/date';
|
||||||
|
|
||||||
const timezones = Intl.supportedValuesOf('timeZone');
|
const timezones = Intl.supportedValuesOf('timeZone');
|
||||||
|
|
@ -8,12 +8,21 @@ const timezones = Intl.supportedValuesOf('timeZone');
|
||||||
export function TimezoneSetting() {
|
export function TimezoneSetting() {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { updatePreferences } = usePreferences();
|
||||||
const { timezone, saveTimezone } = useTimezone();
|
const { timezone, saveTimezone } = useTimezone();
|
||||||
const items = search
|
const items = search
|
||||||
? timezones.filter(n => n.toLowerCase().includes(search.toLowerCase()))
|
? timezones.filter(n => n.toLowerCase().includes(search.toLowerCase()))
|
||||||
: timezones;
|
: timezones;
|
||||||
|
|
||||||
const handleReset = () => saveTimezone(getTimezone());
|
const handleChange = (value: string) => {
|
||||||
|
saveTimezone(value);
|
||||||
|
updatePreferences({ timezone: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
saveTimezone(getTimezone());
|
||||||
|
updatePreferences({ timezone: null });
|
||||||
|
};
|
||||||
|
|
||||||
const handleOpen = isOpen => {
|
const handleOpen = isOpen => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
|
|
@ -25,7 +34,7 @@ export function TimezoneSetting() {
|
||||||
<Row gap>
|
<Row gap>
|
||||||
<Select
|
<Select
|
||||||
value={timezone}
|
value={timezone}
|
||||||
onChange={(value: any) => saveTimezone(value)}
|
onChange={handleChange}
|
||||||
allowSearch={true}
|
allowSearch={true}
|
||||||
onSearch={setSearch}
|
onSearch={setSearch}
|
||||||
onOpenChange={handleOpen}
|
onOpenChange={handleOpen}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { createSecureToken } from '@/lib/jwt';
|
import { createSecureToken } from '@/lib/jwt';
|
||||||
import redis from '@/lib/redis';
|
import redis from '@/lib/redis';
|
||||||
import { getUserByUsername } from '@/queries/prisma';
|
import { getUserByUsername, getUserPreferences } from '@/queries/prisma';
|
||||||
import { json, unauthorized } from '@/lib/response';
|
import { json, unauthorized } from '@/lib/response';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { parseRequest } from '@/lib/request';
|
||||||
import { saveAuth } from '@/lib/auth';
|
import { saveAuth } from '@/lib/auth';
|
||||||
|
|
@ -39,8 +39,10 @@ export async function POST(request: Request) {
|
||||||
token = createSecureToken({ userId: user.id, role }, secret());
|
token = createSecureToken({ userId: user.id, role }, secret());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const preferences = await getUserPreferences(id);
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
token,
|
token,
|
||||||
user: { id, username, role, createdAt, isAdmin: role === ROLES.admin },
|
user: { id, username, role, createdAt, isAdmin: role === ROLES.admin, preferences },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { parseRequest } from '@/lib/request';
|
import { parseRequest } from '@/lib/request';
|
||||||
import { json } from '@/lib/response';
|
import { json } from '@/lib/response';
|
||||||
import { getAllUserTeams } from '@/queries/prisma';
|
import { getAllUserTeams, getUserPreferences } from '@/queries/prisma';
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
const { auth, error } = await parseRequest(request);
|
const { auth, error } = await parseRequest(request);
|
||||||
|
|
@ -10,6 +10,7 @@ export async function POST(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const teams = await getAllUserTeams(auth.user.id);
|
const teams = await getAllUserTeams(auth.user.id);
|
||||||
|
const preferences = await getUserPreferences(auth.user.id);
|
||||||
|
|
||||||
return json({ ...auth.user, teams });
|
return json({ ...auth.user, teams, preferences });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
50
src/app/api/users/[userId]/preferences/route.ts
Normal file
50
src/app/api/users/[userId]/preferences/route.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { canUpdateUser, canViewUser } from '@/permissions';
|
||||||
|
import { getUserPreferences, updateUserPreferences } from '@/queries/prisma';
|
||||||
|
import { json, unauthorized } from '@/lib/response';
|
||||||
|
import { parseRequest } from '@/lib/request';
|
||||||
|
|
||||||
|
export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) {
|
||||||
|
const { auth, error } = await parseRequest(request);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userId } = await params;
|
||||||
|
|
||||||
|
if (!(await canViewUser(auth, userId))) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
const preferences = await getUserPreferences(userId);
|
||||||
|
|
||||||
|
return json(preferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: Request, { params }: { params: Promise<{ userId: string }> }) {
|
||||||
|
const schema = z.object({
|
||||||
|
dateRange: z.string().max(50).nullable().optional(),
|
||||||
|
timezone: z.string().max(100).nullable().optional(),
|
||||||
|
language: z.string().max(10).nullable().optional(),
|
||||||
|
theme: z.string().max(20).nullable().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { auth, body, error } = await parseRequest(request, schema);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userId } = await params;
|
||||||
|
|
||||||
|
if (!(await canUpdateUser(auth, userId))) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined));
|
||||||
|
|
||||||
|
const preferences = await updateUserPreferences(userId, data);
|
||||||
|
|
||||||
|
return json(preferences);
|
||||||
|
}
|
||||||
|
|
@ -8,22 +8,33 @@ import {
|
||||||
Icon,
|
Icon,
|
||||||
Column,
|
Column,
|
||||||
Heading,
|
Heading,
|
||||||
|
useTheme,
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useMessages, useUpdateQuery } from '@/components/hooks';
|
import { useMessages, useUpdateQuery } from '@/components/hooks';
|
||||||
import { setUser } from '@/store/app';
|
import { setUser } from '@/store/app';
|
||||||
import { setClientAuthToken } from '@/lib/client';
|
import { setClientAuthToken, setClientPreferences } from '@/lib/client';
|
||||||
import { Logo } from '@/components/svg';
|
import { Logo } from '@/components/svg';
|
||||||
|
import { DEFAULT_THEME } from '@/lib/constants';
|
||||||
|
|
||||||
export function LoginForm() {
|
export function LoginForm() {
|
||||||
const { formatMessage, labels, getErrorMessage } = useMessages();
|
const { formatMessage, labels, getErrorMessage } = useMessages();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutateAsync, error } = useUpdateQuery('/auth/login');
|
const { mutateAsync, error } = useUpdateQuery('/auth/login');
|
||||||
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
const handleSubmit = async (data: any) => {
|
const handleSubmit = async (data: any) => {
|
||||||
await mutateAsync(data, {
|
await mutateAsync(data, {
|
||||||
onSuccess: async ({ token, user }) => {
|
onSuccess: async ({ token, user }) => {
|
||||||
setClientAuthToken(token);
|
setClientAuthToken(token);
|
||||||
|
|
||||||
|
if (user.preferences) {
|
||||||
|
setClientPreferences(user.preferences);
|
||||||
|
|
||||||
|
const themeValue = user.preferences.theme || DEFAULT_THEME;
|
||||||
|
setTheme(themeValue);
|
||||||
|
}
|
||||||
|
|
||||||
setUser(user);
|
setUser(user);
|
||||||
|
|
||||||
router.push('/websites');
|
router.push('/websites');
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useApi } from '@/components/hooks';
|
import { useApi } from '@/components/hooks';
|
||||||
import { setUser } from '@/store/app';
|
import { setUser } from '@/store/app';
|
||||||
import { removeClientAuthToken } from '@/lib/client';
|
import { removeClientAuthToken, removeClientPreferences } from '@/lib/client';
|
||||||
|
|
||||||
export function LogoutPage() {
|
export function LogoutPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -17,6 +17,7 @@ export function LogoutPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeClientAuthToken();
|
removeClientAuthToken();
|
||||||
|
removeClientPreferences();
|
||||||
setUser(null);
|
setUser(null);
|
||||||
logout();
|
logout();
|
||||||
}, [router, post]);
|
}, [router, post]);
|
||||||
|
|
|
||||||
|
|
@ -82,3 +82,4 @@ export * from './useRegionNames';
|
||||||
export * from './useSlug';
|
export * from './useSlug';
|
||||||
export * from './useSticky';
|
export * from './useSticky';
|
||||||
export * from './useTimezone';
|
export * from './useTimezone';
|
||||||
|
export * from './usePreferences';
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,26 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useTheme } from '@umami/react-zen';
|
||||||
import { useApp, setUser } from '@/store/app';
|
import { useApp, setUser } from '@/store/app';
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
|
import { setClientPreferences } from '@/lib/client';
|
||||||
|
import { DEFAULT_THEME } from '@/lib/constants';
|
||||||
|
|
||||||
const selector = (state: { user: any }) => state.user;
|
const selector = (state: { user: any }) => state.user;
|
||||||
|
|
||||||
export function useLoginQuery() {
|
export function useLoginQuery() {
|
||||||
const { post, useQuery } = useApi();
|
const { post, useQuery } = useApi();
|
||||||
const user = useApp(selector);
|
const user = useApp(selector);
|
||||||
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: ['login'],
|
queryKey: ['login'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await post('/auth/verify');
|
const data = await post('/auth/verify');
|
||||||
|
|
||||||
|
if (data.preferences) {
|
||||||
|
setClientPreferences(data.preferences);
|
||||||
|
}
|
||||||
|
|
||||||
setUser(data);
|
setUser(data);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|
@ -19,5 +28,12 @@ export function useLoginQuery() {
|
||||||
enabled: !user,
|
enabled: !user,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (query.data?.preferences !== undefined) {
|
||||||
|
const themeValue = query.data.preferences.theme || DEFAULT_THEME;
|
||||||
|
setTheme(themeValue);
|
||||||
|
}
|
||||||
|
}, [query.data, setTheme]);
|
||||||
|
|
||||||
return { user, setUser, ...query };
|
return { user, setUser, ...query };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
src/components/hooks/usePreferences.ts
Normal file
29
src/components/hooks/usePreferences.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { User } from '@/generated/prisma/client';
|
||||||
|
import { useApi } from './useApi';
|
||||||
|
import { useApp } from '@/store/app';
|
||||||
|
|
||||||
|
const userSelector = (state: { user: User }) => state.user;
|
||||||
|
|
||||||
|
export function usePreferences() {
|
||||||
|
const { post } = useApi();
|
||||||
|
const user = useApp(userSelector);
|
||||||
|
|
||||||
|
const updatePreferences = async (preferences: {
|
||||||
|
dateRange?: string | null;
|
||||||
|
timezone?: string | null;
|
||||||
|
language?: string | null;
|
||||||
|
theme?: string | null;
|
||||||
|
}) => {
|
||||||
|
if (!user?.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await post(`/users/${user.id}/preferences`, preferences);
|
||||||
|
} catch {
|
||||||
|
// Silent fail: sync next login
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { updatePreferences };
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
import { getItem, setItem, removeItem } from '@/lib/storage';
|
import { getItem, setItem, removeItem } from '@/lib/storage';
|
||||||
import { AUTH_TOKEN } from './constants';
|
import {
|
||||||
|
AUTH_TOKEN,
|
||||||
|
LOCALE_CONFIG,
|
||||||
|
TIMEZONE_CONFIG,
|
||||||
|
DATE_RANGE_CONFIG,
|
||||||
|
THEME_CONFIG,
|
||||||
|
} from './constants';
|
||||||
|
import { setLocale, setTimezone, setDateRangeValue } from '@/store/app';
|
||||||
|
|
||||||
export function getClientAuthToken() {
|
export function getClientAuthToken() {
|
||||||
return getItem(AUTH_TOKEN);
|
return getItem(AUTH_TOKEN);
|
||||||
|
|
@ -12,3 +19,54 @@ export function setClientAuthToken(token: string) {
|
||||||
export function removeClientAuthToken() {
|
export function removeClientAuthToken() {
|
||||||
removeItem(AUTH_TOKEN);
|
removeItem(AUTH_TOKEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setClientPreferences(preferences: {
|
||||||
|
dateRange?: string | null;
|
||||||
|
timezone?: string | null;
|
||||||
|
language?: string | null;
|
||||||
|
theme?: string | null;
|
||||||
|
}) {
|
||||||
|
const { dateRange, timezone, language, theme } = preferences;
|
||||||
|
|
||||||
|
if (dateRange !== undefined) {
|
||||||
|
if (dateRange === null) {
|
||||||
|
removeItem(DATE_RANGE_CONFIG);
|
||||||
|
} else {
|
||||||
|
setItem(DATE_RANGE_CONFIG, dateRange);
|
||||||
|
setDateRangeValue(dateRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timezone !== undefined) {
|
||||||
|
if (timezone === null) {
|
||||||
|
removeItem(TIMEZONE_CONFIG);
|
||||||
|
} else {
|
||||||
|
setItem(TIMEZONE_CONFIG, timezone);
|
||||||
|
setTimezone(timezone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (language !== undefined) {
|
||||||
|
if (language === null) {
|
||||||
|
removeItem(LOCALE_CONFIG);
|
||||||
|
} else {
|
||||||
|
setItem(LOCALE_CONFIG, language);
|
||||||
|
setLocale(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theme !== undefined) {
|
||||||
|
if (theme === null) {
|
||||||
|
removeItem(THEME_CONFIG);
|
||||||
|
} else {
|
||||||
|
setItem(THEME_CONFIG, theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeClientPreferences() {
|
||||||
|
removeItem(DATE_RANGE_CONFIG);
|
||||||
|
removeItem(TIMEZONE_CONFIG);
|
||||||
|
removeItem(LOCALE_CONFIG);
|
||||||
|
removeItem(THEME_CONFIG);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ export const AUTH_TOKEN = 'umami.auth';
|
||||||
export const LOCALE_CONFIG = 'umami.locale';
|
export const LOCALE_CONFIG = 'umami.locale';
|
||||||
export const TIMEZONE_CONFIG = 'umami.timezone';
|
export const TIMEZONE_CONFIG = 'umami.timezone';
|
||||||
export const DATE_RANGE_CONFIG = 'umami.date-range';
|
export const DATE_RANGE_CONFIG = 'umami.date-range';
|
||||||
export const THEME_CONFIG = 'umami.theme';
|
export const THEME_CONFIG = 'zen.theme';
|
||||||
export const DASHBOARD_CONFIG = 'umami.dashboard';
|
export const DASHBOARD_CONFIG = 'umami.dashboard';
|
||||||
export const LAST_TEAM_CONFIG = 'umami.last-team';
|
export const LAST_TEAM_CONFIG = 'umami.last-team';
|
||||||
export const VERSION_CHECK = 'umami.version-check';
|
export const VERSION_CHECK = 'umami.version-check';
|
||||||
|
|
|
||||||
|
|
@ -203,3 +203,40 @@ export async function deleteUser(userId: string) {
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getUserPreferences(userId: string) {
|
||||||
|
return prisma.client.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
dateRange: true,
|
||||||
|
timezone: true,
|
||||||
|
language: true,
|
||||||
|
theme: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateUserPreferences(
|
||||||
|
userId: string,
|
||||||
|
data: {
|
||||||
|
dateRange?: string;
|
||||||
|
timezone?: string;
|
||||||
|
language?: string;
|
||||||
|
theme?: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
return prisma.client.user.update({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
select: {
|
||||||
|
dateRange: true,
|
||||||
|
timezone: true,
|
||||||
|
language: true,
|
||||||
|
theme: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue