| | bnk>bnk>import React from 'react';
bnk>import {
bnk> Dialog,
bnk> DialogType,
bnk> IDialogContentProps,
bnk> DialogFooter,
bnk> PrimaryButton,
bnk> DefaultButton,
bnk> Stack,
bnk> Text,
bnk> Icon,
bnk> Spinner,
bnk> SpinnerSize,
bnk> MessageBar,
bnk> MessageBarType,
bnk> Separator
bnk>} from '@fluentui/react';
bnk>import { AuthService } from 'services/AuthService';
bnk>import { TokenKind } from 'shared/TokenKind';
bnk>import { TextService } from 'services/TextService';
bnk>import strings from 'VistoWebPartStrings';
bnk>import { useTheme } from '@fluentui/react';
bnk>import { trackClient } from 'services/trackClient';
bnk>export interface IConsentDialogProps {
bnk> onDismiss: () => void;
bnk>}
bnk>interface IConsentItem {
bnk> key: string;
bnk> name: string;
bnk> description: string;
bnk> tokenKind: TokenKind;
bnk> domain?: string;
bnk> isGranted: boolean;
bnk> isChecking: boolean;
bnk> error?: string;
bnk>}
bnk>export const ConsentDialog: React.FC<IConsentDialogProps> = (props) => {
bnk> const theme = useTheme();
bnk> const [consentItems, setConsentItems] = React.useState<IConsentItem[]>([
bnk> {
bnk> key: 'default',
bnk> name: TextService.format(strings.ConsentDialog_Default),
bnk> description: TextService.format(strings.ConsentDialog_DefaultDescription),
bnk> tokenKind: TokenKind.dashboard,
bnk> domain: 'graph.microsoft.com',
bnk> isGranted: false,
bnk> isChecking: false
bnk> },
bnk> {
bnk> key: 'sharepoint',
bnk> name: TextService.format(strings.ConsentDialog_SharePoint),
bnk> description: TextService.format(strings.ConsentDialog_SharePointDescription),
bnk> tokenKind: TokenKind.sharepoint,
bnk> isGranted: false,
bnk> isChecking: false
bnk> },
bnk> {
bnk> key: 'devops',
bnk> name: TextService.format(strings.ConsentDialog_DevOps),
bnk> description: TextService.format(strings.ConsentDialog_DevOpsDescription),
bnk> tokenKind: TokenKind.devops,
bnk> domain: 'dev.azure.com',
bnk> isGranted: false,
bnk> isChecking: false
bnk> },
bnk> {
bnk> key: 'planner',
bnk> name: TextService.format(strings.ConsentDialog_Planner),
bnk> description: TextService.format(strings.ConsentDialog_PlannerDescription),
bnk> tokenKind: TokenKind.planner,
bnk> domain: 'graph.microsoft.com',
bnk> isGranted: false,
bnk> isChecking: false
bnk> },
bnk> {
bnk> key: 'excel',
bnk> name: TextService.format(strings.ConsentDialog_Excel),
bnk> description: TextService.format(strings.ConsentDialog_ExcelDescription),
bnk> tokenKind: TokenKind.excel,
bnk> domain: 'graph.microsoft.com',
bnk> isGranted: false,
bnk> isChecking: false
bnk> }
bnk> ]);
bnk> const [isInitializing, setIsInitializing] = React.useState(true);
bnk> const formatErrorMessage = (error: any, item: IConsentItem): string => {
bnk> const errorMessage = error?.message || error?.toString() || 'Unknown error';
bnk> // Special handling for DevOps AADSTS650052 error
bnk> if (item.key === 'devops' && errorMessage.includes('AADSTS650052')) {
bnk> return "DevOps may not be available for your organization or user account. This is a common reason for this error.";
bnk> }
bnk> return errorMessage;
bnk> };
bnk> const checkConsentStatus = async (item: IConsentItem): Promise<boolean> => {
bnk> try {
bnk> // Try to get a token - if it succeeds, consent is granted
bnk> const domain = item.domain || 'graph.microsoft.com';
bnk> await AuthService.getAuthToken(item.tokenKind, domain);
bnk> return true;
bnk> } catch (error) {
bnk> // If we get a consent error, consent is not granted
bnk> trackClient.warn(`Consent check failed for ${TokenKind[item.tokenKind]}`, error);
bnk> return false;
bnk> }
bnk> };
bnk> const updateConsentItem = (key: string, updates: Partial<IConsentItem>) => {
bnk> setConsentItems(prev => prev.map(item =>
bnk> item.key === key ? { ...item, ...updates } : item
bnk> ));
bnk> };
bnk> const checkAllConsents = async () => {
bnk> setIsInitializing(true);
bnk> const checkPromises = consentItems.map(async (item) => {
bnk> updateConsentItem(item.key, { isChecking: true, error: undefined });
bnk> try {
bnk> const isGranted = await checkConsentStatus(item);
bnk> updateConsentItem(item.key, {
bnk> isGranted,
bnk> isChecking: false,
bnk> error: undefined
bnk> });
bnk> } catch (error) {
bnk> updateConsentItem(item.key, {
bnk> isGranted: false,
bnk> isChecking: false,
bnk> error: formatErrorMessage(error, item)
bnk> });
bnk> }
bnk> });
bnk> await Promise.all(checkPromises);
bnk> setIsInitializing(false);
bnk> };
bnk> React.useEffect(() => {
bnk> checkAllConsents();
bnk> }, []);
bnk> const requestConsent = async (item: IConsentItem) => {
bnk> if (!AuthService.getConsent) {
bnk> trackClient.error('getConsent provider not available');
bnk> return;
bnk> }
bnk> updateConsentItem(item.key, { isChecking: true, error: undefined });
bnk> try {
bnk> const domain = item.domain || 'graph.microsoft.com';
bnk> await AuthService.getConsent(item.tokenKind, async () => {
bnk> // Test callback to verify consent was granted
bnk> await AuthService.getAuthToken(item.tokenKind, domain);
bnk> }, domain);
bnk> // If we get here, consent was granted
bnk> updateConsentItem(item.key, {
bnk> isGranted: true,
bnk> isChecking: false,
bnk> error: undefined
bnk> });
bnk> } catch (error) {
bnk> updateConsentItem(item.key, {
bnk> isGranted: false,
bnk> isChecking: false,
bnk> error: formatErrorMessage(error, item)
bnk> });
bnk> }
bnk> };
bnk> const getStatusIcon = (item: IConsentItem) => {
bnk> if (item.isChecking) {
bnk> return <Spinner size={SpinnerSize.small} />;
bnk> }
bnk> if (item.isGranted) {
bnk> return <Icon iconName="CheckMark" style={{ color: theme.palette.green }} />;
bnk> }
bnk> return <Icon iconName="ErrorBadge" style={{ color: theme.palette.red }} />;
bnk> };
bnk> const contentProps: IDialogContentProps = {
bnk> type: DialogType.largeHeader,
bnk> title: TextService.format(strings.ConsentDialog_Title),
bnk> subText: TextService.format(strings.ConsentDialog_Description)
bnk> };
bnk> const grantedCount = consentItems.filter(item => item.isGranted).length;
bnk> const totalCount = consentItems.length;
bnk> return (
bnk> <Dialog
bnk> minWidth={400}
bnk> maxWidth={600}
bnk> isBlocking={false}
bnk> dialogContentProps={contentProps}
bnk> isOpen={true}
bnk> onDismiss={props.onDismiss}
>>
bnk> <Stack tokens={{ childrenGap: 'l1' }} styles={{ root: { paddingBottom: '32px' } }}>
bnk> {isInitializing && (
bnk> <MessageBar messageBarType={MessageBarType.info}>
bnk> <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 's1' }}>
bnk> <Spinner size={SpinnerSize.small} />
bnk> <Text>{TextService.format(strings.ConsentDialog_CheckingAll)}</Text>
bnk> </Stack>
bnk> </MessageBar>
bnk> )}
bnk> {!isInitializing && (
bnk> <MessageBar
bnk> messageBarType={grantedCount === totalCount ? MessageBarType.success : MessageBarType.warning}
>>
bnk> <Text>
bnk> {TextService.format(strings.ConsentDialog_Status, {
bnk> granted: grantedCount.toString(),
bnk> total: totalCount.toString()
bnk> })}
bnk> </Text>
bnk> </MessageBar>
bnk> )}
bnk> <Stack tokens={{ childrenGap: 's1' }}>
bnk> {consentItems.map((item, index) => (
bnk> <div key={item.key}>
bnk> <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 'm' }}>
bnk> <div style={{ width: 20, flexShrink: 0 }}>
bnk> {getStatusIcon(item)}
bnk> </div>
bnk> <Stack grow tokens={{ childrenGap: 'xs' }}>
bnk> <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 's2' }}>
bnk> <Text variant="medium" style={{ fontWeight: 600 }}>
bnk> {item.name}
bnk> </Text>
bnk> {(!item.isGranted && !item.isChecking) && (
bnk> <Text variant="small" style={{
bnk> color: theme.palette.orange,
bnk> backgroundColor: theme.palette.neutralLighter,
bnk> padding: '2px 6px',
bnk> borderRadius: '2px',
bnk> fontSize: '11px',
bnk> fontWeight: 600
bnk> }}>
bnk> {TextService.format(strings.ConsentDialog_NotGranted)}
bnk> </Text>
bnk> )}
bnk> </Stack>
bnk> <Text variant="small" style={{ color: theme.palette.neutralPrimary }}>
bnk> {item.description}
bnk> </Text>
bnk> {item.error && (
bnk> <Text variant="small" style={{
bnk> color: theme.palette.red,
bnk> wordBreak: 'break-word',
bnk> maxWidth: '100%'
bnk> }}>
bnk> {TextService.format(strings.ConsentDialog_Error)}: {item.error}
bnk> </Text>
bnk> )}
bnk> </Stack>
bnk> <Stack horizontal verticalAlign="center" style={{ minWidth: 140, justifyContent: 'flex-end' }}>
bnk> {item.isGranted ? (
bnk> <DefaultButton
bnk> disabled={true}
bnk> text={TextService.format(strings.ConsentDialog_Granted)}
bnk> iconProps={{ iconName: 'CheckMark' }}
bnk> styles={{
bnk> root: {
bnk> backgroundColor: theme.palette.neutralLighter,
bnk> borderColor: theme.palette.neutralLight,
bnk> color: theme.palette.neutralSecondary
bnk> },
bnk> icon: { color: theme.palette.green }
bnk> }}
bnk> />
bnk> ) : item.isChecking ? (
bnk> <DefaultButton
bnk> disabled={true}
bnk> text={TextService.format(strings.ConsentDialog_Checking)}
bnk> onRenderIcon={() => <Spinner size={SpinnerSize.xSmall} />}
bnk> styles={{
bnk> root: {
bnk> backgroundColor: theme.palette.neutralLighter,
bnk> borderColor: theme.palette.neutralLight,
bnk> color: theme.palette.neutralSecondary
bnk> }
bnk> }}
bnk> />
bnk> ) : (
bnk> <PrimaryButton
bnk> text={TextService.format(strings.ConsentDialog_GrantConsent)}
bnk> onClick={() => requestConsent(item)}
bnk> />
bnk> )}
bnk> </Stack>
bnk> </Stack>
bnk> {index < consentItems.length - 1 && <Separator styles={{ root: { margin: '4px 0' } }} />}
bnk> </div>
bnk> ))}
bnk> </Stack>
bnk> </Stack>
bnk> <DialogFooter styles={{ actions: { display: 'flex', flexGrow: 1 }, actionsRight: { flexGrow: 1, justifyContent: 'space-between' } }}>
bnk> <DefaultButton
bnk> text={TextService.format(strings.ConsentDialog_RefreshAll)}
bnk> onClick={checkAllConsents}
bnk> disabled={isInitializing}
bnk> iconProps={{ iconName: 'Refresh' }}
bnk> />
bnk> <DefaultButton onClick={props.onDismiss} text={TextService.format(strings.ConsentDialog_Close)} />
bnk> </DialogFooter>
bnk> </Dialog>
bnk> );
bnk>};
bnk>
|