diff --git a/apps/backend/prisma/seed.ts b/apps/backend/prisma/seed.ts index 3ed92e87..595f0491 100644 --- a/apps/backend/prisma/seed.ts +++ b/apps/backend/prisma/seed.ts @@ -104,6 +104,20 @@ async function main() { }, }); + await prismaClient.user.createMany({ + data: [ + {username: 'user1', email: 'user1@example.com', password_hash: await getPasswordHash(SAMPLE_PASSWORD)}, + {username: 'user2', email: 'user2@example.com', password_hash: await getPasswordHash(SAMPLE_PASSWORD)}, + {username: 'user3', email: 'user3@example.com', password_hash: await getPasswordHash(SAMPLE_PASSWORD)}, + {username: 'user4', email: 'user4@example.com', password_hash: await getPasswordHash(SAMPLE_PASSWORD)}, + {username: 'user5', email: 'user5@example.com', password_hash: await getPasswordHash(SAMPLE_PASSWORD)}, + {username: 'user6', email: 'user6@example.com', password_hash: await getPasswordHash(SAMPLE_PASSWORD)}, + {username: 'user7', email: 'user7@example.com', password_hash: await getPasswordHash(SAMPLE_PASSWORD)}, + {username: 'user8', email: 'user8@example.com', password_hash: await getPasswordHash(SAMPLE_PASSWORD)}, + {username: 'user9', email: 'user9@example.com', password_hash: await getPasswordHash(SAMPLE_PASSWORD)}, + ] + }) + const users = [bob, radu, shwetank, antonina, maria, mike, leia]; //--------------------------------------------------------- diff --git a/apps/backend/src/services/neighborhoodServices.ts b/apps/backend/src/services/neighborhoodServices.ts index de0e75d6..ca188b76 100644 --- a/apps/backend/src/services/neighborhoodServices.ts +++ b/apps/backend/src/services/neighborhoodServices.ts @@ -123,7 +123,7 @@ const isUserMemberOfNeighborhood = async ( return userIdsAssociatedWithNeighborhood.includes(loggedUserID); } - const error = new Error('No Neighborhood found'); + const error = new Error('Neighborhood not found.'); error.name = 'ResourceDoesNotExistError'; throw error; }; diff --git a/apps/backend/src/tests/neighborhood_api.test.ts b/apps/backend/src/tests/neighborhood_api.test.ts index c50153a0..7f94fe1b 100644 --- a/apps/backend/src/tests/neighborhood_api.test.ts +++ b/apps/backend/src/tests/neighborhood_api.test.ts @@ -100,10 +100,10 @@ describe('Tests for getting all neighborhoods: GET /neighborhoods', () => { .set('Authorization', `Bearer ${token}`); expect(res1.status).toEqual(404); - expect(res1.body).toEqual({ error: 'No Neighborhood found' }); + expect(res1.body).toEqual({ error: 'Neighborhood not found.' }); expect(res2.status).toEqual(404); - expect(res2.body).toEqual({ error: 'No Neighborhood found' }); + expect(res2.body).toEqual({ error: 'Neighborhood not found.' }); }); test('/?searchTerm=:searchTerm returns neighborhoods that match the search term', async () => { @@ -222,7 +222,7 @@ describe('Tests for getting a single neighborhood: GET /neighborhoods/:id', () = .get('/api/neighborhoods/0') .set('Authorization', `Bearer ${token}`); expect(response.status).toEqual(404); - expect(response.body.error).toEqual('No Neighborhood found'); + expect(response.body.error).toEqual('Neighborhood not found.'); }); test('GET /neighborhoods/:id only returns the id, name, description and location of neighborhood if user is not logged in', async () => { @@ -323,7 +323,8 @@ describe('Tests for creating a single neighborhood: POST /neighborhoods/:id ', ( const neighborhoodUsers = createNeighborhoodResponse.body.users; - const userNames = neighborhoodUsers.map((u: { username: any }) => u.username); + const userNames = neighborhoodUsers.map((u: { username: string }) => u.username); + expect(userNames).toContain(BOBS_LOGIN_DATA.username); expect(createNeighborhoodResponse.body.name).toBe(NEW_NEIGHBORHOOD_NAME); diff --git a/apps/frontend/public/favicon.ico b/apps/frontend/public/favicon.ico new file mode 100644 index 00000000..157f7012 Binary files /dev/null and b/apps/frontend/public/favicon.ico differ diff --git a/apps/frontend/public/index.html b/apps/frontend/public/index.html index 6972edd0..113fd700 100644 --- a/apps/frontend/public/index.html +++ b/apps/frontend/public/index.html @@ -21,6 +21,7 @@ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" /> + - React App + Neighborhood diff --git a/apps/frontend/public/manifest.json b/apps/frontend/public/manifest.json index e1f0221a..9a2ade28 100644 --- a/apps/frontend/public/manifest.json +++ b/apps/frontend/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "Neighborhood", + "name": "Neighborhood", "icons": [ ], diff --git a/apps/frontend/src/assets/robots.png b/apps/frontend/src/assets/robots.png new file mode 100644 index 00000000..da8b6881 Binary files /dev/null and b/apps/frontend/src/assets/robots.png differ diff --git a/apps/frontend/src/components/UserCircle/UserCircle.module.css b/apps/frontend/src/components/UserCircle/UserCircle.module.css index 0eb0a6ae..74dd712d 100644 --- a/apps/frontend/src/components/UserCircle/UserCircle.module.css +++ b/apps/frontend/src/components/UserCircle/UserCircle.module.css @@ -6,7 +6,7 @@ border: 2px solid #340900; /* rgb(123, 123, 123); */ color: #333333; display: flex; - justify-content: flex-start; + justify-content: center; align-items: center; padding: 0 5px; font-size: 0.9rem; @@ -21,10 +21,30 @@ .lastCircle { display: flex; justify-content: center; + cursor: pointer; } .inStack { - margin-left: -19px; + margin-left: -16px; letter-spacing: 0.1rem; font-size: 0.62rem; + transition: all 0.5s ease-in-out; +} + +.inStack:hover { + margin-top: -3px; + transform: scale(1.1); +} + +.inList { + margin: 0 10px 0 5px; + width: 30px; + height: 30px; +} + +.inList:hover { + background-color: inherit; + color: inherit; + margin-top: 0; + transform: none; } diff --git a/apps/frontend/src/components/UserCircle/UserCircle.tsx b/apps/frontend/src/components/UserCircle/UserCircle.tsx index 6bf98ce7..16cbcb91 100644 --- a/apps/frontend/src/components/UserCircle/UserCircle.tsx +++ b/apps/frontend/src/components/UserCircle/UserCircle.tsx @@ -4,18 +4,23 @@ export default function UserCircle({ username, isLast = false, inStack = true, + inList = false, + onHover, }: { username: string; + onHover?: () => void; isLast?: boolean; inStack?: boolean; - }) { + inList?: boolean; +}) { const firstTwoLetters = username?.slice(0, 2).toUpperCase(); return (
+ } ${inList ? styles.inList : ' '}`} + onMouseOver={onHover}> {firstTwoLetters}
); diff --git a/apps/frontend/src/components/UserCircleStack/UserCircleStack.module.css b/apps/frontend/src/components/UserCircleStack/UserCircleStack.module.css index 2f994f61..1ca3f5c4 100644 --- a/apps/frontend/src/components/UserCircleStack/UserCircleStack.module.css +++ b/apps/frontend/src/components/UserCircleStack/UserCircleStack.module.css @@ -1,3 +1,39 @@ .circleContainer { display: flex; } + +.circleLink { + height: fit-content; +} + +.dropdownContainer { + width: fit-content; + padding-bottom: 2rem; +} + +.dropdown { + position: relative; + right: 5rem; + top: 0.5rem; +} + +.dropdownMenu { + max-height: 50vh; + overflow-y: scroll; +} + +.dropdownItem { + display: flex; + align-items: center; +} + +.dropdownItem:active { + background-color: pink; +} + +.hiddenOverlay { + position: relative; + z-index: -1; + height: 5rem; + width: 5rem; +} diff --git a/apps/frontend/src/components/UserCircleStack/UserCircleStack.tsx b/apps/frontend/src/components/UserCircleStack/UserCircleStack.tsx index 7818f934..ccb807ea 100644 --- a/apps/frontend/src/components/UserCircleStack/UserCircleStack.tsx +++ b/apps/frontend/src/components/UserCircleStack/UserCircleStack.tsx @@ -1,22 +1,53 @@ +import { User } from '@prisma/client'; +import { Link } from 'react-router-dom'; +import { Dropdown } from 'react-bootstrap'; +import { useState } from 'react'; import UserCircle from '../UserCircle/UserCircle'; import styles from './UserCircleStack.module.css'; -export default function UserCircleStack({ usernames }: { usernames?: string[] }) { - if (Array.isArray(usernames)) { - const displayUsers = usernames.slice(0, 3); - const usersLeft = usernames.length - 3; +export default function UserCircleStack({ users }: { users?: User[] | null }) { + const [showUserList, setShowUserList] = useState(false); + + if (Array.isArray(users)) { + const displayUsers = users.slice(0, 3); + // const restOfUsers = users.slice(3); + const usersLeft = users.length - 3; return (
- {displayUsers.map((username, index) => + {displayUsers.map((user, index) => index === displayUsers.length - 1 ? ( - + + + ) : ( - + + + ), )} {/* If there are more than 3 users a circle is shown with how many users there are left. */} - {usersLeft > 0 ? : null} + {usersLeft > 0 ? ( +
setShowUserList(true)} + onMouseLeave={() => setShowUserList(false)}> + + + + {users.map((user) => ( + + + {user.username} + + ))} + + +
+ ) : null}
); } diff --git a/apps/frontend/src/pages/ErrorPage/ErrorPage.module.css b/apps/frontend/src/pages/ErrorPage/ErrorPage.module.css index 19be7f1d..ba7f128e 100644 --- a/apps/frontend/src/pages/ErrorPage/ErrorPage.module.css +++ b/apps/frontend/src/pages/ErrorPage/ErrorPage.module.css @@ -1,5 +1,12 @@ .error { - text-align: center; + margin: auto; + text-align: justify; + padding: 0 2rem; + width: fit-content; +} + +.imgContainer { + padding: 0 2rem; } .row { @@ -7,9 +14,9 @@ } .column { - min-height: 100vh; + /* min-height: 100vh; */ padding: 0; - min-width: 0; + /* min-width: 0; */ } .errorColumn { @@ -20,6 +27,15 @@ position: sticky; top: 0; } + +.p { + margin: 0; +} + +.errorImg { + max-width: 400px; + margin: auto; +} @media (max-width: 576px) { .column { diff --git a/apps/frontend/src/pages/ErrorPage/ErrorPage.tsx b/apps/frontend/src/pages/ErrorPage/ErrorPage.tsx index b7f1b8d0..19342bcb 100644 --- a/apps/frontend/src/pages/ErrorPage/ErrorPage.tsx +++ b/apps/frontend/src/pages/ErrorPage/ErrorPage.tsx @@ -1,12 +1,15 @@ import { useRouteError, isRouteErrorResponse } from 'react-router-dom'; -import { Container, Row, Col } from 'react-bootstrap'; +import { Container, Row, Col, Image } from 'react-bootstrap'; +import { AxiosError } from 'axios'; import MainNav from '../../components/MainNavigation/MainNav'; import styles from './ErrorPage.module.css'; +const errorImg = require('../../assets/robots.png'); + function ErrorPage() { const error = useRouteError(); let errorMessage: string; - let errorStatus: string = 'Unknown'; + let errorStatus: string | number = 'Unknown'; // inspired from keipala's response in this forum at SO // https://stackoverflow.com/questions/75944820/whats-the-correct-type-for-error-in-userouteerror-from-react-router-dom @@ -15,12 +18,21 @@ function ErrorPage() { if (isRouteErrorResponse(error)) { // error is type `ErrorResponse` - errorMessage = error.statusText; + if (error.status === 404) { + errorMessage = "We couldn't find what you're looking for." + } else { + errorMessage = error.statusText; + } + errorStatus = String(error.status); - } else if (error instanceof Error) { - errorMessage = error.message; + } else if (error instanceof AxiosError) { + console.log(error); + errorStatus = error.response?.status || 'Unknown'; + errorMessage = error.response?.data.error || error.message; } else if (typeof error === 'string') { errorMessage = error; + } else if (error instanceof Error) { + errorMessage = error.message; } else { console.error(error); errorMessage = 'Unknown error'; @@ -28,18 +40,27 @@ function ErrorPage() { const errorContent = (
-

Oops, An Error Occurred

-

{`${errorStatus}, ${errorMessage}`}

+

Oops! A {errorStatus} error occurred...

+

{errorMessage}

+

+ Don't worry. Our team of robot experts will get to the bottom of this. +

+

Just kidding - we will!

); return ( - + - {errorContent} + + + + + {errorContent} + ); diff --git a/apps/frontend/src/pages/RootLayout/RootLayout.tsx b/apps/frontend/src/pages/RootLayout/RootLayout.tsx index a07db135..0dc79543 100644 --- a/apps/frontend/src/pages/RootLayout/RootLayout.tsx +++ b/apps/frontend/src/pages/RootLayout/RootLayout.tsx @@ -1,4 +1,4 @@ -import { Outlet } from 'react-router-dom'; +import { Outlet, ScrollRestoration } from 'react-router-dom'; import { Row, Col, Container } from 'react-bootstrap'; import Footer from '../../components/Footer/Footer'; import styles from './RootLayout.module.css'; @@ -20,6 +20,7 @@ const RootLayout = () => (