diff --git a/components/PostLayout.tsx b/components/PostLayout.tsx
index 73d7b1c..9460b3d 100644
--- a/components/PostLayout.tsx
+++ b/components/PostLayout.tsx
@@ -6,7 +6,7 @@ import { NavMenu } from "./common";
import TitleHead from "./TitleHead";
import Logo from "./common/Logo";
-const PostLayout = ({ children, title, permalink, thumbnail, token }: Props) => (
+const PostLayout = ({ children, title, permalink, thumbnail }: Props) => (
@@ -30,7 +30,7 @@ const PostLayout = ({ children, title, permalink, thumbnail, token }: Props) =>
-
+
{children}
diff --git a/components/Subpage.tsx b/components/Subpage.tsx
index 3f22816..e42dbfc 100644
--- a/components/Subpage.tsx
+++ b/components/Subpage.tsx
@@ -9,7 +9,6 @@ const Subpage = ({
children,
title,
subreddit,
- token,
backgroundColor = "white"
}: Props) => (
@@ -28,7 +27,7 @@ const Subpage = ({
-
+
{children}
diff --git a/components/common/Header.tsx b/components/common/Header.tsx
index cb2dda6..424200f 100644
--- a/components/common/Header.tsx
+++ b/components/common/Header.tsx
@@ -8,7 +8,7 @@ interface HeaderProps {
className?: string;
}
-const Header: React.FC = ({ token, className = '' }) => {
+const Header: React.FC = ({ className = '' }) => {
return (
-
+
);
diff --git a/components/common/index.tsx b/components/common/index.tsx
index 4bffe51..5b1aca4 100644
--- a/components/common/index.tsx
+++ b/components/common/index.tsx
@@ -1,12 +1,13 @@
import _ from 'lodash';
-import React, { useEffect, useRef, useState } from "react";
+import React, { useEffect, useRef, useState, useCallback } from "react";
import Image from 'next/image';
import Link from 'next/link';
import { getIntFromString, getTime, limitText } from "../../functions/common";
import { DESC_MAX } from "../../functions/constants";
import { DropdownProps, Props } from "../../interfaces";
-import { useConfig } from '../../lib/ConfigContext';
-import LoginButton from './LoginButton'; // Import the new LoginButton component
+import { useConfig } from '../../functions/useConfig';
+import LoginButton from './LoginButton';
+import { useAuth } from '../../contexts/AuthContext'; // Add this import
export const MidContainer = ({ children }: Props) => (
{children}
@@ -14,44 +15,61 @@ export const MidContainer = ({ children }: Props) => (
const ProfileOptions = () => {
const [showDropdown, setShowDropdown] = useState(false);
- const dropdown = useRef(null);
+ const dropdownRef = useRef(null);
+ const avatarRef = useRef(null);
+
+ console.log("ProfileOptions - showDropdown:", showDropdown);
+
+ const toggleDropdown = useCallback((e: React.MouseEvent) => {
+ e.stopPropagation();
+ setShowDropdown(prev => !prev);
+ }, []);
useEffect(() => {
- if (!showDropdown) return;
- function handleClick(e: any) {
- if (dropdown.current && !dropdown.current.contains(e.target)) {
+ const handleOutsideClick = (e: MouseEvent) => {
+ if (
+ dropdownRef.current &&
+ !dropdownRef.current.contains(e.target as Node) &&
+ avatarRef.current &&
+ !avatarRef.current.contains(e.target as Node)
+ ) {
setShowDropdown(false);
}
+ };
+
+ if (showDropdown) {
+ document.addEventListener('mousedown', handleOutsideClick);
}
- window.addEventListener("click", handleClick);
- return () => window.removeEventListener("click", handleClick);
- });
+
+ return () => {
+ document.removeEventListener('mousedown', handleOutsideClick);
+ };
+ }, [showDropdown]);
return (
setShowDropdown(!showDropdown)}
+ onClick={toggleDropdown}
>
- {showDropdown ? (
+ {showDropdown && (
-
+
View Profile
-
+
Sign out
- ) : (
-
)}
);
@@ -130,10 +148,11 @@ export const PostMetadata = ({
);
-export const NavMenu = ({ token = "" }: any) => {
+export const NavMenu = () => {
const [showSearch, setShowSearch] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
- const config = useConfig();
+ const { config } = useConfig(); // Destructure to get the config object
+ const { token } = useAuth();
const newSearch = () => (window.location.href = `/search/?q=${searchTerm}`);
@@ -189,7 +208,7 @@ export const NavMenu = ({ token = "" }: any) => {
)}
- {token != "" ? (
+ {token ? (
) : (
diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx
new file mode 100644
index 0000000..aa0c3f1
--- /dev/null
+++ b/contexts/AuthContext.tsx
@@ -0,0 +1,43 @@
+import React, { createContext, useState, useContext, useEffect } from 'react';
+import Cookies from 'js-cookie';
+
+interface AuthContextType {
+ token: string | null;
+ setToken: (token: string | null) => void;
+}
+
+const AuthContext = createContext(undefined);
+
+export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [token, setToken] = useState(null);
+
+ useEffect(() => {
+ const storedToken = Cookies.get('token');
+ if (storedToken) {
+ setToken(storedToken);
+ }
+ }, []);
+
+ const updateToken = (newToken: string | null) => {
+ setToken(newToken);
+ if (newToken) {
+ Cookies.set('token', newToken, { expires: 7 });
+ } else {
+ Cookies.remove('token');
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+ if (context === undefined) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+};
diff --git a/lib/ConfigContext.tsx b/contexts/ConfigContext.tsx
similarity index 100%
rename from lib/ConfigContext.tsx
rename to contexts/ConfigContext.tsx
diff --git a/next.config.js b/next.config.js
index 1ddba17..f3ce6ef 100644
--- a/next.config.js
+++ b/next.config.js
@@ -8,6 +8,7 @@ const nextConfig = {
publicRuntimeConfig: {
REDDIUM_DISABLE_ABOUT: true,
REDDIUM_DISABLE_KOFI_LINK: true,
+ REDDIUM_DISABLE_GITHUB_LINK: true,
REDDIUM_DISABLE_LOGIN: true,
REDDIUM_DOMAIN: 'http://localhost:3000',
...Object.fromEntries(
diff --git a/package-lock.json b/package-lock.json
index ac946e6..b6df022 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,10 +20,12 @@
"cookies-js": "^1.2.3",
"glob": "^11.0.0",
"highlight.run": "^9.5.0",
+ "js-cookie": "^3.0.5",
"lodash": "^4.17.21",
"micromark": "^4.0.0",
"micromark-extension-gfm": "^3.0.0",
"next": "^14.0.0",
+ "nookies": "^2.5.2",
"postcss": "^8.4.47",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -35,6 +37,7 @@
},
"devDependencies": {
"@eslint/object-schema": "^2.0.0",
+ "@types/js-cookie": "^3.0.6",
"@types/node": "^18.11.3",
"@types/react": "18.0.21",
"@types/react-dom": "18.0.6",
@@ -1759,6 +1762,13 @@
"integrity": "sha512-DaZNUvLDCAnCTjgwxgiL1eQdxIKEpNLOlTNtAgnZc50bG2copGhRrFN9/PxPBuJe+tZVLCbQ7ls0xveXVRPkvw==",
"license": "MIT"
},
+ "node_modules/@types/js-cookie": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -2898,6 +2908,15 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/cookies": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz",
@@ -5411,6 +5430,15 @@
"jiti": "bin/jiti.js"
}
},
+ "node_modules/js-cookie": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6670,6 +6698,16 @@
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
"license": "MIT"
},
+ "node_modules/nookies": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/nookies/-/nookies-2.5.2.tgz",
+ "integrity": "sha512-x0TRSaosAEonNKyCrShoUaJ5rrT5KHRNZ5DwPCuizjgrnkpE5DRf3VL7AyyQin4htict92X1EQ7ejDbaHDVdYA==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^0.4.1",
+ "set-cookie-parser": "^2.4.6"
+ }
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -8426,6 +8464,12 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz",
+ "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==",
+ "license": "MIT"
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
diff --git a/package.json b/package.json
index 089cd14..23fcfe9 100644
--- a/package.json
+++ b/package.json
@@ -21,10 +21,12 @@
"cookies-js": "^1.2.3",
"glob": "^11.0.0",
"highlight.run": "^9.5.0",
+ "js-cookie": "^3.0.5",
"lodash": "^4.17.21",
"micromark": "^4.0.0",
"micromark-extension-gfm": "^3.0.0",
"next": "^14.0.0",
+ "nookies": "^2.5.2",
"postcss": "^8.4.47",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -36,6 +38,7 @@
},
"devDependencies": {
"@eslint/object-schema": "^2.0.0",
+ "@types/js-cookie": "^3.0.6",
"@types/node": "^18.11.3",
"@types/react": "18.0.21",
"@types/react-dom": "18.0.6",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index edce820..4ab81fc 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -2,8 +2,9 @@ import React, { useEffect } from "react";
import { AppProps } from "next/app";
import "../styles/styles.css";
import { H } from "highlight.run";
-import { ConfigProvider } from '../lib/ConfigContext'
+import { ConfigProvider } from '../contexts/ConfigContext'
import { useConfig } from '../functions/useConfig';
+import { AuthProvider } from '../contexts/AuthContext';
if (typeof window !== "undefined") {
H.init("5ldw65eo");
@@ -20,9 +21,11 @@ const App = ({ Component, pageProps }: AppProps) => {
return (
-
-
-
+
+
+
+
+
);
};
diff --git a/pages/login/index.tsx b/pages/login/index.tsx
index d5e654c..7a61135 100644
--- a/pages/login/index.tsx
+++ b/pages/login/index.tsx
@@ -1,48 +1,49 @@
import { GetServerSideProps } from "next";
import React from "react";
-import Cookies from "cookies";
+import { setCookie } from 'nookies';
import getConfig from 'next/config';
-import { REDIRECT_URI } from "../../functions/constants";
-const { publicRuntimeConfig, serverRuntimeConfig } = getConfig() || {};
+export const getServerSideProps: GetServerSideProps = async (context) => {
+ const { query } = context;
+ const config = getConfig().publicRuntimeConfig;
+
+ if (query.code) {
+ try {
+ const response = await fetch('https://www.reddit.com/api/v1/access_token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ Authorization: `Basic ${Buffer.from(`${config.REDDIUM_CLIENT_ID}:${config.REDDIUM_CLIENT_SECRET}`).toString('base64')}`,
+ },
+ body: new URLSearchParams({
+ grant_type: 'authorization_code',
+ code: query.code as string,
+ redirect_uri: `${config.REDDIUM_DOMAIN}/login`,
+ }),
+ });
+
+ const data = await response.json();
+ if (data.access_token) {
+ setCookie(context, 'token', data.access_token, {
+ maxAge: 30 * 24 * 60 * 60, // 30 days
+ path: '/',
+ });
+ }
+ } catch (error) {
+ console.error("Error fetching access token:", error);
+ }
+ }
-export const getServerSideProps: GetServerSideProps = async ({
- req,
- res,
- query,
-}) => {
- const cookies = new Cookies(req, res);
- if (query.hasOwnProperty("code")) {
- const requestOptions = {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- Authorization: `Basic ${Buffer.from(
- `${publicRuntimeConfig.REDDIUM_CLIENT_ID}:${publicRuntimeConfig.REDDIUM_CLIENT_SECRET}`
- ).toString("base64")}`,
- },
- body: new URLSearchParams({
- grant_type: "authorization_code",
- code: query.code ? query.code.toString() : "",
- redirect_uri: REDIRECT_URI,
- }),
- };
- const resp = await (
- await fetch("https://www.reddit.com/api/v1/access_token", requestOptions)
- ).json();
- cookies.set("token", resp.access_token, { maxAge: 600000 });
- delete query.code;
- } else {
- console.log("No code received: ", query); // Debug output
- }
- res.statusCode = 302;
- res.setHeader("Location", `/`);
- res.end();
return {
- props: {},
+ redirect: {
+ destination: '/',
+ permanent: false,
+ },
};
};
-const LoginPage = () => ;
+const LoginPage = () => {
+ return Processing login...
;
+};
export default LoginPage;
diff --git a/pages/logout/index.tsx b/pages/logout/index.tsx
index 3608089..8d48629 100644
--- a/pages/logout/index.tsx
+++ b/pages/logout/index.tsx
@@ -1,18 +1,24 @@
-import { GetServerSideProps } from "next";
-import React from "react";
-import Cookies from "cookies";
+import { useEffect } from 'react';
+import { useRouter } from 'next/router';
+import { destroyCookie } from 'nookies';
+import { useAuth } from '../../contexts/AuthContext';
-export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
- const cookies = new Cookies(req, res);
- cookies.set("token", "");
- res.statusCode = 302;
- res.setHeader("Location", `/`);
- res.end();
- return {
- props: {}
- };
-};
+const LogoutPage = () => {
+ const router = useRouter();
+ const { setToken } = useAuth();
+
+ useEffect(() => {
+ const logout = async () => {
+ destroyCookie(null, 'token', { path: '/' });
+ setToken(null);
+ await new Promise(resolve => setTimeout(resolve, 100));
+ router.push('/');
+ };
-const LogoutPage = () => ;
+ logout();
+ }, [router, setToken]);
+
+ return Logging out...
;
+};
-export default LogoutPage;
+export default LogoutPage;
\ No newline at end of file
diff --git a/pages/me/index.tsx b/pages/me/index.tsx
index c63f44a..a0c9408 100644
--- a/pages/me/index.tsx
+++ b/pages/me/index.tsx
@@ -8,13 +8,12 @@ import {
import React, { useState } from "react";
import Image from 'next/image';
import TitleHead from "../../components/TitleHead";
-import { NavMenu } from "../../components/common";
+import Header from "../../components/common/Header"; // Import the Header component
import UserPost from "../../components/user-page/UserPost";
import UserComment from "../../components/user-page/UserComment";
import { getIntFromString } from "../../functions/common";
import { DOMAIN } from "../../functions/constants";
import Cookies from "cookies";
-import Logo from '../../components/common/Logo'; // Import the Logo component
export const getServerSideProps: GetServerSideProps = async ({
req,
@@ -51,10 +50,7 @@ export const getServerSideProps: GetServerSideProps = async ({
const MePage = ({ postData, userInfo, params }: any) => {
const [{ posts, after }, setPostData] = useState(postData);
- // const [selectedParams, setSelectedParams] = useState({
- // ...zipObject(POPULAR_PARAM_KEY, POPULAR_PARAM_DEFAULT),
- // ...params
- // });
+
const fetchMorePosts = async () => {
const next = await getUserPostsClient({
...params,
@@ -62,6 +58,7 @@ const MePage = ({ postData, userInfo, params }: any) => {
});
setPostData({ posts: [...posts, ...next.posts], after: next.after });
};
+
return (
@@ -73,16 +70,9 @@ const MePage = ({ postData, userInfo, params }: any) => {
/>
-
+
+
+
-