Parcourir la source

Finished UI desktop

djoka/header
Djordje Mitrovic il y a 2 ans
Parent
révision
04aad493c5

+ 6
- 1
src/components/Header/HeaderProfileBar/HeaderNotifications/HeaderNotifications.js Voir le fichier

@@ -13,7 +13,12 @@ const HeaderNotifications = () => {
<NotificationsIcon />
</HeaderIconContainer>
}
contentContainerStyles={{ borderRadius: "8px" }}
contentContainerStyles={{
"& .MuiPopover-paper": {
borderRadius: "6px",
backgroundColor: "transparent",
},
}}
content={<HeaderNotificationsContent />}
popoverProps={{
anchorOrigin: {

+ 30
- 8
src/components/Header/HeaderProfileBar/HeaderNotifications/HeaderNotificationsContent/HeaderNotificationsContent.js Voir le fichier

@@ -1,4 +1,4 @@
import React from "react";
import React, { useMemo } from "react";
import PropTypes from "prop-types";
import {
HeaderLatestNotificationsContainer,
@@ -8,23 +8,45 @@ import {
HeaderNotificationsContentHeaderTitle,
} from "./HeaderNotificationsContent.styled";
import SingleNotification from "./SingleNotification/SingleNotification";
import { useNotificationsQuery } from "features/user/usersApiSlice";
import { AccountCircle } from "@mui/icons-material";
import { useTranslation } from "react-i18next";

const HeaderNotificationsContent = (props) => {
console.log(props);
const dummyNotification = {
userPicture: <AccountCircle />,
notificationText: (
<p>
<i>Olivia Saturday</i> commented on your post.
</p>
),
date: new Date(2023, 6, 10),
};

const HeaderNotificationsContent = () => {
const { data } = useNotificationsQuery(); //eslint-disable-line
const { t } = useTranslation();

const latestNotifications = useMemo(() => {
// return data?.notifications?.slice(0, 3);
return Array(3).fill(dummyNotification, 0);
});
return (
<HeaderNotificationsContentContainer>
<HeaderNotificationsContentHeader>
<HeaderNotificationsContentHeaderTitle>
Notifications
{t("notifications.title")}
</HeaderNotificationsContentHeaderTitle>
<HeaderNotificationsContentHeaderSeeMore>
See more
{t("common.seeMore")}
</HeaderNotificationsContentHeaderSeeMore>
</HeaderNotificationsContentHeader>
<HeaderLatestNotificationsContainer>
<SingleNotification />
<SingleNotification />
<SingleNotification />
{latestNotifications?.map?.((singleNotification, index) => (
<SingleNotification
key={index}
notificationObject={singleNotification}
/>
))}
</HeaderLatestNotificationsContainer>
</HeaderNotificationsContentContainer>
);

+ 32
- 12
src/components/Header/HeaderProfileBar/HeaderNotifications/HeaderNotificationsContent/SingleNotification/SingleNotification.js Voir le fichier

@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect, useMemo } from "react";
import PropTypes from "prop-types";
import {
SingleNotificationContainer,
@@ -8,24 +8,39 @@ import {
SingleNotificationProfile,
SingleNotificationUnseen,
} from "./SingleNotification.styled";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
import { formatTimeSpan } from "util/dateHelpers";
import { useSeeNotificationMutation } from "features/user/usersApiSlice";
import { makeErrorToastMessage } from "util/toastMessage";

const SingleNotification = (props) => {

const [seeNotification, result] = useSeeNotificationMutation();
const timespan = useMemo(
() => formatTimeSpan(props?.notificationObject?.date),
[props?.notificationObject?.date]
);

const handleSeeNotification = () => {
seeNotification(2);
console.log(result)
}

useEffect(() => {
if (result.isError) {
makeErrorToastMessage("Server error")
}
}, [result.isError])

const SingleNotification = () => {
return (
<SingleNotificationContainer>
<SingleNotificationContainer onClick={handleSeeNotification}>
<SingleNotificationProfile>
<AccountCircleIcon />
{props?.notificationObject?.userPicture}
</SingleNotificationProfile>
<SingleNotificationDetails>
<SingleNotificationContent>
<p>
<i>Olivia Saturday</i> commented on your
post.
</p>
{props?.notificationObject?.notificationText}
</SingleNotificationContent>
<SingleNotificationDate>
12 hours ago
</SingleNotificationDate>
<SingleNotificationDate>{timespan}</SingleNotificationDate>
</SingleNotificationDetails>
<SingleNotificationUnseen />
</SingleNotificationContainer>
@@ -34,6 +49,11 @@ const SingleNotification = () => {

SingleNotification.propTypes = {
children: PropTypes.node,
notificationObject: PropTypes.shape({
userPicture: PropTypes.oneOfType([PropTypes.element, PropTypes.node]),
notificationText: PropTypes.node,
date: PropTypes.object,
}),
};

export default SingleNotification;

+ 22
- 1
src/components/Header/HeaderProfileBar/HeaderProfile/HeaderProfile.js Voir le fichier

@@ -4,8 +4,29 @@ import PropTypes from "prop-types";
import { HeaderProfileContainer } from "./HeaderProfile.styled";
import PersonIcon from "@mui/icons-material/Person";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
import PopoverComponent from "components/Popover/Popover";
import { HeaderIconContainer } from "components/Header/Header.styled";
import HeaderProfilePopoverContent from "./HeaderProfilePopoverContent/HeaderProfilePopoverContent";
const HeaderProfile = () => {
return <HeaderProfileContainer>profile</HeaderProfileContainer>;
return (
<HeaderProfileContainer>
<PopoverComponent
contentContainerStyles={{
"& .MuiPopover-paper": {
borderRadius: "8px",
overflow: "hidden",
backgroundColor: "transparent",
},
}}
trigger={
<HeaderIconContainer>
<AccountCircleIcon />
</HeaderIconContainer>
}
content={<HeaderProfilePopoverContent />}
/>
</HeaderProfileContainer>
);
};

HeaderProfile.propTypes = {

+ 5
- 1
src/components/Header/HeaderProfileBar/HeaderProfile/HeaderProfile.styled.js Voir le fichier

@@ -1,4 +1,8 @@
import { Box } from "@mui/material";
import styled from "styled-components";

export const HeaderProfileContainer = styled(Box)``
export const HeaderProfileContainer = styled(Box)`
& .MuiPopover-paper {
border-radius: 8px;
}
`

+ 56
- 0
src/components/Header/HeaderProfileBar/HeaderProfile/HeaderProfilePopoverContent/HeaderProfilePopoverContent.js Voir le fichier

@@ -0,0 +1,56 @@
import React, { useMemo } from "react";
import PropTypes from "prop-types";
import {
HeaderProfileMenu,
HeaderProfileMenuItem,
HeaderProfilePopoverContentContainer,
HeaderProfilePopoverContentHeaderContainer,
ProfileDetails,
ProfileInitials,
ProfileMail,
ProfileName,
} from "./HeaderProfilePopoverContent.styled";
import { PAGES } from "constants/pages";
import { useMyUserQuery } from "features/user/usersApiSlice";
import { useTranslation } from "react-i18next";

const HeaderProfilePopoverContent = () => {
const { data } = useMyUserQuery(); //eslint-disable-line
const { t } = useTranslation();

const myInitials = useMemo(() => {
// return `${data?.firstName?.[0]}${data?.lastName?.[0]}`
return "AM";
}, [data]);

const myName = useMemo(() => {
// return `${data?.firstName} ${data?.lastName}`
return "Andrew Marks";
}, [data]);

const myMail = useMemo(() => {
// return `${data?.mail}`
return "dummymail@dilig.net";
}, [data]);
return (
<HeaderProfilePopoverContentContainer>
<HeaderProfilePopoverContentHeaderContainer>
<ProfileInitials to={PAGES.PROFILE.route}>{myInitials}</ProfileInitials>
<ProfileDetails>
<ProfileName to={PAGES.PROFILE.route}>{myName}</ProfileName>
<ProfileMail>{myMail}</ProfileMail>
</ProfileDetails>
</HeaderProfilePopoverContentHeaderContainer>
<HeaderProfileMenu>
<HeaderProfileMenuItem>{t("pages.settings")}</HeaderProfileMenuItem>
<HeaderProfileMenuItem>{t("common.logout")}</HeaderProfileMenuItem>
</HeaderProfileMenu>
</HeaderProfilePopoverContentContainer>
);
};

HeaderProfilePopoverContent.propTypes = {
children: PropTypes.node,
};

export default HeaderProfilePopoverContent;

+ 66
- 0
src/components/Header/HeaderProfileBar/HeaderProfile/HeaderProfilePopoverContent/HeaderProfilePopoverContent.styled.js Voir le fichier

@@ -0,0 +1,66 @@
import { Box, Typography } from "@mui/material";
import { NavLink } from "react-router-dom";
import styled from "styled-components";

export const HeaderProfilePopoverContentContainer = styled(Box)`
width: 250px;
padding: 0 8px;
padding-bottom: 8px;
cursor: default;
background-color: ${(props) => props?.theme?.colors?.primaryDark};
border-radius: 8px;
`;
export const HeaderProfilePopoverContentHeaderContainer = styled(Box)`
display: flex;
align-items: center;
gap: 12px;
height: 60px;
border-bottom: 1px solid ${(props) => props?.theme?.colors?.secondaryDark};
`;
export const ProfileInitials = styled(NavLink)`
width: 44px;
height: 44px;
min-width: 44px;
min-height: 44px;
border-radius: 100%;
text-align: center;
vertical-align: middle;
line-height: 44px;
text-decoration: none;
background-color: ${(props) => props?.theme?.colors?.primaryLighter};
`;
export const ProfileDetails = styled(Box)`
display: flex;
flex-direction: column;
justify-content: space-evenly;
height: 100%;
padding: 8px 0;
`;
export const ProfileName = styled(NavLink)`
font-family: Inter;
text-decoration: none;
font-size: 18px;
font-weight: 700;
color: ${(props) => props?.theme?.colors?.textColor};
`;
export const ProfileMail = styled(Typography)`
font-family: Inter;
font-size: 14px;
font-weight: 400;
color: ${(props) => props?.theme?.colors?.textColor};
`;
export const HeaderProfileMenu = styled(Box)`
display: flex;
flex-direction: column;
`;
export const HeaderProfileMenuItem = styled(NavLink)`
padding: 4px 8px;
border-radius: 8px;
text-decoration: none;
cursor: pointer;
color: ${(props) => props?.theme?.colors?.textColor};
&:hover {
background-color: ${(props) => props?.theme?.colors?.textColor};
color: ${(props) => props?.theme?.colors?.primaryDark};
}
`;

+ 1
- 1
src/components/Popover/Popover.js Voir le fichier

@@ -20,7 +20,7 @@ const PopoverComponent = (props) => {
{props?.trigger}
</PopoverTrigger>
<Popover
style={props?.contentContainerStyles}
sx={props?.contentContainerStyles}
open={isOpened}
anchorEl={anchorEl}
onClose={togglePopover}

+ 2
- 2
src/components/Popover/Popover.styled.js Voir le fichier

@@ -1,5 +1,5 @@
import { Box } from "@mui/material";
import styled from "styled-components";

export const PopoverContainer = styled(Box)``
export const PopoverTrigger = styled(Box)``
export const PopoverContainer = styled(Box)``;
export const PopoverTrigger = styled(Box)``;

+ 33
- 0
src/features/user/usersApiSlice.js Voir le fichier

@@ -0,0 +1,33 @@
import { apiSlice } from "features/api/apiSlice";

const notificationTag = "notifications";

export const usersApiSlice = apiSlice.injectEndpoints({
tagTypes: [notificationTag],
endpoints: (builder) => ({
myUser: builder.query({
query: () => ({
url: "api/user/me",
}),
}),
notifications: builder.query({
query: () => ({
url: "api/user/me/notifications",
}),
providesTags: [notificationTag],
}),
seeNotification: builder.mutation({
query: (notificationId) => ({
url: `api/user/me/notifications/${notificationId}`,
method: "PATCH",
}),
invalidatesTags: [notificationTag],
}),
}),
});

export const {
useMyUserQuery,
useNotificationsQuery,
useSeeNotificationMutation,
} = usersApiSlice;

+ 17
- 1
src/i18nt/resources/sr.js Voir le fichier

@@ -40,10 +40,15 @@ export default {
select: "Select...",
none: "None",
date: {
range: "{{start}} to {{end}}",
range: "{{start}} do {{end}}",
},
logout: "Izloguj se",
seeMore: "Vidi još",
},

notifications: {
title: "Obaveštenja",
},
pages: {
home: "Početna",
login: "Login",
@@ -116,4 +121,15 @@ export default {
WrongPasswordAccountIsLocked: "Wrong credentials, account is locked",
AccountIsLocked: "Account is locked",
},
date: {
timespan: {
yearsAgo: "Pre {{years}} godine",
monthsAgo: "Pre {{months}} meseca",
daysAgo: "Pre {{days}} dana",
hoursAgo: "Pre {{hours}} sata",
minutesAgo: "Pre {{minutes}} minuta",
secondsAgo: "Pre {{seconds}} sekunde",
now: "Upravo sada",
},
},
};

+ 73
- 2
src/util/dateHelpers.js Voir le fichier

@@ -1,6 +1,6 @@
import { format } from "date-fns";
import { enUS } from "date-fns/locale";
import i18next from "i18next";
import i18n from "../i18nt/index";

export function formatDate(date, fmt = "MM/dd/y", locale = enUS) {
const dt = new Date(date);
@@ -36,5 +36,76 @@ export function formatDateTimeLocale(date) {
export function formatDateRange(dates) {
const start = formatDate(dates.start);
const end = formatDate(dates.end);
return i18next.t("common.date.range", { start, end });
return i18n.t("common.date.range", { start, end });
}

export const calculateTimeSpan = (date) => {
let currentDate = new Date();
let timeElapsed = (currentDate.getTime() - date?.getTime()) / 1000;
if (timeElapsed <= 0) {
return {
yearsPassed: 0,
monthsPassed: 0,
daysPassed: 0,
hoursPassed: 0,
minutesPassed: 0,
secondsPassed: 0,
};
}

const yearsPassed = Math.floor(timeElapsed / (60 * 60 * 24 * 365));
timeElapsed -= yearsPassed * (60 * 60 * 24 * 365);

const monthsPassed = Math.floor(timeElapsed / (60 * 60 * 24 * 31));
timeElapsed -= monthsPassed * (60 * 60 * 24 * 31);

const daysPassed = Math.floor(timeElapsed / (60 * 60 * 24));
timeElapsed -= daysPassed * (60 * 60 * 24);

const hoursPassed = Math.floor(timeElapsed / (60 * 60));
timeElapsed -= hoursPassed * (60 * 60);

const minutesPassed = Math.floor(timeElapsed / 60);
timeElapsed -= minutesPassed * 60;

const secondsPassed = Math.floor(timeElapsed);
timeElapsed -= secondsPassed;

return {
yearsPassed,
monthsPassed,
daysPassed,
hoursPassed,
minutesPassed,
secondsPassed,
};
};

export const formatTimeSpan = (date) => {
const timespanObject = calculateTimeSpan(date);
if (timespanObject.yearsPassed > 0)
return i18n.t("date.timespan.yearsAgo", {
years: timespanObject.yearsPassed,
});
if (timespanObject.monthsPassed > 0)
return i18n.t("date.timespan.monthsAgo", {
months: timespanObject.monthsPassed,
});
if (timespanObject.daysPassed > 0)
return i18n.t("date.timespan.daysAgo", {
days: timespanObject.daysPassed,
});
if (timespanObject.hoursPassed > 0)
return i18n.t("date.timespan.hoursAgo", {
hours: timespanObject.hoursPassed,
});
if (timespanObject.minutesPassed > 0)
return i18n.t("date.timespan.minutesAgo", {
minutes: timespanObject.minutesPassed,
});
if (timespanObject.secondsPassed > 0)
return i18n.t("date.timespan.secondsAgo", {
seconds: timespanObject.secondsPassed,
});
return i18n.t("date.timespan.now")
};

Chargement…
Annuler
Enregistrer