Skip to content

Commit

Permalink
📦 Added Toast Notification and added warning imminent deprecation
Browse files Browse the repository at this point in the history
  • Loading branch information
UnknownRori committed Sep 17, 2023
1 parent 4d2215f commit 53d1132
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 8 deletions.
21 changes: 13 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Footer from '@/components/navigation/Footer';
import Navbar from '@/components/navigation/Navbar';
import Sidebar from '@/components/navigation/Sidebar';
import RouterView from "@/components/navigation/RouterView";
import ToastProvider from "./components/notification/ToastProvider";
import ToastView from "./components/notification/ToastView";

import router from "@/router/router";
import Router from "./components/navigation/Router";
Expand All @@ -11,16 +13,19 @@ export default function App() {
return (
<>
<Background />
<div className='min-h-screen flex overflow-x-hidden'>
<Router router={router}>
<Navbar />
<ToastProvider>
<ToastView />
<div className='min-h-screen flex overflow-x-hidden'>
<Router router={router}>
<Navbar />

<Sidebar />
<Sidebar />

<RouterView />
</Router>
<Footer />
</div>
<RouterView />
</Router>
<Footer />
</div>
</ToastProvider>
</>
);
}
28 changes: 28 additions & 0 deletions src/components/notification/ToastProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useEffect } from "react";
import useToast, { createMessage, ToastContext, ToastContextInner } from "@/hooks/useToast";

type Props = {} & React.PropsWithChildren;

export default function(props: Props) {
const [push, animateDone, remove, queue] = useToast();

const contextValue: ToastContextInner = {
queue: queue,
remove: remove,
animateDone: animateDone,
push: push,
};

useEffect(() => {
const timeout = setTimeout(() => {
push(createMessage("WARNING", "This site will be decommisioned, please go to <a class='text-blue-600 underline font-bold'href='https://unknownrori.vercel.app'>https://unknownrori.vercel.app</a> instead"));
}, 1000);
return () => clearTimeout(timeout);
}, [])

return (
<ToastContext.Provider value={contextValue}>
{props.children}
</ToastContext.Provider>
)
}
95 changes: 95 additions & 0 deletions src/components/notification/ToastView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useContext, useEffect } from "react";

import { Message, MessageType, ToastContext, ToastContextInner } from "@/hooks/useToast"

Check failure on line 3 in src/components/notification/ToastView.tsx

View workflow job for this annotation

GitHub Actions / build_and_deploy

Missing semicolon

const AUTO_REMOVE_DELAY = 5000;

type ToastCardProp = {
removeToast: (id: number) => void,
doneAnimate: (id: number) => void,
} & Message;

function getIconType(type: MessageType) {


switch (type) {
case "INFO":
return (
<svg className="h-8 w-8 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" /> <circle cx="12" cy="12" r="9" /> <line x1="12" y1="8" x2="12.01" y2="8" /> <polyline points="11 12 12 12 12 16 13 16" /></svg>
);
case "ERROR":
return (
<svg className="h-8 w-8 text-red-700" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path className="text-red-700" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)

Check failure on line 25 in src/components/notification/ToastView.tsx

View workflow job for this annotation

GitHub Actions / build_and_deploy

Missing semicolon
case "WARNING":
return (
<svg className="h-8 w-8 text-yellow-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path className="text-yellow-600" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
);
}
}

function ToastCard(props: ToastCardProp) {

const Icon = getIconType(props.type);

useEffect(() => {
const timeoutAutoRemove = setTimeout(() => {
props.removeToast(props.id);
}, AUTO_REMOVE_DELAY);

const timeoutSpawn = setTimeout(() => {
props.doneAnimate(props.id);
}, 0);

return () => {
clearTimeout(timeoutAutoRemove);
clearTimeout(timeoutSpawn);
}

Check failure on line 51 in src/components/notification/ToastView.tsx

View workflow job for this annotation

GitHub Actions / build_and_deploy

Missing semicolon
}, []);

return (
<div className={`flex justify-start items-center gap-2 py-2 px-1 rounded bg-opacity-70 shadow
min:w-[34vw] sm:max-w-[68vw] md:max-w-[58vw] lg:max-w-[44vw] min-w-[24vw] z-[9999] duration-500
${props.animatedIn ? "opacity-0 translate-x-12" : props.removed ? "opacity-0 translate-x-12" : "opacity-100 translate-x-0"}
bg-gray-700`}>

<button onClick={() => props.removeToast(props.id)} className="absolute top-0 right-0 p-1">
<svg className="h-4 w-4 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18" /> <line x1="6" y1="6" x2="18" y2="18" /></svg>
</button>

<span>
{Icon}
</span>

<span className="p-1" dangerouslySetInnerHTML={{ __html: props.content }}>

</span>

</div>
)

Check failure on line 73 in src/components/notification/ToastView.tsx

View workflow job for this annotation

GitHub Actions / build_and_deploy

Missing semicolon
}

export default function() {
const context = useContext<ToastContextInner | null>(ToastContext);

if (context == null) {
throw new Error("Cannot get the context!");
}

return (
<div className="absolute bottom-12 right-0 p-3 flex flex-col justify-center items-end gap-2 w-full overflow-x-hidden">
<div className="relative flex flex-col gap-2">
{context.queue.map((content) => {
return (
<ToastCard id={content.id} content={content.content} type={content.type} animatedIn={content.animatedIn}
removed={content.removed} key={content.id} removeToast={context.remove} doneAnimate={context.animateDone} />
)

Check failure on line 90 in src/components/notification/ToastView.tsx

View workflow job for this annotation

GitHub Actions / build_and_deploy

Missing semicolon
})}
</div>
</div>
)

Check failure on line 94 in src/components/notification/ToastView.tsx

View workflow job for this annotation

GitHub Actions / build_and_deploy

Missing semicolon
}
3 changes: 3 additions & 0 deletions src/helpers/generateId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function(min: number, max: number) {
return Math.floor(((Math.random() * max) - min) % max);
}
76 changes: 76 additions & 0 deletions src/hooks/useToast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import generateId from "@/helpers/generateId";
import React, { useState, useEffect } from "react";

Check warning on line 2 in src/hooks/useToast.ts

View workflow job for this annotation

GitHub Actions / build_and_deploy

'useEffect' is defined but never used

const EXPIRE_MESSAGE = 2000;

export type MessageType = "ERROR" | "INFO" | "WARNING";
export type Message = {
id: number,
type: MessageType,
content: string,
animatedIn: boolean,
removed: boolean,
}

type PushMessage = (content: Message) => void;
type RemoveMessage = (id: number) => void;
type DoneAnimateMessage = (id: number) => void;

type ReturningValue = [
PushMessage,
DoneAnimateMessage,
RemoveMessage,
Array<Message>,
]

export type ToastContextInner = {
queue: Array<Message>,
push: PushMessage,
animateDone: DoneAnimateMessage,
remove: RemoveMessage,
};

export const ToastContext = React.createContext<ToastContextInner | null>(null)

Check failure on line 33 in src/hooks/useToast.ts

View workflow job for this annotation

GitHub Actions / build_and_deploy

Missing semicolon

export function createMessage(type: MessageType, content: string): Message {
return {
id: generateId(0, 100000),
type: type,
content: content,
animatedIn: true,
removed: false,
}

Check failure on line 42 in src/hooks/useToast.ts

View workflow job for this annotation

GitHub Actions / build_and_deploy

Missing semicolon
}

export default function(): ReturningValue {
const [queue, setQueue] = useState<Array<Message>>([]);

function remove(id: number) {
setQueue(queue.map((message) => {
if (message.id == id) {

message.removed = true;
setTimeout(() => {
setQueue(queue.filter((a) => a.id !== id));
}, EXPIRE_MESSAGE);
}


return message;
}));
}
function push(content: Message) {
setQueue([...queue, content]);
}

function doneAnimate(id: number) {
setQueue(queue.map((message) => {
if (message.id == id)
message.animatedIn = false;

return message;
}))
}

return [push, doneAnimate, remove, queue];
}

0 comments on commit 53d1132

Please sign in to comment.