+
Cat's Profile
+ {message && (
+ <>
+ {message === 'Updates have been saved' && (
+
+ )}
+ {message === "Updates haven't been saved" && (
+
+ )}
+ {message === 'Profile Removed' && (
+
+ )}
+ {message === 'Failed to delete the profile' && (
+
+ )}
+ >
+ )}
{}}
+ value={catName}
+ onChange={handleProfileEditBy}
/>
{}}
+ value={catPicture ?? ''}
+ onChange={handleProfileEditBy}
/>
{}}
+ value={catDescription ?? ''}
+ onChange={handleProfileEditBy}
/>
-
+
+
);
}
-
export default CatProfile;
diff --git a/src/Components/Login.tsx b/src/Components/Login.tsx
index f43aad0..dff4f9f 100644
--- a/src/Components/Login.tsx
+++ b/src/Components/Login.tsx
@@ -1,49 +1,92 @@
import React, { useContext, useState } from 'react';
import { UserContext } from '../Contexts/UserContext';
+// import { registerUser } from '../network';
+import FormInput from './Styling/FormInput';
+import H2 from './Styling/H2';
+import Button from './Styling/Button';
+import ModalPopover from './Styling/ModalPopover';
export default function Login() {
+ const { setUsername, setUserId, setHome } = useContext(UserContext);
const [formUsername, setFormUsername] = useState
('');
const [latitude, setLatitude] = useState('');
const [longitude, setLongitude] = useState('');
+ const [errorMessage, setErrorMessage] = useState('');
+
+ function isValidLat(input: string): boolean {
+ const value = parseFloat(input);
+ return !isNaN(value) && value >= -90 && value <= 90;
+ }
+
+ function isValidLong(input: string): boolean {
+ const value = parseFloat(input);
+ return !isNaN(value) && value >= -180 && value <= 180;
+ }
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
+
+ // For demo
+
+ setUsername('user1');
+ setUserId('cm3op7iwu0000jrcqa60tc9kv');
+ // return registerUser(formUsername).then(
+ // (newUser: null | { username: string }) => {
+ // if (!newUser) {
+ // setErrorMessage(
+ // 'Sorry, there was an error creating an account. Please try again later.'
+ // );
+ // } else {
+ // setUsername(newUser.username);
+ // setHome([parseFloat(latitude), parseFloat(longitude)]);
+ // }
+ // }
+ // );
}
return (
-
+ <>
+
+ Login
+
+
+ >
);
}
diff --git a/src/Components/MapContainer.tsx b/src/Components/MapContainer.tsx
index 24da93a..46aa9a0 100644
--- a/src/Components/MapContainer.tsx
+++ b/src/Components/MapContainer.tsx
@@ -1,11 +1,11 @@
import React, { useEffect, useRef, useState } from 'react';
import 'mapbox-gl/dist/mapbox-gl.css';
-import Cat from '../Interfaces/Cat';
+import Cat from '../Types/Cat';
import Coordinates from '../Types/Coordinates';
import createMap from '../createMap';
-import axios from 'axios';
-import Device from '../Interfaces/Device';
-import CatFromAxios from '../Interfaces/CatFromAxios';
+import Device from '../Types/Device';
+import CatFromAxios from '../Types/CatFromAxios';
+import { getCatsForUser, getCatsNear, getDevicesForUser } from '../network';
export default function MapContainer() {
const mapContainer = useRef(null);
@@ -15,47 +15,49 @@ export default function MapContainer() {
const [userId, setUserId] = useState('cm3op7iwu0000jrcqa60tc9kv'); // Test user id for now as no global user variable
useEffect(() => {
- Promise.all([
- axios.get(`http://localhost:9090/api/users/${userId}/devices`),
- axios.get(`http://localhost:9090/api/users/${userId}/cats`),
- ])
- .then(
- ([
- {
- data: { data: deviceData },
- },
- {
- data: { data: catData },
- },
- ]) => {
- const catsHistory: { lat: number; lon: number }[][] = deviceData.map(
- (device: Device) => device.location_history.slice(-205) // If it updates every 7 mins this should be the last 24 hours
+ Promise.all([getDevicesForUser(userId), getCatsForUser(userId)])
+ .then(([devices, cats]: [Device[], CatFromAxios[]]) => {
+ const ownedCats: Cat[] = devices.map((device) => {
+ const catWearingDevice = cats.find(
+ (aCat) => (aCat.device_id = device.id)
);
- const catsNameAndImage: { name: string; image: string }[] =
- catData.map((cat: CatFromAxios) => ({
- name: cat.name,
- image: cat.picture_url,
- }));
-
- const fullCatsMapInfo: Cat[] = catsHistory.map(
- (history, index: number) => ({
- name: catsNameAndImage[index].name,
- image: catsNameAndImage[index].image,
- history,
- })
- );
- setCatsMapInfo(fullCatsMapInfo);
- }
- )
+ if (!catWearingDevice)
+ throw new Error('No cat found for that device');
+ return {
+ id: catWearingDevice.id,
+ name: catWearingDevice.name,
+ image: catWearingDevice.picture_url,
+ history: device.location_history.slice(-205),
+ owned: true,
+ };
+ });
+ return Promise.all([
+ ownedCats,
+ ...ownedCats.map(({ id }) => getCatsNear(id)),
+ ]);
+ })
+ .then(([ownedCats, ...nearbyCats]) => {
+ const dedupedNearbyCats: Cat[] = Array.from(
+ new Set(nearbyCats.flat())
+ ).map(({ cat: { id, name, picture_url }, last_location }) => {
+ return {
+ id,
+ name,
+ image: picture_url,
+ history: [last_location],
+ owned: false,
+ };
+ });
+ setCatsMapInfo([...ownedCats, ...dedupedNearbyCats]);
+ })
.catch((err) => {
console.log(err);
});
}, []);
- useEffect(
- () => createMap(mapContainer, home, catsMapInfo, map),
- [catsMapInfo]
- );
+ useEffect(() => {
+ createMap(mapContainer, home, catsMapInfo, map);
+ }, [catsMapInfo]);
return ;
}
diff --git a/src/Components/MyClowder.tsx b/src/Components/MyClowder.tsx
index f06ba62..2a63989 100644
--- a/src/Components/MyClowder.tsx
+++ b/src/Components/MyClowder.tsx
@@ -1,15 +1,41 @@
-import React, { SetStateAction } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
import CatProfile from './CatProfile';
import H2 from './Styling/H2';
import ModalPopover from './Styling/ModalPopover';
+import { getCatsProfiles } from '../network';
+import { UserContext } from '../Contexts/UserContext';
+import CatFromAxios from '../Interfaces/CatFromAxios';
+import CatCard from './CatCard';
export default function MyClowder() {
+ const { userId } = useContext(UserContext);
+ const [myCats, setMyCats] = useState([]);
+
+ useEffect(() => {
+ if (userId) {
+ getCatsProfiles(userId).then((cats) => {
+ if (cats) {
+ setMyCats(cats);
+ }
+ });
+ }
+ }, [userId]);
+
return (
<>
My Clowder
-
-
-
+
+ {myCats.map((cat) => (
+ -
+
+
+ ))}
+
+ {false && (
+
+
+
+ )}
>
);
}
diff --git a/src/Components/Styling/Button.tsx b/src/Components/Styling/Button.tsx
index 0e6960d..c082f04 100644
--- a/src/Components/Styling/Button.tsx
+++ b/src/Components/Styling/Button.tsx
@@ -2,14 +2,20 @@ import React, { ReactNode } from 'react';
interface Props {
children: ReactNode;
- onClick: React.MouseEventHandler | undefined;
+ type?: React.ButtonHTMLAttributes['type'];
+ onClick?: React.MouseEventHandler | undefined;
+ disabled?: React.ButtonHTMLAttributes['disabled'];
}
-export default function Button({ children, onClick }: Props) {
+export default function Button({ children, type, onClick, disabled }: Props) {
return (
diff --git a/src/Components/Styling/Card.tsx b/src/Components/Styling/Card.tsx
index d736f90..10553b0 100644
--- a/src/Components/Styling/Card.tsx
+++ b/src/Components/Styling/Card.tsx
@@ -8,7 +8,7 @@ interface Props {
export default function Card(props: Props) {
return (
-
+
{props.heading}
diff --git a/src/Components/Styling/FormElement.tsx b/src/Components/Styling/FormElement.tsx
index ceb4d4f..eafd6b1 100644
--- a/src/Components/Styling/FormElement.tsx
+++ b/src/Components/Styling/FormElement.tsx
@@ -7,9 +7,11 @@ interface Props {
export default function FormElement({ label, children }: Props) {
return (
-
+
+
+
);
}
diff --git a/src/Components/Styling/FormInput.tsx b/src/Components/Styling/FormInput.tsx
index 5a601bb..fc3e897 100644
--- a/src/Components/Styling/FormInput.tsx
+++ b/src/Components/Styling/FormInput.tsx
@@ -6,7 +6,8 @@ interface Props {
label: string;
name: string | undefined;
value: string | number | readonly string[] | undefined;
- onChange: React.ChangeEventHandler
| undefined;
+ onChange?: React.ChangeEventHandler | undefined;
+ placeholder?: string;
}
export default function FormInput({
@@ -19,7 +20,7 @@ export default function FormInput({
return (
{children};
+ return {children}
;
}
diff --git a/src/Components/Styling/Messages.tsx b/src/Components/Styling/Messages.tsx
new file mode 100644
index 0000000..e07779b
--- /dev/null
+++ b/src/Components/Styling/Messages.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+interface Props {
+ text: string;
+ status: 'success' | 'error';
+}
+
+export default function Messages({ text, status }: Props) {
+ const baseClass = 'text-2xl font-bold mb-20';
+ const statusClass = {
+ success: 'bg-green-100 text-green-900',
+ error: 'bg-red-100 text-red-800',
+ }[status];
+
+ return {text}
;
+}
diff --git a/src/Components/Styling/ModalPopover.tsx b/src/Components/Styling/ModalPopover.tsx
index 9cc00a9..ac851f3 100644
--- a/src/Components/Styling/ModalPopover.tsx
+++ b/src/Components/Styling/ModalPopover.tsx
@@ -5,5 +5,9 @@ interface Props {
}
export default function ModalPopover({ children }: Props) {
- return {children}
;
+ return (
+
+ {children}
+
+ );
}
diff --git a/src/Interfaces/Cat.ts b/src/Types/Cat.ts
similarity index 62%
rename from src/Interfaces/Cat.ts
rename to src/Types/Cat.ts
index 33cee06..424ee6a 100644
--- a/src/Interfaces/Cat.ts
+++ b/src/Types/Cat.ts
@@ -1,5 +1,7 @@
export default interface Cat {
+ id: string;
name: string;
- image: string;
+ image: string | null;
history: { lat: number; lon: number }[];
+ owned: boolean;
}
diff --git a/src/Interfaces/CatFromAxios.ts b/src/Types/CatFromAxios.ts
similarity index 71%
rename from src/Interfaces/CatFromAxios.ts
rename to src/Types/CatFromAxios.ts
index 2cd7471..43f7744 100644
--- a/src/Interfaces/CatFromAxios.ts
+++ b/src/Types/CatFromAxios.ts
@@ -7,4 +7,9 @@ export default interface CatFromAxios {
owner_id: string;
updated_at: string;
created_at: string;
+ deleted_at: string | null;
+ battle_profile: {
+ level: Number;
+ xp: number;
+ };
}
diff --git a/src/Interfaces/Device.ts b/src/Types/Device.ts
similarity index 100%
rename from src/Interfaces/Device.ts
rename to src/Types/Device.ts
diff --git a/src/createMap.ts b/src/createMap.ts
index 5a2ad65..1e93e9b 100644
--- a/src/createMap.ts
+++ b/src/createMap.ts
@@ -1,5 +1,5 @@
import mapboxgl from 'mapbox-gl';
-import Cat from './Interfaces/Cat';
+import Cat from './Types/Cat';
import Coordinates from './Types/Coordinates';
export default function annotateMap(
@@ -29,15 +29,12 @@ export default function annotateMap(
}
if (map.current) {
+ console.log(cats, '<<< Cats for map');
cats.forEach((cat) => {
- const historyFormatted: Coordinates[] = cat.history.map((coord) => [
- coord.lat,
- coord.lon,
- ]);
-
- if (historyFormatted.length > 0) {
- drawCat(cat, historyFormatted, map);
- }
+ const historyFormatted: Coordinates[] = cat.owned
+ ? cat.history.map((coord) => [coord.lat, coord.lon])
+ : [];
+ drawCat(cat, historyFormatted, map);
});
}
}
@@ -48,7 +45,8 @@ function drawCat(
) {
drawCatPopup(cat, historyFormatted, map);
- drawCatPath(historyFormatted, map, cat);
+ if (historyFormatted.length && cat.owned)
+ drawCatPath(historyFormatted, map, cat);
}
function drawCatPath(
@@ -98,15 +96,17 @@ function drawCatPopup(
) {
const catLocation: Coordinates | undefined = historyFormatted.at(-1);
if (catLocation) {
+ const imageDimensions = cat.owned ? '50px' : '100px';
const catIcon = document.createElement('div');
- catIcon.style.width = '50px';
- catIcon.style.height = '50px';
+ catIcon.style.width = imageDimensions;
+ catIcon.style.height = imageDimensions;
catIcon.style.backgroundImage = `url(${cat.image})`;
catIcon.style.backgroundSize = 'cover'; // Sets icon ontop of map properly
- const popup = new mapboxgl.Popup({ offset: 25 }).setHTML(
- ``
- );
+ const popupHtml = cat.owned
+ ? ``
+ : ``;
+ const popup = new mapboxgl.Popup({ offset: 25 }).setHTML(popupHtml);
new mapboxgl.Marker({ element: catIcon })
.setLngLat({
diff --git a/src/network.ts b/src/network.ts
index 492786b..9f1ffcf 100644
--- a/src/network.ts
+++ b/src/network.ts
@@ -1,5 +1,9 @@
import axios, { AxiosResponse } from 'axios';
import { User } from './Types/User';
+import Device from './Types/Device';
+import CatFromAxios from './Types/CatFromAxios';
+
+const NEARBY_DISTANCE = 1000;
const network = axios.create({
baseURL: process.env.REACT_APP_BACKEND_HOST,
@@ -31,3 +35,66 @@ export function updateUser(data: User): Promise {
return {};
});
}
+
+export function getDevicesForUser(userId: string): Promise {
+ return network
+ .get(`/api/users/${userId}/devices`)
+ .then(({ data: { data: devices } }) => {
+ return devices;
+ })
+ .catch((err) => {
+ console.log(err);
+ return [];
+ });
+}
+
+export function getCatsForUser(userId: string): Promise {
+ return network
+ .get(`/api/users/${userId}/cats`)
+ .then(({ data: { data: cats } }) => {
+ return cats;
+ })
+ .catch((err) => {
+ console.log(err);
+ return [];
+ });
+}
+
+export function getCatsNear(
+ catId: string
+): Promise<
+ { cat: CatFromAxios; last_location: { lat: number; lon: number } }[]
+> {
+ return network
+ .get(`/api/cats/nearby/${catId}/${NEARBY_DISTANCE}`)
+ .then(({ data }) => {
+ return data;
+ });
+}
+
+export const deleteCatProfile = (
+ id: string
+): Promise => {
+ return network
+ .delete(`/api/users/${id}/cats`)
+ .then(() => {
+ console.log('Cats profile has been deleted');
+ })
+ .catch((error) => {
+ console.log('Cats profile hasnt been deleted');
+ });
+};
+
+export const updateCatProfile = (
+ updatedProfile: CatFromAxios
+): Promise => {
+ return network
+ .post(`/api/cats/update`, updatedProfile)
+ .then(({ data: { data: updatedProfile } }) => {
+ return updatedProfile;
+ })
+ .catch((error) => {
+ console.log(error);
+ return [];
+ });
+};