Files
cal.diy/packages/lib/OgImages.tsx
T
12466fb064 feat: New Organization Schema along with the ability to invite existing users to an organization (#13002)
* Add profile table

Copy to profile table

Update schema

Migrations

* wip

* Get switcher working

* Add organization seeder

* wip

* Remove dead code

* Fix unit tests

* relevantProfile -> profile

* cache bust

* One more comment

* Squash migrations

* Copy to profile table

* Update schema

* Fix existing user invite flow

* Add profile table

* Copy to profile table

* Update schema

* Format

* Add profile table

Copy to profile table

Update schema

Migrations

* Format

* wip

Get switcher working

Add organization seeder

wip

Remove dead code

Fix unit tests

relevantProfile -> profile

cache bust

One more comment

Squash migrations

Copy to profile table

Update schema

Fix existing user invite flow

Add profile table

Copy to profile table

Update schema

chore: small improvements to signup (#13227)

* removed features on mobile, better background color

* nit

* dark mode nits

New Crowdin translations by Github Action

fix: event reschedule workflow trigger (#13122)

* fix event reschedule workflow trigger

* uncomment previously failed test

* review fix

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>

chore: added community support to help (#13213)

refactor: Abstract `handleSeats` from `handleNewBooking` (#12062)

* Refactor createBooking

* Type fix

* Abstract handleSeats

* Create Invitee type

* Create OrganizerUser type

* Abstract addVideoCallDataToEvt

* Abstract createLoggerWithEventDetails

* Abstract `handleAppStatus` from handler

* Create ReqAppsStatus type

* Move `deleteMeeting` and `getCalendar`

* Set parameters for `handleSeats`

* Typescript refactor

* Change function params from req

* Type fix

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Erik <erik@erosemberg.com>

fix: Dynamic group booking  org-migration redirect (#13223)

* fix: Dynamic group booking redirect

* Fix flaky private team test

test: Check availability tab and all your functions (teste2e-availability) (#12434)

* add changes

* add changes

* add changes

* add changes

* Requested changes

* Change way to grab element

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>

fix: Reschedule a cancelled booking (#13170)

* fix: Reschedule a cancelled booking

* canceled instead of cancelled

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

v3.7.0

test: In Team Event type when location is Organizer Default App and U… (#12939)

* test: In Team Event type when location is Organizer Default App and User Metadata is emoty

* chore: use variable

* fix: type error

test: add test for invalid cal video (#13103)

* test: add test for invalid cal video

* chore: add confirm

* chore: fix type error

* chore: update code

* test: add unit test

* chore: type error

* chore

* chore: type

* chore: fix test

* chore

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

chore: add error message for no availability (#13230)

* chore: add error message for no default user availability

* chore: check only availability

* chore: change message

* chore: add eventType

fix: Team revert with members (#13228)

refactor: Refactor seats logic (#12905)

* Refactor createBooking

* Type fix

* Abstract handleSeats

* Create Invitee type

* Create OrganizerUser type

* Abstract addVideoCallDataToEvt

* Abstract createLoggerWithEventDetails

* Abstract `handleAppStatus` from handler

* Create ReqAppsStatus type

* Move `deleteMeeting` and `getCalendar`

* Set parameters for `handleSeats`

* Typescript refactor

* Change function params from req

* Type fix

* Move handleSeats

* Abstract lastAttendeeDeleteBooking

* Create function for rescheduling seated events

* Fix imports on reschedule seats function

* Fix imports

* Import handleSeats function

* Fix rescheduleUid type

* Refactor owner reschedule to new time slot

* Refactor combine two booking times together

* Reschedule as an attendee

* Refactor createNewSeat

* Remove old handleSeats

* Remove lastAttendeeDeleteBooking from handleNewBooking

* Test for new attendee right params are passed

* Unit test params for reschedule

* Typo fix

* Clean up

* Create new seat test

* Test when attendee already signs up for booking

* Type fix

* Test reschedule move attendee to existing booking

* On reschedule create new booking

* Test on last attendee cancel booking

* Owner reschedule to new time slot

* Owner rescheduling, merge two bookings together

* Test: when merging more than available seats, then fail

* Test: fail when event is full

* Remove duplicate E2E tests

* Clean up

* Rename `addVideoCallDataToEvt` to `addVideoCallDataToEvent`

* Refactor `calcAppsStatus`

* Assign `evt` to resutl of `addVideoCallDataToEvent`

* Use prisma.transaction when moving attendees

* Clean create seat call

* Use ErrorCode enum

* Use attendeeRescheduledSeatedBooking function

* Await function

* Prevent double triggering of workflows

* Use inviteeToAdd in createNewSeat

* Remove unused error code

* Remove old handleSeats file

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Erik <erik@erosemberg.com>

feat: Allow admins to filter bookings only by attendee emails (#13236)

* chore: Allow admins to filter bookings only by attendee emails

* Fix type error

Fixed [CAL-2930] Org branding isnt centered in shell #13225 (#13237)

Add profile table

Copy to profile table

Update schema

Migrations

Format

* wip

* Add profile relation everywhere

* Fix schema

* Fix invite handler

* Add profileId during eventTypec reation

* Get dashboard working with profile relatioon

* Add workflow create repository

* Add profileId to host

* Add profileId to credential table

* Add destinationCalendar repository

* Add profileId to membership

* Revert all profile related changes

* Add acme and dunder

* Fix membership profileId usgae

* Various fixes

* Some renaming

* Fix failing tests

* Debugging next-aut

* Consolidate migrations

* Testing fixes

* Fix unit tests

* Self review feedback

* PR feedback

* Fix orgMigration as well

* Add profile schema and copy users to it

* Remove migrations from here

* Fix type issues

* Fix type error after main merge

* Remove movedFromUserId

* Add missing migration file

* Self review addressed

* Joe's review addressed

* Fix profile switcher

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-02-02 21:34:13 +00:00

270 lines
10 KiB
TypeScript

import React from "react";
import { CAL_URL, LOGO } from "./constants";
// Ensures tw prop is typed.
declare module "react" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface HTMLAttributes<T> {
tw?: string;
}
}
export interface MeetingImageProps {
title: string;
profile: { name: string; image?: string | null };
users?: { name: string; username: string }[];
}
export interface AppImageProps {
name: string;
description: string;
slug: string;
}
export interface GenericImageProps {
title: string;
description: string;
}
export interface ScreenshotImageProps {
image: string;
/**
* Fallback image to use if the image prop fails to load.
*/
fallbackImage: string;
}
interface WrapperProps {
children: React.ReactNode;
variant?: "light" | "dark";
rotateBackground?: boolean;
}
const joinMultipleNames = (names: string[] = []) => {
const lastName = names.pop();
return `${names.length > 0 ? `${names.join(", ")} & ${lastName}` : lastName}`;
};
/**
* Test urls:
* 1. 1 user http://localhost:3000/api/social/og/image?type=meeting&title=super%20long%20event%20title%20for%20testing%20purposes&meetingProfileName=Pro%20Example&meetingImage=http://localhost:3000/pro/avatar.png&names=Pro%20Example&usernames=pro
* 2. Team event (collection), lot's of people, long title http://localhost:3000/api/social/og/image?type=meeting&title=Getting%20to%20know%20us%20and%20have%20a%20beer%20together&meetingProfileName=Seeded%20Team&names=Team%20Pro%20Example%202&names=Team%20Pro%20Example%203&names=Team%20Pro%20Example%204&names=Team%20Free%20Example&names=Team%20Pro%20Example&usernames=teampro2&usernames=teampro3&usernames=teampro4&usernames=teamfree&usernames=teampro
* 3. Team event of 2 (collection), http://localhost:3000/api/social/og/image?type=meeting&title=Getting%20to%20know%20each%20other&meetingProfileName=Seeded%20Team&names=Team%20Pro%20Example%202&names=Team%20Pro%20Example%203&usernames=teampro2&usernames=teampro3
* 4. Team event (round robin) http://localhost:3000/api/social/og/image?type=meeting&title=Round%20Robin%20Seeded%20Team%20Event&meetingProfileName=Seeded%20Team
* 5. Dynamic collective (2 persons) http://localhost:3000/api/social/og/image?type=meeting&title=15min&meetingProfileName=Team%20Pro%20Example,%20Pro%20Example&names=Team%20Pro%20Example&names=Pro%20Example&usernames=teampro&usernames=pro
*/
export const constructMeetingImage = (
{ title, users = [], profile }: MeetingImageProps,
encodeUri = true
): string => {
const url = [
`?type=meeting`,
`&title=${encodeURIComponent(title)}`,
`&meetingProfileName=${encodeURIComponent(profile.name)}`,
profile.image && `&meetingImage=${encodeURIComponent(CAL_URL + profile.image)}`,
`${users.map((user) => `&names=${encodeURIComponent(user.name)}`).join("")}`,
`${users.map((user) => `&usernames=${encodeURIComponent(user.username)}`).join("")}`,
// Joining a multiline string for readability.
].join("");
return encodeUri ? encodeURIComponent(url) : url;
};
/**
* Test url:
* http://localhost:3000/api/social/og/image?type=app&name=Huddle01&slug=/api/app-store/huddle01video/icon.svg&description=Huddle01%20is%20a%20new%20video%20conferencing%20software%20native%20to%20Web3%20and%20is%20comparable%20to%20a%20decentralized%20version%20of%20Zoom.%20It%20supports%20conversations%20for...
*/
export const constructAppImage = ({ name, slug, description }: AppImageProps, encodeUri = true): string => {
const url = [
`?type=app`,
`&name=${encodeURIComponent(name)}`,
`&slug=${encodeURIComponent(slug)}`,
`&description=${encodeURIComponent(description)}`,
// Joining a multiline string for readability.
].join("");
return encodeUri ? encodeURIComponent(url) : url;
};
export const constructGenericImage = ({ title, description }: GenericImageProps, encodeUri = true) => {
const url = [
`?type=generic`,
`&title=${encodeURIComponent(title)}`,
`&description=${encodeURIComponent(description)}`,
// Joining a multiline string for readability.
].join("");
return encodeUri ? encodeURIComponent(url) : url;
};
const Wrapper = ({ children, variant = "light", rotateBackground }: WrapperProps) => (
<div tw="flex w-full h-full">
<img
tw="flex absolute left-0 top-0 w-full h-[110%]"
style={rotateBackground ? { transform: "rotate(180deg)" } : undefined}
src={`${CAL_URL}/social-bg-${variant}-lines.jpg`}
alt="background"
width="1200"
height="600"
/>
<div tw="flex flex-col w-full h-full px-[80px] py-[70px] items-start justify-center">{children}</div>
</div>
);
export const Meeting = ({ title, users = [], profile }: MeetingImageProps) => {
// We filter attendees here based on whether they have an image and filter duplicates.
// Users ALWAYS have an image (albeit a gray empty person avatar), so this mainly filters out
// any non existing images for dynamic collectives, while at the same time removing them from
// the names list, because the profile name of that event is a concatenation of all names.
const attendees = (profile.image ? [profile, ...users] : users).filter(
(value, index, self) => self.findIndex((v) => v.name === value.name) == index
);
// Construct list of avatar urls, removes duplicates and empty profile images
const avatars = attendees
.map((user) => {
if ("image" in user && user?.image) return user.image;
if ("username" in user && user?.username) return `${CAL_URL}/${user.username}/avatar.png`;
return null;
})
.filter(Boolean) as string[];
// In case there is NO other attendee than the single meeting profile without an image, we add
// that name back in here, since the event probably is a round robin event.
const names = attendees.length > 0 ? attendees.map((user) => user.name) : [profile.name];
return (
<Wrapper variant="dark">
<div tw="h-full flex flex-col justify-start">
<div tw="flex items-center justify-center" style={{ fontFamily: "cal", fontWeight: 300 }}>
<img src={`${CAL_URL}/${LOGO}`} width="350" alt="Logo" />
{avatars.length > 0 && (
<div style={{ color: "#111827" }} tw="font-bold text-[92px] mx-8 bottom-2">
/
</div>
)}
<div tw="flex flex-row">
{avatars.slice(0, 3).map((avatar) => (
<img
tw="rounded-full mr-[-36px] border-[6px] border-[#CDCED2]"
key={avatar}
src={avatar}
alt="Profile picture"
width="160"
/>
))}
{avatars.length > 3 && (
<div tw="flex items-center justify-center w-[160px] h-[160px] rounded-full bg-black text-inverted text-[54px] font-bold">
<span tw="flex top-[-5px] left-[-5px]">+{avatars.length - 3}</span>
</div>
)}
</div>
</div>
<div style={{ color: "#111827" }} tw="relative flex text-[54px] w-full flex-col mt-auto">
<div
tw="flex w-[1040px] overflow-hidden"
style={{ whiteSpace: "nowrap", fontFamily: "cal", textOverflow: "ellipsis" }}>
Meet {joinMultipleNames(names)}
</div>
<div
tw="flex mt-3 w-[1040px] overflow-hidden"
style={{ whiteSpace: "nowrap", fontFamily: "inter", textOverflow: "ellipsis" }}>
{title}
</div>
</div>
</div>
</Wrapper>
);
};
const VisualBlur = ({ visualSlug }: { visualSlug: string }) => {
// Making a blur of a dark logo is very ugly. We use the filename to indicate,
// when we don't want to render these blurry blob backgrounds.
if (visualSlug.indexOf("dark") > -1) return null;
return (
<div tw="flex relative">
{/* Blob top left */}
<div
tw="flex absolute top-[-100px] left-[-100px] w-[400px] h-[400px] opacity-80"
style={{
filter: "blur(98px)",
backgroundColor: "rgba(255, 255, 255, 0.7)",
backgroundImage: `url(${CAL_URL}${visualSlug})`,
backgroundSize: "400px 400px",
}}
/>
{/* Blob bottom right */}
<div
tw="flex absolute top-[230px] left-[660px] w-[630px] h-[630px] opacity-80"
style={{
filter: "blur(150px)",
backgroundColor: "rgba(255, 255, 255, 0.7)",
backgroundImage: `url(${CAL_URL}${visualSlug})`,
backgroundSize: "630px 630px",
}}
/>
</div>
);
};
export const App = ({ name, description, slug }: AppImageProps) => (
<Wrapper>
<img src={`${CAL_URL}/${LOGO}`} width="150" alt="Logo" tw="absolute right-[48px] top-[48px]" />
<VisualBlur visualSlug={slug} />
<div tw="flex items-center w-full">
<div tw="flex">
<img src={`${CAL_URL}${slug}`} alt="App icon" width="172" />
</div>
</div>
<div style={{ color: "#111827" }} tw="flex mt-auto w-full flex-col">
<div tw="flex text-[64px] mb-7" style={{ fontFamily: "cal", fontWeight: 600 }}>
{name}
</div>
<div tw="flex text-[36px]" style={{ fontFamily: "inter" }}>
{description}
</div>
</div>
</Wrapper>
);
export const Generic = ({ title, description }: GenericImageProps) => (
<Wrapper>
<div tw="h-full flex flex-col justify-start">
<div tw="flex items-center justify-center" style={{ fontFamily: "cal", fontWeight: 300 }}>
<img src={`${CAL_URL}/cal-logo-word-black.svg`} width="350" alt="Logo" />
</div>
<div style={{ color: "#111827" }} tw="relative flex text-[54px] w-full flex-col mt-auto">
<div tw="flex w-[1040px]" style={{ fontFamily: "cal" }}>
{title}
</div>
<div tw="flex mt-3 w-[1040px]" style={{ fontFamily: "inter" }}>
{description}
</div>
</div>
</div>
</Wrapper>
);
export const ScreenShot = ({ image, fallbackImage }: ScreenshotImageProps) => (
<Wrapper rotateBackground>
<div tw="relative h-full w-full flex flex-col justify-center items-center">
<div tw="relative mt-[140px] flex rounded-2xl" style={{ boxShadow: "0 0 45px -3px rgba(0,0,0,.3)" }}>
<img
src={fallbackImage}
tw="absolute inset-0 rounded-2xl"
width="1024"
height="576"
alt="screenshot"
/>
<img src={image} width="1024" height="576" tw="rounded-2xl" alt="screenshot" />
</div>
</div>
</Wrapper>
);