fix: footer rebrand + iç sayfada genel-bakış'a yanlış redirect
1) Footer The template's 'Made with ❤ by ShadcnStore Team' is gone. Footer now shows '© <year> İşletmem — bir KovakSoft ürünüdür.' on the left and Kullanım şartları / Gizlilik links on the right. Compact one-line layout on desktop, stacked on mobile. 2) Internal pages were redirecting users to /onboarding (and from there to /dashboard) even when they had a valid tenant. Root cause: requireTenant() called getActiveTenantId() and threw NO_TENANT when the active-tenant cookie/prefs were missing — even though the user's actually a member of one or more teams. getActiveContext() already handled this fallback for the layout, but the per-page requireTenant() guard didn't, so /customers, /tasks, /invoices, etc. all bounced through /onboarding back to /dashboard. - requireTenant() now falls back to the user's first team via teams.list(), and writes the cookie so the next request is fast. 3) Side fix: acceptInviteAction now sets account.prefs.activeTenant + the ACTIVE_TENANT cookie after successful join, so a freshly invited member lands directly in the right workspace instead of relying on the team-list fallback.
This commit is contained in:
@@ -1,27 +1,32 @@
|
||||
import { Heart } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
export function SiteFooter() {
|
||||
const year = new Date().getFullYear()
|
||||
return (
|
||||
<footer className="border-t bg-background">
|
||||
<div className="px-4 py-6 lg:px-6">
|
||||
<div className="flex flex-col items-center justify-center space-y-2 text-center">
|
||||
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
||||
<span>Made with</span>
|
||||
<Heart className="h-4 w-4 fill-red-500 text-red-500" />
|
||||
<span>by</span>
|
||||
<footer className="bg-background border-t">
|
||||
<div className="px-4 py-4 lg:px-6">
|
||||
<div className="text-muted-foreground flex flex-col items-center justify-between gap-2 text-xs sm:flex-row">
|
||||
<p>
|
||||
© {year} İşletmem — bir{" "}
|
||||
<Link
|
||||
href="https://shadcnstore.com"
|
||||
href="https://kovaksoft.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-foreground hover:text-primary transition-colors"
|
||||
className="text-foreground hover:text-primary font-medium transition-colors"
|
||||
>
|
||||
ShadcnStore Team
|
||||
KovakSoft
|
||||
</Link>{" "}
|
||||
ürünüdür.
|
||||
</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href="#" className="hover:text-foreground transition-colors">
|
||||
Kullanım şartları
|
||||
</Link>
|
||||
<span aria-hidden>·</span>
|
||||
<Link href="#" className="hover:text-foreground transition-colors">
|
||||
Gizlilik
|
||||
</Link>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Building beautiful, accessible blocks, templates and dashboards for modern web applications.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { AppwriteException, ID, Permission, Query, Role } from "node-appwrite";
|
||||
|
||||
@@ -8,6 +9,7 @@ import { DATABASE_ID, TABLES, type InviteLink, type InviteRole } from "./schema"
|
||||
import { createAdminClient, createSessionClient } from "./server";
|
||||
import { requireRole, requireTenant } from "./tenant-guard";
|
||||
import type { InviteState, MemberActionState } from "./team-types";
|
||||
import { ACTIVE_TENANT_COOKIE } from "./tenant-types";
|
||||
|
||||
const APP_URL = process.env.APP_URL ?? "http://localhost:3000";
|
||||
const INVITE_TTL_DAYS = 7;
|
||||
@@ -306,7 +308,7 @@ export async function resolveInviteCode(code: string): Promise<InviteLink | null
|
||||
export async function acceptInviteAction(code: string): Promise<MemberActionState> {
|
||||
if (!code) return { ok: false, error: "Geçersiz davet linki." };
|
||||
|
||||
let user;
|
||||
let user: Awaited<ReturnType<Awaited<ReturnType<typeof createSessionClient>>["account"]["get"]>>;
|
||||
try {
|
||||
const session = await createSessionClient();
|
||||
user = await session.account.get();
|
||||
@@ -331,6 +333,30 @@ export async function acceptInviteAction(code: string): Promise<MemberActionStat
|
||||
|
||||
const admin = createAdminClient();
|
||||
|
||||
const activeTenantId = invite.tenantId;
|
||||
async function activateTenant() {
|
||||
try {
|
||||
const session = await createSessionClient();
|
||||
await session.account.updatePrefs({
|
||||
...(user.prefs ?? {}),
|
||||
activeTenant: activeTenantId,
|
||||
});
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
(await cookies()).set(ACTIVE_TENANT_COOKIE, activeTenantId, {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
maxAge: 60 * 60 * 24 * 365,
|
||||
});
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
// Already a member? Mark accepted, redirect.
|
||||
try {
|
||||
const memberships = await admin.teams.listMemberships(invite.tenantId);
|
||||
@@ -341,6 +367,7 @@ export async function acceptInviteAction(code: string): Promise<MemberActionStat
|
||||
acceptedAt: new Date().toISOString(),
|
||||
acceptedBy: user.$id,
|
||||
});
|
||||
await activateTenant();
|
||||
return { ok: true };
|
||||
}
|
||||
} catch {
|
||||
@@ -369,6 +396,8 @@ export async function acceptInviteAction(code: string): Promise<MemberActionStat
|
||||
entityId: invite.$id,
|
||||
changes: { via: "invite", code },
|
||||
});
|
||||
|
||||
await activateTenant();
|
||||
} catch (e) {
|
||||
return { ok: false, error: appwriteError(e) };
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import "server-only";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import { Query } from "node-appwrite";
|
||||
|
||||
import { createAdminClient, getCurrentUser } from "./server";
|
||||
import { DATABASE_ID, TABLES, type TenantSettings } from "./schema";
|
||||
import { getActiveTenantId } from "./tenant";
|
||||
import { ACTIVE_TENANT_COOKIE } from "./tenant-types";
|
||||
import { getActiveTenantId, getUserTeams } from "./tenant";
|
||||
|
||||
export type TenantRole = "owner" | "admin" | "member";
|
||||
|
||||
@@ -26,7 +28,26 @@ export async function requireTenant(): Promise<TenantContext> {
|
||||
const user = await getCurrentUser();
|
||||
if (!user) throw new Error("UNAUTHENTICATED");
|
||||
|
||||
const tenantId = await getActiveTenantId();
|
||||
let tenantId = await getActiveTenantId();
|
||||
if (!tenantId) {
|
||||
// Fallback: pick the user's first team (handles invite acceptees and
|
||||
// sessions where the active-tenant cookie/prefs weren't set yet).
|
||||
const userTeams = await getUserTeams();
|
||||
tenantId = userTeams?.teams[0]?.$id ?? null;
|
||||
if (tenantId) {
|
||||
try {
|
||||
(await cookies()).set(ACTIVE_TENANT_COOKIE, tenantId, {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
maxAge: 60 * 60 * 24 * 365,
|
||||
});
|
||||
} catch {
|
||||
/* setting cookie can fail in some Server Component paths; ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!tenantId) throw new Error("NO_TENANT");
|
||||
|
||||
const { tablesDB, teams } = createAdminClient();
|
||||
|
||||
Reference in New Issue
Block a user