fix: store and populate memberKey for share invite link
This commit is contained in:
+1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+25
-25
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -10,8 +10,8 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Press+Start+2P&family=Silkscreen:wght@400;700&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D2ZOrmDk.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-COtT48NQ.css">
|
||||
<script type="module" crossorigin src="/assets/index-h2AVDMMH.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C1k3Ryr0.css">
|
||||
</head>
|
||||
<body class="bg-slate-50 text-slate-900 min-h-screen font-sans antialiased selection:bg-indigo-500 selection:text-white transition-colors duration-200">
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -960,9 +960,11 @@ export const BoardView: React.FC<BoardViewProps> = ({ boardId, hash: propHash, o
|
||||
id: board.id,
|
||||
title: board.title,
|
||||
adminKey: role === 'admin' ? key : '***',
|
||||
memberKey: role === 'member' ? key : '***',
|
||||
adminUrlFragment: `/b/${board.id}#admin=${role === 'admin' ? key : ''}`,
|
||||
memberUrlFragment: `/b/${board.id}#member=${role === 'member' ? key : ''}`,
|
||||
memberKey: role === 'admin' ? (board.memberKey || '') : (role === 'member' ? key : '***'),
|
||||
adminUrlFragment: role === 'admin' ? `/b/${board.id}#admin=${key}` : '',
|
||||
memberUrlFragment: role === 'admin'
|
||||
? (board.memberKey ? `/b/${board.id}#member=${board.memberKey}` : `/b/${board.id}#member=`)
|
||||
: `/b/${board.id}#member=${key}`,
|
||||
createdAt: board.createdAt,
|
||||
}}
|
||||
onEnterBoard={() => setShowShareModal(false)}
|
||||
|
||||
@@ -67,14 +67,15 @@ export const Navbar: React.FC<NavbarProps> = ({
|
||||
if (onLanguageChange) onLanguageChange(next);
|
||||
};
|
||||
|
||||
const handleSaveName = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (tempName.trim()) {
|
||||
setUserName(tempName.trim());
|
||||
setProfileState(getUserProfile());
|
||||
setIsEditingName(false);
|
||||
const handleSaveName = (e?: React.FormEvent) => {
|
||||
if (e) e.preventDefault();
|
||||
const trimmed = tempName.trim();
|
||||
if (trimmed) {
|
||||
const updated = setUserName(trimmed);
|
||||
setProfileState(updated);
|
||||
soundFx.playClick();
|
||||
}
|
||||
setIsEditingName(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -118,11 +119,19 @@ export const Navbar: React.FC<NavbarProps> = ({
|
||||
maxLength={18}
|
||||
value={tempName}
|
||||
onChange={(e) => setTempName(e.target.value)}
|
||||
className="bg-white dark:bg-slate-900 border border-indigo-500 text-slate-900 dark:text-indigo-300 text-xs px-2 py-1 rounded-md outline-none w-20 sm:w-32"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
setTempName(profile.userName);
|
||||
setIsEditingName(false);
|
||||
}
|
||||
}}
|
||||
className="bg-white dark:bg-slate-900 border border-indigo-500 text-slate-900 dark:text-indigo-300 text-xs px-2 py-1 rounded-md outline-none w-24 sm:w-36"
|
||||
autoFocus
|
||||
onBlur={() => setIsEditingName(false)}
|
||||
/>
|
||||
<button type="submit" className="bg-indigo-600 text-white text-[10px] font-bold px-1.5 py-1 rounded-md">
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-indigo-600 hover:bg-indigo-500 text-white text-[10px] font-bold px-2 py-1.5 rounded-md shadow-sm"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</form>
|
||||
@@ -130,13 +139,16 @@ export const Navbar: React.FC<NavbarProps> = ({
|
||||
<button
|
||||
onClick={() => {
|
||||
soundFx.playClick();
|
||||
setTempName(profile.userName);
|
||||
setIsEditingName(true);
|
||||
}}
|
||||
className="flex items-center gap-1 bg-slate-100 dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800 px-2 py-1.5 text-xs text-slate-700 dark:text-slate-200 rounded-lg hover:border-indigo-500 transition-all"
|
||||
title={t('changeName')}
|
||||
>
|
||||
<User size={12} className="text-indigo-600 dark:text-indigo-400 shrink-0" />
|
||||
<span className="max-w-[50px] xs:max-w-[70px] sm:max-w-[120px] truncate text-[11px]">{profile.userName}</span>
|
||||
<span className="max-w-[50px] xs:max-w-[70px] sm:max-w-[120px] truncate text-[11px] font-medium">
|
||||
{profile.userName}
|
||||
</span>
|
||||
<span className="text-[9px] text-slate-400">✏️</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -55,6 +55,8 @@ export interface Board {
|
||||
id: string;
|
||||
title: string;
|
||||
role: Role;
|
||||
memberKey?: string;
|
||||
memberUrlFragment?: string;
|
||||
autoMoveStale: boolean;
|
||||
columns: Column[];
|
||||
createdAt: string;
|
||||
|
||||
@@ -21,6 +21,7 @@ model Board {
|
||||
title String @default("Agile Kanban Board")
|
||||
adminKeyHash String
|
||||
memberKeyHash String
|
||||
memberKey String? @default("")
|
||||
autoMoveStale Boolean @default(false) // Auto move stale cards to Backlog
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -14,6 +14,7 @@ model Board {
|
||||
title String @default("Agile Kanban Board")
|
||||
adminKeyHash String
|
||||
memberKeyHash String
|
||||
memberKey String? @default("")
|
||||
autoMoveStale Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -13,11 +13,12 @@ export class BoardService {
|
||||
const adminKeyHash = hashKey(adminKey);
|
||||
const memberKeyHash = hashKey(memberKey);
|
||||
|
||||
const board = await prisma.board.create({
|
||||
const board = await (prisma.board as any).create({
|
||||
data: {
|
||||
title: title.trim() || 'Agile Kanban Board',
|
||||
adminKeyHash,
|
||||
memberKeyHash,
|
||||
memberKey,
|
||||
autoMoveStale: false,
|
||||
columns: {
|
||||
create: [
|
||||
@@ -90,22 +91,20 @@ export class BoardService {
|
||||
boardId: col.boardId,
|
||||
title: col.title,
|
||||
order: col.order,
|
||||
maxWipLimit: col.maxWipLimit || 0,
|
||||
autoStaleHours: col.autoStaleHours || 0,
|
||||
createdAt: col.createdAt.toISOString(),
|
||||
updatedAt: col.updatedAt.toISOString(),
|
||||
maxWipLimit: col.maxWipLimit,
|
||||
autoStaleHours: col.autoStaleHours,
|
||||
tickets: col.tickets.map((t) => {
|
||||
let parsedTags: string[] = [];
|
||||
let parsedSubtasks: Subtask[] = [];
|
||||
let tags: string[] = [];
|
||||
let subtasks: Subtask[] = [];
|
||||
try {
|
||||
parsedTags = JSON.parse(t.tags || '[]');
|
||||
tags = JSON.parse(t.tags || '[]');
|
||||
} catch {
|
||||
parsedTags = [];
|
||||
tags = [];
|
||||
}
|
||||
try {
|
||||
parsedSubtasks = JSON.parse(t.subtasks || '[]');
|
||||
subtasks = JSON.parse(t.subtasks || '[]');
|
||||
} catch {
|
||||
parsedSubtasks = [];
|
||||
subtasks = [];
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -115,8 +114,8 @@ export class BoardService {
|
||||
description: t.description,
|
||||
order: t.order,
|
||||
priority: t.priority as Priority,
|
||||
tags: parsedTags,
|
||||
subtasks: parsedSubtasks,
|
||||
tags,
|
||||
subtasks,
|
||||
dueDate: t.dueDate || null,
|
||||
isBlocked: t.isBlocked || false,
|
||||
blockedReason: t.blockedReason || null,
|
||||
@@ -128,10 +127,26 @@ export class BoardService {
|
||||
}),
|
||||
}));
|
||||
|
||||
let memberKeyVal = (board as any).memberKey || '';
|
||||
if (!memberKeyVal && role === 'admin') {
|
||||
const newMemberKey = generateSecureKey(32);
|
||||
const newMemberKeyHash = hashKey(newMemberKey);
|
||||
await (prisma.board as any).update({
|
||||
where: { id: boardId },
|
||||
data: {
|
||||
memberKey: newMemberKey,
|
||||
memberKeyHash: newMemberKeyHash,
|
||||
},
|
||||
});
|
||||
memberKeyVal = newMemberKey;
|
||||
}
|
||||
|
||||
return {
|
||||
id: board.id,
|
||||
title: board.title,
|
||||
role,
|
||||
memberKey: memberKeyVal,
|
||||
memberUrlFragment: memberKeyVal ? `/b/${board.id}#member=${memberKeyVal}` : '',
|
||||
autoMoveStale: board.autoMoveStale || false,
|
||||
columns,
|
||||
createdAt: board.createdAt.toISOString(),
|
||||
@@ -146,10 +161,11 @@ export class BoardService {
|
||||
const newMemberKey = generateSecureKey(32);
|
||||
const newMemberKeyHash = hashKey(newMemberKey);
|
||||
|
||||
await prisma.board.update({
|
||||
await (prisma.board as any).update({
|
||||
where: { id: boardId },
|
||||
data: {
|
||||
memberKeyHash: newMemberKeyHash,
|
||||
memberKey: newMemberKey,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ export interface BoardPayload {
|
||||
id: string;
|
||||
title: string;
|
||||
role: Role;
|
||||
memberKey?: string;
|
||||
memberUrlFragment?: string;
|
||||
autoMoveStale: boolean;
|
||||
columns: ColumnPayload[];
|
||||
createdAt: string;
|
||||
|
||||
Reference in New Issue
Block a user