mirror of
https://github.com/fergalmoran/kidarr-server.git
synced 2025-12-22 09:17:51 +00:00
Merge branch '@feature/update_trpc' into develop
This commit is contained in:
22
README.md
22
README.md
@@ -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://github.com/parentgrine/parentgrine-server/issues)
|
||||
[](https://github.com/parentgrine/parentgrine-server/stargazers)
|
||||
[](https://github.com/parentgrine/parentgrine-server/network)
|
||||
[](https://github.com/kid-arr/kidarr-server/issues)
|
||||
[](https://github.com/kid-arr/kidarr-server/stargazers)
|
||||
[](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! 🌍✨
|
||||
|
||||
34
package.json
34
package.json
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import { GripVertical } from "lucide-react"
|
||||
import * as ResizablePrimitive from "react-resizable-panels"
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
|
||||
10
src/lib/helpers/location/ping.ts
Normal file
10
src/lib/helpers/location/ping.ts
Normal 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
2
src/lib/helpers/name.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const getInitials = (name: string) =>
|
||||
name?.match(/(\b\S)?/g)?.join("") ?? "";
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
8
ssl-proxy.json
Normal 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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user