Branch search and styles (#50)

* Add LatestCommit component

* Add branches search

* Proper link for root
This commit is contained in:
Tan Nhu 2022-11-01 00:53:46 -07:00 committed by GitHub
parent 2d4db78991
commit 3f7fc109d0
13 changed files with 188 additions and 109 deletions

View File

@ -38,13 +38,13 @@
"@blueprintjs/select": "3.12.3", "@blueprintjs/select": "3.12.3",
"@emotion/core": "^10.0.28", "@emotion/core": "^10.0.28",
"@emotion/styled": "^10.0.27", "@emotion/styled": "^10.0.27",
"@harness/design-system": "1.0.0", "@harness/design-system": "1.4.0",
"@harness/icons": "^1.59.0", "@harness/icons": "1.71.0",
"@harness/monaco-yaml": ">=1.0.0", "@harness/monaco-yaml": ">=1.0.0",
"@harness/ng-tooltip": ">=1.31.25", "@harness/ng-tooltip": ">=1.31.25",
"@harness/telemetry": ">=1.0.42", "@harness/telemetry": ">=1.0.42",
"@harness/uicore": "3.75.0", "@harness/uicore": "3.84.0",
"@harness/use-modal": ">=1.1.0", "@harness/use-modal": "1.3.0",
"@popperjs/core": "^2.4.2", "@popperjs/core": "^2.4.2",
"@projectstorm/react-diagrams-core": "^6.6.0", "@projectstorm/react-diagrams-core": "^6.6.0",
"@uiw/react-markdown-preview": "^4.1.2", "@uiw/react-markdown-preview": "^4.1.2",

View File

@ -90,10 +90,7 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
const { showError } = useToaster() const { showError } = useToaster()
const { mutate: createRepo, loading: submitLoading } = useMutate<TypesRepository>({ const { mutate: createRepo, loading: submitLoading } = useMutate<TypesRepository>({
verb: 'POST', verb: 'POST',
path: `/api/v1/repos?spacePath=${space}`, path: `/api/v1/repos?spacePath=${space}`
queryParams: {
// spacePath: space
}
}) })
const { const {
data: gitignores, data: gitignores,

View File

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react' import React, { ChangeEvent, useEffect, useMemo, useState } from 'react'
import { import {
Container, Container,
Layout, Layout,
@ -19,6 +19,8 @@ import { useGet } from 'restful-react'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { useAppContext } from 'AppContext' import { useAppContext } from 'AppContext'
import type { RepoBranch, TypesRepository } from 'services/scm' import type { RepoBranch, TypesRepository } from 'services/scm'
import { BRANCH_PER_PAGE } from 'utils/Utils'
import { GitIcon } from 'utils/GitUtils'
import css from './ContentHeader.module.scss' import css from './ContentHeader.module.scss'
interface ContentHeaderProps { interface ContentHeaderProps {
@ -31,11 +33,14 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
const { getString } = useStrings() const { getString } = useStrings()
const { routes } = useAppContext() const { routes } = useAppContext()
const history = useHistory() const history = useHistory()
// const [searchTerm, setSearchTerm] = useState('README.md') const [query, setQuery] = useState('')
const [branch, setBranch] = useState(gitRef || repoMetadata.defaultBranch) const [activeBranch, setActiveBranch] = useState(gitRef || repoMetadata.defaultBranch)
const { data } = useGet<RepoBranch[]>({ const path = useMemo(
path: `/api/v1/repos/${repoMetadata.path}/+/branches?sort=date&direction=desc&per_page=20&page=1` () =>
}) `/api/v1/repos/${repoMetadata.path}/+/branches?sort=date&direction=desc&per_page=${BRANCH_PER_PAGE}&page=1&query=${query}`,
[query, repoMetadata.path]
)
const { data, loading } = useGet<RepoBranch[]>({ path })
// defaultBranches is computed using repository default branch, and gitRef in URL, if it exists // defaultBranches is computed using repository default branch, and gitRef in URL, if it exists
const defaultBranches = useMemo( const defaultBranches = useMemo(
() => [repoMetadata.defaultBranch].concat(gitRef ? gitRef : []), () => [repoMetadata.defaultBranch].concat(gitRef ? gitRef : []),
@ -60,11 +65,29 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
<Container className={css.folderHeader}> <Container className={css.folderHeader}>
<Layout.Horizontal spacing="medium"> <Layout.Horizontal spacing="medium">
<DropDown <DropDown
icon="git-branch" icon={GitIcon.BRANCH}
value={branch} value={activeBranch}
items={branches} items={branches}
{...{
inputProps: {
leftElement: (
<Icon name={loading ? 'steps-spinner' : 'thinner-search'} size={12} color={Color.GREY_500} />
),
placeholder: getString('search'),
onInput: (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.value !== query) {
setQuery(event.target.value)
}
},
onBlur: (event: ChangeEvent<HTMLInputElement>) => {
setTimeout(() => {
setQuery(event.target.value || '')
}, 250)
}
}
}}
onChange={({ value: switchBranch }) => { onChange={({ value: switchBranch }) => {
setBranch(switchBranch as string) setActiveBranch(switchBranch as string)
history.push( history.push(
routes.toSCMRepository({ routes.toSCMRepository({
repoPath: repoMetadata.path as string, repoPath: repoMetadata.path as string,
@ -77,23 +100,23 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
/> />
<Container> <Container>
<Layout.Horizontal spacing="small"> <Layout.Horizontal spacing="small">
<Link to={routes.toSCMRepository({ repoPath: repoMetadata.path as string })}> <Link to={routes.toSCMRepository({ repoPath: repoMetadata.path as string, gitRef })}>
<Icon name="main-folder" /> <Icon name="main-folder" />
</Link> </Link>
<Text color={Color.GREY_900}>/</Text> <Text color={Color.GREY_900}>/</Text>
<ReactJoin separator={<Text color={Color.GREY_900}>/</Text>}> <ReactJoin separator={<Text color={Color.GREY_900}>/</Text>}>
{resourcePath.split('/').map((path, index, paths) => { {resourcePath.split('/').map((_path, index, paths) => {
const pathAtIndex = paths.slice(0, index + 1).join('/') const pathAtIndex = paths.slice(0, index + 1).join('/')
return ( return (
<Link <Link
key={path + index} key={_path + index}
to={routes.toSCMRepository({ to={routes.toSCMRepository({
repoPath: repoMetadata.path as string, repoPath: repoMetadata.path as string,
gitRef, gitRef,
resourcePath: pathAtIndex resourcePath: pathAtIndex
})}> })}>
<Text color={Color.GREY_900}>{path}</Text> <Text color={Color.GREY_900}>{_path}</Text>
</Link> </Link>
) )
})} })}
@ -117,3 +140,5 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
</Container> </Container>
) )
} }
// TODO: Optimize branch fetching when first fetch return less than request PER_PAGE

View File

@ -4,6 +4,7 @@ import { SourceCodeViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
import type { OpenapiGetContentOutput, RepoFileContent } from 'services/scm' import type { OpenapiGetContentOutput, RepoFileContent } from 'services/scm'
import { GitIcon } from 'utils/GitUtils' import { GitIcon } from 'utils/GitUtils'
import { filenameToLanguage } from 'utils/Utils' import { filenameToLanguage } from 'utils/Utils'
import { LatestCommit } from '../LatestCommit/LatestCommit'
import css from './FileContent.module.scss' import css from './FileContent.module.scss'
interface FileContentProps { interface FileContentProps {
@ -12,22 +13,25 @@ interface FileContentProps {
export function FileContent({ contentInfo }: FileContentProps): JSX.Element { export function FileContent({ contentInfo }: FileContentProps): JSX.Element {
return ( return (
<Container className={css.container} background={Color.WHITE}> <Layout.Vertical spacing="small">
<Layout.Horizontal padding="small" className={css.heading}> <LatestCommit latestCommit={contentInfo.latestCommit} standaloneStyle />
<Heading level={5}>{contentInfo.name}</Heading> <Container className={css.container} background={Color.WHITE}>
<FlexExpander /> <Layout.Horizontal padding="small" className={css.heading}>
<Button variation={ButtonVariation.ICON} icon={GitIcon.EDIT} /> <Heading level={5}>{contentInfo.name}</Heading>
</Layout.Horizontal> <FlexExpander />
<Button variation={ButtonVariation.ICON} icon={GitIcon.EDIT} />
</Layout.Horizontal>
{/* TODO: Loading and Error handling */} {/* TODO: Loading and Error handling */}
{(contentInfo?.content as RepoFileContent)?.data && ( {(contentInfo?.content as RepoFileContent)?.data && (
<Container className={css.content}> <Container className={css.content}>
<SourceCodeViewer <SourceCodeViewer
language={filenameToLanguage(contentInfo?.name)} language={filenameToLanguage(contentInfo?.name)}
source={window.atob((contentInfo?.content as RepoFileContent)?.data || '')} source={window.atob((contentInfo?.content as RepoFileContent)?.data || '')}
/> />
</Container> </Container>
)} )}
</Container> </Container>
</Layout.Vertical>
) )
} }

View File

@ -1,15 +1,4 @@
.folderContent { .folderContent {
.lastCommit {
display: flex;
align-items: center;
margin-left: var(--spacing-medium);
margin-right: var(--spacing-xxlarge);
.shaBtn {
border: 1px solid var(--grey-200) !important;
}
}
.table { .table {
background-color: var(--white) !important; background-color: var(--white) !important;
@ -26,9 +15,15 @@
height: 20px; height: 20px;
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16); box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
overflow: hidden; overflow: hidden;
padding-top: 5px; border-radius: 0;
padding-bottom: 25px; padding-top: 10px;
margin-bottom: 0; margin-bottom: 0;
padding-bottom: 25px;
.rowText {
font-size: 13px;
font-weight: 400;
}
} }
} }

View File

@ -2,10 +2,9 @@
// this is an auto-generated file // this is an auto-generated file
declare const styles: { declare const styles: {
readonly folderContent: string readonly folderContent: string
readonly lastCommit: string
readonly shaBtn: string
readonly table: string readonly table: string
readonly row: string readonly row: string
readonly rowText: string
readonly readmeContainer: string readonly readmeContainer: string
readonly heading: string readonly heading: string
readonly readmeContent: string readonly readmeContent: string

View File

@ -1,26 +1,15 @@
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { import { Container, Color, TableV2 as Table, Text } from '@harness/uicore'
Container,
Color,
Layout,
Button,
ButtonSize,
FlexExpander,
ButtonVariation,
TableV2 as Table,
Text,
FontVariation
} from '@harness/uicore'
import type { CellProps, Column } from 'react-table' import type { CellProps, Column } from 'react-table'
import { sortBy } from 'lodash-es' import { sortBy } from 'lodash-es'
import ReactTimeago from 'react-timeago' import { useHistory } from 'react-router-dom'
import { Link, useHistory } from 'react-router-dom'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { useAppContext } from 'AppContext' import { useAppContext } from 'AppContext'
import type { OpenapiContentInfo, OpenapiDirContent, OpenapiGetContentOutput, TypesRepository } from 'services/scm' import type { OpenapiContentInfo, OpenapiDirContent, OpenapiGetContentOutput, TypesRepository } from 'services/scm'
import { formatDate } from 'utils/Utils' import { formatDate } from 'utils/Utils'
import { findReadmeInfo, GitIcon, isFile } from 'utils/GitUtils' import { findReadmeInfo, GitIcon, isFile } from 'utils/GitUtils'
import { Readme } from './Readme' import { Readme } from './Readme'
import { LatestCommit } from '../LatestCommit/LatestCommit'
import css from './FolderContent.module.scss' import css from './FolderContent.module.scss'
interface FolderContentProps { interface FolderContentProps {
@ -37,11 +26,11 @@ export function FolderContent({ repoMetadata, contentInfo, gitRef }: FolderConte
() => [ () => [
{ {
Header: getString('name'), Header: getString('name'),
accessor: (row: OpenapiContentInfo) => row.name, width: '40%',
width: '30%',
Cell: ({ row }: CellProps<OpenapiContentInfo>) => { Cell: ({ row }: CellProps<OpenapiContentInfo>) => {
return ( return (
<Text <Text
className={css.rowText}
color={Color.BLACK} color={Color.BLACK}
icon={isFile(row.original) ? GitIcon.FILE : GitIcon.FOLDER} icon={isFile(row.original) ? GitIcon.FILE : GitIcon.FOLDER}
lineClamp={1} lineClamp={1}
@ -53,11 +42,10 @@ export function FolderContent({ repoMetadata, contentInfo, gitRef }: FolderConte
}, },
{ {
Header: getString('commits'), Header: getString('commits'),
accessor: row => row.latestCommit, width: 'calc(60% - 100px)',
width: '55%',
Cell: ({ row }: CellProps<OpenapiContentInfo>) => { Cell: ({ row }: CellProps<OpenapiContentInfo>) => {
return ( return (
<Text color={Color.BLACK} lineClamp={1}> <Text color={Color.BLACK} lineClamp={1} className={css.rowText}>
{row.original.latestCommit?.title} {row.original.latestCommit?.title}
</Text> </Text>
) )
@ -65,12 +53,14 @@ export function FolderContent({ repoMetadata, contentInfo, gitRef }: FolderConte
}, },
{ {
Header: getString('repos.lastChange'), Header: getString('repos.lastChange'),
accessor: row => row.latestCommit?.author?.when, width: '100px',
width: '15%',
Cell: ({ row }: CellProps<OpenapiContentInfo>) => { Cell: ({ row }: CellProps<OpenapiContentInfo>) => {
return <Text lineClamp={1}>{formatDate(row.original.latestCommit?.author?.when as string)}</Text> return (
}, <Text lineClamp={1} color={Color.GREY_500} className={css.rowText}>
disableSortBy: true {formatDate(row.original.latestCommit?.author?.when as string)}
</Text>
)
}
} }
], ],
[getString] [getString]
@ -79,27 +69,11 @@ export function FolderContent({ repoMetadata, contentInfo, gitRef }: FolderConte
return ( return (
<Container className={css.folderContent}> <Container className={css.folderContent}>
<Container> <LatestCommit latestCommit={contentInfo?.latestCommit} />
<Layout.Horizontal spacing="medium" padding={{ bottom: 'medium' }} className={css.lastCommit}>
<Text font={{ variation: FontVariation.SMALL_SEMI }}>
{contentInfo.latestCommit?.author?.identity?.name || contentInfo.latestCommit?.author?.identity?.email}
</Text>
<Link to="">{contentInfo.latestCommit?.title}</Link>
<FlexExpander />
<Button
className={css.shaBtn}
text={contentInfo.latestCommit?.sha?.substring(0, 6)}
variation={ButtonVariation.SECONDARY}
size={ButtonSize.SMALL}
/>
<Text font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
<ReactTimeago date={contentInfo.latestCommit?.author?.when as string} />
</Text>
</Layout.Horizontal>
</Container>
<Table<OpenapiContentInfo> <Table<OpenapiContentInfo>
className={css.table} className={css.table}
hideHeaders
columns={columns} columns={columns}
data={sortBy((contentInfo.content as OpenapiDirContent)?.entries || [], ['type', 'name'])} data={sortBy((contentInfo.content as OpenapiDirContent)?.entries || [], ['type', 'name'])}
onRowClick={data => { onRowClick={data => {

View File

@ -0,0 +1,27 @@
.latestCommit {
display: flex;
align-items: center;
margin-left: var(--spacing-medium);
margin-right: var(--spacing-xxlarge);
background-color: var(--primary-1) !important;
align-items: center;
padding: var(--spacing-small) var(--spacing-large) !important;
box-shadow: var(--elevation-3);
border-top-left-radius: 4px;
border-top-right-radius: 4px;
&.standalone {
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
border-radius: 4px;
}
.shaBtn span {
font-size: 12px !important;
}
.commitLink {
font-weight: 500;
font-size: 12px;
color: var(--primary-8);
}
}

View File

@ -0,0 +1,9 @@
/* eslint-disable */
// this is an auto-generated file
declare const styles: {
readonly latestCommit: string
readonly standalone: string
readonly shaBtn: string
readonly commitLink: string
}
export default styles

View File

@ -0,0 +1,47 @@
import React from 'react'
import {
Container,
Color,
Layout,
Button,
ButtonSize,
FlexExpander,
ButtonVariation,
Text,
FontVariation
} from '@harness/uicore'
import { Link } from 'react-router-dom'
import ReactTimeago from 'react-timeago'
import cx from 'classnames'
import type { RepoCommit } from 'services/scm'
import css from './LatestCommit.module.scss'
interface LatestCommitProps {
latestCommit?: RepoCommit
standaloneStyle?: boolean
}
export function LatestCommit({ latestCommit, standaloneStyle }: LatestCommitProps): JSX.Element | null {
return latestCommit ? (
<Container>
<Layout.Horizontal spacing="small" className={cx(css.latestCommit, standaloneStyle ? css.standalone : '')}>
<Text font={{ variation: FontVariation.SMALL_BOLD }}>
{latestCommit.author?.identity?.name || latestCommit.author?.identity?.email}
</Text>
<Link to="" className={css.commitLink}>
{latestCommit.title}
</Link>
<FlexExpander />
<Button
className={css.shaBtn}
text={latestCommit.sha?.substring(0, 6)}
variation={ButtonVariation.SECONDARY}
size={ButtonSize.SMALL}
/>
<Text font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
<ReactTimeago date={latestCommit.author?.when as string} />
</Text>
</Layout.Horizontal>
</Container>
) : null
}

View File

@ -25,7 +25,8 @@ export const GitIcon: Readonly<Record<string, IconName>> = {
PULL_REQUEST: 'git-pull', PULL_REQUEST: 'git-pull',
SETTINGS: 'cog', SETTINGS: 'cog',
FOLDER: 'main-folder', FOLDER: 'main-folder',
EDIT: 'edit' EDIT: 'edit',
BRANCH: 'git-branch'
} }
export type Nullable<T> = T | undefined | null export type Nullable<T> = T | undefined | null

View File

@ -4,7 +4,8 @@ import { get } from 'lodash-es'
import moment from 'moment' import moment from 'moment'
import langMap from 'lang-map' import langMap from 'lang-map'
export const LIST_FETCHING_PER_PAGE = 5 export const LIST_FETCHING_PER_PAGE = 20
export const BRANCH_PER_PAGE = 100
export const DEFAULT_DATE_FORMAT = 'MM/DD/YYYY hh:mm a' export const DEFAULT_DATE_FORMAT = 'MM/DD/YYYY hh:mm a'
export const X_TOTAL = 'x-total' export const X_TOTAL = 'x-total'
export const X_TOTAL_PAGES = 'x-total-pages' export const X_TOTAL_PAGES = 'x-total-pages'

View File

@ -1918,15 +1918,15 @@
dependencies: dependencies:
loader-utils "^2.0.0" loader-utils "^2.0.0"
"@harness/design-system@1.0.0": "@harness/design-system@1.4.0":
version "1.0.0" version "1.4.0"
resolved "https://npm.pkg.github.com/download/@harness/design-system/1.0.0/e463410da2c7ad9bff84eaa43fbe4ba9ba347da1#e463410da2c7ad9bff84eaa43fbe4ba9ba347da1" resolved "https://npm.pkg.github.com/download/@harness/design-system/1.4.0/b2a77f73696d71a53765c71efd0a5b28039fa1cf#b2a77f73696d71a53765c71efd0a5b28039fa1cf"
integrity sha512-sMsLP5Rxg08t87dJamTgpzEfRAef06C14bZO92SGqiXYtXsK01evjnXaOAiUS4gO36gMGi5cYJAqkJ0XBkERIg== integrity sha512-LuzuPEHPkE6xgIuXxn16RCCvPY1NDXF3o1JWlIjxmepoDTkgFuwnV1OhBdQftvAVBawJ5wJP10IIKUL161LdYg==
"@harness/icons@^1.59.0": "@harness/icons@1.71.0":
version "1.68.1" version "1.71.0"
resolved "https://npm.pkg.github.com/download/@harness/icons/1.68.1/56d8f98170aad544c44b3a77d15ffe0cbd6b5931#56d8f98170aad544c44b3a77d15ffe0cbd6b5931" resolved "https://npm.pkg.github.com/download/@harness/icons/1.71.0/86c3b579e3785106a03af048a15a93ae684e7358#86c3b579e3785106a03af048a15a93ae684e7358"
integrity sha512-9PLPVE/LffliSgYrP0TEWMWTZJX5ANYMTyqSSS4115tw8K52Wbl35C9Wg0ftLrzvJrt7ok+i+RZAdjc123kwpw== integrity sha512-mnUvcI1o8mi7A1437/fG8xXS2spzVte4PZbKlbguZ5WtojKGxVe4FMT6xhUt7goSihEz9cu91WY5V/KE7Cc5Ew==
"@harness/jarvis@^0.12.0": "@harness/jarvis@^0.12.0":
version "0.12.0" version "0.12.0"
@ -1967,12 +1967,12 @@
resolved "https://npm.pkg.github.com/download/@harness/telemetry/1.0.44/55e75d8caccbcdcb0a11226c813edd631578d9af#55e75d8caccbcdcb0a11226c813edd631578d9af" resolved "https://npm.pkg.github.com/download/@harness/telemetry/1.0.44/55e75d8caccbcdcb0a11226c813edd631578d9af#55e75d8caccbcdcb0a11226c813edd631578d9af"
integrity sha512-t6N3Ie/F9Nw/tANAmptsunebGYBkC3j865bc75MZVL2ZqFM0CBRxFR1MG8zC+hU6uDpr8Drqsn81NmdlVlBSmA== integrity sha512-t6N3Ie/F9Nw/tANAmptsunebGYBkC3j865bc75MZVL2ZqFM0CBRxFR1MG8zC+hU6uDpr8Drqsn81NmdlVlBSmA==
"@harness/uicore@3.75.0": "@harness/uicore@3.84.0":
version "3.75.0" version "3.84.0"
resolved "https://npm.pkg.github.com/download/@harness/uicore/3.75.0/fecb45629a1d55783bf6088424957bbdf81af743#fecb45629a1d55783bf6088424957bbdf81af743" resolved "https://npm.pkg.github.com/download/@harness/uicore/3.84.0/9c02acd3106254a08530f68065317d031cd15e67#9c02acd3106254a08530f68065317d031cd15e67"
integrity sha512-j61mbRnfkJAWuBrVXBDaio+MR+lS//klVY277EiGb4iWRo0opX9nyBmRhd4hD+rtfux9JSS7BhqUHdG1pzCQ/A== integrity sha512-3AvETaoDHGGKSbnFN9rZAg0HgWWBKMeqGpbonxWj/6gUEahlMnzd8QGvpjUdbYOVCAtPbUVxrtI0Yf4VQPuf5g==
"@harness/use-modal@>=1.1.0": "@harness/use-modal@1.3.0":
version "1.3.0" version "1.3.0"
resolved "https://npm.pkg.github.com/download/@harness/use-modal/1.3.0/fdedeb097e62000c856ed9e73368ff439692549f#fdedeb097e62000c856ed9e73368ff439692549f" resolved "https://npm.pkg.github.com/download/@harness/use-modal/1.3.0/fdedeb097e62000c856ed9e73368ff439692549f#fdedeb097e62000c856ed9e73368ff439692549f"
integrity sha512-2CxYlv4rLtHNAZz3Kg7zC+UyMaTeyLwYh3cr7xUtBVJsYpggm4GgyuJSui+gRaO75Os9mdtKQzBt+paPgY8krQ== integrity sha512-2CxYlv4rLtHNAZz3Kg7zC+UyMaTeyLwYh3cr7xUtBVJsYpggm4GgyuJSui+gRaO75Os9mdtKQzBt+paPgY8krQ==