feat: add PendingRewardDialog, RewardConfirmDialog, and TaskConfirmDialog components
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 25s

- Implemented PendingRewardDialog for handling pending reward requests.
- Created RewardConfirmDialog for confirming reward redemption.
- Developed TaskConfirmDialog for task confirmation with child name display.

test: add unit tests for ChildView and ParentView components

- Added comprehensive tests for ChildView including task triggering and SSE event handling.
- Implemented tests for ParentView focusing on override modal and SSE event management.

test: add ScrollingList component tests

- Created tests for ScrollingList to verify item fetching, loading states, and custom item classes.
- Included tests for two-step click interactions and edit button display logic.
- Moved toward hashed passwords.
This commit is contained in:
2026-02-10 20:21:05 -05:00
parent 3dee8b80a2
commit 401c21ad82
45 changed files with 4353 additions and 441 deletions

View File

@@ -11,6 +11,9 @@ const props = defineProps<{
isParentAuthenticated?: boolean
filterFn?: (item: any) => boolean
getItemClass?: (item: any) => string | string[] | Record<string, boolean>
enableEdit?: boolean
childId?: string
readyItemId?: string | null
}>()
// Compute the fetch URL with ids if present
@@ -24,6 +27,8 @@ const fetchUrl = computed(() => {
const emit = defineEmits<{
(e: 'trigger-item', item: any): void
(e: 'edit-item', item: any): void
(e: 'item-ready', itemId: string): void
}>()
const items = ref<any[]>([])
@@ -32,7 +37,6 @@ const error = ref<string | null>(null)
const scrollWrapper = ref<HTMLDivElement | null>(null)
const itemRefs = ref<Record<string, HTMLElement | Element | null>>({})
const lastCenteredItemId = ref<string | null>(null)
const readyItemId = ref<string | null>(null)
const fetchItems = async () => {
loading.value = true
@@ -112,23 +116,41 @@ const handleClicked = async (item: any) => {
const card = itemRefs.value[item.id]
if (!wrapper || !card) return
// If this item is already ready (has edit button showing)
if (props.readyItemId === item.id) {
// Second click - trigger the item and reset
emit(
'trigger-item',
items.value.find((i) => i.id === item.id),
)
emit('item-ready', '')
lastCenteredItemId.value = null
return
}
// First click or different item clicked
// Check if item needs to be centered
const wrapperRect = wrapper.getBoundingClientRect()
const cardRect = card.getBoundingClientRect()
const cardCenter = cardRect.left + cardRect.width / 2
const cardFullyVisible = cardCenter >= wrapperRect.left && cardCenter <= wrapperRect.right
if (!cardFullyVisible || lastCenteredItemId.value !== item.id) {
// Center the item, but don't trigger
if (!cardFullyVisible) {
// Center the item first
await centerItem(item.id)
lastCenteredItemId.value = item.id
readyItemId.value = item.id
return
}
emit(
'trigger-item',
items.value.find((i) => i.id === item.id),
)
readyItemId.value = null
// Mark this item as ready (show edit button)
lastCenteredItemId.value = item.id
emit('item-ready', item.id)
}
const handleEditClick = (item: any, event: Event) => {
event.stopPropagation()
emit('edit-item', item)
// Reset the 2-step process after opening edit modal
emit('item-ready', '')
lastCenteredItemId.value = null
}
watch(
@@ -160,6 +182,21 @@ onBeforeUnmount(() => {
:ref="(el) => (itemRefs[item.id] = el)"
@click.stop="handleClicked(item)"
>
<button
v-if="enableEdit && readyItemId === item.id"
class="edit-button"
@click="handleEditClick(item, $event)"
title="Edit custom value"
>
<img src="/edit.png" alt="Edit" />
</button>
<span
v-if="
isParentAuthenticated && item.custom_value !== undefined && item.custom_value !== null
"
class="override-badge"
>⭐</span
>
<slot name="item" :item="item">
<div class="item-name">{{ item.name }}</div>
</slot>
@@ -249,6 +286,45 @@ onBeforeUnmount(() => {
box-shadow: var(--item-card-hover-shadow, 0 8px 20px rgba(0, 0, 0, 0.12));
}
.edit-button {
position: absolute;
top: 4px;
right: 4px;
width: 34px;
height: 34px;
border: none;
background-color: var(--btn-primary);
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
transition:
opacity 0.2s,
transform 0.1s;
z-index: 10;
}
.edit-button:hover {
opacity: 0.9;
transform: scale(1.05);
}
.edit-button img {
width: 20px;
height: 20px;
filter: brightness(0) invert(1);
}
.override-badge {
position: absolute;
top: 4px;
left: 4px;
font-size: 12px;
z-index: 5;
}
@keyframes ready-glow {
0% {
box-shadow: 0 0 0 0 #667eea00;