mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Compare commits
4
Commits
1.1.3-rc.2
...
1.1.3-rc.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4ebc876bf | ||
|
|
a435a92d41 | ||
|
|
bcbd6bbea1 | ||
|
|
82559b49bd |
+4
-3
@@ -5,9 +5,9 @@ NODE_ENV=production
|
|||||||
DATABASE_URL=postgresql://devuser:changeme@db:5432/devdb?schema=public
|
DATABASE_URL=postgresql://devuser:changeme@db:5432/devdb?schema=public
|
||||||
|
|
||||||
# Projet
|
# Projet
|
||||||
NEXT_PUBLIC_PROJECT_NAME="Portabase"
|
PROJECT_NAME="Portabase"
|
||||||
NEXT_PUBLIC_PROJECT_DESCRIPTION="Portabase is a powerful database manager"
|
PROJECT_DESCRIPTION="Portabase is a powerful database manager"
|
||||||
NEXT_PUBLIC_PROJECT_URL=http://app.portabase.io
|
PROJECT_URL=http://app.portabase.io
|
||||||
PROJECT_SECRET=
|
PROJECT_SECRET=
|
||||||
|
|
||||||
# SMTP (email)
|
# SMTP (email)
|
||||||
@@ -20,6 +20,7 @@ SMTP_FROM=
|
|||||||
# Google
|
# Google
|
||||||
AUTH_GOOGLE_ID=
|
AUTH_GOOGLE_ID=
|
||||||
AUTH_GOOGLE_SECRET=
|
AUTH_GOOGLE_SECRET=
|
||||||
|
AUTH_GOOGLE_METHOD=
|
||||||
|
|
||||||
# S3
|
# S3
|
||||||
S3_ENDPOINT=http://app.s3.portabase.io
|
S3_ENDPOINT=http://app.s3.portabase.io
|
||||||
|
|||||||
Binary file not shown.
+9
-38
@@ -1,49 +1,20 @@
|
|||||||
"use client"
|
import React from "react";
|
||||||
import React, {useEffect, useState} from "react";
|
import {redirect} from "next/navigation";
|
||||||
import {LayoutAdmin} from "@/components/layout";
|
import {currentUser} from "@/lib/auth/current-user";
|
||||||
import Image from "next/image";
|
import {AuthLogoSection} from "@/components/wrappers/auth/auth-logo-section";
|
||||||
import {env} from "@/env.mjs";
|
|
||||||
import {useTheme} from "next-themes";
|
|
||||||
import {useSession} from "@/lib/auth/auth-client";
|
|
||||||
import {useRouter} from "next/navigation";
|
|
||||||
|
|
||||||
|
export default async function Layout({children}: { children: React.ReactNode }) {
|
||||||
|
|
||||||
export default function Layout({children}: { children: React.ReactNode }) {
|
const user = await currentUser();
|
||||||
|
|
||||||
const { resolvedTheme } = useTheme();
|
if (user && !user.banned && user.role !== "pending") {
|
||||||
const [mounted, setMounted] = useState(false);
|
redirect("/dashboard/home");
|
||||||
const router = useRouter();
|
|
||||||
const { data: session } = useSession();
|
|
||||||
|
|
||||||
if (session && session.user && !session.user.banned && session.user.role !== "pending") {
|
|
||||||
router.replace("/dashboard/home");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!mounted) return null;
|
|
||||||
|
|
||||||
const imageTheme = resolvedTheme === "dark" ? "/images/logo-white.png" : "/images/logo-black.png";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-full flex-1 flex-col justify-center py-12 sm:px-6 lg:px-8 ">
|
<div className="flex min-h-full flex-1 flex-col justify-center py-12 sm:px-6 lg:px-8 ">
|
||||||
<div className="mx-auto w-full max-w-md">
|
<div className="mx-auto w-full max-w-md">
|
||||||
<div className="sm:mx-auto sm:w-full sm:max-w-md flex items-center justify-center space-x-2">
|
<AuthLogoSection/>
|
||||||
<img
|
|
||||||
className="p-12 text-black dark:text-white"
|
|
||||||
src={imageTheme}
|
|
||||||
// loading="eager"
|
|
||||||
alt="Logo"
|
|
||||||
// width={1024}
|
|
||||||
// height={1024}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="text-sm text-muted-foreground -ml-12 -mb-12">
|
|
||||||
v{env.NEXT_PUBLIC_PROJECT_VERSION}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
<footer className="py-4 text-center text-xs justify-items-end text-muted-foreground">
|
<footer className="py-4 text-center text-xs justify-items-end text-muted-foreground">
|
||||||
|
|||||||
+11
-23
@@ -1,32 +1,20 @@
|
|||||||
"use client"
|
import {notFound} from "next/navigation";
|
||||||
|
import {env} from "@/env.mjs";
|
||||||
import {useEffect, useRef, useState} from "react";
|
|
||||||
import {useSearchParams} from "next/navigation";
|
|
||||||
|
|
||||||
import {LoginForm} from "@/components/wrappers/auth/login/login-form/login-form";
|
import {LoginForm} from "@/components/wrappers/auth/login/login-form/login-form";
|
||||||
import {toast} from "sonner";
|
import {Metadata} from "next";
|
||||||
|
|
||||||
export default function SignInPage(props: {
|
export const metadata: Metadata = {
|
||||||
searchParams: Promise<{ callbackUrl: string | undefined }>
|
title: "Login",
|
||||||
}) {
|
};
|
||||||
|
|
||||||
const [urlParams, setUrlParams] = useState<URLSearchParams>();
|
export default async function SignInPage() {
|
||||||
|
const authGoogleEnabled = env.AUTH_GOOGLE_METHOD;
|
||||||
useEffect(() => {
|
if (!authGoogleEnabled) {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
notFound()
|
||||||
setUrlParams(urlParams);
|
|
||||||
const error = urlParams.get("error");
|
|
||||||
console.log(urlParams.get("redirect"));
|
|
||||||
if (error?.includes("pending")) {
|
|
||||||
toast.error("Your account is not active.");
|
|
||||||
urlParams.delete("error");
|
|
||||||
window.history.replaceState({}, document.title, window.location.pathname + "?" + urlParams.toString());
|
|
||||||
}
|
}
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto grid w-full gap-6">
|
<div className="mx-auto grid w-full gap-6">
|
||||||
<LoginForm/>
|
<LoginForm authGoogleEnabled={authGoogleEnabled}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {RegisterForm} from "@/components/wrappers/auth/register/register-form/register-form";
|
import {RegisterForm} from "@/components/wrappers/auth/register/register-form/register-form";
|
||||||
|
import {Metadata} from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Register",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
|||||||
import {AdminTabs} from "@/components/wrappers/dashboard/admin/admin-tabs";
|
import {AdminTabs} from "@/components/wrappers/dashboard/admin/admin-tabs";
|
||||||
import {db} from "@/db";
|
import {db} from "@/db";
|
||||||
import {isNull} from "drizzle-orm";
|
import {isNull} from "drizzle-orm";
|
||||||
|
import {Metadata} from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Admin",
|
||||||
|
};
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
|
|
||||||
const users = await db.query.user.findMany({
|
const users = await db.query.user.findMany({
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { PageParams } from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
|
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import { AgentForm } from "@/components/wrappers/dashboard/agent/agent-form/agent-form";
|
import {AgentForm} from "@/components/wrappers/dashboard/agent/agent-form/agent-form";
|
||||||
|
import {Metadata} from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Create Agent",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
return (
|
return (
|
||||||
@@ -9,7 +14,7 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
<PageTitle>Create new agent</PageTitle>
|
<PageTitle>Create new agent</PageTitle>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<AgentForm />
|
<AgentForm/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import { PageParams } from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import { AgentCard } from "@/components/wrappers/dashboard/agent/agent-card/agent-card";
|
import {AgentCard} from "@/components/wrappers/dashboard/agent/agent-card/agent-card";
|
||||||
import { CardsWithPagination } from "@/components/wrappers/common/cards-with-pagination";
|
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
|
||||||
import { Button } from "@/components/ui/button";
|
import {Button} from "@/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Page, PageActions, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
|
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import { notFound } from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import { db } from "@/db";
|
import {db} from "@/db";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
|
import {eq, not} from "drizzle-orm";
|
||||||
import {and, eq, not} from "drizzle-orm";
|
|
||||||
import {Plus} from "lucide-react";
|
|
||||||
import {cn} from "@/lib/utils";
|
|
||||||
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
|
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
|
||||||
// export const dynamic = "force-dynamic";
|
import {Metadata} from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Agents",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageContent>
|
<PageContent>
|
||||||
{agents.length > 0 ? (
|
{agents.length > 0 ? (
|
||||||
<CardsWithPagination data={agents} cardItem={AgentCard} cardsPerPage={4} numberOfColumns={1} />
|
<CardsWithPagination data={agents} cardItem={AgentCard} cardsPerPage={4} numberOfColumns={1}/>
|
||||||
) : (
|
) : (
|
||||||
<EmptyStatePlaceholder
|
<EmptyStatePlaceholder
|
||||||
url={"/dashboard/agents/new"}
|
url={"/dashboard/agents/new"}
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import {db} from "@/db";
|
|||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
|
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
|
||||||
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
|
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
|
||||||
|
import {Metadata} from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Projects",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import {EditButtonSettings} from "@/components/wrappers/dashboard/settings/edit-
|
|||||||
import {
|
import {
|
||||||
SettingsOrganizationMembersTable
|
SettingsOrganizationMembersTable
|
||||||
} from "@/components/wrappers/dashboard/settings/settings-organization-members-table";
|
} from "@/components/wrappers/dashboard/settings/settings-organization-members-table";
|
||||||
|
import {Metadata} from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Settings",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{ slug: string }>) {
|
export default async function RoutePage(props: PageParams<{ slug: string }>) {
|
||||||
const organization = await getOrganization({});
|
const organization = await getOrganization({});
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import {and, asc, count, eq, inArray} from "drizzle-orm";
|
|||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
import {getOrganization} from "@/lib/auth/auth";
|
import {getOrganization} from "@/lib/auth/auth";
|
||||||
import {Building2, DatabaseBackup, Folder, RefreshCcw} from "lucide-react";
|
import {Building2, DatabaseBackup, Folder, RefreshCcw} from "lucide-react";
|
||||||
|
import {Metadata} from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Statistics",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
const organization = await getOrganization({});
|
const organization = await getOrganization({});
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import {db} from "@/db";
|
|||||||
import {asc, inArray} from "drizzle-orm";
|
import {asc, inArray} from "drizzle-orm";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
import {listOrganizations} from "@/lib/auth/auth";
|
import {listOrganizations} from "@/lib/auth/auth";
|
||||||
|
import {Metadata} from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Home",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import {ButtonDeleteAccount} from "@/components/wrappers/dashboard/profile/butto
|
|||||||
import {AvatarWithUpload} from "@/components/wrappers/dashboard/profile/avatar/avatar-with-upload";
|
import {AvatarWithUpload} from "@/components/wrappers/dashboard/profile/avatar/avatar-with-upload";
|
||||||
import {currentUser} from "@/lib/auth/current-user";
|
import {currentUser} from "@/lib/auth/current-user";
|
||||||
import {getAccounts, getSessions} from "@/lib/auth/auth";
|
import {getAccounts, getSessions} from "@/lib/auth/auth";
|
||||||
|
import {Metadata} from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Profile",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
const user = await currentUser();
|
const user = await currentUser();
|
||||||
@@ -39,11 +44,8 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
{user.name}
|
{user.name}
|
||||||
<Badge className="ml-3 hidden lg:block">{user.role}</Badge>
|
<Badge className="ml-3 hidden lg:block">{user.role}</Badge>
|
||||||
</PageTitle>
|
</PageTitle>
|
||||||
{/*<PageActions className="mt-2 hidden sm:block">*/}
|
|
||||||
{/* <ButtonDeleteAccount text="Delete my account"/>*/}
|
|
||||||
{/*</PageActions>*/}
|
|
||||||
</div>
|
</div>
|
||||||
<PageContent >
|
<PageContent>
|
||||||
<UserForm
|
<UserForm
|
||||||
userId={user.id}
|
userId={user.id}
|
||||||
sessions={sessions} accounts={accounts}
|
sessions={sessions} accounts={accounts}
|
||||||
@@ -53,9 +55,6 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
role: user.role ?? undefined,
|
role: user.role ?? undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/*<div className="mt-4 sm:hidden ">*/}
|
|
||||||
{/* <ButtonDeleteAccount text="Delete my account"/>*/}
|
|
||||||
{/*</div>*/}
|
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import {NextResponse} from "next/server";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
return NextResponse.json({
|
||||||
|
PROJECT_URL: process.env.PROJECT_URL,
|
||||||
|
PROJECT_NAME: process.env.PROJECT_NAME,
|
||||||
|
PROJECT_DESCRIPTION: process.env.PROJECT_DESCRIPTION,
|
||||||
|
});
|
||||||
|
}
|
||||||
+7
-2
@@ -6,9 +6,14 @@ import {cn} from "@/lib/utils";
|
|||||||
import {ConsoleSilencer} from "@/components/wrappers/common/console-silencer";
|
import {ConsoleSilencer} from "@/components/wrappers/common/console-silencer";
|
||||||
import {inter} from "@/fonts/fonts";
|
import {inter} from "@/fonts/fonts";
|
||||||
|
|
||||||
|
const title = process.env.PROJECT_NAME ?? "App Title";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: process.env.NEXT_PUBLIC_PROJECT_NAME ?? "Portabase",
|
title: {
|
||||||
description: process.env.NEXT_PUBLIC_PROJECT_DESCRIPTION ?? undefined,
|
default: title,
|
||||||
|
template: `%s - ${title}`
|
||||||
|
},
|
||||||
|
description: process.env.PROJECT_DESCRIPTION ?? undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import BackButton from "@/components/wrappers/common/button/back-button";
|
||||||
|
|
||||||
export default async function NotFound() {
|
export default async function NotFound() {
|
||||||
return(
|
return(
|
||||||
@@ -7,6 +8,7 @@ export default async function NotFound() {
|
|||||||
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">Not found</h1>
|
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">Not found</h1>
|
||||||
<p className="leading-7 [&:not(:first-child)]:mt-6">The content you are trying to view is not available.</p>
|
<p className="leading-7 [&:not(:first-child)]:mt-6">The content you are trying to view is not available.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<BackButton>Go home</BackButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
# context: .
|
# context: .
|
||||||
# dockerfile: docker/dockerfile/Dockerfile
|
# dockerfile: docker/dockerfile/Dockerfile
|
||||||
# target: prod
|
# target: prod
|
||||||
image: solucetechnologies/portabase:1.1.3-rc.2
|
image: solucetechnologies/portabase:1.1.3-rc.3
|
||||||
ports:
|
ports:
|
||||||
- '8887:80'
|
- '8887:80'
|
||||||
env_file:
|
env_file:
|
||||||
|
|||||||
@@ -94,9 +94,6 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
COPY --chown=nextjs:nodejs src/db ./src/db
|
COPY --chown=nextjs:nodejs src/db ./src/db
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
COPY ./docker/entrypoints/app-prod-entrypoint.sh /app/app-prod-entrypoint.sh
|
COPY ./docker/entrypoints/app-prod-entrypoint.sh /app/app-prod-entrypoint.sh
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/types/routes.d.ts";
|
import "./.next/dev/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@@ -41,9 +41,6 @@ const nextConfig: NextConfig = {
|
|||||||
typescript: {
|
typescript: {
|
||||||
ignoreBuildErrors: true,
|
ignoreBuildErrors: true,
|
||||||
},
|
},
|
||||||
experimental: {
|
|
||||||
turbopackFileSystemCacheForDev: true,
|
|
||||||
},
|
|
||||||
async headers() {
|
async headers() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
"db:drop": "drizzle-kit drop",
|
"db:drop": "drizzle-kit drop",
|
||||||
"auth:generate": "npx @better-auth/cli generate --config ./src/lib/auth/auth.ts"
|
"auth:generate": "npx @better-auth/cli generate --config ./src/lib/auth/auth.ts"
|
||||||
},
|
},
|
||||||
"type": "module",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@radix-ui/react-accordion": "^1.2.10",
|
"@radix-ui/react-accordion": "^1.2.10",
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import {NextRequest, NextResponse} from "next/server";
|
||||||
import { loggingMiddleware } from "@/middleware/loggingMiddleware";
|
import {loggingMiddleware} from "@/middleware/loggingMiddleware";
|
||||||
import { errorHandler } from "@/middleware/errorHandler";
|
import {errorHandler} from "@/middleware/errorHandler";
|
||||||
import { auth } from "@/lib/auth/auth";
|
import {auth} from "@/lib/auth/auth";
|
||||||
import { headers } from "next/headers";
|
import {headers} from "next/headers";
|
||||||
import { signOut } from "@/lib/auth/auth-client";
|
|
||||||
|
|
||||||
export async function proxy(request: NextRequest) {
|
export async function proxy(request: NextRequest) {
|
||||||
const url = request.nextUrl.clone();
|
const url = request.nextUrl.clone();
|
||||||
@@ -13,25 +12,20 @@ export async function proxy(request: NextRequest) {
|
|||||||
const session = await auth.api.getSession({
|
const session = await auth.api.getSession({
|
||||||
headers: await headers(),
|
headers: await headers(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return NextResponse.redirect(new URL(`/login?redirect=${redirectUrl}`, request.url));
|
return NextResponse.redirect(new URL(`/login?redirect=${redirectUrl}`, request.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.user.banned) {
|
if (session.user.banned) {
|
||||||
signOut();
|
await auth.api.signOut({headers: await headers()});
|
||||||
return NextResponse.redirect(new URL("/login?error=banned", request.url));
|
return NextResponse.redirect(new URL("/login?error=banned", request.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.user.role === "pending") {
|
if (session.user.role === "pending") {
|
||||||
signOut();
|
await auth.api.signOut({headers: await headers()});
|
||||||
return NextResponse.redirect(new URL(`/login?error=pending?redirect=${redirectUrl}`, request.url));
|
return NextResponse.redirect(new URL(`/login?error=pending?redirect=${redirectUrl}`, request.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.pathname === "/dashboard") {
|
if (url.pathname === "/dashboard") {
|
||||||
return NextResponse.redirect(new URL(`/dashboard/home`, request.url));
|
return NextResponse.redirect(new URL(`/dashboard/home`, request.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +36,9 @@ export async function proxy(request: NextRequest) {
|
|||||||
if (url.pathname.startsWith("/api")) {
|
if (url.pathname.startsWith("/api")) {
|
||||||
const routeExists = checkRouteExists(url.pathname);
|
const routeExists = checkRouteExists(url.pathname);
|
||||||
if (!routeExists) {
|
if (!routeExists) {
|
||||||
return new NextResponse(JSON.stringify({ message: "This API route does not exist.", status: 404 }), {
|
return new NextResponse(JSON.stringify({message: "This API route does not exist.", status: 404}), {
|
||||||
status: 404,
|
status: 404,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: {"Content-Type": "application/json"},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,6 +58,7 @@ function checkRouteExists(pathname: string) {
|
|||||||
/^\/api\/images\/[^/]+\/?$/,
|
/^\/api\/images\/[^/]+\/?$/,
|
||||||
/^\/api\/events\/?$/,
|
/^\/api\/events\/?$/,
|
||||||
/^\/api\/init\/?$/,
|
/^\/api\/init\/?$/,
|
||||||
|
/^\/api\/config\/?$/,
|
||||||
];
|
];
|
||||||
return routePatterns.some((pattern) => pattern.test(pathname));
|
return routePatterns.some((pattern) => pattern.test(pathname));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
import {env} from "@/env.mjs";
|
||||||
|
import React, {useEffect, useState} from "react";
|
||||||
|
import {useTheme} from "next-themes";
|
||||||
|
|
||||||
|
|
||||||
|
export const AuthLogoSection = () => {
|
||||||
|
|
||||||
|
const {resolvedTheme} = useTheme();
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!mounted) return null;
|
||||||
|
|
||||||
|
|
||||||
|
const imageTheme = resolvedTheme === "dark" ? "/images/logo-white.png" : "/images/logo-black.png";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sm:mx-auto sm:w-full sm:max-w-md flex items-center justify-center space-x-2">
|
||||||
|
<img
|
||||||
|
className="p-12 text-black dark:text-white"
|
||||||
|
src={imageTheme}
|
||||||
|
alt="Logo"
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-muted-foreground -ml-12 -mb-12">v{env.NEXT_PUBLIC_PROJECT_VERSION}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Button } from "@/components/ui/button";
|
import {Button} from "@/components/ui/button";
|
||||||
import { signIn } from "@/lib/auth/auth-client";
|
import {signIn} from "@/lib/auth/auth-client";
|
||||||
import { JSX } from "react";
|
import {JSX} from "react";
|
||||||
|
|
||||||
export type AuthButtonProps = {
|
export type AuthButtonProps = {
|
||||||
providers: SocialProviderType[];
|
providers: SocialProviderType[];
|
||||||
|
callBackURL?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SocialProviderType = {
|
export type SocialProviderType = {
|
||||||
@@ -41,7 +42,7 @@ export const SocialAuthButton = (props: AuthButtonProps): JSX.Element => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void signIn.social({
|
void signIn.social({
|
||||||
provider: provider.id,
|
provider: provider.id,
|
||||||
callbackURL: "/dashboard/profile",
|
callbackURL: props.callBackURL ?? "/dashboard/profile",
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,3 +1,150 @@
|
|||||||
|
// "use client";
|
||||||
|
//
|
||||||
|
// import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||||
|
// import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
||||||
|
// import {Input} from "@/components/ui/input";
|
||||||
|
// import {Form} from "@/components/ui/form";
|
||||||
|
// import {Button} from "@/components/ui/button";
|
||||||
|
// import {toast} from "sonner";
|
||||||
|
// import {useMutation} from "@tanstack/react-query";
|
||||||
|
// import {TooltipProvider} from "@/components/ui/tooltip";
|
||||||
|
// import Link from "next/link";
|
||||||
|
// import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
|
||||||
|
// import {LoginSchema, LoginType} from "@/components/wrappers/auth/login/login-form/login-form.schema";
|
||||||
|
// import {SocialAuthButton, SocialProviderType} from "@/components/wrappers/auth/login/button-auth/social-auth-button";
|
||||||
|
// import {signIn} from "@/lib/auth/auth-client";
|
||||||
|
// import {useRouter} from "next/navigation";
|
||||||
|
// import {Icon} from "@iconify/react";
|
||||||
|
// import {useEffect, useState} from "react";
|
||||||
|
//
|
||||||
|
// export type loginFormProps = {
|
||||||
|
// defaultValues?: LoginType;
|
||||||
|
// authGoogleEnabled: boolean;
|
||||||
|
//
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// export const LoginForm = (props: loginFormProps) => {
|
||||||
|
// const router = useRouter();
|
||||||
|
//
|
||||||
|
// const form = useZodForm({
|
||||||
|
// schema: LoginSchema,
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const [urlParams, setUrlParams] = useState<URLSearchParams>();
|
||||||
|
//
|
||||||
|
// useEffect(() => {
|
||||||
|
// const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
// console.log(urlParams);
|
||||||
|
// setUrlParams(urlParams);
|
||||||
|
// const error = urlParams.get("error");
|
||||||
|
// console.log(urlParams.get("redirect"));
|
||||||
|
// if (error?.includes("pending")) {
|
||||||
|
// toast.error("Your account is not active.");
|
||||||
|
// urlParams.delete("error");
|
||||||
|
// window.history.replaceState({}, document.title, window.location.pathname + "?" + urlParams.toString());
|
||||||
|
// }
|
||||||
|
// }, []);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// const mutation = useMutation({
|
||||||
|
// mutationFn: async (values: LoginType) => {
|
||||||
|
// const {error} = await signIn.email(
|
||||||
|
// {
|
||||||
|
// password: values.password,
|
||||||
|
// email: values.email,
|
||||||
|
// callbackURL: urlParams?.get("redirect") ?? "/dashboard/profile",
|
||||||
|
// }, {
|
||||||
|
// onSuccess: () => {
|
||||||
|
// toast.success("Login success");
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// if (error) {
|
||||||
|
// toast.error(error.message);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const availableProviders: SocialProviderType[] = [];
|
||||||
|
//
|
||||||
|
// if (props.authGoogleEnabled) {
|
||||||
|
// availableProviders.push(
|
||||||
|
// {
|
||||||
|
// id: "google",
|
||||||
|
// name: "Google",
|
||||||
|
// icon: <Icon icon={"logos:google-icon"} width="25" height="25"/>,
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// return (
|
||||||
|
// <TooltipProvider>
|
||||||
|
// <Card>
|
||||||
|
// <CardHeader>
|
||||||
|
// <div className="grid gap-2 text-center mb-2">
|
||||||
|
// <h1 className="text-3xl font-bold">Login</h1>
|
||||||
|
// <p className="text-balance text-muted-foreground">Enter your informations below to login</p>
|
||||||
|
// </div>
|
||||||
|
// </CardHeader>
|
||||||
|
// <CardContent>
|
||||||
|
// <Form
|
||||||
|
// form={form}
|
||||||
|
// className="flex flex-col gap-4"
|
||||||
|
// onSubmit={async (values) => {
|
||||||
|
// await mutation.mutateAsync(values);
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// <FormField
|
||||||
|
// control={form.control}
|
||||||
|
// name="email"
|
||||||
|
// defaultValue=""
|
||||||
|
// render={({field}) => (
|
||||||
|
// <FormItem>
|
||||||
|
// <FormLabel>Email</FormLabel>
|
||||||
|
// <FormControl>
|
||||||
|
// <Input autoComplete="email webauthn"
|
||||||
|
// placeholder="exemple@portabase.io" {...field} />
|
||||||
|
// </FormControl>
|
||||||
|
// <FormMessage/>
|
||||||
|
// </FormItem>
|
||||||
|
// )}
|
||||||
|
// />
|
||||||
|
// <FormField
|
||||||
|
// control={form.control}
|
||||||
|
// name="password"
|
||||||
|
// defaultValue=""
|
||||||
|
// render={({field}) => (
|
||||||
|
// <FormItem>
|
||||||
|
// <div className="flex items-center">
|
||||||
|
// <FormLabel>Password</FormLabel>
|
||||||
|
// {/* <Link href={"/forgot-password"} className="ml-auto inline-block text-sm underline">
|
||||||
|
// Forgot your password?
|
||||||
|
// </Link>*/}
|
||||||
|
// </div>
|
||||||
|
// <FormControl>
|
||||||
|
// <PasswordInput autoComplete="current-password webauthn"
|
||||||
|
// placeholder="Your password" {...field} />
|
||||||
|
// </FormControl>
|
||||||
|
// <FormMessage/>
|
||||||
|
// </FormItem>
|
||||||
|
// )}
|
||||||
|
// />
|
||||||
|
// <Button>Sign in</Button>
|
||||||
|
// <div className="mt-4 text-center text-sm">
|
||||||
|
// Don't have an account?{" "}
|
||||||
|
// <Link href={"/register"} className="underline">
|
||||||
|
// Sign up
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// </Form>
|
||||||
|
// <SocialAuthButton
|
||||||
|
// callBackURL={urlParams?.get("redirect") ?? "/dashboard/profile"}
|
||||||
|
// providers={availableProviders}/>
|
||||||
|
// </CardContent>
|
||||||
|
// </Card>
|
||||||
|
// </TooltipProvider>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||||
@@ -15,10 +162,11 @@ import {SocialAuthButton, SocialProviderType} from "@/components/wrappers/auth/l
|
|||||||
import {signIn} from "@/lib/auth/auth-client";
|
import {signIn} from "@/lib/auth/auth-client";
|
||||||
import {useRouter} from "next/navigation";
|
import {useRouter} from "next/navigation";
|
||||||
import {Icon} from "@iconify/react";
|
import {Icon} from "@iconify/react";
|
||||||
import {env} from "@/env.mjs";
|
import {useEffect, useState} from "react";
|
||||||
|
|
||||||
export type loginFormProps = {
|
export type loginFormProps = {
|
||||||
defaultValues?: LoginType;
|
defaultValues?: LoginType;
|
||||||
|
authGoogleEnabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LoginForm = (props: loginFormProps) => {
|
export const LoginForm = (props: loginFormProps) => {
|
||||||
@@ -28,40 +176,67 @@ export const LoginForm = (props: loginFormProps) => {
|
|||||||
schema: LoginSchema,
|
schema: LoginSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [urlParams] = useState(() =>
|
||||||
|
new URLSearchParams(typeof window !== "undefined" ? window.location.search : "")
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const error = urlParams.get("error");
|
||||||
|
if (error?.includes("pending")) {
|
||||||
|
toast.error("Your account is not active.");
|
||||||
|
urlParams.delete("error");
|
||||||
|
window.history.replaceState({}, document.title, window.location.pathname + "?" + urlParams.toString());
|
||||||
|
}
|
||||||
|
}, [urlParams]);
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: async (values: LoginType) => {
|
mutationFn: async (values: LoginType) => {
|
||||||
const {error} = await signIn.email(values, {
|
try {
|
||||||
onSuccess: () => {
|
const callbackURL =
|
||||||
toast.success("Login success");
|
urlParams.get("redirect")?.startsWith("/")
|
||||||
router.push("/dashboard/profile");
|
? urlParams.get("redirect")
|
||||||
},
|
: "/dashboard/profile";
|
||||||
|
|
||||||
|
const {error} = await signIn.email({
|
||||||
|
email: values.email,
|
||||||
|
password: values.password,
|
||||||
|
callbackURL: callbackURL ?? "/dashboard/profile",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
|
} else {
|
||||||
|
toast.success("Login success");
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error("Unexpected client error during login");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (err: any) => {
|
||||||
|
toast.error(err.message || "Client error");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const availableProviders: SocialProviderType[] = [];
|
const availableProviders: SocialProviderType[] = [];
|
||||||
|
|
||||||
if (env.NEXT_PUBLIC_GOOGLE_AUTH) {
|
if (props.authGoogleEnabled) {
|
||||||
availableProviders.push(
|
availableProviders.push({
|
||||||
{
|
|
||||||
id: "google",
|
id: "google",
|
||||||
name: "Google",
|
name: "Google",
|
||||||
icon: <Icon icon={"logos:google-icon"} width="25" height="25"/>,
|
icon: <Icon icon="logos:google-icon" width="25" height="25"/>,
|
||||||
},
|
});
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="grid gap-2 text-center mb-2">
|
<div className="grid gap-2 text-center mb-2">
|
||||||
<h1 className="text-3xl font-bold">Login</h1>
|
<h1 className="text-3xl font-bold">Login</h1>
|
||||||
<p className="text-balance text-muted-foreground">Enter your informations below to login</p>
|
<p className="text-balance text-muted-foreground">
|
||||||
|
Enter your information below to login
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@@ -80,8 +255,11 @@ export const LoginForm = (props: loginFormProps) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Email</FormLabel>
|
<FormLabel>Email</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input autoComplete="email webauthn"
|
<Input
|
||||||
placeholder="exemple@portabase.io" {...field} />
|
autoComplete="email"
|
||||||
|
placeholder="example@portabase.io"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage/>
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -95,27 +273,34 @@ export const LoginForm = (props: loginFormProps) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<FormLabel>Password</FormLabel>
|
<FormLabel>Password</FormLabel>
|
||||||
{/* <Link href={"/forgot-password"} className="ml-auto inline-block text-sm underline">
|
{/* Optional forgot password link */}
|
||||||
Forgot your password?
|
|
||||||
</Link>*/}
|
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<PasswordInput autoComplete="current-password webauthn"
|
<PasswordInput
|
||||||
placeholder="Your password" {...field} />
|
autoComplete="current-password"
|
||||||
|
placeholder="Your password"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage/>
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Button>Sign in</Button>
|
<Button type="submit" disabled={mutation.isPending}>
|
||||||
|
{mutation.isPending ? "Signing in..." : "Sign in"}
|
||||||
|
</Button>
|
||||||
<div className="mt-4 text-center text-sm">
|
<div className="mt-4 text-center text-sm">
|
||||||
Don't have an account?{" "}
|
Don't have an account?{" "}
|
||||||
<Link href={"/register"} className="underline">
|
<Link href="/register" className="underline">
|
||||||
Sign up
|
Sign up
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
<SocialAuthButton providers={availableProviders}/>
|
|
||||||
|
<SocialAuthButton
|
||||||
|
callBackURL={urlParams.get("redirect") ?? "/dashboard/profile"}
|
||||||
|
providers={availableProviders}
|
||||||
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
"use client";
|
||||||
|
import React from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
type BackButtonProps = React.ComponentProps<typeof Button> & {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function BackButton({ children, ...props }: BackButtonProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={() => router.back()} aria-label={children?.toString()} {...props}>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,10 +19,6 @@ export const sessionsColumns: ColumnDef<Session>[] = [
|
|||||||
return timeAgo(row.original.expiresAt);
|
return timeAgo(row.original.expiresAt);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accessorKey: "ipAddress",
|
|
||||||
header: "IP Address",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "device",
|
id: "device",
|
||||||
header: "Device",
|
header: "Device",
|
||||||
|
|||||||
@@ -10,15 +10,16 @@ import {SidebarMenuCustomMain} from "@/components/wrappers/dashboard/common/side
|
|||||||
import {SideBarFooterCredit} from "@/components/wrappers/dashboard/common/sidebar/side-bar-footer-credit";
|
import {SideBarFooterCredit} from "@/components/wrappers/dashboard/common/sidebar/side-bar-footer-credit";
|
||||||
import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox";
|
import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox";
|
||||||
import {LoggedInButton} from "@/components/wrappers/dashboard/common/logged-in/logged-in-button";
|
import {LoggedInButton} from "@/components/wrappers/dashboard/common/logged-in/logged-in-button";
|
||||||
|
import {env} from "@/env.mjs";
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
|
const projectName = env.PROJECT_NAME;
|
||||||
return (
|
return (
|
||||||
<Sidebar collapsible="icon">
|
<Sidebar collapsible="icon">
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarLogo/>
|
<SidebarLogo projectName={projectName ?? "Portabase"}/>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<OrganizationCombobox/>
|
<OrganizationCombobox/>
|
||||||
@@ -32,10 +33,10 @@ export function AppSidebar() {
|
|||||||
|
|
||||||
<SidebarMenu className="mb-2">
|
<SidebarMenu className="mb-2">
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<LoggedInButton />
|
<LoggedInButton/>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
<SideBarFooterCredit />
|
<SideBarFooterCredit/>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
import { useSidebar } from "@/components/ui/sidebar";
|
import { useSidebar } from "@/components/ui/sidebar";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { env } from "@/env.mjs";
|
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export const SidebarLogo = () => {
|
export const SidebarLogo = ({projectName}: {
|
||||||
|
projectName: string;
|
||||||
|
}) => {
|
||||||
const { state, isMobile } = useSidebar();
|
const { state, isMobile } = useSidebar();
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ export const SidebarLogo = () => {
|
|||||||
<Image
|
<Image
|
||||||
loading="eager"
|
loading="eager"
|
||||||
src={"/images/logo.png"}
|
src={"/images/logo.png"}
|
||||||
alt={`Logo ${env.NEXT_PUBLIC_PROJECT_NAME}`}
|
alt={`Logo ${projectName}`}
|
||||||
className="h-10 w-10 object-contain"
|
className="h-10 w-10 object-contain"
|
||||||
height={10}
|
height={10}
|
||||||
width={10}
|
width={10}
|
||||||
@@ -38,7 +39,7 @@ export const SidebarLogo = () => {
|
|||||||
<Image
|
<Image
|
||||||
loading="eager"
|
loading="eager"
|
||||||
src={imageTheme}
|
src={imageTheme}
|
||||||
alt={`Logo ${env.NEXT_PUBLIC_PROJECT_NAME}`}
|
alt={`Logo ${projectName}`}
|
||||||
className="object-contain"
|
className="object-contain"
|
||||||
width={190}
|
width={190}
|
||||||
height={90}
|
height={90}
|
||||||
|
|||||||
+14
-14
@@ -6,12 +6,15 @@ const {version} = packageJson;
|
|||||||
|
|
||||||
export const env = createEnv({
|
export const env = createEnv({
|
||||||
server: {
|
server: {
|
||||||
NODE_ENV: z.enum(["development", "production"]).optional(),
|
|
||||||
DATABASE_URL: z.string().url().optional(),
|
|
||||||
NEXT_PUBLIC_PROJECT_NAME: z.string().optional(),
|
|
||||||
NEXT_PUBLIC_PROJECT_DESCRIPTION: z.string().optional(),
|
|
||||||
NEXT_PUBLIC_PROJECT_URL: z.string().optional(),
|
|
||||||
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
||||||
|
|
||||||
|
NODE_ENV: z.enum(["development", "production"]).optional(),
|
||||||
|
|
||||||
|
DATABASE_URL: z.string().url().optional(),
|
||||||
|
|
||||||
|
PROJECT_NAME: z.string().optional(),
|
||||||
|
PROJECT_DESCRIPTION: z.string().optional(),
|
||||||
|
PROJECT_URL: z.string().optional(),
|
||||||
PROJECT_SECRET: z.string().optional(),
|
PROJECT_SECRET: z.string().optional(),
|
||||||
|
|
||||||
SMTP_PASSWORD: z.string().optional(),
|
SMTP_PASSWORD: z.string().optional(),
|
||||||
@@ -22,7 +25,7 @@ export const env = createEnv({
|
|||||||
|
|
||||||
AUTH_GOOGLE_ID: z.string().optional(),
|
AUTH_GOOGLE_ID: z.string().optional(),
|
||||||
AUTH_GOOGLE_SECRET: z.string().optional(),
|
AUTH_GOOGLE_SECRET: z.string().optional(),
|
||||||
NEXT_PUBLIC_GOOGLE_AUTH: z.boolean().default(false).optional(),
|
AUTH_GOOGLE_METHOD: z.boolean().default(false).optional(),
|
||||||
|
|
||||||
S3_ENDPOINT: z.string().optional(),
|
S3_ENDPOINT: z.string().optional(),
|
||||||
S3_ACCESS_KEY: z.string().optional(),
|
S3_ACCESS_KEY: z.string().optional(),
|
||||||
@@ -38,18 +41,15 @@ export const env = createEnv({
|
|||||||
.default(process.env.NODE_ENV === "production" ? "0 7 * * *" : "* * * * *"),
|
.default(process.env.NODE_ENV === "production" ? "0 7 * * *" : "* * * * *"),
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
NEXT_PUBLIC_PROJECT_NAME: z.string().optional(),
|
|
||||||
NEXT_PUBLIC_PROJECT_DESCRIPTION: z.string().optional(),
|
|
||||||
NEXT_PUBLIC_PROJECT_URL: z.string().optional(),
|
|
||||||
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
||||||
|
|
||||||
NEXT_PUBLIC_GOOGLE_AUTH: z.boolean().default(false).optional(),
|
|
||||||
},
|
},
|
||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
NEXT_PUBLIC_PROJECT_NAME: process.env.NEXT_PUBLIC_PROJECT_NAME,
|
|
||||||
NEXT_PUBLIC_PROJECT_DESCRIPTION: process.env.NEXT_PUBLIC_PROJECT_DESCRIPTION,
|
|
||||||
NEXT_PUBLIC_PROJECT_URL: process.env.NEXT_PUBLIC_PROJECT_URL,
|
|
||||||
NEXT_PUBLIC_PROJECT_VERSION: version || "Unknown Version",
|
NEXT_PUBLIC_PROJECT_VERSION: version || "Unknown Version",
|
||||||
|
|
||||||
|
PROJECT_NAME: process.env.PROJECT_NAME,
|
||||||
|
PROJECT_DESCRIPTION: process.env.PROJECT_DESCRIPTION,
|
||||||
|
PROJECT_URL: process.env.PROJECT_URL,
|
||||||
PROJECT_SECRET: process.env.PROJECT_SECRET,
|
PROJECT_SECRET: process.env.PROJECT_SECRET,
|
||||||
|
|
||||||
DATABASE_URL: process.env.DATABASE_URL,
|
DATABASE_URL: process.env.DATABASE_URL,
|
||||||
@@ -62,7 +62,7 @@ export const env = createEnv({
|
|||||||
|
|
||||||
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
|
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
|
||||||
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
|
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
|
||||||
NEXT_PUBLIC_GOOGLE_AUTH: process.env.NEXT_PUBLIC_GOOGLE_AUTH === "true",
|
AUTH_GOOGLE_METHOD: process.env.AUTH_GOOGLE_METHOD === "true",
|
||||||
|
|
||||||
S3_ENDPOINT: process.env.S3_ENDPOINT,
|
S3_ENDPOINT: process.env.S3_ENDPOINT,
|
||||||
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
|
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
|
"use client"
|
||||||
import {createAuthClient} from "better-auth/react";
|
import {createAuthClient} from "better-auth/react";
|
||||||
|
|
||||||
import {adminClient, inferAdditionalFields, organizationClient} from "better-auth/client/plugins";
|
import {adminClient, inferAdditionalFields, organizationClient} from "better-auth/client/plugins";
|
||||||
import {env} from "@/env.mjs";
|
|
||||||
import {ac, user, admin as adminRole, pending, superadmin, orgAdmin, orgMember, orgOwner} from "./permissions";
|
import {ac, user, admin as adminRole, pending, superadmin, orgAdmin, orgMember, orgOwner} from "./permissions";
|
||||||
import {auth} from "@/lib/auth/auth";
|
import {auth} from "@/lib/auth/auth";
|
||||||
|
import {getServerUrl} from "@/utils/get-server-url";
|
||||||
|
|
||||||
|
const res = await fetch(`${getServerUrl()}/api/config`);
|
||||||
|
const {PROJECT_URL} = await res.json();
|
||||||
|
console.log(PROJECT_URL);
|
||||||
|
|
||||||
export const authClient = createAuthClient({
|
export const authClient = createAuthClient({
|
||||||
baseURL: env.NEXT_PUBLIC_PROJECT_URL,
|
baseURL: PROJECT_URL,
|
||||||
plugins: [
|
plugins: [
|
||||||
organizationClient({
|
organizationClient({
|
||||||
ac,
|
ac,
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ export const auth = betterAuth({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},*/
|
},*/
|
||||||
trustedOrigins: [env.NEXT_PUBLIC_PROJECT_URL!, "http://app"],
|
trustedOrigins: [env.PROJECT_URL!, "http://app"],
|
||||||
});
|
});
|
||||||
|
|
||||||
/*export const signUpUser = async (email: string, password: string, name: string) => {
|
/*export const signUpUser = async (email: string, password: string, name: string) => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const getServerUrl = () => {
|
|||||||
return window.location.origin;
|
return window.location.origin;
|
||||||
}
|
}
|
||||||
if (env.NODE_ENV === "development") {
|
if (env.NODE_ENV === "development") {
|
||||||
return `${env.NEXT_PUBLIC_PROJECT_URL}`;
|
return `${env.PROJECT_URL}`;
|
||||||
}
|
}
|
||||||
return `${env.NEXT_PUBLIC_PROJECT_URL}`;
|
return `${env.PROJECT_URL}`;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user