Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7414a13
feat(frontend): add command palette
lajczi Feb 28, 2026
e794f6a
chore(frontend): revert navbar changes
lajczi Feb 28, 2026
c8d4c4a
feat(frontend): add tasks to command palette
lajczi Feb 28, 2026
aeef881
feat(frontend): add more actions to command palette
lajczi Feb 28, 2026
11c21f3
feat(frontend): add keybind for submitting flag & make requested changes
lajczi Feb 28, 2026
dfb0bc7
fix: close command palette before opening submit flag modal
lajczi Feb 28, 2026
56b092f
chore(frontend): update empty state message
lajczi Feb 28, 2026
40a845c
chore: make all requested changes
lajczi Mar 3, 2026
778adc0
chore: fix lint
lajczi Mar 3, 2026
798bda6
Discard changes to frontend/app/pages/panel/index.vue
lajczi Mar 21, 2026
002ba6d
feat(cp): allow to collapse all categories
lajczi Apr 6, 2026
90d67df
chore: address review feedback
lajczi Apr 7, 2026
1070bda
Delete frontend/app/pages/panel/profile.vue
lajczi May 21, 2026
7ba475e
Discard changes to frontend/app/components/Navbar.vue
lajczi May 21, 2026
c867e79
Merge branch 'master' into issue/616
lajczi May 31, 2026
dddb4d9
update FlagForm component
lajczi May 31, 2026
1a0d45c
Discard changes to frontend/app/components/panel/tile/FlagForm.vue
lajczi May 31, 2026
5ff3c3c
...
lajczi May 31, 2026
ca18304
fix import
lajczi May 31, 2026
4020655
Merge branch 'Hack4Krak:master' into issue/616
lajczi Jun 12, 2026
290248d
Merge branch 'Hack4Krak:master' into issue/616
lajczi Jul 15, 2026
57fa8b5
fix lint
lajczi Jul 15, 2026
67bc41d
address feedback
lajczi Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions frontend/app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { setFavicon } from '~/utils/setFavicon'

useOgImage()

const isCommandPaletteOpen = useCommandPaletteState()
const isFlagModalOpen = useFlagModalState()

if (import.meta.client) {
useCommandPaletteShortcuts()
}

onMounted(() => {
setFavicon()
})
Expand All @@ -16,5 +23,8 @@ onMounted(() => {
<NuxtLayout>
<NuxtPage />
</NuxtLayout>

<LazyCommandPalette v-if="isCommandPaletteOpen" v-model="isCommandPaletteOpen" hydrate-on-idle />
<LazyFlagSubmitModal v-model="isFlagModalOpen" hydrate-on-idle />
</UApp>
</template>
194 changes: 194 additions & 0 deletions frontend/app/components/CommandPalette.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<script setup lang="ts">
import { LANDING_SOCIALS } from '~~/content/landing/socials'
import { NAVBAR_ITEMS } from '~~/content/navbar'

const isOpen = defineModel<boolean>({ default: false })

const { logout } = useSession()

function close() {
isOpen.value = false
}

const { data: tasks } = useLazyApi('/tasks/list')

const { data: completedTasks } = useAuth('/teams/membership/completed_tasks', {
onResponseError: undefined,
redirect: 'error',
})

const completedTaskIds = computed(() => new Set(
Array.isArray(completedTasks.value) ? completedTasks.value : [],
))

function openFlagModal() {
close()
openFlagSubmitModal()
}

async function handleLogout() {
close()
await logout()
}

const { data: isLoggedIn } = useAuth('/auth/status', {
redirect: 'error',
onResponseError: undefined,
})

const { data: team } = useAuth('/teams/membership/my_team', {
onResponseError: undefined,
redirect: 'error',
})

const collapsedGroups = useLocalStorage<string[]>('command-palette-collapsed-groups', [])

function isCollapsed(id: string) {
return collapsedGroups.value.includes(id)
}

function toggleGroup(id: string) {
if (isCollapsed(id)) {
collapsedGroups.value = collapsedGroups.value.filter(g => g !== id)
} else {
collapsedGroups.value.push(id)
}
}

const groups = computed(() => {
const rawGroups = [
{
id: 'tasks',
label: 'Zadania',
items: [
...(isLoggedIn.value && team.value?.team_name
? [{ label: 'Z艂贸偶 flag臋', icon: 'pixelarticons:flag', kbds: ['S', 'F'], onSelect: openFlagModal }]
: []),
...(tasks.value ?? []).filter(task => task.id).map(task => ({
id: `task-${task.id}`,
taskId: task.id,
label: task.name,
icon: 'pixelarticons:flag',
to: `/tasks/description/${task.id}`,
onSelect: close,
})),
],
},
{
id: 'navigation',
label: 'Nawigacja',
items: [
{ label: 'Strona g艂贸wna', icon: 'pixelarticons:home', to: '/', onSelect: close },
...NAVBAR_ITEMS.flat()
.filter(item => 'to' in item)
.map(item => ({ label: item.label, icon: 'pixelarticons:link', to: item.to, onSelect: close })),
...(isLoggedIn.value
? [{ label: 'Panel', icon: 'pixelarticons:dashboard', to: '/panel/', onSelect: close }]
: []),
],
},
{
id: 'account',
label: 'Konto',
items: [
...(!isLoggedIn.value
? [
{ label: 'Zaloguj si臋', icon: 'pixelarticons:login', to: '/login', onSelect: close },
{ label: 'Zarejestruj si臋', icon: 'pixelarticons:user-plus', to: '/register', onSelect: close },
]
: [
{ label: 'Profil', icon: 'pixelarticons:user', to: '/panel/profile', onSelect: close },
...(team.value?.team_name
? [{ label: 'Dru偶yna', icon: 'pixelarticons:users', to: '/panel/team', onSelect: close }]
: []),
{ label: 'Wyloguj si臋', icon: 'pixelarticons:logout', onSelect: handleLogout },
]),
],
},
{
id: 'contact',
label: 'Kontakt',
items: LANDING_SOCIALS.map(social => ({ ...social, onSelect: close })),
},
]

return rawGroups.map((group) => {
const headerItem = {
id: `${group.id}-header`,
label: group.label,
slot: 'group-header' as const,
ui: { item: 'cursor-pointer' },
onSelect(e: Event) {
e.preventDefault()
toggleGroup(group.id)
},
groupId: group.id,
}

return {
id: group.id,
items: [headerItem, ...group.items],
postFilter(searchTerm: string, filteredItems: any[]) {
const rest = filteredItems.filter(i => i.id !== headerItem.id)
if (!searchTerm && isCollapsed(group.id)) return [headerItem]
if (!searchTerm && group.id === 'tasks') {
const incomplete = rest.filter((i: any) => !i.taskId || !completedTaskIds.value.has(i.taskId))
return [headerItem, ...incomplete]
}
return [headerItem, ...rest]
},
}
})
})
</script>

<template>
<UModal v-model:open="isOpen" title="Paleta polece艅" description="Szukaj i nawiguj po stronie">
<template #content>
<UCommandPalette
:groups="groups"
placeholder="Szukaj..."
close
icon="pixelarticons:search"
@update:open="isOpen = $event"
>
<template #group-header-leading />
<template #group-header-label="{ item }">
<span class="font-semibold text-highlighted">{{ item.label }}</span>
</template>
<template #group-header-trailing="{ item }">
<UIcon
:name="isCollapsed(item.groupId) ? 'pixelarticons:chevron-down' : 'pixelarticons:chevron-up'"
class="size-5 text-primary"
/>
</template>
<template #empty>
Brak wynik贸w
</template>
<template #footer>
<div class="flex items-center justify-between gap-2 text-sm text-muted">
<div class="flex items-center gap-2">
<UKbd>
<UIcon name="pixelarticons:arrow-up" />
</UKbd>
<UKbd>
<UIcon name="pixelarticons:arrow-down" />
</UKbd>
<span>Nawigacja</span>
</div>
<div class="flex items-center gap-2">
<UKbd>
<UIcon name="pixelarticons:corner-down-left" />
</UKbd>
<span>Otw贸rz</span>
</div>
<div class="flex items-center gap-2">
<UKbd value="esc" />
<span>Zamknij</span>
</div>
</div>
</template>
</UCommandPalette>
</template>
</UModal>
</template>
11 changes: 11 additions & 0 deletions frontend/app/components/FlagSubmitModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
const isOpen = defineModel<boolean>({ default: false })
</script>

<template>
<UModal v-model:open="isOpen" title="Z艂贸偶 flag臋" description="Wpisz flag臋 w formacie hack4KrakCTF{...}" :ui="{ body: 'p-3 sm:p-4' }">
<template #body>
<PanelTileFlagForm :show-heading="false" @submitted="isOpen = false" />
</template>
</UModal>
</template>
6 changes: 5 additions & 1 deletion frontend/app/components/panel/tile/FlagForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import type { FormSubmitEvent } from '@nuxt/ui'
import * as party from 'party-js'

const { showHeading = true } = defineProps<{
showHeading?: boolean
}>()

const emit = defineEmits<{
submitted: []
}>()
Expand Down Expand Up @@ -92,7 +96,7 @@ onBeforeUnmount(() => {

<template>
<section class="relative overflow-hidden border-2 border-primary bg-default p-5 lg:p-6">
<div class="mb-4 flex items-center gap-2">
<div v-if="showHeading" class="mb-4 flex items-center gap-2">
<UIcon name="pixelarticons:flag" class="size-5 text-primary" />
<PanelSectionTitle>
Prze艣lij flag臋
Expand Down
32 changes: 32 additions & 0 deletions frontend/app/composables/useCommandPaletteUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export function useCommandPaletteState() {
return useState('command-palette-open', () => false)
}

export function useFlagModalState() {
return useState('flag-modal-open', () => false)
}

export function openFlagSubmitModal() {
const isFlagModalOpen = useFlagModalState()
isFlagModalOpen.value = true
}

export function useCommandPaletteShortcuts() {
const isCommandPaletteOpen = useCommandPaletteState()

defineShortcuts({
'meta_k': {
usingInput: true,
handler: () => {
isCommandPaletteOpen.value = !isCommandPaletteOpen.value
},
},
'/': () => {
isCommandPaletteOpen.value = true
},
's-f': () => {
isCommandPaletteOpen.value = false
openFlagSubmitModal()
},
})
}
2 changes: 2 additions & 0 deletions frontend/content/pages/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
title: Regulamin Hack4Krak - edycja dla Szk贸艂 艢rednich w Ma艂opolsce
description: Regulamin Hack4Krak - edycja dla Szk贸艂 艢rednich w Ma艂opolsce
---

::caution
Ten regulamin dotyczy poprzedniej edycji wydarzenia, kt贸ra odby艂a si臋 w **Maju 2026**.
::

## 1. Postanowienia og贸lne

1. Organizatorem Hack4Krak - edycji dla Szk贸艂 艢rednich w Ma艂opolsce (zwanego dalej CTF) jest Fundacja Ambasada Spo艂eczno艣ci z siedzib膮 przy ul. Zab艂ocie 20.22/207, 30-701 Krak贸w, wpisana do KRS pod numerem 0000764613.
Expand Down
Loading