Merge branch '@feature/update_trpc' into develop

This commit is contained in:
Fergal Moran
2024-01-14 19:26:07 +00:00
25 changed files with 309 additions and 282 deletions

View File

@@ -1,18 +1,18 @@
# ParentGrine Server🚀
# Kidarr Server🚀
Welcome to ParentGrine, the ultimate child location tracking app that prioritizes safety and peace of mind for parents!
Welcome to Kidarr, the ultimate child location tracking app that prioritizes safety and peace of mind for parents!
🌟
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![GitHub issues](https://img.shields.io/github/issues/parentgrine/parentgrine-server)](https://github.com/parentgrine/parentgrine-server/issues)
[![GitHub stars](https://img.shields.io/github/stars/parentgrine/parentgrine-server)](https://github.com/parentgrine/parentgrine-server/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/parentgrine/parentgrine-server)](https://github.com/parentgrine/parentgrine-server/network)
[![GitHub issues](https://img.shields.io/github/issues/kid-arr/kidarr-server)](https://github.com/kid-arr/kidarr-server/issues)
[![GitHub stars](https://img.shields.io/github/stars/kid-arr/kidarr-server)](https://github.com/kid-arr/kidarr-server/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/kid-arr/kidarr-server)](https://github.com/kid-arr/kidarr-server/network)
## Overview
ParentGrine is a secure and user-friendly mobile application designed to help parents keep track of their children's
Kidarr is a secure and user-friendly mobile application designed to help parents keep track of their children's
whereabouts in real-time. With advanced location tracking features, intuitive UI, and robust security measures,
ParentGrine provides parents with the peace of mind they deserve.
Kidarr provides parents with the peace of mind they deserve.
## Features
@@ -36,12 +36,12 @@ ParentGrine provides parents with the peace of mind they deserve.
### Installation
1. Clone the repository: `git clone https://github.com/parentgrine/parentgrine-server.git`
1. Clone the repository: `git clone https://github.com/kid-arr/kidarr-server.git`
2. Follow the installation instructions in the [documentation](docs/INSTALL.md).
## Usage
1. Open the ParentGrine app on your child's device.
1. Open the Kidarr app on your child's device.
2. Log in with your parent account.
3. Enjoy peace of mind by tracking your child's location.
@@ -55,10 +55,10 @@ Check out our [contribution guidelines](CONTRIBUTING.md) for more information.
## Support
If you encounter any issues or have questions, feel free
to [open an issue](https://github.com/parentgrine/parentgrine-server/issues). We're here to help!
to [open an issue](https://github.com/kid-arr/kidarr-server/issues). We're here to help!
## License
ParentGrine is licensed under the [MIT License](LICENSE).
Kidarr is licensed under the [MIT License](LICENSE).
Happy tracking! 🌍✨

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,5 +1,5 @@
{
"name": "parentgrine-server",
"name": "kidarr",
"version": "0.1.0",
"private": true,
"type": "module",
@@ -7,14 +7,14 @@
"build": "next build",
"db:push": "dotenv drizzle-kit push:postgres",
"db:studio": "dotenv drizzle-kit studio",
"dev": "NODE_ENV=development next dev -p 3002 & local-ssl-proxy --key /etc/letsencrypt/live/dev.fergl.ie/privkey.pem --cert /etc/letsencrypt/live/dev.fergl.ie/fullchain.pem --source 3000 --target 3002",
"dev": "NODE_ENV=development next dev -p 3002 & local-ssl-proxy --config ./ssl-proxy.json",
"lint": "next lint",
"start": "next start"
},
"dependencies": {
"@auth/drizzle-adapter": "^0.3.14",
"@faker-js/faker": "^8.3.1",
"@hookform/resolvers": "^3.3.3",
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-aspect-ratio": "^1.0.3",
@@ -43,17 +43,17 @@
"@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@t3-oss/env-nextjs": "^0.7.1",
"@tanstack/react-query": "^4.36.1",
"@trpc/client": "^10.43.6",
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"@tanstack/react-query": "^4.35.3",
"@trpc/client": "^10.45.0",
"@trpc/next": "^10.45.0",
"@trpc/react-query": "^10.45.0",
"@trpc/server": "^10.45.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^0.2.0",
"date-fns": "^3.0.6",
"date-fns": "^3.1.0",
"drizzle-orm": "^0.29.3",
"embla-carousel-react": "^8.0.0-rc17",
"embla-carousel-react": "^8.0.0-rc19",
"generate-api-key": "^1.0.2",
"http-status-codes": "^2.3.0",
"leaflet": "^1.9.4",
@@ -67,7 +67,7 @@
"react": "18.2.0",
"react-day-picker": "^8.10.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.49.2",
"react-hook-form": "^7.49.3",
"react-leaflet": "^4.2.1",
"react-qr-code": "^2.0.12",
"react-resizable-panels": "^1.0.7",
@@ -84,19 +84,19 @@
"devDependencies": {
"@types/leaflet": "^1.9.8",
"@next/eslint-plugin-next": "^14.0.4",
"@types/eslint": "^8.56.0",
"@types/node": "^20.10.6",
"@types/react": "^18.2.46",
"@types/eslint": "^8.56.1",
"@types/node": "^20.10.7",
"@types/react": "^18.2.47",
"@types/react-dom": "^18.2.18",
"@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.16.0",
"@typescript-eslint/eslint-plugin": "^6.18.0",
"@typescript-eslint/parser": "^6.18.0",
"autoprefixer": "^10.4.16",
"dotenv-cli": "^7.3.0",
"drizzle-kit": "^0.20.10",
"eslint": "^8.56.0",
"postcss": "^8.4.33",
"prettier": "^3.1.1",
"prettier-plugin-tailwindcss": "^0.5.10",
"prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3"
},

View File

@@ -1,6 +1,6 @@
{
"name": "Parent Grin",
"short_name": "parentgrine",
"name": "Kidarr - radar for your kids",
"short_name": "kidarr",
"icons": [
{
"src": "/android-chrome-192x192.png",

View File

@@ -7,9 +7,9 @@ echo Removing migrations
rm -rf drizzle
echo "Dropping db"
dropdb -f --if-exists parentgrine
dropdb -f --if-exists kidarr
echo "Creating db"
createdb parentgrine
createdb kidarr
bunx drizzle-kit generate:pg --config=./drizzle.config.ts
bunx drizzle-kit push:pg --config=./drizzle.config.ts

View File

@@ -1,25 +1,25 @@
import '@/styles/globals.css';
import "@/styles/globals.css";
import { ABeeZee as TheFont } from 'next/font/google';
import { cookies } from 'next/headers';
import { ABeeZee as TheFont } from "next/font/google";
import { cookies } from "next/headers";
import { TRPCReactProvider } from '@/trpc/react';
import { ThemeProvider } from '@/components/providers/theme-provider';
import { type Metadata } from 'next';
import NextAuthProvider from '@/lib/services/auth/provider';
import { TRPCReactProvider } from "@/trpc/react";
import { ThemeProvider } from "@/components/providers/theme-provider";
import { type Metadata } from "next";
import NextAuthProvider from "@/lib/services/auth/provider";
const inter = TheFont({
weight: '400',
subsets: ['latin'],
variable: '--font-sans',
weight: "400",
subsets: ["latin"],
variable: "--font-sans",
});
export const metadata: Metadata = {
title: 'ParentGrine Falcon',
description: 'Laser focused on your kids',
manifest: '/site.webmanifest',
title: "Kidarr",
description: "Radar for your kids",
manifest: "/site.webmanifest",
icons: {
icon: '/favicon.ico',
icon: "/favicon.ico",
},
};

View File

@@ -1,24 +1,25 @@
'use client';
import 'leaflet/dist/leaflet.css';
import React, { useEffect, useState } from 'react';
import { MapContainer, Marker, Popup, TileLayer, Circle } from 'react-leaflet';
import { usePingSocket } from '@/lib/hooks/use-ping-socket';
import type ChildModel from '@/lib/models/child';
import MapMarker from '@/components/maps/map-marker';
import { getLatestPing } from '@/lib/utils';
"use client";
import "leaflet/dist/leaflet.css";
import React, { useEffect, useState } from "react";
import { MapContainer, Marker, Popup, TileLayer, Circle } from "react-leaflet";
import { usePingSocket } from "@/lib/hooks/use-ping-socket";
import type ChildModel from "@/lib/models/child";
import MapMarker from "@/components/maps/map-marker";
import { getLatestPing } from "@/lib/helpers/location/ping";
type MainMapProps = {
kids: ChildModel[];
}
};
const MainMap: React.FC<MainMapProps> = ({ kids }) => {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
console.log('MainMap', 'kids', kids);
console.log("MainMap", "kids", kids);
}, [kids]);
return isMounted && <div>
return (
isMounted && (
<div>
<MapContainer
className="map"
center={[51.903614, -8.468399]}
@@ -27,11 +28,13 @@ const MainMap: React.FC<MainMapProps> = ({ kids }) => {
>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<>
{kids?.map((kid) =>
{kids?.map(
(kid) =>
kid.devices?.map((device) => {
const latestPing = getLatestPing(device.pings);
return (
latestPing && <MapMarker
latestPing && (
<MapMarker
key={latestPing.id}
childName={kid.name}
avatar={kid.avatar}
@@ -39,13 +42,16 @@ const MainMap: React.FC<MainMapProps> = ({ kids }) => {
deviceName={device.deviceName}
latitude={latestPing.latitude}
longitude={latestPing.longitude}
timestamp={latestPing.timestamp} />
timestamp={latestPing.timestamp}
/>
)
);
},
),
}),
)}
</>
</MapContainer>
</div>;
</div>
)
);
};
export default MainMap;

View File

@@ -1,22 +1,21 @@
import React from 'react';
import L from 'leaflet';
import { Marker, Popup } from 'react-leaflet';
import React from "react";
import L from "leaflet";
import { Marker, Popup } from "react-leaflet";
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from '@/components/ui/card';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { getInitials } from '@/lib/utils';
import Link from 'next/link';
import { Icons } from '@/components/icons';
import { usePingSocket } from '@/lib/hooks/use-ping-socket';
} from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import Link from "next/link";
import { Icons } from "@/components/icons";
import { usePingSocket } from "@/lib/hooks/use-ping-socket";
import { getInitials } from "@/lib/helpers/name";
type MapMarkerProps = {
childName: string;
avatar: string | null
avatar: string | null;
deviceId: string;
deviceName: string;
latitude: number;
@@ -24,30 +23,39 @@ type MapMarkerProps = {
timestamp: Date;
};
const _getAvatarImage = (avatar: string | null) => avatar ?? '/img/default-avatar.png';
const _getAvatarImage = (avatar: string | null) =>
avatar ?? "/img/default-avatar.png";
const _getIcon = (icon: string | null) => {
return L.icon({
iconUrl: _getAvatarImage(icon),
iconSize: [24, 24],
});
};
const MapMarker: React.FC<MapMarkerProps> = (
{ childName, avatar, deviceId, deviceName, latitude, longitude, timestamp }) => {
const [position, setPosition] = React.useState<[number, number]>([latitude, longitude]);
const MapMarker: React.FC<MapMarkerProps> = ({
childName,
avatar,
deviceId,
deviceName,
latitude,
longitude,
timestamp,
}) => {
const [position, setPosition] = React.useState<[number, number]>([
latitude,
longitude,
]);
usePingSocket({
deviceId: deviceId,
locationUpdate: (ping) => {
console.log('MainMap', 'locationUpdate', ping);
console.log("MainMap", "locationUpdate", ping);
setPosition([ping.location.latitude, ping.location.longitude]);
},
});
return <div className="animate-spin">
<Marker
position={position}
icon={_getIcon(avatar)}
>
return (
<div className="animate-spin">
<Marker position={position} icon={_getIcon(avatar)}>
<Popup>
<Card className="w-full max-w-md grid gap-4">
<Card className="grid w-full max-w-md gap-4">
<CardHeader>
<div className="flex items-center gap-4">
<Avatar className="h-12 w-12">
@@ -57,7 +65,7 @@ const MapMarker: React.FC<MapMarkerProps> = (
<div className="grid gap-1">
<div className="text-lg font-bold">{childName}</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
<div className="flex flex-row items-center px-2 space-x-3">
<div className="flex flex-row items-center space-x-3 px-2">
<Icons.device /> {deviceName}
</div>
</div>
@@ -66,7 +74,10 @@ const MapMarker: React.FC<MapMarkerProps> = (
</CardHeader>
<CardContent>
<div className="text-sm text-gray-500 dark:text-gray-400">
Last seen - {timestamp.toLocaleDateString() + ' ' + timestamp.toLocaleTimeString()}
Last seen -{" "}
{timestamp.toLocaleDateString() +
" " +
timestamp.toLocaleTimeString()}
</div>
</CardContent>
<CardFooter className="flex justify-end gap-4">
@@ -80,7 +91,8 @@ const MapMarker: React.FC<MapMarkerProps> = (
</Card>
</Popup>
</Marker>
</div>;
</div>
);
};
export default MapMarker;

View File

@@ -12,8 +12,8 @@ function HomePage() {
<div className="container mx-auto text-center">
<h2 className="text-4xl font-bold">Track Your Children with Ease</h2>
<p className="mt-4 text-lg text-muted-foreground">
Parentgrine Falcon helps you keep an eye on your loved ones and
ensure their safety.
Kidarr helps you keep an eye on your loved ones and ensure their
safety.
</p>
<Link
className={cn(
@@ -22,13 +22,13 @@ function HomePage() {
)}
href="/signin"
>
<Icons.rocket className="w-4 h-4 mr-2" /> Let's go
<Icons.rocket className="mr-2 h-4 w-4" /> Let's go
</Link>
</div>
</section>
<section className="py-16">
<div className="container grid grid-cols-1 gap-8 mx-auto md:grid-cols-3">
<div className="container mx-auto grid grid-cols-1 gap-8 md:grid-cols-3">
<Card>
<CardHeader>
<CardTitle>Real-Time Location Tracking</CardTitle>
@@ -65,14 +65,13 @@ function HomePage() {
</Card>
</div>
</section>
<section className="py-16 bg-background">
<section className="bg-background py-16">
<div className="container mx-auto text-center">
<h3 className="text-3xl font-semibold">
Keep Your Children Safe Today!
</h3>
<p className="mt-4 text-lg text-muted-foreground">
Download Parentgrine Falcon now and stay connected with your loved
ones.
Download Kidarr now and stay connected with your loved ones.
</p>
<Link
@@ -82,14 +81,14 @@ function HomePage() {
)}
href="/download"
>
<Icons.mobile className="w-4 h-4 mr-2" /> Download Now
<Icons.mobile className="mr-2 h-4 w-4" /> Download Now
</Link>
</div>
</section>
<footer className="py-8 text-center bg-secondary-foreground text-secondary">
<footer className="bg-secondary-foreground py-8 text-center text-secondary">
<p>
An open source experiment from PodNoms - source code available{" "}
<Link target="_blank" href="https://github.com/parentgrine">
<Link target="_blank" href="https://github.com/kid-arr">
here
</Link>
</p>

View File

@@ -1,24 +1,21 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
"use client";
import * as React from "react";
import useEmblaCarousel, {
type EmblaCarouselType as CarouselApi,
type EmblaOptionsType as CarouselOptions,
type EmblaPluginType as CarouselPlugin,
type UseEmblaCarouselType,
} from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = {
opts?: CarouselOptions;
plugins?: CarouselPlugin[];
plugins?: CarouselPlugin;
orientation?: "horizontal" | "vertical";
setApi?: (api: CarouselApi) => void;
};
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1];

View File

@@ -23,7 +23,7 @@ const Command = React.forwardRef<
))
Command.displayName = CommandPrimitive.displayName
type CommandDialogProps = DialogProps
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (

View File

@@ -1,11 +1,11 @@
import * as React from "react"
import type * as LabelPrimitive from "@radix-ui/react-label"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
type ControllerProps,
type FieldPath,
type FieldValues,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"

View File

@@ -2,7 +2,8 @@ import * as React from "react"
import { cn } from "@/lib/utils"
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
import { cva } from 'class-variance-authority';
import { ChevronDown } from 'lucide-react';
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"
import { cn } from '@/lib/utils';
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
@@ -12,16 +12,16 @@ const NavigationMenu = React.forwardRef<
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
'relative z-10 flex max-w-max flex-1 items-center justify-center',
className,
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
));
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
@@ -30,19 +30,19 @@ const NavigationMenuList = React.forwardRef<
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
'group flex flex-1 list-none items-center justify-center space-x-1',
className,
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
));
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item;
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50',
);
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
@@ -50,17 +50,17 @@ const NavigationMenuTrigger = React.forwardRef<
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), 'group', className)}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{' '}
{children}{" "}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
));
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
@@ -69,33 +69,33 @@ const NavigationMenuContent = React.forwardRef<
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
'left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ',
className,
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
));
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link;
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn('absolute left-0 top-full flex justify-center')}>
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
'origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]',
className,
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
));
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName;
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
@@ -104,16 +104,16 @@ const NavigationMenuIndicator = React.forwardRef<
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
'top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in',
className,
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
));
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName;
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
@@ -125,4 +125,4 @@ export {
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
};
}

View File

@@ -2,7 +2,7 @@ import * as React from "react"
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
import { type ButtonProps, buttonVariants } from "@/components/ui/button"
import { ButtonProps, buttonVariants } from "@/components/ui/button"
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav

View File

@@ -1,10 +1,10 @@
'use client';
"use client"
import * as React from 'react';
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import { Circle } from 'lucide-react';
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from "lucide-react"
import { cn } from '@/lib/utils';
import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
@@ -12,13 +12,13 @@ const RadioGroup = React.forwardRef<
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn('grid gap-2', className)}
className={cn("grid gap-2", className)}
{...props}
ref={ref}
/>
);
});
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
@@ -28,8 +28,8 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
'aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
@@ -37,8 +37,8 @@ const RadioGroupItem = React.forwardRef<
<Circle className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
});
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem };
export { RadioGroup, RadioGroupItem }

View File

@@ -1,3 +1,5 @@
"use client"
import { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels"

View File

@@ -2,7 +2,8 @@ import * as React from "react"
import { cn } from "@/lib/utils"
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {

View File

@@ -1,18 +1,18 @@
"use client";
"use client"
import * as React from "react";
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
import { type VariantProps } from "class-variance-authority";
import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils";
import { toggleVariants } from "@/components/ui/toggle";
import { cn } from "@/lib/utils"
import { toggleVariants } from "@/components/ui/toggle"
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
});
})
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
@@ -28,34 +28,34 @@ const ToggleGroup = React.forwardRef<
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
));
))
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext);
const context = React.useContext(ToggleGroupContext)
return (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
toggleVariants({
variant: context.variant ?? variant,
size: context.size ?? size,
variant: context.variant || variant,
size: context.size || size,
}),
className,
className
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
);
});
)
})
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
export { ToggleGroup, ToggleGroupItem };
export { ToggleGroup, ToggleGroupItem }

View File

@@ -1,20 +1,20 @@
export type SiteConfig = typeof siteConfig;
export const siteConfig = {
name: 'Parentgrine Falcon',
description: 'Free & open source children tracking',
name: "Kidarr",
description: "Radar for your kids",
mainNav: [
{
title: 'Home',
href: '/dashboard',
title: "Home",
href: "/dashboard",
},
{
title: 'Children',
href: '/children',
title: "Children",
href: "/children",
},
],
links: {
twitter: 'https://twitter.com/podnoms',
github: 'https://github.com/parentgrine',
twitter: "https://twitter.com/podnoms",
github: "https://github.com/kid-arr",
},
};

View File

@@ -0,0 +1,10 @@
import type PingModel from "@/lib/models/ping";
export const getLatestPing = (pings: PingModel[]): PingModel | undefined => {
if (pings && pings.length !== 0) {
return pings.reduce((ping, current) =>
ping.timestamp > current.timestamp ? ping : current,
);
}
return undefined;
};

2
src/lib/helpers/name.ts Normal file
View File

@@ -0,0 +1,2 @@
export const getInitials = (name: string) =>
name?.match(/(\b\S)?/g)?.join("") ?? "";

View File

@@ -1,17 +1,6 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import type PingModel from '@/lib/models/ping';
import { ping } from '@/server/db/schema';
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export const getInitials = (name: string) => name?.match(/(\b\S)?/g)?.join('') ?? '';
export const getLatestPing = (pings: PingModel[]): PingModel | undefined => {
if (pings && pings.length !== 0) {
return pings.reduce((ping, current) => (ping.timestamp > current.timestamp ? ping : current));
}
return undefined;
};

View File

@@ -1,14 +1,14 @@
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
import { db } from '..';
import * as schema from '../schema';
import { faker } from '@faker-js/faker';
import { db } from "..";
import * as schema from "../schema";
import { faker } from "@faker-js/faker";
const main = async () => {
const seedUsers = [
{
id: '2250f34e-997a-44de-ab8d-beddeda13525',
name: 'Fergal Moran',
email: 'fergal.moran@gmail.com',
id: "2250f34e-997a-44de-ab8d-beddeda13525",
name: "Fergal Moran",
email: "fergal.moran@gmail.com",
emailVerified: new Date(),
},
];
@@ -16,10 +16,10 @@ const main = async () => {
const seedChildren = [
{
id: '2250f34e-997a-44de-ab8d-beddeda13525',
name: 'Lil Debuggles',
phone: '123 456 789',
email: 'lildebuggles@parentgrine.com',
id: "2250f34e-997a-44de-ab8d-beddeda13525",
name: "Lil Debuggles",
phone: "123 456 789",
email: "lildebuggles@kidarr.com",
avatar: faker.image.avatar(),
parentId: seedUsers[0]?.id!,
},
@@ -28,11 +28,11 @@ const main = async () => {
const seedDevices = [
{
id: '5af79a30-df27-4646-9d9f-77e19b4191c1',
deviceId: '373791e3-afe3-49de-b0a2-842a44071585',
id: "5af79a30-df27-4646-9d9f-77e19b4191c1",
deviceId: "373791e3-afe3-49de-b0a2-842a44071585",
childId: seedChildren[0]?.id!,
deviceName: 'Not an iPhone',
apiKey: 'nQhXtqemsWjzBpbDxlIV2qtDx9xxO4oZVBJADdhJLfA=',
deviceName: "Not an iPhone",
apiKey: "nQhXtqemsWjzBpbDxlIV2qtDx9xxO4oZVBJADdhJLfA=",
pin: 1234,
expires: new Date(2065),
},
@@ -63,12 +63,12 @@ const main = async () => {
return;
};
console.log('seed', 'Seeding');
console.log("seed", "Seeding");
main().then(
() => {
console.log('seed', 'Seeded');
console.log("seed", "Seeded");
},
(err) => {
console.log('seed', 'Error', err);
console.log("seed", "Error", err);
},
);

8
ssl-proxy.json Normal file
View File

@@ -0,0 +1,8 @@
{
"Dev Proxy": {
"source": "3000",
"target": "3002",
"key": "/etc/letsencrypt/live/dev.fergl.ie/privkey.pem",
"cert": "/etc/letsencrypt/live/dev.fergl.ie/fullchain.pem"
}
}