diff --git a/apps/desktop2/public/assets/teams.png b/apps/desktop2/public/assets/teams.png new file mode 100644 index 0000000000..bc4bc8024c Binary files /dev/null and b/apps/desktop2/public/assets/teams.png differ diff --git a/apps/desktop2/src/components/main/body/sessions/outer-header/metadata.tsx b/apps/desktop2/src/components/main/body/sessions/outer-header/metadata.tsx index 9a2ece97a6..107e598322 100644 --- a/apps/desktop2/src/components/main/body/sessions/outer-header/metadata.tsx +++ b/apps/desktop2/src/components/main/body/sessions/outer-header/metadata.tsx @@ -76,9 +76,9 @@ export function SessionMetadata({ title: eventRow.title ?? "Untitled Event", started_at: eventRow.started_at, ended_at: eventRow.ended_at, - location: null, // TODO: Add location field to event schema - meeting_link: null, // TODO: Add meeting_link field to event schema - description: null, // TODO: Add description field to event schema + location: (eventRow.location as string | undefined) ?? null, + meeting_link: (eventRow.meeting_link as string | undefined) ?? null, + description: (eventRow.description as string | undefined) ?? null, participants, }; }, [sessionRow.event_id, eventRow, store, participantMappingIds]); diff --git a/apps/desktop2/src/devtool/seed/empty.ts b/apps/desktop2/src/devtool/seed/empty.ts new file mode 100644 index 0000000000..c39e50db48 --- /dev/null +++ b/apps/desktop2/src/devtool/seed/empty.ts @@ -0,0 +1,10 @@ +import type { Store as PersistedStore } from "../../store/tinybase/persisted"; +import type { SeedDefinition } from "./shared"; + +export const emptySeed: SeedDefinition = { + id: "empty", + label: "Empty State", + run: (store: PersistedStore) => { + store.delTables(); + }, +}; diff --git a/apps/desktop2/src/devtool/seed/index.ts b/apps/desktop2/src/devtool/seed/index.ts index 2c11e641f7..469c67ee9a 100644 --- a/apps/desktop2/src/devtool/seed/index.ts +++ b/apps/desktop2/src/devtool/seed/index.ts @@ -1,6 +1,7 @@ +import { emptySeed } from "./empty"; import type { SeedDefinition } from "./shared"; import { v1Seed } from "./v1"; export { type SeedDefinition } from "./shared"; -export const seeds: SeedDefinition[] = [v1Seed]; +export const seeds: SeedDefinition[] = [emptySeed, v1Seed]; diff --git a/apps/desktop2/src/devtool/seed/shared.ts b/apps/desktop2/src/devtool/seed/shared.ts index 8ae5a3a60c..c16787386d 100644 --- a/apps/desktop2/src/devtool/seed/shared.ts +++ b/apps/desktop2/src/devtool/seed/shared.ts @@ -273,53 +273,40 @@ export const createChatMessage = ( export const createEvent = (calendar_id: string) => { const timePattern = faker.helpers.weightedArrayElement([ - { weight: 10, value: "past-recent" }, - { weight: 5, value: "past-older" }, - { weight: 15, value: "imminent" }, - { weight: 25, value: "today-tomorrow" }, - { weight: 20, value: "this-week" }, - { weight: 15, value: "next-few-weeks" }, - { weight: 10, value: "distant" }, + { weight: 5, value: "last-year" }, + { weight: 15, value: "past-two-weeks" }, + { weight: 10, value: "today" }, + { weight: 15, value: "next-few-days" }, + { weight: 15, value: "next-two-weeks" }, ]); let startsAt: Date; const now = faker.defaultRefDate(); switch (timePattern) { - case "past-recent": - const daysAgo = faker.number.int({ min: 1, max: 7 }); - startsAt = new Date(now.getTime() - daysAgo * 24 * 60 * 60 * 1000); + case "last-year": + const daysLastYear = faker.number.int({ min: 180, max: 365 }); + startsAt = new Date(now.getTime() - daysLastYear * 24 * 60 * 60 * 1000); break; - case "past-older": - const weeksAgo = faker.number.int({ min: 1, max: 4 }); - startsAt = new Date(now.getTime() - weeksAgo * 7 * 24 * 60 * 60 * 1000); + case "past-two-weeks": + const daysPast = faker.number.int({ min: 1, max: 14 }); + startsAt = new Date(now.getTime() - daysPast * 24 * 60 * 60 * 1000); break; - case "imminent": - const minutes = faker.helpers.arrayElement([5, 10, 15, 30, 45, 60, 90, 120]); - startsAt = new Date(now.getTime() + minutes * 60 * 1000); + case "today": + const hoursToday = faker.number.float({ min: -12, max: 12, fractionDigits: 1 }); + startsAt = new Date(now.getTime() + hoursToday * 60 * 60 * 1000); break; - case "today-tomorrow": - const hoursAhead = faker.number.float({ min: 0.5, max: 36, fractionDigits: 1 }); - startsAt = new Date(now.getTime() + hoursAhead * 60 * 60 * 1000); + case "next-few-days": + const daysNext = faker.number.int({ min: 1, max: 7 }); + startsAt = new Date(now.getTime() + daysNext * 24 * 60 * 60 * 1000); break; - case "this-week": - const daysAhead = faker.number.int({ min: 2, max: 7 }); - startsAt = new Date(now.getTime() + daysAhead * 24 * 60 * 60 * 1000); - break; - - case "next-few-weeks": - const weeksAhead = faker.number.int({ min: 1, max: 3 }); - const extraDays = faker.number.int({ min: 0, max: 6 }); - startsAt = new Date(now.getTime() + (weeksAhead * 7 + extraDays) * 24 * 60 * 60 * 1000); - break; - - case "distant": - const monthsAhead = faker.number.float({ min: 1, max: 3, fractionDigits: 1 }); - startsAt = new Date(now.getTime() + monthsAhead * 30 * 24 * 60 * 60 * 1000); + case "next-two-weeks": + const daysLater = faker.number.int({ min: 8, max: 14 }); + startsAt = new Date(now.getTime() + daysLater * 24 * 60 * 60 * 1000); break; default: @@ -329,6 +316,51 @@ export const createEvent = (calendar_id: string) => { const durationHours = faker.number.float({ min: 0.25, max: 4, fractionDigits: 2 }); const endsAt = new Date(startsAt.getTime() + durationHours * 60 * 60 * 1000); + const meetingType = faker.helpers.weightedArrayElement([ + { weight: 50, value: "online" }, + { weight: 30, value: "offline" }, + { weight: 20, value: "hybrid" }, + ]); + + const videoProviders = [ + { domain: "zoom.us", name: "Zoom" }, + { domain: "meet.google.com", name: "Google Meet" }, + { domain: "teams.microsoft.com", name: "Microsoft Teams" }, + { domain: "whereby.com", name: "Whereby" }, + { domain: "around.co", name: "Around" }, + ]; + + const locations = [ + "Conference Room A", + "Conference Room B", + "Main Office - 3rd Floor", + "Starbucks Downtown", + "WeWork Coworking Space", + "Client Office", + "HQ Building 2", + "Meeting Room Delta", + "Cafeteria", + "Rooftop Lounge", + ]; + + let meeting_link: string | undefined; + let location: string | undefined; + let description: string | undefined; + + if (meetingType === "online" || meetingType === "hybrid") { + const provider = faker.helpers.arrayElement(videoProviders); + const meetingId = faker.string.alphanumeric(10); + meeting_link = `https://${provider.domain}/${meetingId}`; + } + + if (meetingType === "offline" || meetingType === "hybrid") { + location = faker.helpers.arrayElement(locations); + } + + if (faker.datatype.boolean({ probability: 0.7 })) { + description = faker.lorem.sentences(faker.number.int({ min: 1, max: 3 })); + } + return { id: id(), data: { @@ -338,6 +370,9 @@ export const createEvent = (calendar_id: string) => { started_at: startsAt.toISOString(), ended_at: endsAt.toISOString(), created_at: faker.date.recent({ days: 30 }).toISOString(), + location, + meeting_link, + description, } satisfies Event, }; }; @@ -449,7 +484,7 @@ export const generateMockData = (config: MockConfig) => { ); Array.from({ length: sessionCount }, () => { - const shouldLinkToEvent = endedEvents.length > 0 && faker.datatype.boolean({ probability: 0.5 }); + const shouldLinkToEvent = endedEvents.length > 0 && faker.datatype.boolean({ probability: 0.6 }); const shouldAddToFolder = allFolderIds.length > 0 && faker.datatype.boolean({ probability: 0.6 }); const eventId = shouldLinkToEvent ? faker.helpers.arrayElement(endedEvents).id : undefined; diff --git a/apps/desktop2/src/devtool/seed/v1.ts b/apps/desktop2/src/devtool/seed/v1.ts index 5275e851fa..9366779f6c 100644 --- a/apps/desktop2/src/devtool/seed/v1.ts +++ b/apps/desktop2/src/devtool/seed/v1.ts @@ -24,6 +24,7 @@ export const v1Seed: SeedDefinition = { id: "v1", label: "Seed V1", run: (store: PersistedStore) => { + store.delTables(); store.setTables(V1); }, }; diff --git a/apps/desktop2/src/store/tinybase/persisted.ts b/apps/desktop2/src/store/tinybase/persisted.ts index c8f718858c..0a651bbb45 100644 --- a/apps/desktop2/src/store/tinybase/persisted.ts +++ b/apps/desktop2/src/store/tinybase/persisted.ts @@ -47,6 +47,9 @@ export const eventSchema = baseEventSchema.omit({ id: true }).extend({ created_at: z.string(), started_at: z.string(), ended_at: z.string(), + location: z.preprocess(val => val ?? undefined, z.string().optional()), + meeting_link: z.preprocess(val => val ?? undefined, z.string().optional()), + description: z.preprocess(val => val ?? undefined, z.string().optional()), }); export const calendarSchema = baseCalendarSchema.omit({ id: true }).extend({ created_at: z.string() }); @@ -158,6 +161,9 @@ const SCHEMA = { title: { type: "string" }, started_at: { type: "string" }, ended_at: { type: "string" }, + location: { type: "string" }, + meeting_link: { type: "string" }, + description: { type: "string" }, } satisfies InferTinyBaseSchema, mapping_session_participant: { user_id: { type: "string" }, diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 959fbcb26f..4c7f32a011 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -51,6 +51,9 @@ export const events = pgTable(TABLE_EVENTS, { title: text("title").notNull(), started_at: timestamp("started_at").notNull(), ended_at: timestamp("ended_at").notNull(), + location: text("location"), + meeting_link: text("meeting_link"), + description: text("description"), }); export const TABLE_CALENDARS = "calendars"; diff --git a/packages/ui/src/components/ui/kbd.tsx b/packages/ui/src/components/ui/kbd.tsx new file mode 100644 index 0000000000..d314bf237a --- /dev/null +++ b/packages/ui/src/components/ui/kbd.tsx @@ -0,0 +1,28 @@ +import { cn } from "@hypr/ui/lib/utils"; + +function Kbd({ className, ...props }: React.ComponentProps<"kbd">) { + return ( + + ); +} + +function KbdGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( + + ); +} + +export { Kbd, KbdGroup };