consent skip feature

This commit is contained in:
Igor Loskutov
2025-12-19 16:56:31 -05:00
parent 3929a80665
commit 15afd57ed9
11 changed files with 152 additions and 86 deletions

View File

@@ -5,6 +5,7 @@ import { useError } from "../(errors)/errorContext";
import { QueryClient, useQueryClient } from "@tanstack/react-query";
import type { components } from "../reflector-api";
import { useAuth } from "./AuthProvider";
import { MeetingId } from "./types";
/*
* XXX error types returned from the hooks are not always correct; declared types are ValidationError but real type could be string or any other
@@ -718,7 +719,7 @@ export function useRoomActiveMeetings(roomName: string | null) {
export function useRoomGetMeeting(
roomName: string | null,
meetingId: string | null,
meetingId: MeetingId | null,
) {
return $api.useQuery(
"get",

View File

@@ -9,16 +9,26 @@ import {
CONSENT_BUTTON_Z_INDEX,
CONSENT_DIALOG_TEXT,
} from "./constants";
import { MeetingId } from "../types";
import type { components } from "../../reflector-api";
interface ConsentDialogButtonProps {
meetingId: string;
}
type Meeting = components["schemas"]["Meeting"];
export function ConsentDialogButton({ meetingId }: ConsentDialogButtonProps) {
const { showConsentModal, consentState, hasAnswered, consentLoading } =
useConsentDialog(meetingId);
type ConsentDialogButtonProps = {
meetingId: MeetingId;
recordingType: Meeting["recording_type"];
skipConsent: boolean;
};
if (!consentState.ready || hasAnswered(meetingId) || consentLoading) {
export function ConsentDialogButton({
meetingId,
recordingType,
skipConsent,
}: ConsentDialogButtonProps) {
const { showConsentModal, consentState, showConsentButton, consentLoading } =
useConsentDialog({ meetingId, recordingType, skipConsent });
if (!consentState.ready || !showConsentButton || consentLoading) {
return null;
}

View File

@@ -1,10 +1,14 @@
export interface ConsentDialogResult {
import { MeetingId } from "../types";
export type ConsentDialogResult = {
showConsentModal: () => void;
consentState: {
ready: boolean;
consentForMeetings?: Map<string, boolean>;
consentForMeetings?: Map<MeetingId, boolean>;
};
hasAnswered: (meetingId: string) => boolean;
hasAccepted: (meetingId: string) => boolean;
hasAnswered: (meetingId: MeetingId) => boolean;
hasAccepted: (meetingId: MeetingId) => boolean;
consentLoading: boolean;
}
showRecordingIndicator: boolean;
showConsentButton: boolean;
};

View File

@@ -7,8 +7,23 @@ import { useMeetingAudioConsent } from "../apiHooks";
import { ConsentDialog } from "./ConsentDialog";
import { TOAST_CHECK_INTERVAL_MS } from "./constants";
import type { ConsentDialogResult } from "./types";
import { MeetingId } from "../types";
import { recordingTypeRequiresConsent } from "./utils";
import type { components } from "../../reflector-api";
export function useConsentDialog(meetingId: string): ConsentDialogResult {
type Meeting = components["schemas"]["Meeting"];
type UseConsentDialogParams = {
meetingId: MeetingId;
recordingType: Meeting["recording_type"];
skipConsent: boolean;
};
export function useConsentDialog({
meetingId,
recordingType,
skipConsent,
}: UseConsentDialogParams): ConsentDialogResult {
const {
state: consentState,
touch,
@@ -105,11 +120,23 @@ export function useConsentDialog(meetingId: string): ConsentDialogResult {
});
}, [handleConsent, modalOpen]);
const requiresConsent = Boolean(
recordingType && recordingTypeRequiresConsent(recordingType),
);
const showRecordingIndicator =
requiresConsent && (skipConsent || hasAccepted(meetingId));
const showConsentButton =
requiresConsent && !skipConsent && !hasAnswered(meetingId);
return {
showConsentModal,
consentState,
hasAnswered,
hasAccepted,
consentLoading: audioConsentMutation.isPending,
showRecordingIndicator,
showConsentButton,
};
}

View File

@@ -1,6 +1,10 @@
import type { Session } from "next-auth";
import type { JWT } from "next-auth/jwt";
import { parseMaybeNonEmptyString } from "./utils";
import {
assertExistsAndNonEmptyString,
NonEmptyString,
parseMaybeNonEmptyString,
} from "./utils";
export interface JWTWithAccessToken extends JWT {
accessToken: string;
@@ -78,3 +82,10 @@ export const assertCustomSession = <T extends Session>(
export type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
export type MeetingId = NonEmptyString & { __type: "MeetingId" };
export const assertMeetingId = (s: string): MeetingId => {
const nes = assertExistsAndNonEmptyString(s);
// just cast for now
return nes as MeetingId;
};