✨ 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",
|
"eslint-config-next": "13.2.4",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"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",
|
"next": "13.2.4",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
@ -36,5 +36,3 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {}
|
"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">
|
<svg width="22" height="22" viewBox="0 0 22 22" 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"/>
|
<path d="M18 6L8.375 16L4 11.4545" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 497 B After Width: | Height: | Size: 219 B |
@ -1,16 +1,36 @@
|
|||||||
import Module from "@Front/Config/Module";
|
import Module from "@Front/Config/Module";
|
||||||
import React from "react";
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
import HeaderLink from "../HeaderLink";
|
import HeaderLink from "../HeaderLink";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
|
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
|
||||||
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
|
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 = {};
|
const getNotifications = useCallback(async () => {
|
||||||
type IState = {};
|
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 (
|
return (
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
<HeaderLink
|
<HeaderLink
|
||||||
@ -43,4 +63,3 @@ export default class Navigation extends React.Component<IProps, IState> {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
background: $orange-soft;
|
background: var(--orange-soft);
|
||||||
box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.11);
|
box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.11);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
@ -42,6 +42,13 @@
|
|||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[data-clickable="true"] {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: var(--orange-soft-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.loadbar {
|
.loadbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -8,18 +8,25 @@ import React from "react";
|
|||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import Toasts, { IToast } from "@Front/Stores/Toasts";
|
import Toasts, { IToast } from "@Front/Stores/Toasts";
|
||||||
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
|
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 = {
|
type IProps = {
|
||||||
toast: IToast;
|
toast: IToast;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type IPropsClass = IProps & {
|
||||||
|
router: NextRouter;
|
||||||
|
};
|
||||||
|
|
||||||
type IState = {
|
type IState = {
|
||||||
willClose: boolean;
|
willClose: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ToastElement extends React.Component<IProps, IState> {
|
class ToastElementClass extends React.Component<IPropsClass, IState> {
|
||||||
private closeTimeout = 0;
|
private closeTimeout = 0;
|
||||||
constructor(props: IProps) {
|
constructor(props: IPropsClass) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -27,6 +34,7 @@ export default class ToastElement extends React.Component<IProps, IState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.onClose = this.onClose.bind(this);
|
this.onClose = this.onClose.bind(this);
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override render(): JSX.Element {
|
public override render(): JSX.Element {
|
||||||
@ -35,7 +43,11 @@ export default class ToastElement extends React.Component<IProps, IState> {
|
|||||||
"--data-duration": `${toast.time}ms`,
|
"--data-duration": `${toast.time}ms`,
|
||||||
} as React.CSSProperties;
|
} as React.CSSProperties;
|
||||||
return (
|
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} />}
|
{toast.time !== 0 && <div className={classes["loadbar"]} style={style} />}
|
||||||
<div className={classes["header"]}>
|
<div className={classes["header"]}>
|
||||||
<div className={classes["text-icon_row"]}>
|
<div className={classes["text-icon_row"]}>
|
||||||
@ -45,7 +57,7 @@ export default class ToastElement extends React.Component<IProps, IState> {
|
|||||||
{this.getToastText(toast.text)}
|
{this.getToastText(toast.text)}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
{toast.button}
|
{toast.button}
|
||||||
</div>
|
</div>
|
||||||
@ -95,4 +107,16 @@ export default class ToastElement extends React.Component<IProps, IState> {
|
|||||||
Toasts.getInstance().close(this.props.toast);
|
Toasts.getInstance().close(this.props.toast);
|
||||||
}, 200);
|
}, 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 EventEmitter from "@Front/Services/EventEmitter";
|
||||||
// import I18n from "Components/Elements/I18n";
|
// import I18n from "Components/Elements/I18n";
|
||||||
|
|
||||||
@ -7,6 +8,8 @@ export enum EToastPriority {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IToast {
|
export interface IToast {
|
||||||
|
uid?: string;
|
||||||
|
redirectUrl?: string;
|
||||||
id?: number;
|
id?: number;
|
||||||
title: string | React.ReactNode;
|
title: string | React.ReactNode;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
@ -23,7 +26,7 @@ export default class Toasts {
|
|||||||
private toastList: IToast[] = [];
|
private toastList: IToast[] = [];
|
||||||
private uid: number = 0;
|
private uid: number = 0;
|
||||||
|
|
||||||
private defaultTime: IToast["time"] = 10000;
|
private defaultTime: IToast["time"] = 0;
|
||||||
private defaultClosable: IToast["closable"] = true;
|
private defaultClosable: IToast["closable"] = true;
|
||||||
private defaultPriority: IToast["priority"] = EToastPriority.LOW;
|
private defaultPriority: IToast["priority"] = EToastPriority.LOW;
|
||||||
|
|
||||||
@ -49,6 +52,8 @@ export default class Toasts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public open(toast: IToast): () => void {
|
public open(toast: IToast): () => void {
|
||||||
|
const toastExists = this.toastList.find((t) => t.uid === toast.uid);
|
||||||
|
if (toastExists) return () => {};
|
||||||
const index = this.toastList.indexOf(toast);
|
const index = this.toastList.indexOf(toast);
|
||||||
if (index !== -1) return () => this.close(toast);
|
if (index !== -1) return () => this.close(toast);
|
||||||
|
|
||||||
@ -84,6 +89,11 @@ export default class Toasts {
|
|||||||
const index = this.toastList.indexOf(toast);
|
const index = this.toastList.indexOf(toast);
|
||||||
if (index === -1) return;
|
if (index === -1) return;
|
||||||
this.toastList.splice(index, 1);
|
this.toastList.splice(index, 1);
|
||||||
|
|
||||||
|
if (toast.uid)
|
||||||
|
Notifications.getInstance().put(toast.uid, {
|
||||||
|
read: true,
|
||||||
|
});
|
||||||
this.event.emit("change", this.toastList);
|
this.event.emit("change", this.toastList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ $orange-soft: #ffdc99;
|
|||||||
$red-soft: #f08771;
|
$red-soft: #f08771;
|
||||||
$pink-soft: #f8b9df;
|
$pink-soft: #f8b9df;
|
||||||
|
|
||||||
|
$orange-soft-hover: #ffd078;
|
||||||
$grey: #939393;
|
$grey: #939393;
|
||||||
$grey-medium: #e7e7e7;
|
$grey-medium: #e7e7e7;
|
||||||
$grey-soft: #f9f9f9;
|
$grey-soft: #f9f9f9;
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
--turquoise-soft: #{$turquoise-soft};
|
--turquoise-soft: #{$turquoise-soft};
|
||||||
--purple-soft: #{$purple-soft};
|
--purple-soft: #{$purple-soft};
|
||||||
--orange-soft: #{$orange-soft};
|
--orange-soft: #{$orange-soft};
|
||||||
|
--orange-soft-hover: #{$orange-soft-hover};
|
||||||
--red-soft: #{$red-soft};
|
--red-soft: #{$red-soft};
|
||||||
--pink-soft: #{$pink-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 { ICustomerJwtPayload, IUserJwtPayload } from "@Front/Services/JwtService/JwtService";
|
||||||
import jwt_decode from "jwt-decode";
|
import jwt_decode from "jwt-decode";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
@ -6,7 +7,6 @@ import type { NextRequest } from "next/server";
|
|||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
// Get the JWT from the cookies
|
// Get the JWT from the cookies
|
||||||
const cookies = request.cookies.get("leCoffreAccessToken");
|
const cookies = request.cookies.get("leCoffreAccessToken");
|
||||||
console.log("cookies", cookies)
|
|
||||||
if (!cookies) return NextResponse.redirect(new URL("/login", request.url));
|
if (!cookies) return NextResponse.redirect(new URL("/login", request.url));
|
||||||
|
|
||||||
// Decode it
|
// Decode it
|
||||||
@ -14,7 +14,6 @@ export async function middleware(request: NextRequest) {
|
|||||||
const customerDecodedToken = jwt_decode(cookies.value) as ICustomerJwtPayload;
|
const customerDecodedToken = jwt_decode(cookies.value) as ICustomerJwtPayload;
|
||||||
|
|
||||||
// If no JWT provided, redirect to login page
|
// 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 (!userDecodedToken && !customerDecodedToken) return NextResponse.redirect(new URL("/login", request.url));
|
||||||
|
|
||||||
// If JWT expired, redirect to login page
|
// If JWT expired, redirect to login page
|
||||||
@ -22,13 +21,9 @@ export async function middleware(request: NextRequest) {
|
|||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
const time = currentDate.getTime();
|
const time = currentDate.getTime();
|
||||||
const now = Math.floor(time / 1000);
|
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) {
|
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();
|
return NextResponse.next();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user