From be1e890330ca6a5fb3c0095695e8316a3603b1e6 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Fri, 26 Jun 2026 10:58:32 +0300 Subject: [PATCH] add media create modal --- frontend/rabbi_gerzi/src/stores/mediaSets.ts | 62 ++- frontend/rabbi_gerzi/src/views/MediaPage.vue | 435 ++++++++++++++++++- 2 files changed, 493 insertions(+), 4 deletions(-) diff --git a/frontend/rabbi_gerzi/src/stores/mediaSets.ts b/frontend/rabbi_gerzi/src/stores/mediaSets.ts index b899929..e869470 100644 --- a/frontend/rabbi_gerzi/src/stores/mediaSets.ts +++ b/frontend/rabbi_gerzi/src/stores/mediaSets.ts @@ -9,16 +9,32 @@ export interface MediaSet { rootElementId: number | null } +export interface CreateMediaSetInput { + name: string + description: string + iconImage: File +} + interface SetsResponse { sets: MediaSet[] } +interface CreateSetResponse { + set: MediaSet +} + +interface ErrorResponse { + error?: string +} + const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string export const useMediaSetsStore = defineStore('mediaSets', () => { const sets = ref([]) const isLoading = ref(false) const error = ref(null) + const isCreating = ref(false) + const createError = ref(null) async function fetchSets(): Promise { error.value = null @@ -43,5 +59,49 @@ export const useMediaSetsStore = defineStore('mediaSets', () => { } } - return { sets, isLoading, error, fetchSets } + async function createSet( + input: CreateMediaSetInput, + ): Promise { + createError.value = null + isCreating.value = true + + const formData = new FormData() + formData.append('name', input.name) + formData.append('description', input.description) + formData.append('iconImage', input.iconImage) + + try { + const response = await fetch(`${API_BASE_URL}/api/sets`, { + method: 'POST', + credentials: 'include', + body: formData, + }) + + if (!response.ok) { + const data: ErrorResponse = await response.json() + createError.value = data.error ?? 'Could not create media set' + return null + } + + const data: CreateSetResponse = await response.json() + sets.value = [...sets.value, data.set] + + return data.set + } catch { + createError.value = 'Network error - could not create media set' + return null + } finally { + isCreating.value = false + } + } + + return { + sets, + isLoading, + error, + isCreating, + createError, + fetchSets, + createSet, + } }) diff --git a/frontend/rabbi_gerzi/src/views/MediaPage.vue b/frontend/rabbi_gerzi/src/views/MediaPage.vue index c1880cd..513a329 100644 --- a/frontend/rabbi_gerzi/src/views/MediaPage.vue +++ b/frontend/rabbi_gerzi/src/views/MediaPage.vue @@ -1,15 +1,121 @@ + + @@ -183,6 +398,205 @@ a.media-page__card:focus-visible { line-height: 1.38; } +.media-page__create-card { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + border-style: dashed; + border-color: #b8a16a; + background: #fbf8ef; + color: var(--color-slate); +} + +.media-page__create-card:hover, +.media-page__create-card:focus-visible { + border-color: #8d7b4e; + background: #f7f0db; + box-shadow: 0 14px 35px rgb(44 44 44 / 8%); + transform: translateY(-2px); +} + +.media-page__create-card:focus-visible { + outline: 3px solid rgb(94 107 76 / 35%); + outline-offset: 4px; +} + +.media-page__create-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 140px; + height: 140px; + margin: 0 auto 2.65rem; + background: var(--color-white); + border: 1px solid #e5cf9f; + border-radius: 9999px; + color: var(--color-olive); +} + +.media-page__create-title { + color: #333333; + font-family: var(--font-serif); + font-size: clamp(2rem, 3.7vw, 2.4rem); + font-weight: 400; + line-height: 1.2; +} + +.media-page__create-description { + margin-top: 2.15rem; + color: var(--color-text-muted); + font-family: var(--font-serif); + font-size: clamp(1.25rem, 2.3vw, 1.45rem); + line-height: 1.38; +} + +.create-set-modal { + position: fixed; + inset: 0; + z-index: 20; + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; + background: rgb(31 31 31 / 46%); +} + +.create-set-modal__panel { + width: min(100%, 520px); + padding: 1.5rem; + background: var(--color-white); + border: 1px solid var(--color-border); + border-radius: 8px; + box-shadow: 0 24px 70px rgb(31 31 31 / 20%); +} + +.create-set-modal__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.create-set-modal__title { + margin: 0; + color: var(--color-slate); + font-family: var(--font-serif); + font-size: 1.6rem; + font-weight: 400; + line-height: 1.2; +} + +.create-set-modal__close { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.25rem; + height: 2.25rem; + padding: 0; + color: var(--color-slate); + background: var(--color-cream); + border: 1px solid var(--color-border); + border-radius: 9999px; +} + +.create-set-modal__form { + display: flex; + flex-direction: column; + gap: 1rem; + margin-top: 1.5rem; +} + +.create-set-modal__field { + display: flex; + flex-direction: column; + gap: 0.35rem; +} + +.create-set-modal__label { + color: var(--color-text-muted); + font-size: 0.9rem; +} + +.create-set-modal__input, +.create-set-modal__textarea { + width: 100%; + padding: 0.7rem 0.8rem; + color: var(--color-text); + background: var(--color-white); + border: 1px solid var(--color-border); + border-radius: 6px; + font-family: var(--font-sans); + font-size: 0.95rem; +} + +.create-set-modal__textarea { + resize: vertical; +} + +.create-set-modal__input:focus, +.create-set-modal__textarea:focus { + border-color: var(--color-olive); + outline: 3px solid rgb(94 107 76 / 18%); +} + +.create-set-modal__file { + width: 100%; + color: var(--color-text); + font-size: 0.92rem; +} + +.create-set-modal__file-name { + color: var(--color-text-muted); + font-size: 0.85rem; +} + +.create-set-modal__error { + margin: 0; + padding: 0.65rem 0.8rem; + color: #7c2d2d; + background: #fef2f2; + border: 1px solid #fecaca; + border-radius: 6px; + font-size: 0.9rem; +} + +.create-set-modal__actions { + display: flex; + justify-content: flex-end; + gap: 0.75rem; + flex-wrap: wrap; + margin-top: 0.5rem; +} + +.create-set-modal__secondary, +.create-set-modal__submit { + min-width: 7.5rem; + padding: 0.7rem 1rem; + border-radius: 6px; + font-size: 0.95rem; + font-weight: 600; +} + +.create-set-modal__secondary { + color: var(--color-slate); + background: var(--color-white); + border: 1px solid var(--color-border); +} + +.create-set-modal__submit { + color: var(--color-white); + background: var(--color-olive); + border: 1px solid var(--color-olive); +} + +.create-set-modal__secondary:disabled, +.create-set-modal__submit:disabled, +.create-set-modal__close:disabled { + cursor: not-allowed; + opacity: 0.65; +} + @media (max-width: 768px) { .media-page__main { padding: 3rem 1rem; @@ -207,5 +621,20 @@ a.media-page__card:focus-visible { height: 118px; margin-bottom: 2rem; } + + .media-page__create-icon { + width: 118px; + height: 118px; + margin-bottom: 2rem; + } + + .create-set-modal { + align-items: flex-start; + padding: 1rem; + } + + .create-set-modal__panel { + padding: 1.25rem; + } }