Skip to content

Commit b80ff1f

Browse files
committed
Add theme hard-delete and admin UI controls
Implement hardDelete in useDataThemes and wire a delete action: expose a delete emit and admin/owner visibility checks in ThemeCard, and make ThemeGallery use hardDelete. Also update package deps and include regenerated database.types.ts.
2 parents f831e03 + e69fb5f commit b80ff1f

File tree

7 files changed

+2972
-635
lines changed

7 files changed

+2972
-635
lines changed

app/components/Settings/ThemeCard.vue

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@ const props = defineProps<{
1616
const emit = defineEmits<{
1717
apply: []
1818
deprecate: []
19+
delete: []
1920
edit: []
2021
}>()
2122
2223
const userId = useUserId()
23-
const user = useSupabaseUser()
24+
const { user: userData } = useDataUser(userId, { includeRole: true })
25+
26+
const isAdmin = computed(() => userData.value?.role === 'admin')
27+
const isOwner = computed(() => userId.value != null && props.item.created_by === userId.value)
28+
const canSeeDropdown = computed(() => isOwner.value || isAdmin.value)
2429
2530
const confirmDeprecate = ref(false)
31+
const confirmDelete = ref(false)
2632
2733
// Fetch the forked theme name & author
2834
const fork = ref<{ name: string, created_by: string } | null>(null)
@@ -112,18 +118,21 @@ if (props.item.forked_from) {
112118
</template>
113119
Apply
114120
</Button>
115-
<Dropdown v-if="userId && props.item.created_by === userId">
121+
<Dropdown v-if="canSeeDropdown && !props.item.is_official">
116122
<template #trigger="{ toggle, isOpen }">
117123
<Button size="s" square :class="{ active: isOpen }" @click="toggle">
118124
<Icon name="ph:dots-three-bold" :size="18" />
119125
</Button>
120126
</template>
121-
<DropdownItem @click="emit('edit')">
127+
<DropdownItem v-if="isOwner" @click="emit('edit')">
122128
Edit
123129
</DropdownItem>
124-
<DropdownItem v-if="userId === props.item.created_by || user?.role === 'moderator' || user?.role === 'admin'" @click="confirmDeprecate = true">
130+
<DropdownItem v-if="!props.item.is_unmaintained && !props.item.is_official" @click="confirmDeprecate = true">
125131
Deprecate
126132
</DropdownItem>
133+
<DropdownItem v-if="isAdmin && props.item.is_unmaintained" class="text-danger" @click="confirmDelete = true">
134+
Delete
135+
</DropdownItem>
127136
</Dropdown>
128137
</ButtonGroup>
129138
</Flex>
@@ -135,11 +144,25 @@ if (props.item.forked_from) {
135144
title="Deprecate theme?"
136145
confirm-text="Deprecate"
137146
destructive
138-
@confirm="emit('deprecate')"
147+
@confirm="emit('deprecate'); confirmDeprecate = false"
139148
@cancel="confirmDeprecate = false"
140149
>
141150
<p class="mb-xs">
142-
Users who are currently using the theme will still be able to keep it, but it won't be visible.
151+
Users who are currently using the theme will still be able to keep it, but it won't be visible in the gallery.
152+
</p>
153+
<p>This action cannot be undone.</p>
154+
</ConfirmModal>
155+
156+
<ConfirmModal
157+
:open="confirmDelete"
158+
title="Delete theme?"
159+
confirm-text="Delete"
160+
destructive
161+
@confirm="emit('delete'); confirmDelete = false"
162+
@cancel="confirmDelete = false"
163+
>
164+
<p class="mb-xs">
165+
This will permanently delete <strong>{{ props.item.name }}</strong> and remove it for all users currently using it.
143166
</p>
144167
<p>This action cannot be undone.</p>
145168
</ConfirmModal>
@@ -279,6 +302,10 @@ if (props.item.forked_from) {
279302
}
280303
}
281304
305+
.text-danger {
306+
color: var(--color-text-red);
307+
}
308+
282309
:root.light .theme-menu__card--preview::before {
283310
background: linear-gradient(to top, rgba(8, 8, 8, 0.1), transparent);
284311
}

app/components/Settings/ThemeGallery.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const supabase = useSupabaseClient<Database>()
1414
const userId = useUserId()
1515
1616
const { activeTheme, setActiveTheme } = useUserTheme()
17-
const { softDelete } = useDataThemes()
17+
const { softDelete, hardDelete } = useDataThemes()
1818
1919
const activeTab = ref<'community' | 'official' | 'created'>('official')
2020
const search = ref('')
@@ -136,6 +136,16 @@ function deprecateTheme(id: string) {
136136
})
137137
}
138138
139+
function deleteTheme(id: string) {
140+
if (activeTheme.value?.id === id) {
141+
setActiveTheme(null)
142+
}
143+
144+
void hardDelete(id).then(() => {
145+
void fetchPage(activeTab.value, currentPage.value, search.value)
146+
})
147+
}
148+
139149
// Initial load
140150
onMounted(() => {
141151
void fetchPage(activeTab.value, currentPage.value, search.value)
@@ -228,6 +238,7 @@ defineExpose({ refresh })
228238
@apply="setActiveTheme(item.id)"
229239
@edit="emit('edit', item)"
230240
@deprecate="deprecateTheme(item.id)"
241+
@delete="deleteTheme(item.id)"
231242
/>
232243
</template>
233244
</Grid>

app/composables/useDataThemes.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,26 @@ export function useDataThemes() {
103103
return null
104104
}
105105

106+
/**
107+
* Hard-delete a theme row. Admin-only - RLS will reject this for regular users.
108+
* Removes the theme from the local ref immediately and invalidates the cache.
109+
* Returns an error string on failure, or null on success.
110+
*/
111+
async function hardDelete(id: string): Promise<string | null> {
112+
const { error: deleteError } = await supabase
113+
.from('themes')
114+
.delete()
115+
.eq('id', id)
116+
117+
if (deleteError)
118+
return deleteError.message
119+
120+
themes.value = themes.value.filter(t => t.id !== id)
121+
invalidate()
122+
123+
return null
124+
}
125+
106126
onMounted(() => {
107127
void fetch()
108128
})
@@ -115,5 +135,6 @@ export function useDataThemes() {
115135
refresh,
116136
invalidate,
117137
softDelete,
138+
hardDelete,
118139
}
119140
}

app/composables/useForumActivityFeed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export function useForumActivityFeed({
159159
const isTopic = !('discussion_topic_id' in item)
160160
const id = item.id
161161

162-
const title = (isTopic ? (item as Tables<'discussion_topics'>).name : (item).title) ?? (isTopic ? 'Topic' : 'Discussion')
162+
const title = (isTopic ? (item).name : (item).title) ?? (isTopic ? 'Topic' : 'Discussion')
163163

164164
return {
165165
id,

0 commit comments

Comments
 (0)