add notification list, add notification page
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
import PageTitle from '@/app/ui/page-title';
|
||||||
|
import { NotificationsList } from '@/app/ui/notifications/notifications-list';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageTitle title='notifications' />
|
||||||
|
|
||||||
|
<NotificationsList />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+45
-22
@@ -124,8 +124,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-dropdown {
|
.notification {
|
||||||
.notification-header {
|
/* &-dropdown {} */
|
||||||
|
|
||||||
|
&-header {
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
border-bottom: 1px solid v.$b-color-1;
|
border-bottom: 1px solid v.$b-color-1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -138,29 +140,30 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: v.$text-p;
|
color: v.$text-p;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-count {
|
|
||||||
font-size: 12px;
|
|
||||||
color: v.$p-color;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-list {
|
&-count {
|
||||||
max-height: 400px;
|
font-size: 12px;
|
||||||
|
color: v.$p-color;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-list {
|
||||||
|
max-height: 600px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-item {
|
&-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 4px;
|
||||||
padding: 15px 20px;
|
padding: 5px;
|
||||||
border-bottom: 1px solid v.$b-color-1;
|
border-bottom: 1px solid v.$b-color-1;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-title {
|
&-title {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -168,24 +171,43 @@
|
|||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-icon {
|
&-content {
|
||||||
font-size: 24px;
|
width: 100%;
|
||||||
flex-shrink: 0;
|
position: relative;
|
||||||
|
padding: 15px 5px;
|
||||||
|
/* display: flex;
|
||||||
|
align-items: center; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-text {
|
&-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: v.$p-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: v.$text-s;
|
color: v.$text-s;
|
||||||
margin-bottom: 4px;
|
/* margin-bottom: 4px; */
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-time {
|
&-data {
|
||||||
font-size: 12px;
|
font-size: 10px;
|
||||||
color: v.$text-m;
|
color: v.$text-m;
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-footer {
|
&-footer {
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
border-top: 1px solid v.$b-color-1;
|
border-top: 1px solid v.$b-color-1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -195,6 +217,7 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5047,3 +5047,79 @@
|
|||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notifications-list {
|
||||||
|
&-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
background-color: v.$bg-light;
|
||||||
|
padding: 20px 20px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 1px solid v.$b-color-2;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 20px;
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-action {
|
||||||
|
.btn-primary {
|
||||||
|
/* background: v.$p-color; */
|
||||||
|
background: #2b7fff;
|
||||||
|
padding: 5px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-body {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
&-item {
|
||||||
|
border-bottom: 1px solid v.$b-color-2;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-check {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid v.$border-color-1;
|
||||||
|
border-radius: 4px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
transition: all .3s ease-in;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 2px solid v.$border-color-1-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,60 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useRef } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { useClickOutside } from '@/app/hooks/useClickOutside';
|
import { useClickOutside } from '@/app/hooks/useClickOutside';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { IconNotification } from '@/app/ui/icons/icons';
|
import { IconNotification } from '@/app/ui/icons/icons';
|
||||||
|
|
||||||
|
interface Notification {
|
||||||
|
id: string,
|
||||||
|
notificationText: string,
|
||||||
|
data: string,
|
||||||
|
link: string,
|
||||||
|
}
|
||||||
|
|
||||||
export default function NotificationsButton() {
|
export default function NotificationsButton() {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [data, setData] = useState<string | null>(null);
|
const [notificationData, setNotificationData] = useState<Notification[] | []>([]);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const menuRef = useRef<HTMLDivElement | null>(null);
|
const menuRef = useRef<HTMLDivElement | null>(null);
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNotificationData([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
notificationText: 'text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1',
|
||||||
|
data: '20.20.20',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
notificationText: 'text2',
|
||||||
|
data: '20.20.20',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
notificationText: 'text3',
|
||||||
|
data: '20.20.20',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
notificationText: 'text4',
|
||||||
|
data: '20.20.20',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
notificationText: 'text5',
|
||||||
|
data: '20.20.20',
|
||||||
|
link: '#'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}, [])
|
||||||
|
|
||||||
useClickOutside(
|
useClickOutside(
|
||||||
menuRef,
|
menuRef,
|
||||||
() => {
|
() => {
|
||||||
@@ -29,6 +71,14 @@ export default function NotificationsButton() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function markAsReadHandler() {
|
||||||
|
console.log('click: mark as read');
|
||||||
|
}
|
||||||
|
|
||||||
|
function notificationClickHandler(id: string) {
|
||||||
|
console.log(`click: ${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="notification-wrapper" ref={menuRef}>
|
<div className="notification-wrapper" ref={menuRef}>
|
||||||
<button
|
<button
|
||||||
@@ -50,35 +100,63 @@ export default function NotificationsButton() {
|
|||||||
<h4>
|
<h4>
|
||||||
{t('notifications')}
|
{t('notifications')}
|
||||||
</h4>
|
</h4>
|
||||||
<span className="notification-count">
|
<Link
|
||||||
{t('no-new')}
|
href={'/pages/notifications'}
|
||||||
</span>
|
className="notification-count"
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('no-new')} / {t('all')}
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
{data ? (
|
{notificationData.length !== 0 ? (
|
||||||
<div className="notification-list">
|
<div
|
||||||
<div className="notification-item">
|
className="notification-list"
|
||||||
<div className="notification-icon">
|
>
|
||||||
<IconNotification />
|
{notificationData.map((item) => {
|
||||||
</div>
|
return (
|
||||||
<div className="notification-content">
|
<div
|
||||||
<div className="notification-title">
|
key={item.id}
|
||||||
{t('tokens-added')}
|
className="notification-item"
|
||||||
|
onClick={() => {
|
||||||
|
notificationClickHandler(item.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="notification-icon">
|
||||||
|
<IconNotification />
|
||||||
|
</div>
|
||||||
|
<div className="notification-content">
|
||||||
|
<div className="notification-data">
|
||||||
|
{item.data}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="notification-title">
|
||||||
|
{t('tokens-added')}
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
<div className="notification-text">
|
||||||
|
{item.notificationText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="notification-text">{t('added')} 100 {t('tokens')}.</div>
|
)
|
||||||
<div className="notification-time">4 {t('days-ago')}</div>
|
})}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{JSON.stringify(data)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p>
|
<p>
|
||||||
{t('no-data')}
|
{/* {t('no-data')} */}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="notification-footer">
|
<div className="notification-footer">
|
||||||
<Link href="#" className="notification-link">
|
<button
|
||||||
{t('view-all')}
|
className="notification-link"
|
||||||
</Link>
|
onClick={() => {
|
||||||
|
markAsReadHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('mark-all-as-read')}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { IconCheck } from '@/app/ui/icons/icons';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface Notification {
|
||||||
|
id: string,
|
||||||
|
notificationText: string,
|
||||||
|
data: string,
|
||||||
|
link: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const noitficationList: Notification[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
notificationText: 'text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1 text1',
|
||||||
|
data: '20.20.20',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
notificationText: 'text2',
|
||||||
|
data: '20.20.20',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
notificationText: 'text3',
|
||||||
|
data: '20.20.20',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
notificationText: 'text4',
|
||||||
|
data: '20.20.20',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
notificationText: 'text5',
|
||||||
|
data: '20.20.20',
|
||||||
|
link: '#'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export function NotificationsList() {
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
function selectHandler(fileId: string) {
|
||||||
|
setSelectedFiles((prev) => {
|
||||||
|
const newSet = new Set(prev)
|
||||||
|
if (newSet.has(fileId)) {
|
||||||
|
newSet.delete(fileId)
|
||||||
|
} else {
|
||||||
|
newSet.add(fileId)
|
||||||
|
}
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearHandler() {
|
||||||
|
setSelectedFiles(new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="block-wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="notifications-list-wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="notifications-list-header"
|
||||||
|
>
|
||||||
|
<h4>
|
||||||
|
{t('all-notifications')}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="notifications-list-action"
|
||||||
|
>
|
||||||
|
{(selectedFiles.size !== 0) && (
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => {
|
||||||
|
clearHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('deselect')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="notifications-list-body"
|
||||||
|
>
|
||||||
|
{(noitficationList.length !== 0) && (
|
||||||
|
noitficationList.map(item => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="notification-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="notification-check"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
selectHandler(item.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedFiles.has(item.id) && (
|
||||||
|
<IconCheck />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="notification-text"
|
||||||
|
>
|
||||||
|
{item.notificationText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -382,7 +382,11 @@
|
|||||||
"no-information-about-the-complaint": "No information about the complaint",
|
"no-information-about-the-complaint": "No information about the complaint",
|
||||||
"the-complaint-has-not-been-registered": "The complaint has not been registered",
|
"the-complaint-has-not-been-registered": "The complaint has not been registered",
|
||||||
"the-complaint-has-been-registered": "The complaint has been registered",
|
"the-complaint-has-been-registered": "The complaint has been registered",
|
||||||
"all-statuses": "All statuses"
|
"all-statuses": "All statuses",
|
||||||
|
"all": "All",
|
||||||
|
"mark-all-as-read": "Mark all as read",
|
||||||
|
"all-notifications": "All notifications",
|
||||||
|
"deselect": "Deselect"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "and",
|
"and": "and",
|
||||||
|
|||||||
@@ -382,7 +382,11 @@
|
|||||||
"no-information-about-the-complaint": "Нет информации о жалобе",
|
"no-information-about-the-complaint": "Нет информации о жалобе",
|
||||||
"the-complaint-has-not-been-registered": "Жалоба не зарегистрирована",
|
"the-complaint-has-not-been-registered": "Жалоба не зарегистрирована",
|
||||||
"the-complaint-has-been-registered": "Жалоба зарегистрирована",
|
"the-complaint-has-been-registered": "Жалоба зарегистрирована",
|
||||||
"all-statuses": "Все статусы"
|
"all-statuses": "Все статусы",
|
||||||
|
"all": "Все",
|
||||||
|
"mark-all-as-read": "Отметить все как прочитанное",
|
||||||
|
"all-notifications": "Все уведомления",
|
||||||
|
"deselect": "Снять выделение"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "и",
|
"and": "и",
|
||||||
|
|||||||
Reference in New Issue
Block a user