Member-management UX cleanup:
- Replaced window.confirm() with shadcn Dialog confirmation (matches
every other destructive action in the app).
- Toast feedback on success/error for both removal and role updates —
before, errors from the server (örn. 'Sahibi yalnızca başka bir sahip
kaldırabilir') were swallowed.
- New 'Ayrıl' (leave) button on the current user's own row — previously
there was no way for a member to leave a workspace except by being
removed by an admin.
Server (lib/appwrite/team-actions.ts):
- New leaveWorkspaceAction:
* Refuses if the caller is the only owner (would leave the workspace
ownerless).
* Calls teams.deleteMembership for the caller's own membership.
* Clears account.prefs.activeTenant + isletmem-tenant cookie so the
next request goes to fallback tenant or onboarding.
* Audit-logged with self:true marker.
- removeMemberAction unchanged (already had owner-only-can-remove-owner
+ can't-remove-self guards).
UI:
- Each row's action cell now shows: 'Ayrıl' (self) / 'Çıkar' (others, if
canManage and target isn't owner) / nothing (the rest).
- Removal dialog explains data isn't deleted, just access revoked.
- Leave dialog warns the user about losing access.
- Both dialogs gate close-on-outside-click while a request is in flight.
Multi-tenant invite system without SMTP dependency. Designed for dev/early
stage; promotes to email-driven later by adding SMTP to Appwrite.
New schema:
- invite_links table (code, email, role, status, expiresAt, invitedBy)
with unique index on code, indexes on (tenantId,status) and (tenantId,email)
New code:
- lib/appwrite/audit.ts: logAudit() helper writes to audit_logs with
X-Forwarded-For/User-Agent capture; never throws.
- lib/appwrite/tenant-guard.ts: requireTenant() returns
{ user, tenantId, role, settings }; pulls highest role from team
memberships. requireRole() guard.
- lib/appwrite/team-actions.ts:
* inviteMemberAction — creates short code (8 char nanoid-style),
inserts invite_links row with team-scoped perms, returns shortUrl.
Reuses existing pending invite for same email instead of duplicating.
Blocks self-invite, blocks invite of existing members.
* cancelInviteAction — owner/admin only, marks status=cancelled.
* removeMemberAction — owner/admin only; protects self-removal and
requires owner-on-owner.
* updateMemberRoleAction — owner only.
* resolveInviteCode — public-ish lookup by code (admin SDK).
* acceptInviteAction — verifies session.email matches invite.email,
creates membership via admin SDK, marks invite accepted.
All mutations write to audit_logs.
UI:
- /d/[code] short-URL accept page (server). Logged-in matching user
sees 'Daveti kabul et' button; non-matching user sees error; logged-out
user gets sign-up / sign-in CTAs that preserve the code.
- /settings/members page (server): InviteForm, PendingInvitesTable,
MembersTable. Owner/admin gates respected; only owner can change roles.
- Sign-up and sign-in forms accept ?invite=CODE (and ?email= for sign-up):
hidden input -> server action redirects to /d/CODE on success.
Other:
- next.config.ts: removed eslint config block (deprecated in Next 16);
kept typescript.ignoreBuildErrors for template legacy.