diff --git a/templates/sites/basic/src/pushkin/front-end/package.json b/templates/sites/basic/src/pushkin/front-end/package.json
index d2eac5834..747ad087e 100644
--- a/templates/sites/basic/src/pushkin/front-end/package.json
+++ b/templates/sites/basic/src/pushkin/front-end/package.json
@@ -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",
diff --git a/templates/sites/basic/src/pushkin/front-end/src/App.js b/templates/sites/basic/src/pushkin/front-end/src/App.js
index 960868436..67c51c03a 100644
--- a/templates/sites/basic/src/pushkin/front-end/src/App.js
+++ b/templates/sites/basic/src/pushkin/front-end/src/App.js
@@ -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";
@@ -49,6 +50,10 @@ function App() {
+
+
+
+
diff --git a/templates/sites/basic/src/pushkin/front-end/src/actions/AuthSync.jsx b/templates/sites/basic/src/pushkin/front-end/src/actions/AuthSync.jsx
new file mode 100644
index 000000000..68f9ba58c
--- /dev/null
+++ b/templates/sites/basic/src/pushkin/front-end/src/actions/AuthSync.jsx
@@ -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;
diff --git a/templates/sites/basic/src/pushkin/front-end/src/actions/userInfo.js b/templates/sites/basic/src/pushkin/front-end/src/actions/userInfo.js
index 6a5b72125..4652719d6 100644
--- a/templates/sites/basic/src/pushkin/front-end/src/actions/userInfo.js
+++ b/templates/sites/basic/src/pushkin/front-end/src/actions/userInfo.js
@@ -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,
};
}
diff --git a/templates/sites/basic/src/pushkin/front-end/src/components/Authentication/Login.js b/templates/sites/basic/src/pushkin/front-end/src/components/Authentication/Login.js
new file mode 100644
index 000000000..5dfe45a91
--- /dev/null
+++ b/templates/sites/basic/src/pushkin/front-end/src/components/Authentication/Login.js
@@ -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 ;
+};
+
+export default LoginButton;
diff --git a/templates/sites/basic/src/pushkin/front-end/src/components/Authentication/Logout.js b/templates/sites/basic/src/pushkin/front-end/src/components/Authentication/Logout.js
new file mode 100644
index 000000000..ca06d0184
--- /dev/null
+++ b/templates/sites/basic/src/pushkin/front-end/src/components/Authentication/Logout.js
@@ -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 (
+
+ );
+};
+
+export default LogoutButton;
diff --git a/templates/sites/basic/src/pushkin/front-end/src/components/Authentication/Profile.js b/templates/sites/basic/src/pushkin/front-end/src/components/Authentication/Profile.js
new file mode 100644
index 000000000..da24d86c4
--- /dev/null
+++ b/templates/sites/basic/src/pushkin/front-end/src/components/Authentication/Profile.js
@@ -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
Loading ...
;
+ }
+
+ return (
+ isAuthenticated && (
+
+
+
+ Username: {user.name}
+ Email: {user.email}
+ {authDomain && (
+
+
+
+ )}
+
+ )
+ );
+};
+
+export default Profile;
diff --git a/templates/sites/basic/src/pushkin/front-end/src/components/Layout/Header.js b/templates/sites/basic/src/pushkin/front-end/src/components/Layout/Header.js
index 4fb3c1b70..1ee5b49c4 100644
--- a/templates/sites/basic/src/pushkin/front-end/src/components/Layout/Header.js
+++ b/templates/sites/basic/src/pushkin/front-end/src/components/Layout/Header.js
@@ -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 {
@@ -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 (
{
About
+ {CONFIG.useAuth && isAuthenticated ?
+
+ My account
+
+ : null}
+ {CONFIG.useAuth && (
+
+ )}
);
diff --git a/templates/sites/basic/src/pushkin/front-end/src/config.js b/templates/sites/basic/src/pushkin/front-end/src/config.js
new file mode 100644
index 000000000..242b0b9d7
--- /dev/null
+++ b/templates/sites/basic/src/pushkin/front-end/src/config.js
@@ -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 || ''
+};
diff --git a/templates/sites/basic/src/pushkin/front-end/src/index.js b/templates/sites/basic/src/pushkin/front-end/src/index.js
index 8b76e1620..d028e2e58 100644
--- a/templates/sites/basic/src/pushkin/front-end/src/index.js
+++ b/templates/sites/basic/src/pushkin/front-end/src/index.js
@@ -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
@@ -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 (
+
+
+
+
+ );
+ }
+ return ;
+};
+
root.render(
-
+
);
diff --git a/templates/sites/basic/src/pushkin/front-end/src/reducers/userInfo.js b/templates/sites/basic/src/pushkin/front-end/src/reducers/userInfo.js
index 67928e8ab..27383ff60 100644
--- a/templates/sites/basic/src/pushkin/front-end/src/reducers/userInfo.js
+++ b/templates/sites/basic/src/pushkin/front-end/src/reducers/userInfo.js
@@ -1,7 +1,17 @@
-import { SET_USER_ID } from '../actions/userInfo';
+import {
+ GET_USER,
+ SET_USER_ID,
+ SET_AUTH0_USER,
+ CLEAR_AUTH_USER
+} from '../actions/userInfo';
const initialState = {
- userID: null
+ isSessionAuthenticated: false,
+ isAuthenticated: false, // Auth0
+ user: null,
+ userID: null,
+ token: null, // Auth0
+ authMode: null // 'legacy' or 'auth0'
};
export default function error(state = initialState, action) {
@@ -11,6 +21,32 @@ export default function error(state = initialState, action) {
...state,
userID: action.id
};
+
+ case GET_USER:
+ return {
+ ...state,
+ isSessionAuthenticated: action.isSessionAuthenticated,
+ user: action.user,
+ userID: action.userID || action.user?.id || state.userID,
+ authMode: 'legacy'
+ };
+
+ case SET_AUTH0_USER:
+ console.log("Reducer received SET_AUTH0_USER", action.payload);
+ return {
+ ...state,
+ isAuthenticated: true,
+ user: action.payload.user,
+ token: action.payload.token,
+ userID: action.payload.userID || action.payload.user?.sub ||null,
+ authMode: 'auth0'
+ };
+
+ case CLEAR_AUTH_USER:
+ return {
+ ...initialState
+ };
+
default:
return state;
}
diff --git a/templates/sites/basic/src/pushkin/front-end/src/sagas/userInfo.js b/templates/sites/basic/src/pushkin/front-end/src/sagas/userInfo.js
index 7497da38c..e12d9f18b 100644
--- a/templates/sites/basic/src/pushkin/front-end/src/sagas/userInfo.js
+++ b/templates/sites/basic/src/pushkin/front-end/src/sagas/userInfo.js
@@ -1,13 +1,27 @@
import { SET_USER_ID, GET_USER } from '../actions/userInfo';
-//import { put, takeEvery, takeLatest, all } from 'redux-saga/effects';
import { put, takeLatest } from 'redux-saga/effects';
import session from '../utils/session';
export function* getUserLogic(action) {
console.log('Saga2 initialized...');
- const id = action.isAuthenticated ? action.user : yield session.get();
- console.log(id);
- yield put({ type: SET_USER_ID, id: id });
+
+ try {
+ let userId;
+
+ if (action.isAuthenticated && action.user) {
+ userId = action.user.sub || action.user.email;
+ console.log("Using Auth0 user ID:", userId);
+ } else {
+ userId = session.get(); // no need for yield/call since it's sync
+ console.log("Using session-based user ID:", userId);
+ }
+
+ yield put({ type: SET_USER_ID, id: userId });
+
+ } catch (error) {
+ console.error('Error in getUserLogic saga:', error);
+ // Optional: dispatch an error action
+ }
}
export function* getUser() {