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 = () => (
+
);
diff --git a/apps/frontend/src/pages/SingleNeighborhoodPage/SingleNeighborhoodPage.module.css b/apps/frontend/src/pages/SingleNeighborhoodPage/SingleNeighborhoodPage.module.css
index f883c470..1bd83e1c 100644
--- a/apps/frontend/src/pages/SingleNeighborhoodPage/SingleNeighborhoodPage.module.css
+++ b/apps/frontend/src/pages/SingleNeighborhoodPage/SingleNeighborhoodPage.module.css
@@ -19,6 +19,7 @@
.membersContainer {
margin-top: 10px;
+ margin-right: 30px;
display: flex;
align-self: flex-start;
}
diff --git a/apps/frontend/src/pages/SingleNeighborhoodPage/SingleNeighborhoodPage.tsx b/apps/frontend/src/pages/SingleNeighborhoodPage/SingleNeighborhoodPage.tsx
index 5a423946..421c42f8 100644
--- a/apps/frontend/src/pages/SingleNeighborhoodPage/SingleNeighborhoodPage.tsx
+++ b/apps/frontend/src/pages/SingleNeighborhoodPage/SingleNeighborhoodPage.tsx
@@ -140,11 +140,11 @@ export default function SingleNeighborhood() {
);
let neighborhoodRequests;
- let usernames;
+ let users;
if (checkForNeighborhoodDetails(neighborhoodData)) {
neighborhoodRequests = neighborhoodData.requests;
- usernames = neighborhoodData.users?.map((user) => user.username);
+ users = neighborhoodData.users;
} else {
neighborhoodRequests = null;
}
@@ -175,7 +175,7 @@ export default function SingleNeighborhood() {
xs="12"
md="2"
className={`${styles.membersContainer} justify-content-md-end ms-md-auto ms-3 pe-0`}>
- {userRole !== 'NON-MEMBER' ? : null}
+ {userRole !== 'NON-MEMBER' ? : null}
{userRole === 'MEMBER' ? (