mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
B153 — simplify: phone assert helper, displayValue interno
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
This commit is contained in:
parent
bb4c1ab199
commit
607eecd12b
6 changed files with 51 additions and 61 deletions
|
|
@ -64,6 +64,11 @@ flowchart LR
|
|||
- **Refresh durante digitação:** guard `document.activeElement === inputRef` (precedente AdvisorDebouncedTextCell).
|
||||
- **Revalidação lista:** `router.refresh()` no cliente; `revalidatePath` só no detalhe (chip writes não revalidam lista).
|
||||
|
||||
## Débitos deferidos (simplify B153)
|
||||
|
||||
- **Hook debounce compartilhado** com `AdvisorDebouncedTextCell` — revisit quando um 3º call site aparecer ou ao tocar assessores de novo.
|
||||
- **`nullableBrazilianMobileInput` em primitives** — duplicação com advisor schema; extrair no próximo touch de phone schemas.
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [x] Aceite de produto da intenção ainda coberto
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ import {
|
|||
resolvedPortfolioEntriesById,
|
||||
type MunicipalityPortfolioIndexEntry,
|
||||
} from '@/lib/municipalityPortfolio'
|
||||
import { formatBrazilianPhoneInput, whatsAppHrefForPhone } from '@/lib/phone'
|
||||
import { whatsAppHrefForPhone } from '@/lib/phone'
|
||||
import { MAX_LEADERSHIP_MUNICIPALITIES } from '@/lib/schemas/leadership'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { getAdvisorMunicipalityIds } from '@/utilities/campaignAccess'
|
||||
|
|
@ -163,7 +163,6 @@ const leadershipColumns = ({
|
|||
leadershipId={row.id}
|
||||
field="phone"
|
||||
value={row.phone}
|
||||
displayValue={row.phone ? formatBrazilianPhoneInput(row.phone) : undefined}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -46,6 +46,40 @@ const getFreshStaffActor = (
|
|||
req?: PayloadTransactionRequest,
|
||||
): Promise<CampaignUser> => reloadStaffActor(payload, actor, LEADERSHIP_STAFF_MESSAGE, req)
|
||||
|
||||
const assertLeadershipContactPhoneWritable = async (
|
||||
payload: Payload,
|
||||
req: PayloadTransactionRequest,
|
||||
contactID: number,
|
||||
phone: string,
|
||||
) => {
|
||||
if (payload.db.name !== 'postgres') {
|
||||
throw new Error(POSTGRES_DEDUP_LOCK_MESSAGE)
|
||||
}
|
||||
|
||||
await acquireContactPhoneLocks(payload, req, [phone])
|
||||
|
||||
// Intentional admin bypass: staff scope was checked on the leadership; phone
|
||||
// uniqueness must see every contact with the number, not only visible rows.
|
||||
const contactsWithPhone = await payload.find({
|
||||
collection: 'contact',
|
||||
where: { phone: { equals: phone } },
|
||||
depth: 0,
|
||||
limit: 2,
|
||||
pagination: false,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
})
|
||||
|
||||
if (contactsWithPhone.totalDocs > 1) {
|
||||
throw new Error(CONTACT_PHONE_AMBIGUOUS_MESSAGE)
|
||||
}
|
||||
|
||||
const phoneOwner = contactsWithPhone.docs[0]
|
||||
if (phoneOwner && phoneOwner.id !== contactID) {
|
||||
await assertContactPhoneAvailable(payload, req, phone, contactID)
|
||||
}
|
||||
}
|
||||
|
||||
/** Advisors may only link leaderships to municipalities they administer. */
|
||||
const assertMunicipalitiesWithinScope = async (
|
||||
payload: Payload,
|
||||
|
|
@ -274,37 +308,13 @@ export const updateLeadershipContactRecord = async (
|
|||
contactData.name = data.name
|
||||
} else if (data.field === 'email') {
|
||||
contactData.email = data.email ?? null
|
||||
} else if (data.phone) {
|
||||
if (payload.db.name !== 'postgres') {
|
||||
throw new Error(POSTGRES_DEDUP_LOCK_MESSAGE)
|
||||
} else if (data.field === 'phone') {
|
||||
if (data.phone) {
|
||||
await assertLeadershipContactPhoneWritable(payload, req, contactID, data.phone)
|
||||
contactData.phone = data.phone
|
||||
} else {
|
||||
contactData.phone = null
|
||||
}
|
||||
|
||||
await acquireContactPhoneLocks(payload, req, [data.phone])
|
||||
|
||||
// Intentional admin bypass: staff scope was checked on the leadership; phone
|
||||
// uniqueness must see every contact with the number, not only visible rows.
|
||||
const contactsWithPhone = await payload.find({
|
||||
collection: 'contact',
|
||||
where: { phone: { equals: data.phone } },
|
||||
depth: 0,
|
||||
limit: 2,
|
||||
pagination: false,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
})
|
||||
|
||||
if (contactsWithPhone.totalDocs > 1) {
|
||||
throw new Error(CONTACT_PHONE_AMBIGUOUS_MESSAGE)
|
||||
}
|
||||
|
||||
const phoneOwner = contactsWithPhone.docs[0]
|
||||
if (phoneOwner && phoneOwner.id !== contactID) {
|
||||
await assertContactPhoneAvailable(payload, req, data.phone, contactID)
|
||||
}
|
||||
|
||||
contactData.phone = data.phone
|
||||
} else {
|
||||
contactData.phone = null
|
||||
}
|
||||
|
||||
// bypass: contact write is staff-scoped via leadership access check above.
|
||||
|
|
@ -369,28 +379,7 @@ const updateLeadershipWizardRecord = async (
|
|||
throw new Error(LEADERSHIP_INVALID_CONTACT_MESSAGE)
|
||||
}
|
||||
|
||||
await acquireContactPhoneLocks(payload, req, [data.phone])
|
||||
|
||||
// Intentional admin bypass: staff scope was checked on the leadership; contact
|
||||
// PII is updated atomically with the leadership fields in this transaction.
|
||||
const contactsWithPhone = await payload.find({
|
||||
collection: 'contact',
|
||||
where: { phone: { equals: data.phone } },
|
||||
depth: 0,
|
||||
limit: 2,
|
||||
pagination: false,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
})
|
||||
|
||||
if (contactsWithPhone.totalDocs > 1) {
|
||||
throw new Error(CONTACT_PHONE_AMBIGUOUS_MESSAGE)
|
||||
}
|
||||
|
||||
const phoneOwner = contactsWithPhone.docs[0]
|
||||
if (phoneOwner && phoneOwner.id !== contactID) {
|
||||
await assertContactPhoneAvailable(payload, req, data.phone, contactID)
|
||||
}
|
||||
await assertLeadershipContactPhoneWritable(payload, req, contactID, data.phone)
|
||||
|
||||
// bypass: contact write is staff-scoped via leadership access check above.
|
||||
await payload.update({
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ type LeadershipContactFieldControlProps = {
|
|||
leadershipId: number
|
||||
field: LeadershipContactField
|
||||
value: string | null
|
||||
/** Preformatted display for phone in read mode. */
|
||||
displayValue?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
|
|
@ -48,10 +46,11 @@ export const LeadershipContactFieldControl = ({
|
|||
leadershipId,
|
||||
field,
|
||||
value,
|
||||
displayValue,
|
||||
className,
|
||||
}: LeadershipContactFieldControlProps) => {
|
||||
const label = fieldLabels[field]
|
||||
const phoneDisplayValue =
|
||||
field === 'phone' && value ? formatBrazilianPhoneInput(value) : undefined
|
||||
const router = useRouter()
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [draft, setDraft] = useState(value ?? '')
|
||||
|
|
@ -185,7 +184,7 @@ export const LeadershipContactFieldControl = ({
|
|||
<CampaignCopyableCell
|
||||
value={value}
|
||||
label={label}
|
||||
displayValue={displayValue}
|
||||
displayValue={phoneDisplayValue}
|
||||
className={field === 'phone' ? 'tabular-nums' : undefined}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import { LeadershipContactFieldControl } from '@/components/campaign/leadership/LeadershipContactFieldControl'
|
||||
import { formatBrazilianPhoneInput } from '@/lib/phone'
|
||||
|
||||
type LeadershipContactSectionProps = {
|
||||
leadershipId: number
|
||||
|
|
@ -40,7 +39,6 @@ export const LeadershipContactSection = ({
|
|||
leadershipId={leadershipId}
|
||||
field="phone"
|
||||
value={phone}
|
||||
displayValue={phone ? formatBrazilianPhoneInput(phone) : undefined}
|
||||
/>
|
||||
</dd>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -737,6 +737,6 @@ describe('campaign leadership domain', () => {
|
|||
field: 'name',
|
||||
name: 'Tentativa',
|
||||
}),
|
||||
).rejects.toThrow(OUT_OF_SCOPE_MUNICIPALITY_MESSAGE)
|
||||
).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue