wacrm/src/lib/auth/beta-features.ts
Arnas Donauskas 873ba1f4c6
feat(inbox): gate media send behind 'chat_media' beta flag
Media sends (photo/video/document/voice) are now opt-in per account via
the existing profiles.beta_features mechanism (migration 011 — same
pattern used to dogfood Flows before GA). No migration needed.

- New shared `hasBetaFeature` + `CHAT_MEDIA_BETA` ('chat_media') key, and
  a `useBetaFeature` client hook mirroring useCan (false while loading).
- Composer: the attach menu, hidden file inputs, and voice recorder only
  render when the flag is on; the hint indent reverts so non-beta users
  see the unchanged text + template composer.
- Server: the send route rejects media kinds (403) and the transcode
  route rejects (403) unless the caller's profile is opted in — so the
  endpoints can't be driven ahead of the rollout. Text + templates stay
  available to everyone.

Enable for one account via Supabase Studio:
  UPDATE profiles SET beta_features = array_append(beta_features, 'chat_media')
  WHERE email = 'someone@example.com';

Tests: hasBetaFeature null-safety + membership (419 total). typecheck/lint/build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:44:02 +03:00

32 lines
1.1 KiB
TypeScript

/**
* Beta feature flags, stored per-profile in `profiles.beta_features`
* (a `TEXT[]`, migration 011). A feature is "on" for an account when its
* key is present in that array; the owner opts in by editing the column
* in Supabase Studio:
*
* UPDATE profiles
* SET beta_features = array_append(beta_features, 'chat_media')
* WHERE email = 'someone@example.com';
*
* This module is the single source of truth for the keys + the membership
* check so the client hook (`useBetaFeature`) and the server routes gate
* on exactly the same logic.
*/
/** Agent-initiated media sends in the inbox composer (issue #213). */
export const CHAT_MEDIA_BETA = "chat_media";
export type BetaFeature = typeof CHAT_MEDIA_BETA;
/**
* Whether an opted-in feature list contains `feature`. Pure + null-safe so
* both the client (reading `profile.beta_features`) and the server
* (reading the row from Supabase) share one implementation.
*/
export function hasBetaFeature(
betaFeatures: readonly string[] | null | undefined,
feature: BetaFeature,
): boolean {
return betaFeatures?.includes(feature) ?? false;
}