Skip to content

Commit

Permalink
refactor(app): removed auth context flow in favor of redux;
Browse files Browse the repository at this point in the history
- fixed avatar placeholders;
- adjusted profile requests not to fetch own data
- overall code cleanup
  • Loading branch information
omelchenkoy committed Feb 21, 2025
1 parent 060f80a commit dbbc01f
Show file tree
Hide file tree
Showing 25 changed files with 289 additions and 263 deletions.
41 changes: 40 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import React, { useEffect } from 'react';
import { Route, Routes } from 'react-router-dom';
import { Toaster } from 'react-hot-toast';
import { useDispatch, useSelector } from 'react-redux';

import { fetchUserData, setToken, updateUser } from './lib/auth/authSlice';

import Header from './components/layouts/Header/Header';
import Footer from './components/layouts/Footer/Footer';
import HomePage from './pages/HomePage';
Expand All @@ -8,9 +13,11 @@ import Profile from './pages/Profile';
import FAQPage from './pages/FAQ';
import ProtectedRoute from './components/ProtectedRoute';
import './App.css';
import { Toaster } from 'react-hot-toast';

function App() {
const dispatch = useDispatch();
const { token } = useSelector((state) => state.auth);

useEffect(() => {
const env = process.env.NODE_ENV;
if (env === 'development') {
Expand All @@ -22,6 +29,38 @@ function App() {
}
}, []);

// Fetch user data whenever the token changes
useEffect(() => {
if (token) {
dispatch(fetchUserData(token));
} else {
dispatch(updateUser(null));
}
}, [token, dispatch]);

// Initialize token and user data from localStorage or URL
useEffect(() => {
try {
const storedToken = localStorage.getItem('token');
if (storedToken) {
dispatch(setToken(storedToken));
}

const params = new URLSearchParams(window.location.search);
const urlToken = params.get('token');

if (urlToken) {
localStorage.setItem('token', urlToken);
dispatch(setToken(urlToken));

params.delete('token');
window.history.replaceState({}, '', window.location.pathname);
}
} catch (error) {
console.error('Error accessing localStorage:', error);
}
}, [dispatch]);

return (
<div className="app-wrapper">
<Header />
Expand Down
76 changes: 39 additions & 37 deletions src/PageComponents/SettingsPage/SuggestionsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import useClickOutside from '../../lib/hooks/useClickOutside';
import { updateWalletAddress } from '../../lib/utils/apiHandlers';
import { domains } from '../../lib/utils/constants/settings.constants';
import React, { ReactNode, useEffect, useRef, useState } from 'react';
import { useAuth } from '../../lib/contexts/AuthContext';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';

// interface ISuggestionsDropdown {
// children?: ReactNode;
Expand All @@ -12,10 +11,10 @@ const Suggestions = () => {
const inputRef = useRef<any>(null);
const [walletAddress, setWalletAddress] = useState('');
const [suggestions, setSuggestions] = useState<string[]>([]);
const [showError, setShowError] = useState(false);
// const [showError, setShowError] = useState(false);
const [showSuggestions, setShowSuggestions] = useState(false);

const { user } = useAuth();
const { user } = useSelector((state: any) => state.auth);

useEffect(() => {
if (user?.wallet_address) {
Expand Down Expand Up @@ -47,7 +46,7 @@ const Suggestions = () => {
};

const handleSuggestionClick = async (domain: string) => {
const baseAddress = walletAddress.split('@')[0];
// const baseAddress = walletAddress.split('@')[0];
inputRef.current?.focus();
const atIndex = walletAddress.lastIndexOf('@');
const newAddress = walletAddress.slice(0, atIndex + 1) + domain;
Expand All @@ -73,7 +72,7 @@ const Suggestions = () => {
placeholder="satoshi@example.org"
className={'walletInput w-full'}
/>
{showError && <ErrorMessage />}
{/* {showError && <ErrorMessage />} */}

{/* ========= CLOSE BUTTON ========= */}
{walletAddress && (
Expand All @@ -97,14 +96,14 @@ const Suggestions = () => {
);
};

const ErrorMessage = () => {
return (
<div className={'warningMessage'}>
<span className={'warningIcon'}></span>
Please add a wallet address
</div>
);
};
// const ErrorMessage = () => {
// return (
// <div className={'warningMessage'}>
// <span className={'warningIcon'}>⚠</span>
// Please add a wallet address
// </div>
// );
// };

interface ISuggestionsDropdown {
shouldShow: boolean;
Expand All @@ -120,23 +119,7 @@ const SuggestionsDropdown: React.FC<ISuggestionsDropdown> = ({
}) => {
const [currIdx, setCurrIdx] = useState(0);

const handleKeydown = (event: any) => {
if (event.key === 'ArrowUp') {
return upHandler();
}

if (event.key === 'ArrowDown') {
return downHandler();
}

if (event.key === 'Enter') {
event.preventDefault();
enterHandler();
return;
}
};

const upHandler = () => {
const upHandler = useCallback(() => {
if (currIdx === 0) return;
const selectedIdx =
(currIdx + filteredElements.length - 1) % filteredElements.length;
Expand All @@ -146,9 +129,9 @@ const SuggestionsDropdown: React.FC<ISuggestionsDropdown> = ({
block: 'nearest',
inline: 'center',
});
};
}, [currIdx, filteredElements.length]);

const downHandler = () => {
const downHandler = useCallback(() => {
if (currIdx === filteredElements.length - 1) return;
const selectedIdx = (currIdx + 1) % filteredElements.length;
setCurrIdx(selectedIdx);
Expand All @@ -157,12 +140,31 @@ const SuggestionsDropdown: React.FC<ISuggestionsDropdown> = ({
block: 'nearest',
inline: 'center',
});
};
}, [currIdx, filteredElements.length]);

const enterHandler = () => {
const enterHandler = useCallback(() => {
if (filteredElements.length === 0) return;
onClick(filteredElements[currIdx]);
};
}, [currIdx, filteredElements, onClick]);

const handleKeydown = useCallback(
(event: KeyboardEvent) => {
if (event.key === 'ArrowUp') {
return upHandler();
}

if (event.key === 'ArrowDown') {
return downHandler();
}

if (event.key === 'Enter') {
event.preventDefault();
enterHandler();
return;
}
},
[downHandler, enterHandler, upHandler]
);

useEffect(() => {
setCurrIdx(0);
Expand Down
2 changes: 1 addition & 1 deletion src/components/DynamicHeightContainer.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactNode, useEffect, useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { cn } from '../lib/utils/cn';

const DynamicHeightContainer = ({
Expand Down
7 changes: 3 additions & 4 deletions src/components/ProtectedRoute.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// src/components/ProtectedRoute.js
import React from 'react';
import { Navigate } from 'react-router-dom';
import { useAuth } from '../lib/contexts/AuthContext';
import { useSelector } from 'react-redux';

const ProtectedRoute = ({ children }) => {
const { token } = useAuth()

const { token } = useSelector((state) => state.auth);

// return children
return token ? children : <Navigate to="/" />;
};

export default ProtectedRoute;
export default ProtectedRoute;
17 changes: 4 additions & 13 deletions src/components/layouts/Header/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { useAuth } from '../../../lib/contexts/AuthContext';
import { API_ENDPOINT } from '../../../config';
import { HeaderZapLogo } from '../../../lib/utils/icons';
import LogoutButton from './components/LogoutButton';
import useLogout from './hooks';
import './navbar.scss';
import { LogOut } from 'lucide-react';
import { useSelector } from 'react-redux';

const mobile_menu_id = 'mobile-menu';

Expand All @@ -32,7 +32,7 @@ const navLinks = [
];

const NavLinks = ({ username, closeMenu }) => {
const { token } = useAuth();
const { token } = useSelector((state) => state.auth);
const handleLogout = useLogout();
return (
<div className="desktop-links-container ">
Expand Down Expand Up @@ -76,7 +76,7 @@ const NavLinks = ({ username, closeMenu }) => {
};

const UserSection = ({ userAvatar, handleTwitterLogin, username }) => {
const { token } = useAuth();
const { token } = useSelector((state) => state.auth);
return token ? (
<div className="flex gap-3 items-center">
<Link to={`/${username}`}>
Expand All @@ -102,7 +102,7 @@ const UserSection = ({ userAvatar, handleTwitterLogin, username }) => {

const Header = () => {
const [userAvatar, setUserAvatar] = useState(null);
const { user } = useAuth();
const { user } = useSelector((state) => state.auth);

const [isMenuOpen, setIsMenuOpen] = useState(false);

Expand Down Expand Up @@ -137,15 +137,6 @@ const Header = () => {
<nav className="zz-navbar">
<div className="zz-navbar-container">
<div className="zz-navbar-content">
{/* Logo */}
{/* <div className="logo">
<div className="logo-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<span className="logo-text">ZapZap</span>
</div> */}
<Link to="/" className="navDesktopLogoLink">
<HeaderZapLogo />
</Link>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/SvgIcons/DefaultProfileAvatar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const DefaultProfileAvatar = () => {
width="32"
height="32"
fill="currentColor"
class="bi bi-person"
className="bi bi-person"
viewBox="0 0 16 16"
>
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10s-3.516.68-4.168 1.332c-.678.678-.83 1.418-.832 1.664z" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/SvgIcons/EditIcon.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const EditIcon = () => (
width="16"
height="16"
fill="currentColor"
class="bi bi-pencil-fill"
className="bi bi-pencil-fill"
viewBox="0 0 16 16"
>
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.5.5 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11z" />
Expand Down
5 changes: 1 addition & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@ import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
import { AuthProvider } from './lib/contexts/AuthContext';
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<BrowserRouter>
<AuthProvider>
<App />
</AuthProvider>
<App />
</BrowserRouter>
</Provider>
);
77 changes: 77 additions & 0 deletions src/lib/auth/authSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { API_ENDPOINT } from '../../config';

// Async thunk for fetching user data
export const fetchUserData = createAsyncThunk(
'user/fetchUserData',
async (token, { rejectWithValue }) => {
try {
const response = await fetch(`${API_ENDPOINT}/users/me`, {
headers: {
Authorization: `Bearer ${token}`,
},
});

if (!response.ok) {
throw new Error(
`Failed to fetch user data. Status: ${response.status}`
);
}

const user = await response.json();
return user;
} catch (error) {
return rejectWithValue(error.message);
}
}
);

const userSlice = createSlice({
name: 'auth',
initialState: {
user: JSON.parse(localStorage.getItem('user')) || null,
token: localStorage.getItem('token') || null,
isLoading: false,
error: null,
},
reducers: {
setToken: (state, action) => {
state.token = action.payload;
localStorage.setItem('token', action.payload);
},
clearToken: (state) => {
state.token = null;
localStorage.removeItem('token');
},
updateUser: (state, action) => {
state.user = action.payload;
if (action.payload) {
localStorage.setItem('user', JSON.stringify(action.payload));
} else {
localStorage.removeItem('user');
}
},
},
extraReducers: (builder) => {
builder
.addCase(fetchUserData.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(fetchUserData.fulfilled, (state, action) => {
state.isLoading = false;
state.user = action.payload;
localStorage.setItem('user', JSON.stringify(action.payload));
})
.addCase(fetchUserData.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload;
state.user = null;
localStorage.removeItem('user');
});
},
});

export const { setToken, clearToken, updateUser } = userSlice.actions;

export default userSlice.reducer;
Loading

0 comments on commit dbbc01f

Please sign in to comment.