mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix
This commit is contained in:
+5
-2
@@ -1,4 +1,5 @@
|
||||
import BackButton from "@/components/common/back-button";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export default async function NotFound() {
|
||||
return (
|
||||
@@ -12,7 +13,9 @@ export default async function NotFound() {
|
||||
The content you are trying to view is not available.
|
||||
</p>
|
||||
</div>
|
||||
<BackButton>Go home</BackButton>
|
||||
<Button asChild>
|
||||
<Link href="/">Go home</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { db } from "@/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { and, eq, inArray, ne } from "drizzle-orm";
|
||||
import { member } from "@/db/schema/04_member";
|
||||
import { organization } from "@/db/schema/03_organization";
|
||||
|
||||
@@ -15,3 +15,15 @@ export async function getUserOrganization(userId: string) {
|
||||
where: eq(organization.id, memberRow.organizationId),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUserOwnOrganization(userId: string) {
|
||||
const memberRows = await db.query.member.findMany({
|
||||
columns: { organizationId: true },
|
||||
where: eq(member.userId, userId),
|
||||
});
|
||||
if (!memberRows.length) return null;
|
||||
const orgIds = memberRows.map((r) => r.organizationId);
|
||||
return db.query.organization.findFirst({
|
||||
where: and(inArray(organization.id, orgIds), ne(organization.slug, "default")),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useOnboarding } from "@onboardjs/react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
createOrganizationAction,
|
||||
getMyOrganizationAction,
|
||||
updateOrganizationAction,
|
||||
} from "@/features/organizations/organization.action";
|
||||
import { slugify } from "@/utils/slugify";
|
||||
@@ -18,18 +17,10 @@ export const useCreateOrg = () => {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) throw new Error("Organisation name is required");
|
||||
|
||||
let existingOrg = state?.context.flowData.org as
|
||||
const existingOrg = state?.context.flowData.org as
|
||||
| { id: string; name: string }
|
||||
| undefined;
|
||||
|
||||
if (!existingOrg) {
|
||||
const fetchResult = await getMyOrganizationAction({});
|
||||
const fetchData = fetchResult?.data;
|
||||
if (fetchData?.success && fetchData.value) {
|
||||
existingOrg = { id: fetchData.value.id, name: fetchData.value.name };
|
||||
}
|
||||
}
|
||||
|
||||
if (existingOrg) {
|
||||
const result = await updateOrganizationAction({
|
||||
organizationId: existingOrg.id,
|
||||
|
||||
@@ -7,7 +7,10 @@ import { member } from "@/db/schema/04_member";
|
||||
import { currentUser } from "@/lib/auth/current-user";
|
||||
import { getSettings } from "@/db/services/setting";
|
||||
import { hasUsers } from "@/db/services/user";
|
||||
import { getUserOrganization } from "@/db/services/organization";
|
||||
import {
|
||||
getUserOrganization,
|
||||
getUserOwnOrganization,
|
||||
} from "@/db/services/organization";
|
||||
import { getOrganizationProject } from "@/db/services/project";
|
||||
import { getOrganizationAgents } from "@/db/services/agent";
|
||||
import { getDatabasesSettings } from "@/db/services/database";
|
||||
@@ -20,6 +23,8 @@ import type {
|
||||
OnboardingFlowData,
|
||||
OnboardingMeta,
|
||||
} from "@/features/onboarding/types";
|
||||
import { getOAuthProviders } from "@/lib/auth/oauth";
|
||||
import { getOidcProviders } from "@/lib/auth/oidc";
|
||||
import { generateEdgeKey } from "@/utils/edge_key";
|
||||
import { getServerUrl } from "@/utils/get-server-url";
|
||||
|
||||
@@ -38,26 +43,33 @@ export async function resolveOnboardingState(): Promise<ResolvedOnboardingState>
|
||||
emailPasswordEnabled: env.AUTH_EMAIL_PASSWORD_ENABLED === "true",
|
||||
hasExistingUsers: false,
|
||||
ssoProviders: [],
|
||||
oauthProviders: [],
|
||||
defaultUserMode: !!(env.AUTH_DEFAULT_USER && env.AUTH_DEFAULT_PASSWORD),
|
||||
resumeStepId: "login",
|
||||
};
|
||||
|
||||
meta.hasExistingUsers = await hasUsers();
|
||||
|
||||
if (env.AUTH_OIDC_CLIENT) {
|
||||
for (const p of getOidcProviders()) {
|
||||
meta.ssoProviders.push({
|
||||
id: env.AUTH_OIDC_ID ?? "oidc",
|
||||
label: env.AUTH_OIDC_TITLE ?? "SSO",
|
||||
id: p.id,
|
||||
label: p.title,
|
||||
icon: p.icon,
|
||||
description: p.description || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
for (const p of getOAuthProviders()) {
|
||||
meta.oauthProviders.push({ id: p.id, label: p.title, icon: p.icon });
|
||||
}
|
||||
|
||||
const user = await currentUser();
|
||||
if (!user) {
|
||||
return { stepId: "login", flowData: { meta } };
|
||||
}
|
||||
|
||||
let org = await getUserOrganization(user.id);
|
||||
if (!org) {
|
||||
const inDefaultOrg = await getUserOrganization(user.id);
|
||||
if (!inDefaultOrg) {
|
||||
const defaultOrg = await db.query.organization.findFirst({
|
||||
where: eq(organization.slug, "default"),
|
||||
});
|
||||
@@ -67,13 +79,15 @@ export async function resolveOnboardingState(): Promise<ResolvedOnboardingState>
|
||||
organizationId: defaultOrg.id,
|
||||
role: "owner",
|
||||
});
|
||||
org = defaultOrg;
|
||||
} else {
|
||||
meta.resumeStepId = "preferences";
|
||||
return { stepId: "preferences", flowData: { meta } };
|
||||
}
|
||||
}
|
||||
|
||||
const org = await getUserOwnOrganization(user.id);
|
||||
if (!org) {
|
||||
meta.resumeStepId = "preferences";
|
||||
return { stepId: "preferences", flowData: { meta } };
|
||||
}
|
||||
|
||||
const orgData = { id: org.id, name: org.name };
|
||||
|
||||
const [notifierChannels, storageChannels, agents, project] =
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useMutation } from "@tanstack/react-query";
|
||||
import { z } from "zod";
|
||||
import { toast } from "sonner";
|
||||
import { Globe, Mail, KeyRound, ShieldAlert } from "lucide-react";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
@@ -38,8 +39,9 @@ export const StepLogin = () => {
|
||||
const passkeyEnabled = meta?.passkeyEnabled ?? false;
|
||||
const emailPasswordEnabled = meta?.emailPasswordEnabled ?? false;
|
||||
const hasAnySsoProvider = (meta?.ssoProviders?.length ?? 0) > 0;
|
||||
const hasAnyOAuthProvider = (meta?.oauthProviders?.length ?? 0) > 0;
|
||||
const hasAnyAuthMethod =
|
||||
emailPasswordEnabled || passkeyEnabled || hasAnySsoProvider;
|
||||
emailPasswordEnabled || passkeyEnabled || hasAnySsoProvider || hasAnyOAuthProvider;
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.user) {
|
||||
@@ -76,11 +78,21 @@ export const StepLogin = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const handleSso = async (providerId: string) => {
|
||||
const handleOAuth = async (providerId: string) => {
|
||||
const result = await signIn.social({
|
||||
provider: providerId as any,
|
||||
callbackURL: "/welcome",
|
||||
});
|
||||
if (result?.error)
|
||||
toast.error(result.error.message ?? "OAuth sign in failed");
|
||||
};
|
||||
|
||||
const handleSso = async (providerId: string) => {
|
||||
const result = await signIn.sso({
|
||||
providerId,
|
||||
providerType: "oidc",
|
||||
callbackURL: "/welcome",
|
||||
});
|
||||
if (result?.error)
|
||||
toast.error(result.error.message ?? "SSO sign in failed");
|
||||
};
|
||||
@@ -129,7 +141,7 @@ export const StepLogin = () => {
|
||||
Set up your instance by creating the first account.
|
||||
</p>
|
||||
</div>
|
||||
{hasAnySsoProvider && (
|
||||
{(hasAnySsoProvider || hasAnyOAuthProvider) && (
|
||||
<div className="flex flex-col gap-2">
|
||||
{meta?.ssoProviders.map((provider) => (
|
||||
<button
|
||||
@@ -139,7 +151,29 @@ export const StepLogin = () => {
|
||||
className="flex items-center gap-3 rounded-lg border border-border p-3 text-sm hover:bg-accent/50 hover:border-primary/20 transition-colors w-full"
|
||||
>
|
||||
<div className="size-9 rounded-md border bg-muted/50 shadow-sm flex items-center justify-center shrink-0">
|
||||
<Globe className="size-4 text-muted-foreground" />
|
||||
{provider.icon ? (
|
||||
<Icon icon={provider.icon} className="size-4" />
|
||||
) : (
|
||||
<Globe className="size-4 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-start text-left">
|
||||
<span>Continue with {provider.label}</span>
|
||||
{provider.description && (
|
||||
<span className="text-xs text-muted-foreground">{provider.description}</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
{meta?.oauthProviders.map((provider) => (
|
||||
<button
|
||||
key={provider.id}
|
||||
type="button"
|
||||
onClick={() => handleOAuth(provider.id)}
|
||||
className="flex items-center gap-3 rounded-lg border border-border p-3 text-sm hover:bg-accent/50 hover:border-primary/20 transition-colors w-full"
|
||||
>
|
||||
<div className="size-9 rounded-md border bg-muted/50 shadow-sm flex items-center justify-center shrink-0">
|
||||
<Icon icon={provider.icon} className="size-4" />
|
||||
</div>
|
||||
<span>Continue with {provider.label}</span>
|
||||
</button>
|
||||
@@ -174,7 +208,7 @@ export const StepLogin = () => {
|
||||
: "Your session expired. Sign in to continue where you left off."}
|
||||
</p>
|
||||
</div>
|
||||
{hasAnySsoProvider && !meta?.defaultUserMode && (
|
||||
{(hasAnySsoProvider || hasAnyOAuthProvider) && !meta?.defaultUserMode && (
|
||||
<div className="flex flex-col gap-2">
|
||||
{meta?.ssoProviders.map((provider) => (
|
||||
<button
|
||||
@@ -184,7 +218,29 @@ export const StepLogin = () => {
|
||||
className="flex items-center gap-3 rounded-lg border border-border p-3 text-sm hover:bg-accent/50 hover:border-primary/20 transition-colors w-full"
|
||||
>
|
||||
<div className="size-9 rounded-md border bg-muted/50 shadow-sm flex items-center justify-center shrink-0">
|
||||
<Globe className="size-4 text-muted-foreground" />
|
||||
{provider.icon ? (
|
||||
<Icon icon={provider.icon} className="size-4" />
|
||||
) : (
|
||||
<Globe className="size-4 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-start text-left">
|
||||
<span>Continue with {provider.label}</span>
|
||||
{provider.description && (
|
||||
<span className="text-xs text-muted-foreground">{provider.description}</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
{meta?.oauthProviders.map((provider) => (
|
||||
<button
|
||||
key={provider.id}
|
||||
type="button"
|
||||
onClick={() => handleOAuth(provider.id)}
|
||||
className="flex items-center gap-3 rounded-lg border border-border p-3 text-sm hover:bg-accent/50 hover:border-primary/20 transition-colors w-full"
|
||||
>
|
||||
<div className="size-9 rounded-md border bg-muted/50 shadow-sm flex items-center justify-center shrink-0">
|
||||
<Icon icon={provider.icon} className="size-4" />
|
||||
</div>
|
||||
<span>Continue with {provider.label}</span>
|
||||
</button>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
export type OnboardingSsoProvider = { id: string; label: string };
|
||||
export type OnboardingSsoProvider = { id: string; label: string; icon: string; description?: string };
|
||||
export type OnboardingOAuthProvider = { id: string; label: string; icon: string };
|
||||
|
||||
export type OnboardingMeta = {
|
||||
passkeyEnabled: boolean;
|
||||
hasExistingUsers: boolean;
|
||||
ssoProviders: OnboardingSsoProvider[];
|
||||
oauthProviders: OnboardingOAuthProvider[];
|
||||
defaultUserMode: boolean;
|
||||
resumeStepId: string;
|
||||
emailPasswordEnabled: boolean;
|
||||
|
||||
@@ -21,7 +21,7 @@ export function ProfileAppearance() {
|
||||
}
|
||||
|
||||
function ThemeSelector() {
|
||||
const {theme} = useTheme();
|
||||
const {theme, setTheme} = useTheme();
|
||||
|
||||
|
||||
return (
|
||||
@@ -39,6 +39,7 @@ function ThemeSelector() {
|
||||
isActive ? "border-primary bg-primary/5" : "border-muted/40"
|
||||
)}
|
||||
onClick={async () => {
|
||||
setTheme(item.value);
|
||||
await authClient.updateUser({theme: item.value});
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user