mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-03-24 17:41:27 -04:00
Finish notif translations
This commit is contained in:
@@ -8,7 +8,7 @@ import {getNotificationDestinationsForUser} from 'common/user-notification-prefe
|
||||
import {richTextToString} from 'common/util/parse'
|
||||
import * as crypto from 'crypto'
|
||||
import {sendNewEndorsementEmail} from 'email/functions/helpers'
|
||||
import {createSupabaseDirectClient, SupabaseDirectClient} from 'shared/supabase/init'
|
||||
import {createSupabaseDirectClient} from 'shared/supabase/init'
|
||||
import {insertNotificationToSupabase} from 'shared/supabase/notifications'
|
||||
import {getPrivateUser, getUser} from 'shared/utils'
|
||||
import {broadcastUpdatedComment} from 'shared/websockets/helpers'
|
||||
@@ -44,7 +44,6 @@ export const createComment: APIHandler<'create-comment'> = async (
|
||||
creator,
|
||||
richTextToString(content),
|
||||
comment.id,
|
||||
pg,
|
||||
)
|
||||
|
||||
broadcastUpdatedComment(convertComment(comment))
|
||||
@@ -78,7 +77,6 @@ const createNewCommentOnProfileNotification = async (
|
||||
creator: User,
|
||||
sourceText: string,
|
||||
commentId: number,
|
||||
pg: SupabaseDirectClient,
|
||||
) => {
|
||||
const privateUser = await getPrivateUser(onUser.id)
|
||||
if (!privateUser) return
|
||||
@@ -104,7 +102,7 @@ const createNewCommentOnProfileNotification = async (
|
||||
sourceSlug: onUser.username,
|
||||
}
|
||||
if (sendToBrowser) {
|
||||
await insertNotificationToSupabase(notification, pg)
|
||||
await insertNotificationToSupabase(notification)
|
||||
}
|
||||
if (sendToMobile) {
|
||||
// await createPushNotification(
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
NotificationTemplateTranslation,
|
||||
} from 'shared/supabase/notifications'
|
||||
|
||||
const COMPASS_LOGO_URL =
|
||||
'https://firebasestorage.googleapis.com/v0/b/compass-130ba.firebasestorage.app/o/misc%2Fcompass-192.png?alt=media&token=9fd251c5-fc43-4375-b629-1a8f4bbe8185'
|
||||
|
||||
export const createAndroidReleaseNotifications = async () => {
|
||||
const createdTime = Date.now()
|
||||
const id = `android-release-${createdTime}`
|
||||
@@ -20,8 +23,7 @@ export const createAndroidReleaseNotifications = async () => {
|
||||
sourceType: 'info',
|
||||
sourceUpdateType: 'created',
|
||||
sourceSlug: ANDROID_APP_URL,
|
||||
sourceUserAvatarUrl:
|
||||
'https://firebasestorage.googleapis.com/v0/b/compass-130ba.firebasestorage.app/o/misc%2Fcompass-192.png?alt=media&token=9fd251c5-fc43-4375-b629-1a8f4bbe8185',
|
||||
sourceUserAvatarUrl: COMPASS_LOGO_URL,
|
||||
title: 'Android App Released on Google Play',
|
||||
sourceText:
|
||||
'The Compass Android app is now publicly available on Google Play! Download it today to stay connected on the go.',
|
||||
@@ -40,8 +42,7 @@ export const createAndroidTestNotifications = async () => {
|
||||
sourceType: 'info',
|
||||
sourceUpdateType: 'created',
|
||||
sourceSlug: '/contact',
|
||||
sourceUserAvatarUrl:
|
||||
'https://firebasestorage.googleapis.com/v0/b/compass-130ba.firebasestorage.app/o/misc%2Fcompass-192.png?alt=media&token=9fd251c5-fc43-4375-b629-1a8f4bbe8185',
|
||||
sourceUserAvatarUrl: COMPASS_LOGO_URL,
|
||||
title: 'Android App Ready for Review — Help Us Unlock the Google Play Release',
|
||||
sourceText:
|
||||
'To release our app, Google requires a closed test with at least 12 testers for 14 days. Please share your Google Play–registered email address so we can add you as a tester and complete the review process.',
|
||||
@@ -129,38 +130,16 @@ export const createNotification = async (
|
||||
* Uses the new template-based system for efficient bulk notifications
|
||||
*/
|
||||
export const createEventsAvailableNotifications = async () => {
|
||||
const pg = createSupabaseDirectClient()
|
||||
|
||||
// Fetch all users
|
||||
const {data: users, error} = await tryCatch(pg.many<Row<'users'>>('select id from users'))
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching users', error)
|
||||
return {success: false, error}
|
||||
}
|
||||
|
||||
if (!users || users.length === 0) {
|
||||
console.error('No users found')
|
||||
return {success: false, error: 'No users found'}
|
||||
}
|
||||
|
||||
const userIds = users.map((u) => u.id)
|
||||
|
||||
// Create template and bulk notifications using the new system
|
||||
const {templateId, count} = await createBulkNotification(
|
||||
{
|
||||
sourceType: 'info',
|
||||
title: 'New Events Page',
|
||||
sourceText:
|
||||
'You can now create and join events on Compass! Meet up with other members online or in person for workshops, social events, etc.',
|
||||
sourceSlug: '/events',
|
||||
sourceUserAvatarUrl:
|
||||
'https://firebasestorage.googleapis.com/v0/b/compass-130ba.firebasestorage.app/o/misc%2Fcompass-192.png?alt=media&token=9fd251c5-fc43-4375-b629-1a8f4bbe8185',
|
||||
sourceUpdateType: 'created',
|
||||
},
|
||||
userIds,
|
||||
pg,
|
||||
)
|
||||
const {templateId, count} = await createBulkNotification({
|
||||
sourceType: 'info',
|
||||
title: 'New Events Page',
|
||||
sourceText:
|
||||
'You can now create and join events on Compass! Meet up with other members online or in person for workshops, social events, etc.',
|
||||
sourceSlug: '/events',
|
||||
sourceUserAvatarUrl: COMPASS_LOGO_URL,
|
||||
sourceUpdateType: 'created',
|
||||
})
|
||||
|
||||
console.log(`Created events notification template ${templateId} for ${count} users`)
|
||||
|
||||
@@ -172,58 +151,32 @@ export const createEventsAvailableNotifications = async () => {
|
||||
}
|
||||
|
||||
export const createSomeNotifications = async () => {
|
||||
const pg = createSupabaseDirectClient()
|
||||
|
||||
// Fetch all users
|
||||
const {data: users, error} = await tryCatch(pg.many<Row<'users'>>('select id from users'))
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching users', error)
|
||||
return {success: false, error}
|
||||
}
|
||||
|
||||
if (!users || users.length === 0) {
|
||||
console.error('No users found')
|
||||
return {success: false, error: 'No users found'}
|
||||
}
|
||||
|
||||
const userIds = users.map((u: Row<'users'>) => u.id)
|
||||
|
||||
const translations: Omit<NotificationTemplateTranslation, 'template_id' | 'created_time'>[] = [
|
||||
// French translation
|
||||
{
|
||||
locale: 'fr',
|
||||
title: '',
|
||||
source_text: '',
|
||||
title: 'Bonjour',
|
||||
source_text: "C'est une notif",
|
||||
},
|
||||
// German translation
|
||||
{
|
||||
locale: 'de',
|
||||
title: '',
|
||||
source_text: '',
|
||||
title: 'Halo',
|
||||
source_text: 'Dis das',
|
||||
},
|
||||
]
|
||||
|
||||
// Create template with translations
|
||||
const {templateId, count} = await createBulkNotification(
|
||||
{
|
||||
sourceType: 'welcome',
|
||||
title: '',
|
||||
sourceText: '',
|
||||
sourceSlug: '/',
|
||||
sourceUserAvatarUrl: '',
|
||||
sourceType: 'hello',
|
||||
title: 'Hello world',
|
||||
sourceText: 'This is a notification',
|
||||
sourceSlug: '/settings',
|
||||
sourceUserAvatarUrl: COMPASS_LOGO_URL,
|
||||
sourceUpdateType: 'created',
|
||||
},
|
||||
userIds,
|
||||
pg,
|
||||
translations,
|
||||
)
|
||||
|
||||
console.log(`Created notification template ${templateId} for ${count} users`)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
templateId,
|
||||
userCount: count,
|
||||
}
|
||||
console.log(`Created some notification template ${templateId} for ${count} users`)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@ import {defaultLocale} from 'common/constants'
|
||||
import {Notification} from 'common/notifications'
|
||||
import {createSupabaseDirectClient} from 'shared/supabase/init'
|
||||
|
||||
// Helper function to substitute placeholders in template text
|
||||
function substitutePlaceholders(templateText: string, templateData: any): string {
|
||||
let result = templateText
|
||||
if (templateData) {
|
||||
for (const [key, value] of Object.entries(templateData)) {
|
||||
// Replace all occurrences of {key} with the value
|
||||
result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), String(value))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const getNotifications: APIHandler<'get-notifications'> = async (props, auth, _req) => {
|
||||
const {limit, after, locale = defaultLocale} = props
|
||||
const pg = createSupabaseDirectClient()
|
||||
@@ -23,7 +35,8 @@ export const getNotifications: APIHandler<'get-notifications'> = async (props, a
|
||||
'sourceText', COALESCE(ntt.source_text, nt.source_text),
|
||||
'sourceSlug', nt.source_slug,
|
||||
'sourceUserAvatarUrl', nt.source_user_avatar_url,
|
||||
'data', nt.data
|
||||
'data', nt.data,
|
||||
'templateData', un.data->'templateData'
|
||||
)
|
||||
else
|
||||
un.data
|
||||
@@ -47,9 +60,31 @@ export const getNotifications: APIHandler<'get-notifications'> = async (props, a
|
||||
limit $2
|
||||
`
|
||||
|
||||
return await pg.map(
|
||||
const rawNotifications = await pg.map(
|
||||
query,
|
||||
[auth.uid, limit, after, locale],
|
||||
(row) => row.notification_data as Notification,
|
||||
(row) => row.notification_data,
|
||||
)
|
||||
|
||||
// Process notifications to apply template data substitution
|
||||
const processedNotifications: Notification[] = rawNotifications.map((notif: any) => {
|
||||
if (notif.templateId) {
|
||||
// Apply template data substitution to title and sourceText
|
||||
const templateData = notif.templateData || {}
|
||||
const processedNotif = {...notif}
|
||||
|
||||
if (processedNotif.title) {
|
||||
processedNotif.title = substitutePlaceholders(processedNotif.title, templateData)
|
||||
}
|
||||
|
||||
if (processedNotif.sourceText) {
|
||||
processedNotif.sourceText = substitutePlaceholders(processedNotif.sourceText, templateData)
|
||||
}
|
||||
|
||||
return processedNotif as Notification
|
||||
}
|
||||
return notif as Notification
|
||||
})
|
||||
|
||||
return processedNotifications
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import {getPrivateUser, getUser} from './utils'
|
||||
|
||||
export const createProfileLikeNotification = async (like: Row<'profile_likes'>) => {
|
||||
const {creator_id, target_id, like_id} = like
|
||||
const pg = createSupabaseDirectClient()
|
||||
|
||||
const targetPrivateUser = await getPrivateUser(target_id)
|
||||
const profile = await getProfile(creator_id)
|
||||
@@ -34,7 +33,7 @@ export const createProfileLikeNotification = async (like: Row<'profile_likes'>)
|
||||
sourceUserAvatarUrl: profile.pinned_url ?? profile.user.avatarUrl,
|
||||
sourceText: '',
|
||||
}
|
||||
return await insertNotificationToSupabase(notification, pg)
|
||||
return await insertNotificationToSupabase(notification)
|
||||
}
|
||||
|
||||
export const createProfileShipNotification = async (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Notification, NotificationTemplate} from 'common/notifications'
|
||||
import {Row} from 'common/supabase/utils'
|
||||
import {SupabaseDirectClient} from 'shared/supabase/init'
|
||||
import {tryCatch} from 'common/util/try-catch'
|
||||
import {createSupabaseDirectClient, SupabaseDirectClient} from 'shared/supabase/init'
|
||||
import {bulkInsert} from 'shared/supabase/utils'
|
||||
import {broadcast} from 'shared/websockets/server'
|
||||
|
||||
@@ -16,8 +17,9 @@ export type NotificationTemplateTranslation = Row<'notification_template_transla
|
||||
*/
|
||||
export const insertNotificationToSupabase = async (
|
||||
notification: Notification,
|
||||
pg: SupabaseDirectClient,
|
||||
pg?: SupabaseDirectClient,
|
||||
) => {
|
||||
pg = pg ?? createSupabaseDirectClient()
|
||||
// Check if this notification has a template_id (new style)
|
||||
if (notification.templateId) {
|
||||
await pg.none(
|
||||
@@ -31,6 +33,7 @@ export const insertNotificationToSupabase = async (
|
||||
{
|
||||
isSeen: notification.isSeen,
|
||||
viewTime: notification.viewTime,
|
||||
templateData: notification.templateData || {}, // Store dynamic template data
|
||||
},
|
||||
],
|
||||
)
|
||||
@@ -73,8 +76,8 @@ export const bulkInsertNotifications = async (
|
||||
*/
|
||||
export const createNotificationTemplate = async (
|
||||
template: NotificationTemplate,
|
||||
pg: SupabaseDirectClient,
|
||||
): Promise<string> => {
|
||||
const pg = createSupabaseDirectClient()
|
||||
await pg.none(
|
||||
`insert into notification_templates
|
||||
(id, source_type, title, source_text, source_slug, source_user_avatar_url, source_update_type, created_time, data)
|
||||
@@ -106,9 +109,9 @@ export const createNotificationTemplate = async (
|
||||
*/
|
||||
export const createNotificationTemplateTranslations = async (
|
||||
translations: NotificationTemplateTranslation[],
|
||||
pg: SupabaseDirectClient,
|
||||
): Promise<void> => {
|
||||
if (translations.length === 0) return
|
||||
const pg = createSupabaseDirectClient()
|
||||
|
||||
await bulkInsert(pg, 'notification_template_translations', translations)
|
||||
}
|
||||
@@ -120,10 +123,9 @@ export const createNotificationTemplateTranslations = async (
|
||||
export const createNotificationTemplateWithTranslations = async (
|
||||
template: NotificationTemplate,
|
||||
translations: Omit<NotificationTemplateTranslation, 'template_id' | 'created_time'>[],
|
||||
pg: SupabaseDirectClient,
|
||||
): Promise<string> => {
|
||||
// Create the base template
|
||||
await createNotificationTemplate(template, pg)
|
||||
await createNotificationTemplate(template)
|
||||
|
||||
// Add template_id to translations and create them
|
||||
if (translations.length > 0) {
|
||||
@@ -132,11 +134,10 @@ export const createNotificationTemplateWithTranslations = async (
|
||||
({
|
||||
...t,
|
||||
template_id: template.id,
|
||||
created_time: Date.now(),
|
||||
}) as NotificationTemplateTranslation,
|
||||
)
|
||||
|
||||
await createNotificationTemplateTranslations(fullTranslations, pg)
|
||||
await createNotificationTemplateTranslations(fullTranslations)
|
||||
}
|
||||
|
||||
return template.id
|
||||
@@ -149,15 +150,19 @@ export const createNotificationTemplateWithTranslations = async (
|
||||
export const createUserNotifications = async (
|
||||
templateId: string,
|
||||
userIds: string[],
|
||||
pg: SupabaseDirectClient,
|
||||
templateData?: {[key: string]: string | number | boolean}, // Dynamic data for template placeholders
|
||||
// baseNotificationId?: string
|
||||
) => {
|
||||
const timestamp = Date.now()
|
||||
const pg = createSupabaseDirectClient()
|
||||
const notifications = userIds.map((userId, index) => ({
|
||||
user_id: userId,
|
||||
notification_id: `${templateId}-${userId}-${timestamp}-${index}`,
|
||||
template_id: templateId,
|
||||
data: {isSeen: false},
|
||||
data: {
|
||||
isSeen: false,
|
||||
templateData: templateData || {}, // Store dynamic template data
|
||||
},
|
||||
}))
|
||||
|
||||
await bulkInsert(pg, 'user_notifications', notifications)
|
||||
@@ -172,17 +177,43 @@ export const createUserNotifications = async (
|
||||
return notifications.length
|
||||
}
|
||||
|
||||
async function getUserIds() {
|
||||
const pg = createSupabaseDirectClient()
|
||||
|
||||
// Fetch all users
|
||||
const {data: users, error} = await tryCatch(pg.many<Row<'users'>>('select id from users'))
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching users', error)
|
||||
return
|
||||
}
|
||||
|
||||
if (!users || users.length === 0) {
|
||||
console.error('No users found')
|
||||
return
|
||||
}
|
||||
|
||||
const userIds = users.map((u: Row<'users'>) => u.id)
|
||||
|
||||
return userIds
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bulk notification using the template system
|
||||
* Creates one template and many lightweight user notification entries
|
||||
* Optionally includes translations
|
||||
* Each template can have dynamic (user-dependent) data
|
||||
*/
|
||||
export const createBulkNotification = async (
|
||||
template: Omit<NotificationTemplate, 'id' | 'createdTime'>,
|
||||
userIds: string[],
|
||||
pg: SupabaseDirectClient,
|
||||
translations?: Omit<NotificationTemplateTranslation, 'template_id' | 'created_time'>[],
|
||||
templateData?: {[key: string]: string | number | boolean}, // Dynamic data for template placeholders
|
||||
userIds?: string[],
|
||||
) => {
|
||||
if (!userIds) {
|
||||
userIds = await getUserIds()
|
||||
if (!userIds) return {}
|
||||
}
|
||||
const timestamp = Date.now()
|
||||
const templateId = `${template.sourceType}-${timestamp}`
|
||||
|
||||
@@ -195,22 +226,18 @@ export const createBulkNotification = async (
|
||||
createdTime: timestamp,
|
||||
},
|
||||
translations,
|
||||
pg,
|
||||
)
|
||||
} else {
|
||||
// Create just the base template
|
||||
await createNotificationTemplate(
|
||||
{
|
||||
...template,
|
||||
id: templateId,
|
||||
createdTime: timestamp,
|
||||
},
|
||||
pg,
|
||||
)
|
||||
await createNotificationTemplate({
|
||||
...template,
|
||||
id: templateId,
|
||||
createdTime: timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
// Create lightweight user notifications
|
||||
const count = await createUserNotifications(templateId, userIds, pg)
|
||||
// Create lightweight user notifications with template data
|
||||
const count = await createUserNotifications(templateId, userIds, templateData)
|
||||
|
||||
return {templateId, count}
|
||||
}
|
||||
|
||||
@@ -474,6 +474,19 @@
|
||||
"news.title": "Neuigkeiten",
|
||||
"news.view_on_github": "Auf GitHub ansehen",
|
||||
"notifications.empty": "Sie haben noch keine Benachrichtigungen.",
|
||||
"notifications.comment.commented": "kommentierte ",
|
||||
"notifications.comment.on_your_profile": "auf Ihrem Profil",
|
||||
"notifications.match.proposed_new_match": "schlug einen neuen Match vor:",
|
||||
"notifications.profile.liked_you": "mag dich gerne!",
|
||||
"notifications.who_liked_it": "Wer hat es gern?",
|
||||
"notifications.profile.are_being_shipped_by": "werden versandt von",
|
||||
"time.units.y": "y",
|
||||
"time.units.mo": "mo",
|
||||
"time.units.w": "w",
|
||||
"time.units.d": "d",
|
||||
"time.units.h": "h",
|
||||
"time.units.m": "m",
|
||||
"time.units.s": "s",
|
||||
"notifications.heading": "Wo möchten Sie benachrichtigt werden, wenn eine Person",
|
||||
"notifications.options.email": "Per E-Mail",
|
||||
"notifications.options.page": "Auf der Benachrichtigungsseite",
|
||||
|
||||
@@ -474,6 +474,18 @@
|
||||
"news.title": "Quoi de neuf",
|
||||
"news.view_on_github": "Voir sur GitHub",
|
||||
"notifications.empty": "Vous n'avez pas encore de notifications.",
|
||||
"notifications.comment.commented": "a commenté ",
|
||||
"notifications.comment.on_your_profile": "votre profil",
|
||||
"notifications.match.proposed_new_match": "proposé un nouveau match :",
|
||||
"notifications.profile.liked_you": "vous a plu !",
|
||||
"notifications.who_liked_it": "Qui l'a aimé ?",
|
||||
"time.units.y": "ans",
|
||||
"time.units.mo": "mo",
|
||||
"time.units.w": "sem",
|
||||
"time.units.d": "j",
|
||||
"time.units.h": "h",
|
||||
"time.units.m": "m",
|
||||
"time.units.s": "s",
|
||||
"notifications.heading": "Où voulez-vous être notifié lorsqu'une personne",
|
||||
"notifications.options.email": "Par e‑mail",
|
||||
"notifications.options.page": "Sur la page des notifications",
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
import {Row, SupabaseClient} from 'common/supabase/utils'
|
||||
|
||||
// Notification template - stores the shared content for notifications sent to multiple users
|
||||
export type NotificationTemplate = {
|
||||
id: string
|
||||
sourceType: string
|
||||
title?: string
|
||||
sourceText: string
|
||||
sourceText: string // May contain placeholders like "{user}"
|
||||
sourceSlug?: string
|
||||
sourceUserAvatarUrl?: string
|
||||
sourceUpdateType?: 'created' | 'updated' | 'deleted'
|
||||
createdTime: number
|
||||
data?: {[key: string]: any}
|
||||
data?: {[key: string]: any} // Static data for the template
|
||||
}
|
||||
|
||||
// Notification template translations
|
||||
export type NotificationTemplateTranslation = {
|
||||
templateId: string
|
||||
locale: string
|
||||
title?: string
|
||||
sourceText: string // May contain placeholders like "{user}"
|
||||
createdTime: number
|
||||
}
|
||||
|
||||
// User-specific notification data (lightweight - references template)
|
||||
@@ -20,6 +27,8 @@ export type UserNotification = {
|
||||
templateId: string
|
||||
isSeen: boolean
|
||||
viewTime?: number
|
||||
// Dynamic values to substitute in template placeholders
|
||||
templateData?: {[key: string]: string | number | boolean}
|
||||
}
|
||||
|
||||
// Full notification (combines template + user data) - for backwards compatibility
|
||||
@@ -54,37 +63,33 @@ export type Notification = {
|
||||
|
||||
// New field for template-based notifications
|
||||
templateId?: string
|
||||
}
|
||||
|
||||
// export const NOTIFICATION_TYPES_TO_SELECT = [
|
||||
// 'new_match', // new match markets
|
||||
// 'comment_on_profile', // endorsements
|
||||
// 'profile_like',
|
||||
// 'profile_ship',
|
||||
// ]
|
||||
// Dynamic values to substitute in template placeholders
|
||||
templateData?: {[key: string]: string | number | boolean}
|
||||
}
|
||||
|
||||
export const NOTIFICATIONS_PER_PAGE = 30
|
||||
|
||||
export async function getNotifications(db: SupabaseClient, userId: string, limit: number) {
|
||||
const {data} = await db
|
||||
.from('user_notifications')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.order('data->createdTime', {ascending: false} as any)
|
||||
.limit(limit)
|
||||
return data?.map((d: Row<'user_notifications'>) => d)
|
||||
}
|
||||
|
||||
export async function getUnseenNotifications(db: SupabaseClient, userId: string, limit: number) {
|
||||
const {data} = await db
|
||||
.from('user_notifications')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.eq('data->>isSeen', 'false')
|
||||
.order('data->createdTime', {ascending: false} as any)
|
||||
.limit(limit)
|
||||
|
||||
return data?.map((d: Row<'user_notifications'>) => d) ?? []
|
||||
}
|
||||
|
||||
export type NotificationReason = any // TODO
|
||||
// export async function getNotifications(db: SupabaseClient, userId: string, limit: number) {
|
||||
// const {data} = await db
|
||||
// .from('user_notifications')
|
||||
// .select('*')
|
||||
// .eq('user_id', userId)
|
||||
// .order('data->createdTime', {ascending: false} as any)
|
||||
// .limit(limit)
|
||||
// return data?.map((d: Row<'user_notifications'>) => d)
|
||||
// }
|
||||
//
|
||||
// export async function getUnseenNotifications(db: SupabaseClient, userId: string, limit: number) {
|
||||
// const {data} = await db
|
||||
// .from('user_notifications')
|
||||
// .select('*')
|
||||
// .eq('user_id', userId)
|
||||
// .eq('data->>isSeen', 'false')
|
||||
// .order('data->createdTime', {ascending: false} as any)
|
||||
// .limit(limit)
|
||||
//
|
||||
// return data?.map((d: Row<'user_notifications'>) => d) ?? []
|
||||
// }
|
||||
//
|
||||
// export type NotificationReason = any
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from 'axios'
|
||||
import {createSomeNotifications} from 'backend/api/src/create-notification'
|
||||
import {tryCatch} from 'common/util/try-catch'
|
||||
import {createSupabaseDirectClient} from 'shared/supabase/init'
|
||||
import {insert} from 'shared/supabase/utils'
|
||||
@@ -48,12 +49,15 @@ async function seedCompatibilityPrompts(pg: any, userId: string | null = null) {
|
||||
console.log('Compatibility prompts created', {data, error})
|
||||
}
|
||||
|
||||
async function seedNotifications() {
|
||||
await createSomeNotifications()
|
||||
console.log('Notifications created', {})
|
||||
}
|
||||
|
||||
type ProfileType = 'basic' | 'medium' | 'full'
|
||||
;(async () => {
|
||||
const pg = createSupabaseDirectClient()
|
||||
|
||||
await seedCompatibilityPrompts(pg)
|
||||
|
||||
//Edit the count seedConfig to specify the amount of each profiles to create
|
||||
const seedConfig = [
|
||||
{count: 1, profileType: 'basic' as ProfileType},
|
||||
@@ -78,5 +82,8 @@ type ProfileType = 'basic' | 'medium' | 'full'
|
||||
}
|
||||
}
|
||||
|
||||
await seedCompatibilityPrompts(pg)
|
||||
await seedNotifications()
|
||||
|
||||
process.exit(0)
|
||||
})()
|
||||
|
||||
@@ -6,6 +6,7 @@ import {sortBy} from 'lodash'
|
||||
import Link from 'next/link'
|
||||
import {ReactNode, useState} from 'react'
|
||||
import {useIsMobile} from 'web/hooks/use-is-mobile'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
import {Col} from './layout/col'
|
||||
import {Row} from './layout/row'
|
||||
@@ -74,7 +75,8 @@ export function CommentOnProfileNotification(props: {
|
||||
}) {
|
||||
const {notification, isChildOfGroup, highlighted, setHighlighted} = props
|
||||
const {sourceUserName, sourceUserUsername, sourceText} = notification
|
||||
const reasonText = `commented `
|
||||
const t = useT()
|
||||
const reasonText = t('notifications.comment.commented', `commented `)
|
||||
return (
|
||||
<NotificationFrame
|
||||
notification={notification}
|
||||
@@ -91,7 +93,9 @@ export function CommentOnProfileNotification(props: {
|
||||
>
|
||||
<div className="line-clamp-3">
|
||||
<NotificationUserLink name={sourceUserName} username={sourceUserUsername} /> {reasonText}
|
||||
{!isChildOfGroup && <span>on your profile</span>}
|
||||
{!isChildOfGroup && (
|
||||
<span>{t('notifications.comment.on_your_profile', 'on your profile')}</span>
|
||||
)}
|
||||
</div>
|
||||
</NotificationFrame>
|
||||
)
|
||||
@@ -105,6 +109,7 @@ export function NewMatchNotification(props: {
|
||||
}) {
|
||||
const {notification, isChildOfGroup, highlighted, setHighlighted} = props
|
||||
const {sourceContractTitle, sourceText, sourceUserName, sourceUserUsername} = notification
|
||||
const t = useT()
|
||||
return (
|
||||
<NotificationFrame
|
||||
notification={notification}
|
||||
@@ -122,7 +127,8 @@ export function NewMatchNotification(props: {
|
||||
<div className="line-clamp-3">
|
||||
<NotificationUserLink name={sourceUserName} username={sourceUserUsername} />{' '}
|
||||
<span>
|
||||
proposed a new match: <PrimaryNotificationLink text={sourceContractTitle} />
|
||||
{t('notifications.match.proposed_new_match', 'proposed a new match:')}
|
||||
<PrimaryNotificationLink text={sourceContractTitle} />
|
||||
</span>
|
||||
</div>
|
||||
</NotificationFrame>
|
||||
@@ -137,6 +143,7 @@ function ProfileLikeNotification(props: {
|
||||
}) {
|
||||
const {notification, highlighted, setHighlighted, isChildOfGroup} = props
|
||||
const [open, setOpen] = useState(false)
|
||||
const t = useT()
|
||||
const {sourceUserName, sourceUserUsername} = notification
|
||||
const relatedNotifications: Notification[] = notification.data?.relatedNotifications ?? [
|
||||
notification,
|
||||
@@ -157,10 +164,11 @@ function ProfileLikeNotification(props: {
|
||||
link={`https://${ENV_CONFIG.domain}/${sourceUserUsername}`}
|
||||
subtitle={<></>}
|
||||
>
|
||||
{reactorsText && <PrimaryNotificationLink text={reactorsText} />} liked you!
|
||||
{reactorsText && <PrimaryNotificationLink text={reactorsText} />}{' '}
|
||||
{t('notifications.profile.liked_you', 'liked you!')}
|
||||
<MultiUserReactionModal
|
||||
similarNotifications={relatedNotifications}
|
||||
modalLabel={'Who liked it?'}
|
||||
modalLabel={t('notifications.who_liked_it', 'Who liked it?')}
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
@@ -176,6 +184,7 @@ function ProfileShipNotification(props: {
|
||||
}) {
|
||||
const {notification, highlighted, setHighlighted, isChildOfGroup} = props
|
||||
const [open, setOpen] = useState(false)
|
||||
const t = useT()
|
||||
const {sourceUserName, sourceUserUsername} = notification
|
||||
const relatedNotifications: Notification[] = notification.data?.relatedNotifications ?? [
|
||||
notification,
|
||||
@@ -198,7 +207,8 @@ function ProfileShipNotification(props: {
|
||||
link={`https://${ENV_CONFIG.domain}/${sourceUserUsername}`}
|
||||
subtitle={<></>}
|
||||
>
|
||||
You and {reactorsText && <PrimaryNotificationLink text={reactorsText} />} are being shipped by{' '}
|
||||
You and {reactorsText && <PrimaryNotificationLink text={reactorsText} />}{' '}
|
||||
{t('notifications.profile.are_being_shipped_by', 'are being shipped by')}{' '}
|
||||
<NotificationUserLink
|
||||
name={creatorName}
|
||||
username={creatorUsername}
|
||||
@@ -208,7 +218,7 @@ function ProfileShipNotification(props: {
|
||||
!
|
||||
<MultiUserReactionModal
|
||||
similarNotifications={relatedNotifications}
|
||||
modalLabel={'Who liked it?'}
|
||||
modalLabel={t('notifications.who_liked_it', 'Who liked it?')}
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@ import {Placement} from '@floating-ui/react'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import {useIsClient} from 'web/hooks/use-is-client'
|
||||
import {useLocale, useT} from 'web/lib/locale'
|
||||
import {shortenedFromNow} from 'web/lib/util/shortenedFromNow'
|
||||
import {fromNow} from 'web/lib/util/time'
|
||||
|
||||
@@ -35,9 +36,11 @@ export function RelativeTimestampNoTooltip(props: {
|
||||
}) {
|
||||
const {time, className, shortened} = props
|
||||
const isClient = useIsClient()
|
||||
const {locale} = useLocale()
|
||||
const t = useT()
|
||||
return (
|
||||
<span className={className}>
|
||||
{isClient && (shortened ? shortenedFromNow(time) : fromNow(time))}
|
||||
{isClient && (shortened ? shortenedFromNow(time, t) : fromNow(time, false, t, locale))}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {type User} from 'common/user'
|
||||
import {first, groupBy, sortBy} from 'lodash'
|
||||
import {useEffect, useMemo} from 'react'
|
||||
import {api} from 'web/lib/api'
|
||||
import {useLocale} from 'web/lib/locale'
|
||||
|
||||
import {useApiSubscription} from './use-api-subscription'
|
||||
import {usePersistentLocalState} from './use-persistent-local-state'
|
||||
@@ -69,13 +70,13 @@ function useNotifications(userId: string, count = 15 * NOTIFICATIONS_PER_PAGE) {
|
||||
undefined,
|
||||
'notifications-' + userId,
|
||||
)
|
||||
const {locale} = useLocale()
|
||||
useEffect(() => {
|
||||
if (userId)
|
||||
api('get-notifications', {limit: count}).then((data) => {
|
||||
api('get-notifications', {limit: count, locale}).then((data) => {
|
||||
setNotifications(data)
|
||||
})
|
||||
}, [userId])
|
||||
|
||||
}, [userId, locale])
|
||||
useApiSubscription({
|
||||
topics: [`user-notifications/${userId}`],
|
||||
onBroadcast: ({data}) => {
|
||||
|
||||
@@ -3,10 +3,9 @@ import duration from 'dayjs/plugin/duration'
|
||||
|
||||
dayjs.extend(duration)
|
||||
|
||||
export function shortenedFromNow(time: number): string {
|
||||
export function shortenedFromNow(time: number, t: any = undefined): string {
|
||||
const diff = dayjs.duration(Math.abs(dayjs().diff(time)))
|
||||
|
||||
return shortenedDuration(diff)
|
||||
return shortenedDuration(diff, t)
|
||||
}
|
||||
|
||||
export function simpleFromNow(time: number): string {
|
||||
@@ -15,7 +14,7 @@ export function simpleFromNow(time: number): string {
|
||||
return durationFormat(diff)
|
||||
}
|
||||
|
||||
export function shortenedDuration(diff: duration.Duration) {
|
||||
export function shortenedDuration(diff: duration.Duration, t: any = undefined) {
|
||||
const units: {[key: string]: number} = {
|
||||
y: diff.years(),
|
||||
mo: diff.months(),
|
||||
@@ -27,7 +26,7 @@ export function shortenedDuration(diff: duration.Duration) {
|
||||
|
||||
for (const unit in units) {
|
||||
if (units[unit] > 0) {
|
||||
return `${units[unit]}${unit}`
|
||||
return `${units[unit]}${t(`time.units.${unit}`, unit)}`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import {type User} from 'common/src/user'
|
||||
import {Fragment, useEffect, useMemo, useState} from 'react'
|
||||
import {Col} from 'web/components/layout/col'
|
||||
import {UncontrolledTabs} from 'web/components/layout/tabs'
|
||||
import {EnglishOnlyWarning} from 'web/components/news/english-only-warning'
|
||||
import {NoSEO} from 'web/components/NoSEO'
|
||||
import {NotificationItem} from 'web/components/notification-items'
|
||||
import {NotificationSettings} from 'web/components/notifications'
|
||||
@@ -24,7 +23,6 @@ export default function NotificationsPage() {
|
||||
<PageBase trackPageView={'notifications page'} className={'mx-4'}>
|
||||
<NoSEO />
|
||||
<Title>{t('notifications.title', 'Updates')}</Title>
|
||||
<EnglishOnlyWarning />
|
||||
<UncontrolledTabs
|
||||
name={'notifications-page'}
|
||||
tabs={[
|
||||
|
||||
Reference in New Issue
Block a user