Skip to content
Open
3 changes: 3 additions & 0 deletions config/env/dev.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ SQL_PORT=5432
# SQL_SSL_MODE=require

LOGIN_REDIRECT_URL=
CORS_ALLOWED_ORIGINS=http://localhost:3000
# Domain used by Djoser for activation and password reset email links (should be the frontend URL)
FRONTEND_DOMAIN=localhost:3000
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
PINECONE_API_KEY=
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/api/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export const AUTH_ENDPOINTS = {
USER_ME: `${API_BASE}/auth/users/me/`,
RESET_PASSWORD: `${API_BASE}/auth/users/reset_password/`,
RESET_PASSWORD_CONFIRM: `${API_BASE}/auth/users/reset_password_confirm/`,
USERS_CREATE: `${API_BASE}/auth/users/`,
USERS_ACTIVATION: `${API_BASE}/auth/users/activation/`,
USERS_RESEND_ACTIVATION: `${API_BASE}/auth/users/resend_activation/`,
} as const;

/**
Expand Down
38 changes: 38 additions & 0 deletions frontend/src/components/ProtectedRoute/AdminRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ReactNode, useEffect } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../../services/actions/types';
import { AppDispatch, checkAuthenticated } from '../../services/actions/auth';
import Spinner from '../LoadingSpinner/LoadingSpinner';

interface AdminRouteProps {
children: ReactNode;
}

const AdminRoute = ({ children }: AdminRouteProps) => {
const location = useLocation();
const dispatch = useDispatch<AppDispatch>();
const { isAuthenticated, isSuperuser } = useSelector((state: RootState) => state.auth);

useEffect(() => {
if (isAuthenticated === null) {
dispatch(checkAuthenticated());
}
}, [dispatch, isAuthenticated]);

if (isAuthenticated === null) {
return <Spinner />;
}

if (!isAuthenticated) {
return <Navigate to="/login" replace state={{ from: location }} />;
}

if (!isSuperuser) {
return <Navigate to="/" replace />;
}

return children;
};

export default AdminRoute;
76 changes: 76 additions & 0 deletions frontend/src/pages/Activate/Activate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useEffect, useState } from "react";
import { useParams, Link } from "react-router-dom";
import { useDispatch } from "react-redux";
import { verify, AppDispatch } from "../../services/actions/auth";
import Layout from "../Layout/Layout";
import Spinner from "../../components/LoadingSpinner/LoadingSpinner";

const Activate = () => {
const { uid, token } = useParams<{ uid: string; token: string }>();
const dispatch = useDispatch<AppDispatch>();
const [status, setStatus] = useState<"loading" | "success" | "error">("loading");

useEffect(() => {
if (!uid || !token) {
setStatus("error");
return;
}

(async () => {
try {
await dispatch(verify(uid, token));
setStatus("success");
} catch {
setStatus("error");
}
})();
}, [dispatch, uid, token]);

if (status === "loading") {
return (
<Layout>
<Spinner />
</Layout>
);
}

if (status === "error") {
return (
<Layout>
<section className="mx-auto mt-24 w-[20rem] md:mt-48 md:w-[32rem] text-center">
<div className="mb-4 rounded-md bg-white px-3 pb-12 pt-6 shadow-md ring-1 md:px-12">
<h2 className="blue_gradient mb-4 font-satoshi text-3xl font-bold text-gray-600">
Activation failed
</h2>
<p className="text-gray-600 mb-6">
This activation link is invalid or has already been used. Please register again or request a new activation email.
</p>
<Link to="/register" className="btnBlue w-full text-lg text-center block">
Back to register
</Link>
</div>
</section>
</Layout>
);
}

return (
<Layout>
<section className="mx-auto mt-24 w-[20rem] md:mt-48 md:w-[32rem] text-center">
<div className="mb-4 rounded-md bg-white px-3 pb-12 pt-6 shadow-md ring-1 md:px-12">
<h2 className="blue_gradient mb-4 font-satoshi text-3xl font-bold text-gray-600">
Email verified
</h2>
<p className="text-gray-600 mb-6">
Your account has been activated. You can now log in.
</p>
<Link to="/login" className="btnBlue w-full text-lg text-center block">
Continue to log in
</Link>
</div>
</section>
</Layout>
);
};

export default Activate;
Loading
Loading