Skip to content
Merged
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Foundational mandates here take absolute precedence. **NEVER** invent ad-hoc beh

- **Type Integrity**: Always run `bun sb:typegen` after schema changes. **Prefer** importing types from `packages/types/src/db.ts`.
- **Bilingual Support**: ALWAYS provide translations for both English (en.json) AND Vietnamese (vi.json) for all user-facing strings.
- **Shared UI Translation Parity**: When adding new translation keys consumed from shared packages like `packages/ui` (for example `common.*` keys), update every app-level `messages/en.json` and `messages/vi.json` bundle that ships that shared UI, and keep the message files alphabetically sorted.
- **Navigation Parity**: ALWAYS update `navigation.tsx` in the relevant app when adding new routes (aliases + children + icons + permissions).
- **Proactive Refactoring**: Evaluate files >400 LOC and components >200 LOC for extraction into smaller, focused units.
- **Unified Verification**: Always end your session with a `bun check`. Ensure all checks pass (you may ignore ones that were not introduced by you). For mobile changes, run `bun check:mobile`.
Expand Down
2 changes: 2 additions & 0 deletions apps/calendar/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1692,6 +1692,8 @@
"archive_tasks_confirmation": "Are you sure you want to archive all {count} task(s) from this list? They will be moved to the archive.",
"archived": "Archived",
"archived_boards": "Archived Boards",
"archived_tasks_partially": "{moved, plural, one {Archived 1 task} other {Archived # tasks}}, {failed, plural, one {1 task failed} other {# tasks failed}}",
"archived_tasks_successfully": "{count, plural, one {Archived 1 task successfully} other {Archived # tasks successfully}}",
"archiving": "Archiving...",
"are_you_sure": "Are you sure?",
"ascending": "Ascending",
Expand Down
2 changes: 2 additions & 0 deletions apps/calendar/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1692,6 +1692,8 @@
"archive_tasks_confirmation": "Bạn có chắc chắn muốn lưu trữ tất cả {count} công việc từ danh sách này không?",
"archived": "Đã lưu trữ",
"archived_boards": "Bảng đã lưu trữ",
"archived_tasks_partially": "{moved, plural, one {Đã lưu trữ 1 công việc} other {Đã lưu trữ # công việc}}, {failed, plural, one {1 công việc thất bại} other {# công việc thất bại}}",
"archived_tasks_successfully": "{count, plural, one {Đã lưu trữ thành công 1 công việc} other {Đã lưu trữ thành công # công việc}}",
"archiving": "Đang lưu trữ...",
"are_you_sure": "Bạn có chắc chắn không?",
"ascending": "Tăng dần",
Expand Down
2 changes: 2 additions & 0 deletions apps/finance/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,8 @@
"archive_tasks_confirmation": "Are you sure you want to archive all {count} task(s) from this list? They will be moved to the archive.",
"archived": "Archived",
"archived_boards": "Archived Boards",
"archived_tasks_partially": "{moved, plural, one {Archived 1 task} other {Archived # tasks}}, {failed, plural, one {1 task failed} other {# tasks failed}}",
"archived_tasks_successfully": "{count, plural, one {Archived 1 task successfully} other {Archived # tasks successfully}}",
"archiving": "Archiving...",
"are_you_sure": "Are you sure?",
"ascending": "Ascending",
Expand Down
2 changes: 2 additions & 0 deletions apps/finance/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,8 @@
"archive_tasks_confirmation": "Bạn có chắc chắn muốn lưu trữ tất cả {count} công việc từ danh sách này không?",
"archived": "Đã lưu trữ",
"archived_boards": "Bảng đã lưu trữ",
"archived_tasks_partially": "{moved, plural, one {Đã lưu trữ 1 công việc} other {Đã lưu trữ # công việc}}, {failed, plural, one {1 công việc thất bại} other {# công việc thất bại}}",
"archived_tasks_successfully": "{count, plural, one {Đã lưu trữ thành công 1 công việc} other {Đã lưu trữ thành công # công việc}}",
"archiving": "Đang lưu trữ...",
"are_you_sure": "Bạn có chắc chắn không?",
"ascending": "Tăng dần",
Expand Down
2 changes: 2 additions & 0 deletions apps/rewise/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,8 @@
"archive_tasks_confirmation": "Are you sure you want to archive all {count} task(s) from this list? They will be moved to the archive.",
"archived": "Archived",
"archived_boards": "Archived Boards",
"archived_tasks_partially": "{moved, plural, one {Archived 1 task} other {Archived # tasks}}, {failed, plural, one {1 task failed} other {# tasks failed}}",
"archived_tasks_successfully": "{count, plural, one {Archived 1 task successfully} other {Archived # tasks successfully}}",
"archiving": "Archiving...",
"are_you_sure": "Are you sure?",
"ascending": "Ascending",
Expand Down
2 changes: 2 additions & 0 deletions apps/rewise/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,8 @@
"archive_tasks_confirmation": "Bạn có chắc chắn muốn lưu trữ tất cả {count} công việc từ danh sách này không?",
"archived": "Đã lưu trữ",
"archived_boards": "Bảng đã lưu trữ",
"archived_tasks_partially": "{moved, plural, one {Đã lưu trữ 1 công việc} other {Đã lưu trữ # công việc}}, {failed, plural, one {1 công việc thất bại} other {# công việc thất bại}}",
"archived_tasks_successfully": "{count, plural, one {Đã lưu trữ thành công 1 công việc} other {Đã lưu trữ thành công # công việc}}",
"archiving": "Đang lưu trữ...",
"are_you_sure": "Bạn có chắc chắn không?",
"ascending": "Tăng dần",
Expand Down
2 changes: 2 additions & 0 deletions apps/tasks/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1774,6 +1774,8 @@
"archive_tasks_confirmation": "Are you sure you want to archive all {count} task(s) from this list? They will be moved to the archive.",
"archived": "Archived",
"archived_boards": "Archived Boards",
"archived_tasks_partially": "{moved, plural, one {Archived 1 task} other {Archived # tasks}}, {failed, plural, one {1 task failed} other {# tasks failed}}",
"archived_tasks_successfully": "{count, plural, one {Archived 1 task successfully} other {Archived # tasks successfully}}",
"archiving": "Archiving...",
"are_you_sure": "Are you sure?",
"ascending": "Ascending",
Expand Down
2 changes: 2 additions & 0 deletions apps/tasks/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1774,6 +1774,8 @@
"archive_tasks_confirmation": "Bạn có chắc chắn muốn lưu trữ tất cả {count} công việc từ danh sách này không?",
"archived": "Đã lưu trữ",
"archived_boards": "Bảng đã lưu trữ",
"archived_tasks_partially": "{moved, plural, one {Đã lưu trữ 1 công việc} other {Đã lưu trữ # công việc}}, {failed, plural, one {1 công việc thất bại} other {# công việc thất bại}}",
"archived_tasks_successfully": "{count, plural, one {Đã lưu trữ thành công 1 công việc} other {Đã lưu trữ thành công # công việc}}",
"archiving": "Đang lưu trữ...",
"are_you_sure": "Bạn có chắc chắn không?",
"ascending": "Tăng dần",
Expand Down
2 changes: 2 additions & 0 deletions apps/track/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,8 @@
"archive_tasks_confirmation": "Are you sure you want to archive all {count} task(s) from this list? They will be moved to the archive.",
"archived": "Archived",
"archived_boards": "Archived Boards",
"archived_tasks_partially": "{moved, plural, one {Archived 1 task} other {Archived # tasks}}, {failed, plural, one {1 task failed} other {# tasks failed}}",
"archived_tasks_successfully": "{count, plural, one {Archived 1 task successfully} other {Archived # tasks successfully}}",
"archiving": "Archiving...",
"are_you_sure": "Are you sure?",
"ascending": "Ascending",
Expand Down
2 changes: 2 additions & 0 deletions apps/track/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,8 @@
"archive_tasks_confirmation": "Bạn có chắc chắn muốn lưu trữ tất cả {count} công việc từ danh sách này không?",
"archived": "Đã lưu trữ",
"archived_boards": "Bảng đã lưu trữ",
"archived_tasks_partially": "{moved, plural, one {Đã lưu trữ 1 công việc} other {Đã lưu trữ # công việc}}, {failed, plural, one {1 công việc thất bại} other {# công việc thất bại}}",
"archived_tasks_successfully": "{count, plural, one {Đã lưu trữ thành công 1 công việc} other {Đã lưu trữ thành công # công việc}}",
"archiving": "Đang lưu trữ...",
"are_you_sure": "Bạn có chắc chắn không?",
"ascending": "Tăng dần",
Expand Down
2 changes: 2 additions & 0 deletions apps/web/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1935,6 +1935,8 @@
"archive_whiteboard_title": "Archive Whiteboard",
"archived": "Archived",
"archived_boards": "Archived Boards",
"archived_tasks_partially": "{moved, plural, one {Archived 1 task} other {Archived # tasks}}, {failed, plural, one {1 task failed} other {# tasks failed}}",
"archived_tasks_successfully": "{count, plural, one {Archived 1 task successfully} other {Archived # tasks successfully}}",
"archived_whiteboards": "Archived Whiteboards",
"archiving": "Archiving...",
"are_you_sure": "Are you sure?",
Expand Down
2 changes: 2 additions & 0 deletions apps/web/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1935,6 +1935,8 @@
"archive_whiteboard_title": "Lưu trữ bảng trắng",
"archived": "Đã lưu trữ",
"archived_boards": "Bảng đã lưu trữ",
"archived_tasks_partially": "{moved, plural, one {Đã lưu trữ 1 công việc} other {Đã lưu trữ # công việc}}, {failed, plural, one {1 công việc thất bại} other {# công việc thất bại}}",
"archived_tasks_successfully": "{count, plural, one {Đã lưu trữ thành công 1 công việc} other {Đã lưu trữ thành công # công việc}}",
"archived_whiteboards": "Bảng trắng đã lưu trữ",
"archiving": "Đang lưu trữ...",
"are_you_sure": "Bạn có chắc chắn không?",
Expand Down
41 changes: 41 additions & 0 deletions apps/web/src/app/api/v1/task-board-status-templates/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
createAdminClient,
createClient,
} from '@tuturuuu/supabase/next/server';
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
try {
const supabase = await createClient(request);
const {
data: { user },
error: authError,
} = await supabase.auth.getUser();

if (authError || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

const sbAdmin = await createAdminClient();
const { data: templates, error } = await sbAdmin
.from('task_board_status_templates')
.select('*')
.order('is_default', { ascending: false })
.order('name', { ascending: true });

if (error) {
return NextResponse.json(
{ error: 'Failed to fetch status templates' },
{ status: 500 }
);
}

return NextResponse.json({ templates: templates ?? [] });
} catch (error) {
console.error('Error fetching task board status templates:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
createClient,
} from '@tuturuuu/supabase/next/server';
import { createTask } from '@tuturuuu/utils/task-helper';
import { normalizeWorkspaceId } from '@tuturuuu/utils/workspace-helper';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { z } from 'zod';
Expand All @@ -24,9 +25,10 @@ export async function POST(
{ params }: { params: Promise<{ wsId: string; draftId: string }> }
) {
try {
const { wsId, draftId } = await params;
const supabase = await createClient();
const { wsId: rawWsId, draftId } = await params;
const supabase = await createClient(request);
const sbAdmin = await createAdminClient();
const wsId = await normalizeWorkspaceId(rawWsId, supabase);

const {
data: { user },
Expand All @@ -36,12 +38,23 @@ export async function POST(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

const { data: membership } = await supabase
const { data: membership, error: membershipError } = await supabase
.from('workspace_members')
.select('ws_id')
.eq('ws_id', wsId)
.eq('user_id', user.id)
.single();
.maybeSingle();

if (membershipError) {
console.error(
'Failed to verify workspace membership for draft conversion:',
membershipError
);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}

if (!membership) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
Expand Down Expand Up @@ -98,51 +111,26 @@ export async function POST(
? (draft.priority as ValidPriority)
: undefined;

const newTask = await createTask(supabase, listId, {
const newTask = await createTask(wsId, listId, {
name: draft.name,
description: draft.description || undefined,
priority,
start_date: draft.start_date || undefined,
end_date: draft.end_date || undefined,
estimation_points: draft.estimation_points ?? undefined,
assignee_ids: Array.isArray(draft.assignee_ids)
? (draft.assignee_ids as string[])
: [],
label_ids: Array.isArray(draft.label_ids)
? (draft.label_ids as string[])
: [],
project_ids: Array.isArray(
(draft as { project_ids?: unknown }).project_ids
)
? (((draft as { project_ids?: unknown }).project_ids as string[]) ?? [])
: [],
});

// Add assignees one by one to ensure triggers fire
const assigneeIds = (draft.assignee_ids as string[]) || [];
for (const userId of assigneeIds) {
const { error } = await supabase.from('task_assignees').insert({
task_id: newTask.id,
user_id: userId,
});
if (error) {
console.error(`Failed to add assignee ${userId}:`, error);
}
}

// Add labels one by one to ensure triggers fire
const labelIds = (draft.label_ids as string[]) || [];
for (const labelId of labelIds) {
const { error } = await supabase.from('task_labels').insert({
task_id: newTask.id,
label_id: labelId,
});
if (error) {
console.error(`Failed to add label ${labelId}:`, error);
}
}

// Add projects one by one to ensure triggers fire
const projectIds = ((draft as any).project_ids as string[]) || [];
for (const projectId of projectIds) {
const { error } = await supabase.from('task_project_tasks').insert({
task_id: newTask.id,
project_id: projectId,
});
if (error) {
console.error(`Failed to add project ${projectId}:`, error);
}
}

// Delete the draft
await sbAdmin
.from('task_drafts')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { google } from '@ai-sdk/google';
import { createClient } from '@tuturuuu/supabase/next/server';
import {
createAdminClient,
createClient,
} from '@tuturuuu/supabase/next/server';
import { normalizeWorkspaceId } from '@tuturuuu/utils/workspace-helper';
import { embed } from 'ai';
import { NextResponse } from 'next/server';

Expand All @@ -14,7 +18,7 @@ interface Params {
* Generate and update embedding for a specific task
* Requires GOOGLE_GENERATIVE_AI_API_KEY to be set
*/
export async function POST(_: Request, { params }: Params) {
export async function POST(request: Request, { params }: Params) {
try {
// Check if API key is available
const hasApiKey = !!process.env.GOOGLE_GENERATIVE_AI_API_KEY;
Expand All @@ -26,8 +30,9 @@ export async function POST(_: Request, { params }: Params) {
);
}

const supabase = await createClient();
const { taskId } = await params;
const supabase = await createClient(request);
const { wsId: rawWsId, taskId } = await params;
const wsId = await normalizeWorkspaceId(rawWsId, supabase);

// Check authentication
const {
Expand All @@ -38,14 +43,36 @@ export async function POST(_: Request, { params }: Params) {
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
}

const sbAdmin = await createAdminClient();

// Fetch the task
const { data: task, error: fetchError } = await supabase
const { data: task, error: fetchError } = await sbAdmin
.from('tasks')
.select('id, name, description')
.select(
`
id,
name,
description,
task_lists!inner(
workspace_boards!inner(
ws_id
)
)
`
)
.eq('id', taskId)
.single();
.eq('task_lists.workspace_boards.ws_id', wsId)
.maybeSingle();

if (fetchError) {
console.error('Error loading task for embedding generation:', fetchError);
return NextResponse.json(
{ message: 'Failed to load task' },
{ status: 500 }
);
}

if (fetchError || !task) {
if (!task) {
return NextResponse.json({ message: 'Task not found' }, { status: 404 });
}

Expand Down Expand Up @@ -80,10 +107,10 @@ export async function POST(_: Request, { params }: Params) {
});

// Update task with embedding
const { error: updateError } = await supabase
const { error: updateError } = await sbAdmin
.from('tasks')
.update({ embedding: JSON.stringify(embedding) })
.eq('id', taskId);
.eq('id', task.id);

if (updateError) {
console.error('Error updating task embedding:', updateError);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/command/add-task-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function AddTaskForm({
const workspaceId = wsId;

// Fetch board config for estimation settings
const { data: boardConfig } = useBoardConfig(selectedBoardId);
const { data: boardConfig } = useBoardConfig(selectedBoardId, wsId);

// Fetch workspace labels (only after board and list are selected)
const { data: workspaceLabels = [], isLoading: labelsLoading } = useQuery({
Expand Down
Loading
Loading