Skip to content

Commit 77c9992

Browse files
feat: add project archiving functionality with UI integration and backend support
1 parent 498b838 commit 77c9992

4 files changed

Lines changed: 44 additions & 4 deletions

File tree

apps/client/components/ProjectOptions.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
{{ pinned ? 'Unpin chat' : 'Pin chat' }}
108108
</button>
109109
<button
110+
@click="archiveProject"
110111
class="w-full flex items-center gap-2.5 px-2 py-1.5 text-[13px] font-medium rounded-lg text-foreground/80 hover:bg-orange-50 dark:hover:bg-orange-900/20 hover:text-foreground transition-colors"
111112
>
112113
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -148,6 +149,7 @@ const emit = defineEmits<{
148149
select: []
149150
active: [value: boolean]
150151
pin: [pinned: boolean]
152+
archive: []
151153
}>()
152154
153155
const { apiClient, API_ENDPOINTS } = useApi()
@@ -206,6 +208,11 @@ const togglePin = () => {
206208
emit('pin', !props.pinned)
207209
}
208210
211+
const archiveProject = () => {
212+
isDropdownOpen.value = false
213+
emit('archive')
214+
}
215+
209216
const cancelRename = () => {
210217
isRenaming.value = false
211218
renameValue.value = ''

apps/client/components/Sidebar.vue

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
@select="openProject(project.id)"
5353
@active="isDropdownOpen = $event"
5454
@pin="(pinned) => togglePin(project, pinned)"
55+
@archive="archiveProject(project)"
5556
/>
5657

5758
<!-- Load More Button -->
@@ -132,7 +133,7 @@ const fetchProjects = async (page = 1, append = false) => {
132133
isLoadingMore.value = true
133134
}
134135
try {
135-
const response = await apiClient.get(`${API_ENDPOINTS.projects.getAll}?page=${page}`)
136+
const response = await apiClient.get(`${API_ENDPOINTS.projects.getAll}?page=${page}&archived=false`)
136137
const newProjects = response.data.projects || []
137138
if (append) {
138139
projects.value = [...projects.value, ...newProjects]
@@ -158,6 +159,21 @@ const loadMore = () => {
158159
}
159160
}
160161
162+
const archiveProject = async (proj: Project) => {
163+
const archivedAt = Math.floor(Date.now() / 1000)
164+
projects.value = projects.value.filter(p => p.id !== proj.id)
165+
166+
try {
167+
await apiClient.patch(API_ENDPOINTS.projects.update(proj.id), { archivedAt })
168+
if (route.path === `/chat/${proj.id}`) {
169+
router.push('/')
170+
}
171+
} catch (err) {
172+
console.error('Failed to archive project:', err)
173+
await fetchProjects()
174+
}
175+
}
176+
161177
const togglePin = async (proj: Project, pinned: boolean) => {
162178
const previousPinnedAt = proj.pinnedAt
163179
const newPinnedAt = pinned ? Math.floor(Date.now() / 1000) : null

apps/server/src/routes/projects.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,24 @@ router.get('/', async (c) => {
1313
try {
1414
const page = parseInt(c.req.query('page') || '1')
1515
const limit = parseInt(c.req.query('limit') || '10')
16+
const archived = c.req.query('archived')
1617
const offset = (page - 1) * limit
1718

18-
const projects = await db
19+
const archiveFilter = archived === 'true'
20+
? isNotNull(project.archivedAt)
21+
: archived === 'false'
22+
? isNull(project.archivedAt)
23+
: undefined
24+
25+
const query = db
1926
.select()
2027
.from(project)
28+
29+
if (archiveFilter) {
30+
query.where(archiveFilter)
31+
}
32+
33+
const projects = await query
2134
.orderBy(
2235
sql`CASE WHEN ${project.pinnedAt} IS NOT NULL THEN 0 ELSE 1 END`,
2336
desc(project.pinnedAt),
@@ -138,14 +151,14 @@ router.patch('/:projectId', async (c) => {
138151
try {
139152
const projectId = c.req.param('projectId')
140153
const body = await c.req.json()
141-
const { model: modelId, name: newName, pinnedAt: pinnedAtValue } = body
154+
const { model: modelId, name: newName, pinnedAt: pinnedAtValue, archivedAt: archivedAtValue } = body
142155

143156
const [existing] = await db.select().from(project).where(eq(project.id, projectId)).limit(1)
144157
if (!existing) {
145158
return c.json({ error: 'Project not found' }, 404)
146159
}
147160

148-
const updates: Partial<{ name: string | null; model: string | null; pinnedAt: number | null; updatedAt: number }> = {
161+
const updates: Partial<{ name: string | null; model: string | null; pinnedAt: number | null; archivedAt: number | null; updatedAt: number }> = {
149162
updatedAt: Math.floor(Date.now() / 1000),
150163
}
151164
if (modelId !== undefined) {
@@ -157,6 +170,9 @@ router.patch('/:projectId', async (c) => {
157170
if (pinnedAtValue !== undefined) {
158171
updates.pinnedAt = pinnedAtValue
159172
}
173+
if (archivedAtValue !== undefined) {
174+
updates.archivedAt = archivedAtValue
175+
}
160176

161177
const [updated] = await db
162178
.update(project)

packages/db/src/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const project = pgTable('project', {
1919
name: varchar('name'),
2020
model: varchar('model'),
2121
pinnedAt: integer('pinned_at'),
22+
archivedAt: integer('archived_at'),
2223
createdAt: integer('created_at').notNull(),
2324
updatedAt: integer('updated_at').notNull()
2425
});

0 commit comments

Comments
 (0)