✨ Notifications working
This commit is contained in:
parent
4b49d91fcf
commit
7f6cd05ebf
4993
package-lock.json
generated
Normal file
4993
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,7 @@
|
||||
"eslint-config-next": "13.2.4",
|
||||
"form-data": "^4.0.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.73",
|
||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.82",
|
||||
"next": "13.2.4",
|
||||
"prettier": "^2.8.7",
|
||||
"react": "18.2.0",
|
||||
@ -36,5 +36,3 @@
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
|
||||
|
||||
|
54
src/front/Api/LeCoffreApi/Notifications/Notifications.ts
Normal file
54
src/front/Api/LeCoffreApi/Notifications/Notifications.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import BaseApiService from "@Front/Api/BaseApiService";
|
||||
import { UserNotification } from "le-coffre-resources/dist/Notary";
|
||||
|
||||
// TODO Type get query params -> Where + inclue + orderby
|
||||
export interface IGetNotificationsParams {
|
||||
where?: {};
|
||||
include?: {};
|
||||
select?: {};
|
||||
}
|
||||
|
||||
export type IPutNotificationsParams = {
|
||||
read?: boolean;
|
||||
};
|
||||
|
||||
export default class Notifications extends BaseApiService {
|
||||
private static instance: Notifications;
|
||||
private baseUrl = this.getBaseUrl().concat("/notifications");
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (!this.instance) {
|
||||
return new this();
|
||||
} else {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
public async get(q?: IGetNotificationsParams): Promise<UserNotification[]> {
|
||||
const url = new URL(this.baseUrl);
|
||||
if (q) {
|
||||
const query = { q };
|
||||
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||
}
|
||||
try {
|
||||
return await this.getRequest<UserNotification[]>(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async put(uid: string, body: IPutNotificationsParams): Promise<UserNotification> {
|
||||
const url = new URL(this.baseUrl.concat(`/${uid}`));
|
||||
try {
|
||||
return await this.putRequest<UserNotification>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.2251 3.36088C13.4893 3.59571 13.5131 4.00024 13.2783 4.26442L6.4516 11.9444C6.33015 12.0811 6.15607 12.1592 5.97326 12.1592C5.79045 12.1592 5.61637 12.0811 5.49492 11.9444L2.08159 8.10442C1.84676 7.84024 1.87055 7.43571 2.13474 7.20088C2.39892 6.96606 2.80344 6.98985 3.03827 7.25403L5.97326 10.5559L12.3216 3.41403C12.5564 3.14985 12.9609 3.12606 13.2251 3.36088Z" fill="white"/>
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 6L8.375 16L4 11.4545" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 497 B After Width: | Height: | Size: 219 B |
@ -1,16 +1,36 @@
|
||||
import Module from "@Front/Config/Module";
|
||||
import React from "react";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
|
||||
import HeaderLink from "../HeaderLink";
|
||||
import classes from "./classes.module.scss";
|
||||
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
|
||||
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Notifications from "@Front/Api/LeCoffreApi/Notifications/Notifications";
|
||||
import Toasts from "@Front/Stores/Toasts";
|
||||
export default function Navigation() {
|
||||
const pathname = usePathname();
|
||||
|
||||
type IProps = {};
|
||||
type IState = {};
|
||||
const getNotifications = useCallback(async () => {
|
||||
const notifications = await Notifications.getInstance().get({
|
||||
where: {
|
||||
read: false,
|
||||
},
|
||||
});
|
||||
notifications.forEach((notification) => {
|
||||
Toasts.getInstance().open({
|
||||
title: notification.notification.message,
|
||||
uid: notification.uid,
|
||||
redirectUrl: notification.notification.redirection_url,
|
||||
});
|
||||
});
|
||||
console.log(notifications);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getNotifications();
|
||||
}, [pathname, getNotifications]);
|
||||
|
||||
export default class Navigation extends React.Component<IProps, IState> {
|
||||
public override render(): JSX.Element {
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<HeaderLink
|
||||
@ -43,4 +63,3 @@ export default class Navigation extends React.Component<IProps, IState> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
pointer-events: all;
|
||||
position: relative;
|
||||
padding: 24px;
|
||||
background: $orange-soft;
|
||||
background: var(--orange-soft);
|
||||
box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.11);
|
||||
border-radius: 5px;
|
||||
|
||||
@ -42,6 +42,13 @@
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&[data-clickable="true"] {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: var(--orange-soft-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.loadbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -8,18 +8,25 @@ import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Toasts, { IToast } from "@Front/Stores/Toasts";
|
||||
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import CheckIcon from "@Assets/Icons/check.svg";
|
||||
import Image from "next/image";
|
||||
import { NextRouter, useRouter } from "next/router";
|
||||
|
||||
type IProps = {
|
||||
toast: IToast;
|
||||
};
|
||||
|
||||
type IPropsClass = IProps & {
|
||||
router: NextRouter;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
willClose: boolean;
|
||||
};
|
||||
|
||||
export default class ToastElement extends React.Component<IProps, IState> {
|
||||
class ToastElementClass extends React.Component<IPropsClass, IState> {
|
||||
private closeTimeout = 0;
|
||||
constructor(props: IProps) {
|
||||
constructor(props: IPropsClass) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@ -27,6 +34,7 @@ export default class ToastElement extends React.Component<IProps, IState> {
|
||||
};
|
||||
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
public override render(): JSX.Element {
|
||||
@ -35,7 +43,11 @@ export default class ToastElement extends React.Component<IProps, IState> {
|
||||
"--data-duration": `${toast.time}ms`,
|
||||
} as React.CSSProperties;
|
||||
return (
|
||||
<div className={classes["root"]} data-will-close={this.state.willClose}>
|
||||
<div
|
||||
className={classes["root"]}
|
||||
data-will-close={this.state.willClose}
|
||||
data-clickable={toast.redirectUrl ? true : false}
|
||||
onClick={this.handleClick}>
|
||||
{toast.time !== 0 && <div className={classes["loadbar"]} style={style} />}
|
||||
<div className={classes["header"]}>
|
||||
<div className={classes["text-icon_row"]}>
|
||||
@ -45,7 +57,7 @@ export default class ToastElement extends React.Component<IProps, IState> {
|
||||
{this.getToastText(toast.text)}
|
||||
</div>
|
||||
</div>
|
||||
{/* {toast.closable && <Cross className={classes["cross"]} onClick={this.onClose} />} */}
|
||||
{toast.closable && <Image src={CheckIcon} alt="Document check" className={classes["cross"]} onClick={this.onClose} />}
|
||||
</div>
|
||||
{toast.button}
|
||||
</div>
|
||||
@ -95,4 +107,16 @@ export default class ToastElement extends React.Component<IProps, IState> {
|
||||
Toasts.getInstance().close(this.props.toast);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
private handleClick(e: React.MouseEvent) {
|
||||
if (this.props.toast.redirectUrl) {
|
||||
this.props.router.push(this.props.toast.redirectUrl);
|
||||
this.onClose(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function ToastElement(props: IProps) {
|
||||
const router = useRouter();
|
||||
return <ToastElementClass {...props} router={router} />;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Notifications from "@Front/Api/LeCoffreApi/Notifications/Notifications";
|
||||
import EventEmitter from "@Front/Services/EventEmitter";
|
||||
// import I18n from "Components/Elements/I18n";
|
||||
|
||||
@ -7,6 +8,8 @@ export enum EToastPriority {
|
||||
}
|
||||
|
||||
export interface IToast {
|
||||
uid?: string;
|
||||
redirectUrl?: string;
|
||||
id?: number;
|
||||
title: string | React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
@ -23,7 +26,7 @@ export default class Toasts {
|
||||
private toastList: IToast[] = [];
|
||||
private uid: number = 0;
|
||||
|
||||
private defaultTime: IToast["time"] = 10000;
|
||||
private defaultTime: IToast["time"] = 0;
|
||||
private defaultClosable: IToast["closable"] = true;
|
||||
private defaultPriority: IToast["priority"] = EToastPriority.LOW;
|
||||
|
||||
@ -49,6 +52,8 @@ export default class Toasts {
|
||||
}
|
||||
|
||||
public open(toast: IToast): () => void {
|
||||
const toastExists = this.toastList.find((t) => t.uid === toast.uid);
|
||||
if (toastExists) return () => {};
|
||||
const index = this.toastList.indexOf(toast);
|
||||
if (index !== -1) return () => this.close(toast);
|
||||
|
||||
@ -84,6 +89,11 @@ export default class Toasts {
|
||||
const index = this.toastList.indexOf(toast);
|
||||
if (index === -1) return;
|
||||
this.toastList.splice(index, 1);
|
||||
|
||||
if (toast.uid)
|
||||
Notifications.getInstance().put(toast.uid, {
|
||||
read: true,
|
||||
});
|
||||
this.event.emit("change", this.toastList);
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ $orange-soft: #ffdc99;
|
||||
$red-soft: #f08771;
|
||||
$pink-soft: #f8b9df;
|
||||
|
||||
$orange-soft-hover: #ffd078;
|
||||
$grey: #939393;
|
||||
$grey-medium: #e7e7e7;
|
||||
$grey-soft: #f9f9f9;
|
||||
|
@ -23,6 +23,7 @@
|
||||
--turquoise-soft: #{$turquoise-soft};
|
||||
--purple-soft: #{$purple-soft};
|
||||
--orange-soft: #{$orange-soft};
|
||||
--orange-soft-hover: #{$orange-soft-hover};
|
||||
--red-soft: #{$red-soft};
|
||||
--pink-soft: #{$pink-soft};
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Notifications from "@Front/Api/LeCoffreApi/Notifications/Notifications";
|
||||
import { ICustomerJwtPayload, IUserJwtPayload } from "@Front/Services/JwtService/JwtService";
|
||||
import jwt_decode from "jwt-decode";
|
||||
import { NextResponse } from "next/server";
|
||||
@ -6,7 +7,6 @@ import type { NextRequest } from "next/server";
|
||||
export async function middleware(request: NextRequest) {
|
||||
// Get the JWT from the cookies
|
||||
const cookies = request.cookies.get("leCoffreAccessToken");
|
||||
console.log("cookies", cookies)
|
||||
if (!cookies) return NextResponse.redirect(new URL("/login", request.url));
|
||||
|
||||
// Decode it
|
||||
@ -14,7 +14,6 @@ export async function middleware(request: NextRequest) {
|
||||
const customerDecodedToken = jwt_decode(cookies.value) as ICustomerJwtPayload;
|
||||
|
||||
// If no JWT provided, redirect to login page
|
||||
console.log("decoded tokens", userDecodedToken, customerDecodedToken )
|
||||
if (!userDecodedToken && !customerDecodedToken) return NextResponse.redirect(new URL("/login", request.url));
|
||||
|
||||
// If JWT expired, redirect to login page
|
||||
@ -22,13 +21,9 @@ export async function middleware(request: NextRequest) {
|
||||
const currentDate = new Date();
|
||||
const time = currentDate.getTime();
|
||||
const now = Math.floor(time / 1000);
|
||||
console.log("date now", Date.now());
|
||||
console.log("date local", new Date().toLocaleString())
|
||||
console.log("date iso",new Date().toISOString());
|
||||
|
||||
if (token.exp < now) {
|
||||
console.log('token expired')
|
||||
//return NextResponse.redirect(new URL("/login", request.url));
|
||||
return NextResponse.redirect(new URL("/login", request.url));
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
|
Loading…
x
Reference in New Issue
Block a user