refactor packages

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-02-19 10:55:15 +01:00
parent ae8482c1e3
commit 2f3c5ddf76
142 changed files with 2234 additions and 5507 deletions

View File

@@ -5,7 +5,6 @@ import { StickyBelowHeader } from '@/app/(app)/[organizationId]/[projectId]/layo
import { LazyChart } from '@/components/report/chart/LazyChart';
import { ReportRange } from '@/components/report/ReportRange';
import { Button } from '@/components/ui/button';
import { Combobox } from '@/components/ui/combobox';
import {
DropdownMenu,
DropdownMenuContent,
@@ -14,14 +13,15 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useAppParams } from '@/hooks/useAppParams';
import type { getReportsByDashboardId } from '@/server/services/reports.service';
import type { IChartRange } from '@/types';
import { cn } from '@/utils/cn';
import { getDefaultIntervalByRange, timeRanges } from '@/utils/constants';
import { ChevronRight, MoreHorizontal, PlusIcon, Trash } from 'lucide-react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { getDefaultIntervalByRange } from '@mixan/constants';
import type { getReportsByDashboardId } from '@mixan/db';
import type { IChartRange } from '@mixan/validation';
interface ListReportsProps {
reports: Awaited<ReturnType<typeof getReportsByDashboardId>>;
}

View File

@@ -1,9 +1,9 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists';
import { getDashboardById } from '@/server/services/dashboard.service';
import { getReportsByDashboardId } from '@/server/services/reports.service';
import { notFound } from 'next/navigation';
import { getDashboardById, getReportsByDashboardId } from '@mixan/db';
import { ListReports } from './list-reports';
interface PageProps {

View File

@@ -7,12 +7,13 @@ import { Button } from '@/components/ui/button';
import { ToastAction } from '@/components/ui/toast';
import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals';
import type { IServiceDashboards } from '@/server/services/dashboard.service';
import { LayoutPanelTopIcon, Pencil, PlusIcon, Trash } from 'lucide-react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import type { IServiceDashboards } from '@mixan/db';
interface ListDashboardsProps {
dashboards: IServiceDashboards;
}

View File

@@ -1,6 +1,7 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists';
import { getDashboardsByProjectId } from '@/server/services/dashboard.service';
import { getDashboardsByProjectId } from '@mixan/db';
import { HeaderDashboards } from './header-dashboards';
import { ListDashboards } from './list-dashboards';

View File

@@ -1,13 +1,16 @@
'use client';
import type { RouterOutputs } from '@/app/_trpc/client';
import { ExpandableListItem } from '@/components/general/ExpandableListItem';
import { KeyValue, KeyValueSubtle } from '@/components/ui/key-value';
import { useAppParams } from '@/hooks/useAppParams';
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
import {
useEventQueryFilters,
useEventQueryNamesFilter,
} from '@/hooks/useEventQueryFilters';
import { cn } from '@/utils/cn';
import { getProfileName } from '@/utils/getters';
import { round } from '@/utils/math';
import { uniq } from 'ramda';
import type { IServiceCreateEventPayload } from '@mixan/db';
@@ -40,7 +43,8 @@ export function EventListItem({
meta,
}: EventListItemProps) {
const params = useAppParams();
const eventQueryFilters = useEventQueryFilters({ shallow: false });
const [, setEvents] = useEventQueryNamesFilter({ shallow: false });
const [, setFilter] = useEventQueryFilters({ shallow: false });
const keyValueList = [
{
name: 'Duration',
@@ -50,98 +54,98 @@ export function EventListItem({
name: 'Referrer',
value: referrer,
onClick() {
eventQueryFilters.referrer.set(referrer ?? null);
setFilter('referrer', referrer ?? '');
},
},
{
name: 'Referrer name',
value: referrerName,
onClick() {
eventQueryFilters.referrerName.set(referrerName ?? null);
setFilter('referrer_name', referrerName ?? '');
},
},
{
name: 'Referrer type',
value: referrerType,
onClick() {
eventQueryFilters.referrerType.set(referrerType ?? null);
setFilter('referrer_type', referrerType ?? '');
},
},
{
name: 'Brand',
value: brand,
onClick() {
eventQueryFilters.brand.set(brand ?? null);
setFilter('brand', brand ?? '');
},
},
{
name: 'Model',
value: model,
onClick() {
eventQueryFilters.model.set(model ?? null);
setFilter('model', model ?? '');
},
},
{
name: 'Browser',
value: browser,
onClick() {
eventQueryFilters.browser.set(browser ?? null);
setFilter('browser', browser ?? '');
},
},
{
name: 'Browser version',
value: browserVersion,
onClick() {
eventQueryFilters.browserVersion.set(browserVersion ?? null);
setFilter('browser_version', browserVersion ?? '');
},
},
{
name: 'OS',
value: os,
onClick() {
eventQueryFilters.os.set(os ?? null);
setFilter('os', os ?? '');
},
},
{
name: 'OS cersion',
value: osVersion,
onClick() {
eventQueryFilters.osVersion.set(osVersion ?? null);
setFilter('os_version', osVersion ?? '');
},
},
{
name: 'City',
value: city,
onClick() {
eventQueryFilters.city.set(city ?? null);
setFilter('city', city ?? '');
},
},
{
name: 'Region',
value: region,
onClick() {
eventQueryFilters.region.set(region ?? null);
setFilter('region', region ?? '');
},
},
{
name: 'Country',
value: country,
onClick() {
eventQueryFilters.country.set(country ?? null);
setFilter('country', country ?? '');
},
},
{
name: 'Continent',
value: continent,
onClick() {
eventQueryFilters.continent.set(continent ?? null);
setFilter('continent', continent ?? '');
},
},
{
name: 'Device',
value: device,
onClick() {
eventQueryFilters.device.set(device ?? null);
setFilter('device', device ?? '');
},
},
].filter((item) => typeof item.value === 'string' && item.value);
@@ -156,7 +160,11 @@ export function EventListItem({
return (
<ExpandableListItem
className={cn(meta?.conversion && 'ring-2 ring-primary-500')}
title={name.split('_').join(' ')}
title={
<button onClick={() => setEvents((p) => uniq([...p, name]))}>
{name.split('_').join(' ')}
</button>
}
content={
<>
<KeyValueSubtle name="Time" value={createdAt.toLocaleString()} />
@@ -172,7 +180,7 @@ export function EventListItem({
name="Path"
value={path}
onClick={() => {
eventQueryFilters.path.set(path);
setFilter('path', path);
}}
/>
)}
@@ -191,6 +199,13 @@ export function EventListItem({
key={item.name}
name={item.name}
value={item.value}
onClick={() => {
setFilter(
`properties.${item.name}`,
item.value ? String(item.value) : '',
'is'
);
}}
/>
))}
</div>

View File

@@ -5,7 +5,7 @@ import { FullPageEmptyState } from '@/components/FullPageEmptyState';
import { Pagination } from '@/components/Pagination';
import { Button } from '@/components/ui/button';
import { useCursor } from '@/hooks/useCursor';
import { useEventFilters } from '@/hooks/useEventQueryFilters';
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
import { GanttChartIcon } from 'lucide-react';
import type { IServiceCreateEventPayload } from '@mixan/db';
@@ -18,8 +18,7 @@ interface EventListProps {
}
export function EventList({ data, count }: EventListProps) {
const { cursor, setCursor } = useCursor();
const filters = useEventFilters();
const [filters] = useEventQueryFilters();
return (
<Suspense>
<div className="p-4">

View File

@@ -1,7 +1,10 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
import { getEventFilters } from '@/hooks/useEventQueryFilters';
import {
eventQueryFiltersParser,
eventQueryNamesFilter,
} from '@/hooks/useEventQueryFilters';
import { getExists } from '@/server/pageExists';
import { getEventList, getEventsCount } from '@mixan/db';
@@ -15,27 +18,9 @@ interface PageProps {
organizationId: string;
};
searchParams: {
events?: string;
cursor?: string;
path?: string;
device?: string;
referrer?: string;
referrerName?: string;
referrerType?: string;
utmSource?: string;
utmMedium?: string;
utmCampaign?: string;
utmContent?: string;
utmTerm?: string;
continent?: string;
country?: string;
region?: string;
city?: string;
browser?: string;
browserVersion?: string;
os?: string;
osVersion?: string;
brand?: string;
model?: string;
f?: string;
};
}
@@ -59,53 +44,13 @@ export default async function Page({
cursor: parseQueryAsNumber(searchParams.cursor),
projectId,
take: 50,
filters: getEventFilters({
path: searchParams.path ?? null,
device: searchParams.device ?? null,
referrer: searchParams.referrer ?? null,
referrerName: searchParams.referrerName ?? null,
referrerType: searchParams.referrerType ?? null,
utmSource: searchParams.utmSource ?? null,
utmMedium: searchParams.utmMedium ?? null,
utmCampaign: searchParams.utmCampaign ?? null,
utmContent: searchParams.utmContent ?? null,
utmTerm: searchParams.utmTerm ?? null,
continent: searchParams.continent ?? null,
country: searchParams.country ?? null,
region: searchParams.region ?? null,
city: searchParams.city ?? null,
browser: searchParams.browser ?? null,
browserVersion: searchParams.browserVersion ?? null,
os: searchParams.os ?? null,
osVersion: searchParams.osVersion ?? null,
brand: searchParams.brand ?? null,
model: searchParams.model ?? null,
}),
events: eventQueryNamesFilter.parse(searchParams.events ?? ''),
filters: eventQueryFiltersParser.parse(searchParams.f ?? '') ?? undefined,
}),
getEventsCount({
projectId,
filters: getEventFilters({
path: searchParams.path ?? null,
device: searchParams.device ?? null,
referrer: searchParams.referrer ?? null,
referrerName: searchParams.referrerName ?? null,
referrerType: searchParams.referrerType ?? null,
utmSource: searchParams.utmSource ?? null,
utmMedium: searchParams.utmMedium ?? null,
utmCampaign: searchParams.utmCampaign ?? null,
utmContent: searchParams.utmContent ?? null,
utmTerm: searchParams.utmTerm ?? null,
continent: searchParams.continent ?? null,
country: searchParams.country ?? null,
region: searchParams.region ?? null,
city: searchParams.city ?? null,
browser: searchParams.browser ?? null,
browserVersion: searchParams.browserVersion ?? null,
os: searchParams.os ?? null,
osVersion: searchParams.osVersion ?? null,
brand: searchParams.brand ?? null,
model: searchParams.model ?? null,
}),
events: eventQueryNamesFilter.parse(searchParams.events ?? ''),
filters: eventQueryFiltersParser.parse(searchParams.f ?? '') ?? undefined,
}),
getExists(organizationId, projectId),
]);
@@ -116,6 +61,7 @@ export default async function Page({
<OverviewFiltersDrawer
projectId={projectId}
nuqsOptions={nuqsOptions}
enableEventsFilter
/>
<OverviewFiltersButtons
className="p-0 justify-end"

View File

@@ -2,7 +2,6 @@
import { useEffect } from 'react';
import { useAppParams } from '@/hooks/useAppParams';
import type { IServiceDashboards } from '@/server/services/dashboard.service';
import { cn } from '@/utils/cn';
import { useUser } from '@clerk/nextjs';
import {
@@ -21,6 +20,8 @@ import type { LucideProps } from 'lucide-react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import type { IServiceDashboards } from '@mixan/db';
function LinkWithIcon({
href,
icon: Icon,

View File

@@ -2,10 +2,11 @@
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import type { IServiceOrganization } from '@/server/services/organization.service';
import { Building } from 'lucide-react';
import { useRouter } from 'next/navigation';
import type { IServiceOrganization } from '@mixan/db';
interface LayoutOrganizationSelectorProps {
organizations: IServiceOrganization[];
}

View File

@@ -2,11 +2,12 @@
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import type { getCurrentProjects } from '@/server/services/project.service';
import { usePathname, useRouter } from 'next/navigation';
import type { getProjectsByOrganizationSlug } from '@mixan/db';
interface LayoutProjectSelectorProps {
projects: Awaited<ReturnType<typeof getCurrentProjects>>;
projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>;
}
export default function LayoutProjectSelector({
projects,

View File

@@ -2,13 +2,13 @@
import { useEffect, useState } from 'react';
import { Logo } from '@/components/Logo';
import type { IServiceDashboards } from '@/server/services/dashboard.service';
import type { IServiceOrganization } from '@/server/services/organization.service';
import { cn } from '@/utils/cn';
import { Rotate as Hamburger } from 'hamburger-react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import type { IServiceDashboards, IServiceOrganization } from '@mixan/db';
import LayoutMenu from './layout-menu';
import LayoutOrganizationSelector from './layout-organization-selector';

View File

@@ -1,5 +1,7 @@
import { getDashboardsByOrganization } from '@/server/services/dashboard.service';
import { getCurrentOrganizations } from '@/server/services/organization.service';
import {
getCurrentOrganizations,
getDashboardsByOrganization,
} from '@mixan/db';
import { LayoutSidebar } from './layout-sidebar';

View File

@@ -4,17 +4,18 @@ import { WidgetHead } from '@/components/overview/overview-widget';
import { useOverviewOptions } from '@/components/overview/useOverviewOptions';
import { Chart } from '@/components/report/chart';
import { Widget, WidgetBody } from '@/components/Widget';
import { useEventFilters } from '@/hooks/useEventQueryFilters';
import type { IChartInput } from '@/types';
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
import { cn } from '@/utils/cn';
import type { IChartInput } from '@mixan/validation';
interface OverviewMetricsProps {
projectId: string;
}
export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
const { previous, range, interval, metric, setMetric } = useOverviewOptions();
const filters = useEventFilters();
const [filters] = useEventQueryFilters();
const reports = [
{

View File

@@ -1,4 +1,4 @@
import { getCurrentProjects } from '@/server/services/project.service';
import { getProjectsByOrganizationSlug } from '@mixan/db';
import LayoutProjectSelector from './layout-project-selector';
@@ -13,7 +13,7 @@ export default async function PageLayout({
title,
organizationSlug,
}: PageLayoutProps) {
const projects = await getCurrentProjects(organizationSlug);
const projects = await getProjectsByOrganizationSlug(organizationSlug);
return (
<>

View File

@@ -18,7 +18,7 @@ export default function ListProfileEvents({
projectId,
profileId,
}: ListProfileEvents) {
const pagination = usePagination();
const pagination = usePagination(50);
const [eventFilters, setEventFilters] = useQueryState(
'events',
parseAsJson<string[]>().withDefault([])

View File

@@ -3,13 +3,11 @@ import { ListProperties } from '@/components/events/ListProperties';
import { ProfileAvatar } from '@/components/profiles/ProfileAvatar';
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
import { getExists } from '@/server/pageExists';
import {
getProfileById,
getProfilesByExternalId,
} from '@/server/services/profile.service';
import { formatDateTime } from '@/utils/date';
import { getProfileName } from '@/utils/getters';
import { getProfileById, getProfilesByExternalId } from '@mixan/db';
import ListProfileEvents from './list-profile-events';
interface PageProps {

View File

@@ -1,6 +1,5 @@
'use client';
import { useMemo } from 'react';
import type { RouterOutputs } from '@/app/_trpc/client';
import { ListProperties } from '@/components/events/ListProperties';
import { ExpandableListItem } from '@/components/general/ExpandableListItem';
@@ -16,24 +15,24 @@ export function ProfileListItem(props: ProfileListItemProps) {
const { id, properties, createdAt } = props;
const params = useAppParams();
const bullets = useMemo(() => {
const bullets: React.ReactNode[] = [
<span>{formatDateTime(createdAt)}</span>,
<Link
href={`/${params.organizationId}/${params.projectId}/profiles/${id}`}
className="text-black font-medium hover:underline"
>
See profile
</Link>,
];
return bullets;
}, [createdAt, id, params]);
const renderContent = () => {
return (
<>
<span>{formatDateTime(createdAt)}</span>
<Link
href={`/${params.organizationId}/${params.projectId}/profiles/${id}`}
className="text-black font-medium hover:underline"
>
See profile
</Link>
</>
);
};
return (
<ExpandableListItem
title={getProfileName(props)}
bullets={bullets}
content={renderContent()}
image={<ProfileAvatar {...props} />}
>
<ListProperties data={properties} className="rounded-none border-none" />

View File

@@ -1,10 +1,10 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists';
import { getOrganizationBySlug } from '@/server/services/organization.service';
import { getReportById } from '@/server/services/reports.service';
import { Pencil } from 'lucide-react';
import { notFound } from 'next/navigation';
import { getOrganizationBySlug, getReportById } from '@mixan/db';
import ReportEditor from '../report-editor';
interface PageProps {

View File

@@ -1,9 +1,10 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists';
import { getOrganizationBySlug } from '@/server/services/organization.service';
import { Pencil } from 'lucide-react';
import { notFound } from 'next/navigation';
import { getOrganizationBySlug } from '@mixan/db';
import ReportEditor from './report-editor';
interface PageProps {

View File

@@ -19,9 +19,10 @@ import { Button } from '@/components/ui/button';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch, useSelector } from '@/redux';
import type { IServiceReport } from '@/server/services/reports.service';
import { GanttChartSquareIcon } from 'lucide-react';
import type { IServiceReport } from '@mixan/db';
interface ReportEditorProps {
report: IServiceReport | null;
}

View File

@@ -6,9 +6,10 @@ import { DataTable } from '@/components/DataTable';
import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals';
import type { getClientsByOrganizationId } from '@/server/services/clients.service';
import { PlusIcon } from 'lucide-react';
import type { getClientsByOrganizationId } from '@mixan/db';
interface ListClientsProps {
clients: Awaited<ReturnType<typeof getClientsByOrganizationId>>;
}

View File

@@ -1,6 +1,7 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists';
import { getClientsByOrganizationId } from '@/server/services/clients.service';
import { getClientsByOrganizationId } from '@mixan/db';
import ListClients from './list-clients';

View File

@@ -4,12 +4,13 @@ import { api, handleError } from '@/app/_trpc/client';
import { InputWithLabel } from '@/components/forms/InputWithLabel';
import { Button } from '@/components/ui/button';
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
import type { getOrganizationBySlug } from '@/server/services/organization.service';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';
import type { getOrganizationBySlug } from '@mixan/db';
const validator = z.object({
id: z.string().min(2),
name: z.string().min(2),

View File

@@ -2,7 +2,6 @@ import { api } from '@/app/_trpc/client';
import { InputWithLabel } from '@/components/forms/InputWithLabel';
import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams';
import { zInviteUser } from '@/utils/validation';
import { zodResolver } from '@hookform/resolvers/zod';
import { SendIcon } from 'lucide-react';
import { useRouter } from 'next/navigation';
@@ -10,6 +9,8 @@ import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import type { z } from 'zod';
import { zInviteUser } from '@mixan/validation';
type IForm = z.infer<typeof zInviteUser>;
export function InviteUser() {

View File

@@ -9,7 +9,8 @@ import {
TableRow,
} from '@/components/ui/table';
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
import type { IServiceInvites } from '@/server/services/organization.service';
import type { IServiceInvites } from '@mixan/db';
import { InviteUser } from './invite-user';

View File

@@ -1,11 +1,9 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import {
getInvites,
getOrganizationBySlug,
} from '@/server/services/organization.service';
import { clerkClient } from '@clerk/nextjs';
import { notFound } from 'next/navigation';
import { getInvites, getOrganizationBySlug } from '@mixan/db';
import EditOrganization from './edit-organization';
import InvitedUsers from './invited-users';

View File

@@ -4,13 +4,14 @@ import { api, handleError } from '@/app/_trpc/client';
import { InputWithLabel } from '@/components/forms/InputWithLabel';
import { Button } from '@/components/ui/button';
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
import type { getUserById } from '@/server/services/user.service';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';
import type { getUserById } from '@mixan/db';
const validator = z.object({
firstName: z.string().min(2),
lastName: z.string().min(2),

View File

@@ -1,8 +1,9 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists';
import { getUserById } from '@/server/services/user.service';
import { auth } from '@clerk/nextjs';
import { getUserById } from '@mixan/db';
import EditProfile from './edit-profile';
import { Logout } from './logout';

View File

@@ -6,11 +6,12 @@ import { columns } from '@/components/projects/table';
import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals';
import type { getProjectsByOrganizationId } from '@/server/services/project.service';
import { PlusIcon } from 'lucide-react';
import type { getProjectsByOrganizationSlug } from '@mixan/db';
interface ListProjectsProps {
projects: Awaited<ReturnType<typeof getProjectsByOrganizationId>>;
projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>;
}
export default function ListProjects({ projects }: ListProjectsProps) {
const organizationId = useAppParams().organizationId;

View File

@@ -1,6 +1,7 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists';
import { getProjectsByOrganizationSlug } from '@/server/services/project.service';
import { getProjectsByOrganizationSlug } from '@mixan/db';
import ListProjects from './list-projects';