mirror of
https://github.com/polaroi8d/cactoide.git
synced 2026-03-21 21:55:27 +00:00
Compare commits
13 Commits
feat/landi
...
fix/date-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
accfd540f0 | ||
|
|
9b1ef64618 | ||
|
|
c340088434 | ||
|
|
984c296725 | ||
|
|
9acfa08ea8 | ||
|
|
45cb95f6a8 | ||
|
|
8426bd5704 | ||
|
|
b9833db3bb | ||
|
|
b3572293ba | ||
|
|
c1752efe4b | ||
|
|
491d0020bd | ||
|
|
5c1182dc66 | ||
|
|
638b5ff1ca |
@@ -2,7 +2,7 @@ FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json .
|
||||
|
||||
RUN npm ci --omit=dev
|
||||
RUN npm ci
|
||||
|
||||
ARG PUBLIC_LANDING_INFO
|
||||
ENV PUBLIC_LANDING_INFO=$PUBLIC_LANDING_INFO
|
||||
|
||||
13
README.md
13
README.md
@@ -82,6 +82,19 @@ make i18n
|
||||
make i18n FILE=src/lib/i18n/it.json
|
||||
```
|
||||
|
||||
### Support
|
||||
|
||||
Cactoide is an open-source project licensed under `AGPL-3.0`. Its growth and development are possible thanks to the amazing support of the community. This project is the result of many late nights, weekends, and after-hours work.
|
||||
|
||||
It isn’t backed by a big company. Development depends on the support and generosity of people like you. With your help, I can focus more on making Cactoide even better and building tools that make coding more enjoyable.
|
||||
|
||||
You can support in a few ways:
|
||||
|
||||
- Send a one-time donation via [paypal.me/zenoazurben](paypal.me/zenoazurben)
|
||||
- Reach me directly: leventeorb[@]gmail.com
|
||||
|
||||
If you enjoy using Cactoide, or if your business depends on it, please consider sponsoring its development. Your support keeps the project alive, improves it for everyone, and helps create educational content like blog posts and videos for the whole Cactoide community.
|
||||
|
||||
### License
|
||||
|
||||
This project is licensed under the `AGPL-3.0 License` - see the [LICENSE](./LICENSE) file for details.
|
||||
|
||||
92
package-lock.json
generated
92
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "event-cactus",
|
||||
"version": "0.0.1",
|
||||
"version": "0.1.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "event-cactus",
|
||||
"version": "0.0.1",
|
||||
"version": "0.1.1",
|
||||
"dependencies": {
|
||||
"@sveltejs/adapter-node": "^5.3.1",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
@@ -35,7 +35,7 @@
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.20.0",
|
||||
"vite": "^7.0.4"
|
||||
"vite": "^7.1.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@drizzle-team/brocli": {
|
||||
@@ -1949,6 +1949,66 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.4.5",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.0.4",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.4.5",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.0.4",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "0.2.12",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.4.3",
|
||||
"@emnapi/runtime": "^1.4.3",
|
||||
"@tybys/wasm-util": "^0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||
"version": "2.8.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
|
||||
@@ -2602,9 +2662,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
|
||||
"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==",
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.4.1.tgz",
|
||||
"integrity": "sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/drizzle-kit": {
|
||||
@@ -4737,13 +4797,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.14",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
||||
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2"
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
@@ -4864,17 +4924,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz",
|
||||
"integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
|
||||
"version": "7.1.10",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz",
|
||||
"integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.43.0",
|
||||
"tinyglobby": "^0.2.14"
|
||||
"tinyglobby": "^0.2.15"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.20.0",
|
||||
"vite": "^7.0.4"
|
||||
"vite": "^7.1.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/adapter-node": "^5.3.1",
|
||||
|
||||
22
src/hooks.server.ts
Normal file
22
src/hooks.server.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
// src/hooks.server.ts
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { generateUserId } from '$lib/generateUserId.js';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
const cactoideUserId = event.cookies.get('cactoideUserId');
|
||||
const userId = generateUserId();
|
||||
|
||||
const DAYS = 400; // practical upper bound in many browsers for cookies
|
||||
const MAX_AGE = 60 * 60 * 24 * DAYS;
|
||||
const PATH = '/';
|
||||
|
||||
if (!cactoideUserId) {
|
||||
console.debug(`There is no cactoideUserId cookie, generating new one...`);
|
||||
event.cookies.set('cactoideUserId', userId, { path: PATH, maxAge: MAX_AGE });
|
||||
} else {
|
||||
console.debug(`cactoideUserId: ${cactoideUserId}`);
|
||||
console.debug(`cactoideUserId cookie found, using existing one...`);
|
||||
}
|
||||
|
||||
return resolve(event);
|
||||
};
|
||||
@@ -15,7 +15,10 @@ export interface CalendarEvent {
|
||||
* Formats a date and time string for iCal format (UTC)
|
||||
*/
|
||||
export const formatDateForICal = (date: string, time: string): string => {
|
||||
const eventDate = new Date(`${date}T${time}`);
|
||||
// Parse date and time as local timezone to avoid timezone issues
|
||||
const [year, month, day] = date.split('-').map(Number);
|
||||
const [hours, minutes, seconds] = time.split(':').map(Number);
|
||||
const eventDate = new Date(year, month - 1, day, hours, minutes, seconds || 0);
|
||||
return eventDate.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z';
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { Event } from './types';
|
||||
|
||||
export const formatDate = (dateString: string): string => {
|
||||
const date = new Date(dateString);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}/${month}/${day}`;
|
||||
// Parse the date string as local date to avoid timezone issues
|
||||
// Split the date string and create a Date object in local timezone
|
||||
const [year, month, day] = dateString.split('-').map(Number);
|
||||
const date = new Date(year, month - 1, day); // month is 0-indexed in Date constructor
|
||||
const formattedYear = date.getFullYear();
|
||||
const formattedMonth = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const formattedDay = String(date.getDate()).padStart(2, '0');
|
||||
return `${formattedYear}/${formattedMonth}/${formattedDay}`;
|
||||
};
|
||||
|
||||
export const formatTime = (timeString: string): string => {
|
||||
@@ -17,7 +20,10 @@ export const formatTime = (timeString: string): string => {
|
||||
export const isEventInTimeRange = (event: Event, timeFilter: string): boolean => {
|
||||
if (timeFilter === 'any') return true;
|
||||
|
||||
const eventDate = new Date(`${event.date}T${event.time}`);
|
||||
// Parse date and time as local timezone to avoid timezone issues
|
||||
const [year, month, day] = event.date.split('-').map(Number);
|
||||
const [hours, minutes, seconds] = event.time.split(':').map(Number);
|
||||
const eventDate = new Date(year, month - 1, day, hours, minutes, seconds || 0);
|
||||
const now = new Date();
|
||||
|
||||
// Handle temporal status filters
|
||||
|
||||
@@ -1,20 +1,5 @@
|
||||
import { generateUserId } from '$lib/generateUserId.js';
|
||||
|
||||
export function load({ cookies }) {
|
||||
const cactoideUserId = cookies.get('cactoideUserId');
|
||||
const userId = generateUserId();
|
||||
|
||||
const DAYS = 400; // practical upper bound in many browsers for cookies
|
||||
const MAX_AGE = 60 * 60 * 24 * DAYS;
|
||||
const PATH = '/';
|
||||
|
||||
if (!cactoideUserId) {
|
||||
console.debug(`There is no cactoideUserId cookie, generating new one...`);
|
||||
cookies.set('cactoideUserId', userId, { path: PATH, maxAge: MAX_AGE });
|
||||
} else {
|
||||
console.debug(`cactoideUserId: ${cactoideUserId}`);
|
||||
console.debug(`cactoideUserId cookie found, using existing one...`);
|
||||
}
|
||||
|
||||
return {
|
||||
cactoideUserId
|
||||
|
||||
@@ -56,7 +56,13 @@ export const actions: Actions = {
|
||||
});
|
||||
}
|
||||
|
||||
if (new Date(date) < new Date()) {
|
||||
// Check if date is in the past using local timezone
|
||||
const [year, month, day] = date.split('-').map(Number);
|
||||
const eventDate = new Date(year, month - 1, day);
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
if (eventDate < today) {
|
||||
return fail(400, {
|
||||
error: 'Date cannot be in the past.',
|
||||
values: {
|
||||
@@ -105,7 +111,7 @@ export const actions: Actions = {
|
||||
type: type,
|
||||
attendeeLimit: type === 'limited' ? parseInt(attendeeLimit) : null,
|
||||
visibility: visibility,
|
||||
userId: userId
|
||||
userId: userId!
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Unexpected error', error);
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { Event, EventType } from '$lib/types';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { PageData } from '../$types';
|
||||
import { formatTime, formatDate, isEventInTimeRange } from '$lib/dateHelpers';
|
||||
import { t } from '$lib/i18n/i18n.js';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
type DiscoverPageData = {
|
||||
events: Event[];
|
||||
};
|
||||
|
||||
let publicEvents: Event[] = [];
|
||||
let error = '';
|
||||
let searchQuery = '';
|
||||
@@ -16,7 +19,7 @@
|
||||
let showFilters = false;
|
||||
let fuse: Fuse<Event>;
|
||||
|
||||
export let data: PageData;
|
||||
export let data: DiscoverPageData;
|
||||
// Use the server-side data
|
||||
$: publicEvents = data?.events || [];
|
||||
|
||||
@@ -67,8 +70,15 @@
|
||||
|
||||
// Sort events by date and time
|
||||
events = events.sort((a, b) => {
|
||||
const dateA = new Date(`${a.date}T${a.time}`);
|
||||
const dateB = new Date(`${b.date}T${b.time}`);
|
||||
// Parse dates as local timezone to avoid timezone issues
|
||||
const parseEventDateTime = (event: Event) => {
|
||||
const [year, month, day] = event.date.split('-').map(Number);
|
||||
const [hours, minutes, seconds] = event.time.split(':').map(Number);
|
||||
return new Date(year, month - 1, day, hours, minutes, seconds || 0);
|
||||
};
|
||||
|
||||
const dateA = parseEventDateTime(a);
|
||||
const dateB = parseEventDateTime(b);
|
||||
|
||||
if (selectedSortOrder === 'asc') {
|
||||
return dateA.getTime() - dateB.getTime();
|
||||
|
||||
@@ -86,8 +86,9 @@ export const actions: Actions = {
|
||||
});
|
||||
}
|
||||
|
||||
// Check if date is in the past (but allow editing past events for corrections)
|
||||
const eventDate = new Date(date);
|
||||
// Check if date is in the past using local timezone (but allow editing past events for corrections)
|
||||
const [year, month, day] = date.split('-').map(Number);
|
||||
const eventDate = new Date(year, month - 1, day);
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user