diff --git a/apps/mail-bridge/routes/postal/mail/inbound/[...mailServer].post.ts b/apps/mail-bridge/routes/postal/mail/inbound/[...mailServer].post.ts index 5cef66aa..dfc328c1 100644 --- a/apps/mail-bridge/routes/postal/mail/inbound/[...mailServer].post.ts +++ b/apps/mail-bridge/routes/postal/mail/inbound/[...mailServer].post.ts @@ -1,6 +1,6 @@ import { db } from '@u22n/database'; import { simpleParser } from 'mailparser'; -// @ts-expect-error, No types yet +// @ts-expect-error, not typed yet import { authenticate } from 'mailauth'; import { and, eq, inArray } from '@u22n/database/orm'; import type { InferInsertModel } from '@u22n/database/orm'; @@ -55,24 +55,26 @@ export default eventHandler(async (event) => { return; } - let orgId: number | null = null; + let orgId: number = 0; let orgPublicId: string | null = null; - - if (!event.context.params?.mailServer) { - console.error('⛔ no mailserver found in the event context', { + const [orgIdStr, mailserverId] = event.context.params!.mailServer!.split('/'); + if (!orgIdStr || !mailserverId) { + console.error('⛔ no orgId or mailserverId found', { payloadPostalEmailId }); return; } - const [orgIdStr = '', mailserverId = ''] = - event.context.params.mailServer.split('/'); - if (orgIdStr === '0' || mailserverId === 'root') { // handle for root emails // get the email identity for the root email - const [rootEmailUsername = '', rootEmailDomain = ''] = - payloadEmailTo.split('@'); + const [rootEmailUsername, rootEmailDomain] = payloadEmailTo.split('@'); + if (!rootEmailUsername || !rootEmailDomain) { + console.error('⛔ invalid root email username or domain', { + payloadPostalEmailId + }); + return; + } const rootEmailIdentity = await db.query.emailIdentities.findFirst({ where: and( eq(emailIdentities.username, rootEmailUsername), @@ -100,6 +102,8 @@ export default eventHandler(async (event) => { orgId = rootEmailIdentity.orgId; orgPublicId = rootEmailIdentity.org.publicId; } else { + orgId = Number(orgIdStr); + // handle for org emails if (!validateTypeId('postalServers', mailserverId)) { console.error('⛔ invalid mailserver id', { @@ -123,7 +127,6 @@ export default eventHandler(async (event) => { } } }); - // prelimary checks if (!mailServer || +mailServer.orgId !== orgId) { console.error('⛔ mailserver not found or does not belong to this org', { @@ -138,6 +141,13 @@ export default eventHandler(async (event) => { orgPublicId = mailServer.org.publicId; } + if (orgId === 0 || !orgPublicId) { + console.error('⛔ orgId or orgPublicId not found', { + payloadPostalEmailId + }); + return; + } + //* parse the email payload const payloadEmail = Buffer.from(payloadEmailB64, 'base64').toString('utf-8'); const parsedEmail = await simpleParser(payloadEmail); @@ -162,6 +172,17 @@ export default eventHandler(async (event) => { } // Extract key email properties + if ( + !parsedEmail.from || + !parsedEmail.to || + !parsedEmail.subject || + !parsedEmail.messageId + ) { + console.error('⛔ missing email attributes', { + payloadPostalEmailId + }); + return; + } if (parsedEmail.from.value.length > 1) { console.error( '⛔ multiple from addresses detected in a message, only using first email address', @@ -238,6 +259,20 @@ export default eventHandler(async (event) => { : Promise.resolve([]) ]); + if ( + !messageToPlatformObject || + !messageToPlatformObject[0] || + !messageFromPlatformObject || + !messageFromPlatformObject[0] + ) { + console.error( + '⛔ no messageToPlatformObject or messageFromPlatformObject found', + { + payloadPostalEmailId + } + ); + return; + } // check the from contact and update their signature if it is null if (messageFromPlatformObject[0]?.type === 'contact') { const contact = await db.query.contacts.findFirst({ @@ -250,7 +285,13 @@ export default eventHandler(async (event) => { signaturePlainText: true } }); - if (!contact?.signaturePlainText) { + if (!contact) { + console.error('⛔ no contact found for from address', { + payloadPostalEmailId + }); + return; + } + if (!contact.signaturePlainText) { await db .update(contacts) .set({ @@ -279,7 +320,7 @@ export default eventHandler(async (event) => { // if theres no email identity ids, then we assume that this email has no destination, so we need to send the bounce message if (!emailIdentityIds.length) { - //! SEND BOUNCE MESSAGE + // !FIX SEND BOUNCE MESSAGE console.error('⛔ no email identity ids found', { messageAddressIds }); return; @@ -325,7 +366,7 @@ export default eventHandler(async (event) => { //* start to process the conversation let hasReplyToButIsNewConvo: boolean | null = null; - let convoId: number | null = null; + let convoId: number = 0; let replyToId: number | null = null; let subjectId: number | null = null; @@ -336,6 +377,12 @@ export default eventHandler(async (event) => { const fromAddressPlatformObject = messageFromPlatformObject.find( (a) => a.ref === 'from' ); + if (!fromAddressPlatformObject) { + console.error('⛔ no from address platform object found', { + payloadPostalEmailId + }); + return; + } const convoParticipantsToAdd: ConvoParticipantInsertDbType[] = []; // if the email has a reply to header, then we need to check if a message exists in the system with that reply to id @@ -382,6 +429,15 @@ export default eventHandler(async (event) => { convoId = existingMessage.convoId; replyToId = existingMessage.id; + if (!existingMessage.convoId || convoId === 0) { + console.error('⛔ no convoId found for existing message', { + payloadPostalEmailId + }); + return; + } + if (!existingMessage.subject) { + existingMessage.subject = { id: 0, subject: 'No Subject' }; + } // check if the subject is the same as existing, if not, add a new subject to the convo if (subject !== existingMessage.subject?.subject) { const newSubject = await db.insert(convos).values({ @@ -535,8 +591,9 @@ export default eventHandler(async (event) => { id: true } }); - fromAddressParticipantId = contactParticipant?.id || null; - } else if (fromAddressPlatformObject?.type === 'emailIdentity') { + // @ts-expect-error we check and define earlier up + fromAddressParticipantId = contactParticipant.id; + } else if (fromAddressPlatformObject.type === 'emailIdentity') { // we need to get the first person/group in the routing rule and add them to the convo const emailIdentityParticipant = await db.query.emailIdentities.findFirst( { @@ -565,26 +622,31 @@ export default eventHandler(async (event) => { } ); const firstDestination = - emailIdentityParticipant?.routingRules.destinations[0]; + // @ts-expect-error we check and define earlier up + emailIdentityParticipant.routingRules.destinations[0]; let convoParticipantFromAddressIdentity; - if (firstDestination?.orgMemberId) { + // @ts-expect-error we check and define earlier up + if (firstDestination.orgMemberId) { convoParticipantFromAddressIdentity = await db.query.convoParticipants.findFirst({ where: and( eq(convoParticipants.orgId, orgId), - eq(convoParticipants.convoId, convoId || 0), + eq(convoParticipants.convoId, convoId), + // @ts-expect-error we check and define earlier up eq(convoParticipants.orgMemberId, firstDestination.orgMemberId) ), columns: { id: true } }); - } else if (firstDestination?.groupId) { + // @ts-expect-error we check and define earlier up + } else if (firstDestination.groupId) { convoParticipantFromAddressIdentity = await db.query.convoParticipants.findFirst({ where: and( eq(convoParticipants.orgId, orgId), - eq(convoParticipants.convoId, convoId || 0), + eq(convoParticipants.convoId, convoId), + // @ts-expect-error we check and define earlier up eq(convoParticipants.userGroupId, firstDestination.groupId) ), columns: { @@ -592,8 +654,8 @@ export default eventHandler(async (event) => { } }); } - fromAddressParticipantId = - convoParticipantFromAddressIdentity?.id || null; + // @ts-expect-error we check and define earlier up + fromAddressParticipantId = convoParticipantFromAddressIdentity.id; } } @@ -604,20 +666,26 @@ export default eventHandler(async (event) => { to: messageToPlatformObject.map((a) => { return { id: a.id, - type: a.type + type: a.type, + publicId: a.publicId, + email: a.email }; }), from: messageFromPlatformObject.map((a) => { return { id: a.id, - type: a.type + type: a.type, + publicId: a.publicId, + email: a.email }; }), cc: messageCcPlatformObject.map((a) => { return { id: a.id, - type: a.type + type: a.type, + publicId: a.publicId, + email: a.email }; }) || [], postalMessages: [ @@ -625,8 +693,7 @@ export default eventHandler(async (event) => { id: payloadPostalEmailId, postalMessageId: messageId, recipient: payloadEmailTo, - // @ts-expect-error, not sure about this yet - token: null + token: '' } ], emailHeaders: JSON.stringify(parsedEmail.headers) @@ -641,7 +708,7 @@ export default eventHandler(async (event) => { convoEntryBody, tipTapExtensions ); - + // @ts-expect-error we check and define earlier up const insertNewConvoEntry = await db.insert(convoEntries).values({ orgId: orgId, publicId: typeIdGenerator('convoEntries'), @@ -682,7 +749,7 @@ export default eventHandler(async (event) => { publicId: string; signedUrl: string; }; - const preUpload = (await fetch( + const preUpload: PreSignedData = await fetch( `${useRuntimeConfig().storage.url}/api/attachments/internalPresign`, { method: 'post', @@ -696,7 +763,7 @@ export default eventHandler(async (event) => { filename: input.fileName }) } - ).then((res) => res.json())) as PreSignedData; + ).then((res: Response) => res.json() as Promise); if (!preUpload || !preUpload.publicId || !preUpload.signedUrl) { throw new Error('Missing attachmentPublicId or presignedUrl'); } @@ -736,8 +803,8 @@ export default eventHandler(async (event) => { await Promise.all( attachments.map((attachment) => { return uploadAndAttachAttachment({ - orgId: orgId || 0, - fileName: attachment.filename || '', + orgId: orgId, + fileName: attachment.filename || 'No Filename', fileType: attachment.contentType, fileContent: attachment.content, convoId: convoId || 0, diff --git a/apps/mail-bridge/trpc/routers/sendMailRouter.ts b/apps/mail-bridge/trpc/routers/sendMailRouter.ts index 31347448..4987dc65 100644 --- a/apps/mail-bridge/trpc/routers/sendMailRouter.ts +++ b/apps/mail-bridge/trpc/routers/sendMailRouter.ts @@ -56,7 +56,7 @@ export const sendMailRouter = router({ } ] } - } as ConvoEntryMetadata + } }; } @@ -77,6 +77,7 @@ export const sendMailRouter = router({ where: eq(emailIdentities.publicId, sendAsEmailIdentityPublicId), columns: { id: true, + publicId: true, username: true, domainName: true, sendName: true, @@ -256,7 +257,17 @@ export const sendMailRouter = router({ const entryMetadata: ConvoEntryMetadata = { email: { to: [], - from: [{ id: +sendAsEmailIdentity.id, type: 'emailIdentity' }], + from: [ + { + id: +sendAsEmailIdentity.id, + type: 'emailIdentity', + publicId: sendAsEmailIdentity.publicId, + email: + sendAsEmailIdentity.username + + '@' + + sendAsEmailIdentity.domainName + } + ], cc: [], messageId: sendMailPostalResponse.data.message_id, postalMessages: transformedMessages.map((message) => ({ @@ -267,7 +278,7 @@ export const sendMailRouter = router({ }; return { success: true, - metadata: entryMetadata + metadata: entryMetadata as ConvoEntryMetadata }; } else { console.error( diff --git a/apps/mail-bridge/types.ts b/apps/mail-bridge/types.ts index d56c6e0c..5c71a176 100644 --- a/apps/mail-bridge/types.ts +++ b/apps/mail-bridge/types.ts @@ -14,6 +14,8 @@ export interface postalEmailPayload { export interface MessageParseAddressPlatformObject { id: number; type: 'contact' | 'emailIdentity'; + publicId: string; + email: string; contactType: | 'person' | 'product' diff --git a/apps/mail-bridge/utils/contactParsing.ts b/apps/mail-bridge/utils/contactParsing.ts index ca9f1249..6c410eb7 100644 --- a/apps/mail-bridge/utils/contactParsing.ts +++ b/apps/mail-bridge/utils/contactParsing.ts @@ -17,11 +17,12 @@ export async function parseAddressIds(input: { const parsedAddressIds: MessageParseAddressPlatformObject[] = []; for (const addressObject of input.addresses) { if (!addressObject.address) { - continue; + return []; + } + const [emailUsername, emailDomain] = addressObject.address.split('@'); + if (!emailDomain || !emailUsername) { + return []; } - const [emailUsername = '', emailDomain = ''] = - addressObject.address.split('@'); - // check if email is existing contact const contactQuery = await db.query.contacts.findFirst({ where: and( @@ -31,6 +32,9 @@ export async function parseAddressIds(input: { ), columns: { id: true, + publicId: true, + emailUsername: true, + emailDomain: true, name: true, type: true } @@ -39,6 +43,8 @@ export async function parseAddressIds(input: { parsedAddressIds.push({ id: contactQuery.id, type: 'contact', + publicId: contactQuery.publicId, + email: contactQuery.emailUsername + '@' + contactQuery.emailDomain, contactType: contactQuery.type, ref: input.addressType }); @@ -61,7 +67,10 @@ export async function parseAddressIds(input: { eq(emailIdentities.username, emailUsername) ), columns: { - id: true + id: true, + publicId: true, + username: true, + domainName: true } }); @@ -69,6 +78,9 @@ export async function parseAddressIds(input: { parsedAddressIds.push({ id: emailIdentityQuery.id, type: 'emailIdentity', + publicId: emailIdentityQuery.publicId, + email: + emailIdentityQuery.username + '@' + emailIdentityQuery.domainName, contactType: null, ref: input.addressType }); @@ -85,7 +97,10 @@ export async function parseAddressIds(input: { eq(emailIdentities.isCatchAll, true) ), columns: { - id: true + id: true, + publicId: true, + username: true, + domainName: true } }); @@ -93,6 +108,11 @@ export async function parseAddressIds(input: { parsedAddressIds.push({ id: emailIdentityCatchAllQuery.id, type: 'emailIdentity', + publicId: emailIdentityCatchAllQuery.publicId, + email: + emailIdentityCatchAllQuery.username + + '@' + + emailIdentityCatchAllQuery.domainName, contactType: null, ref: input.addressType }); @@ -108,6 +128,7 @@ export async function parseAddressIds(input: { id: true } }); + let contactGlobalReputationId: number | null = null; if (contactGlobalReputation) { contactGlobalReputationId = contactGlobalReputation.id; @@ -127,16 +148,19 @@ export async function parseAddressIds(input: { const contactInsert = await db.insert(contacts).values({ publicId: typeIdGenerator('contacts'), orgId: input.orgId, - reputationId: contactGlobalReputationId || 0, + reputationId: +contactGlobalReputationId!, type: 'unknown', emailUsername: emailUsername, emailDomain: emailDomain, name: addressObject.name || emailUsername + '@' + emailDomain, screenerStatus: 'pending' }); + parsedAddressIds.push({ id: Number(contactInsert.insertId), type: 'contact', + publicId: contactInsert.insertId, + email: emailUsername + '@' + emailDomain, contactType: 'unknown', ref: input.addressType }); diff --git a/apps/platform/trpc/routers/convoRouter/convoRouter.ts b/apps/platform/trpc/routers/convoRouter/convoRouter.ts index 3e1c181d..34ec9ec9 100644 --- a/apps/platform/trpc/routers/convoRouter/convoRouter.ts +++ b/apps/platform/trpc/routers/convoRouter/convoRouter.ts @@ -24,7 +24,8 @@ import { userGroupMembers, type ConvoEntryMetadataEmailAddress, convoAttachments, - pendingAttachments + pendingAttachments, + type ConvoEntryMetadata } from '@u22n/database/schema'; import { typeIdValidator, @@ -126,6 +127,7 @@ export const convoRouter = router({ where: eq(contacts.publicId, convoMessageTo.publicId), columns: { id: true, + publicId: true, emailUsername: true, emailDomain: true } @@ -136,7 +138,12 @@ export const convoRouter = router({ message: 'TO address contact not found' }); } - convoMetadataToAddress = { id: +contactResponse.id, type: 'contact' }; + convoMetadataToAddress = { + id: Number(contactResponse.id), + type: 'contact', + publicId: contactResponse.publicId, + email: `${contactResponse.emailUsername}@${contactResponse.emailDomain}` + }; return `${contactResponse.emailUsername}@${contactResponse.emailDomain}`; } else if (convoMessageToType === 'group') { if (!validateTypeId('userGroups', convoMessageTo.publicId)) { @@ -175,6 +182,8 @@ export const convoRouter = router({ with: { identity: { columns: { + id: true, + publicId: true, username: true, domainName: true } @@ -188,8 +197,10 @@ export const convoRouter = router({ }); } convoMetadataToAddress = { - id: +emailIdentitiesResponse.id, - type: 'emailIdentity' + id: Number(emailIdentitiesResponse.identity.id), + type: 'emailIdentity', + publicId: emailIdentitiesResponse.identity.publicId, + email: `${emailIdentitiesResponse.identity.username}@${emailIdentitiesResponse.identity.domainName}` }; return `${emailIdentitiesResponse.identity.username}@${emailIdentitiesResponse.identity.domainName}`; } else if (convoMessageToType === 'user') { @@ -235,6 +246,8 @@ export const convoRouter = router({ with: { identity: { columns: { + id: true, + publicId: true, username: true, domainName: true } @@ -248,8 +261,10 @@ export const convoRouter = router({ }); } convoMetadataToAddress = { - id: +emailIdentitiesResponse.id, - type: 'emailIdentity' + id: Number(emailIdentitiesResponse.identity.id), + type: 'emailIdentity', + publicId: emailIdentitiesResponse.identity.publicId, + email: `${emailIdentitiesResponse.identity.username}@${emailIdentitiesResponse.identity.domainName}` }; return `${emailIdentitiesResponse.identity.username}@${emailIdentitiesResponse.identity.domainName}`; } else { @@ -346,7 +361,10 @@ export const convoRouter = router({ ), columns: { id: true, - reputationId: true + reputationId: true, + publicId: true, + emailUsername: true, + emailDomain: true } }); @@ -354,12 +372,16 @@ export const convoRouter = router({ if (newConvoToEmailAddress === email && !convoMetadataToAddress) { convoMetadataToAddress = { id: +existingContact.id, - type: 'contact' + type: 'contact', + publicId: existingContact.publicId, + email: `${existingContact.emailUsername}@${existingContact.emailDomain}` }; } else { convoMetadataCcAddresses.push({ - id: +existingContact.id, - type: 'contact' + id: Number(existingContact.id), + type: 'contact', + publicId: existingContact.publicId, + email: `${existingContact.emailUsername}@${existingContact.emailDomain}` }); } orgContactIds.push(existingContact.id); @@ -389,12 +411,16 @@ export const convoRouter = router({ if (newConvoToEmailAddress === email && !convoMetadataToAddress) { convoMetadataToAddress = { id: +newContactInsertResponse.insertId, - type: 'contact' + type: 'contact', + publicId: newContactPublicId, + email: `${emailUsername}@${emailDomain}` }; } else { convoMetadataCcAddresses.push({ id: +newContactInsertResponse.insertId, - type: 'contact' + type: 'contact', + publicId: newContactPublicId, + email: `${emailUsername}@${emailDomain}` }); } orgContactIds.push(+newContactInsertResponse.insertId); @@ -422,12 +448,16 @@ export const convoRouter = router({ if (newConvoToEmailAddress === email && !convoMetadataToAddress) { convoMetadataToAddress = { id: +newContactInsertResponse.insertId, - type: 'contact' + type: 'contact', + publicId: newContactPublicId, + email: `${emailUsername}@${emailDomain}` }; } else { convoMetadataCcAddresses.push({ id: +newContactInsertResponse.insertId, - type: 'contact' + type: 'contact', + publicId: newContactPublicId, + email: `${emailUsername}@${emailDomain}` }); } orgContactIds.push(+newContactInsertResponse.insertId); @@ -632,6 +662,8 @@ export const convoRouter = router({ with: { identity: { columns: { + id: true, + publicId: true, username: true, domainName: true } @@ -664,8 +696,10 @@ export const convoRouter = router({ } if (emailIdentityResponse) { convoMetadataCcAddresses.push({ - id: +emailIdentityResponse.id, - type: 'emailIdentity' + id: +emailIdentityResponse.identity.id, + type: 'emailIdentity', + publicId: emailIdentityResponse.identity.publicId, + email: `${emailIdentityResponse.identity.username}@${emailIdentityResponse.identity.domainName}` }); ccEmailAddresses.push( `${emailIdentityResponse.identity.username}@${emailIdentityResponse.identity.domainName}` @@ -691,6 +725,8 @@ export const convoRouter = router({ with: { identity: { columns: { + id: true, + publicId: true, username: true, domainName: true } @@ -718,7 +754,9 @@ export const convoRouter = router({ if (emailIdentityResponse) { convoMetadataCcAddresses.push({ id: +emailIdentityResponse.id, - type: 'emailIdentity' + type: 'emailIdentity', + publicId: emailIdentityResponse.identity.publicId, + email: `${emailIdentityResponse.identity.username}@${emailIdentityResponse.identity.domainName}` }); ccEmailAddresses.push( `${emailIdentityResponse.identity.username}@${emailIdentityResponse.identity.domainName}` @@ -764,12 +802,13 @@ export const convoRouter = router({ mailBridgeSendMailResponse.metadata.email.to = [ convoMetadataToAddress! ]; - mailBridgeSendMailResponse.metadata.email.cc = convoMetadataCcAddresses; + mailBridgeSendMailResponse.metadata.email.cc = + convoMetadataCcAddresses!; await db .update(convoEntries) .set({ - metadata: mailBridgeSendMailResponse.metadata + metadata: mailBridgeSendMailResponse.metadata as ConvoEntryMetadata }) .where(eq(convoEntries.id, +insertConvoEntryResponse.insertId)); @@ -941,7 +980,8 @@ export const convoRouter = router({ emailDomain: true, setName: true, signaturePlainText: true, - signatureHtml: true + signatureHtml: true, + type: true } } } diff --git a/apps/web-app/app.config.ts b/apps/web-app/app.config.ts index 35f98d78..7c28da42 100644 --- a/apps/web-app/app.config.ts +++ b/apps/web-app/app.config.ts @@ -1,11 +1,12 @@ import { defineAppConfig } from '#imports'; export default defineAppConfig({ ui: { - primary: 'bronze', - gray: 'zinc', - accent: 'bronze', + primary: 'sand', + gray: 'sand', + accent: 'sand', base: 'sand', - safelistColors: ['bronze'], + colors: ['sand', 'base', 'bronze', 'green', 'red', 'amber', 'blue'], + safelistColors: ['sand'], notification: { rounded: 'rounded-lg rounded-br-2xl' }, @@ -45,7 +46,7 @@ export default defineAppConfig({ default: { size: 'sm', variant: 'solid', - color: 'bronze', + color: 'sand', loadingIcon: 'i-heroicons-arrow-path-20-solid' } }, @@ -85,7 +86,7 @@ export default defineAppConfig({ default: { size: 'sm', variant: 'solid', - color: 'bronze' + color: 'sand' } }, @@ -392,13 +393,13 @@ export default defineAppConfig({ //tooltip tooltip: { wrapper: 'relative inline-flex', - container: 'z-500 group', + container: 'z-50 group', width: 'max-w-xs', background: 'bg-base-3 dark:bg-base-3', color: 'text-base-12 dark:text-base-12', shadow: 'shadow', rounded: 'rounded', - ring: 'ring-1 ring-base-7 dark:ring-base-7', + ring: 'ring-0 ring-base-7 dark:ring-base-7', base: '[@media(pointer:coarse)]:hidden h-6 px-2 py-1 text-xs font-normal truncate relative', shortcuts: 'hidden md:inline-flex flex-shrink-0 gap-0.5', middot: 'mx-1 text-base-11 dark:text-base-11', @@ -419,17 +420,111 @@ export default defineAppConfig({ }, arrow: { base: '[@media(pointer:coarse)]:hidden invisible before:visible before:block before:rotate-45 before:z-[-1] before:w-2 before:h-2', - ring: 'before:ring-1 before:ring-base-7 dark:before:ring-base-7', + ring: 'before:ring-0 before:ring-base-7 dark:before:ring-base-7', rounded: 'before:rounded-sm', background: 'before:bg-base-3 dark:before:bg-base-3', shadow: 'before:shadow', placement: "group-data-[popper-placement*='right']:-left-1 group-data-[popper-placement*='left']:-right-1 group-data-[popper-placement*='top']:-bottom-1 group-data-[popper-placement*='bottom']:-top-1" } + }, + + // avatar + avatar: { + wrapper: 'relative inline-flex items-center justify-center flex-shrink-0', + background: 'bg-base-7 dark:bg-base-7', + rounded: 'rounded-full', + text: 'font-medium leading-none text-gray-900 dark:text-white truncate', + placeholder: + 'font-medium leading-none text-gray-500 dark:text-gray-400 truncate', + size: { + '3xs': 'h-4 w-4 text-[8px]', + '2xs': 'h-5 w-5 text-[10px]', + xs: 'h-6 w-6 text-xs', + sm: 'h-8 w-8 text-sm', + md: 'h-10 w-10 text-base', + lg: 'h-12 w-12 text-lg', + xl: 'h-14 w-14 text-xl', + '2xl': 'h-16 w-16 text-2xl', + '3xl': 'h-20 w-20 text-3xl' + }, + chip: { + base: 'absolute rounded-full ring-1 ring-white dark:ring-gray-900 flex items-center justify-center text-white dark:text-gray-900 font-medium', + background: 'bg-{color}-500 dark:bg-{color}-400', + position: { + 'top-right': 'top-0 right-0', + 'bottom-right': 'bottom-0 right-0', + 'top-left': 'top-0 left-0', + 'bottom-left': 'bottom-0 left-0' + }, + size: { + '3xs': 'h-[4px] min-w-[4px] text-[4px] p-px', + '2xs': 'h-[5px] min-w-[5px] text-[5px] p-px', + xs: 'h-1.5 min-w-[0.375rem] text-[6px] p-px', + sm: 'h-2 min-w-[0.5rem] text-[7px] p-0.5', + md: 'h-2.5 min-w-[0.625rem] text-[8px] p-0.5', + lg: 'h-3 min-w-[0.75rem] text-[10px] p-0.5', + xl: 'h-3.5 min-w-[0.875rem] text-[11px] p-1', + '2xl': 'h-4 min-w-[1rem] text-[12px] p-1', + '3xl': 'h-5 min-w-[1.25rem] text-[14px] p-1' + } + }, + icon: { + base: 'text-gray-500 dark:text-gray-400 flex-shrink-0', + size: { + '3xs': 'h-2 w-2', + '2xs': 'h-2.5 w-2.5', + xs: 'h-3 w-3', + sm: 'h-4 w-4', + md: 'h-5 w-5', + lg: 'h-6 w-6', + xl: 'h-7 w-7', + '2xl': 'h-8 w-8', + '3xl': 'h-10 w-10' + } + }, + default: { + size: 'sm', + icon: null, + chipColor: null, + chipPosition: 'top-right' + } + }, + //avatar group + avatarGroup: { + wrapper: 'inline-flex flex-row-reverse justify-end', + ring: 'ring-0 ring-base-5 dark:ring-base-5', + margin: '-me-1.5 first:me-0' } } }); +const sand = [ + 'bg-sand-1', + 'bg-sand-2', + 'bg-sand-3', + 'hover:bg-sand-3', + 'bg-sand-4', + 'hover:bg-sand-4', + 'active:bg-sand-4', + 'bg-sand-5', + 'hover:bg-sand-5', + 'active:bg-sand-5', + 'bg-sand-9', + 'bg-sand-10', + 'hover:bg-sand-10', + 'bg-sand-11', + 'bg-sand-12', + 'text-sand-5', + 'text-sand-9', + 'text-sand-10', + 'text-sand-11', + 'hover:text-sand-12', + 'ring-sand-5', + 'ring-sand-8', + 'ring-sand-9', + 'active:ring-sand-9' +]; const bronze = [ 'bg-bronze-1', 'bg-bronze-2', diff --git a/apps/web-app/app.vue b/apps/web-app/app.vue index 55dc2b61..49ad7fb9 100644 --- a/apps/web-app/app.vue +++ b/apps/web-app/app.vue @@ -5,7 +5,8 @@ diff --git a/apps/web-app/components/convos/convoMessageItem.vue b/apps/web-app/components/convos/convoMessageItem.vue index 33d96601..70a26b7b 100644 --- a/apps/web-app/components/convos/convoMessageItem.vue +++ b/apps/web-app/components/convos/convoMessageItem.vue @@ -13,9 +13,12 @@ type ConvoEntryItem = NonNullable[number]; type Props = { entry: ConvoEntryItem; + isReplyTo: boolean; }; const props = defineProps(); + const emits = defineEmits(['set-as-reply-to']); + const participantPublicId = inject('participantPublicId'); const convoParticipants = inject('convoParticipants'); @@ -45,7 +48,7 @@ const typeClasses = computed(() => { switch (tempColor) { case 'message': - return 'bg-white dark:bg-black'; + return 'bg-base-2 dark:bg-base-2'; default: return 'bg-gray-100 dark:bg-gray-900'; } @@ -57,12 +60,16 @@ const convoBubbleClasses = computed(() => { return `${typeClasses.value}`; }); + + function setAsReplyTo() { + emits('set-as-reply-to'); + }