Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
19 changes: 17 additions & 2 deletions db/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { defineDb } from 'astro:db';
import { defineDb, defineTable, column } from 'astro:db';

const UserLocation = defineTable({
columns: {
id: column.number({ primaryKey: true }),
city: column.text(),
country: column.text(),
countryFlag: column.text(),
isHome: column.boolean({ default: false }),
isTravelling: column.boolean({ default: false }),
currentEvent: column.text({ optional: true }),
updatedAt: column.date({ default: new Date() })
}
});

// https://astro.build/db/config
export default defineDb({
tables: {}
tables: {
UserLocation
}
});
13 changes: 11 additions & 2 deletions db/seed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { db } from 'astro:db';
import { db, UserLocation } from 'astro:db';

// https://astro.build/db/seed
export default async function seed() {
// TODO
// Insert default home location
await db.insert(UserLocation).values({
id: 1,
city: 'Ghent',
country: 'Belgium',
countryFlag: '🇧🇪',
isHome: true,
isTravelling: false,
updatedAt: new Date()
});
}
69 changes: 69 additions & 0 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { defineAction, ActionError } from 'astro:actions';
import { z } from 'astro:schema';
import { db, UserLocation } from 'astro:db';

export const server = {
updateLocation: defineAction({
accept: 'form',
input: z.object({
city: z.string().min(1, 'City is required'),
country: z.string().min(1, 'Country is required'),
countryFlag: z.string().min(1, 'Country flag is required'),
isTravelling: z.boolean().optional().default(false),
currentEvent: z.string().optional(),
}),
handler: async (input) => {
try {
// Update location (delete and insert for simplicity since we only store one location)
await db.delete(UserLocation);
await db.insert(UserLocation).values({
id: 1,
city: input.city,
country: input.country,
countryFlag: input.countryFlag,
isHome: !input.isTravelling,
isTravelling: input.isTravelling,
currentEvent: input.currentEvent || null,
updatedAt: new Date()
});

return {
success: true,
message: 'Location updated successfully',
location: {
city: input.city,
country: input.country,
countryFlag: input.countryFlag,
isTravelling: input.isTravelling,
currentEvent: input.currentEvent
}
};
} catch (error) {
throw new ActionError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to update location'
});
}
}
}),

getCurrentLocation: defineAction({
input: z.object({}),
handler: async () => {
try {
const locations = await db.select().from(UserLocation).limit(1);
const location = locations[0] || null;

return {
success: true,
location
};
} catch (error) {
throw new ActionError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to get current location'
});
}
}
})
};
199 changes: 199 additions & 0 deletions src/components/admin/LocationAdmin.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
---
import { actions, isInputError } from 'astro:actions';

// Handle form result (only if form was submitted)
const result = Astro.getActionResult(actions.updateLocation);
const inputErrors = isInputError(result?.error) ? result.error.fields : {};

// Show success message if location was updated
const showSuccess = result && !result.error;

// Default location for form (will be loaded client-side)
const defaultLocation = {
city: 'Ghent',
country: 'Belgium',
countryFlag: '🇧🇪',
isHome: true,
isTravelling: false,
currentEvent: '',
};
---

<div
class='location-admin max-w-md mx-auto p-6 border border-gray-300 rounded-lg bg-white shadow-sm'
>
<h3 class='text-lg font-semibold mb-4'>Update Location</h3>

{
showSuccess && (
<div class='mb-4 p-3 bg-green-100 border border-green-300 text-green-800 rounded'>
Location updated successfully! ✅
</div>
)
}

{
result?.error && !isInputError(result.error) && (
<div class='mb-4 p-3 bg-red-100 border border-red-300 text-red-800 rounded'>
Error: {result.error.message}
</div>
)
}

<form method='POST' action={actions.updateLocation} class='space-y-4'>
<div>
<label for='city' class='block text-sm font-medium text-gray-700 mb-1'>
City
</label>
<input
type='text'
id='city'
name='city'
value={defaultLocation.city}
required
class='w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500'
/>
{
inputErrors.city && (
<p class='mt-1 text-sm text-red-600'>{inputErrors.city.join(', ')}</p>
)
}
</div>

<div>
<label for='country' class='block text-sm font-medium text-gray-700 mb-1'>
Country
</label>
<input
type='text'
id='country'
name='country'
value={defaultLocation.country}
required
class='w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500'
/>
{
inputErrors.country && (
<p class='mt-1 text-sm text-red-600'>
{inputErrors.country.join(', ')}
</p>
)
}
</div>

<div>
<label
for='countryFlag'
class='block text-sm font-medium text-gray-700 mb-1'
>
Country Flag (emoji)
</label>
<input
type='text'
id='countryFlag'
name='countryFlag'
value={defaultLocation.countryFlag}
required
placeholder='🇧🇪'
class='w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500'
/>
{
inputErrors.countryFlag && (
<p class='mt-1 text-sm text-red-600'>
{inputErrors.countryFlag.join(', ')}
</p>
)
}
</div>

<div class='flex items-center'>
<input
type='checkbox'
id='isTravelling'
name='isTravelling'
checked={defaultLocation.isTravelling}
class='h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded'
/>
<label for='isTravelling' class='ml-2 block text-sm text-gray-900'>
Currently travelling
</label>
</div>

<div>
<label
for='currentEvent'
class='block text-sm font-medium text-gray-700 mb-1'
>
Current Event (optional)
</label>
<input
type='text'
id='currentEvent'
name='currentEvent'
value={defaultLocation.currentEvent || ''}
placeholder='e.g., React Brussels Conference'
class='w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500'
/>
{
inputErrors.currentEvent && (
<p class='mt-1 text-sm text-red-600'>
{inputErrors.currentEvent.join(', ')}
</p>
)
}
</div>

<button
type='submit'
class='w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors'
>
Update Location
</button>
</form>
</div>

<script>
import { actions } from 'astro:actions';

// Load current location when page loads
document.addEventListener('DOMContentLoaded', async () => {
try {
const { data, error } = await actions.getCurrentLocation({});
if (!error && data?.location) {
const location = data.location;

// Update form fields with current location
const cityInput = document.getElementById('city') as HTMLInputElement;
const countryInput = document.getElementById(
'country'
) as HTMLInputElement;
const flagInput = document.getElementById(
'countryFlag'
) as HTMLInputElement;
const travellingInput = document.getElementById(
'isTravelling'
) as HTMLInputElement;
const eventInput = document.getElementById(
'currentEvent'
) as HTMLInputElement;

if (cityInput) cityInput.value = location.city;
if (countryInput) countryInput.value = location.country;
if (flagInput) flagInput.value = location.countryFlag;
if (travellingInput) travellingInput.checked = location.isTravelling;
if (eventInput) eventInput.value = location.currentEvent || '';
}
} catch (error) {
console.warn('Could not load current location:', error);
}
});
</script>

<style>
.location-admin {
font-family:
system-ui,
-apple-system,
sans-serif;
}
</style>
13 changes: 5 additions & 8 deletions src/components/home/AboutMe.astro
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
---
import { Image } from "astro:assets";
import { Button, Card } from "@eliancodes/brutal-ui";
import { Image } from 'astro:assets';
import { Button, Card } from '@eliancodes/brutal-ui';
import CurrentLocation from './CurrentLocation.astro';

import headshot from "../../assets/elian.jpg";
import headshot from '../../assets/elian.jpg';
---

<section id='about' class='grid md:grid-cols-8 gap-8 mt-4'>
Expand Down Expand Up @@ -44,11 +45,7 @@ import headshot from "../../assets/elian.jpg";
href='https://www.devs.gent/'>devs.gent</a
> meetup organizer
</p>
<p class='sanchez mt-4 mb-8 text-lg md:text-xl lg:text-3xl'>
Located in Ghent, 🇧🇪 <small class='text-sm'
>(or somewhere travelling)</small
>
</p>
<CurrentLocation server:defer />
<div class='flex gap-3 flex-wrap'>
<Button href='/events/'>See what I do &rarr;</Button>
<Button href='https://eliancodes.com' target='_blank'
Expand Down
29 changes: 29 additions & 0 deletions src/components/home/CurrentLocation.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
import { db, UserLocation } from 'astro:db';

let location = null;
try {
const locations = await db.select().from(UserLocation).limit(1);
location = locations[0];
} catch (error) {
console.warn('Could not connect to database during static generation');
}

// Default fallback if no location in DB
const defaultLocation = {
city: 'Ghent',
country: 'Belgium',
countryFlag: '🇧🇪',
isHome: true,
isTravelling: false,
currentEvent: null,
};

const loc = location || defaultLocation;
---

<p class='sanchez mt-4 mb-8 text-lg md:text-xl lg:text-3xl'>
Located in {loc.city}, {loc.countryFlag}
{loc.isTravelling && <small class='text-sm'>(currently travelling)</small>}
{loc.currentEvent && <small class='text-sm'>at {loc.currentEvent}</small>}
</p>
5 changes: 5 additions & 0 deletions src/content/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ const eventCollection = defineCollection({
link: z.string(),
name: z.string(),
img: image(),
location: z.object({
city: z.string(),
country: z.string(),
countryFlag: z.string(),
}).optional(),
}),
});

Expand Down
4 changes: 4 additions & 0 deletions src/content/events/23-09-13-qwik-paris.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ link: "https://www.meetup.com/qwik-paris/events/295786958/"
date: 2023-09-13
name: "Qwik Paris"
img: "../../assets/events/qwikparis.jpeg"
location:
city: "Paris"
country: "France"
countryFlag: "🇫🇷"
---
Loading