Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions templates/sites/basic/src/pushkin/front-end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@auth0/auth0-react": "^2.2.4",
"aphrodite": "^2.4.0",
"axios": "^1.7.7",
"bootstrap": "^4.3.1",
Expand Down
5 changes: 5 additions & 0 deletions templates/sites/basic/src/pushkin/front-end/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Header from "./components/Layout/Header";
import Footer from "./components/Layout/Footer";
import TakeQuiz from "./components/Quizzes/TakeQuiz";
import Results from "./components/Quizzes/Results";
import Profile from "./components/Authentication/Profile";

//import pages
import HomePage from "./pages/Home";
Expand Down Expand Up @@ -49,6 +50,10 @@ function App() {
<FeedbackPage />
</Route>

<Route path="/profile">
<Profile />
</Route>

<Route path="/quizzes/:quizName/results">
<Results />
</Route>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect } from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { useDispatch, useSelector } from "react-redux";
import { setAuth0User, clearAuthUser } from "./userInfo";

function AuthSync() {
const { isAuthenticated, user, isLoading, getAccessTokenSilently } = useAuth0();
const dispatch = useDispatch();
const sessionUserID = useSelector((state) => state.userInfo.userID); // get current Redux userID

useEffect(() => {
if (isLoading) return; // wait until Auth0 finishes

const syncAuth = async () => {
if (isAuthenticated && user) {
let token = null;
try {
token = await getAccessTokenSilently();
} catch (err) {
console.warn("No token obtained", err);
}

console.log("Dispatching SET_AUTH0_USER with user:", user, "token:", token);
dispatch(setAuth0User(user, token));
} else {
// Only clear if there is no session-based userID
if (!sessionUserID) {
dispatch(clearAuthUser());
} else {
console.log("Preserving session userID:", sessionUserID);
}
}
};

syncAuth();
}, [isAuthenticated, user, isLoading, dispatch, getAccessTokenSilently, sessionUserID]);

return null;
}

export default AuthSync;
38 changes: 28 additions & 10 deletions templates/sites/basic/src/pushkin/front-end/src/actions/userInfo.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
export const GET_USER = 'GET_USER';
export const SET_USER_ID = 'SET_USER_ID';
export const SET_AUTH0_USER = 'SET_AUTH0_USER';
export const CLEAR_AUTH_USER = 'CLEAR_AUTH_USER';
export const GET_SESSION_USER = 'GET_SESSION_USER';
export const GET_USER = 'GET_SESSION_USER';

export function getSessionUser() {
// For session or Auth0 user
export function getUser(isSessionAuthenticated, user) {
return {
type: GET_SESSION_USER
type: GET_USER,
isSessionAuthenticated,
user,
userID: user?.id || null,
};
}

export function getUser(isAuthenticated, user) {
// For setting userID after saga logic
export function setUserID(id) {
return {
type: GET_USER,
isAuthenticated: isAuthenticated,
user: user
type: SET_USER_ID,
id,
};
}

export function setUserID(userID) {
// For Auth0 login
export function setAuth0User(user, token) {
return {
type: SET_USER_ID,
id: userID
type: SET_AUTH0_USER,
payload: {
user,
token,
userID: user?.sub || null,
},
};
}

// For logout
export function clearAuthUser() {
return {
type: CLEAR_AUTH_USER,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useAuth0 } from "@auth0/auth0-react";
import React from "react";
import { Button } from "react-bootstrap";


const LoginButton = () => {
const { loginWithRedirect } = useAuth0();

return <Button onClick={() => loginWithRedirect()}>Log In</Button>;
};

export default LoginButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useAuth0 } from "@auth0/auth0-react";
import React from "react";
import { Button } from "react-bootstrap";

const LogoutButton = () => {
const { logout } = useAuth0();

return (
<Button onClick={() => logout({ returnTo: window.location.origin })}>
Log Out
</Button>
);
};

export default LogoutButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useAuth0 } from "@auth0/auth0-react";
import React from "react";
import { Container, Button } from 'react-bootstrap';
import { pushkinConfig } from '../../.pushkin';

const Profile = () => {
const { user, isAuthenticated, isLoading } = useAuth0();
const authDomain = pushkinConfig?.addons?.authDomain || '';

if (isLoading) {
return <div>Loading ...</div>;
}

return (
isAuthenticated && (
<Container className="text-center p-3">
<img src={user.picture} alt={user.name} />
<br />
<p><h2>Username: {user.name}</h2></p>
<p>Email: {user.email}</p>
{authDomain && (
<p><a href={`https://${authDomain}/user/settings`} target="_blank" rel="noopener noreferrer">
<Button variant="outline-primary" className="mt-3">
Edit profile
</Button>
</a></p>
)}
</Container>
)
);
};

export default Profile;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { Nav, Navbar, Button, Image } from 'react-bootstrap';

//other
import { CONFIG } from '../../config';
import LoginButton from '../Authentication/Login';
import LogoutButton from '../Authentication/Logout';
import { useAuth0 } from "@auth0/auth0-react";

const mapStateToProps = (state) => {
return {
Expand All @@ -20,12 +23,29 @@ const mapStateToProps = (state) => {
};

const Header = (props) => {
const isAuthenticated = false;
const user = null;
// Use Auth0 if enabled, otherwise fall back to session-based auth
let isAuthenticated = false;
let user = null;
let isLoading = false;

if (CONFIG.useAuth && CONFIG.authDomain && CONFIG.authClientID) {
const auth0Data = useAuth0();
isAuthenticated = auth0Data.isAuthenticated;
user = auth0Data.user;
isLoading = auth0Data.isLoading;
}

useEffect(() => {
props.dispatch(getUser(isAuthenticated, user));
}, [isAuthenticated]);
// Only dispatch getUser for legacy session-based auth
// Auth0 users are handled by AuthSync component
if (!isLoading && !(CONFIG.useAuth && CONFIG.authDomain && CONFIG.authClientID)) {
props.dispatch(getUser(isAuthenticated, user));
}
}, [isAuthenticated, isLoading, user, props]);

if (isLoading) {
return null; // or a loading spinner if preferred
}

return (
<Navbar
Expand Down Expand Up @@ -56,7 +76,19 @@ const Header = (props) => {
<LinkContainer to="/about">
<Nav.Link>About</Nav.Link>
</LinkContainer>
{CONFIG.useAuth && isAuthenticated ?
<LinkContainer to="/profile">
<Nav.Link>My account</Nav.Link>
</LinkContainer>
: null}
</Nav>
{CONFIG.useAuth && (
<Nav className="ml-auto">
<Nav.Item>
{isAuthenticated ? <LogoutButton /> : <LoginButton />}
</Nav.Item>
</Nav>
)}
</Navbar.Collapse>
</Navbar>
);
Expand Down
60 changes: 60 additions & 0 deletions templates/sites/basic/src/pushkin/front-end/src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Global configuration file

//import util from 'util';
//import fs from 'fs';
//import jsYaml from 'js-yaml';
import { pushkinConfig } from './.pushkin.js'
import { debug, codespaces, codespaceName } from './.env.js'

// Front-end configuration file

// --- API ---
let apiEndpoint;
let frontEndURL;
let logoutURL;
if (debug) {
// Debug / Test
let rootDomain;
if (codespaces) {
// Make sure to point to the Codespaces URL if testing in a codespace
rootDomain = 'https://' + codespaceName + '-80.app.github.dev';
} else {
rootDomain = 'http://localhost';
}
apiEndpoint = rootDomain + '/api';
frontEndURL = rootDomain + '/callback';
logoutURL = rootDomain;
} else {
// Production
const rootDomain = pushkinConfig.info.rootDomain;
if (pushkinConfig.apiEndpoint) {
//What's in the YAML can override default
apiEndpoint = pushkinConfig.apiEndpoint
} else{
apiEndpoint = 'https://api.' + rootDomain;
}
frontEndURL = 'https://' + rootDomain + '/callback';
logoutURL = 'https://' + rootDomain;
}

export const CONFIG = {
production: !debug,
debug: debug,

apiEndpoint: apiEndpoint,
frontEndURL: frontEndURL,
logoutURL: logoutURL,

useForum: pushkinConfig.addons.useForum,
useAuth: pushkinConfig.addons.useAuth,

whoAmI: pushkinConfig.info.whoAmI,
hashtags: pushkinConfig.info.hashtags,
email: pushkinConfig.info.email,
shortName: pushkinConfig.info.shortName,
salt: pushkinConfig.salt,

fc: pushkinConfig.fc,
authClientID: pushkinConfig.addons?.authClientID || '',
authDomain: pushkinConfig.addons?.authDomain || ''
};
27 changes: 26 additions & 1 deletion templates/sites/basic/src/pushkin/front-end/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers/index';
import rootSaga from './sagas/index';

// Auth0 integration
import { Auth0Provider } from '@auth0/auth0-react';
import AuthSync from "./actions/AuthSync";

// //Stylin
// import './index.css'; // drop??
// import './styles/styles.less'; //Bootstrap styles
Expand Down Expand Up @@ -50,10 +54,31 @@ const onRedirectCallback = (appState) => {
//Renders the front end
const root = createRoot(document.getElementById('root'));

// Conditionally wrap with Auth0Provider if useAuth is enabled
const AppWithAuth = () => {
if (CONFIG.useAuth && CONFIG.authDomain && CONFIG.authClientID) {
return (
<Auth0Provider
domain={CONFIG.authDomain}
clientId={CONFIG.authClientID}
authorizationParams={{
redirect_uri: window.location.origin
}}
useRefreshTokens={true}
cacheLocation="localstorage"
>
<AuthSync />
<App />
</Auth0Provider>
);
}
return <App />;
};

root.render(
<Provider store={store}>
<Router>
<App />
<AppWithAuth />
</Router>
</Provider>
);
Loading
Loading