mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Update auth flow and added localisation
This commit is contained in:
@@ -3,6 +3,11 @@ NEXT_PUBLIC_AGPT_SERVER_URL=http://localhost:8006/api
|
||||
NEXT_PUBLIC_AGPT_WS_SERVER_URL=ws://localhost:8001/ws
|
||||
NEXT_PUBLIC_AGPT_MARKETPLACE_URL=http://localhost:8015/api/v1/market
|
||||
|
||||
## Locale settings
|
||||
|
||||
NEXT_PUBLIC_DEFAULT_LOCALE=en
|
||||
NEXT_PUBLIC_LOCALES=en,es
|
||||
|
||||
## Supabase credentials
|
||||
|
||||
NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
|
||||
|
||||
@@ -14,15 +14,15 @@ const nextConfig = {
|
||||
images: {
|
||||
domains: ["images.unsplash.com"],
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/monitor", // FIXME: Remove after 2024-09-01
|
||||
destination: "/",
|
||||
permanent: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
// async redirects() {
|
||||
// return [
|
||||
// {
|
||||
// source: "/monitor", // FIXME: Remove after 2024-09-01
|
||||
// destination: "/",
|
||||
// permanent: false,
|
||||
// },
|
||||
// ];
|
||||
// },
|
||||
// TODO: Re-enable TypeScript checks once current issues are resolved
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"defaults"
|
||||
],
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "^0.5.5",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@next/third-parties": "^14.2.5",
|
||||
"@radix-ui/react-avatar": "^1.1.0",
|
||||
@@ -56,6 +57,7 @@
|
||||
"framer-motion": "^11.11.9",
|
||||
"lucide-react": "^0.407.0",
|
||||
"moment": "^2.30.1",
|
||||
"negotiator": "^1.0.0",
|
||||
"next": "^14.2.13",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18",
|
||||
@@ -84,6 +86,7 @@
|
||||
"@storybook/react": "^8.3.5",
|
||||
"@storybook/test": "^8.3.5",
|
||||
"@storybook/test-runner": "^0.19.1",
|
||||
"@types/negotiator": "^0.6.3",
|
||||
"@types/node": "^22.7.3",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
|
||||
9
autogpt_platform/frontend/src/app/[lang]/dictionaries.ts
Normal file
9
autogpt_platform/frontend/src/app/[lang]/dictionaries.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import "server-only";
|
||||
|
||||
const dictionaries: Record<string, () => Promise<any>> = {
|
||||
en: () => import("./dictionaries/en.json").then((module) => module.default),
|
||||
es: () => import("./dictionaries/es.json").then((module) => module.default),
|
||||
};
|
||||
|
||||
export const getDictionary = async (locale: string): Promise<any> =>
|
||||
dictionaries[locale]();
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"auth": {
|
||||
"signIn": "Sign In",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"submit": "Submit",
|
||||
"error": "Invalid login credentials"
|
||||
},
|
||||
"dashboard": {
|
||||
"welcome": "Welcome to your dashboard",
|
||||
"stats": "Your Stats",
|
||||
"recentActivity": "Recent Activity"
|
||||
},
|
||||
"admin": {
|
||||
"title": "Admin Dashboard",
|
||||
"users": "Users Management",
|
||||
"settings": "System Settings"
|
||||
},
|
||||
"home": {
|
||||
"welcome": "Welcome to the Home Page"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"auth": {
|
||||
"signIn": "Iniciar Sesión",
|
||||
"email": "Correo electrónico",
|
||||
"password": "Contraseña",
|
||||
"submit": "Enviar",
|
||||
"error": "Credenciales inválidas"
|
||||
},
|
||||
"dashboard": {
|
||||
"welcome": "Bienvenido a tu panel",
|
||||
"stats": "Tus Estadísticas",
|
||||
"recentActivity": "Actividad Reciente"
|
||||
},
|
||||
"admin": {
|
||||
"title": "Panel de Administración",
|
||||
"users": "Gestión de Usuarios",
|
||||
"settings": "Configuración del Sistema"
|
||||
},
|
||||
"home": {
|
||||
"welcome": "Bienvenido a la Página de Inicio"
|
||||
}
|
||||
}
|
||||
10
autogpt_platform/frontend/src/app/[lang]/page.tsx
Normal file
10
autogpt_platform/frontend/src/app/[lang]/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { getDictionary } from "./dictionaries";
|
||||
|
||||
export default async function Page({
|
||||
params: { lang },
|
||||
}: {
|
||||
params: { lang: string };
|
||||
}) {
|
||||
const dict = await getDictionary(lang); // en
|
||||
return <h1>{dict.home.welcome}</h1>; // Add to Cart
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { Button } from "./Button";
|
||||
import Link from "next/link";
|
||||
import { StarRatingIcons } from "../ui/icons";
|
||||
import { StarRatingIcons } from "@/components/ui/icons";
|
||||
interface AgentInfoProps {
|
||||
onRunAgent: () => void;
|
||||
name: string;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { Button } from "./Button";
|
||||
import { IconStarFilled, IconEdit } from "../ui/icons";
|
||||
import { Card } from "../ui/card";
|
||||
import { IconStarFilled, IconEdit } from "@/components/ui/icons";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { AgentTableRowProps } from "./AgentTableRow";
|
||||
|
||||
export const AgentTableCard: React.FC<AgentTableRowProps> = ({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { Button } from "./Button";
|
||||
import { IconStarFilled, IconEdit } from "../ui/icons";
|
||||
import { IconStarFilled, IconEdit } from "@/components/ui/icons";
|
||||
|
||||
export interface AgentTableRowProps {
|
||||
agentName: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { StarRatingIcons } from "../ui/icons";
|
||||
import { StarRatingIcons } from "@/components/ui/icons";
|
||||
interface FeaturedStoreCardProps {
|
||||
agentName: string;
|
||||
agentImage: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import Link from "next/link";
|
||||
import { ProfilePopoutMenu } from "./ProfilePopoutMenu";
|
||||
import { IconType, IconLogIn } from "../ui/icons";
|
||||
import { IconType, IconLogIn } from "@/components/ui/icons";
|
||||
import { MobileNavBar } from "./MobileNavBar";
|
||||
import { Button } from "./Button";
|
||||
interface NavLink {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import Image from "next/image";
|
||||
import { StarRatingIcons } from "../ui/icons";
|
||||
import { StarRatingIcons } from "@/components/ui/icons";
|
||||
interface StoreCardProps {
|
||||
agentName: string;
|
||||
agentImage: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { StoreCard } from "../StoreCard";
|
||||
import { StoreCard } from "@/components/agptui/StoreCard";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "../../ui/avatar";
|
||||
import { getIconForSocial, StarRatingIcons } from "../../ui/icons";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { getIconForSocial, StarRatingIcons } from "@/components/ui/icons";
|
||||
|
||||
interface CreatorDetailsProps {
|
||||
name: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { CreatorCard } from "../CreatorCard";
|
||||
import { CreatorCard } from "@/components/agptui/CreatorCard";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { FeaturedStoreCard } from "../FeaturedStoreCard";
|
||||
import { FeaturedStoreCard } from "@/components/agptui/FeaturedStoreCard";
|
||||
|
||||
interface FeaturedAgent {
|
||||
agentName: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { SearchBar } from "../SearchBar";
|
||||
import { FilterChips } from "../FilterChips";
|
||||
import { SearchBar } from "@/components/agptui/SearchBar";
|
||||
import { FilterChips } from "@/components/agptui/FilterChips";
|
||||
|
||||
interface HeroSectionProps {
|
||||
onSearch: (query: string) => void;
|
||||
|
||||
@@ -15,6 +15,7 @@ const meta = {
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
isLoggedIn: { control: "boolean" },
|
||||
userName: { control: "text" },
|
||||
userEmail: { control: "text" },
|
||||
navLinks: { control: "object" },
|
||||
@@ -155,6 +156,7 @@ const mockSimilarAgents = [
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
isLoggedIn: true,
|
||||
userName: "John Doe",
|
||||
userEmail: "john.doe@example.com",
|
||||
navLinks: mockNavLinks,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import * as React from "react";
|
||||
import { Navbar } from "../Navbar";
|
||||
import { AgentInfo } from "../AgentInfo";
|
||||
import { AgentImages } from "../AgentImages";
|
||||
import { BecomeACreator } from "../BecomeACreator";
|
||||
import { AgentsSection } from "../composite/AgentsSection";
|
||||
import { Separator } from "../../ui/separator";
|
||||
import { IconType } from "../../ui/icons";
|
||||
import { BreadCrumbs } from "../BreadCrumbs";
|
||||
import { Navbar } from "@/components/agptui/Navbar";
|
||||
import { AgentInfo } from "@/components/agptui/AgentInfo";
|
||||
import { AgentImages } from "@/components/agptui/AgentImages";
|
||||
import { BecomeACreator } from "@/components/agptui/BecomeACreator";
|
||||
import { AgentsSection } from "@/components/agptui/composite/AgentsSection";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { IconType } from "@/components/ui/icons";
|
||||
import { BreadCrumbs } from "@/components/agptui/BreadCrumbs";
|
||||
|
||||
interface AgentPageProps {
|
||||
isLoggedIn: boolean;
|
||||
userName: string;
|
||||
userEmail: string;
|
||||
navLinks: { name: string; href: string }[];
|
||||
@@ -52,6 +53,7 @@ interface AgentPageProps {
|
||||
}
|
||||
|
||||
export const AgentPage: React.FC<AgentPageProps> = ({
|
||||
isLoggedIn,
|
||||
userName,
|
||||
userEmail,
|
||||
navLinks,
|
||||
@@ -85,6 +87,7 @@ export const AgentPage: React.FC<AgentPageProps> = ({
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<Navbar
|
||||
isLoggedIn={isLoggedIn}
|
||||
userName={userName}
|
||||
userEmail={userEmail}
|
||||
links={navLinks}
|
||||
|
||||
@@ -13,6 +13,9 @@ const meta: Meta<typeof CreatorDashboardPage> = {
|
||||
},
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
isLoggedIn: { control: "boolean" },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -97,6 +100,7 @@ const sampleAgents = [
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
isLoggedIn: true,
|
||||
userName: "John Doe",
|
||||
userEmail: "john.doe@example.com",
|
||||
navLinks: sampleNavLinks,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import * as React from "react";
|
||||
import { Navbar } from "../Navbar";
|
||||
import { Sidebar } from "../Sidebar";
|
||||
import { AgentTable } from "../AgentTable";
|
||||
import { Button } from "../Button";
|
||||
import { Navbar } from "@/components/agptui/Navbar";
|
||||
import { Sidebar } from "@/components/agptui/Sidebar";
|
||||
import { AgentTable } from "@/components/agptui/AgentTable";
|
||||
import { Button } from "@/components/agptui/Button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { IconType } from "../../ui/icons";
|
||||
import { IconType } from "@/components/ui/icons";
|
||||
|
||||
interface CreatorDashboardPageProps {
|
||||
isLoggedIn: boolean;
|
||||
userName: string;
|
||||
userEmail: string;
|
||||
navLinks: { name: string; href: string }[];
|
||||
@@ -39,6 +40,7 @@ interface CreatorDashboardPageProps {
|
||||
}
|
||||
|
||||
export const CreatorDashboardPage: React.FC<CreatorDashboardPageProps> = ({
|
||||
isLoggedIn,
|
||||
userName,
|
||||
userEmail,
|
||||
navLinks,
|
||||
@@ -50,6 +52,7 @@ export const CreatorDashboardPage: React.FC<CreatorDashboardPageProps> = ({
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-[1440px] bg-white">
|
||||
<Navbar
|
||||
isLoggedIn={isLoggedIn}
|
||||
userName={userName}
|
||||
userEmail={userEmail}
|
||||
links={navLinks}
|
||||
|
||||
@@ -15,6 +15,7 @@ const meta = {
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
isLoggedIn: { control: "boolean" },
|
||||
userName: { control: "text" },
|
||||
userEmail: { control: "text" },
|
||||
navLinks: { control: "object" },
|
||||
@@ -76,6 +77,7 @@ const mockCreatorInfo = {
|
||||
avgRating: 4.8,
|
||||
agentCount: 15,
|
||||
topCategories: ["SEO", "Marketing", "Data Analysis"],
|
||||
avatarSrc: "https://example.com/avatar1.jpg",
|
||||
otherLinks: {
|
||||
website: "https://ailabs.com",
|
||||
github: "https://github.com/ailabs",
|
||||
@@ -158,6 +160,7 @@ const mockCreatorAgents = [
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
isLoggedIn: true,
|
||||
userName: "John Doe",
|
||||
userEmail: "john.doe@example.com",
|
||||
navLinks: mockNavLinks,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as React from "react";
|
||||
import { Navbar } from "../Navbar";
|
||||
import { CreatorDetails } from "../composite/CreatorDetails";
|
||||
import { AgentsSection } from "../composite/AgentsSection";
|
||||
import { BreadCrumbs } from "../BreadCrumbs";
|
||||
import { IconType } from "../../ui/icons";
|
||||
import { Navbar } from "@/components/agptui/Navbar";
|
||||
import { CreatorDetails } from "@/components/agptui/composite/CreatorDetails";
|
||||
import { AgentsSection } from "@/components/agptui/composite/AgentsSection";
|
||||
import { BreadCrumbs } from "@/components/agptui/BreadCrumbs";
|
||||
import { IconType } from "@/components/ui/icons";
|
||||
|
||||
interface CreatorPageProps {
|
||||
isLoggedIn: boolean;
|
||||
userName: string;
|
||||
userEmail: string;
|
||||
navLinks: { name: string; href: string }[];
|
||||
@@ -44,6 +45,7 @@ interface CreatorPageProps {
|
||||
}
|
||||
|
||||
export const CreatorPage: React.FC<CreatorPageProps> = ({
|
||||
isLoggedIn,
|
||||
userName,
|
||||
userEmail,
|
||||
navLinks,
|
||||
@@ -65,6 +67,7 @@ export const CreatorPage: React.FC<CreatorPageProps> = ({
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1440px]">
|
||||
<Navbar
|
||||
isLoggedIn={isLoggedIn}
|
||||
userName={userName}
|
||||
userEmail={userEmail}
|
||||
links={navLinks}
|
||||
|
||||
@@ -15,6 +15,7 @@ const meta = {
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
isLoggedIn: { control: "boolean" },
|
||||
userName: { control: "text" },
|
||||
navLinks: { control: "object" },
|
||||
activeLink: { control: "text" },
|
||||
@@ -237,6 +238,7 @@ const mockFeaturedCreators = [
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
isLoggedIn: true,
|
||||
userName: "John Doe",
|
||||
userEmail: "john.doe@example.com",
|
||||
navLinks: mockNavLinks,
|
||||
@@ -272,6 +274,7 @@ export const WithInteraction: Story = {
|
||||
|
||||
export const EmptyState: Story = {
|
||||
args: {
|
||||
isLoggedIn: false,
|
||||
userName: "Jane Smith",
|
||||
userEmail: "jane.smith@example.com",
|
||||
navLinks: mockNavLinks,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import * as React from "react";
|
||||
import { Navbar } from "../Navbar";
|
||||
import { HeroSection } from "../composite/HeroSection";
|
||||
import { FeaturedSection } from "../composite/FeaturedSection";
|
||||
import { AgentsSection } from "../composite/AgentsSection";
|
||||
import { BecomeACreator } from "../BecomeACreator";
|
||||
import { FeaturedCreators } from "../composite/FeaturedCreators";
|
||||
import { Separator } from "../../ui/separator";
|
||||
import { IconType } from "../../ui/icons";
|
||||
import { Navbar } from "@/components/agptui/Navbar";
|
||||
import { HeroSection } from "@/components/agptui/composite/HeroSection";
|
||||
import { FeaturedSection } from "@/components/agptui/composite/FeaturedSection";
|
||||
import { AgentsSection } from "@/components/agptui/composite/AgentsSection";
|
||||
import { BecomeACreator } from "@/components/agptui/BecomeACreator";
|
||||
import { FeaturedCreators } from "@/components/agptui/composite/FeaturedCreators";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { IconType } from "@/components/ui/icons";
|
||||
interface PageProps {
|
||||
isLoggedIn: boolean;
|
||||
userName: string;
|
||||
userEmail: string;
|
||||
navLinks: { name: string; href: string }[];
|
||||
@@ -47,6 +48,7 @@ interface PageProps {
|
||||
}
|
||||
|
||||
export const Page: React.FC<PageProps> = ({
|
||||
isLoggedIn,
|
||||
userName,
|
||||
userEmail,
|
||||
navLinks,
|
||||
@@ -84,6 +86,7 @@ export const Page: React.FC<PageProps> = ({
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<Navbar
|
||||
isLoggedIn={isLoggedIn}
|
||||
userName={userName}
|
||||
userEmail={userEmail}
|
||||
links={navLinks}
|
||||
|
||||
@@ -12,10 +12,11 @@ const getServerUser = async () => {
|
||||
data: { user },
|
||||
error,
|
||||
} = await supabase.auth.getUser();
|
||||
if (error) {
|
||||
console.error("Supabase auth error:", error);
|
||||
return { user: null, role: null, error: `Auth error: ${error.message}` };
|
||||
}
|
||||
// if (error) {
|
||||
// // FIX: Suppressing error for now. Need to stop the nav bar calling this all the time
|
||||
// // console.error("Supabase auth error:", error);
|
||||
// return { user: null, role: null, error: `Auth error: ${error.message}` };
|
||||
// }
|
||||
if (!user) {
|
||||
return { user: null, role: null, error: "No user found in the response" };
|
||||
}
|
||||
|
||||
@@ -271,13 +271,14 @@ export default class BaseAutoGPTServerAPI {
|
||||
if (!response.ok) {
|
||||
console.warn(`${method} ${path} returned non-OK response:`, response);
|
||||
|
||||
if (
|
||||
response.status === 403 &&
|
||||
response.statusText === "Not authenticated" &&
|
||||
typeof window !== "undefined" // Check if in browser environment
|
||||
) {
|
||||
window.location.href = "/login";
|
||||
}
|
||||
// console.warn("baseClient is attempting to redirect by changing window location")
|
||||
// if (
|
||||
// response.status === 403 &&
|
||||
// response.statusText === "Not authenticated" &&
|
||||
// typeof window !== "undefined" // Check if in browser environment
|
||||
// ) {
|
||||
// window.location.href = "/login";
|
||||
// }
|
||||
|
||||
let errorDetail;
|
||||
try {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { createServerClient } from "@supabase/ssr";
|
||||
import { NextResponse, type NextRequest } from "next/server";
|
||||
import { LOCALES } from "@/lib/utils";
|
||||
|
||||
// TODO: Update the protected pages list
|
||||
const PROTECTED_PAGES = ['/monitor', '/build', ]
|
||||
const ADMIN_PAGES = ['/admin']
|
||||
const PROTECTED_PAGES = ["/monitor", "/build"];
|
||||
const ADMIN_PAGES = ["/admin"];
|
||||
|
||||
export async function updateSession(request: NextRequest) {
|
||||
export async function updateSession(request: NextRequest, locale: string) {
|
||||
let supabaseResponse = NextResponse.next({
|
||||
request,
|
||||
});
|
||||
|
||||
const isAvailable = Boolean(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL &&
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||
);
|
||||
|
||||
if (!isAvailable) {
|
||||
@@ -54,28 +55,47 @@ export async function updateSession(request: NextRequest) {
|
||||
|
||||
// Get the user role
|
||||
const userRole = user?.role;
|
||||
const url = request.nextUrl.clone();
|
||||
const pathname = request.nextUrl.pathname;
|
||||
const pathnameHasLocale = LOCALES.some(
|
||||
(locale) =>
|
||||
pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`,
|
||||
);
|
||||
const lang = pathnameHasLocale ? `/${pathname.split("/")[1]}` : "";
|
||||
|
||||
// AUTH REDIRECTS
|
||||
// If not logged in and trying to access a protected page, redirect to login
|
||||
if (
|
||||
!user &&
|
||||
(PROTECTED_PAGES.some(page => request.nextUrl.pathname.startsWith(page)) ||
|
||||
ADMIN_PAGES.some(page => request.nextUrl.pathname.startsWith(page)))
|
||||
|
||||
(!user &&
|
||||
PROTECTED_PAGES.some((page) => {
|
||||
const combinedPath = `${lang}${page}`;
|
||||
// console.log("Checking pathname:", request.nextUrl.pathname, "against:", combinedPath);
|
||||
return request.nextUrl.pathname.startsWith(combinedPath);
|
||||
})) ||
|
||||
ADMIN_PAGES.some((page) => {
|
||||
const combinedPath = `${lang}${page}`;
|
||||
// console.log("Checking pathname:", request.nextUrl.pathname, "against:", combinedPath);
|
||||
return request.nextUrl.pathname.startsWith(combinedPath);
|
||||
})
|
||||
) {
|
||||
// no user, potentially respond by redirecting the user to the login page
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = "/login";
|
||||
return NextResponse.redirect(url)
|
||||
url.pathname = `${locale}/login`;
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
if (
|
||||
user && userRole != "admin" &&
|
||||
ADMIN_PAGES.some(page => request.nextUrl.pathname.startsWith(page))
|
||||
user &&
|
||||
userRole != "admin" &&
|
||||
ADMIN_PAGES.some((page) =>
|
||||
request.nextUrl.pathname.startsWith(`${lang}${page}`),
|
||||
)
|
||||
) {
|
||||
// no user, potentially respond by redirecting the user to the login page
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = "/dashboard";
|
||||
return NextResponse.redirect(url)
|
||||
url.pathname = `${locale}/monitoring`;
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
if (locale) {
|
||||
url.pathname = `${locale}${pathname}`;
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
|
||||
// IMPORTANT: You *must* return the supabaseResponse object as it is. If you're
|
||||
|
||||
@@ -213,3 +213,6 @@ export function getBehaveAs(): BehaveAs {
|
||||
? BehaveAs.CLOUD
|
||||
: BehaveAs.LOCAL;
|
||||
}
|
||||
|
||||
export const LOCALES = process.env.NEXT_PUBLIC_LOCALES?.split(",") || ["en"];
|
||||
export const DEFAULT_LOCALE = process.env.NEXT_PUBLIC_DEFAULT_LOCALE || "en";
|
||||
|
||||
@@ -4,18 +4,13 @@ import React from "react";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
export async function withRoleAccess(allowedRoles: string[]) {
|
||||
"use server";
|
||||
console.log("withRoleAccess called:", allowedRoles);
|
||||
("use server");
|
||||
return await Sentry.withServerActionInstrumentation(
|
||||
"withRoleAccess",
|
||||
{},
|
||||
async () => {
|
||||
return async function <T extends React.ComponentType<any>>(Component: T) {
|
||||
const { user, role, error } = await getServerUser();
|
||||
|
||||
if (error || !user || !role || !allowedRoles.includes(role)) {
|
||||
redirect("/unauthorized");
|
||||
}
|
||||
|
||||
return Component;
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
import { updateSession } from "@/lib/supabase/middleware";
|
||||
import { type NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
import { match } from "@formatjs/intl-localematcher";
|
||||
import Negotiator from "negotiator";
|
||||
import { LOCALES, DEFAULT_LOCALE } from "@/lib/utils";
|
||||
|
||||
function getLocale(request: NextRequest) {
|
||||
let headers = Object.fromEntries(request.headers.entries());
|
||||
let languages = new Negotiator({ headers }).languages();
|
||||
|
||||
return match(languages, LOCALES, DEFAULT_LOCALE);
|
||||
}
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
return await updateSession(request);
|
||||
// Check if there is any supported locale in the pathname
|
||||
const { pathname } = request.nextUrl;
|
||||
const pathnameHasLocale = LOCALES.some(
|
||||
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`,
|
||||
);
|
||||
let locale = "";
|
||||
if (!pathnameHasLocale) {
|
||||
// Redirect if there is no locale
|
||||
locale = `/${getLocale(request)}`;
|
||||
}
|
||||
|
||||
return await updateSession(request, locale);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
@@ -14,6 +36,6 @@ export const config = {
|
||||
* - favicon.ico (favicon file)
|
||||
* Feel free to modify this pattern to include more paths.
|
||||
*/
|
||||
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
|
||||
"/((?!_next/static|_next/image|favicon.ico|auth|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1118,6 +1118,13 @@
|
||||
resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz"
|
||||
integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==
|
||||
|
||||
"@formatjs/intl-localematcher@^0.5.5":
|
||||
version "0.5.5"
|
||||
resolved "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz"
|
||||
integrity sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==
|
||||
dependencies:
|
||||
tslib "^2.7.0"
|
||||
|
||||
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
|
||||
version "9.3.0"
|
||||
resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz"
|
||||
@@ -3596,6 +3603,11 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/negotiator@^0.6.3":
|
||||
version "0.6.3"
|
||||
resolved "https://registry.npmjs.org/@types/negotiator/-/negotiator-0.6.3.tgz"
|
||||
integrity sha512-JkXTOdKs5MF086b/pt8C3+yVp3iDUwG635L7oCH6HvJvvr6lSUU5oe/gLXnPEfYRROHjJIPgCV6cuAg8gGkntQ==
|
||||
|
||||
"@types/node@*", "@types/node@^22.0.0", "@types/node@^22.7.3":
|
||||
version "22.7.4"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz"
|
||||
@@ -9157,6 +9169,11 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
negotiator@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz"
|
||||
integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
|
||||
|
||||
negotiator@0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz"
|
||||
@@ -11569,10 +11586,10 @@ tslib@^1.8.1:
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz"
|
||||
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
|
||||
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.7.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz"
|
||||
integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
|
||||
Reference in New Issue
Block a user