<commit_before>import PropTypes from 'prop-types'
import React from 'react'
import {SanityLogo as SanityLogotype} from '@sanity/logos'
import {Spinner, Container, Flex} from '@sanity/ui'
import {of} from 'rxjs'
import {map, catchError} from 'rxjs/operators'
import CookieTest from './CookieTest'
import ErrorDialog from './ErrorDialog'
import UnauthorizedUser from './UnauthorizedUser'
import {versionedClient} from './versionedClient'
import {userStore, LoginDialog} from './legacyParts'

const isProjectLogin = versionedClient.config().useProjectHostname
const projectId =
  isProjectLogin && versionedClient.config().projectId ? versionedClient.config().projectId : null

export default class LoginWrapper extends React.PureComponent {
  static propTypes = {
    children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
    title: PropTypes.node,
    description: PropTypes.node,
    sanityLogo: PropTypes.node,
    SanityLogo: PropTypes.oneOfType([PropTypes.object, PropTypes.node, PropTypes.func]),
    LoadingScreen: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
  }

  static defaultProps = {
    title: 'Choose a login provider',
    description: null,
    sanityLogo: null,
    SanityLogo: SanityLogotype,
    LoadingScreen: (
      <Container padding={4} height="fill">
        <Flex justify="center" align="center" height="fill">
          <Spinner />
        </Flex>
      </Container>
    ),
  }

  state = {isLoading: true, user: null, error: null}

  userSubscription = null

  constructor(props) {
    super(props)

    let sync = true
    this.userSubscription = userStore.me
      .pipe(
        map((currentUser) => ({
          user: currentUser,
          isLoading: false,
        })),
        catchError((error) => {
          return of({user: null, error, isLoading: false})
        })
      )
      .subscribe({
        next: (userState) => {
          // Because observables _can_ be synchronous, it's not safe to call `setState` as it is a noop
          // We must therefore explicitly check whether or not we were call synchronously
          if (sync) {
            // eslint-disable-next-line react/no-direct-mutation-state
            this.state = {...this.state, ...userState}
          } else {
            this.setState(userState)
          }
        },
      })
    sync = false
  }

  componentWillUnmount() {
    if (this.userSubscription) {
      this.userSubscription.unsubscribe()
    }
  }

  handleRetry = () => {
    this.setState({error: null, isLoading: true})
    userStore.actions.retry()
  }

  render() {
    const {error, user, isLoading} = this.state
    const {children, LoadingScreen, sanityLogo, SanityLogo} = this.props
    if (sanityLogo) {
      const warning =
        'sanityLogo is a deprecated property on LoginWrapper. Pass a React component to the SanityLogo property instead.'
      console.warn(warning) // eslint-disable-line no-console
    }

    if (isLoading) {
      return typeof LoadingScreen === 'function' ? (
        <LoadingScreen center fullscreen />
      ) : (
        LoadingScreen
      )
    }

    if (error) {
      return <ErrorDialog onRetry={this.handleRetry} error={error} />
    }

    if (!user) {
      return (
        <CookieTest {...this.props}>
          <LoginDialog
            title={this.props.title}
            description={this.props.description}
            SanityLogo={SanityLogo}
            projectId={projectId}
          />
        </CookieTest>
      )
    }

    if (projectId && !user.role) {
      return <UnauthorizedUser user={user} />
    }

    return typeof children === 'function' ? children(user) : <>{children}</>
  }
}
<commit_msg>feature(default-login): add feature to sync window's login state<commit_after>import PropTypes from 'prop-types'
import React from 'react'
import {SanityLogo as SanityLogotype} from '@sanity/logos'
import {Spinner, Container, Flex} from '@sanity/ui'
import {of} from 'rxjs'
import {map, catchError} from 'rxjs/operators'
import {authToken$} from '@sanity/base/_internal'
import CookieTest from './CookieTest'
import ErrorDialog from './ErrorDialog'
import UnauthorizedUser from './UnauthorizedUser'
import {versionedClient} from './versionedClient'
import {userStore, LoginDialog} from './legacyParts'

const ENABLE_CROSS_WINDOW_TOKEN_LOGIN_SYNC = false

const isProjectLogin = versionedClient.config().useProjectHostname
const projectId =
  isProjectLogin && versionedClient.config().projectId ? versionedClient.config().projectId : null

export default class LoginWrapper extends React.PureComponent {
  static propTypes = {
    children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
    title: PropTypes.node,
    description: PropTypes.node,
    sanityLogo: PropTypes.node,
    SanityLogo: PropTypes.oneOfType([PropTypes.object, PropTypes.node, PropTypes.func]),
    LoadingScreen: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
  }

  static defaultProps = {
    title: 'Choose a login provider',
    description: null,
    sanityLogo: null,
    SanityLogo: SanityLogotype,
    LoadingScreen: (
      <Container padding={4} height="fill">
        <Flex justify="center" align="center" height="fill">
          <Spinner />
        </Flex>
      </Container>
    ),
  }

  state = {isLoading: true, user: null, error: null}

  userSubscription = null
  tokenSubscription = null

  constructor(props) {
    super(props)

    let sync = true
    this.tokenSubscription = ENABLE_CROSS_WINDOW_TOKEN_LOGIN_SYNC
      ? authToken$.subscribe((token) => {
          if (token) {
            if (!this.state.user) {
              this.handleRetry()
            }
            return
          }
          if (sync) {
            // eslint-disable-next-line react/no-direct-mutation-state
            this.state = {...this.state, ...{user: null}}
          } else {
            this.setState({user: null})
          }
        })
      : null
    this.userSubscription = userStore.me
      .pipe(
        map((currentUser) => ({
          user: currentUser,
          isLoading: false,
        })),
        catchError((error) => {
          return of({user: null, error, isLoading: false})
        })
      )
      .subscribe({
        next: (userState) => {
          // Because observables _can_ be synchronous, it's not safe to call `setState` as it is a noop
          // We must therefore explicitly check whether or not we were call synchronously
          if (sync) {
            // eslint-disable-next-line react/no-direct-mutation-state
            this.state = {...this.state, ...userState}
          } else {
            this.setState(userState)
          }
        },
      })
    sync = false
  }

  componentWillUnmount() {
    if (this.userSubscription) {
      this.userSubscription.unsubscribe()
    }
    if (this.tokenSubscription) {
      this.tokenSubscription.unsubscribe()
    }
  }

  handleRetry = () => {
    this.setState({error: null, isLoading: true})
    userStore.actions.retry()
  }

  render() {
    const {error, user, isLoading} = this.state
    const {children, LoadingScreen, sanityLogo, SanityLogo} = this.props
    if (sanityLogo) {
      const warning =
        'sanityLogo is a deprecated property on LoginWrapper. Pass a React component to the SanityLogo property instead.'
      console.warn(warning) // eslint-disable-line no-console
    }

    if (isLoading) {
      return typeof LoadingScreen === 'function' ? (
        <LoadingScreen center fullscreen />
      ) : (
        LoadingScreen
      )
    }

    if (error) {
      return <ErrorDialog onRetry={this.handleRetry} error={error} />
    }

    if (!user) {
      return (
        <CookieTest {...this.props}>
          <LoginDialog
            title={this.props.title}
            description={this.props.description}
            SanityLogo={SanityLogo}
            projectId={projectId}
          />
        </CookieTest>
      )
    }

    if (projectId && !user.role) {
      return <UnauthorizedUser user={user} />
    }

    return typeof children === 'function' ? children(user) : <>{children}</>
  }
}

{'fraction_non_alphanumeric': 0.11885347170743761, 'fraction_numerical': 0.00024709661477637757, 'mean_word_length': 2.53339153208206, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 38, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2489114', 'n_tokens_mistral': 2356, 'n_tokens_neox': 2350, 'n_words': 564}