diff --git a/functions/src/discussionUpdates/index.ts b/functions/src/discussionUpdates/index.ts index 6da937f61e..e64e03680e 100644 --- a/functions/src/discussionUpdates/index.ts +++ b/functions/src/discussionUpdates/index.ts @@ -1,10 +1,10 @@ -import { firestore } from 'firebase-admin' import * as functions from 'firebase-functions' import { db } from '../Firebase/firestoreDB' import { DB_ENDPOINTS } from '../models' -import type { IUserDB, IDiscussion } from '../models' +import type { firestore } from 'firebase-admin' +import type { IDiscussion, IUserDB } from '../models' /********************************************************************* * Side-effects to be carried out on various question updates, namely: @@ -21,7 +21,6 @@ export const handleDiscussionUpdate = functions async function updateDocument( change: functions.Change, ) { - // add new comments to user.stats.userCreatedComments const addedComments = getAddedComments(change) for (const addedComment of addedComments) { const commentId = addedComment._id @@ -31,10 +30,12 @@ async function updateDocument( .get() const user = userSnapshot.docs[0].data() as IUserDB - let userCreatedComments = user.stats?.userCreatedComments ?? {} + const _lastActive = Date.now().toString(16) + const userCreatedComments = user.stats?.userCreatedComments ?? {} userCreatedComments[commentId] = addedComment.parentCommentId await userSnapshot.docs[0].ref.update({ + _lastActive, 'stats.userCreatedComments': userCreatedComments, }) } @@ -49,10 +50,12 @@ async function updateDocument( .get() const user = userSnapshot.docs[0].data() as IUserDB - let userCreatedComments = user.stats?.userCreatedComments ?? {} + const _lastActive = Date.now().toString(16) + const userCreatedComments = user.stats?.userCreatedComments ?? {} delete userCreatedComments[commentId] await userSnapshot.docs[0].ref.update({ + _lastActive, 'stats.userCreatedComments': userCreatedComments, }) } diff --git a/functions/src/emulator/seed/users-create.ts b/functions/src/emulator/seed/users-create.ts index 483c52af77..1a5308495d 100644 --- a/functions/src/emulator/seed/users-create.ts +++ b/functions/src/emulator/seed/users-create.ts @@ -1,8 +1,9 @@ import { MOCK_AUTH_USERS } from 'oa-shared/mocks/auth' -import type { IUserDB } from '../../models' + import { firebaseAuth } from '../../Firebase/auth' import { setDoc } from '../../Firebase/firestoreDB' -import { IModerationStatus } from 'oa-shared' + +import type { IUserDB } from '../../models' /** * Create auth users to allow sign-in on firebase emulators @@ -43,11 +44,10 @@ export async function seedUsersCreate() { _deleted: false, displayName: user.label, userName: uid, - moderation: IModerationStatus.AWAITING_MODERATION, notifications: [], userRoles: roles, links: [], - ['profileType' as any]: 'member', // include pp profile field + profileType: 'member', coverImages: [], verified: true, } diff --git a/functions/src/questionUpdates/index.ts b/functions/src/questionUpdates/index.ts index 6a33583a35..8bc2441e13 100644 --- a/functions/src/questionUpdates/index.ts +++ b/functions/src/questionUpdates/index.ts @@ -1,10 +1,10 @@ -import { firestore } from 'firebase-admin' import * as functions from 'firebase-functions' import { db } from '../Firebase/firestoreDB' import { DB_ENDPOINTS } from '../models' -import type { IUserDB, IQuestionDB } from '../models' +import type { firestore } from 'firebase-admin' +import type { IQuestionDB, IUserDB } from '../models' /********************************************************************* * Side-effects to be carried out on various question updates, namely: @@ -41,10 +41,12 @@ async function updateDocument(docSnapshot: firestore.QueryDocumentSnapshot) { .get() const user = userSnapshot.docs[0].data() as IUserDB - let userCreatedQuestions = user.stats?.userCreatedQuestions ?? {} + const _lastActive = Date.now().toString(16) + const userCreatedQuestions = user.stats?.userCreatedQuestions ?? {} userCreatedQuestions[question_id] = question.moderation await userSnapshot.docs[0].ref.update({ + _lastActive, 'stats.userCreatedQuestions': userCreatedQuestions, }) } @@ -59,10 +61,12 @@ async function deleteDocument(docSnapshot: firestore.QueryDocumentSnapshot) { .get() const user = userSnapshot.docs[0].data() as IUserDB - let userCreatedQuestions = user.stats?.userCreatedQuestions ?? {} + const _lastActive = Date.now().toString(16) + const userCreatedQuestions = user.stats?.userCreatedQuestions ?? {} delete userCreatedQuestions[question_id] await userSnapshot.docs[0].ref.update({ + _lastActive, 'stats.userCreatedQuestions': userCreatedQuestions, }) } diff --git a/functions/src/userUpdates/hasKeyDetailsChanged.ts b/functions/src/userUpdates/hasKeyDetailsChanged.ts deleted file mode 100644 index ded54ae56c..0000000000 --- a/functions/src/userUpdates/hasKeyDetailsChanged.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { valuesAreDeepEqual } from '../Utils' - -import type { IUserDB } from '../models' - -export const hasDetailsChanged = ( - prevUser: IUserDB, - user: IUserDB, -): boolean[] => { - return [ - prevUser.displayName !== user.displayName, - prevUser.location?.countryCode !== user.location?.countryCode, - hasUserImageChanged(prevUser, user), - prevUser.badges?.verified !== user.badges?.verified, - prevUser.badges?.supporter !== user.badges?.supporter, - ] -} - -export const hasKeyDetailsChanged = ( - prevUser: IUserDB, - user: IUserDB, -): boolean => { - const detailsChanged = hasDetailsChanged(prevUser, user) - return !!detailsChanged.find((detail) => detail === true) -} - -export const hasUserImageChanged = ( - prevUser: IUserDB, - user: IUserDB, -): boolean => { - if (!prevUser.userImage && !user.userImage) return false - - if (prevUser.userImage && user.userImage) { - return !valuesAreDeepEqual(prevUser.userImage, user.userImage) - } - - if (prevUser.userImage && !user.userImage) return true - if (!prevUser.userImage && user.userImage) return true - - return false -} diff --git a/functions/src/userUpdates/index.ts b/functions/src/userUpdates/index.ts index b9bbd0b55f..36aef48c8f 100644 --- a/functions/src/userUpdates/index.ts +++ b/functions/src/userUpdates/index.ts @@ -4,6 +4,7 @@ import { db } from '../Firebase/firestoreDB' import { DB_ENDPOINTS } from '../models' import { backupUser } from './backupUser' import { updateDiscussionComments } from './updateDiscussionComments' +import { updateMapPins } from './updateMapPins' import type { IDBDocChange, IUserDB } from '../models' @@ -17,12 +18,12 @@ import type { IDBDocChange, IUserDB } from '../models' export const handleUserUpdates = functions .runWith({ memory: '512MB' }) .firestore.document(`${DB_ENDPOINTS.users}/{id}`) - .onUpdate(async (change, context) => { + .onUpdate(async (change, _) => { await backupUser(change) await updateDocuments(change) }) -const isUserCountryDifferent = (prevInfo, info) => { +const isUserCountryDifferent = (prevInfo: IUserDB, info: IUserDB) => { const prevCountryCode = prevInfo.location?.countryCode const newCountryCode = info.location?.countryCode const prevCountry = prevInfo.country @@ -48,6 +49,7 @@ async function updateDocuments(change: IDBDocChange) { } await updateDiscussionComments(prevInfo, info) + await updateMapPins(prevInfo, info) const didDelete = prevDeleted !== deleted && deleted if (didDelete) { diff --git a/functions/src/userUpdates/updateDiscussionComments.ts b/functions/src/userUpdates/updateDiscussionComments.ts index 0b096ba1d0..9c8df29138 100644 --- a/functions/src/userUpdates/updateDiscussionComments.ts +++ b/functions/src/userUpdates/updateDiscussionComments.ts @@ -1,7 +1,7 @@ import { DB_ENDPOINTS } from 'oa-shared' import { db } from '../Firebase/firestoreDB' -import { hasKeyDetailsChanged } from './hasKeyDetailsChanged' +import { getCreatorImage, hasDetailsForCommentsChanged } from './utils' import type { IDiscussion, IUserDB } from '../models' @@ -9,7 +9,7 @@ export const updateDiscussionComments = async ( prevUser: IUserDB, user: IUserDB, ) => { - if (!hasKeyDetailsChanged(prevUser, user)) return + if (!hasDetailsForCommentsChanged(prevUser, user)) return const snapshot = await db .collection(DB_ENDPOINTS.discussions) @@ -17,11 +17,12 @@ export const updateDiscussionComments = async ( .get() if (!snapshot.empty) { - const { _id, badges, userImage, location } = user + const { _id, badges, country, userImage, location } = user const creatorImage = getCreatorImage(userImage) + const creatorCountry = location?.countryCode || country || '' const userDetails = { - creatorCountry: location?.countryCode || '', + creatorCountry, creatorImage, isUserVerified: !!badges?.verified, isUserSupporter: !!badges?.supporter, @@ -47,10 +48,3 @@ export const updateDiscussionComments = async ( return console.log(`Updated ${updatedCommentCount} discussion comments`) } } - -const getCreatorImage = (userImage: IUserDB['userImage']) => { - if (userImage && userImage.downloadUrl) { - return userImage.downloadUrl - } - return null -} diff --git a/functions/src/userUpdates/updateMapPins.ts b/functions/src/userUpdates/updateMapPins.ts new file mode 100644 index 0000000000..15ff04cdc5 --- /dev/null +++ b/functions/src/userUpdates/updateMapPins.ts @@ -0,0 +1,62 @@ +import { DB_ENDPOINTS } from 'oa-shared' + +import { db } from '../Firebase/firestoreDB' +import { + getCreatorImage, + getFirstCoverImage, + hasDetailsForMapPinChanged, +} from './utils' + +import type { IUserDB } from '../models' + +export const updateMapPins = async (prevUser: IUserDB, user: IUserDB) => { + if (!hasDetailsForMapPinChanged(prevUser, user)) { + return + } + + const snapshot = await db + .collection(DB_ENDPOINTS.mappins) + .where('_id', '==', user._id) + .get() + + if (snapshot.empty) { + return + } + + const { + _id, + _lastActive, + about, + badges, + country, + coverImages, + displayName, + isContactableByPublic, + profileType, + subType, + userImage, + location, + } = user + const creatorImage = getCreatorImage(userImage) + const coverImage = getFirstCoverImage(coverImages) + const countryCode = location?.countryCode || country || '' + + const creator = { + _lastActive, + about, + badges, + countryCode, + coverImage, + displayName, + isContactableByPublic, + profileType, + subType, + userImage: creatorImage, + } + + // Only one expected + for (const doc of snapshot.docs) { + await doc.ref.update({ creator }) + } + return console.log(`Updated ${_id}'s mapPin`) +} diff --git a/functions/src/userUpdates/hasKeyDetailsChanged.test.ts b/functions/src/userUpdates/utils.test.ts similarity index 55% rename from functions/src/userUpdates/hasKeyDetailsChanged.test.ts rename to functions/src/userUpdates/utils.test.ts index b3ede40f7c..6739fcb3f1 100644 --- a/functions/src/userUpdates/hasKeyDetailsChanged.test.ts +++ b/functions/src/userUpdates/utils.test.ts @@ -1,12 +1,80 @@ import { hasDetailsChanged, - hasKeyDetailsChanged, + hasDetailsForCommentsChanged, + hasDetailsForMapPinChanged, + hasLocationDetailsChanged, hasUserImageChanged, -} from './hasKeyDetailsChanged' +} from './utils' import type { IUserDB } from '../../../src/models' -describe('hasKeyDetailsChanged', () => { +describe('hasDetailsChanged', () => { + it("returns false for every field that's the same", () => { + const user = { + displayName: 'same', + userImage: { + downloadUrl: 'https://more.same/image.jpg', + }, + badges: { + verified: true, + supporter: false, + }, + } as IUserDB + + expect(hasDetailsChanged(user, user)).toEqual([false, false, false, false]) + }) + + it("returns true for every field that's different", () => { + const prevUser = { + displayName: 'old', + userImage: { + downloadUrl: 'https://more.old/image.jpg', + }, + badges: { + verified: true, + supporter: true, + }, + } as IUserDB + + const user = { + displayName: 'new', + userImage: { + downloadUrl: 'https://more.new/image.jpg', + }, + badges: { + verified: false, + supporter: false, + }, + } as IUserDB + + expect(hasDetailsChanged(prevUser, user)).toEqual([true, true, true, true]) + }) +}) + +describe('hasLocationDetailsChanged', () => { + it('returns true when details have changed', () => { + const prevUser = { + location: { countryCode: 'mw' }, + country: 'mw', + } as IUserDB + + const user = { + location: { countryCode: 'uk' }, + country: 'uk', + } as IUserDB + expect(hasLocationDetailsChanged(prevUser, user)).toEqual([true, true]) + }) + + it('returns false when details are the same', () => { + const user = { + location: { countryCode: 'uk' }, + country: 'uk', + } as IUserDB + expect(hasLocationDetailsChanged(user, user)).toEqual([false, false]) + }) +}) + +describe('hasDetailsForCommentsChanged', () => { it('returns true when details have changed', () => { const prevUser = { displayName: 'old name', @@ -17,12 +85,11 @@ describe('hasKeyDetailsChanged', () => { userImage: { downloadUrl: 'http//etc.', }, - location: { countryCode: 'USA' }, badges: { verified: true }, } as IUserDB - expect(hasKeyDetailsChanged(prevUser, user)).toEqual(true) + expect(hasDetailsForCommentsChanged(prevUser, user)).toEqual(true) }) it('returns false when details are the same', () => { @@ -39,11 +106,56 @@ describe('hasKeyDetailsChanged', () => { }, } as IUserDB - expect(hasKeyDetailsChanged(user, user)).toEqual(false) + expect(hasDetailsForCommentsChanged(user, user)).toEqual(false) }) }) -describe('hasKeyDetailsChanged', () => { +describe('hasDetailsForMapPinChanged', () => { + it('returns true when details have changed', () => { + const prevUser = { + _lastActive: 'yesterday', + about: 'Old description', + displayName: 'id_name', + isContactableByPublic: true, + profileType: 'member', + subType: null, + } as IUserDB + const user = { + _authID: '', + _lastActive: 'today', + about: 'Super new description', + coverImages: [], + displayName: 'id name', + isContactableByPublic: false, + links: [], + profileType: 'workspace', + subType: 'shredder', + userName: 'idname', + verified: false, + } as IUserDB + + expect(hasDetailsForMapPinChanged(prevUser, user)).toEqual(true) + }) + + it('returns false when details are the same', () => { + const user = { + _authID: '', + _lastActive: 'today', + about: 'Super new description', + coverImages: [], + displayName: 'id name', + isContactableByPublic: false, + profileType: 'workspace', + subType: 'shredder', + userName: 'idname', + verified: false, + } as IUserDB + + expect(hasDetailsForMapPinChanged(user, user)).toEqual(false) + }) +}) + +describe('hasUserImageChanged', () => { it('returns false when userImage array is missing from both', () => { const user = {} as IUserDB expect(hasUserImageChanged(user, user)).toEqual(false) @@ -84,61 +196,3 @@ describe('hasKeyDetailsChanged', () => { expect(hasUserImageChanged(prevUser, user)).toEqual(true) }) }) - -describe('hasDetailsChanged', () => { - it("returns false for every field that's the same", () => { - const user = { - displayName: 'same', - location: { countryCode: 'same' }, - userImage: { - downloadUrl: 'https://more.same/image.jpg', - }, - badges: { - verified: true, - supporter: false, - }, - } as IUserDB - - expect(hasDetailsChanged(user, user)).toEqual([ - false, - false, - false, - false, - false, - ]) - }) - - it("returns truw for every field that's different", () => { - const prevUser = { - displayName: 'old', - location: { countryCode: 'old' }, - userImage: { - downloadUrl: 'https://more.old/image.jpg', - }, - badges: { - verified: true, - supporter: true, - }, - } as IUserDB - - const user = { - displayName: 'new', - location: { countryCode: 'new' }, - userImage: { - downloadUrl: 'https://more.new/image.jpg', - }, - badges: { - verified: false, - supporter: false, - }, - } as IUserDB - - expect(hasDetailsChanged(prevUser, user)).toEqual([ - true, - true, - true, - true, - true, - ]) - }) -}) diff --git a/functions/src/userUpdates/utils.ts b/functions/src/userUpdates/utils.ts new file mode 100644 index 0000000000..91ba552cf6 --- /dev/null +++ b/functions/src/userUpdates/utils.ts @@ -0,0 +1,75 @@ +import { valuesAreDeepEqual } from '../Utils' + +import type { IUserDB } from '../models' + +export const hasDetailsChanged = ( + prevUser: IUserDB, + user: IUserDB, +): boolean[] => { + return [ + prevUser.displayName !== user.displayName, + hasUserImageChanged(prevUser, user), + prevUser.badges?.verified !== user.badges?.verified, + prevUser.badges?.supporter !== user.badges?.supporter, + ] +} + +export const hasLocationDetailsChanged = ( + prevUser: IUserDB, + user: IUserDB, +): boolean[] => { + return [ + prevUser.location?.countryCode !== user.location?.countryCode, + prevUser.country !== user.country, + ] +} + +export const hasDetailsForCommentsChanged = ( + prevUser: IUserDB, + user: IUserDB, +): boolean => { + const detailsChanged = [ + ...hasDetailsChanged(prevUser, user), + ...hasLocationDetailsChanged(prevUser, user), + ] + return !!detailsChanged.find((detail) => detail === true) +} + +export const hasDetailsForMapPinChanged = ( + prevUser: IUserDB, + user: IUserDB, +): boolean => { + const detailsChanged = [ + prevUser._lastActive !== user._lastActive, + prevUser.about !== user.about, + prevUser.displayName !== user.displayName, + prevUser.isContactableByPublic !== user.isContactableByPublic, + prevUser.profileType !== user.profileType, + prevUser.subType !== user.subType, + ...hasDetailsChanged(prevUser, user), + ...hasLocationDetailsChanged(prevUser, user), + ] + return !!detailsChanged.find((detail) => detail === true) +} + +export const hasUserImageChanged = ( + prevUser: IUserDB, + user: IUserDB, +): boolean => { + if (!prevUser.userImage && !user.userImage) return false + + if (prevUser.userImage && user.userImage) { + return !valuesAreDeepEqual(prevUser.userImage, user.userImage) + } + + if (prevUser.userImage && !user.userImage) return true + if (!prevUser.userImage && user.userImage) return true +} + +export const getCreatorImage = (userImage: IUserDB['userImage']) => { + return userImage?.downloadUrl || null +} + +export const getFirstCoverImage = (coverImages: IUserDB['coverImages']) => { + return coverImages?.[0]?.downloadUrl || null +} diff --git a/packages/cypress/src/integration/map.spec.ts b/packages/cypress/src/integration/map.spec.ts index 8d289d7452..fd5cdda8de 100644 --- a/packages/cypress/src/integration/map.spec.ts +++ b/packages/cypress/src/integration/map.spec.ts @@ -27,7 +27,7 @@ describe('[Map]', () => { cy.step('New map shows the cards') cy.get('[data-cy="welome-header"]').should('be.visible') cy.get('[data-cy="CardList-desktop"]').should('be.visible') - cy.get('[data-cy="list-results"]').contains('51 results in view') + cy.get('[data-cy="list-results"]').contains('52 results in view') cy.step('Map filters can be used') cy.get('[data-cy=FilterList]') @@ -37,10 +37,10 @@ describe('[Map]', () => { cy.get('[data-cy=MapListFilter]').first().click() cy.get('[data-cy="list-results"]').contains('6 results in view') cy.get('[data-cy=MapListFilter-active]').first().click() - cy.get('[data-cy="list-results"]').contains('51 results in view') + cy.get('[data-cy="list-results"]').contains('52 results in view') cy.step('As the user moves in the list updates') - for (let i = 0; i < 10; i++) { + for (let i = 0; i < 9; i++) { cy.get('.leaflet-control-zoom-in').click() } cy.get('[data-cy="list-results"]').contains('1 result') diff --git a/packages/cypress/src/support/CustomAssertations.ts b/packages/cypress/src/support/CustomAssertations.ts index 3ece7ecd1a..833c221c97 100644 --- a/packages/cypress/src/support/CustomAssertations.ts +++ b/packages/cypress/src/support/CustomAssertations.ts @@ -1,12 +1,12 @@ import chaiSubset from 'chai-subset' +import type { ProfileTypeName } from 'oa-shared' import type { IHowto, IHowtoStep, IResearchDB, - IUserPPDB, + IUserDB, } from '../../../../src/models' -import type { ProfileTypeLabel } from '../../../../src/modules/profile/types' declare global { namespace Chai { @@ -134,7 +134,7 @@ const eqSettings = (chaiObj) => { this.asserts.push(...asserts) } } - const basicInfoAssert: Assert = (subject, expected) => { + const basicInfoAssert: Assert = (subject, expected) => { const { _deleted, about, displayName, profileType, verified } = expected expect(subject, 'Basic Info').to.containSubset({ _deleted, @@ -144,7 +144,7 @@ const eqSettings = (chaiObj) => { about, }) } - const basicMemberInfoAssert: Assert = (subject, expected) => { + const basicMemberInfoAssert: Assert = (subject, expected) => { const { _deleted, about, profileType, displayName, verified } = expected expect(subject, 'Basic Info').to.containSubset({ _deleted, @@ -154,66 +154,66 @@ const eqSettings = (chaiObj) => { about, }) } - const linkAssert: Assert = (subject, expected) => + const linkAssert: Assert = (subject, expected) => expect(subject.links.length, 'Links').to.eq(expected.links.length) - const coverImageAssert: Assert = (subject, expected) => + const coverImageAssert: Assert = (subject, expected) => // only test length as uploaded images get new url and meta expect(subject.coverImages, 'CoverImages').to.have.same.length( expected.coverImages.length, ) - const locationAssert: Assert = (subject, expected) => { + const locationAssert: Assert = (subject, expected) => { expect(subject.location, 'Location').to.containSubset(expected.location) expect(subject.mapPinDescription, 'MapPinDescription').to.containSubset( expected.mapPinDescription, ) } - const workspaceAssert: Assert = (subject, expected) => + const workspaceAssert: Assert = (subject, expected) => expect(subject.workspaceType, 'workspaceType').to.containSubset( expected.workspaceType, ) - const machineExpertiseAssert: Assert = (subject, expected) => + const machineExpertiseAssert: Assert = (subject, expected) => expect(subject.machineBuilderXp, 'MachineBuilderXp').to.containSubset( expected.machineBuilderXp, ) - const openingHoursAssert: Assert = (subject, expected) => + const openingHoursAssert: Assert = (subject, expected) => expect(subject.openingHours, 'OpeningHours').to.containSubset( expected.openingHours, ) - const plasticTypeAssert: Assert = (subject, expected) => + const plasticTypeAssert: Assert = (subject, expected) => expect( subject.collectedPlasticTypes, 'CollectedPlasticTypes', ).to.containSubset(expected.collectedPlasticTypes) const assertMap: { - [key in ProfileTypeLabel]: ChainAssert + [key in ProfileTypeName]: ChainAssert } = { - workspace: new ChainAssert( + workspace: new ChainAssert( workspaceAssert, basicInfoAssert, coverImageAssert, linkAssert, locationAssert, ), - member: new ChainAssert( + member: new ChainAssert( basicMemberInfoAssert, coverImageAssert, linkAssert, ), - 'machine-builder': new ChainAssert( + 'machine-builder': new ChainAssert( basicInfoAssert, coverImageAssert, linkAssert, locationAssert, machineExpertiseAssert, ), - 'community-builder': new ChainAssert( + 'community-builder': new ChainAssert( basicInfoAssert, coverImageAssert, linkAssert, locationAssert, ), - 'collection-point': new ChainAssert( + 'collection-point': new ChainAssert( basicInfoAssert, coverImageAssert, linkAssert, diff --git a/shared/models/user.ts b/shared/models/user.ts index 514211e0f6..7b7a899c70 100644 --- a/shared/models/user.ts +++ b/shared/models/user.ts @@ -89,3 +89,58 @@ export const ProfileTypeList = { export type ProfileTypeName = (typeof ProfileTypeList)[keyof typeof ProfileTypeList] + +// Below are primarily used for PP + +export type PlasticTypeLabel = + | 'pet' + | 'hdpe' + | 'pvc' + | 'ldpe' + | 'pp' + | 'ps' + | 'other' + +export type MachineBuilderXpLabel = + | 'electronics' + | 'machining' + | 'welding' + | 'assembling' + | 'mould-making' + +export type WorkspaceType = + | 'shredder' + | 'sheetpress' + | 'extrusion' + | 'injection' + | 'mix' + +export interface IPlasticType { + label: PlasticTypeLabel + number: string + imageSrc?: string +} + +export interface IProfileType { + label: ProfileTypeName + imageSrc?: string + cleanImageSrc?: string + cleanImageVerifiedSrc?: string + textLabel?: string +} +export interface IWorkspaceType { + label: WorkspaceType + imageSrc?: string + textLabel?: string + subText?: string +} + +export interface IMAchineBuilderXp { + label: MachineBuilderXpLabel +} + +export interface IOpeningHours { + day: string + openFrom: string + openTo: string +} diff --git a/src/common/AuthWrapper.tsx b/src/common/AuthWrapper.tsx index a3f99f5c42..a712f260d5 100644 --- a/src/common/AuthWrapper.tsx +++ b/src/common/AuthWrapper.tsx @@ -5,7 +5,7 @@ import { DEV_SITE_ROLE, SITE } from 'src/config/config' import { isTestEnvironment } from 'src/utils/isTestEnvironment' import type { UserRole } from 'oa-shared' -import type { IUserPPDB } from 'src/models' +import type { IUserDB } from 'src/models' /* Simple wrapper to only render a component if the user is logged in (plus optional user role required) @@ -31,7 +31,7 @@ export const AuthWrapper = observer((props: IProps) => { }) const isUserAuthorized = ( - user?: IUserPPDB | null, + user?: IUserDB | null, roleRequired?: UserRole | UserRole[], ) => { const userRoles = user?.userRoles || [] diff --git a/src/common/DownloadWithDonationAsk.test.tsx b/src/common/DownloadWithDonationAsk.test.tsx index 19a18fafe1..ff23b58b53 100644 --- a/src/common/DownloadWithDonationAsk.test.tsx +++ b/src/common/DownloadWithDonationAsk.test.tsx @@ -8,7 +8,7 @@ import { describe, expect, it, vi } from 'vitest' import { useCommonStores } from './hooks/useCommonStores' import { DownloadWithDonationAsk } from './DownloadWithDonationAsk' -import type { IUserPPDB } from 'src/models' +import type { IUserDB } from 'src/models' import type { IUploadedFileMeta } from 'src/stores/storage' import type { Mock } from 'vitest' @@ -21,7 +21,7 @@ vi.mock('src/common/hooks/useCommonStores', () => ({ __esModule: true, useCommonStores: vi.fn(), })) -const userToMock = (user?: IUserPPDB) => { +const userToMock = (user?: IUserDB) => { return (useCommonStores as Mock).mockImplementation(() => ({ stores: { userStore: { user: user ?? undefined }, diff --git a/src/common/transformToUserComments.ts b/src/common/transformToUserComments.ts index ae38cf0fd6..c5c9fb4cd5 100644 --- a/src/common/transformToUserComments.ts +++ b/src/common/transformToUserComments.ts @@ -1,9 +1,9 @@ import { UserRole } from 'oa-shared' -import { type IComment, type IUserPPDB } from 'src/models' +import { type IComment, type IUserDB } from 'src/models' export const transformToUserComments = ( comments: IComment[], - loggedInUser: IUserPPDB | null | undefined, + loggedInUser: IUserDB | null | undefined, ): IComment[] => { return ( comments?.map((c) => ({ diff --git a/src/config/config.ts b/src/config/config.ts index 84eeab34fd..f2843f0011 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -10,7 +10,7 @@ Dev config is hardcoded - You can find more information about potential security https://javebratt.com/hide-firebase-api/ *****************************************************************************************/ -import type { UserRole } from '../models' +import type { UserRole } from 'oa-shared' import type { ConfigurationOption } from './constants' import type { IFirebaseConfig, ISentryConfig, siteVariants } from './types' diff --git a/src/models/index.ts b/src/models/index.ts index 1e01c604b1..57f5dea5b6 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -13,7 +13,6 @@ export * from './selectorList.models' export * from './tags.model' export * from './user.models' export * from './moderation.model' -export * from './userPreciousPlastic.models' export interface UserComment extends IComment { isEditable: boolean diff --git a/src/models/maps.models.tsx b/src/models/maps.models.tsx index 190b7edd63..99f77da2dc 100644 --- a/src/models/maps.models.tsx +++ b/src/models/maps.models.tsx @@ -5,8 +5,8 @@ import type { IPinGrouping, IProfileCreator, ProfileTypeName, + WorkspaceType, } from 'oa-shared' -import type { WorkspaceType } from './userPreciousPlastic.models' /** * Map pins keep minimal information required for pin display. diff --git a/src/models/user.models.tsx b/src/models/user.models.tsx index 7142e0ce8d..bdfedcd964 100644 --- a/src/models/user.models.tsx +++ b/src/models/user.models.tsx @@ -1,21 +1,20 @@ import type { EmailNotificationFrequency, ExternalLinkLabel, + IMAchineBuilderXp, IModerationStatus, + IOpeningHours, NotificationType, PatreonUser, + PlasticTypeLabel, + ProfileTypeName, UserRole, + WorkspaceType, } from 'oa-shared' +import type { IUploadedFileMeta } from '../stores/storage' import type { ILocation, ISODateString } from './common.models' import type { DBDoc } from './dbDoc.model' -export type { UserRole } -import type { IUploadedFileMeta } from '../stores/storage' - -export interface IUserState { - user?: IUser -} - // IUser retains most of the fields from legacy users (omitting passwords), // and has a few additional fields. Note 'email' is excluded // _uid is unique/fixed identifier @@ -28,9 +27,6 @@ export interface IUser { // firebase auth displayName property userName: string displayName: string - moderation: IModerationStatus - // note, user avatar url is taken direct from userName so no longer populated here - // avatar:string verified: boolean badges?: IUserBadges // images will be in different formats if they are pending upload vs pulled from db @@ -39,8 +35,6 @@ export interface IUser { links: IExternalLink[] userRoles?: UserRole[] about?: string | null - DHSite_id?: number - DHSite_mention_name?: string country?: string | null location?: ILocation | null year?: ISODateString @@ -56,6 +50,17 @@ export interface IUser { isContactableByPublic?: boolean patreon?: PatreonUser | null totalUseful?: number + + // Primary PP profile type related fields + profileType: ProfileTypeName + subType?: WorkspaceType | null + workspaceType?: WorkspaceType | null + mapPinDescription?: string | null + openingHours?: IOpeningHours[] + collectedPlasticTypes?: PlasticTypeLabel[] | null + machineBuilderXp?: IMAchineBuilderXp[] | null + isExpert?: boolean | null + isV4Member?: boolean | null } export interface IUserImpact { diff --git a/src/models/userPreciousPlastic.models.tsx b/src/models/userPreciousPlastic.models.tsx deleted file mode 100644 index daa9b808c0..0000000000 --- a/src/models/userPreciousPlastic.models.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import type { ProfileTypeName } from 'oa-shared' -import type { DBDoc } from './dbDoc.model' -import type { IUser } from './user.models' - -export type PlasticTypeLabel = - | 'pet' - | 'hdpe' - | 'pvc' - | 'ldpe' - | 'pp' - | 'ps' - | 'other' - -export type MachineBuilderXpLabel = - | 'electronics' - | 'machining' - | 'welding' - | 'assembling' - | 'mould-making' - -export type WorkspaceType = - | 'shredder' - | 'sheetpress' - | 'extrusion' - | 'injection' - | 'mix' - -export interface IPlasticType { - label: PlasticTypeLabel - number: string - imageSrc?: string -} - -export interface IProfileType { - label: ProfileTypeName - imageSrc?: string - cleanImageSrc?: string - cleanImageVerifiedSrc?: string - textLabel?: string -} -export interface IWorkspaceType { - label: WorkspaceType - imageSrc?: string - textLabel?: string - subText?: string -} - -export interface IMAchineBuilderXp { - label: MachineBuilderXpLabel -} - -export interface IOpeningHours { - day: string - openFrom: string - openTo: string -} - -/** - * PP users can have a bunch of custom meta fields depending on profile type - */ -export interface IUserPP extends IUser { - profileType: ProfileTypeName - workspaceType?: WorkspaceType | null - mapPinDescription?: string | null - openingHours?: IOpeningHours[] - collectedPlasticTypes?: PlasticTypeLabel[] | null - machineBuilderXp?: IMAchineBuilderXp[] | null - isExpert?: boolean | null - isV4Member?: boolean | null -} - -export type IUserPPDB = IUserPP & DBDoc diff --git a/src/pages/Howto/howto.service.ts b/src/pages/Howto/howto.service.ts index 61073d3367..10ae380a41 100644 --- a/src/pages/Howto/howto.service.ts +++ b/src/pages/Howto/howto.service.ts @@ -22,7 +22,7 @@ import type { QueryFilterConstraint, QueryNonFilterConstraint, } from 'firebase/firestore' -import type { IHowto, IUser, IUserPPDB } from '../../models' +import type { IHowto, IUserDB } from '../../models' import type { ICategory } from '../../models/categories.model' import type { HowtoSortOption } from './Content/HowtoList/HowtoSortOptions' @@ -36,7 +36,7 @@ const search = async ( words: string[], category: string, sort: HowtoSortOption, - currentUser?: IUserPPDB, + currentUser?: IUserDB, snapshot?: QueryDocumentSnapshot, take: number = 10, ) => { @@ -62,7 +62,7 @@ const search = async ( return { items, total, lastVisible } } -const moderationFilters = (currentUser?: IUser) => { +const moderationFilters = (currentUser?: IUserDB) => { const filters = [where('moderation', '==', IModerationStatus.ACCEPTED)] if (currentUser) { @@ -83,7 +83,7 @@ const createQueries = ( words: string[], category: string, sort: HowtoSortOption, - currentUser?: IUser, + currentUser?: IUserDB, snapshot?: QueryDocumentSnapshot, take: number = 10, ) => { diff --git a/src/pages/Maps/Content/Controls/transformAvailableFiltersToGroups.tsx b/src/pages/Maps/Content/Controls/transformAvailableFiltersToGroups.tsx index ddad3efd67..67f389e203 100644 --- a/src/pages/Maps/Content/Controls/transformAvailableFiltersToGroups.tsx +++ b/src/pages/Maps/Content/Controls/transformAvailableFiltersToGroups.tsx @@ -5,8 +5,8 @@ import { Image } from 'theme-ui' import { transformSpecialistWorkspaceTypeToWorkspace } from './transformSpecialistWorkspaceTypeToWorkspace' -import type { IPinGrouping, ProfileTypeName } from 'oa-shared' -import type { IMapGrouping, IMapPin, WorkspaceType } from 'src/models' +import type { IPinGrouping, ProfileTypeName, WorkspaceType } from 'oa-shared' +import type { IMapGrouping, IMapPin } from 'src/models' const ICON_SIZE = 30 diff --git a/src/pages/Maps/Maps.test.tsx b/src/pages/Maps/Maps.test.tsx index 3ae0d81f38..5d489bdae3 100644 --- a/src/pages/Maps/Maps.test.tsx +++ b/src/pages/Maps/Maps.test.tsx @@ -9,7 +9,6 @@ import { import { ThemeProvider } from '@emotion/react' import { act, render, waitFor } from '@testing-library/react' import { Provider } from 'mobx-react' -import { IModerationStatus } from 'oa-shared' import { useCommonStores } from 'src/common/hooks/useCommonStores' import { FactoryMapPin } from 'src/test/factories/MapPin' import { FactoryUser } from 'src/test/factories/User' @@ -92,9 +91,7 @@ const Wrapper = (path = '/map') => { const mockMapPinService: IMapPinService = { getMapPinByUserId: vi.fn().mockResolvedValue({ - ...FactoryUser({ - moderation: IModerationStatus.ACCEPTED, - }), + ...FactoryUser(), ...FactoryMapPin(), detail: { shortDescription: 'description', diff --git a/src/pages/Research/researchHelpers.test.ts b/src/pages/Research/researchHelpers.test.ts index 9a9e29e481..ca9a54bc2a 100644 --- a/src/pages/Research/researchHelpers.test.ts +++ b/src/pages/Research/researchHelpers.test.ts @@ -3,13 +3,13 @@ import { describe, expect, it } from 'vitest' import { researchUpdateStatusFilter } from './researchHelpers' -import type { IResearch, IUserPPDB } from 'src/models' +import type { IResearch, IUserDB } from 'src/models' describe('Research Helpers', () => { describe('Research Update Status Filter', () => { it('should not show item when deleted', () => { // prepare - const user = { _id: 'author' } as IUserPPDB + const user = { _id: 'author' } as IUserDB const item = { _createdBy: user._id } as IResearch.Item const update = { _deleted: true } as IResearch.Update @@ -22,7 +22,7 @@ describe('Research Helpers', () => { it('should not show item when deleted and draft', () => { // prepare - const user = { _id: 'author' } as IUserPPDB + const user = { _id: 'author' } as IUserDB const item = { _createdBy: user._id } as IResearch.Item const update = { _deleted: true, @@ -38,7 +38,7 @@ describe('Research Helpers', () => { it('should not show when draft and not author', () => { // prepare - const user = { _id: 'non-author' } as IUserPPDB + const user = { _id: 'non-author' } as IUserDB const item = { _createdBy: 'author' } as IResearch.Item const update = { status: ResearchUpdateStatus.DRAFT } as IResearch.Update @@ -51,7 +51,7 @@ describe('Research Helpers', () => { it('should not show when draft and not authenticated', () => { // prepare - const user = { _id: 'author' } as IUserPPDB + const user = { _id: 'author' } as IUserDB const item = { _createdBy: user._id } as IResearch.Item const update = { status: ResearchUpdateStatus.DRAFT } as IResearch.Update @@ -64,7 +64,7 @@ describe('Research Helpers', () => { it('should show when not draft and not deleted', () => { // prepare - const user = { _id: 'author' } as IUserPPDB + const user = { _id: 'author' } as IUserDB const item = { _createdBy: user._id } as IResearch.Item const update = { status: ResearchUpdateStatus.PUBLISHED, @@ -79,7 +79,7 @@ describe('Research Helpers', () => { it('should show when draft and current user is the author', () => { // prepare - const user = { _id: 'author' } as IUserPPDB + const user = { _id: 'author' } as IUserDB const item = { _createdBy: user._id } as IResearch.Item const update = { status: ResearchUpdateStatus.DRAFT } as IResearch.Update @@ -92,7 +92,7 @@ describe('Research Helpers', () => { it('should show when draft and current user is a collaborator', () => { // prepare - const user = { _id: 'author' } as IUserPPDB + const user = { _id: 'author' } as IUserDB const item = { collaborators: [user._id] } as IResearch.Item const update = { status: ResearchUpdateStatus.DRAFT } as IResearch.Update @@ -105,7 +105,7 @@ describe('Research Helpers', () => { it('should show when draft and current user is an Admin', () => { // prepare - const user = { _id: 'admin', userRoles: [UserRole.ADMIN] } as IUserPPDB + const user = { _id: 'admin', userRoles: [UserRole.ADMIN] } as IUserDB const item = {} as IResearch.Item const update = { status: ResearchUpdateStatus.DRAFT } as IResearch.Update diff --git a/src/pages/Research/researchHelpers.ts b/src/pages/Research/researchHelpers.ts index 4bd59b3995..210918033b 100644 --- a/src/pages/Research/researchHelpers.ts +++ b/src/pages/Research/researchHelpers.ts @@ -1,11 +1,11 @@ import { ResearchStatus, ResearchUpdateStatus, UserRole } from 'oa-shared' -import type { IResearch, IUserPPDB } from 'src/models' +import type { IResearch, IUserDB } from 'src/models' export const researchUpdateStatusFilter = ( item: IResearch.Item, update: IResearch.Update, - currentUser?: IUserPPDB | null, + currentUser?: IUserDB | null, ) => { const isCollaborator = currentUser?._id && @@ -25,7 +25,7 @@ export const researchUpdateStatusFilter = ( export const getPublicUpdates = ( item: IResearch.Item, - currentUser?: IUserPPDB | null, + currentUser?: IUserDB | null, ) => { if (item.updates) { return item.updates.filter((update) => diff --git a/src/pages/User/content/MemberProfile.tsx b/src/pages/User/content/MemberProfile.tsx index 84caaacb62..21909c52d0 100644 --- a/src/pages/User/content/MemberProfile.tsx +++ b/src/pages/User/content/MemberProfile.tsx @@ -9,12 +9,12 @@ import { Avatar, Box, Card, Flex, Heading, Paragraph } from 'theme-ui' import UserContactAndLinks from './UserContactAndLinks' import UserCreatedDocuments from './UserCreatedDocuments' -import type { IUserPPDB } from 'src/models/userPreciousPlastic.models' +import type { IUserDB } from 'src/models' import type { UserCreatedDocs } from '../types' interface IProps { docs: UserCreatedDocs | undefined - user: IUserPPDB + user: IUserDB } export const MemberProfile = ({ docs, user }: IProps) => { diff --git a/src/pages/User/content/SpaceProfile.tsx b/src/pages/User/content/SpaceProfile.tsx index c6eb81ee34..83e5d73b61 100644 --- a/src/pages/User/content/SpaceProfile.tsx +++ b/src/pages/User/content/SpaceProfile.tsx @@ -41,13 +41,13 @@ import UserCreatedDocuments from './UserCreatedDocuments' import type { IMAchineBuilderXp, IOpeningHours, - IUserPP, PlasticTypeLabel, -} from 'src/models/userPreciousPlastic.models' +} from 'oa-shared' +import type { IUser } from 'src/models' import type { UserCreatedDocs } from '../types' interface IProps { - user: IUserPP + user: IUser docs: UserCreatedDocs | undefined } @@ -135,7 +135,7 @@ const renderMachineBuilderXp = (machineBuilderXp: IMAchineBuilderXp[]) => ( ) -const getCoverImages = (user: IUserPP) => { +const getCoverImages = (user: IUser) => { if (user.coverImages && user.coverImages.length) { return user.coverImages } diff --git a/src/pages/User/content/UserProfile.tsx b/src/pages/User/content/UserProfile.tsx index 5a99f1ecae..a7b440b884 100644 --- a/src/pages/User/content/UserProfile.tsx +++ b/src/pages/User/content/UserProfile.tsx @@ -11,7 +11,7 @@ import { logger } from '../../../logger' import { MemberProfile } from './MemberProfile' import { SpaceProfile } from './SpaceProfile' -import type { IUserPPDB } from 'src/models' +import type { IUserDB } from 'src/models' import type { UserCreatedDocs } from '../types' /** @@ -21,7 +21,7 @@ import type { UserCreatedDocs } from '../types' export const UserProfile = observer(() => { const { id } = useParams() const { userStore } = useCommonStores().stores - const [user, setUser] = useState() + const [user, setUser] = useState() const [userCreatedDocs, setUserCreatedDocs] = useState< UserCreatedDocs | undefined >() @@ -34,7 +34,7 @@ export const UserProfile = observer(() => { const fetchUserData = async () => { try { const userData = await userStore.getUserProfile(userId) - if (userData as IUserPPDB) { + if (userData as IUserDB) { setUser(userData) seoTagsUpdate({ diff --git a/src/pages/User/impact/Impact.tsx b/src/pages/User/impact/Impact.tsx index 1b21789275..a916b8101d 100644 --- a/src/pages/User/impact/Impact.tsx +++ b/src/pages/User/impact/Impact.tsx @@ -3,11 +3,11 @@ import { Flex } from 'theme-ui' import { IMPACT_YEARS } from './constants' import { ImpactItem } from './ImpactItem' -import type { IUserImpact, IUserPP } from 'src/models' +import type { IUser, IUserImpact } from 'src/models' interface Props { impact: IUserImpact | undefined - user: IUserPP | undefined + user: IUser | undefined } export const Impact = (props: Props) => { diff --git a/src/pages/User/impact/ImpactItem.tsx b/src/pages/User/impact/ImpactItem.tsx index 8f3c173a87..dc3eb84658 100644 --- a/src/pages/User/impact/ImpactItem.tsx +++ b/src/pages/User/impact/ImpactItem.tsx @@ -4,12 +4,12 @@ import { Box, Heading } from 'theme-ui' import { ImpactField } from './ImpactField' import { ImpactMissing } from './ImpactMissing' -import type { IImpactYear, IImpactYearFieldList, IUserPP } from 'src/models' +import type { IImpactYear, IImpactYearFieldList, IUser } from 'src/models' interface Props { year: IImpactYear fields: IImpactYearFieldList | undefined - user: IUserPP | undefined + user: IUser | undefined } export const ImpactItem = ({ fields, user, year }: Props) => { diff --git a/src/pages/User/impact/ImpactMissing.tsx b/src/pages/User/impact/ImpactMissing.tsx index e3ff29c3f3..c71f7ae5fe 100644 --- a/src/pages/User/impact/ImpactMissing.tsx +++ b/src/pages/User/impact/ImpactMissing.tsx @@ -7,11 +7,11 @@ import { Flex, Text } from 'theme-ui' import { IMPACT_REPORT_LINKS } from './constants' import { invisible, missing, reportYearLabel } from './labels' -import type { IImpactYear, IImpactYearFieldList, IUserPP } from 'src/models' +import type { IImpactYear, IImpactYearFieldList, IUser } from 'src/models' interface Props { fields: IImpactYearFieldList | undefined - owner: IUserPP | undefined + owner: IUser | undefined visibleFields: IImpactYearFieldList | undefined year: IImpactYear } diff --git a/src/pages/UserSettings/SettingsPageMapPin.tsx b/src/pages/UserSettings/SettingsPageMapPin.tsx index 8d6712cf32..4c1b3945e4 100644 --- a/src/pages/UserSettings/SettingsPageMapPin.tsx +++ b/src/pages/UserSettings/SettingsPageMapPin.tsx @@ -25,7 +25,7 @@ import { Alert, Box, Flex, Heading, Text } from 'theme-ui' import { SettingsFormNotifications } from './content/SettingsFormNotifications' import { MAX_PIN_LENGTH } from './constants' -import type { ILocation, IMapPin, IUserPPDB } from 'src/models' +import type { ILocation, IMapPin, IUserDB } from 'src/models' import type { IFormNotification } from './content/SettingsFormNotifications' interface IPinProps { @@ -33,7 +33,7 @@ interface IPinProps { } interface ILocationProps { - location: IUserPPDB['location'] + location: IUserDB['location'] } const LocationDataTextDisplay = ({ location }: ILocationProps) => { @@ -88,7 +88,7 @@ const MapPinModerationComments = ({ mapPin }: IPinProps) => { interface IPropsDeletePin { setIsLoading: (arg: boolean) => void setNotification: (arg: IFormNotification) => void - user: IUserPPDB + user: IUserDB } const DeleteMapPin = (props: IPropsDeletePin) => { diff --git a/src/pages/UserSettings/SettingsPageUserProfile.tsx b/src/pages/UserSettings/SettingsPageUserProfile.tsx index f232cccc69..d0c1bc5861 100644 --- a/src/pages/UserSettings/SettingsPageUserProfile.tsx +++ b/src/pages/UserSettings/SettingsPageUserProfile.tsx @@ -22,7 +22,7 @@ import { SettingsFormNotifications } from './content/SettingsFormNotifications' import { DEFAULT_PUBLIC_CONTACT_PREFERENCE } from './constants' import { buttons } from './labels' -import type { IUserPP } from 'src/models/userPreciousPlastic.models' +import type { IUser } from 'src/models' import type { IFormNotification } from './content/SettingsFormNotifications' export const SettingsPageUserProfile = () => { @@ -34,7 +34,7 @@ export const SettingsPageUserProfile = () => { const { userStore } = useCommonStores().stores const user = userStore.activeUser - const saveProfile = async (values: IUserPP) => { + const saveProfile = async (values: IUser) => { if (!user) return setIsLoading(true) @@ -72,7 +72,7 @@ export const SettingsPageUserProfile = () => { setIsLoading(false) } - const validateForm = (v: IUserPP) => { + const validateForm = (v: IUser) => { const errors: any = {} // must have at least 1 cover (awkard react final form array format) if (!v.coverImages[0] && v.profileType !== ProfileTypeList.MEMBER) { diff --git a/src/pages/UserSettings/__mocks__/FormProvider.tsx b/src/pages/UserSettings/__mocks__/FormProvider.tsx index dc0691d779..827461d149 100644 --- a/src/pages/UserSettings/__mocks__/FormProvider.tsx +++ b/src/pages/UserSettings/__mocks__/FormProvider.tsx @@ -11,12 +11,12 @@ import { useCommonStores } from 'src/common/hooks/useCommonStores' import { testingThemeStyles } from 'src/test/utils/themeUtils' import { vi } from 'vitest' -import type { IUserPPDB } from 'src/models' +import type { IUserDB } from 'src/models' const Theme = testingThemeStyles export const FormProvider = ( - user: IUserPPDB, + user: IUserDB, element: React.ReactNode, routerInitialEntry?: string, ) => { diff --git a/src/pages/UserSettings/content/fields/PatreonIntegration.test.tsx b/src/pages/UserSettings/content/fields/PatreonIntegration.test.tsx index 36ffc9905d..94f2ffb6ed 100644 --- a/src/pages/UserSettings/content/fields/PatreonIntegration.test.tsx +++ b/src/pages/UserSettings/content/fields/PatreonIntegration.test.tsx @@ -13,11 +13,11 @@ import { UPDATE_BUTTON_TEXT, } from './PatreonIntegration' -import type { IUserPP } from 'src/models' +import type { IUser } from 'src/models' const mockUser = { userName: 'test', -} as IUserPP +} as IUser const MOCK_PATREON_TIER_TITLE = 'Patreon Tier Title' const mockPatreonSupporter = { @@ -41,7 +41,7 @@ const mockPatreonSupporter = { ], }, }, -} as unknown as IUserPP +} as unknown as IUser const WRONG_PATREON_TIER_TITLE = 'Wrong Patreon Tier Title' const mockPatreonNotSupporter = { @@ -66,7 +66,7 @@ const mockPatreonNotSupporter = { tiers: [], }, }, -} as unknown as IUserPP +} as unknown as IUser const mockRemovePatreonConnection = vi.fn() diff --git a/src/pages/UserSettings/content/sections/Collection.section.tsx b/src/pages/UserSettings/content/sections/Collection.section.tsx index 03bac93874..f202674a14 100644 --- a/src/pages/UserSettings/content/sections/Collection.section.tsx +++ b/src/pages/UserSettings/content/sections/Collection.section.tsx @@ -15,11 +15,11 @@ import { FlexSectionContainer } from '../elements' import { CustomCheckbox } from '../fields/CustomCheckbox.field' import { OpeningHoursPicker } from '../fields/OpeningHoursPicker.field' -import type { IPlasticType } from 'src/models' -import type { IUserPP } from 'src/models/userPreciousPlastic.models' +import type { IPlasticType } from 'oa-shared' +import type { IUser } from 'src/models' interface IProps { - formValues: IUserPP + formValues: IUser required: boolean } diff --git a/src/pages/UserSettings/content/sections/Expertise.section.tsx b/src/pages/UserSettings/content/sections/Expertise.section.tsx index 368844c7fa..b4e1594b5d 100644 --- a/src/pages/UserSettings/content/sections/Expertise.section.tsx +++ b/src/pages/UserSettings/content/sections/Expertise.section.tsx @@ -5,7 +5,7 @@ import { Flex, Heading, Text } from 'theme-ui' import { FlexSectionContainer } from '../elements' import { CustomCheckbox } from '../fields/CustomCheckbox.field' -import type { IMAchineBuilderXp } from 'src/models' +import type { IMAchineBuilderXp } from 'oa-shared' interface IProps { required: boolean diff --git a/src/pages/UserSettings/content/sections/UserInfos.section.tsx b/src/pages/UserSettings/content/sections/UserInfos.section.tsx index 2315389ade..950e06e0fb 100644 --- a/src/pages/UserSettings/content/sections/UserInfos.section.tsx +++ b/src/pages/UserSettings/content/sections/UserInfos.section.tsx @@ -16,10 +16,10 @@ import { import { FlexSectionContainer } from '../elements' import { ProfileLinkField } from '../fields/ProfileLink.field' -import type { IUserPP } from 'src/models/userPreciousPlastic.models' +import type { IUser } from 'src/models' interface IProps { - formValues: Partial + formValues: Partial } export const UserInfosSection = ({ formValues }: IProps) => { diff --git a/src/pages/UserSettings/content/sections/Workspace.section.tsx b/src/pages/UserSettings/content/sections/Workspace.section.tsx index a8289c1c02..91a4db600e 100644 --- a/src/pages/UserSettings/content/sections/Workspace.section.tsx +++ b/src/pages/UserSettings/content/sections/Workspace.section.tsx @@ -11,7 +11,7 @@ import { Flex, Grid, Heading, Text } from 'theme-ui' import { FlexSectionContainer } from '../elements' import { CustomRadioField } from '../fields/CustomRadio.field' -import type { IWorkspaceType } from 'src/models' +import type { IWorkspaceType } from 'oa-shared' const WORKSPACE_TYPES: IWorkspaceType[] = [ { diff --git a/src/pages/common/UserNameSelect/LoadUserNameOptions.tsx b/src/pages/common/UserNameSelect/LoadUserNameOptions.tsx index 23b2af0e60..9b6ef4063d 100644 --- a/src/pages/common/UserNameSelect/LoadUserNameOptions.tsx +++ b/src/pages/common/UserNameSelect/LoadUserNameOptions.tsx @@ -1,4 +1,4 @@ -import type { IUserPP } from 'src/models' +import type { IUser } from 'src/models' import type { UserStore } from 'src/stores/User/user.store' const USER_RESULTS_LIMIT = 20 @@ -25,7 +25,7 @@ export const loadUserNameOptions = async ( : [] return selectOptions } else { - const usersStartingWithInput: IUserPP[] = + const usersStartingWithInput: IUser[] = await userStore.getUsersStartingWith(inputValue, USER_RESULTS_LIMIT) const selectOptions: IOption[] = usersStartingWithInput.map((user) => ({ value: user.userName, diff --git a/src/stores/Discussions/discussion.store.test.ts b/src/stores/Discussions/discussion.store.test.ts index 51de029f0d..6cef27c7e8 100644 --- a/src/stores/Discussions/discussion.store.test.ts +++ b/src/stores/Discussions/discussion.store.test.ts @@ -10,12 +10,12 @@ import { FactoryUser } from 'src/test/factories/User' import { DiscussionStore } from './discussions.store' -import type { IDiscussion, IUserPPDB } from 'src/models' +import type { IDiscussion, IUserDB } from 'src/models' import type { IRootStore } from '../RootStore' const factory = ( discussions: IDiscussion[] = [FactoryDiscussion({})], - activeUser: IUserPPDB = FactoryUser(), + activeUser: IUserDB = FactoryUser(), ) => { const store = new DiscussionStore({} as IRootStore) diff --git a/src/stores/Discussions/discussions.store.tsx b/src/stores/Discussions/discussions.store.tsx index 3cc1c478a9..98f9751f85 100644 --- a/src/stores/Discussions/discussions.store.tsx +++ b/src/stores/Discussions/discussions.store.tsx @@ -13,7 +13,7 @@ import { changeUserReferenceToPlainText } from '../common/mentions' import { ModuleStore } from '../common/module.store' import { getCollectionName, updateDiscussionMetadata } from './discussionEvents' -import type { IResearch, IUserPPDB } from 'src/models' +import type { IResearch, IUserDB } from 'src/models' import type { IComment, IDiscussion, @@ -310,7 +310,7 @@ export class DiscussionStore extends ModuleStore { } private _findAndUpdateComment( - user: IUserPPDB, + user: IUserDB, comments: IComment[], newCommentText: string, commentId: string, @@ -410,7 +410,7 @@ export class DiscussionStore extends ModuleStore { } private _findAndDeleteComment( - user: IUserPPDB, + user: IUserDB, comments: IComment[], commentId: string, ) { @@ -425,7 +425,7 @@ export class DiscussionStore extends ModuleStore { }) } - private _getUserAvatar(user: IUserPPDB) { + private _getUserAvatar(user: IUserDB) { if (user.userImage && user.userImage.downloadUrl) { return cdnImageUrl(user.userImage.downloadUrl, { width: 100 }) } diff --git a/src/stores/Maps/maps.store.test.ts b/src/stores/Maps/maps.store.test.ts index 97677ba9ff..5a4769a5e6 100644 --- a/src/stores/Maps/maps.store.test.ts +++ b/src/stores/Maps/maps.store.test.ts @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { MapsStore } from './maps.store' -import type { IUserPPDB } from 'src/models' +import type { IUserDB } from 'src/models' vi.mock('../common/module.store') @@ -205,7 +205,7 @@ describe('maps.store', () => { // Act await store.deleteUserPin({ userName: 'existing', - } as IUserPPDB) + } as IUserDB) // Assert expect(store.db.update).toHaveBeenCalledWith( diff --git a/src/stores/Maps/maps.store.ts b/src/stores/Maps/maps.store.ts index 2958bb26fa..9ba01743e2 100644 --- a/src/stores/Maps/maps.store.ts +++ b/src/stores/Maps/maps.store.ts @@ -14,14 +14,13 @@ import { filterMapPinsByType } from './filter' import { MAP_GROUPINGS } from './maps.groupings' import type { IMapPinDetail, ProfileTypeName } from 'oa-shared' -import type { IUser } from 'src/models' +import type { IUser, IUserDB } from 'src/models' import type { IDBEndpoint } from 'src/models/dbEndpoints' import type { IMapGrouping, IMapPin, IMapPinWithDetail, } from 'src/models/maps.models' -import type { IUserPP, IUserPPDB } from 'src/models/userPreciousPlastic.models' import type { IRootStore } from '../RootStore' import type { IUploadedFileMeta } from '../storage' @@ -167,7 +166,7 @@ export class MapsStore extends ModuleStore { ) } - public async setUserPin(user: IUserPPDB) { + public async setUserPin(user: IUserDB) { const { _id, _lastActive, @@ -245,7 +244,7 @@ export class MapsStore extends ModuleStore { await this.db.collection(COLLECTION_NAME).doc(pin._id).set(pin) } - public async deleteUserPin(user: IUserPP) { + public async deleteUserPin(user: IUser) { const pin = await this.getPin(user.userName, 'server') logger.debug('marking user pin deleted', pin) diff --git a/src/stores/Research/researchEvents.ts b/src/stores/Research/researchEvents.ts index 43c6523a87..c7ec1e4226 100644 --- a/src/stores/Research/researchEvents.ts +++ b/src/stores/Research/researchEvents.ts @@ -1,6 +1,7 @@ import { toJS } from 'mobx' -import type { IUserDB, UserRole } from 'src/models' +import type { UserRole } from 'oa-shared' +import type { IUserDB } from 'src/models' import type { DatabaseV2 } from '../databaseV2/DatabaseV2' export const setCollaboratorPermission = async ( diff --git a/src/stores/User/notifications.store.ts b/src/stores/User/notifications.store.ts index 8e2736bf79..f5e26fbdc0 100644 --- a/src/stores/User/notifications.store.ts +++ b/src/stores/User/notifications.store.ts @@ -7,8 +7,12 @@ import { ModuleStore } from '../common/module.store' import { COLLECTION_NAME as USER_COLLECTION_NAME } from './user.store' import type { NotificationType } from 'oa-shared' -import type { INotification, INotificationUpdate } from 'src/models/user.models' -import type { IUserPP, IUserPPDB } from 'src/models/userPreciousPlastic.models' +import type { + INotification, + INotificationUpdate, + IUser, + IUserDB, +} from 'src/models' import type { IRootStore } from '../RootStore' export class UserNotificationsStore extends ModuleStore { @@ -83,7 +87,7 @@ export class UserNotificationsStore extends ModuleStore { } const lookup = await this.db - .collection(USER_COLLECTION_NAME) + .collection(USER_COLLECTION_NAME) .getWhere('_id', '==', userId) const user = lookup[0] @@ -156,7 +160,7 @@ export class UserNotificationsStore extends ModuleStore { } } - private async _updateUserNotifications(user: IUserPPDB, notifications) { + private async _updateUserNotifications(user: IUserDB, notifications) { const dbRef = this.db .collection(USER_COLLECTION_NAME) .doc(user.userName) diff --git a/src/stores/User/user.store.test.tsx b/src/stores/User/user.store.test.tsx index af087ce71c..d8d55a39a9 100644 --- a/src/stores/User/user.store.test.tsx +++ b/src/stores/User/user.store.test.tsx @@ -104,7 +104,6 @@ describe('userStore', () => { userName: expect.any(String), profileType: expect.any(String), displayName: expect.any(String), - moderation: expect.any(String), coverImages: expect.any(Array), links: expect.any(Array), notifications: expect.any(Array), @@ -145,7 +144,6 @@ describe('userStore', () => { userName: expect.any(String), profileType: expect.any(String), displayName: expect.any(String), - moderation: expect.any(String), coverImages: expect.any(Array), links: expect.any(Array), notifications: expect.any(Array), @@ -180,7 +178,6 @@ describe('userStore', () => { userName: expect.any(String), profileType: expect.any(String), displayName: expect.any(String), - moderation: expect.any(String), coverImages: expect.any(Array), links: expect.any(Array), notifications: expect.any(Array), @@ -533,12 +530,12 @@ describe('userStore', () => { expect(store.db.set).toHaveBeenCalledWith({ coverImages: [], links: [], - moderation: IModerationStatus.AWAITING_MODERATION, verified: false, _authID: 'testUid', displayName: 'testDisplayName', userName: 'testdisplayname', notifications: [], + profileType: 'member', profileCreated: expect.any(String), profileCreationTrigger: 'registration', notification_settings: { diff --git a/src/stores/User/user.store.ts b/src/stores/User/user.store.ts index 2809370b58..dac5732fc6 100644 --- a/src/stores/User/user.store.ts +++ b/src/stores/User/user.store.ts @@ -26,8 +26,8 @@ import type { IImpactYearFieldList, IUser, IUserBadges, -} from 'src/models/user.models' -import type { IUserPP, IUserPPDB } from 'src/models/userPreciousPlastic.models' + IUserDB, +} from 'src/models' import type { IFirebaseUser } from 'src/utils/firebase' import type { IConvertedFileMeta } from '../../types' import type { IRootStore } from '../RootStore' @@ -37,12 +37,12 @@ The user store listens to login events through the firebase api and exposes logg export const COLLECTION_NAME = 'users' -type PartialUser = Partial +type PartialUser = Partial export class UserStore extends ModuleStore { private authUnsubscribe: firebase.default.Unsubscribe - public user: IUserPPDB | null | undefined = null + public user: IUserDB | null | undefined = null public authUser: User | null = null // TODO: Fix type public updateStatus: IUserUpdateStatus = getInitialUpdateStatus() @@ -63,10 +63,10 @@ export class UserStore extends ModuleStore { public async getUsersStartingWith(prefix: string, limit?: number) { // getWhere with the '>=' operator will return every userName that is lexicographically greater than prefix, so adding filter to avoid getting not relvant userNames - const users: IUserPP[] = await this.db - .collection(COLLECTION_NAME) + const users: IUser[] = await this.db + .collection(COLLECTION_NAME) .getWhere('userName', '>=', prefix, limit) - const uniqueUsers: IUserPP[] = uniqBy( + const uniqueUsers: IUser[] = uniqBy( users.filter((user) => user.userName?.startsWith(prefix)), (user) => user.userName, ) @@ -118,9 +118,9 @@ export class UserStore extends ModuleStore { return signInWithEmailAndPassword(auth, email, password) } - public async getUserByUsername(username: string): Promise { + public async getUserByUsername(username: string): Promise { const [user] = await this.db - .collection(COLLECTION_NAME) + .collection(COLLECTION_NAME) .getWhere('_id', '==', username) return user || null } @@ -132,7 +132,7 @@ export class UserStore extends ModuleStore { // which could then be used as a single lookup public async getUserProfile(_authID: string) { const lookup = await this.db - .collection(COLLECTION_NAME) + .collection(COLLECTION_NAME) .getWhere('_authID', '==', _authID) if (lookup.length === 1) { @@ -148,7 +148,7 @@ export class UserStore extends ModuleStore { } const lookup2 = await this.db - .collection(COLLECTION_NAME) + .collection(COLLECTION_NAME) .getWhere('_id', '==', _authID) return lookup2[0] @@ -319,7 +319,7 @@ export class UserStore extends ModuleStore { if (!this.activeUser) return const user = await this.db - .collection(COLLECTION_NAME) + .collection(COLLECTION_NAME) .doc(this.activeUser._id) .get('server') @@ -429,7 +429,6 @@ export class UserStore extends ModuleStore { const user: IUser = { coverImages: [], links: [], - moderation: IModerationStatus.AWAITING_MODERATION, verified: false, _authID: authUser.uid, displayName, @@ -437,6 +436,7 @@ export class UserStore extends ModuleStore { notifications: [], profileCreated: new Date().toISOString(), profileCreationTrigger: trigger, + profileType: 'member', notification_settings: { emailFrequency: EmailNotificationFrequency.WEEKLY, }, @@ -481,7 +481,7 @@ export class UserStore extends ModuleStore { this.authUnsubscribe() } - public _updateActiveUser(user?: IUserPPDB | null) { + public _updateActiveUser(user?: IUserDB | null) { this.user = user } } diff --git a/src/test/factories/User.ts b/src/test/factories/User.ts index d73fbb5975..d663ef02c3 100644 --- a/src/test/factories/User.ts +++ b/src/test/factories/User.ts @@ -1,11 +1,7 @@ import { faker } from '@faker-js/faker' -import { - ExternalLinkLabel, - IModerationStatus, - ProfileTypeList, -} from 'oa-shared' +import { ExternalLinkLabel, ProfileTypeList } from 'oa-shared' -import type { IExternalLink, IUserPPDB } from 'src/models' +import type { IExternalLink, IUserDB } from 'src/models' import type { IUploadedFileMeta } from 'src/stores/storage' export const factoryImage: IUploadedFileMeta = { @@ -26,9 +22,7 @@ export const factoryLink: IExternalLink = { label: ExternalLinkLabel.WEBSITE, } -export const FactoryUser = ( - userOverloads: Partial = {}, -): IUserPPDB => ({ +export const FactoryUser = (userOverloads: Partial = {}): IUserDB => ({ _id: faker.string.uuid(), _created: faker.date.past().toString(), _modified: faker.date.past().toString(), @@ -40,12 +34,6 @@ export const FactoryUser = ( displayName: faker.person.fullName(), verified: faker.datatype.boolean(), links: [], - moderation: faker.helpers.arrayElement([ - IModerationStatus.DRAFT, - IModerationStatus.AWAITING_MODERATION, - IModerationStatus.REJECTED, - IModerationStatus.ACCEPTED, - ]), country: faker.location.countryCode(), notifications: [], coverImages: [] as any[], diff --git a/src/utils/getUserCountry.test.ts b/src/utils/getUserCountry.test.ts index f7f1d8aafc..d82527a498 100644 --- a/src/utils/getUserCountry.test.ts +++ b/src/utils/getUserCountry.test.ts @@ -1,4 +1,3 @@ -import { IModerationStatus } from 'oa-shared' import { describe, expect, it } from 'vitest' import { getUserCountry } from './getUserCountry' @@ -9,12 +8,10 @@ const user: IUser = { _authID: '1', userName: 'testUser', displayName: 'test user', - moderation: IModerationStatus.ACCEPTED, - verified: true, coverImages: [], + profileType: 'member', links: [], - country: null, location: null, } diff --git a/src/utils/isProfileComplete.ts b/src/utils/isProfileComplete.ts index af2b59727a..488ea46aa6 100644 --- a/src/utils/isProfileComplete.ts +++ b/src/utils/isProfileComplete.ts @@ -1,12 +1,12 @@ import { ProfileTypeList } from 'oa-shared' -import type { IUserPPDB } from 'src/models' +import type { IUserDB } from 'src/models' const nonMemberProfileTypes = Object.values(ProfileTypeList).filter( (type) => type !== 'member', ) -export const isProfileComplete = (user: IUserPPDB): boolean => { +export const isProfileComplete = (user: IUserDB): boolean => { const { about, coverImages, displayName, links, userImage } = user const isBasicInfoFilled = !!(about && displayName && links?.length !== 0)