diff --git a/package-lock.json b/package-lock.json
index bee4557..e1e37e1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,9 +11,11 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/joy": "^5.0.0-beta.51",
+ "@supabase/supabase-js": "^2.48.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "react-router-dom": "^7.1.3"
+ "react-router-dom": "^7.1.3",
+ "react-toastify": "^11.0.3"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
@@ -1738,6 +1740,80 @@
"win32"
]
},
+ "node_modules/@supabase/auth-js": {
+ "version": "2.67.3",
+ "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.67.3.tgz",
+ "integrity": "sha512-NJDaW8yXs49xMvWVOkSIr8j46jf+tYHV0wHhrwOaLLMZSFO4g6kKAf+MfzQ2RaD06OCUkUHIzctLAxjTgEVpzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/functions-js": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz",
+ "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/node-fetch": {
+ "version": "2.6.15",
+ "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
+ "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ }
+ },
+ "node_modules/@supabase/postgrest-js": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.18.0.tgz",
+ "integrity": "sha512-DqUEVF5ZFytrYLAuywnUR0srhZbzSoLZePMfGFamYMNdMttBLwAWXJNJ5kBDHn2gK2n87NwLsIshUbdyxPtpsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/realtime-js": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.2.tgz",
+ "integrity": "sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14",
+ "@types/phoenix": "^1.5.4",
+ "@types/ws": "^8.5.10",
+ "ws": "^8.18.0"
+ }
+ },
+ "node_modules/@supabase/storage-js": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz",
+ "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/supabase-js": {
+ "version": "2.48.0",
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.48.0.tgz",
+ "integrity": "sha512-8ql5ra3NOIHLBYoFZpxYHRx05J/FmJAQW9EYax0lU4DsU1/5PC4yb2hxMcCIf0oimLcBgsogIaAqba+/9jaUVg==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/auth-js": "2.67.3",
+ "@supabase/functions-js": "2.4.4",
+ "@supabase/node-fetch": "2.6.15",
+ "@supabase/postgrest-js": "1.18.0",
+ "@supabase/realtime-js": "2.11.2",
+ "@supabase/storage-js": "2.7.1"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1803,12 +1879,27 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/node": {
+ "version": "22.10.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
+ "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.20.0"
+ }
+ },
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
"license": "MIT"
},
+ "node_modules/@types/phoenix": {
+ "version": "1.6.6",
+ "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz",
+ "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==",
+ "license": "MIT"
+ },
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
@@ -1836,6 +1927,15 @@
"@types/react": "^18.0.0"
}
},
+ "node_modules/@types/ws": {
+ "version": "8.5.13",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
+ "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz",
@@ -3523,6 +3623,19 @@
"react-dom": ">=18"
}
},
+ "node_modules/react-toastify": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.3.tgz",
+ "integrity": "sha512-cbPtHJPfc0sGqVwozBwaTrTu1ogB9+BLLjd4dDXd863qYLj7DGrQ2sg5RAChjFUB4yc3w8iXOtWcJqPK/6xqRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19",
+ "react-dom": "^18 || ^19"
+ }
+ },
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
@@ -3756,6 +3869,12 @@
"node": ">=8.0"
}
},
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
"node_modules/ts-api-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz",
@@ -3825,6 +3944,12 @@
"typescript": ">=4.8.4 <5.8.0"
}
},
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "license": "MIT"
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
@@ -3938,6 +4063,22 @@
}
}
},
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3964,6 +4105,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
diff --git a/package.json b/package.json
index f7f9c4b..a0c6be8 100644
--- a/package.json
+++ b/package.json
@@ -14,9 +14,11 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/joy": "^5.0.0-beta.51",
+ "@supabase/supabase-js": "^2.48.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "react-router-dom": "^7.1.3"
+ "react-router-dom": "^7.1.3",
+ "react-toastify": "^11.0.3"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
diff --git a/src/api/mock-data.ts b/src/api/mock-data.ts
index a91b627..4644091 100644
--- a/src/api/mock-data.ts
+++ b/src/api/mock-data.ts
@@ -1,6 +1,6 @@
export const mockUser = {
isAuth: true,
- hasAvatar: true,
+ hasAvatar: false,
isAvatarLoading: false
};
diff --git a/src/api/useUserApi.ts b/src/api/useUserApi.ts
new file mode 100644
index 0000000..c794980
--- /dev/null
+++ b/src/api/useUserApi.ts
@@ -0,0 +1,20 @@
+import { supabase } from "../lib/supabase";
+import { UserModel } from "../models/user.model";
+import { showToast } from "../utils/toast";
+
+export const getUserByEmail = async (email: string) => {
+ try {
+ const { data, error } = await supabase.from("users").select().eq("email", email).single();
+ if (error) {
+ if (error.details === "The result contains 0 rows") {
+ showToast("error", "Invalid credential.");
+ } else {
+ showToast("error", "Failed to get user.");
+ }
+ }
+ return data as UserModel;
+ } catch (error) {
+ console.error("Failed to get user:", error);
+ throw error;
+ }
+};
diff --git a/src/components/navigation/navbar.tsx b/src/components/navigation/navbar.tsx
index 2c5139c..b0e898b 100644
--- a/src/components/navigation/navbar.tsx
+++ b/src/components/navigation/navbar.tsx
@@ -1,15 +1,17 @@
import { Avatar, Dropdown, IconButton, Menu, MenuButton, MenuItem, Stack, Typography } from "@mui/joy";
-import { useState } from "react";
+import { useContext, useState } from "react";
import { ArrowDownOutlined } from "../../assets/icons/arrow-down";
import { ArrowUpOutlined } from "../../assets/icons/arrow-up";
import { NotificationsOutlined } from "../../assets/icons/notifications-icon";
import { SubvisualLogo } from "../../assets/icons/subvisual-logo";
+import { AuthContext } from "../../contexts/auth.context";
type NavbarProps = {
hasSubvisualIcon?: boolean;
};
export const Navbar = ({ hasSubvisualIcon }: NavbarProps) => {
+ const { handleLogout } = useContext(AuthContext);
const [isOpen, setIsOpen] = useState(false);
return (
@@ -34,9 +36,7 @@ export const Navbar = ({ hasSubvisualIcon }: NavbarProps) => {
{isOpen ?