Skip to content
Open
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
34 changes: 26 additions & 8 deletions samples/animetrix-react-app/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Animetrix: Anime Streaming Site
![image](https://github.com/user-attachments/assets/ea9ef8c5-50c2-4264-a487-283d1f4de97f)

Welcome to Animetrix! This is a demo project designed to showcase how to simplify React app login with Asgardeo and the Asgardeo React SDK. Animetrix is an anime streaming site where users can browse and watch their favorite anime shows.

Expand Down Expand Up @@ -38,15 +38,16 @@ Check out the live demo of Animetrix [here](#).
### Prerequisites

Before you begin, ensure you have met the following requirements:

- Node.js and npm installed
- An Asgardeo account

### Installation

1. Clone the repository:
```sh
git clone https://github.com/your-username/animetrix.git
cd animetrix
git clone https://github.com/asgardeo-samples/asgardeo-react-workshop.git
cd samples/animetrix-react-app
```

2. Install the dependencies:
Expand All @@ -64,10 +65,27 @@ Before you begin, ensure you have met the following requirements:
2. **Environment Variables:**
- Create a `.env` file in the root directory and add the following variables:
```env
REACT_APP_ASGARDEO_CLIENT_ID=your-asgardeo-client-id
REACT_APP_ASGARDEO_BASE_URL=your-asgardeo-base-url
REACT_APP_ASGARDEO_REDIRECT_URI=http://localhost:3000
REACT_APP_GOOGLE_CLIENT_ID=your-google-client-id
# The base URL for the client application
# E.g., http://localhost:5173
VITE_REACT_APP_BASE_URL="<YOUR_APPLICATION_BASE_URL>"

# The client ID for the Asgardeo Single Page Application (SPA) application.
VITE_REACT_APP_CLIENT_ID="<YOUR_APPLICATION_CLIENT_ID>"

# The base URL for the Asgardeo organization's API
# E.g., https://api.asgardeo.io/t/your-organization-name
VITE_REACT_APP_ASGARDEO_BASE_URL="https://api.asgardeo.io/t/<YOUR_ORGANIZATION_NAME>"

# The sign-up URL for the Asgardeo organization
VITE_REACT_APP_ASGARDEO_SIGN_UP_URL="https://accounts.asgardeo.io/t/<YOUR_ORGANIZATION_NAME>/accountrecoveryendpoint/register.do?client_id="

# The application name for the Asgardeo application
VITE_REACT_APP_NAME="<YOUR_APPLICATION_NAME>"

# The organization name for the Asgardeo
VITE_REACT_APP_ORG_NAME="<YOUR_ORGANIZATION_NAME>"

VITE_REACT_APP_RESOURCE_SERVER_URLS=["https://bdc81b0c-bae6-43e8-b4aa-0702a82aee77-prod.e1-us-east-azure.choreoapis.dev/animetrix/movie-catalog-service/v1.0/categories"]
```

## Usage
Expand All @@ -79,7 +97,7 @@ Before you begin, ensure you have met the following requirements:
npm run dev
```

2. Open your browser and navigate to `http://localhost:5173`.
2. Open your browser and navigate to `http://localhost:5173/`.

3. You should see the Animetrix homepage. Click the Sign In button to authenticate using Asgardeo.

Expand Down
71 changes: 42 additions & 29 deletions samples/animetrix-react-app/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,48 @@
import "./App.css";
import { createBrowserRouter, RouterProvider, Navigate } from "react-router-dom";
import { LandingPage } from "./pages/LandingPage";
import { HomePage } from "./pages/HomePage";
import { AuthenticatedComponent } from "@asgardeo/auth-react";
import { InsightsPage } from "./pages/InsightsPage";

const routers = createBrowserRouter([
{
path: "/",
element: <LandingPage />,
},
{
path: "/home",
element: (
<AuthenticatedComponent fallback={<Navigate to='/' />}>
<HomePage />
</AuthenticatedComponent>
),
},
{
path: "/insights",
element: (
<AuthenticatedComponent fallback={<Navigate to='/' />}>
<InsightsPage />
</AuthenticatedComponent>
),
}
]);
import React, { useEffect, useMemo, useState } from "react";
import { createBrowserRouter } from "react-router-dom";
import { useAuthContext } from "@asgardeo/auth-react";
import AppRouter from "./components/AppRouter";
import { Loader } from "./components/Loader";
import { getAppRoutes } from "./configs/Routes";

function App() {
return <RouterProvider router={routers} />;
const { state, getDecodedIDToken } = useAuthContext();
const [ userRoles, setUserRoles ] = useState(undefined);

// Fetch the user roles and update the state
useEffect(() => {
const fetchUserRoles = async () => {
try {
const decodedIdToken = await getDecodedIDToken();

if (!decodedIdToken?.roles) {
return;
}

if (decodedIdToken?.roles && decodedIdToken?.roles?.length > 0) {
setUserRoles(decodedIdToken.roles);
}
} catch (error) {
console.error("Error occurred while decoding the ID token", error);
}
};

if (state?.isAuthenticated) {
fetchUserRoles();
}
}, [ getDecodedIDToken, state?.isAuthenticated ]);

// Memoize the router to avoid recreating it on every render
const router = useMemo(() => {
return createBrowserRouter(getAppRoutes(userRoles));
}, [ userRoles ]);

return (
<div>
{ router ? <AppRouter router={ router } /> : <Loader /> }
</div>
);
}

export default App;
8 changes: 8 additions & 0 deletions samples/animetrix-react-app/src/components/AppRouter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from "react";
import { RouterProvider } from "react-router-dom";

const AppRouter = ({ router }) => {
return <RouterProvider router={ router } />;
};

export default AppRouter;
14 changes: 11 additions & 3 deletions samples/animetrix-react-app/src/components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ import { useAuthContext } from "@asgardeo/auth-react";

export const Header = () => {

const { state, getDecodedIDToken, signOut } = useAuthContext();
const { state, getDecodedIDToken } = useAuthContext();

const [ isResourcesAllowed, setIsResourcesAllowed ] = useState();
const [ isResourcesAllowed, setIsResourcesAllowed ] = useState(false);

// Filter the display of Insights section based on the application role.
useEffect(() => {
getDecodedIDToken()
.then((decodedIdToken) => {
if (decodedIdToken?.application_roles === "Anime-App-Admin") {
if (!decodedIdToken?.roles) {
return;
}

if (decodedIdToken?.roles === "Anime-App-Admin") {
setIsResourcesAllowed(true);
}

if (decodedIdToken?.roles.includes("Anime-App-Admin")) {
setIsResourcesAllowed(true);
}
})
Expand Down
15 changes: 15 additions & 0 deletions samples/animetrix-react-app/src/components/ProtectedRoute.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react";
import { Navigate, Outlet } from "react-router-dom";

const ProtectedRoute = ({ allowedRoles, userRoles }) => {

// If the user doesn't have the required roles, redirect them to unauthorized page.
if (!allowedRoles?.every((role) => userRoles?.includes(role))) {
return <Navigate to='/unauthorized' />;
}

// Render the child routes (Outlet) if the user has the required roles
return <Outlet />;
};

export default ProtectedRoute;
38 changes: 38 additions & 0 deletions samples/animetrix-react-app/src/configs/Routes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Navigate } from "react-router-dom";
import { LandingPage } from "../pages/LandingPage";
import { Unauthorized } from "../pages/UnauthorizedPage";
import { HomePage } from "../pages/HomePage";
import { AuthenticatedComponent } from "@asgardeo/auth-react";
import ProtectedRoute from "../components/ProtectedRoute";
import { InsightsPage } from "../pages/InsightsPage";

// Static routes configuration function
export const getAppRoutes = (currentUserRoles) => [
{
path: "/",
element: <LandingPage />,
},
{
path: "/unauthorized",
element: <Unauthorized />,
},
{
path: "/home",
element: (
<AuthenticatedComponent fallback={<Navigate to='/' />}>
<HomePage />
</AuthenticatedComponent>
),
exact: true,
},
{
path: "/insights",
element: <ProtectedRoute allowedRoles={ ["Anime-App-Admin"] } userRoles={ currentUserRoles } />,
children: [
{
path: "",
element: <InsightsPage />,
},
],
},
];
9 changes: 8 additions & 1 deletion samples/animetrix-react-app/src/pages/LandingPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ export const LandingPage = () => {
const navigate = useNavigate();
const { on, state, signIn, signOut } = useAuthContext();

const handleSignupRedirect = () => {
const signupURL = `${import.meta.env.VITE_REACT_APP_ASGARDEO_SIGN_UP_URL}${import.meta.env.VITE_REACT_APP_CLIENT_ID}
&sp=${import.meta.env.VITE_REACT_APP_NAME}&redirect_url=${import.meta.env.VITE_REACT_APP_BASE_URL}`;

window.location.href = signupURL;
}

useEffect(() => {
on("sign-in", () => {
navigate("/home");
Expand All @@ -34,7 +41,7 @@ export const LandingPage = () => {
<button onClick={ () => signIn() }>Sign In</button>
)
}
<button onClick={ () => navigate("/signup") }>Create Account</button>
<button onClick={ handleSignupRedirect }>Create Account</button>
</div>
</div>
</div>
Expand Down
19 changes: 19 additions & 0 deletions samples/animetrix-react-app/src/pages/UnauthorizedPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import "../styles/UnauthorizedPage.css";
import { useNavigate } from "react-router-dom";

export const Unauthorized = () => {

const navigate = useNavigate();

return (
<div className="container-center unauthorized">
<div className="content-wrapper">
<h1>401</h1>
<h3>UNAUTHORIZED</h3>
<p>You are not authorized to access this page.</p>
<button className="go-back-btn" onClick={ () => navigate("/home") }>Go Back Home</button>
</div>
</div>
);
};
37 changes: 37 additions & 0 deletions samples/animetrix-react-app/src/styles/UnauthorizedPage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.container-center.unauthorized {
background: url("../assets/landing-bg.png") no-repeat right !important;
background-size: cover !important;

.content-wrapper {
padding: 5em;
border-radius: 10px;
/* border: 1px solid #8eb7c3; */
background: rgb(41 44 49 / 70%);

h1 {
font-size: 5rem;
margin-bottom: 0;
}

h3 {
margin-top: 0;
color: #a1a3a3;
}

p {
margin: 20px 0;
}

.go-back-btn {
padding: 10px 20px;
background-color: #8eb7c3;
color: white;
border: none;
width: 300px;
text-align: center;
font-size: medium !important;
font-weight: 600;
margin-top: 15px;
}
}
}