Skip to content

Commit

Permalink
Feature user profiles (#28)
Browse files Browse the repository at this point in the history
* refactor: change dashbard menu items order

* refactor: change dashbard menu items order

* feat: set up explore user profiles page scaffolding

* feat: add new sidebar nav item

* feat: style explore user profiles card grid

* refactor: change dashbard menu items order

* feat: set up explore user profiles page scaffolding

* feat: add new sidebar nav item

* feat: style explore user profiles card grid

* feat: set up userprofile routing

* feat: remove unneeded sidebar link item

* fix: set user data upon refresh page

* feat: implement getUserProfile endpoint

* feat: implement user profile permissions logic

* feat: set up user profile page HTML structure

* chore: install eslint to ensure proper usage of react hooks

* fix: set user data after retrieving tokens via manual login

* feat: style profile section of userprofilepage

* feat: create profile component files

* feat: display user book list

* feat: implement user profile settings logic

* feat: implement explore users page logic

* feat: implement user book stats and progress on change profile picture

* fix: remove align center from book list grid

* chore: remove console.log
  • Loading branch information
MonCaptain authored May 18, 2023
1 parent 55928a4 commit 3046dec
Show file tree
Hide file tree
Showing 11 changed files with 577 additions and 37 deletions.
8 changes: 7 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,11 @@
"eslint-plugin-react-refresh": "^0.3.4",
"vite": "^4.3.2"
},
"proxy": "http://localhost:8000"
"proxy": "http://localhost:8000",
"eslintConfig": {
"extends": "plugin:react-hooks/recommended",
"rules": {
"react/prop-types": "off"
}
}
}
9 changes: 8 additions & 1 deletion client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { useAuthContext } from "./contexts/AuthContext";
import LandingPage from "./pages/LandingPage";
import LoginPage from "./pages/LoginPage";
import ExplorePage from "./pages/ExplorePage";
import ExploreUsersPage from "./pages/ExploreUsersPage";
import UserProfile from "./pages/UserProfile";

const authedRouter = createBrowserRouter(
createRoutesFromElements(
Expand All @@ -20,7 +22,12 @@ const authedRouter = createBrowserRouter(
<Route path="/reading" element={<BookList listType={"reading"} />} />
<Route path="/completed" element={<BookList listType={"completed"} />} />
<Route path="/dropped" element={<BookList listType={"dropped"} />} />
<Route path="/explore" element={<ExplorePage/>}/>
<Route path="/users" element={<ExploreUsersPage />}/>
<Route path="/users">
<Route path=":username" element = {<UserProfile />}/>
</Route>
<Route path="/me" element={<UserProfile isOriginalUser = {true} />} />
<Route path="/explore" element={<ExplorePage />} />
</Route>
)
);
Expand Down
11 changes: 6 additions & 5 deletions client/src/components/SidebarWithHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import {
} from "@chakra-ui/react";
import {
FiHome,
FiSettings,
FiChevronDown,
FiBookOpen,
FiCheck,
FiTrash2,
FiBook,
FiTrendingUp,
} from "react-icons/fi";
import { Link } from "react-router-dom";
import { useColorMode } from "@chakra-ui/react";
Expand All @@ -36,11 +36,11 @@ import { useNavigate } from "react-router-dom";

const LinkItems = [
{ name: "Home", icon: FiHome, path: "/" },
{ name: "Explore", icon: FiBook, path: "/explore" },
{ name: "Users are Reading", icon: FiTrendingUp, path: "/users" },
{ name: "Currently Reading", icon: FiBookOpen, path: "/reading" },
{ name: "Completed", icon: FiCheck, path: "/completed" },
{ name: "Dropped", icon: FiTrash2, path: "/dropped" },
{ name: "Explore", icon: FiBook, path: "/explore" },
{ name: "Settings", icon: FiSettings },
];

export default function SidebarWithHeader({ children }) {
Expand Down Expand Up @@ -216,9 +216,10 @@ const MobileNav = ({ onOpen, ...rest }) => {
bg={useColorModeValue("white", "gray.900")}
borderColor={useColorModeValue("gray.200", "gray.700")}
>
<MenuItem>Profile</MenuItem>
<Link to={"/me"}>
<MenuItem>Profile</MenuItem>
</Link>
<MenuItem>Settings</MenuItem>
<MenuItem>Billing</MenuItem>
<MenuDivider />
<MenuItem onClick={handleSignOut}>Sign out</MenuItem>
</MenuList>
Expand Down
59 changes: 59 additions & 0 deletions client/src/components/UserProfile/ProfileBookList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useParams } from "react-router-dom";
import {
Box,
Flex,
Heading,
Image,
Spacer,
Stack,
Switch,
Text,
Divider,
Button,
Icon,
SlideFade,
SimpleGrid,
useColorModeValue
} from "@chakra-ui/react";
import ProfileBookRow from "./ProfileBookRow";

export default function ProfileBookList({ bookList }) {
const containerColor = useColorModeValue("whiteAlpha.900", "gray.800");

return (
<Box>
<Heading mb={"20px"}>Book List</Heading>
<Flex
flexDirection={"column"}
columnGap={"50px"}
height="100%"
padding={"20px"}
bg={containerColor}
borderRadius={"5px"}
>
<SlideFade in={bookList.length > 0} out={bookList.length === 0}>
<SimpleGrid
spacing={10}
justifyContent={"center"}
columns={[1, 1, 1, 1, 2, 3]}
p={5}
>
{bookList.map((bookObject, index) => {
return (
<ProfileBookRow
key={index}
title={bookObject.book.title}
author={bookObject.book.author}
cover={bookObject.book.cover_image}
status = {bookObject.status}
rating = {bookObject.rating}
currentPage = {bookObject.current_page}
/>
);
})}
</SimpleGrid>
</SlideFade>
</Flex>
</Box>
);
}
56 changes: 56 additions & 0 deletions client/src/components/UserProfile/ProfileBookRow.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
Card,
Flex,
Box,
Text,
Spacer,
CardHeader,
Heading,
CardBody,
Stack,
CardFooter,
Button,
Image,
Badge,
Alert,
} from "@chakra-ui/react";

export default function ProfileBookRow({
title,
author,
cover,
status,
rating,
lastUpdated,
}) {
return (
<Card
direction={{ base: "column", sm: "row" }}
overflow="hidden"
variant="outline"
>
<Image
objectFit="cover"
maxW={{ base: "100%", sm: "200px" }}
src={cover}
alt="Caffe Latte"
/>

<Stack>
<CardBody>
<Heading size="md">{title}</Heading>
<Text size={"md"} py="2">
Written by {author}
</Text>
<Text py="2">
<Alert status="success" variant="subtle" width={"full"}>
{status}
</Alert>
</Text>
<Text py="2">Last updated {lastUpdated}</Text>
<Text py="2">{ rating ? "Rating: " + rating : ""}</Text>
</CardBody>
</Stack>
</Card>
);
}
201 changes: 201 additions & 0 deletions client/src/components/UserProfile/ProfileSettingsStats.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { useParams } from "react-router-dom";
import {
Box,
Flex,
Heading,
Image,
Spacer,
Stack,
Switch,
Text,
Divider,
Button,
useColorModeValue,
useColorMode,
} from "@chakra-ui/react";
import apiClient from "../../services/apiClient";
import { useEffect, useState } from "react";
import BookList from "./../../pages/BookList";
export default function ProfileSettingsStats({ userProfile, isOriginalUser }) {
// styling related
const { colorMode, toggleColorMode } = useColorMode();
const containerColor = useColorModeValue("whiteAlpha.900", "gray.800");
const orangeTextTheme = useColorModeValue("orange.500", "orange.200");
// settings related
const [isProfilePrivate, setIsProfilePrivate] = useState(userProfile.private);
const [selectedImage, setSelectedImage] = useState(null);
// book stats related
const [bookList, setBookList] = useState(userProfile.book_list);
const [bookCountByCategory, setBookCountByCategory] = useState({
All: 0,
"Not started": 0,
Dropped: 0,
Completed: 0,
});

async function handleOnPrivacyToggle() {
await apiClient.editProfile(userProfile.user.username, {
private: !isProfilePrivate,
});
setIsProfilePrivate(!isProfilePrivate);
}

function handleImageChange(event) {
const file = event.target.files[0];
setSelectedImage(file);
}

function handleUpload() {
if (selectedImage) {
apiClient.uploadProfilePicture({ image_url: selectedImage });
}
}

useEffect(() => {
let categoryCountObject = {
All: 0,
"Not started": 0,
Dropped: 0,
Completed: 0,
};
if (userProfile) {
bookList.map((element, index) => {
categoryCountObject[`${element.status}`] += 1;
categoryCountObject.All += 1;
});

setBookCountByCategory(categoryCountObject);
}
}, [bookList]);

return (
<>
<Heading textColor={orangeTextTheme}>User Profile</Heading>
<Flex
columnGap={"50px"}
height="100%"
maxHeight={"30vh"}
padding={"20px"}
bg={containerColor}
borderRadius={"5px"}
>
<Image
boxSize="250px"
objectFit="cover"
src={`http://localhost:8000${userProfile.profile_picture}`}
alt="Dan Abramov"
fallbackSrc="https://via.placeholder.com/250"
/>
{/* Profile and Settings */}
{isOriginalUser &&
<Box
display="flex"
flexDirection={"column"}
rowGap={"10px"}
width={"full"}
maxWidth={"425px"}
>
<Text color={orangeTextTheme} fontWeight={"bold"}>
{userProfile.user.first_name} {userProfile.user.last_name}
</Text>
<Text>Favorite Book: {userProfile.favorite_book.title}</Text>
<Text>{userProfile.book_list.length} book entries</Text>
{isOriginalUser && (
<Stack direction="row">
<Text>Private Profile</Text>
<Spacer />
<Switch
colorScheme="orange"
size="lg"
isChecked={isProfilePrivate}
onChange={handleOnPrivacyToggle}
/>
</Stack>
)}
{isOriginalUser && (
<Stack direction="row">
<Text>Dark mode</Text>
<Spacer />
<Switch
colorScheme="orange"
size="lg"
onChange={toggleColorMode}
isChecked={colorMode === "dark" ? true : false}
/>
</Stack>
)}
{isOriginalUser && (
<Stack direction={"row"} alignItems={"center"}>
<Box
width={"full"}
fontWeight={"semibold"}
colorScheme="orange"
bg={colorMode == "light" ? "gray.100" : "gray.700"}
padding={"9px"}
borderRadius={"5px"}
>
<label
htmlFor="file-upload"
style={{ marginBottom: "1rem" }}
width="100%"
>
<Box _hover={{ cursor: "pointer" }} width={"full"}>
Edit Profile Picture
</Box>
</label>
<input
id="file-upload"
type="file"
onChange={handleImageChange}
style={{ display: "none" }}
accept="image/jpeg,image/png,image/gif"
/>
</Box>
<Button colorScheme="orange" onClick={handleUpload} width={"50%"}>
Upload
</Button>
</Stack>
)}
</Box>}

{/* Book Stats */}
<Box
display="flex"
flexDirection={"column"}
rowGap={"10px"}
width={"full"}
maxWidth={"425px"}
>
<Text color={orangeTextTheme} fontWeight={"bold"}>
Book Stats
</Text>
<Stack direction="row">
<Text>Favorite Book:</Text>
<Spacer />
<Text>{userProfile.favorite_book.title}</Text>
</Stack>
<Stack direction="row">
<Text>All</Text>
<Spacer />
<Text>{bookCountByCategory.All}</Text>
</Stack>
<Stack direction="row">
<Text>Completed</Text>
<Spacer />
<Text>{bookCountByCategory.Completed}</Text>
</Stack>
<Stack direction="row">
<Text>Not yet started</Text>
<Spacer />
<Text>{bookCountByCategory["Not started"]}</Text>
</Stack>
<Stack direction="row">
<Text>Dropped</Text>
<Spacer />
<Text>{bookCountByCategory.Dropped}</Text>
</Stack>
</Box>
</Flex>
</>
);
}
Loading

0 comments on commit 3046dec

Please sign in to comment.