Refactor toast
This commit is contained in:
parent
3856f0cacc
commit
57b66f3f58
29
package-lock.json
generated
29
package-lock.json
generated
@ -19,8 +19,7 @@
|
|||||||
"next": "^14.0.4",
|
"next": "^14.0.4",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18"
|
||||||
"react-hot-toast": "^2.4.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
@ -2008,7 +2007,8 @@
|
|||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
@ -3073,14 +3073,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/goober": {
|
|
||||||
"version": "2.1.14",
|
|
||||||
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz",
|
|
||||||
"integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"csstype": "^3.0.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||||
@ -4609,21 +4601,6 @@
|
|||||||
"react": "^18.3.1"
|
"react": "^18.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-hot-toast": {
|
|
||||||
"version": "2.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz",
|
|
||||||
"integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"goober": "^2.1.10"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16",
|
|
||||||
"react-dom": ">=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
|||||||
@ -20,8 +20,7 @@
|
|||||||
"next": "^14.0.4",
|
"next": "^14.0.4",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18"
|
||||||
"react-hot-toast": "^2.4.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { useToast } from "./ToasterProvider";
|
||||||
|
|
||||||
export type ToastType = "error" | "success" | "info";
|
export type ToastType = "error" | "success" | "info";
|
||||||
export type ToastProps = {
|
export type ToastProps = {
|
||||||
message: string;
|
message: string;
|
||||||
@ -5,16 +7,24 @@ export type ToastProps = {
|
|||||||
onDismiss: () => void;
|
onDismiss: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PlaygroundToast = ({ message, type, onDismiss }: ToastProps) => {
|
export const PlaygroundToast = () => {
|
||||||
|
const { toastMessage, setToastMessage } = useToast();
|
||||||
const color =
|
const color =
|
||||||
type === "error" ? "red" : type === "success" ? "green" : "amber";
|
toastMessage?.type === "error"
|
||||||
|
? "red"
|
||||||
|
: toastMessage?.type === "success"
|
||||||
|
? "green"
|
||||||
|
: "amber";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`absolute text-sm break-words px-4 pr-12 py-2 bg-${color}-950 rounded-sm border border-${color}-800 text-${color}-400 top-4 left-4 right-4`}
|
className={`absolute text-sm break-words px-4 pr-12 py-2 bg-${color}-950 rounded-sm border border-${color}-800 text-${color}-400 top-4 left-4 right-4`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className={`absolute right-2 border border-transparent rounded-md px-2 hover:bg-${color}-900 hover:text-${color}-300`}
|
className={`absolute right-2 border border-transparent rounded-md px-2 hover:bg-${color}-900 hover:text-${color}-300`}
|
||||||
onClick={onDismiss}
|
onClick={() => {
|
||||||
|
setToastMessage(null);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -31,7 +41,7 @@ export const PlaygroundToast = ({ message, type, onDismiss }: ToastProps) => {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{message}
|
{toastMessage?.message}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
40
src/components/toast/ToasterProvider.tsx
Normal file
40
src/components/toast/ToasterProvider.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import React, { createContext, useState } from "react";
|
||||||
|
import { ToastType } from "./PlaygroundToast";
|
||||||
|
|
||||||
|
type ToastProviderData = {
|
||||||
|
setToastMessage: (
|
||||||
|
message: { message: string; type: ToastType } | null
|
||||||
|
) => void;
|
||||||
|
toastMessage: { message: string; type: ToastType } | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ToastContext = createContext<ToastProviderData | undefined>(undefined);
|
||||||
|
|
||||||
|
export const ToastProvider = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
const [toastMessage, setToastMessage] = useState<{message: string, type: ToastType} | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToastContext.Provider
|
||||||
|
value={{
|
||||||
|
toastMessage,
|
||||||
|
setToastMessage
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ToastContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useToast = () => {
|
||||||
|
const context = React.useContext(ToastContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useToast must be used within a ToastProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ import { useCloud } from "@/cloud/useCloud";
|
|||||||
import React, { createContext, useState } from "react";
|
import React, { createContext, useState } from "react";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useConfig } from "./useConfig";
|
import { useConfig } from "./useConfig";
|
||||||
import toast from "react-hot-toast";
|
import { useToast } from "@/components/toast/ToasterProvider";
|
||||||
|
|
||||||
export type ConnectionMode = "cloud" | "manual" | "env"
|
export type ConnectionMode = "cloud" | "manual" | "env"
|
||||||
|
|
||||||
@ -25,6 +25,7 @@ export const ConnectionProvider = ({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const { generateToken, wsUrl: cloudWSUrl } = useCloud();
|
const { generateToken, wsUrl: cloudWSUrl } = useCloud();
|
||||||
|
const { setToastMessage } = useToast();
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
const [connectionDetails, setConnectionDetails] = useState<{
|
const [connectionDetails, setConnectionDetails] = useState<{
|
||||||
wsUrl: string;
|
wsUrl: string;
|
||||||
@ -33,16 +34,19 @@ export const ConnectionProvider = ({
|
|||||||
shouldConnect: boolean;
|
shouldConnect: boolean;
|
||||||
}>({ wsUrl: "", token: "", shouldConnect: false, mode: "manual" });
|
}>({ wsUrl: "", token: "", shouldConnect: false, mode: "manual" });
|
||||||
|
|
||||||
const connect = useCallback(async (mode: ConnectionMode) => {
|
const connect = useCallback(
|
||||||
|
async (mode: ConnectionMode) => {
|
||||||
let token = "";
|
let token = "";
|
||||||
let url = "";
|
let url = "";
|
||||||
if (mode === "cloud") {
|
if (mode === "cloud") {
|
||||||
try {
|
try {
|
||||||
token = await generateToken();
|
token = await generateToken();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
setToastMessage({
|
||||||
"Failed to generate token, you may need to increase your role in this LiveKit Cloud project."
|
type: "error",
|
||||||
);
|
message:
|
||||||
|
"Failed to generate token, you may need to increase your role in this LiveKit Cloud project.",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
url = cloudWSUrl;
|
url = cloudWSUrl;
|
||||||
} else if (mode === "env") {
|
} else if (mode === "env") {
|
||||||
@ -50,19 +54,24 @@ export const ConnectionProvider = ({
|
|||||||
throw new Error("NEXT_PUBLIC_LIVEKIT_URL is not set");
|
throw new Error("NEXT_PUBLIC_LIVEKIT_URL is not set");
|
||||||
}
|
}
|
||||||
url = process.env.NEXT_PUBLIC_LIVEKIT_URL;
|
url = process.env.NEXT_PUBLIC_LIVEKIT_URL;
|
||||||
const {accessToken} = await fetch("/api/token").then((res) => res.json());
|
const { accessToken } = await fetch("/api/token").then((res) =>
|
||||||
|
res.json()
|
||||||
|
);
|
||||||
token = accessToken;
|
token = accessToken;
|
||||||
} else {
|
} else {
|
||||||
token = config.settings.token;
|
token = config.settings.token;
|
||||||
url = config.settings.ws_url;
|
url = config.settings.ws_url;
|
||||||
}
|
}
|
||||||
setConnectionDetails({ wsUrl: url, token, shouldConnect: true, mode });
|
setConnectionDetails({ wsUrl: url, token, shouldConnect: true, mode });
|
||||||
}, [
|
},
|
||||||
|
[
|
||||||
cloudWSUrl,
|
cloudWSUrl,
|
||||||
config.settings.token,
|
config.settings.token,
|
||||||
config.settings.ws_url,
|
config.settings.ws_url,
|
||||||
generateToken,
|
generateToken,
|
||||||
]);
|
setToastMessage,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
const disconnect = useCallback(async () => {
|
const disconnect = useCallback(async () => {
|
||||||
setConnectionDetails((prev) => ({ ...prev, shouldConnect: false }));
|
setConnectionDetails((prev) => ({ ...prev, shouldConnect: false }));
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { CloudProvider } from "@/cloud/useCloud";
|
import { CloudProvider } from "@/cloud/useCloud";
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
import type { AppProps } from "next/app";
|
import type { AppProps } from "next/app";
|
||||||
import { Toaster } from "react-hot-toast";
|
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
<CloudProvider>
|
<CloudProvider>
|
||||||
<Toaster />
|
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</CloudProvider>
|
</CloudProvider>
|
||||||
);}
|
);}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { PlaygroundToast, ToastType } from "@/components/toast/PlaygroundToast";
|
|||||||
import { ConfigProvider, useConfig } from "@/hooks/useConfig";
|
import { ConfigProvider, useConfig } from "@/hooks/useConfig";
|
||||||
import { ConnectionMode, ConnectionProvider, useConnection } from "@/hooks/useConnection";
|
import { ConnectionMode, ConnectionProvider, useConnection } from "@/hooks/useConnection";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { ToastProvider, useToast } from "@/components/toast/ToasterProvider";
|
||||||
|
|
||||||
const themeColors = [
|
const themeColors = [
|
||||||
"cyan",
|
"cyan",
|
||||||
@ -30,23 +31,22 @@ const inter = Inter({ subsets: ["latin"] });
|
|||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
|
<ToastProvider>
|
||||||
<ConfigProvider>
|
<ConfigProvider>
|
||||||
<ConnectionProvider>
|
<ConnectionProvider>
|
||||||
<HomeInner />
|
<HomeInner />
|
||||||
</ConnectionProvider>
|
</ConnectionProvider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
|
</ToastProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HomeInner() {
|
export function HomeInner() {
|
||||||
const [toastMessage, setToastMessage] = useState<{
|
|
||||||
message: string;
|
|
||||||
type: ToastType;
|
|
||||||
} | null>(null);
|
|
||||||
const { shouldConnect, wsUrl, token, mode, connect, disconnect } =
|
const { shouldConnect, wsUrl, token, mode, connect, disconnect } =
|
||||||
useConnection();
|
useConnection();
|
||||||
|
|
||||||
const {config} = useConfig();
|
const {config} = useConfig();
|
||||||
|
const { toastMessage, setToastMessage } = useToast();
|
||||||
|
|
||||||
const handleConnect = useCallback(
|
const handleConnect = useCallback(
|
||||||
async (c: boolean, mode: ConnectionMode) => {
|
async (c: boolean, mode: ConnectionMode) => {
|
||||||
@ -93,13 +93,7 @@ export function HomeInner() {
|
|||||||
animate={{ opacity: 1, translateY: 0 }}
|
animate={{ opacity: 1, translateY: 0 }}
|
||||||
exit={{ opacity: 0, translateY: -50 }}
|
exit={{ opacity: 0, translateY: -50 }}
|
||||||
>
|
>
|
||||||
<PlaygroundToast
|
<PlaygroundToast />
|
||||||
message={toastMessage.message}
|
|
||||||
type={toastMessage.type}
|
|
||||||
onDismiss={() => {
|
|
||||||
setToastMessage(null);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user