Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ You are now ready to use/deploy the app!
> WARNING: You will see many warning messages during startup. This is normal. Ignore them!
> However, if any errors show up, they must be resolved.

Start the development server on `http://localhost:3000`:
Start the development server on `http://localhosnt:3000`:
Comment thread
KF212132 marked this conversation as resolved.
Outdated

```bash
pnpm dev
Expand Down
237 changes: 237 additions & 0 deletions app/pages/admin/donations.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
<script setup lang="ts">
import { ref, computed } from 'vue'

definePageMeta({
layout: 'secondary',
})

//Backend
const { data: fundsData, execute: refresh } = await useAsyncData(
'donations',
() => $fetch<any[]>('/api/admin/donations')
)
const funds = computed(() => fundsData.value ?? [])

//Make dates pretty
function formatShort(dateStr: string) {
if (!dateStr) return ''
const d = new Date(dateStr)
if (isNaN(d.getTime())) return dateStr
const mm = String(d.getMonth() + 1).padStart(2, '0')
const dd = String(d.getDate()+1).padStart(2, '0')
const yy = String(d.getFullYear()).slice(-2)
return `${mm}/${dd}/${yy}`
}

//New Fund Modal States
const open = ref(false)
const fundName = ref('')
const startDate = ref('')
const endDate = ref('')
const image = ref<File | null>(null)
const link = ref('')

//Editing Fund Modal States
const editOpen = ref(false)
const editFundName = ref('')
const editStartDate = ref('')
const editEndDate = ref('')
const editImage = ref<File | null>(null)
const editLink = ref('')
const editingFund = ref<typeof funds.value[0] | null>(null)
const editImagePrevies = ref('')


//New Fund Save
async function saveFund(close: () => void) {
//create the new fund on the backend
const newDonation = await $fetch<{ id: string }>('/api/admin/donations', {
method: 'POST',
body: {
name: fundName.value,
link: link.value,
startDate: startDate.value,
endDate: endDate.value,
},
})
//add the image if it has one
if (image.value) {
const formData = new FormData()
formData.append('file', image.value)
await $fetch(`/api/admin/donations/${newDonation.id}/image`, {
method: 'POST',
body: formData,
})
}
await refresh()
fundName.value = ''
startDate.value = ''
endDate.value = ''
image.value = null
link.value = ''
close()
}


//Editing Fund Modal
async function openEdit(fund: any) {
editingFund.value = fund
editFundName.value = fund.name
editStartDate.value = fund.startDate.split('T')[0] ?? ''
editEndDate.value = fund.endDate.split('T')[0] ?? ''
editLink.value = fund.link
editImagePrevies.value = fund.image
editOpen.value = true

}
//Editing Fund Save
async function saveEdit(close: () => void) {
if (!editingFund.value) return
await $fetch(`/api/admin/donations/${editingFund.value.id}`, {
method: 'PUT',
body: {
name: editFundName.value,
link: editLink.value,
startDate: editStartDate.value,
endDate: editEndDate.value,
imageUrl: editingFund.value?.image,
},
})
//add the image if it has one
if (editImage.value) {
const formData = new FormData()
formData.append('file', editImage.value)
await $fetch(`/api/admin/donations/${editingFund.value.id}/image`, {
method: 'POST',
body: formData,
})
}
await refresh()
editOpen.value = false
close()
}

//delete Fund
async function deleteFund(id: string) {
await $fetch(`/api/admin/donations/${id}`, {
method: 'DELETE',
})
await refresh()
}

//Create Image URL
function getImageUrl(fund: any) {
if (!fund.imageUrl) return '/images/image1.jpeg'
return `/api/admin/donations/${fund.id}/image/${fund.imageUrl.split('/').pop()}`
}

</script>
<template>
<div class="flex flex-col w-screen min-h-screen bg-slate-50 items-stretch pb-20">
<!-- Header -->
<div class="px-6 mt-20">
<h1 class="text-3xl font-bold text-[#313131]">Donation Funds</h1>
</div>

<!--New Button-->
<div class="px-6 mt-4 justify-end">
<UModal v-model:open="open" title="New Fund" :ui="{footer: 'justify-end'}">
<UButton
class="grid place-items-center rounded-xl h-9 w-9
border border-gray-800/70
hover:bg-gray-100/70
transition duration-200"
>
<UIcon name="i-heroicons-plus" class="w-5 h-5" />
</UButton>
<!--New Modal-->
<template #body>
<div class="space-y-4">
<UFormField label="Fund Name">
<UInput v-model="fundName" placeholder = "Name" color="brand4"/>
</UFormField>
<UFormField label="Link">
<UInput v-model="link" placeholder="Link" color="brand4" class="w-full"/>
</UFormField>
<UFormField label="Start Date">
<UInput v-model="startDate" placeholder="Start Date" type="date" color="brand4"/>
</UFormField>
<UFormField label="End Date">
<UInput v-model="endDate" placeholder="End Date" type="date" color="brand4"/>
</UFormField>
<UFormField label="Image">
<UFileUpload v-model="image" accept="image/*" placeholder="Upload image" color="brand4"/>
</UFormField>
</div>
</template>

<template #footer="{ close }">
<UButton label="Save" color="brand4" @click="saveFund(close)" />
</template>
</UModal>
</div>

<!--Funds Layout-->
<div class="px-6 mt-8">
<div class="grid grid-cols-2 gap-6">
<div
v-for="fund in funds"
:key="fund.id"
class="rounded-xl shadow-lg overflow-hidden hover:scale-95 transition-all duration-300 cursor-pointer"
@click="openEdit(fund)">

<!-- Fund Image -->
<div class="h-35 relative overflow-hidden">
<img
:src="fund.image"
:alt="fund.name"
class="w-full h-full object-cover"
>
</div>
<!-- Fund Content -->
<div class="p-2">
<h4 class="text-sm font_semibold text-brand4 mb-1.5"> {{ fund.name }}</h4>
<div class="space-y-2">
<!--Date Range-->
<div class="flex items-center text-gray-600 text-[12px]">
<svg class="w-4 h-4 mr-2 text-teal-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
</svg>
<span>{{ formatShort(fund.startDate) }} - {{ formatShort(fund.endDate) }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!--Edit Modal-->
<UModal v-model:open="editOpen" title="Edit Fund" :ui="{footer: 'justify-end'}">
<template #body>
<div class="space-y-4">
<UFormField label="Fund Name">
<UInput v-model="editFundName" placeholder = "Name" color="brand4"/>
</UFormField>
<UFormField label="Link">
<UInput v-model="editLink" placeholder="Link" color="brand4" class="w-full"/>
</UFormField>
<UFormField label="Start Date">
<UInput v-model="editStartDate" placeholder="Start Date" type="date" color="brand4"/>
</UFormField>
<UFormField label="End Date">
<UInput v-model="editEndDate" placeholder="End Date" type="date" color="brand4"/>
</UFormField>
<UFormField label="Image">
<UFileUpload v-model="editImage" accept="image/*" placeholder="Upload image" color="brand4"/>
</UFormField>
</div>
</template>
<template #footer="{ close }">
<UButton label="Delete" color="brand7" @click="editingFund && deleteFund(editingFund.id)" />
<UButton label="Save" color="brand4" @click="saveEdit(close)" />
</template>
</UModal>
</div>
</template>
Empty file added doc
Empty file.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"dependencies": {
"@internationalized/date": "^3.10.0",
"@nuxt/ui": "^4.1.0",
"@prisma/adapter-better-sqlite3": "^6.19.0",
"@prisma/client": "^7.0.0",
"@prisma/adapter-better-sqlite3": "^7.3.0",
"@prisma/client": "7.3.0",
"better-auth": "^1.3.25",
"dotenv": "^17.2.3",
"maplibre-gl": "^5.11.0",
Expand All @@ -31,6 +31,7 @@
"zod": "^4.1.12"
},
"devDependencies": {
"@iconify-json/heroicons": "^1.2.3",
"@nuxt/eslint": "^1.9.0",
"@types/node": "^24.3.1",
"autoprefixer": "^10.4.21",
Expand Down
Loading