onboarding completed

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-04-16 11:41:15 +02:00
committed by Carl-Gerhard Lindesvärd
parent 97627583ec
commit 7d22d2ddad
79 changed files with 2542 additions and 805 deletions

View File

@@ -1,26 +1,40 @@
import { forwardRef } from 'react';
import { BanIcon, InfoIcon } from 'lucide-react';
import { Input } from '../ui/input';
import type { InputProps } from '../ui/input';
import { Label } from '../ui/label';
import { Tooltiper } from '../ui/tooltip';
type InputWithLabelProps = InputProps & {
label: string;
error?: string | undefined;
info?: string;
};
export const InputWithLabel = forwardRef<HTMLInputElement, InputWithLabelProps>(
({ label, className, ...props }, ref) => {
({ label, className, info, ...props }, ref) => {
return (
<div className={className}>
<div className="mb-2 block flex justify-between">
<Label className="mb-0" htmlFor={label}>
<div className="mb-2 flex items-end justify-between">
<Label
className="mb-0 flex flex-1 shrink-0 items-center gap-1 whitespace-nowrap"
htmlFor={label}
>
{label}
{info && (
<Tooltiper content={info}>
<InfoIcon size={14} />
</Tooltiper>
)}
</Label>
{props.error && (
<span className="text-sm leading-none text-destructive">
{props.error}
</span>
<Tooltiper asChild content={props.error}>
<div className="flex items-center gap-1 text-sm leading-none text-destructive">
Issues
<BanIcon size={14} />
</div>
</Tooltiper>
)}
</div>
<Input ref={ref} id={label} {...props} />

View File

@@ -0,0 +1,21 @@
import { cn } from '@/utils/cn';
import { Logo } from './logo';
type Props = {
children: React.ReactNode;
className?: string;
};
const FullWidthNavbar = ({ children, className }: Props) => {
return (
<div className={cn('border-b border-border bg-background', className)}>
<div className="mx-auto flex h-14 w-full items-center justify-between px-4 md:max-w-[95vw] lg:max-w-[80vw]">
<Logo />
{children}
</div>
</div>
);
};
export default FullWidthNavbar;

View File

@@ -6,10 +6,10 @@ import {
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import useWS from '@/hooks/useWS';
import { cn } from '@/utils/cn';
import { useQueryClient } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import useWebSocket from 'react-use-websocket';
import { toast } from 'sonner';
import { useOverviewOptions } from '../useOverviewOptions';
@@ -28,29 +28,21 @@ const FIFTEEN_SECONDS = 1000 * 15;
export default function LiveCounter({ data = 0, projectId }: LiveCounterProps) {
const { setLiveHistogram } = useOverviewOptions();
const ws = String(process.env.NEXT_PUBLIC_API_URL)
.replace(/^https/, 'wss')
.replace(/^http/, 'ws');
const client = useQueryClient();
const [counter, setCounter] = useState(data);
const [socketUrl] = useState(`${ws}/live/visitors/${projectId}`);
const lastRefresh = useRef(Date.now());
useWebSocket(socketUrl, {
shouldReconnect: () => true,
onMessage(event) {
const value = parseInt(event.data, 10);
if (!isNaN(value)) {
setCounter(value);
if (Date.now() - lastRefresh.current > FIFTEEN_SECONDS) {
lastRefresh.current = Date.now();
toast('Refreshed data');
client.refetchQueries({
type: 'active',
});
}
useWS<number>(`/live/visitors/${projectId}`, (value) => {
if (!isNaN(value)) {
setCounter(value);
if (Date.now() - lastRefresh.current > FIFTEEN_SECONDS) {
lastRefresh.current = Date.now();
toast('Refreshed data');
client.refetchQueries({
type: 'active',
});
}
},
}
});
return (

View File

@@ -4,7 +4,7 @@ const createFlagIcon = (url: string) => {
return function (_props: LucideProps) {
return (
<span
className={`fi !block overflow-hidden rounded-[2px] !leading-[1rem] fi-${url}`}
className={`fi fis !block overflow-hidden rounded-full !leading-[1rem] fi-${url}`}
></span>
);
} as LucideIcon;

View File

@@ -1,4 +1,5 @@
import { useMemo } from 'react';
import { Tooltiper } from '@/components/ui/tooltip';
import type { LucideIcon, LucideProps } from 'lucide-react';
import {
ActivityIcon,
@@ -81,8 +82,10 @@ export function SerieIcon({ name, ...props }: SerieIconProps) {
}, [name]);
return Icon ? (
<div className="[&_a]:![&_a]:!h-4 relative h-4 flex-shrink-0 [&_svg]:!rounded-[2px]">
<Icon size={16} {...props} />
</div>
<Tooltiper asChild content={name!}>
<div className="[&_a]:![&_a]:!h-4 relative h-4 flex-shrink-0 [&_svg]:!rounded-[2px]">
<Icon size={16} {...props} />
</div>
</Tooltiper>
) : null;
}

View File

@@ -1,7 +1,7 @@
// prettier-ignore
const data = {
'chromium os': 'https://upload.wikimedia.org/wikipedia/commons/2/28/Chromium_Logo.svg',
'mac os': 'https://upload.wikimedia.org/wikipedia/commons/c/c9/Finder_Icon_macOS_Big_Sur.png',
'mac os': 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/MacOS_logo.svg/1200px-MacOS_logo.svg.png',
'mobile safari': 'https://upload.wikimedia.org/wikipedia/commons/5/52/Safari_browser_logo.svg',
'openpanel.dev': 'https://openpanel.dev',
'samsung internet': 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Samsung_Internet_logo.svg/1024px-Samsung_Internet_logo.svg.png',
@@ -20,7 +20,7 @@ const data = {
gmail: 'https://mail.google.com',
google: 'https://google.com',
instagram: 'https://instagram.com',
ios: 'https://upload.wikimedia.org/wikipedia/commons/6/66/Apple_iOS_logo.svg',
ios: 'https://cdn0.iconfinder.com/data/icons/flat-round-system/512/apple-1024.png',
linkedin: 'https://linkedin.com',
linux: 'https://upload.wikimedia.org/wikipedia/commons/3/35/Tux.svg',
microlaunch: 'https://microlaunch.net',

View File

@@ -0,0 +1,18 @@
'use client';
import { SignOutButton as ClerkSignOutButton } from '@clerk/nextjs';
import { LogOutIcon } from 'lucide-react';
import { Button } from './ui/button';
const SignOutButton = () => {
return (
<ClerkSignOutButton>
<Button variant={'secondary'} icon={LogOutIcon}>
Sign out
</Button>
</ClerkSignOutButton>
);
};
export default SignOutButton;

View File

@@ -1,8 +1,10 @@
'use client';
import { clipboard } from '@/utils/clipboard';
import { CopyIcon } from 'lucide-react';
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript';
import docco from 'react-syntax-highlighter/dist/cjs/styles/hljs/docco';
import docco from 'react-syntax-highlighter/dist/cjs/styles/hljs/vs2015';
SyntaxHighlighter.registerLanguage('typescript', ts);
@@ -12,8 +14,29 @@ interface SyntaxProps {
export default function Syntax({ code }: SyntaxProps) {
return (
<SyntaxHighlighter wrapLongLines style={docco}>
{code}
</SyntaxHighlighter>
<div className="group relative">
<button
className="absolute right-1 top-1 rounded bg-background p-2 opacity-0 transition-opacity group-hover:opacity-100"
onClick={() => {
clipboard(code);
}}
>
<CopyIcon size={12} />
</button>
<SyntaxHighlighter
// wrapLongLines
style={docco}
language="html"
customStyle={{
borderRadius: '0.5rem',
padding: '1rem',
paddingTop: '0.5rem',
paddingBottom: '0.5rem',
fontSize: 14,
}}
>
{code}
</SyntaxHighlighter>
</div>
);
}

View File

@@ -6,7 +6,7 @@ import { cva } from 'class-variance-authority';
import type { VariantProps } from 'class-variance-authority';
const badgeVariants = cva(
'inline-flex h-[20px] items-center rounded-full border px-1.5 text-[10px] font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
'inline-flex h-[20px] items-center rounded-full border px-1.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
@@ -15,7 +15,7 @@ const badgeVariants = cva(
secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
'border-transparent bg-destructive-foreground text-destructive hover:bg-destructive/80',
success:
'border-transparent bg-emerald-500 text-emerald-100 hover:bg-emerald-500/80',
outline: 'text-foreground',

View File

@@ -0,0 +1,68 @@
import * as React from 'react';
import { cn } from '@/utils/cn';
import { OTPInput, OTPInputContext } from 'input-otp';
import { Dot } from 'lucide-react';
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
'flex items-center gap-2 has-[:disabled]:opacity-50',
containerClassName
)}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
));
InputOTP.displayName = 'InputOTP';
const InputOTPGroup = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center', className)} {...props} />
));
InputOTPGroup.displayName = 'InputOTPGroup';
const InputOTPSlot = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'> & { index: number }
>(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]!;
return (
<div
ref={ref}
className={cn(
'relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
isActive && 'z-10 ring-2 ring-ring ring-offset-background',
className
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink h-4 w-px bg-foreground duration-1000" />
</div>
)}
</div>
);
});
InputOTPSlot.displayName = 'InputOTPSlot';
const InputOTPSeparator = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'>
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Dot />
</div>
));
InputOTPSeparator.displayName = 'InputOTPSeparator';
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };

View File

@@ -100,8 +100,7 @@ const SheetFooter = ({
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
'sticky bottom-0 left-0 right-0 mt-auto bg-background',
'mt-auto flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className
)}
{...props}

View File

@@ -1,3 +1,5 @@
'use client';
import * as React from 'react';
import { cn } from '@/utils/cn';
import * as SwitchPrimitives from '@radix-ui/react-switch';

View File

@@ -7,6 +7,7 @@ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipPortal = TooltipPrimitive.Portal;
const TooltipTrigger = TooltipPrimitive.Trigger;
@@ -49,7 +50,9 @@ export function Tooltiper({
<TooltipTrigger asChild={asChild} className={className}>
{children}
</TooltipTrigger>
<TooltipContent>{content}</TooltipContent>
<TooltipPortal>
<TooltipContent>{content}</TooltipContent>
</TooltipPortal>
</Tooltip>
);
}