add public website

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-02-04 21:35:17 +01:00
parent ccd1a1456f
commit fab3a0d9a8
72 changed files with 4012 additions and 48 deletions

View File

@@ -0,0 +1,9 @@
import { toast } from '@/components/ui/use-toast';
export function clipboard(value: string | number) {
navigator.clipboard.writeText(value.toString());
toast({
title: 'Copied to clipboard',
description: value.toString(),
});
}

View File

@@ -0,0 +1,7 @@
import { clsx } from 'clsx';
import type { ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -0,0 +1,99 @@
export const operators = {
is: 'Is',
isNot: 'Is not',
contains: 'Contains',
doesNotContain: 'Not contains',
} as const;
export const chartTypes = {
linear: 'Linear',
bar: 'Bar',
histogram: 'Histogram',
pie: 'Pie',
metric: 'Metric',
area: 'Area',
map: 'Map',
} as const;
export const lineTypes = {
monotone: 'Monotone',
monotoneX: 'Monotone X',
monotoneY: 'Monotone Y',
linear: 'Linear',
natural: 'Natural',
basis: 'Basis',
step: 'Step',
stepBefore: 'Step before',
stepAfter: 'Step after',
basisClosed: 'Basis closed',
basisOpen: 'Basis open',
bumpX: 'Bump X',
bumpY: 'Bump Y',
bump: 'Bump',
linearClosed: 'Linear closed',
} as const;
export const intervals = {
minute: 'minute',
day: 'day',
hour: 'hour',
month: 'month',
} as const;
export const alphabetIds = [
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
] as const;
export const timeRanges = {
'30min': '30min',
'1h': '1h',
today: 'today',
'24h': '24h',
'7d': '7d',
'14d': '14d',
'1m': '1m',
'3m': '3m',
'6m': '6m',
'1y': '1y',
} as const;
export const metrics = {
sum: 'sum',
average: 'average',
min: 'min',
max: 'max',
} as const;
export function isMinuteIntervalEnabledByRange(range: keyof typeof timeRanges) {
return range === '30min' || range === '1h';
}
export function isHourIntervalEnabledByRange(range: keyof typeof timeRanges) {
return (
isMinuteIntervalEnabledByRange(range) ||
range === 'today' ||
range === '24h'
);
}
export function getDefaultIntervalByRange(
range: keyof typeof timeRanges
): keyof typeof intervals {
if (range === '30min' || range === '1h') {
return 'minute';
} else if (range === 'today' || range === '24h') {
return 'hour';
} else if (range === '7d' || range === '14d' || range === '1m') {
return 'day';
}
return 'month';
}

View File

@@ -0,0 +1,32 @@
export function getDaysOldDate(days: number) {
const date = new Date();
date.setDate(date.getDate() - days);
return date;
}
export function dateDifferanceInDays(date1: Date, date2: Date) {
const diffTime = Math.abs(date2.getTime() - date1.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
export function getLocale() {
if (typeof navigator === 'undefined') {
return 'en-US';
}
return navigator.language ?? 'en-US';
}
export function formatDate(date: Date) {
return new Intl.DateTimeFormat(getLocale()).format(date);
}
export function formatDateTime(date: Date) {
return new Intl.DateTimeFormat(getLocale(), {
day: 'numeric',
month: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
}).format(date);
}

View File

@@ -0,0 +1,6 @@
import type { Profile } from '@mixan/db';
export function getProfileName(profile: Profile | undefined | null) {
if (!profile) return 'No profile';
return [profile.first_name, profile.last_name].filter(Boolean).join(' ');
}

View File

@@ -0,0 +1,22 @@
import { isNumber } from 'mathjs';
export const round = (num: number, decimals = 2) => {
const factor = Math.pow(10, decimals);
return Math.round((num + Number.EPSILON) * factor) / factor;
};
export const average = (arr: (number | null)[]) => {
const filtered = arr.filter(isNumber);
return filtered.reduce((p, c) => p + c, 0) / filtered.length;
};
export const sum = (arr: (number | null)[]): number =>
round(arr.filter(isNumber).reduce((acc, item) => acc + item, 0));
export const min = (arr: (number | null)[]): number =>
Math.min(...arr.filter(isNumber));
export const max = (arr: (number | null)[]): number =>
Math.max(...arr.filter(isNumber));
export const isFloat = (n: number) => n % 1 !== 0;

View File

@@ -0,0 +1,18 @@
import _slugify from 'slugify';
const slugify = (str: string) => {
return _slugify(
str
.replace('å', 'a')
.replace('ä', 'a')
.replace('ö', 'o')
.replace('Å', 'A')
.replace('Ä', 'A')
.replace('Ö', 'O'),
{ lower: true, strict: true, trim: true }
);
};
export function slug(str: string): string {
return slugify(str);
}

View File

@@ -0,0 +1,17 @@
import resolveConfig from 'tailwindcss/resolveConfig';
import tailwinConfig from '../../tailwind.config';
export const resolvedTailwindConfig = resolveConfig(tailwinConfig);
export const theme = resolvedTailwindConfig.theme as Record<string, any>;
export function getChartColor(index: number): string {
const colors = theme?.colors ?? {};
const chartColors: string[] = Object.keys(colors)
.filter((key) => key.startsWith('chart-'))
.map((key) => colors[key])
.filter((item): item is string => typeof item === 'string');
return chartColors[index % chartColors.length]!;
}

View File

@@ -0,0 +1,6 @@
export function truncate(str: string, len: number) {
if (str.length <= len) {
return str;
}
return str.slice(0, len) + '...';
}

View File

@@ -0,0 +1,75 @@
import { z } from 'zod';
import {
chartTypes,
intervals,
lineTypes,
metrics,
operators,
timeRanges,
} from './constants';
export function objectToZodEnums<K extends string>(
obj: Record<K, any>
): [K, ...K[]] {
const [firstKey, ...otherKeys] = Object.keys(obj) as K[];
return [firstKey!, ...otherKeys];
}
export const mapKeys = objectToZodEnums;
export const zChartEvent = z.object({
id: z.string(),
name: z.string(),
displayName: z.string().optional(),
property: z.string().optional(),
segment: z.enum([
'event',
'user',
'user_average',
'one_event_per_user',
'property_sum',
'property_average',
]),
filters: z.array(
z.object({
id: z.string(),
name: z.string(),
operator: z.enum(objectToZodEnums(operators)),
value: z.array(z.string().or(z.number()).or(z.boolean()).or(z.null())),
})
),
});
export const zChartBreakdown = z.object({
id: z.string(),
name: z.string(),
});
export const zChartEvents = z.array(zChartEvent);
export const zChartBreakdowns = z.array(zChartBreakdown);
export const zChartType = z.enum(objectToZodEnums(chartTypes));
export const zLineType = z.enum(objectToZodEnums(lineTypes));
export const zTimeInterval = z.enum(objectToZodEnums(intervals));
export const zMetric = z.enum(objectToZodEnums(metrics));
export const zChartInput = z.object({
name: z.string(),
chartType: zChartType,
lineType: zLineType,
interval: zTimeInterval,
events: zChartEvents,
breakdowns: zChartBreakdowns,
range: z.enum(objectToZodEnums(timeRanges)),
previous: z.boolean(),
formula: z.string().optional(),
metric: zMetric,
unit: z.string().optional(),
previousIndicatorInverted: z.boolean().optional(),
projectId: z.string(),
startDate: z.string().nullish(),
endDate: z.string().nullish(),
});