From 53b36356db3aa148dc36dac3fc478fa454e4a6db Mon Sep 17 00:00:00 2001 From: Dillon Date: Wed, 1 May 2024 21:59:58 -0500 Subject: [PATCH 01/13] added event type to needed places --- api/resolvers/item.js | 9 +++ api/typeDefs/item.js | 1 + components/event-form.js | 150 +++++++++++++++++++++++++++++++++++++++ components/post.js | 5 +- lib/form.js | 3 +- lib/validate.js | 19 +++++ prisma/schema.prisma | 1 + 7 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 components/event-form.js diff --git a/api/resolvers/item.js b/api/resolvers/item.js index 3aa4c4b25..0f398c8a2 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -784,6 +784,15 @@ export default { return await createItem(parent, item, { me, models, lnd, hash, hmac }) } }, + upsertEvent: async (parent, { id, hash, hmac, ...item }, { me, models }) => { + await ssValidate(eventSchema, item, { models, me }) + + if (id) { + return await updateItem(parent, { id, ...item }, { me, models, hash, hmac }) + } else { + return await createItem(parent, item, { me, models, hash, hmac }) + } + }, upsertPoll: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => { const numExistingChoices = id ? await models.pollOption.count({ diff --git a/api/typeDefs/item.js b/api/typeDefs/item.js index e17ffe436..b8ebace36 100644 --- a/api/typeDefs/item.js +++ b/api/typeDefs/item.js @@ -35,6 +35,7 @@ export default gql` upsertJob(id: ID, sub: String!, title: String!, company: String!, location: String, remote: Boolean, text: String!, url: String!, maxBid: Int!, status: String, logo: Int, hash: String, hmac: String): Item! upsertPoll(id: ID, sub: String, title: String!, text: String, options: [String!]!, boost: Int, forward: [ItemForwardInput], hash: String, hmac: String, pollExpiresAt: Date): Item! + upsertEvent(id: ID, sub: String, title: String!, date: Date!, location: String!, text: String, forward: [ItemForwardInput], hash: String, hmac: String): Item! updateNoteId(id: ID!, noteId: String!): Item! upsertComment(id:ID, text: String!, parentId: ID, hash: String, hmac: String): Item! act(id: ID!, sats: Int, act: String, idempotent: Boolean, hash: String, hmac: String): ItemActResult! diff --git a/components/event-form.js b/components/event-form.js new file mode 100644 index 000000000..3b535be0a --- /dev/null +++ b/components/event-form.js @@ -0,0 +1,150 @@ +import { Form, Input, MarkdownInput, DateTimeInput } from '@/components/form' +import { useRouter } from 'next/router' +import { gql, useApolloClient, useMutation } from '@apollo/client' +import AdvPostForm, { AdvPostInitial } from './adv-post-form' +import useCrossposter from './use-crossposter' +import { eventSchema } from '@/lib/validate' +import { SubSelectInitial } from './sub-select' +import { useCallback } from 'react' +import { normalizeForwards, toastDeleteScheduled } from '@/lib/form' +import { MAX_TITLE_LENGTH } from '@/lib/constants' +import { useMe } from './me' +import { useToast } from './toast' +import { ItemButtonBar } from './post' + +export function EventForm ({ + item, + sub, + editThreshold, + titleLabel = 'Title', + dateLabel = 'Date', + locationLabel = 'Location', + textLabel = 'Description', + handleSubmit, + children +}) { + const router = useRouter() + const client = useApolloClient() + const me = useMe() + const toaster = useToast() + const crossposter = useCrossposter() + const schema = eventSchema({ client, me }) + const [upsertEvent] = useMutation( + gql` + mutation upsertEvent( + $sub: String + $id: ID + $title: String! + $date: DateTime! + $location: String! + $text: String + $forward: [ItemForwardInput] + $hash: String + $hmac: String + ) { + upsertEvent( + sub: $sub + id: $id + title: $title + date: $date + location: $location + text: $text + forward: $forward + hash: $hash + hmac: $hmac + ) { + id + deleteScheduledAt + } + } + ` + ) + + const onSubmit = useCallback( + async ({ crosspost, ...values }) => { + const { data, error } = await upsertEvent({ + variables: { + sub: item?.subName || sub?.name, + id: item?.id, + ...values, + forward: normalizeForwards(values.forward) + } + }) + if (error) { + throw new Error({ message: error.toString() }) + } + + const eventId = data?.upsertEvent?.id + + if (crosspost && eventId) { + await crossposter(eventId) + } + + if (item) { + await router.push(`/items/${item.id}`) + } else { + const prefix = sub?.name ? `/~${sub.name}` : '' + await router.push(prefix + '/recent') + } + toastDeleteScheduled(toaster, data, 'upsertEvent', !!item, values.text) + }, [upsertEvent, router] + ) + + return ( +
+ {children} + + + + + + + ) + : null + } + /> + + + + ) +} \ No newline at end of file diff --git a/components/post.js b/components/post.js index 9e6e720d6..56211ed72 100644 --- a/components/post.js +++ b/components/post.js @@ -9,6 +9,7 @@ import { DiscussionForm } from './discussion-form' import { LinkForm } from './link-form' import { PollForm } from './poll-form' import { BountyForm } from './bounty-form' +import { EventForm } from './event-form' import SubSelect from './sub-select' import { useCallback, useState } from 'react' import FeeButton, { FeeButtonProvider, postCommentBaseLineItems, postCommentUseRemoteLineItems } from './fee-button' @@ -136,7 +137,7 @@ export function PostForm ({ type, sub, children }) { ) } - let FormType = JobForm + let FormType = JobFormp if (type === 'discussion') { FormType = DiscussionForm } else if (type === 'link') { @@ -145,6 +146,8 @@ export function PostForm ({ type, sub, children }) { FormType = PollForm } else if (type === 'bounty') { FormType = BountyForm + } else if (type === 'event') { + FormType = BountyForm } return ( diff --git a/lib/form.js b/lib/form.js index 44911d434..d688236b0 100644 --- a/lib/form.js +++ b/lib/form.js @@ -36,7 +36,8 @@ export const toastDeleteScheduled = (toaster, upsertResponseData, dataKey, isEdi upsertPoll: 'poll', upsertBounty: 'bounty', upsertJob: 'job', - upsertComment: 'comment' + upsertComment: 'comment', + upsertEvent: 'event' }[dataKey] ?? 'item' const message = `${itemType === 'comment' ? 'your comment' : isEdit ? `this ${itemType}` : `your new ${itemType}`} will be deleted at ${deleteScheduledAt.toLocaleString()}` diff --git a/lib/validate.js b/lib/validate.js index 440649b10..c3b6f8d0d 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -379,6 +379,25 @@ export function bountySchema (args) { }) } +export function eventSchema (args) { + return object({ + title: titleValidator, + text: textValidator(MAX_POST_TEXT_LENGTH), + date: date() + .required('date is required') + .min(new Date(), 'date must be in the future'), + location: string() + .required('location is required') + .max(100, 'location must be at most 100 characters'), + ...advPostSchemaMembers(args), + ...subSelectSchemaMembers(args) + }).test({ + name: 'post-type-supported', + test: ({ sub }) => subHasPostType(sub, 'EVENT', args), + message: 'territory does not support events' + }) +} + export function discussionSchema (args) { return object({ title: titleValidator, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7a3869b47..40cba8165 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -877,6 +877,7 @@ enum PostType { JOB POLL BOUNTY + EVENT } enum ItemActType { From d0a61a9cf0989f2b5f9e945a94f8a23d7cb0702a Mon Sep 17 00:00:00 2001 From: Dillon Date: Thu, 2 May 2024 14:08:38 -0500 Subject: [PATCH 02/13] added button and some buttoning up --- api/resolvers/item.js | 2 +- components/event-form.js | 1 + components/post.js | 16 ++++++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/api/resolvers/item.js b/api/resolvers/item.js index 0f398c8a2..eb9428c8b 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -14,7 +14,7 @@ import { import { msatsToSats } from '@/lib/format' import { parse } from 'tldts' import uu from 'url-unshort' -import { actSchema, advSchema, bountySchema, commentSchema, discussionSchema, jobSchema, linkSchema, pollSchema, ssValidate } from '@/lib/validate' +import { actSchema, advSchema, bountySchema, eventSchema, commentSchema, discussionSchema, jobSchema, linkSchema, pollSchema, ssValidate } from '@/lib/validate' import { notifyItemParents, notifyUserSubscribers, notifyZapped, notifyTerritorySubscribers, notifyMention } from '@/lib/webPush' import { defaultCommentSort, isJob, deleteItemByAuthor, getDeleteCommand, hasDeleteCommand } from '@/lib/item' import { datePivot, whenRange } from '@/lib/time' diff --git a/components/event-form.js b/components/event-form.js index 3b535be0a..51cdf977c 100644 --- a/components/event-form.js +++ b/components/event-form.js @@ -121,6 +121,7 @@ export function EventForm ({ ) } + + if (sub?.postTypes?.includes('BOUNTY')) { + const array = postButtons.length < 2 ? postButtons : morePostButtons + array.push( + + + + ) + } } else { postButtons = [ @@ -83,6 +92,9 @@ export function PostForm ({ type, sub, children }) { , + , + + ] } @@ -137,7 +149,7 @@ export function PostForm ({ type, sub, children }) { ) } - let FormType = JobFormp + let FormType = JobForm if (type === 'discussion') { FormType = DiscussionForm } else if (type === 'link') { @@ -147,7 +159,7 @@ export function PostForm ({ type, sub, children }) { } else if (type === 'bounty') { FormType = BountyForm } else if (type === 'event') { - FormType = BountyForm + FormType = EventForm } return ( From 3acf6562be00d99b1fd37367544763b619ff1dfa Mon Sep 17 00:00:00 2001 From: Dillon Date: Thu, 2 May 2024 14:21:03 -0500 Subject: [PATCH 03/13] linter fixes --- api/resolvers/item.js | 2 +- components/event-form.js | 3 ++- components/post.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/resolvers/item.js b/api/resolvers/item.js index eb9428c8b..eed6cd06e 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -786,7 +786,7 @@ export default { }, upsertEvent: async (parent, { id, hash, hmac, ...item }, { me, models }) => { await ssValidate(eventSchema, item, { models, me }) - + if (id) { return await updateItem(parent, { id, ...item }, { me, models, hash, hmac }) } else { diff --git a/components/event-form.js b/components/event-form.js index 51cdf977c..906383145 100644 --- a/components/event-form.js +++ b/components/event-form.js @@ -11,6 +11,7 @@ import { MAX_TITLE_LENGTH } from '@/lib/constants' import { useMe } from './me' import { useToast } from './toast' import { ItemButtonBar } from './post' +import Countdown from './countdown' export function EventForm ({ item, @@ -148,4 +149,4 @@ export function EventForm ({ ) -} \ No newline at end of file +} diff --git a/components/post.js b/components/post.js index f315e17cd..c3ac6a520 100644 --- a/components/post.js +++ b/components/post.js @@ -69,7 +69,7 @@ export function PostForm ({ type, sub, children }) { ) } - if (sub?.postTypes?.includes('BOUNTY')) { + if (sub?.postTypes?.includes('EVENT')) { const array = postButtons.length < 2 ? postButtons : morePostButtons array.push( From f3ff7d20b15945f584cffdeb1f2e09f034c322b2 Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 6 May 2024 19:21:42 -0500 Subject: [PATCH 04/13] updated migration and fixed form submit and event date and location viewing --- api/resolvers/item.js | 3 +- api/typeDefs/item.js | 6 +- components/event-form.js | 35 +++--- components/item.js | 8 ++ components/recent-header.js | 2 +- components/territory-form.js | 10 ++ fragments/items.js | 2 + lib/constants.js | 2 +- lib/validate.js | 5 +- .../20240503182545_event_enum/migration.sql | 110 ++++++++++++++++++ prisma/schema.prisma | 2 + 11 files changed, 160 insertions(+), 25 deletions(-) create mode 100644 prisma/migrations/20240503182545_event_enum/migration.sql diff --git a/api/resolvers/item.js b/api/resolvers/item.js index eed6cd06e..09b368676 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -786,7 +786,6 @@ export default { }, upsertEvent: async (parent, { id, hash, hmac, ...item }, { me, models }) => { await ssValidate(eventSchema, item, { models, me }) - if (id) { return await updateItem(parent, { id, ...item }, { me, models, hash, hmac }) } else { @@ -1396,7 +1395,7 @@ export const SELECT = "Item"."rootId", "Item".upvotes, "Item".company, "Item".location, "Item".remote, "Item"."deletedAt", "Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", "Item".boost, "Item".msats, "Item".ncomments, "Item"."commentMsats", "Item"."lastCommentAt", "Item"."weightedVotes", - "Item"."weightedDownVotes", "Item".freebie, "Item".bio, "Item"."otsHash", "Item"."bountyPaidTo", + "Item"."weightedDownVotes", "Item".freebie, "Item".bio, "Item"."otsHash", "Item"."bountyPaidTo", "Item"."eventDate", "Item"."eventLocation", ltree2text("Item"."path") AS "path", "Item"."weightedComments", "Item"."imgproxyUrls", "Item".outlawed, "Item"."pollExpiresAt"` diff --git a/api/typeDefs/item.js b/api/typeDefs/item.js index b8ebace36..66b5fb3d3 100644 --- a/api/typeDefs/item.js +++ b/api/typeDefs/item.js @@ -33,9 +33,9 @@ export default gql` upsertDiscussion(id: ID, sub: String, title: String!, text: String, boost: Int, forward: [ItemForwardInput], hash: String, hmac: String): Item! upsertBounty(id: ID, sub: String, title: String!, text: String, bounty: Int, hash: String, hmac: String, boost: Int, forward: [ItemForwardInput]): Item! upsertJob(id: ID, sub: String!, title: String!, company: String!, location: String, remote: Boolean, - text: String!, url: String!, maxBid: Int!, status: String, logo: Int, hash: String, hmac: String): Item! + text: String!, url: String!, maxBid: Int!, status: String, logo: Int, hash: String, hmac: String): Item! upsertPoll(id: ID, sub: String, title: String!, text: String, options: [String!]!, boost: Int, forward: [ItemForwardInput], hash: String, hmac: String, pollExpiresAt: Date): Item! - upsertEvent(id: ID, sub: String, title: String!, date: Date!, location: String!, text: String, forward: [ItemForwardInput], hash: String, hmac: String): Item! + upsertEvent(id: ID, sub: String, title: String!, eventDate: Date!, eventLocation: String!, text: String, boost: Int, forward: [ItemForwardInput], hash: String, hmac: String): Item! updateNoteId(id: ID!, noteId: String!): Item! upsertComment(id:ID, text: String!, parentId: ID, hash: String, hmac: String): Item! act(id: ID!, sats: Int, act: String, idempotent: Boolean, hash: String, hmac: String): ItemActResult! @@ -125,6 +125,8 @@ export default gql` forwards: [ItemForward] imgproxyUrls: JSONObject rel: String + eventDate: Date + eventLocation: String } input ItemForwardInput { diff --git a/components/event-form.js b/components/event-form.js index 906383145..c213e5e56 100644 --- a/components/event-form.js +++ b/components/event-form.js @@ -36,9 +36,10 @@ export function EventForm ({ $sub: String $id: ID $title: String! - $date: DateTime! - $location: String! + $eventDate: Date! + $eventLocation: String! $text: String + $boost: Int $forward: [ItemForwardInput] $hash: String $hmac: String @@ -47,9 +48,10 @@ export function EventForm ({ sub: $sub id: $id title: $title - date: $date - location: $location + eventDate: $eventDate + eventLocation: $eventLocation text: $text + boost: $boost forward: $forward hash: $hash hmac: $hmac @@ -62,16 +64,18 @@ export function EventForm ({ ) const onSubmit = useCallback( - async ({ crosspost, ...values }) => { + async ({ crosspost, boost, ...values }) => { const { data, error } = await upsertEvent({ variables: { sub: item?.subName || sub?.name, id: item?.id, + boost: boost ? Number(boost) : undefined, ...values, forward: normalizeForwards(values.forward) } }) if (error) { + console.log("Poop") throw new Error({ message: error.toString() }) } @@ -95,11 +99,11 @@ export function EventForm ({
{children} + - + } + {item.eventDate && {new Date(item.eventDate).toLocaleString('en-us', { year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + hour12: true, + timeZone: 'America/Chicago'})} } + {item.eventLocation && at {item.eventLocation}} {item.forwards?.length > 0 && } {image && } diff --git a/components/recent-header.js b/components/recent-header.js index b961ea38b..5805fd4d6 100644 --- a/components/recent-header.js +++ b/components/recent-header.js @@ -9,7 +9,7 @@ export default function RecentHeader ({ type, sub }) { const items = sub ? ITEM_TYPES_UNIVERSAL.concat(sub.postTypes.map(p => - ['LINK', 'DISCUSSION', 'POLL', 'JOB'].includes(p) ? `${p.toLowerCase()}s` : 'bounties' + ['LINK', 'DISCUSSION', 'POLL', 'JOB', 'EVENT'].includes(p) ? `${p.toLowerCase()}s` : 'bounties' )) : ITEM_TYPES diff --git a/components/territory-form.js b/components/territory-form.js index 948a6a773..d2d2310bd 100644 --- a/components/territory-form.js +++ b/components/territory-form.js @@ -199,6 +199,16 @@ export default function TerritoryForm ({ sub }) { groupClassName='ms-1 mb-0' /> + + + {sub?.billingType !== 'ONCE' && diff --git a/fragments/items.js b/fragments/items.js index 9dd09d063..17c294bbc 100644 --- a/fragments/items.js +++ b/fragments/items.js @@ -59,6 +59,8 @@ export const ITEM_FIELDS = gql` mine imgproxyUrls rel + eventDate + eventLocation }` export const ITEM_FULL_FIELDS = gql` diff --git a/lib/constants.js b/lib/constants.js index f149b9b8b..d3d31d9fa 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -22,7 +22,7 @@ export const UPLOAD_TYPES_ALLOW = [ ] export const BOUNTY_MIN = 1000 export const BOUNTY_MAX = 10000000 -export const POST_TYPES = ['LINK', 'DISCUSSION', 'BOUNTY', 'POLL'] +export const POST_TYPES = ['LINK', 'DISCUSSION', 'BOUNTY', 'POLL', 'EVENT'] export const TERRITORY_BILLING_TYPES = ['MONTHLY', 'YEARLY', 'ONCE'] export const TERRITORY_GRACE_DAYS = 5 export const COMMENT_DEPTH_LIMIT = 8 diff --git a/lib/validate.js b/lib/validate.js index c3b6f8d0d..2b3b09737 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -383,12 +383,9 @@ export function eventSchema (args) { return object({ title: titleValidator, text: textValidator(MAX_POST_TEXT_LENGTH), - date: date() + eventDate: date() .required('date is required') .min(new Date(), 'date must be in the future'), - location: string() - .required('location is required') - .max(100, 'location must be at most 100 characters'), ...advPostSchemaMembers(args), ...subSelectSchemaMembers(args) }).test({ diff --git a/prisma/migrations/20240503182545_event_enum/migration.sql b/prisma/migrations/20240503182545_event_enum/migration.sql new file mode 100644 index 000000000..95bca3a4d --- /dev/null +++ b/prisma/migrations/20240503182545_event_enum/migration.sql @@ -0,0 +1,110 @@ +-- AlterEnum +ALTER TYPE "PostType" ADD VALUE 'EVENT'; + +-- AlterTable +ALTER TABLE "Item" ADD COLUMN "eventDate" TIMESTAMP(3); +ALTER TABLE "Item" ADD COLUMN "eventLocation" TEXT; +DROP FUNCTION IF EXISTS create_item( + sub TEXT, title TEXT, url TEXT, text TEXT, event_date TIMESTAMP, event_location TEXT, boost INTEGER, + parent_id INTEGER, user_id INTEGER, fwd_user_id INTEGER, + spam_within INTERVAL); + +CREATE OR REPLACE FUNCTION create_item( + sub TEXT, title TEXT, url TEXT, text TEXT, event_date TIMESTAMP, event_location TEXT, boost INTEGER, + parent_id INTEGER, user_id INTEGER, fwd_user_id INTEGER, + spam_within INTERVAL) +RETURNS "Item" +LANGUAGE plpgsql +AS $$ +DECLARE + user_msats BIGINT; + cost_msats BIGINT; + free_posts INTEGER; + free_comments INTEGER; + freebie BOOLEAN; + item "Item"; + med_votes FLOAT; +BEGIN + PERFORM ASSERT_SERIALIZED(); + + SELECT msats, "freePosts", "freeComments" + INTO user_msats, free_posts, free_comments + FROM users WHERE id = user_id; + + cost_msats := 1000 * POWER(10, item_spam(parent_id, user_id, spam_within)); + -- it's only a freebie if it's a 1 sat cost, they have < 1 sat, boost = 0, and they have freebies left + freebie := (cost_msats <= 1000) AND (user_msats < 1000) AND (boost = 0) AND ((parent_id IS NULL AND free_posts > 0) OR (parent_id IS NOT NULL AND free_comments > 0)); + + IF NOT freebie AND cost_msats > user_msats THEN + RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS'; + END IF; + + -- get this user's median item score + SELECT COALESCE(percentile_cont(0.5) WITHIN GROUP(ORDER BY "weightedVotes" - "weightedDownVotes"), 0) INTO med_votes FROM "Item" WHERE "userId" = user_id; + + -- if their median votes are positive, start at 0 + -- if the median votes are negative, start their post with that many down votes + -- basically: if their median post is bad, presume this post is too + IF med_votes >= 0 THEN + med_votes := 0; + ELSE + med_votes := ABS(med_votes); + END IF; + + INSERT INTO "Item" + ("subName", title, url, text, "eventDate", "eventLocation", "userId", "parentId", "fwdUserId", + freebie, "weightedDownVotes", created_at, updated_at) + VALUES + (sub, title, url, text, event_date, event_location, user_id, parent_id, fwd_user_id, + freebie, med_votes, now_utc(), now_utc()) RETURNING * INTO item; + + IF freebie THEN + IF parent_id IS NULL THEN + UPDATE users SET "freePosts" = "freePosts" - 1 WHERE id = user_id; + ELSE + UPDATE users SET "freeComments" = "freeComments" - 1 WHERE id = user_id; + END IF; + ELSE + UPDATE users SET msats = msats - cost_msats WHERE id = user_id; + + INSERT INTO "ItemAct" (msats, "itemId", "userId", act, created_at, updated_at) + VALUES (cost_msats, item.id, user_id, 'FEE', now_utc(), now_utc()); + END IF; + + IF boost > 0 THEN + PERFORM item_act(item.id, user_id, 'BOOST', boost); + END IF; + + RETURN item; +END; +$$; + +DROP FUNCTION IF EXISTS update_item( + sub TEXT, item_id INTEGER, item_title TEXT, item_url TEXT, item_text TEXT, event_date TIMESTAMP, event_location TEXT, boost INTEGER, + fwd_user_id INTEGER); + +CREATE OR REPLACE FUNCTION update_item( + sub TEXT, item_id INTEGER, item_title TEXT, item_url TEXT, item_text TEXT, event_date TIMESTAMP, event_location TEXT, boost INTEGER, + fwd_user_id INTEGER) +RETURNS "Item" +LANGUAGE plpgsql +AS $$ +DECLARE + user_msats INTEGER; + item "Item"; +BEGIN + PERFORM ASSERT_SERIALIZED(); + + UPDATE "Item" + SET "subName" = sub, title = item_title, url = item_url, + text = item_text, "eventDate" = event_date, "eventLocation" = event_location, "fwdUserId" = fwd_user_id + WHERE id = item_id + RETURNING * INTO item; + + IF boost > 0 THEN + PERFORM item_act(item.id, item."userId", 'BOOST', boost); + END IF; + + RETURN item; +END; +$$; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 40cba8165..cd50884ab 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -420,6 +420,8 @@ model Item { pollExpiresAt DateTime? Ancestors Reply[] @relation("AncestorReplyItem") Replies Reply[] + eventDate DateTime? + eventLocation String? @@index([uploadId]) @@index([lastZapAt]) From c7d6e7e1c118bb81f84f5e4cc14a5a25dcc2759c Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 6 May 2024 19:30:58 -0500 Subject: [PATCH 05/13] linter --- components/event-form.js | 3 +-- components/item.js | 12 +++--------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/components/event-form.js b/components/event-form.js index c213e5e56..71f592412 100644 --- a/components/event-form.js +++ b/components/event-form.js @@ -75,7 +75,6 @@ export function EventForm ({ } }) if (error) { - console.log("Poop") throw new Error({ message: error.toString() }) } @@ -103,7 +102,7 @@ export function EventForm ({ eventLocation: item?.eventLocation || '', text: item?.text || '', crosspost: item ? !!item.noteId : me?.privates?.nostrCrossposting, - ...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }), + ...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }), ...SubSelectInitial({ sub: item?.subName || sub?.name }) }} schema={schema} diff --git a/components/item.js b/components/item.js index b7d840a31..fc1dade55 100644 --- a/components/item.js +++ b/components/item.js @@ -30,7 +30,7 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s const router = useRouter() const image = item.url && item.url.startsWith(process.env.NEXT_PUBLIC_IMGPROXY_URL) - + const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true, timeZone: 'America/Chicago'} return ( <> {rank @@ -77,14 +77,8 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s } - {item.eventDate && {new Date(item.eventDate).toLocaleString('en-us', { year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - hour12: true, - timeZone: 'America/Chicago'})} } - {item.eventLocation && at {item.eventLocation}} + {item.eventDate && {new Date(item.eventDate).toLocaleString('en-us', dateOptions)} } + {item.eventLocation && at {item.eventLocation}} {item.forwards?.length > 0 && } {image && } From 1933bc83b10efde795cf68b6e34bd9c1dbe5c0da Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 6 May 2024 19:32:44 -0500 Subject: [PATCH 06/13] linter --- components/item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/item.js b/components/item.js index fc1dade55..73dc64b87 100644 --- a/components/item.js +++ b/components/item.js @@ -30,7 +30,7 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s const router = useRouter() const image = item.url && item.url.startsWith(process.env.NEXT_PUBLIC_IMGPROXY_URL) - const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true, timeZone: 'America/Chicago'} + const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true, timeZone: 'America/Chicago' } return ( <> {rank From 1b53115784865651e5f4bed8e8b920f6ba32efcf Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 6 May 2024 19:54:09 -0500 Subject: [PATCH 07/13] coderabbitai suggested changes --- lib/constants.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/constants.js b/lib/constants.js index 0df53bd85..5806c088a 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -50,8 +50,8 @@ export const USER_SORTS = ['value', 'stacking', 'spending', 'comments', 'posts', export const ITEM_SORTS = ['zaprank', 'comments', 'sats'] export const SUB_SORTS = ['stacking', 'revenue', 'spending', 'posts', 'comments'] export const WHENS = ['day', 'week', 'month', 'year', 'forever', 'custom'] -export const ITEM_TYPES_USER = ['all', 'posts', 'comments', 'bounties', 'links', 'discussions', 'polls', 'freebies', 'jobs', 'bookmarks'] -export const ITEM_TYPES = ['all', 'posts', 'comments', 'bounties', 'links', 'discussions', 'polls', 'freebies', 'bios', 'jobs'] +export const ITEM_TYPES_USER = ['all', 'posts', 'comments', 'bounties', 'events', 'links', 'discussions', 'polls', 'freebies', 'jobs', 'bookmarks'] +export const ITEM_TYPES = ['all', 'posts', 'comments', 'bounties', 'events', 'links', 'discussions', 'polls', 'freebies', 'bios', 'jobs'] export const ITEM_TYPES_UNIVERSAL = ['all', 'posts', 'comments', 'freebies'] export const OLD_ITEM_DAYS = 3 export const ANON_USER_ID = 27 From 693d64521e49380f5b5bf4bcfc1a9ec92f790265 Mon Sep 17 00:00:00 2001 From: Dillon Date: Wed, 29 May 2024 16:54:04 -0500 Subject: [PATCH 08/13] calendar page added. Still need to link events to page --- components/event-calendar.js | 132 +++++++++++++++++++++++++++++++++++ components/nav/common.js | 11 +++ pages/~/events/index.js | 39 +++++++++++ styles/calendar.module.css | 20 ++++++ 4 files changed, 202 insertions(+) create mode 100644 components/event-calendar.js create mode 100644 pages/~/events/index.js create mode 100644 styles/calendar.module.css diff --git a/components/event-calendar.js b/components/event-calendar.js new file mode 100644 index 000000000..7d2a3455e --- /dev/null +++ b/components/event-calendar.js @@ -0,0 +1,132 @@ +import React, { useState } from 'react'; +import { Container, Row, Col, Button } from 'react-bootstrap'; + +const EventCalendar = () => { + const [currentDate, setCurrentDate] = useState(new Date()); + const [selectedDate, setSelectedDate] = useState(null); + + const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + const months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + + const getDaysInMonth = () => { + const year = currentDate.getFullYear(); + const month = currentDate.getMonth(); + const daysInMonth = new Date(year, month + 1, 0).getDate(); + return daysInMonth; + }; + + const handleDayClick = (day) => { + if (day !== '') { + setSelectedDate(new Date(currentDate.getFullYear(), currentDate.getMonth(), day)); + } + }; + + const handleMonthChange = (direction) => { + const newMonth = currentDate.getMonth() + direction; + setCurrentDate(new Date(currentDate.getFullYear(), newMonth, 1)); + setSelectedDate(null); // Reset the selectedDate when the month changes + }; + + const generateCalendar = () => { + const daysInMonth = getDaysInMonth(); + const month = months[currentDate.getMonth()]; + const year = currentDate.getFullYear(); + const firstDay = new Date(year, currentDate.getMonth(), 1); + const dayOfWeek = firstDay.getDay(); // Adjust the day of the week + + const days = []; + for (let i = 0; i < dayOfWeek; i++) { + days.push(''); + } + for (let i = 1; i <= daysInMonth; i++) { + days.push(i); + } + while (days.length % 7 !== 0) { + days.push(''); + } + + const weeks = []; + let week = []; + for (let i = 0; i < days.length; i++) { + week.push(days[i]); + if ((i + 1) % 7 === 0) { + weeks.push(week); + week = []; + } + } + const cellStyle = { + width: '40px', + height: '40px', + cursor: 'pointer', + '@media (min-width: 768px)': { + height: '80px', + }, + }; + + return ( +
+

+ {month} {year} +

+ + + + {daysOfWeek.map((day, index) => ( + + ))} + + + + {weeks.map((week, weekIndex) => ( + + {week.map((day, dayIndex) => ( + + ))} + + ))} + +
+ {day} +
handleDayClick(day)} + > + {day !== '' && ( +
+ {day} +
+ )} + {/* Placeholder for event names */} + {day !== '' &&
Event Name
} +
+
+ + +
+
+ ); + }; + + return
{generateCalendar()}
; +}; + +export default EventCalendar; \ No newline at end of file diff --git a/components/nav/common.js b/components/nav/common.js index 8be6dfa64..d12c844ec 100644 --- a/components/nav/common.js +++ b/components/nav/common.js @@ -347,6 +347,17 @@ export function Sorts ({ sub, prefix, className }) { top } + {sub !== 'jobs' && + + + events + + } ) } diff --git a/pages/~/events/index.js b/pages/~/events/index.js new file mode 100644 index 000000000..aef0cff4e --- /dev/null +++ b/pages/~/events/index.js @@ -0,0 +1,39 @@ +import Layout from '@/components/layout' +import Items from '@/components/items' +import EventCalendar from '@/components/event-calendar' +import { getGetServerSideProps } from '@/api/ssrApollo' +import RecentHeader from '@/components/recent-header' +import { useRouter } from 'next/router' +import { SUB_FULL, SUB_ITEMS } from '@/fragments/subs' +import { COMMENT_TYPE_QUERY } from '@/lib/constants' +import { useQuery } from '@apollo/client' +import PageLoading from '@/components/page-loading' + +const staticVariables = { sort: 'recent' } +const variablesFunc = vars => + ({ includeComments: COMMENT_TYPE_QUERY.includes(vars.type), ...staticVariables, ...vars }) +export const getServerSideProps = getGetServerSideProps({ + query: SUB_ITEMS, + variables: variablesFunc, + notFound: (data, vars) => vars.sub && !data.sub +}) + +export default function Index ({ ssrData }) { + const router = useRouter() + const variables = variablesFunc(router.query) + const { data } = useQuery(SUB_FULL, { variables }) + + if (!data && !ssrData) return + const { sub } = data || ssrData + + return ( + + {/* */} + + + ) +} diff --git a/styles/calendar.module.css b/styles/calendar.module.css new file mode 100644 index 000000000..dd47e14ef --- /dev/null +++ b/styles/calendar.module.css @@ -0,0 +1,20 @@ +.calendar-header { + width: 40px; + height: 40px; + } + + .calendar-cell { + width: 40px; + height: 40px; + cursor: pointer; + } + + @media (min-width: 768px) { + .calendar-header { + height: 80px; + } + + .calendar-cell { + height: 80px; + } + } \ No newline at end of file From 960057fa92643b4e83ca3b4754cdf217a70ec095 Mon Sep 17 00:00:00 2001 From: Dillon Date: Wed, 29 May 2024 17:19:30 -0500 Subject: [PATCH 09/13] linter --- components/event-calendar.js | 99 +++++++++++++++++------------------- pages/~/events/index.js | 2 - 2 files changed, 48 insertions(+), 53 deletions(-) diff --git a/components/event-calendar.js b/components/event-calendar.js index 7d2a3455e..2f69bf133 100644 --- a/components/event-calendar.js +++ b/components/event-calendar.js @@ -1,11 +1,11 @@ -import React, { useState } from 'react'; -import { Container, Row, Col, Button } from 'react-bootstrap'; +import React, { useState } from 'react' +import { Button } from 'react-bootstrap' -const EventCalendar = () => { - const [currentDate, setCurrentDate] = useState(new Date()); - const [selectedDate, setSelectedDate] = useState(null); +const EventCalendar = ({ ssrData }) => { + const [currentDate, setCurrentDate] = useState(new Date()) + const [selectedDate, setSelectedDate] = useState(null) - const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] const months = [ 'January', 'February', @@ -18,74 +18,67 @@ const EventCalendar = () => { 'September', 'October', 'November', - 'December', + 'December' ]; - + console.log(ssrData); const getDaysInMonth = () => { - const year = currentDate.getFullYear(); - const month = currentDate.getMonth(); - const daysInMonth = new Date(year, month + 1, 0).getDate(); - return daysInMonth; - }; + const year = currentDate.getFullYear() + const month = currentDate.getMonth() + const daysInMonth = new Date(year, month + 1, 0).getDate() + return daysInMonth + } const handleDayClick = (day) => { if (day !== '') { - setSelectedDate(new Date(currentDate.getFullYear(), currentDate.getMonth(), day)); + setSelectedDate(new Date(currentDate.getFullYear(), currentDate.getMonth(), day)) } - }; + } const handleMonthChange = (direction) => { - const newMonth = currentDate.getMonth() + direction; - setCurrentDate(new Date(currentDate.getFullYear(), newMonth, 1)); - setSelectedDate(null); // Reset the selectedDate when the month changes - }; + const newMonth = currentDate.getMonth() + direction + setCurrentDate(new Date(currentDate.getFullYear(), newMonth, 1)) + setSelectedDate(null) // Reset the selectedDate when the month changes + } const generateCalendar = () => { - const daysInMonth = getDaysInMonth(); - const month = months[currentDate.getMonth()]; - const year = currentDate.getFullYear(); - const firstDay = new Date(year, currentDate.getMonth(), 1); - const dayOfWeek = firstDay.getDay(); // Adjust the day of the week + const daysInMonth = getDaysInMonth() + const month = months[currentDate.getMonth()] + const year = currentDate.getFullYear() + const firstDay = new Date(year, currentDate.getMonth(), 1) + const dayOfWeek = firstDay.getDay() // Adjust the day of the week - const days = []; + const days = [] for (let i = 0; i < dayOfWeek; i++) { - days.push(''); + days.push('') } - for (let i = 1; i <= daysInMonth; i++) { - days.push(i); + for (let i = 1; i <= daysInMonth ;i++) { + days.push(i) } while (days.length % 7 !== 0) { - days.push(''); + days.push('') } - const weeks = []; - let week = []; + const weeks = [] + let week = [] for (let i = 0; i < days.length; i++) { - week.push(days[i]); + week.push(days[i]) if ((i + 1) % 7 === 0) { - weeks.push(week); - week = []; + weeks.push(week) + week = [] } } - const cellStyle = { - width: '40px', - height: '40px', - cursor: 'pointer', - '@media (min-width: 768px)': { - height: '80px', - }, - }; + return (

{month} {year}

- +
{daysOfWeek.map((day, index) => ( - ))} @@ -98,16 +91,18 @@ const EventCalendar = () => { ))} @@ -115,18 +110,20 @@ const EventCalendar = () => {
+ {day} handleDayClick(day)} > {day !== '' && ( -
+
{day}
)} {/* Placeholder for event names */} - {day !== '' &&
Event Name
} + {day !== '' &&
+
Bitcoin Halving
+
}
- -
+
); }; - return
{generateCalendar()}
; + return
{generateCalendar()}
}; -export default EventCalendar; \ No newline at end of file +export default EventCalendar diff --git a/pages/~/events/index.js b/pages/~/events/index.js index aef0cff4e..cccdd9f82 100644 --- a/pages/~/events/index.js +++ b/pages/~/events/index.js @@ -1,8 +1,6 @@ import Layout from '@/components/layout' -import Items from '@/components/items' import EventCalendar from '@/components/event-calendar' import { getGetServerSideProps } from '@/api/ssrApollo' -import RecentHeader from '@/components/recent-header' import { useRouter } from 'next/router' import { SUB_FULL, SUB_ITEMS } from '@/fragments/subs' import { COMMENT_TYPE_QUERY } from '@/lib/constants' From 8c0fcb3766e06bf5b2a03e1e9a4ea18b50d77dfd Mon Sep 17 00:00:00 2001 From: Dillon Date: Wed, 26 Jun 2024 20:51:37 -0500 Subject: [PATCH 10/13] Added events to event calendar --- api/resolvers/item.js | 30 +++++++++++ api/typeDefs/item.js | 2 + components/event-calendar.js | 99 ++++++++++++++++++++++-------------- 3 files changed, 92 insertions(+), 39 deletions(-) diff --git a/api/resolvers/item.js b/api/resolvers/item.js index 69ee54774..9edd674cb 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -605,7 +605,37 @@ export default { } return await models.item.count({ where }) + 1 + }, + events: async (parent, { startDate, endDate }, { models }) => { + try { + const events = await models.item.findMany({ + where: { + eventDate: { + gte: new Date(startDate), + lte: new Date(endDate) + }, + // Ensure we're only fetching events + eventLocation: { + not: null + } + }, + select: { + id: true, + title: true, + eventDate: true, + eventLocation: true + } + }) + return events + } catch (error) { + console.error('Error fetching events:', error) + throw new GraphQLError('Failed to fetch events', { + extensions: { code: 'INTERNAL_SERVER_ERROR' } + }) + } } + + }, Mutation: { diff --git a/api/typeDefs/item.js b/api/typeDefs/item.js index b1cc4865e..19196225a 100644 --- a/api/typeDefs/item.js +++ b/api/typeDefs/item.js @@ -10,6 +10,7 @@ export default gql` search(q: String, sub: String, cursor: String, what: String, sort: String, when: String, from: String, to: String): Items auctionPosition(sub: String, id: ID, bid: Int!): Int! itemRepetition(parentId: ID): Int! + events(startDate: Date!, endDate: Date!): [Item!]! } type TitleUnshorted { @@ -134,4 +135,5 @@ export default gql` nym: String! pct: Int! } + ` diff --git a/components/event-calendar.js b/components/event-calendar.js index 2f69bf133..0631401e7 100644 --- a/components/event-calendar.js +++ b/components/event-calendar.js @@ -1,31 +1,41 @@ -import React, { useState } from 'react' -import { Button } from 'react-bootstrap' +import React, { useState, useEffect } from 'react' +import { Button } from 'react-bootstrap' +import { useQuery } from '@apollo/client' +import { gql } from '@apollo/client' +import Link from 'next/link' -const EventCalendar = ({ ssrData }) => { +const GET_EVENTS = gql` + query GetEvents($startDate: Date!, $endDate: Date!) { + events(startDate: $startDate, endDate: $endDate) { + id + title + eventDate + eventLocation + } + } +` + +const EventCalendar = () => { const [currentDate, setCurrentDate] = useState(new Date()) const [selectedDate, setSelectedDate] = useState(null) + const startDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1) + const endDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0) + + const { loading, error, data } = useQuery(GET_EVENTS, { + variables: { startDate, endDate } + }) + const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] const months = [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December' - ]; - console.log(ssrData); + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ] + const getDaysInMonth = () => { const year = currentDate.getFullYear() const month = currentDate.getMonth() - const daysInMonth = new Date(year, month + 1, 0).getDate() - return daysInMonth + return new Date(year, month + 1, 0).getDate() } const handleDayClick = (day) => { @@ -37,7 +47,13 @@ const EventCalendar = ({ ssrData }) => { const handleMonthChange = (direction) => { const newMonth = currentDate.getMonth() + direction setCurrentDate(new Date(currentDate.getFullYear(), newMonth, 1)) - setSelectedDate(null) // Reset the selectedDate when the month changes + setSelectedDate(null) + } + + const getEventsForDay = (day) => { + if (!data || !data.events) return [] + const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day) + return data.events.filter(event => new Date(event.eventDate).toDateString() === date.toDateString()) } const generateCalendar = () => { @@ -45,13 +61,13 @@ const EventCalendar = ({ ssrData }) => { const month = months[currentDate.getMonth()] const year = currentDate.getFullYear() const firstDay = new Date(year, currentDate.getMonth(), 1) - const dayOfWeek = firstDay.getDay() // Adjust the day of the week + const dayOfWeek = firstDay.getDay() const days = [] for (let i = 0; i < dayOfWeek; i++) { days.push('') } - for (let i = 1; i <= daysInMonth ;i++) { + for (let i = 1; i <= daysInMonth; i++) { days.push(i) } while (days.length % 7 !== 0) { @@ -67,7 +83,6 @@ const EventCalendar = ({ ssrData }) => { week = [] } } - return (
@@ -99,10 +114,13 @@ const EventCalendar = ({ ssrData }) => { {day}
)} - {/* Placeholder for event names */} - {day !== '' &&
-
Bitcoin Halving
-
} + {day !== '' && getEventsForDay(day).map((event, index) => ( + +
+ {event.title} +
+ + ))} ))} @@ -110,20 +128,23 @@ const EventCalendar = ({ ssrData }) => {
-
- - -
+
+ + +
- ); - }; + ) + } + + if (loading) return

Loading...

+ if (error) return

Error: {error.message}

return
{generateCalendar()}
-}; +} -export default EventCalendar +export default EventCalendar \ No newline at end of file From d7c89d2313c4ff292ffb7686b452755f164b10c1 Mon Sep 17 00:00:00 2001 From: Dillon Date: Wed, 26 Jun 2024 20:55:06 -0500 Subject: [PATCH 11/13] fixed linter --- api/resolvers/item.js | 5 ++--- components/event-calendar.js | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/api/resolvers/item.js b/api/resolvers/item.js index 9edd674cb..0414d08f2 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -605,7 +605,8 @@ export default { } return await models.item.count({ where }) + 1 - }, + }, + events: async (parent, { startDate, endDate }, { models }) => { try { const events = await models.item.findMany({ @@ -634,8 +635,6 @@ export default { }) } } - - }, Mutation: { diff --git a/components/event-calendar.js b/components/event-calendar.js index 0631401e7..0f9eda5db 100644 --- a/components/event-calendar.js +++ b/components/event-calendar.js @@ -1,7 +1,6 @@ -import React, { useState, useEffect } from 'react' +import React, { useState } from 'react' import { Button } from 'react-bootstrap' -import { useQuery } from '@apollo/client' -import { gql } from '@apollo/client' +import { useQuery, gql } from '@apollo/client' import Link from 'next/link' const GET_EVENTS = gql` @@ -147,4 +146,4 @@ const EventCalendar = () => { return
{generateCalendar()}
} -export default EventCalendar \ No newline at end of file +export default EventCalendar From 06ec2500a9c82fb1db0480b5e663825d1e6bdbb4 Mon Sep 17 00:00:00 2001 From: Dillon Date: Thu, 27 Jun 2024 11:26:56 -0500 Subject: [PATCH 12/13] updated some calendar styles --- components/event-calendar.js | 80 +++++++++++++--------------- styles/event-calendar.module.css | 89 ++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 45 deletions(-) create mode 100644 styles/event-calendar.module.css diff --git a/components/event-calendar.js b/components/event-calendar.js index 0f9eda5db..84358e66e 100644 --- a/components/event-calendar.js +++ b/components/event-calendar.js @@ -2,6 +2,7 @@ import React, { useState } from 'react' import { Button } from 'react-bootstrap' import { useQuery, gql } from '@apollo/client' import Link from 'next/link' +import styles from '../styles/event-calendar.module.css' const GET_EVENTS = gql` query GetEvents($startDate: Date!, $endDate: Date!) { @@ -84,57 +85,46 @@ const EventCalendar = () => { } return ( -
-

+
+

{month} {year}

- - - - {daysOfWeek.map((day, index) => ( - - ))} - - - - {weeks.map((week, weekIndex) => ( - - {week.map((day, dayIndex) => ( - - ))} - - ))} - -
- {day} -
handleDayClick(day)} - > - {day !== '' && ( -
- {day} -
- )} - {day !== '' && getEventsForDay(day).map((event, index) => ( +
+ {daysOfWeek.map((day, index) => ( +
+ {day} +
+ ))} + {weeks.flat().map((day, index) => ( +
handleDayClick(day)} + > + {day !== '' && ( + <> +
{day}
+
+ {getEventsForDay(day).map((event, eventIndex) => ( -
+
{event.title}
))} -
-
-
- - -
+
+ + )} +
+ ))} +

+
+ +
) @@ -143,7 +133,7 @@ const EventCalendar = () => { if (loading) return

Loading...

if (error) return

Error: {error.message}

- return
{generateCalendar()}
+ return generateCalendar() } export default EventCalendar diff --git a/styles/event-calendar.module.css b/styles/event-calendar.module.css new file mode 100644 index 000000000..35170f09e --- /dev/null +++ b/styles/event-calendar.module.css @@ -0,0 +1,89 @@ +.calendarContainer { + max-width: 100%; + overflow-x: auto; + } + + .calendarHeader { + text-align: center; + margin-bottom: 1rem; + } + + .calendarGrid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 1px; + /* background-color: #e0e0e0; */ + background-color: #ffffff; + border: 1px solid #e0e0e0; + } + + .calendarHeaderCell { + background-color: #f5f5f5; + padding: 0.5rem; + text-align: center; + font-weight: bold; + } + + .calendarCell { + aspect-ratio: 1 / 1; + background-color: white; + padding: 0.5rem; + min-height: 100px; + display: flex; + flex-direction: column; + cursor: pointer; + /* border: 1px solid #e0e0e0; */ + } + + .emptyCell { + background-color: #f5f5f5; + } + + .selectedDay { + background-color: #e6f7ff; + } + + .dayNumber { + font-weight: bold; + margin-bottom: 0.25rem; + } + + .eventContainer { + flex-grow: 1; + overflow-y: auto; + } + + .event { + font-size: 0.8rem; + margin-bottom: 0.25rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: #1a1a1a; + background-color: #f0f0f0; + padding: 2px 4px; + border-radius: 2px; + } + + .buttonContainer { + display: flex; + justify-content: space-between; + margin-top: 1rem; + } + + @media (max-width: 768px) { + .calendarGrid { + font-size: 0.8rem; + } + + .calendarCell { + padding: 0.25rem; + min-height: 60px; + } + + .event { + font-size: 0.7rem; + } + } + + \ No newline at end of file From 8cd5a2b2e71734fe2f11c87c3937e07931a3b66c Mon Sep 17 00:00:00 2001 From: Dillon Date: Fri, 28 Jun 2024 10:53:55 -0500 Subject: [PATCH 13/13] updated vackground color for event --- styles/event-calendar.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/event-calendar.module.css b/styles/event-calendar.module.css index 35170f09e..412c63b77 100644 --- a/styles/event-calendar.module.css +++ b/styles/event-calendar.module.css @@ -60,7 +60,7 @@ overflow: hidden; text-overflow: ellipsis; color: #1a1a1a; - background-color: #f0f0f0; + background-color: #d6d6d6; padding: 2px 4px; border-radius: 2px; }