diff --git a/src/app/(main)/NavBar.tsx b/src/app/(main)/NavBar.tsx index 6eb46cb2..8bdee61a 100644 --- a/src/app/(main)/NavBar.tsx +++ b/src/app/(main)/NavBar.tsx @@ -6,31 +6,30 @@ import Icons from 'components/icons'; import ThemeButton from 'components/input/ThemeButton'; import LanguageButton from 'components/input/LanguageButton'; import ProfileButton from 'components/input/ProfileButton'; -import { useMessages } from 'components/hooks'; +import { useMessages, useNavigation } from 'components/hooks'; import HamburgerButton from 'components/common/HamburgerButton'; -import { usePathname } from 'next/navigation'; import styles from './NavBar.module.css'; export function NavBar() { - const pathname = usePathname(); const { formatMessage, labels } = useMessages(); const cloudMode = Boolean(process.env.cloudMode); + const { pathname, renderTeamUrl } = useNavigation(); const links = [ - { label: formatMessage(labels.dashboard), url: '/dashboard' }, - { label: formatMessage(labels.websites), url: '/websites' }, - { label: formatMessage(labels.reports), url: '/reports' }, - { label: formatMessage(labels.settings), url: '/settings' }, + { label: formatMessage(labels.dashboard), url: renderTeamUrl('/dashboard') }, + { label: formatMessage(labels.websites), url: renderTeamUrl('/websites') }, + { label: formatMessage(labels.reports), url: renderTeamUrl('/reports') }, + { label: formatMessage(labels.settings), url: renderTeamUrl('/settings') }, ].filter(n => n); const menuItems = [ { label: formatMessage(labels.dashboard), - url: '/dashboard', + url: renderTeamUrl('/dashboard'), }, !cloudMode && { label: formatMessage(labels.settings), - url: '/settings', + url: renderTeamUrl('/settings'), children: [ { label: formatMessage(labels.websites), diff --git a/src/app/(main)/console/[[...id]]/page.tsx b/src/app/(main)/console/[[...websiteId]]/page.tsx similarity index 73% rename from src/app/(main)/console/[[...id]]/page.tsx rename to src/app/(main)/console/[[...websiteId]]/page.tsx index f3d865f6..3c4b8bb8 100644 --- a/src/app/(main)/console/[[...id]]/page.tsx +++ b/src/app/(main)/console/[[...websiteId]]/page.tsx @@ -5,14 +5,14 @@ async function getEnabled() { return !!process.env.ENABLE_TEST_CONSOLE; } -export default async function ({ params: { id } }) { +export default async function ({ params: { websiteId } }) { const enabled = await getEnabled(); if (!enabled) { return null; } - return ; + return ; } export const metadata: Metadata = { diff --git a/src/app/(main)/reports/ReportsDataTable.tsx b/src/app/(main)/reports/ReportsDataTable.tsx index 3ede4783..5e0de599 100644 --- a/src/app/(main)/reports/ReportsDataTable.tsx +++ b/src/app/(main)/reports/ReportsDataTable.tsx @@ -3,8 +3,14 @@ import { useReports } from 'components/hooks'; import ReportsTable from './ReportsTable'; import DataTable from 'components/common/DataTable'; -export default function ReportsDataTable({ websiteId }: { websiteId?: string }) { - const queryResult = useReports(websiteId); +export default function ReportsDataTable({ + websiteId, + teamId, +}: { + websiteId?: string; + teamId?: string; +}) { + const queryResult = useReports({ websiteId, teamId }); return ( diff --git a/src/app/(main)/reports/ReportsHeader.tsx b/src/app/(main)/reports/ReportsHeader.tsx index 43cce217..0f885af7 100644 --- a/src/app/(main)/reports/ReportsHeader.tsx +++ b/src/app/(main)/reports/ReportsHeader.tsx @@ -1,23 +1,21 @@ 'use client'; import PageHeader from 'components/layout/PageHeader'; -import { Button, Icon, Icons, Text } from 'react-basics'; -import { useMessages } from 'components/hooks'; -import { useRouter } from 'next/navigation'; +import { Icon, Icons, Text } from 'react-basics'; +import { useMessages, useNavigation } from 'components/hooks'; +import LinkButton from 'components/common/LinkButton'; export function ReportsHeader() { const { formatMessage, labels } = useMessages(); - const router = useRouter(); - - const handleClick = () => router.push('/reports/create'); + const { renderTeamUrl } = useNavigation(); return ( - + ); } diff --git a/src/app/(main)/reports/create/ReportTemplates.tsx b/src/app/(main)/reports/create/ReportTemplates.tsx index 003cb3fc..886146b2 100644 --- a/src/app/(main)/reports/create/ReportTemplates.tsx +++ b/src/app/(main)/reports/create/ReportTemplates.tsx @@ -6,7 +6,7 @@ import Funnel from 'assets/funnel.svg'; import Lightbulb from 'assets/lightbulb.svg'; import Magnet from 'assets/magnet.svg'; import styles from './ReportTemplates.module.css'; -import { useMessages } from 'components/hooks'; +import { useMessages, useNavigation } from 'components/hooks'; function ReportItem({ title, description, url, icon }) { const { formatMessage, labels } = useMessages(); @@ -32,26 +32,27 @@ function ReportItem({ title, description, url, icon }) { ); } -export function ReportTemplates({ showHeader = true }) { +export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) { const { formatMessage, labels } = useMessages(); + const { renderTeamUrl } = useNavigation(); const reports = [ { title: formatMessage(labels.insights), description: formatMessage(labels.insightsDescription), - url: '/reports/insights', + url: renderTeamUrl('/reports/insights'), icon: , }, { title: formatMessage(labels.funnel), description: formatMessage(labels.funnelDescription), - url: '/reports/funnel', + url: renderTeamUrl('/reports/funnel'), icon: , }, { title: formatMessage(labels.retention), description: formatMessage(labels.retentionDescription), - url: '/reports/retention', + url: renderTeamUrl('/reports/retention'), icon: , }, ]; diff --git a/src/app/(main)/reports/retention/page.js b/src/app/(main)/reports/retention/page.tsx similarity index 72% rename from src/app/(main)/reports/retention/page.js rename to src/app/(main)/reports/retention/page.tsx index 7c60cee8..4e316793 100644 --- a/src/app/(main)/reports/retention/page.js +++ b/src/app/(main)/reports/retention/page.tsx @@ -1,9 +1,10 @@ +import { Metadata } from 'next'; import RetentionReport from './RetentionReport'; export default function RetentionReportPage() { return ; } -export const metadata = { +export const metadata: Metadata = { title: 'Create Report | umami', }; diff --git a/src/app/(main)/settings/profile/LanguageSetting.tsx b/src/app/(main)/settings/profile/LanguageSetting.tsx index 40546f4d..3004af1e 100644 --- a/src/app/(main)/settings/profile/LanguageSetting.tsx +++ b/src/app/(main)/settings/profile/LanguageSetting.tsx @@ -28,7 +28,7 @@ export function LanguageSetting() { items={options} value={locale} renderValue={renderValue} - onChange={saveLocale} + onChange={val => saveLocale(val as string)} allowSearch={true} onSearch={setSearch} menuProps={{ className: styles.menu }} diff --git a/src/app/(main)/settings/teams/TeamAddForm.tsx b/src/app/(main)/settings/teams/TeamAddForm.tsx index 24a8d96e..0af3cc8f 100644 --- a/src/app/(main)/settings/teams/TeamAddForm.tsx +++ b/src/app/(main)/settings/teams/TeamAddForm.tsx @@ -7,7 +7,7 @@ import { Button, SubmitButton, } from 'react-basics'; -import { setValue } from 'store/cache'; +import { touch } from 'store/cache'; import { useApi, useMessages } from 'components/hooks'; export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) { @@ -17,10 +17,10 @@ export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: mutationFn: (data: any) => post('/teams', data), }); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { - setValue('teams', Date.now()); + touch('teams'); onSave?.(); onClose?.(); }, diff --git a/src/app/(main)/settings/teams/TeamJoinForm.tsx b/src/app/(main)/settings/teams/TeamJoinForm.tsx index 9a91d1a5..5c292b51 100644 --- a/src/app/(main)/settings/teams/TeamJoinForm.tsx +++ b/src/app/(main)/settings/teams/TeamJoinForm.tsx @@ -9,7 +9,7 @@ import { SubmitButton, } from 'react-basics'; import { useApi, useMessages } from 'components/hooks'; -import { setValue } from 'store/cache'; +import { touch } from 'store/cache'; export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) { const { formatMessage, labels, getMessage } = useMessages(); @@ -20,7 +20,7 @@ export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { - setValue('teams:members', Date.now()); + touch('teams:members'); onSave?.(); onClose?.(); }, diff --git a/src/app/(main)/settings/teams/[teamId]/TeamEditForm.tsx b/src/app/(main)/settings/teams/[teamId]/TeamEditForm.tsx index 6cc8de5d..8af74a26 100644 --- a/src/app/(main)/settings/teams/[teamId]/TeamEditForm.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamEditForm.tsx @@ -18,11 +18,11 @@ const generateId = () => getRandomChars(16); export function TeamEditForm({ teamId, data, - readOnly, + allowEdit, }: { teamId: string; data?: { name: string; accessCode: string }; - readOnly?: boolean; + allowEdit?: boolean; }) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); @@ -57,22 +57,24 @@ export function TeamEditForm({ - {!readOnly && ( + {allowEdit && ( )} - {readOnly && data.name} + {!allowEdit && data.name} - - - - {!readOnly && ( - - )} - - - {!readOnly && ( + {allowEdit && ( + + + + {allowEdit && ( + + )} + + + )} + {allowEdit && ( {formatMessage(labels.save)} diff --git a/src/app/(main)/settings/teams/[teamId]/TeamMemberRemoveButton.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMemberRemoveButton.tsx index a5e05bd7..19f29fd2 100644 --- a/src/app/(main)/settings/teams/[teamId]/TeamMemberRemoveButton.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamMemberRemoveButton.tsx @@ -1,6 +1,6 @@ import { useApi, useMessages } from 'components/hooks'; import { Icon, Icons, LoadingButton, Text } from 'react-basics'; -import { setValue } from 'store/cache'; +import { touch } from 'store/cache'; export function TeamMemberRemoveButton({ teamId, @@ -22,7 +22,7 @@ export function TeamMemberRemoveButton({ const handleRemoveTeamMember = () => { mutate(null, { onSuccess: () => { - setValue('team:members', Date.now()); + touch('team:members'); onSave?.(); }, }); diff --git a/src/app/(main)/settings/teams/[teamId]/TeamMembers.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMembers.tsx index cd6965c5..75e88e25 100644 --- a/src/app/(main)/settings/teams/[teamId]/TeamMembers.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamMembers.tsx @@ -1,24 +1,13 @@ -import { useApi, useFilterQuery } from 'components/hooks'; import DataTable from 'components/common/DataTable'; -import useCache from 'store/cache'; import TeamMembersTable from './TeamMembersTable'; +import useTeamMembers from 'components/hooks/queries/useTeamMembers'; -export function TeamMembers({ teamId, readOnly }: { teamId: string; readOnly: boolean }) { - const { get } = useApi(); - const modified = useCache(state => state?.['team:members']); - const queryResult = useFilterQuery({ - queryKey: ['team:members', { teamId, modified }], - queryFn: params => { - return get(`/teams/${teamId}/users`, { - ...params, - }); - }, - enabled: !!teamId, - }); +export function TeamMembers({ teamId, allowEdit }: { teamId: string; allowEdit: boolean }) { + const queryResult = useTeamMembers(teamId); return ( - {({ data }) => } + {({ data }) => } ); } diff --git a/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx index 93d7c521..d993bbc0 100644 --- a/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx @@ -6,11 +6,11 @@ import TeamMemberRemoveButton from './TeamMemberRemoveButton'; export function TeamMembersTable({ data = [], teamId, - readOnly, + allowEdit, }: { data: any[]; teamId: string; - readOnly: boolean; + allowEdit: boolean; }) { const { formatMessage, labels } = useMessages(); const { user } = useLogin(); @@ -23,16 +23,20 @@ export function TeamMembersTable({ return ( - + + {row => row?.user?.username} + - {row => roles[row?.teamUser?.[0]?.role]} + {row => roles[row?.role]} {row => { return ( - !readOnly && - row?.teamUser?.[0]?.role !== ROLES.teamOwner && - user?.id !== row?.id && + allowEdit && + row?.role !== ROLES.teamOwner && + user?.id !== row?.id && ( + + ) ); }} diff --git a/src/app/(main)/settings/teams/[teamId]/TeamSettings.tsx b/src/app/(main)/settings/teams/[teamId]/TeamSettings.tsx index 39d03b87..f0a16604 100644 --- a/src/app/(main)/settings/teams/[teamId]/TeamSettings.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamSettings.tsx @@ -47,9 +47,9 @@ export function TeamSettings({ teamId }: { teamId: string }) { {formatMessage(labels.websites)} {formatMessage(labels.data)} - {tab === 'details' && } - {tab === 'members' && } - {tab === 'websites' && } + {tab === 'details' && } + {tab === 'members' && } + {tab === 'websites' && } {canEdit && tab === 'data' && } diff --git a/src/app/(main)/settings/teams/[teamId]/TeamWebsites.tsx b/src/app/(main)/settings/teams/[teamId]/TeamWebsites.tsx index 24d38bc3..25f459d5 100644 --- a/src/app/(main)/settings/teams/[teamId]/TeamWebsites.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamWebsites.tsx @@ -1,7 +1,7 @@ import WebsitesDataTable from 'app/(main)/settings/websites/WebsitesDataTable'; -export function TeamWebsites({ teamId }: { teamId: string; readOnly: boolean }) { - return ; +export function TeamWebsites({ teamId, allowEdit }: { teamId: string; allowEdit: boolean }) { + return ; } export default TeamWebsites; diff --git a/src/app/(main)/settings/websites/WebsiteAddButton.tsx b/src/app/(main)/settings/websites/WebsiteAddButton.tsx index edda0c1b..e16b900e 100644 --- a/src/app/(main)/settings/websites/WebsiteAddButton.tsx +++ b/src/app/(main)/settings/websites/WebsiteAddButton.tsx @@ -1,7 +1,7 @@ import { Button, Icon, Icons, Modal, ModalTrigger, Text, useToasts } from 'react-basics'; import WebsiteAddForm from './WebsiteAddForm'; import { useMessages } from 'components/hooks'; -import { setValue } from 'store/cache'; +import { touch } from 'store/cache'; export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?: () => void }) { const { formatMessage, labels, messages } = useMessages(); @@ -9,7 +9,7 @@ export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?: const handleSave = async () => { showToast({ message: formatMessage(messages.saved), variant: 'success' }); - setValue('websites', Date.now()); + touch('websites'); onSave?.(); }; diff --git a/src/app/(main)/settings/websites/Websites.tsx b/src/app/(main)/settings/websites/Websites.tsx index f767cd5f..9f14f4e6 100644 --- a/src/app/(main)/settings/websites/Websites.tsx +++ b/src/app/(main)/settings/websites/Websites.tsx @@ -5,6 +5,7 @@ import WebsitesHeader from './WebsitesHeader'; export default function Websites() { const { user } = useLogin(); + return ( <> diff --git a/src/app/(main)/settings/websites/WebsitesTable.tsx b/src/app/(main)/settings/websites/WebsitesTable.tsx index 701678f5..159d0b5e 100644 --- a/src/app/(main)/settings/websites/WebsitesTable.tsx +++ b/src/app/(main)/settings/websites/WebsitesTable.tsx @@ -1,7 +1,7 @@ import { ReactNode } from 'react'; import Link from 'next/link'; import { Button, Text, Icon, Icons, GridTable, GridColumn, useBreakpoint } from 'react-basics'; -import { useMessages, useLogin } from 'components/hooks'; +import { useMessages, useLogin, useNavigation } from 'components/hooks'; export interface WebsitesTableProps { data: any[]; @@ -23,6 +23,7 @@ export function WebsitesTable({ const { formatMessage, labels } = useMessages(); const { user } = useLogin(); const breakpoint = useBreakpoint(); + const { renderTeamUrl } = useNavigation(); return ( @@ -46,7 +47,7 @@ export function WebsitesTable({ )} {allowView && ( - + + + + + + ); +} + +export default TeamReports; diff --git a/src/app/(main)/teams/[teamId]/reports/create/page.tsx b/src/app/(main)/teams/[teamId]/reports/create/page.tsx new file mode 100644 index 00000000..77127dfb --- /dev/null +++ b/src/app/(main)/teams/[teamId]/reports/create/page.tsx @@ -0,0 +1,10 @@ +import { Metadata } from 'next'; +import ReportTemplates from 'app/(main)/reports/create/ReportTemplates'; + +export default function () { + return ; +} + +export const metadata: Metadata = { + title: 'Create Report | umami', +}; diff --git a/src/app/(main)/teams/[teamId]/reports/page.tsx b/src/app/(main)/teams/[teamId]/reports/page.tsx new file mode 100644 index 00000000..ad2ea4dd --- /dev/null +++ b/src/app/(main)/teams/[teamId]/reports/page.tsx @@ -0,0 +1,15 @@ +import { Metadata } from 'next'; +import ReportsHeader from 'app/(main)/reports/ReportsHeader'; +import ReportsDataTable from 'app/(main)/reports/ReportsDataTable'; + +export default function ({ params: { teamId } }) { + return ( + <> + + + + ); +} +export const metadata: Metadata = { + title: 'Reports | umami', +}; diff --git a/src/app/(main)/teams/[teamId]/websites/[websiteId]/page.tsx b/src/app/(main)/teams/[teamId]/websites/[websiteId]/page.tsx index eb506411..1684e740 100644 --- a/src/app/(main)/teams/[teamId]/websites/[websiteId]/page.tsx +++ b/src/app/(main)/teams/[teamId]/websites/[websiteId]/page.tsx @@ -1,4 +1,4 @@ -import WebsiteDetails from '../../../../websites/[websiteId]/WebsiteDetails'; +import WebsiteDetails from 'app/(main)/websites/[websiteId]/WebsiteDetails'; export default function TeamWebsitePage({ params: { websiteId } }) { return ; diff --git a/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx b/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx index 2f7a9a9f..e0826a73 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx @@ -43,7 +43,7 @@ export default function WebsiteExpandedView({ const { formatMessage, labels } = useMessages(); const { router, - makeUrl, + renderUrl, pathname, query: { view }, } = useNavigation(); @@ -52,69 +52,69 @@ export default function WebsiteExpandedView({ { key: 'url', label: formatMessage(labels.pages), - url: makeUrl({ view: 'url' }), + url: renderUrl({ view: 'url' }), }, { key: 'referrer', label: formatMessage(labels.referrers), - url: makeUrl({ view: 'referrer' }), + url: renderUrl({ view: 'referrer' }), }, { key: 'browser', label: formatMessage(labels.browsers), - url: makeUrl({ view: 'browser' }), + url: renderUrl({ view: 'browser' }), }, { key: 'os', label: formatMessage(labels.os), - url: makeUrl({ view: 'os' }), + url: renderUrl({ view: 'os' }), }, { key: 'device', label: formatMessage(labels.devices), - url: makeUrl({ view: 'device' }), + url: renderUrl({ view: 'device' }), }, { key: 'country', label: formatMessage(labels.countries), - url: makeUrl({ view: 'country' }), + url: renderUrl({ view: 'country' }), }, { key: 'region', label: formatMessage(labels.regions), - url: makeUrl({ view: 'region' }), + url: renderUrl({ view: 'region' }), }, { key: 'city', label: formatMessage(labels.cities), - url: makeUrl({ view: 'city' }), + url: renderUrl({ view: 'city' }), }, { key: 'language', label: formatMessage(labels.languages), - url: makeUrl({ view: 'language' }), + url: renderUrl({ view: 'language' }), }, { key: 'screen', label: formatMessage(labels.screens), - url: makeUrl({ view: 'screen' }), + url: renderUrl({ view: 'screen' }), }, { key: 'event', label: formatMessage(labels.events), - url: makeUrl({ view: 'event' }), + url: renderUrl({ view: 'event' }), }, { key: 'query', label: formatMessage(labels.queryParameters), - url: makeUrl({ view: 'query' }), + url: renderUrl({ view: 'query' }), }, ]; const DetailsComponent = views[view] || (() => null); const handleChange = (view: any) => { - router.push(makeUrl({ view })); + router.push(renderUrl({ view })); }; const renderValue = (value: string) => items.find(({ key }) => key === value)?.label; diff --git a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx index c06cff45..63348cc0 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx @@ -11,7 +11,7 @@ export function WebsiteFilterButton({ className?: string; }) { const { formatMessage, labels } = useMessages(); - const { makeUrl, router } = useNavigation(); + const { renderUrl, router } = useNavigation(); const fieldOptions = [ { name: 'url', type: 'string', label: formatMessage(labels.url) }, @@ -25,7 +25,7 @@ export function WebsiteFilterButton({ ]; const handleAddFilter = ({ name, value }) => { - router.push(makeUrl({ [name]: value })); + router.push(renderUrl({ [name]: value })); }; return ( diff --git a/src/app/(main)/websites/[websiteId]/event-data/EventDataTable.tsx b/src/app/(main)/websites/[websiteId]/event-data/EventDataTable.tsx index fb98e7e7..71c36992 100644 --- a/src/app/(main)/websites/[websiteId]/event-data/EventDataTable.tsx +++ b/src/app/(main)/websites/[websiteId]/event-data/EventDataTable.tsx @@ -6,7 +6,7 @@ import { DATA_TYPES } from 'lib/constants'; export function EventDataTable({ data = [] }) { const { formatMessage, labels } = useMessages(); - const { makeUrl } = useNavigation(); + const { renderUrl } = useNavigation(); if (data.length === 0) { return ; @@ -16,7 +16,7 @@ export function EventDataTable({ data = [] }) { {row => ( - + {row.eventName} )} diff --git a/src/app/(main)/websites/[websiteId]/event-data/EventDataValueTable.tsx b/src/app/(main)/websites/[websiteId]/event-data/EventDataValueTable.tsx index 7976ce36..acaa6ae9 100644 --- a/src/app/(main)/websites/[websiteId]/event-data/EventDataValueTable.tsx +++ b/src/app/(main)/websites/[websiteId]/event-data/EventDataValueTable.tsx @@ -8,12 +8,12 @@ import { DATA_TYPES } from 'lib/constants'; export function EventDataValueTable({ data = [], event }: { data: any[]; event: string }) { const { formatMessage, labels } = useMessages(); - const { makeUrl } = useNavigation(); + const { renderUrl } = useNavigation(); const Title = () => { return ( <> - +