diff --git a/calc-frontend/package-lock.json b/calc-frontend/package-lock.json
index ee6de35..7c5a677 100644
--- a/calc-frontend/package-lock.json
+++ b/calc-frontend/package-lock.json
@@ -19,6 +19,7 @@
"clsx": "^2.1.1",
"jsdom": "^26.0.0",
"lucide-react": "^0.483.0",
+ "next-themes": "^0.4.6",
"npm": "^11.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -28,6 +29,7 @@
"react-router": "^7.1.5",
"react-select": "^5.10.1",
"rehype-raw": "^7.0.0",
+ "sonner": "^2.0.3",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.0.4",
"tw-animate-css": "^1.2.5"
@@ -6384,6 +6386,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -9777,6 +9789,16 @@
"node": ">=6"
}
},
+ "node_modules/sonner": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
+ "integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
diff --git a/calc-frontend/package.json b/calc-frontend/package.json
index ca2da0c..b45c9be 100644
--- a/calc-frontend/package.json
+++ b/calc-frontend/package.json
@@ -22,6 +22,7 @@
"clsx": "^2.1.1",
"jsdom": "^26.0.0",
"lucide-react": "^0.483.0",
+ "next-themes": "^0.4.6",
"npm": "^11.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -31,6 +32,7 @@
"react-router": "^7.1.5",
"react-select": "^5.10.1",
"rehype-raw": "^7.0.0",
+ "sonner": "^2.0.3",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.0.4",
"tw-animate-css": "^1.2.5"
diff --git a/calc-frontend/src/App/App.tsx b/calc-frontend/src/App/App.tsx
index c6e6553..35dab8a 100644
--- a/calc-frontend/src/App/App.tsx
+++ b/calc-frontend/src/App/App.tsx
@@ -7,6 +7,8 @@ import './widgets.css'
import RoutesList from "./RoutesList";
import { AuthProvider } from "./AuthContext";
+import { Toaster } from "@/components/ui/sonner";
+
function App() {
// go to the RootLayout component to edit the visual layout
@@ -18,6 +20,7 @@ function App() {
return (
+
)
diff --git a/calc-frontend/src/__tests__/App.test.tsx b/calc-frontend/src/__tests__/App.test.tsx
index ae407e4..035d7b2 100644
--- a/calc-frontend/src/__tests__/App.test.tsx
+++ b/calc-frontend/src/__tests__/App.test.tsx
@@ -4,6 +4,7 @@ import { createMemoryRouter, createRoutesFromElements, RouterProvider } from 're
import { AuthProvider } from '../App/AuthContext'
import RoutesList from '../App/RoutesList'
+import { Toaster } from '@/components/ui/sonner'
test('full app rendering', () => {
@@ -11,7 +12,13 @@ test('full app rendering', () => {
initialEntries:['/error'],
})
- render(, {})
+ render(
+
+
+
+ ,
+ {}
+ );
// check if heading is visible
const heading = screen.getByRole('link', {name: 'Calc Visualizer'})
diff --git a/calc-frontend/src/components/Custom/DerivCustomGraph.tsx b/calc-frontend/src/components/Custom/DerivCustomGraph.tsx
index 662881a..a835249 100644
--- a/calc-frontend/src/components/Custom/DerivCustomGraph.tsx
+++ b/calc-frontend/src/components/Custom/DerivCustomGraph.tsx
@@ -2,6 +2,7 @@
import { useLayoutEffect , useEffect, useState} from "react";
import axios from "axios";
+import { Blocks } from "react-loader-spinner";
function DerivCustomGraph({func, lowerBound, upperBound, handleSave, onAIResponseComplete}) {
@@ -91,8 +92,10 @@ function DerivCustomGraph({func, lowerBound, upperBound, handleSave, onAIRespons
if(!ready){
return(
- Loading...
- Complex functions may take longer
+
)
}
diff --git a/calc-frontend/src/components/Custom/IntCustomGraph.tsx b/calc-frontend/src/components/Custom/IntCustomGraph.tsx
index ae41678..6e3bcb0 100644
--- a/calc-frontend/src/components/Custom/IntCustomGraph.tsx
+++ b/calc-frontend/src/components/Custom/IntCustomGraph.tsx
@@ -2,6 +2,7 @@
import { useLayoutEffect , useEffect, useState} from "react";
import axios from "axios";
+import { Blocks } from "react-loader-spinner";
function IntCustomGraph({func, lowerBound, upperBound, handleSave, onAIResponseComplete}) {
@@ -90,8 +91,10 @@ function IntCustomGraph({func, lowerBound, upperBound, handleSave, onAIResponseC
if(!ready){
return(
- Loading...
- Complex functions may take longer
+
)
}
diff --git a/calc-frontend/src/components/ui/FunctionCard.tsx b/calc-frontend/src/components/ui/FunctionCard.tsx
index 527ed43..8b57ed3 100644
--- a/calc-frontend/src/components/ui/FunctionCard.tsx
+++ b/calc-frontend/src/components/ui/FunctionCard.tsx
@@ -1,52 +1,71 @@
import axios from "axios";
import { useNavigate } from "react-router";
+import { toast } from "sonner";
interface FunctionCardProps {
- topic: string;
- equation: string;
- lowerBound: number;
- upperBound: number;
- id: string;
- onDelete: (id: string) => void;
+ topic: string;
+ equation: string;
+ lowerBound: number;
+ upperBound: number;
+ id: string;
+ onDelete: (id: string) => void;
}
-function FunctionCard({topic, equation, lowerBound, upperBound, id, onDelete}: FunctionCardProps) {
+function FunctionCard({
+ topic,
+ equation,
+ lowerBound,
+ upperBound,
+ id,
+ onDelete,
+}: FunctionCardProps) {
+ const navigate = useNavigate();
- const navigate = useNavigate()
+ const handleView = async () => {
+ navigate(`/${topic}s/custom`, {
+ state: { func: equation, bounds: [lowerBound, upperBound] },
+ });
+ };
- const handleView = async () => {
-
- navigate(`/${topic}s/custom`, {state: {func: equation, bounds: [lowerBound, upperBound]}} )
- }
-
- const handleDelete = async () => {
+ const handleDelete = async () => {
+ const serverUrl =
+ import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
- const serverUrl = import.meta.env.VITE_SERVER_URL || 'http://localhost:3000'
-
- try {
- await axios.delete(`${serverUrl}/func/delete/${id}`);
- onDelete(id);
- } catch (error) {
- console.error(`Delete function error: ${error}`)
- }
+ try {
+ await axios.delete(`${serverUrl}/func/delete/${id}`);
+ onDelete(id);
+ } catch (error) {
+ toast.error("Something went wrong deleting the function");
+ console.error(`Delete function error: ${error}`);
}
+ };
- return (
+ return (
-
{equation}
-

-
-
-
-
+
{equation}
+

+
+
+
+
- )
+ );
}
-export default FunctionCard;
\ No newline at end of file
+export default FunctionCard;
diff --git a/calc-frontend/src/components/ui/SignOutButton.tsx b/calc-frontend/src/components/ui/SignOutButton.tsx
index f3405c2..91e9376 100644
--- a/calc-frontend/src/components/ui/SignOutButton.tsx
+++ b/calc-frontend/src/components/ui/SignOutButton.tsx
@@ -1,3 +1,4 @@
+import { toast } from "sonner";
import { authClient } from "../../lib/auth-client"
function SignOutButton()
@@ -7,7 +8,8 @@ function SignOutButton()
await authClient.signOut();
} catch (error) {
- console.error("Error signing out: ", error);
+ toast.error("Something went wrong signing out");
+ console.error("Error signing out:", error);
}
}
diff --git a/calc-frontend/src/components/ui/sonner.tsx b/calc-frontend/src/components/ui/sonner.tsx
new file mode 100644
index 0000000..cd62aff
--- /dev/null
+++ b/calc-frontend/src/components/ui/sonner.tsx
@@ -0,0 +1,23 @@
+import { useTheme } from "next-themes"
+import { Toaster as Sonner, ToasterProps } from "sonner"
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ const { theme = "system" } = useTheme()
+
+ return (
+
+ )
+}
+
+export { Toaster }
diff --git a/calc-frontend/src/pages/CustomDerivative.tsx b/calc-frontend/src/pages/CustomDerivative.tsx
index a48832c..23323de 100644
--- a/calc-frontend/src/pages/CustomDerivative.tsx
+++ b/calc-frontend/src/pages/CustomDerivative.tsx
@@ -7,6 +7,7 @@ import { Session } from "../lib/auth-client.ts";
import axios from "axios";
import SaveFunctionButton from "../components/ui/SaveFunctionButton.tsx";
import AskAIButtonDerivative from "@/components/ui/AskAIButtonDerivative.tsx";
+import { toast } from "sonner";
function CustomDeriv() {
@@ -80,8 +81,10 @@ function CustomDeriv() {
Authorization: `Bearer ${session.session.token}`
}
})
+ toast.success("Function Saved Successfully!");
}
catch (error) {
+ toast.error("Function Failed to Save");
console.error("save function error: ",error);
}
finally{
diff --git a/calc-frontend/src/pages/CustomIntegral.tsx b/calc-frontend/src/pages/CustomIntegral.tsx
index e0f7c9c..c18531c 100644
--- a/calc-frontend/src/pages/CustomIntegral.tsx
+++ b/calc-frontend/src/pages/CustomIntegral.tsx
@@ -8,6 +8,8 @@ import axios from "axios";
import SaveFunctionButton from "../components/ui/SaveFunctionButton.tsx";
import AskAIButton from "../components/ui/AskAIButtonIntegral.tsx";
+import { toast } from "sonner";
+
function CustomInt() {
const location = useLocation()
@@ -80,9 +82,11 @@ function CustomInt() {
Authorization: `Bearer ${session.session.token}`
}
})
-
+
+ toast.success("Function Saved Successfully!");
}
catch (error) {
+ toast.error("Function Failed to Save");
console.error("save function error: ",error);
}
finally{