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) [![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 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/parentgrine/parentgrine-server)](https://github.com/parentgrine/parentgrine-server/stargazers) [![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/parentgrine/parentgrine-server)](https://github.com/parentgrine/parentgrine-server/network) [![GitHub forks](https://img.shields.io/github/forks/kid-arr/kidarr-server)](https://github.com/kid-arr/kidarr-server/network)
## Overview ## 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, 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 ## Features
@@ -36,12 +36,12 @@ ParentGrine provides parents with the peace of mind they deserve.
### Installation ### 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). 2. Follow the installation instructions in the [documentation](docs/INSTALL.md).
## Usage ## 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. 2. Log in with your parent account.
3. Enjoy peace of mind by tracking your child's location. 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 ## Support
If you encounter any issues or have questions, feel free 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 ## License
ParentGrine is licensed under the [MIT License](LICENSE). Kidarr is licensed under the [MIT License](LICENSE).
Happy tracking! 🌍✨ Happy tracking! 🌍✨

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,5 +1,5 @@
{ {
"name": "parentgrine-server", "name": "kidarr",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -7,14 +7,14 @@
"build": "next build", "build": "next build",
"db:push": "dotenv drizzle-kit push:postgres", "db:push": "dotenv drizzle-kit push:postgres",
"db:studio": "dotenv drizzle-kit studio", "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", "lint": "next lint",
"start": "next start" "start": "next start"
}, },
"dependencies": { "dependencies": {
"@auth/drizzle-adapter": "^0.3.14", "@auth/drizzle-adapter": "^0.3.14",
"@faker-js/faker": "^8.3.1", "@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-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-aspect-ratio": "^1.0.3", "@radix-ui/react-aspect-ratio": "^1.0.3",
@@ -43,17 +43,17 @@
"@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7",
"@t3-oss/env-nextjs": "^0.7.1", "@t3-oss/env-nextjs": "^0.7.1",
"@tanstack/react-query": "^4.36.1", "@tanstack/react-query": "^4.35.3",
"@trpc/client": "^10.43.6", "@trpc/client": "^10.45.0",
"@trpc/next": "^10.43.6", "@trpc/next": "^10.45.0",
"@trpc/react-query": "^10.43.6", "@trpc/react-query": "^10.45.0",
"@trpc/server": "^10.43.6", "@trpc/server": "^10.45.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"cmdk": "^0.2.0", "cmdk": "^0.2.0",
"date-fns": "^3.0.6", "date-fns": "^3.1.0",
"drizzle-orm": "^0.29.3", "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", "generate-api-key": "^1.0.2",
"http-status-codes": "^2.3.0", "http-status-codes": "^2.3.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
@@ -67,7 +67,7 @@
"react": "18.2.0", "react": "18.2.0",
"react-day-picker": "^8.10.0", "react-day-picker": "^8.10.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "^7.49.2", "react-hook-form": "^7.49.3",
"react-leaflet": "^4.2.1", "react-leaflet": "^4.2.1",
"react-qr-code": "^2.0.12", "react-qr-code": "^2.0.12",
"react-resizable-panels": "^1.0.7", "react-resizable-panels": "^1.0.7",
@@ -84,19 +84,19 @@
"devDependencies": { "devDependencies": {
"@types/leaflet": "^1.9.8", "@types/leaflet": "^1.9.8",
"@next/eslint-plugin-next": "^14.0.4", "@next/eslint-plugin-next": "^14.0.4",
"@types/eslint": "^8.56.0", "@types/eslint": "^8.56.1",
"@types/node": "^20.10.6", "@types/node": "^20.10.7",
"@types/react": "^18.2.46", "@types/react": "^18.2.47",
"@types/react-dom": "^18.2.18", "@types/react-dom": "^18.2.18",
"@typescript-eslint/eslint-plugin": "^6.16.0", "@typescript-eslint/eslint-plugin": "^6.18.0",
"@typescript-eslint/parser": "^6.16.0", "@typescript-eslint/parser": "^6.18.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"dotenv-cli": "^7.3.0", "dotenv-cli": "^7.3.0",
"drizzle-kit": "^0.20.10", "drizzle-kit": "^0.20.10",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"postcss": "^8.4.33", "postcss": "^8.4.33",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"prettier-plugin-tailwindcss": "^0.5.10", "prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },

View File

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

View File

@@ -7,9 +7,9 @@ echo Removing migrations
rm -rf drizzle rm -rf drizzle
echo "Dropping db" echo "Dropping db"
dropdb -f --if-exists parentgrine dropdb -f --if-exists kidarr
echo "Creating db" echo "Creating db"
createdb parentgrine createdb kidarr
bunx drizzle-kit generate:pg --config=./drizzle.config.ts bunx drizzle-kit generate:pg --config=./drizzle.config.ts
bunx drizzle-kit push: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 { ABeeZee as TheFont } from "next/font/google";
import { cookies } from 'next/headers'; import { cookies } from "next/headers";
import { TRPCReactProvider } from '@/trpc/react'; import { TRPCReactProvider } from "@/trpc/react";
import { ThemeProvider } from '@/components/providers/theme-provider'; import { ThemeProvider } from "@/components/providers/theme-provider";
import { type Metadata } from 'next'; import { type Metadata } from "next";
import NextAuthProvider from '@/lib/services/auth/provider'; import NextAuthProvider from "@/lib/services/auth/provider";
const inter = TheFont({ const inter = TheFont({
weight: '400', weight: "400",
subsets: ['latin'], subsets: ["latin"],
variable: '--font-sans', variable: "--font-sans",
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'ParentGrine Falcon', title: "Kidarr",
description: 'Laser focused on your kids', description: "Radar for your kids",
manifest: '/site.webmanifest', manifest: "/site.webmanifest",
icons: { icons: {
icon: '/favicon.ico', icon: "/favicon.ico",
}, },
}; };

View File

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

View File

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

View File

@@ -12,8 +12,8 @@ function HomePage() {
<div className="container mx-auto text-center"> <div className="container mx-auto text-center">
<h2 className="text-4xl font-bold">Track Your Children with Ease</h2> <h2 className="text-4xl font-bold">Track Your Children with Ease</h2>
<p className="mt-4 text-lg text-muted-foreground"> <p className="mt-4 text-lg text-muted-foreground">
Parentgrine Falcon helps you keep an eye on your loved ones and Kidarr helps you keep an eye on your loved ones and ensure their
ensure their safety. safety.
</p> </p>
<Link <Link
className={cn( className={cn(
@@ -22,13 +22,13 @@ function HomePage() {
)} )}
href="/signin" 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> </Link>
</div> </div>
</section> </section>
<section className="py-16"> <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> <Card>
<CardHeader> <CardHeader>
<CardTitle>Real-Time Location Tracking</CardTitle> <CardTitle>Real-Time Location Tracking</CardTitle>
@@ -65,14 +65,13 @@ function HomePage() {
</Card> </Card>
</div> </div>
</section> </section>
<section className="py-16 bg-background"> <section className="bg-background py-16">
<div className="container mx-auto text-center"> <div className="container mx-auto text-center">
<h3 className="text-3xl font-semibold"> <h3 className="text-3xl font-semibold">
Keep Your Children Safe Today! Keep Your Children Safe Today!
</h3> </h3>
<p className="mt-4 text-lg text-muted-foreground"> <p className="mt-4 text-lg text-muted-foreground">
Download Parentgrine Falcon now and stay connected with your loved Download Kidarr now and stay connected with your loved ones.
ones.
</p> </p>
<Link <Link
@@ -82,14 +81,14 @@ function HomePage() {
)} )}
href="/download" 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> </Link>
</div> </div>
</section> </section>
<footer className="py-8 text-center bg-secondary-foreground text-secondary"> <footer className="bg-secondary-foreground py-8 text-center text-secondary">
<p> <p>
An open source experiment from PodNoms - source code available{" "} 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 here
</Link> </Link>
</p> </p>

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
import * as React from "react" 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 { Slot } from "@radix-ui/react-slot"
import { import {
Controller, Controller,
type ControllerProps, ControllerProps,
type FieldPath, FieldPath,
type FieldValues, FieldValues,
FormProvider, FormProvider,
useFormContext, useFormContext,
} from "react-hook-form" } from "react-hook-form"

View File

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

View File

@@ -1,9 +1,9 @@
import * as React from 'react'; import * as React from "react"
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu'; import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from 'class-variance-authority'; import { cva } from "class-variance-authority"
import { ChevronDown } from 'lucide-react'; import { ChevronDown } from "lucide-react"
import { cn } from '@/lib/utils'; import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef< const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>, React.ElementRef<typeof NavigationMenuPrimitive.Root>,
@@ -12,16 +12,16 @@ const NavigationMenu = React.forwardRef<
<NavigationMenuPrimitive.Root <NavigationMenuPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn(
'relative z-10 flex max-w-max flex-1 items-center justify-center', "relative z-10 flex max-w-max flex-1 items-center justify-center",
className, className
)} )}
{...props} {...props}
> >
{children} {children}
<NavigationMenuViewport /> <NavigationMenuViewport />
</NavigationMenuPrimitive.Root> </NavigationMenuPrimitive.Root>
)); ))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName; NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef< const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>, React.ElementRef<typeof NavigationMenuPrimitive.List>,
@@ -30,19 +30,19 @@ const NavigationMenuList = React.forwardRef<
<NavigationMenuPrimitive.List <NavigationMenuPrimitive.List
ref={ref} ref={ref}
className={cn( className={cn(
'group flex flex-1 list-none items-center justify-center space-x-1', "group flex flex-1 list-none items-center justify-center space-x-1",
className, className
)} )}
{...props} {...props}
/> />
)); ))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName; NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item; const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva( 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< const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>, React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
@@ -50,17 +50,17 @@ const NavigationMenuTrigger = React.forwardRef<
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger <NavigationMenuPrimitive.Trigger
ref={ref} ref={ref}
className={cn(navigationMenuTriggerStyle(), 'group', className)} className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props} {...props}
> >
{children}{' '} {children}{" "}
<ChevronDown <ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180" className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true" aria-hidden="true"
/> />
</NavigationMenuPrimitive.Trigger> </NavigationMenuPrimitive.Trigger>
)); ))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName; NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef< const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>, React.ElementRef<typeof NavigationMenuPrimitive.Content>,
@@ -69,33 +69,33 @@ const NavigationMenuContent = React.forwardRef<
<NavigationMenuPrimitive.Content <NavigationMenuPrimitive.Content
ref={ref} ref={ref}
className={cn( 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 ', "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, className
)} )}
{...props} {...props}
/> />
)); ))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName; NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link; const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef< const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>, React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport> React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => ( >(({ 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 <NavigationMenuPrimitive.Viewport
className={cn( 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)]', "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, className
)} )}
ref={ref} ref={ref}
{...props} {...props}
/> />
</div> </div>
)); ))
NavigationMenuViewport.displayName = NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName; NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef< const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>, React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
@@ -104,16 +104,16 @@ const NavigationMenuIndicator = React.forwardRef<
<NavigationMenuPrimitive.Indicator <NavigationMenuPrimitive.Indicator
ref={ref} ref={ref}
className={cn( 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', "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, className
)} )}
{...props} {...props}
> >
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" /> <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator> </NavigationMenuPrimitive.Indicator>
)); ))
NavigationMenuIndicator.displayName = NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName; NavigationMenuPrimitive.Indicator.displayName
export { export {
navigationMenuTriggerStyle, navigationMenuTriggerStyle,
@@ -125,4 +125,4 @@ export {
NavigationMenuLink, NavigationMenuLink,
NavigationMenuIndicator, NavigationMenuIndicator,
NavigationMenuViewport, NavigationMenuViewport,
}; }

View File

@@ -2,7 +2,7 @@ import * as React from "react"
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils" 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">) => ( const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav <nav

View File

@@ -1,10 +1,10 @@
'use client'; "use client"
import * as React from 'react'; import * as React from "react"
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from 'lucide-react'; import { Circle } from "lucide-react"
import { cn } from '@/lib/utils'; import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef< const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>, React.ElementRef<typeof RadioGroupPrimitive.Root>,
@@ -12,13 +12,13 @@ const RadioGroup = React.forwardRef<
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
return ( return (
<RadioGroupPrimitive.Root <RadioGroupPrimitive.Root
className={cn('grid gap-2', className)} className={cn("grid gap-2", className)}
{...props} {...props}
ref={ref} ref={ref}
/> />
); )
}); })
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef< const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>, React.ElementRef<typeof RadioGroupPrimitive.Item>,
@@ -28,8 +28,8 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
ref={ref} ref={ref}
className={cn( 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', "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, className
)} )}
{...props} {...props}
> >
@@ -37,8 +37,8 @@ const RadioGroupItem = React.forwardRef<
<Circle className="h-2.5 w-2.5 fill-current text-current" /> <Circle className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator> </RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item> </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 { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels" import * as ResizablePrimitive from "react-resizable-panels"

View File

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

View File

@@ -1,18 +1,18 @@
"use client"; "use client"
import * as React from "react"; import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"; import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { type VariantProps } from "class-variance-authority"; import { VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils"
import { toggleVariants } from "@/components/ui/toggle"; import { toggleVariants } from "@/components/ui/toggle"
const ToggleGroupContext = React.createContext< const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants> VariantProps<typeof toggleVariants>
>({ >({
size: "default", size: "default",
variant: "default", variant: "default",
}); })
const ToggleGroup = React.forwardRef< const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>, React.ElementRef<typeof ToggleGroupPrimitive.Root>,
@@ -28,34 +28,34 @@ const ToggleGroup = React.forwardRef<
{children} {children}
</ToggleGroupContext.Provider> </ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root> </ToggleGroupPrimitive.Root>
)); ))
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName; ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
const ToggleGroupItem = React.forwardRef< const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>, React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> & React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants> VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => { >(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext); const context = React.useContext(ToggleGroupContext)
return ( return (
<ToggleGroupPrimitive.Item <ToggleGroupPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
toggleVariants({ toggleVariants({
variant: context.variant ?? variant, variant: context.variant || variant,
size: context.size ?? size, size: context.size || size,
}), }),
className, className
)} )}
{...props} {...props}
> >
{children} {children}
</ToggleGroupPrimitive.Item> </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 type SiteConfig = typeof siteConfig;
export const siteConfig = { export const siteConfig = {
name: 'Parentgrine Falcon', name: "Kidarr",
description: 'Free & open source children tracking', description: "Radar for your kids",
mainNav: [ mainNav: [
{ {
title: 'Home', title: "Home",
href: '/dashboard', href: "/dashboard",
}, },
{ {
title: 'Children', title: "Children",
href: '/children', href: "/children",
}, },
], ],
links: { links: {
twitter: 'https://twitter.com/podnoms', twitter: "https://twitter.com/podnoms",
github: 'https://github.com/parentgrine', 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 { type ClassValue, clsx } from "clsx";
import { twMerge } from 'tailwind-merge'; import { twMerge } from "tailwind-merge";
import type PingModel from '@/lib/models/ping';
import { ping } from '@/server/db/schema';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); 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 */ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
import { db } from '..'; import { db } from "..";
import * as schema from '../schema'; import * as schema from "../schema";
import { faker } from '@faker-js/faker'; import { faker } from "@faker-js/faker";
const main = async () => { const main = async () => {
const seedUsers = [ const seedUsers = [
{ {
id: '2250f34e-997a-44de-ab8d-beddeda13525', id: "2250f34e-997a-44de-ab8d-beddeda13525",
name: 'Fergal Moran', name: "Fergal Moran",
email: 'fergal.moran@gmail.com', email: "fergal.moran@gmail.com",
emailVerified: new Date(), emailVerified: new Date(),
}, },
]; ];
@@ -16,10 +16,10 @@ const main = async () => {
const seedChildren = [ const seedChildren = [
{ {
id: '2250f34e-997a-44de-ab8d-beddeda13525', id: "2250f34e-997a-44de-ab8d-beddeda13525",
name: 'Lil Debuggles', name: "Lil Debuggles",
phone: '123 456 789', phone: "123 456 789",
email: 'lildebuggles@parentgrine.com', email: "lildebuggles@kidarr.com",
avatar: faker.image.avatar(), avatar: faker.image.avatar(),
parentId: seedUsers[0]?.id!, parentId: seedUsers[0]?.id!,
}, },
@@ -28,11 +28,11 @@ const main = async () => {
const seedDevices = [ const seedDevices = [
{ {
id: '5af79a30-df27-4646-9d9f-77e19b4191c1', id: "5af79a30-df27-4646-9d9f-77e19b4191c1",
deviceId: '373791e3-afe3-49de-b0a2-842a44071585', deviceId: "373791e3-afe3-49de-b0a2-842a44071585",
childId: seedChildren[0]?.id!, childId: seedChildren[0]?.id!,
deviceName: 'Not an iPhone', deviceName: "Not an iPhone",
apiKey: 'nQhXtqemsWjzBpbDxlIV2qtDx9xxO4oZVBJADdhJLfA=', apiKey: "nQhXtqemsWjzBpbDxlIV2qtDx9xxO4oZVBJADdhJLfA=",
pin: 1234, pin: 1234,
expires: new Date(2065), expires: new Date(2065),
}, },
@@ -63,12 +63,12 @@ const main = async () => {
return; return;
}; };
console.log('seed', 'Seeding'); console.log("seed", "Seeding");
main().then( main().then(
() => { () => {
console.log('seed', 'Seeded'); console.log("seed", "Seeded");
}, },
(err) => { (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"
}
}