├── react
├── .eslintignore
├── .babelrc
├── components
│ ├── consts.ts
│ ├── EditorContainer
│ │ ├── Sidebar
│ │ │ ├── consts.ts
│ │ │ ├── BlockSelector
│ │ │ │ ├── BlockList
│ │ │ │ │ ├── BlockListItem
│ │ │ │ │ │ ├── Item.css
│ │ │ │ │ │ └── ExpandArrow
│ │ │ │ │ │ │ ├── styles.css
│ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ └── typings.d.ts
│ │ │ ├── BlockEditor
│ │ │ │ ├── BlockConfigurationEditor
│ │ │ │ │ ├── ConditionControls
│ │ │ │ │ │ ├── typings.d.ts
│ │ │ │ │ │ ├── Separator.tsx
│ │ │ │ │ │ ├── ConditionTitle.tsx
│ │ │ │ │ │ └── ScopeSelector
│ │ │ │ │ │ │ └── ScopeSelector.tsx
│ │ │ │ │ ├── typings.d.ts
│ │ │ │ │ ├── styles.css
│ │ │ │ │ └── hooks.ts
│ │ │ │ ├── BlockConfigurationList
│ │ │ │ │ ├── Card
│ │ │ │ │ │ ├── typings.d.ts
│ │ │ │ │ │ ├── utils.ts
│ │ │ │ │ │ ├── styles.css
│ │ │ │ │ │ ├── ConditionTags
│ │ │ │ │ │ │ └── Tag.tsx
│ │ │ │ │ │ └── StatusLabel.tsx
│ │ │ │ │ ├── CreateButton.tsx
│ │ │ │ │ └── typings.d.ts
│ │ │ │ ├── utils.test.ts
│ │ │ │ └── utils.ts
│ │ │ ├── Transitions
│ │ │ │ ├── consts.ts
│ │ │ │ ├── index.tsx
│ │ │ │ ├── Enter
│ │ │ │ │ ├── styles.css
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── typings.d.ts
│ │ │ │ └── Exit
│ │ │ │ │ ├── styles.css
│ │ │ │ │ └── index.tsx
│ │ │ ├── AbsoluteLoader.tsx
│ │ │ ├── utils.test.ts
│ │ │ ├── FormMetaContext.tsx
│ │ │ └── styles.css
│ │ ├── Topbar
│ │ │ ├── DeviceSwitcher
│ │ │ │ ├── DeviceSwitcher.css
│ │ │ │ ├── messages.ts
│ │ │ │ ├── icons
│ │ │ │ │ ├── IconMobile.tsx
│ │ │ │ │ ├── IconTablet.tsx
│ │ │ │ │ └── IconDesktop.tsx
│ │ │ │ ├── consts.ts
│ │ │ │ ├── DeviceItem.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── ContextSelectors
│ │ │ │ ├── graphql
│ │ │ │ │ ├── appSettings.graphql
│ │ │ │ │ ├── TenantInfo.graphql
│ │ │ │ │ ├── CopyBindings.graphql
│ │ │ │ │ ├── SaveRoute.graphql
│ │ │ │ │ └── GetRoute.graphql
│ │ │ │ ├── typings.d.ts
│ │ │ │ ├── hooks
│ │ │ │ │ └── useBinding.ts
│ │ │ │ └── BindingCloning
│ │ │ │ │ └── utils
│ │ │ │ │ └── initialReducerState.ts
│ │ │ ├── hooks.ts
│ │ │ ├── icons
│ │ │ │ ├── IconView.tsx
│ │ │ │ ├── IconPicker.tsx
│ │ │ │ └── CopyContent.tsx
│ │ │ ├── index.tsx
│ │ │ ├── SidebarVisibilityToggle.tsx
│ │ │ └── BlockPicker.tsx
│ │ ├── Styles
│ │ │ ├── StyleEditor
│ │ │ │ ├── graphql
│ │ │ │ │ ├── DeleteFontFamily.graphql
│ │ │ │ │ ├── GenerateStyleSheet.graphql
│ │ │ │ │ ├── RenameStyle.graphql
│ │ │ │ │ ├── UpdateStyle.graphql
│ │ │ │ │ ├── ListFonts.graphql
│ │ │ │ │ └── SaveFontFamily.graphql
│ │ │ │ ├── colors.d.ts
│ │ │ │ ├── queries
│ │ │ │ │ ├── GenerateStyleSheet.ts
│ │ │ │ │ └── ListFontsQuery.ts
│ │ │ │ ├── typings.d.ts
│ │ │ │ ├── mutations
│ │ │ │ │ ├── RenameStyle.ts
│ │ │ │ │ ├── UpdateStyle.ts
│ │ │ │ │ └── DeleteFontFamily.ts
│ │ │ │ ├── utils
│ │ │ │ │ └── colors.ts
│ │ │ │ ├── typography
│ │ │ │ │ ├── TypeTokensEntry.tsx
│ │ │ │ │ ├── TypeTokensList.tsx
│ │ │ │ │ ├── TypographyEditor.tsx
│ │ │ │ │ ├── FontFamilyEntry.tsx
│ │ │ │ │ └── TypeTokenDropdown.tsx
│ │ │ │ ├── AvailableEditor.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── StyleList
│ │ │ │ ├── graphql
│ │ │ │ │ ├── DeleteStyle.graphql
│ │ │ │ │ ├── SaveSelectedStyle.graphql
│ │ │ │ │ └── CreateStyle.graphql
│ │ │ │ ├── queries
│ │ │ │ │ └── ListStyles.ts
│ │ │ │ ├── mutations
│ │ │ │ │ ├── DeleteStyle.ts
│ │ │ │ │ ├── CreateStyle.ts
│ │ │ │ │ └── SaveSelectedStyle.ts
│ │ │ │ └── icons
│ │ │ │ │ └── CreateNewIcon.tsx
│ │ │ ├── typings
│ │ │ │ ├── typings.d.ts
│ │ │ │ └── config.d.ts
│ │ │ └── components
│ │ │ │ ├── Colors.tsx
│ │ │ │ └── Typography.tsx
│ │ ├── EditableText
│ │ │ └── styles.css
│ │ ├── graphql
│ │ │ ├── DeleteContent.graphql
│ │ │ ├── SendEventToAudit.graphql
│ │ │ ├── ListContent.graphql
│ │ │ └── SaveContent.graphql
│ │ ├── EditorContainer.css
│ │ ├── mutations
│ │ │ ├── DeleteContent.tsx
│ │ │ ├── SaveContent.tsx
│ │ │ └── SendEventToAudit.tsx
│ │ └── queries
│ │ │ └── ListContent.tsx
│ ├── admin
│ │ ├── redirects
│ │ │ ├── UploadModal
│ │ │ │ ├── typings.d.ts
│ │ │ │ ├── UploadPrompt
│ │ │ │ │ ├── UploadPrompt.css
│ │ │ │ │ └── validateRedirect.ts
│ │ │ │ ├── UploadModal.css
│ │ │ │ └── Loading.tsx
│ │ │ ├── mutations
│ │ │ │ ├── SaveRedirectFromFile.graphql
│ │ │ │ ├── DeleteManyRedirectsFromFile.graphql
│ │ │ │ ├── DeleteManyRedirectsFromFile.d.ts
│ │ │ │ └── SaveRedirectFromFile.tsx
│ │ │ ├── ImportErrorModal
│ │ │ │ └── ImportErrorModal.css
│ │ │ ├── List
│ │ │ │ ├── typings.d.ts
│ │ │ │ ├── CreateButton.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── typings.d.ts
│ │ │ ├── consts.ts
│ │ │ ├── Form
│ │ │ │ ├── Operations.tsx
│ │ │ │ └── typings.d.ts
│ │ │ └── bulkUploadRedirects.ts
│ │ ├── pages
│ │ │ ├── List
│ │ │ │ ├── utils.ts
│ │ │ │ ├── SectionSeparator.tsx
│ │ │ │ ├── typings.d.ts
│ │ │ │ ├── Entry.tsx
│ │ │ │ └── Section.tsx
│ │ │ ├── SeparatorWithLine.tsx
│ │ │ ├── Form
│ │ │ │ ├── Title.tsx
│ │ │ │ ├── stateHandlers
│ │ │ │ │ ├── getLoginToggleState.ts
│ │ │ │ │ ├── __fixtures__
│ │ │ │ │ │ ├── setupDate.ts
│ │ │ │ │ │ ├── state.ts
│ │ │ │ │ │ └── newPage.ts
│ │ │ │ │ ├── getRemoveConditionalTemplateState.ts
│ │ │ │ │ ├── getLoginToggleState.test.ts
│ │ │ │ │ ├── getChangeTemplateConditionalTemplateState.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── getChangeStatementsConditionalTemplate.ts
│ │ │ │ │ ├── getChangeOperatorConditionalTemplateState.ts
│ │ │ │ │ └── getAddConditionalTemplateState.ts
│ │ │ │ ├── SectionTitle.tsx
│ │ │ │ ├── utils.test.ts
│ │ │ │ └── typings.d.ts
│ │ │ ├── consts.ts
│ │ │ └── utils.ts
│ │ ├── FormFieldSeparator.tsx
│ │ ├── store
│ │ │ ├── PWAForm
│ │ │ │ ├── mutations
│ │ │ │ │ ├── UpdatePWASettings.graphql
│ │ │ │ │ ├── UpdateManifestIcon.graphql
│ │ │ │ │ └── UpdateManifest.graphql
│ │ │ │ └── queries
│ │ │ │ │ └── PWA.graphql
│ │ │ └── StoreForm
│ │ │ │ ├── queries
│ │ │ │ ├── InstalledApp.graphql
│ │ │ │ └── AvailableApp.graphql
│ │ │ │ └── mutations
│ │ │ │ └── SaveAppSettings.graphql
│ │ ├── institutional
│ │ │ ├── Form
│ │ │ │ ├── Loader.tsx
│ │ │ │ ├── UnallowedWarning.tsx
│ │ │ │ └── utils.tsx
│ │ │ ├── utils.tsx
│ │ │ └── List
│ │ │ │ └── Entry.tsx
│ │ ├── AdminStructure.tsx
│ │ ├── TargetPathContext.tsx
│ │ └── utils.ts
│ ├── icons
│ │ ├── DragHandle.css
│ │ ├── ContentActiveIcon.tsx
│ │ ├── ContentInactiveIcon.tsx
│ │ ├── DangerIcon.tsx
│ │ ├── ArrowIcon.tsx
│ │ ├── ComponentDragHandleIcon.tsx
│ │ ├── CalendarIcon.tsx
│ │ ├── utils.tsx
│ │ ├── PersonIcon.tsx
│ │ ├── PageIcon.tsx
│ │ ├── TemplateIcon.tsx
│ │ ├── ContentScheduledIcon.tsx
│ │ ├── AddIcon.tsx
│ │ ├── EarthIcon.tsx
│ │ ├── DragHandle.tsx
│ │ └── GearIcon.tsx
│ ├── ActionMenu
│ │ ├── typings.d.ts
│ │ └── index.tsx
│ ├── form
│ │ ├── ImageUploader
│ │ │ ├── ImagePreview.css
│ │ │ ├── styles.css
│ │ │ ├── ErrorAlert.tsx
│ │ │ ├── EmptyState.tsx
│ │ │ ├── Dropzone.tsx
│ │ │ └── ImagePreview.tsx
│ │ ├── RichText.tsx
│ │ ├── I18nInput
│ │ │ └── utils.ts
│ │ ├── ArrayFieldTemplate
│ │ │ ├── ArrayFieldTemplateItem
│ │ │ │ ├── PreviewOverlay.tsx
│ │ │ │ ├── Handle.tsx
│ │ │ │ ├── NoImagePlaceholder.tsx
│ │ │ │ ├── ArrayFieldTemplateItem.css
│ │ │ │ └── icons
│ │ │ │ │ └── IconImage.tsx
│ │ │ ├── AddButton.tsx
│ │ │ ├── ItemTransitions.css
│ │ │ └── styles.css
│ │ ├── typings.d.ts
│ │ ├── FieldTemplate.tsx
│ │ ├── ObjectFieldTemplate.tsx
│ │ ├── Toggle.tsx
│ │ └── MultiSelect.tsx
│ ├── Loader.tsx
│ ├── RichTextEditor
│ │ ├── Link.tsx
│ │ ├── Media.tsx
│ │ ├── style.css
│ │ └── StyleButton.tsx
│ ├── HighlightOverlay
│ │ ├── OverlayMask
│ │ │ ├── OverlayMask.css
│ │ │ └── index.tsx
│ │ ├── typings.d.ts
│ │ ├── hooks
│ │ │ ├── useStyles
│ │ │ │ ├── typings.d.ts
│ │ │ │ └── index.ts
│ │ │ └── useAutoScroll.ts
│ │ └── HighlightOverlay.css
│ ├── DomainMessages
│ │ └── typings.d.ts
│ ├── EditorContext.tsx
│ └── Modal.tsx
├── typings
│ ├── draftjs-md-converter.d.ts
│ ├── text-encoding.d.ts
│ ├── simple-element-resize-detector.d.ts
│ ├── json-schema-traverse.d.ts
│ ├── streamsaver.d.ts
│ ├── vtex.native-types.d.ts
│ ├── draft-js.d.ts
│ ├── pages.d.ts
│ └── vtex.render-runtime.d.ts
├── __mocks__
│ ├── vtex.native-types.ts
│ └── vtex.render-runtime.ts
├── queries
│ ├── Languages.graphql
│ ├── ContentIOMessage.graphql
│ ├── UploadFile.graphql
│ ├── DeleteRoute.graphql
│ ├── AvailableTemplates.graphql
│ ├── TenantInfo.graphql
│ ├── RedirectWithoutBinding.graphql
│ ├── DeleteRedirect.graphql
│ ├── Redirect.graphql
│ ├── MessagesForDomain.graphql
│ ├── Redirects.graphql
│ ├── SaveRedirect.graphql
│ ├── ExtensionConfigurations.graphql
│ ├── SaveRoute.graphql
│ ├── Routes.graphql
│ └── Route.graphql
├── utils
│ ├── bindings
│ │ ├── typings.d.ts
│ │ └── index.ts
│ ├── conditions
│ │ ├── typings.d.ts
│ │ └── index.ts
│ ├── blocks
│ │ └── typings.d.ts
│ ├── auditEvents
│ │ └── index.ts
│ ├── AdminLoadingContext.tsx
│ └── components
│ │ └── typings.d.ts
├── .prettierrc
├── MediaGalleryWidget.tsx
├── HighlightOverlay.tsx
├── .eslintrc
├── EmptyExtensionPoint.tsx
├── EditableExtensionPoint.tsx
├── PageFormWrapper.tsx
├── Wrapper.tsx
├── StoreSettings.tsx
├── tsconfig.json
├── RedirectListWrapper.tsx
└── PageEditor.tsx
├── pages
└── plugins.json
├── crowdin.yml
├── .travis.yml
├── .vtexignore
├── .editorconfig
├── package.json
├── .vtex
├── deployment.yaml
└── catalog-info.yaml
├── .github
├── ISSUE_TEMPLATE
│ ├── questions-and-help.md
│ ├── feature_request.md
│ └── bug_report.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── docs
└── CONTENT_PAGE.md
└── manifest.json
/react/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/react/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env", "react-app"]
3 | }
--------------------------------------------------------------------------------
/react/components/consts.ts:
--------------------------------------------------------------------------------
1 | export const ANIMATION_TIMEOUT = 250
2 |
--------------------------------------------------------------------------------
/react/typings/draftjs-md-converter.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'draftjs-md-converter'
2 |
--------------------------------------------------------------------------------
/pages/plugins.json:
--------------------------------------------------------------------------------
1 | {
2 | "storeWrapper > highlight-overlay": "highlight-overlay.cms"
3 | }
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/consts.ts:
--------------------------------------------------------------------------------
1 | export const NEW_CONFIGURATION_ID = 'new'
2 |
--------------------------------------------------------------------------------
/crowdin.yml:
--------------------------------------------------------------------------------
1 | files:
2 | - source: /messages/en.json
3 | translation: /messages/%two_letters_code%.json
4 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/DeviceSwitcher/DeviceSwitcher.css:
--------------------------------------------------------------------------------
1 | .mr05 {
2 | margin-right: 1px;
3 | }
4 |
--------------------------------------------------------------------------------
/react/__mocks__/vtex.native-types.ts:
--------------------------------------------------------------------------------
1 | export function formatIOMessage({ id }: { id: string }) {
2 | return id
3 | }
4 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/UploadModal/typings.d.ts:
--------------------------------------------------------------------------------
1 | export type ModalStates = 'UPLOAD_FILE' | 'LOADING' | 'ERROR'
2 |
--------------------------------------------------------------------------------
/react/queries/Languages.graphql:
--------------------------------------------------------------------------------
1 | query Languages {
2 | languages {
3 | default
4 | supported
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/react/components/icons/DragHandle.css:
--------------------------------------------------------------------------------
1 | .editor-icon--fill path,
2 | .editor-icon--fill circle {
3 | fill: #727273;
4 | }
5 |
--------------------------------------------------------------------------------
/react/queries/ContentIOMessage.graphql:
--------------------------------------------------------------------------------
1 | query ContentIOMessage($args: TranslateArgs!) {
2 | translate(args: $args)
3 | }
4 |
--------------------------------------------------------------------------------
/react/utils/bindings/typings.d.ts:
--------------------------------------------------------------------------------
1 | export interface DropdownChangeInput {
2 | target: {
3 | value: string
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/__mocks__/vtex.render-runtime.ts:
--------------------------------------------------------------------------------
1 | export const global = {}
2 | export const Window = {}
3 | export const canUseDOM = true
4 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockSelector/BlockList/BlockListItem/Item.css:
--------------------------------------------------------------------------------
1 | .track-1 {
2 | letter-spacing: 0.2px;
3 | }
4 |
--------------------------------------------------------------------------------
/react/queries/UploadFile.graphql:
--------------------------------------------------------------------------------
1 | mutation UploadFile($file: Upload!) {
2 | uploadFile(file: $file) {
3 | fileUrl
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "trailingComma": "es5",
5 | "jsxBracketSameLine": false
6 | }
7 |
--------------------------------------------------------------------------------
/react/queries/DeleteRoute.graphql:
--------------------------------------------------------------------------------
1 | mutation DeleteRoute($uuid: String!) {
2 | deleteRoute(uuid: $uuid, dataSource: "vtex.rewriter")
3 | }
4 |
--------------------------------------------------------------------------------
/react/MediaGalleryWidget.tsx:
--------------------------------------------------------------------------------
1 | import MediaGalleryWidget from './components/MediaGalleryWidget/index'
2 |
3 | export default MediaGalleryWidget
4 |
--------------------------------------------------------------------------------
/react/HighlightOverlay.tsx:
--------------------------------------------------------------------------------
1 | export { default } from './components/HighlightOverlay'
2 |
3 | export { State } from './components/HighlightOverlay/typings'
4 |
--------------------------------------------------------------------------------
/react/components/admin/pages/List/utils.ts:
--------------------------------------------------------------------------------
1 | export const sortRoutes = (routes: Route[]) =>
2 | routes.sort((a, b) => a.interfaceId.localeCompare(b.interfaceId))
3 |
--------------------------------------------------------------------------------
/react/queries/AvailableTemplates.graphql:
--------------------------------------------------------------------------------
1 | query AvailableTemplates($interfaceId: String) {
2 | availableTemplates(interfaceId: $interfaceId) {
3 | id
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": "vtex-react"
9 | }
10 |
--------------------------------------------------------------------------------
/react/components/ActionMenu/typings.d.ts:
--------------------------------------------------------------------------------
1 | export interface ActionMenuOption {
2 | isDangerous?: boolean
3 | label: string
4 | onClick: (e: ActionMenuOption) => void
5 | }
6 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationEditor/ConditionControls/typings.d.ts:
--------------------------------------------------------------------------------
1 | export interface DateRange {
2 | from?: Date
3 | to?: Date
4 | }
5 |
--------------------------------------------------------------------------------
/react/typings/text-encoding.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'text-encoding' {
2 | declare const TextEncoder = class TextEnconder {
3 | public encode(data: string): void
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/graphql/DeleteFontFamily.graphql:
--------------------------------------------------------------------------------
1 | mutation DeleteFontFamily($id: String) {
2 | deleteFontFamily(id: $id) {
3 | id
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/graphql/GenerateStyleSheet.graphql:
--------------------------------------------------------------------------------
1 | query GenerateStyleSheet($config: ConfigInput) {
2 | generateStyleSheet(config: $config)
3 | }
4 |
--------------------------------------------------------------------------------
/react/components/admin/FormFieldSeparator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const FormFieldSeparator = () =>
4 |
5 | export default FormFieldSeparator
6 |
--------------------------------------------------------------------------------
/react/components/admin/pages/List/SectionSeparator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const SectionSeparator = () =>
4 |
5 | export default SectionSeparator
6 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationList/Card/typings.d.ts:
--------------------------------------------------------------------------------
1 | export interface GetGenericContextArgs {
2 | context: PageContext
3 | isSitewide: boolean
4 | }
5 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/Transitions/consts.ts:
--------------------------------------------------------------------------------
1 | import { ANIMATION_TIMEOUT } from '../../../consts'
2 |
3 | export const COMMON_PROPS = {
4 | timeout: ANIMATION_TIMEOUT,
5 | }
6 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleList/graphql/DeleteStyle.graphql:
--------------------------------------------------------------------------------
1 | mutation DeleteStyle($id: String) {
2 | deleteStyle(id: $id) {
3 | id
4 | app
5 | name
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/react/utils/conditions/typings.d.ts:
--------------------------------------------------------------------------------
1 | export interface DateInfo {
2 | date?: Date
3 | from?: Date
4 | to?: Date
5 | }
6 |
7 | export type DateVerbOptions = 'between' | 'from' | 'is' | 'to'
8 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/graphql/RenameStyle.graphql:
--------------------------------------------------------------------------------
1 | mutation RenameStyle($id: String, $name: String) {
2 | renameStyle(id: $id, name: $name) {
3 | id
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/components/admin/pages/SeparatorWithLine.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const SeparatorWithLine = () =>
4 |
5 | export default SeparatorWithLine
6 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/colors.d.ts:
--------------------------------------------------------------------------------
1 | interface ColorInfo {
2 | path: string
3 | color: string
4 | configField: string
5 | }
6 |
7 | type Colors = Record
8 |
--------------------------------------------------------------------------------
/react/components/admin/pages/List/typings.d.ts:
--------------------------------------------------------------------------------
1 | export interface CategorizedRoutes {
2 | multipleProducts: Route[]
3 | noProducts: Route[]
4 | singleProduct: Route[]
5 | notFoundSection: Route[]
6 | }
7 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleList/graphql/SaveSelectedStyle.graphql:
--------------------------------------------------------------------------------
1 | mutation SaveSelectedStyle($id: String) {
2 | saveSelectedStyle(id: $id) {
3 | id
4 | app
5 | name
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/react/components/admin/store/PWAForm/mutations/UpdatePWASettings.graphql:
--------------------------------------------------------------------------------
1 | mutation updatePWASettings($settings: PWASettingsInput) {
2 | updatePWASettings(settings: $settings) {
3 | disablePrompt
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/queries/TenantInfo.graphql:
--------------------------------------------------------------------------------
1 | query TenantInfo {
2 | tenantInfo {
3 | bindings {
4 | canonicalBaseAddress
5 | id
6 | supportedLocales
7 | targetProduct
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/react/typings/simple-element-resize-detector.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'simple-element-resize-detector' {
2 | export default function(
3 | el: Element,
4 | handler: (el: Element) => void
5 | ): HTMLIFrameElement
6 | }
7 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/Transitions/index.tsx:
--------------------------------------------------------------------------------
1 | import Enter from './Enter'
2 | import Exit from './Exit'
3 |
4 | const Transitions = {
5 | Enter,
6 | Exit,
7 | }
8 |
9 | export default Transitions
10 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/graphql/UpdateStyle.graphql:
--------------------------------------------------------------------------------
1 | mutation UpdateStyle($id: String, $config: ConfigInput) {
2 | updateStyle(id: $id, config: $config) {
3 | path
4 | selected
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/react/components/form/ImageUploader/ImagePreview.css:
--------------------------------------------------------------------------------
1 | .overlay {
2 | background: rgba(0, 0, 0, 0.15);
3 | opacity: 0;
4 | transition: opacity ease-in-out 100ms;
5 | }
6 |
7 | .overlay:hover {
8 | opacity: 1;
9 | }
10 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleList/graphql/CreateStyle.graphql:
--------------------------------------------------------------------------------
1 | mutation CreateStyle($name: String, $config: ConfigInput) {
2 | createStyle(name: $name, config: $config) {
3 | id
4 | app
5 | name
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/mutations/SaveRedirectFromFile.graphql:
--------------------------------------------------------------------------------
1 | mutation SaveManyRedirects($redirects: [RedirectInput!]!) {
2 | redirect @context(provider: "vtex.rewriter") {
3 | saveMany(routes: $redirects)
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/components/admin/store/StoreForm/queries/InstalledApp.graphql:
--------------------------------------------------------------------------------
1 | query installedApp($slug: String!) {
2 | installedApp(slug: $slug) {
3 | slug
4 | name
5 | vendor
6 | version
7 | settings
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/react/utils/blocks/typings.d.ts:
--------------------------------------------------------------------------------
1 | export type BlockRole = 'after' | 'around' | 'before'
2 |
3 | export type BlockRolesForTree = BlockRole | 'blocks'
4 |
5 | export interface RelativeBlocks {
6 | [role: string]: string[] | undefined
7 | }
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | cache:
4 | yarn: true
5 | directories:
6 | - "node_modules"
7 |
8 | node_js:
9 | - "12"
10 |
11 | install:
12 | - yarn --cwd react
13 | - yarn
14 |
15 | script:
16 | cd react && yarn ci
17 |
--------------------------------------------------------------------------------
/.vtexignore:
--------------------------------------------------------------------------------
1 | .git
2 | src
3 |
4 | .gitignore
5 | ./CHANGELOG.md
6 | README.md
7 |
8 | **/.DS_Store
9 | **/node_modules
10 | **/yarn-error.log
11 |
12 | **/*.{spec,test}.{ts,tsx}
13 | **/tests/**
14 | **/__fixtures__/**
15 | **/__mocks__/**
16 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationEditor/ConditionControls/Separator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Separator: React.FunctionComponent = () =>
4 |
5 | export default Separator
6 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/Title.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Title: React.FunctionComponent = ({ children }) => (
4 | {children}
5 | )
6 |
7 | export default Title
8 |
--------------------------------------------------------------------------------
/react/components/admin/store/StoreForm/mutations/SaveAppSettings.graphql:
--------------------------------------------------------------------------------
1 | mutation saveAppSettings($app: String, $version: String, $settings: String) {
2 | saveAppSettings(app: $app, version: $version, settings: $settings) {
3 | message
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockSelector/BlockList/BlockListItem/ExpandArrow/styles.css:
--------------------------------------------------------------------------------
1 | .transition {
2 | transition-property: transform, color;
3 | transition-duration: 200ms;
4 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
5 | }
6 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/graphql/ListFonts.graphql:
--------------------------------------------------------------------------------
1 | query ListFonts {
2 | listFonts {
3 | id
4 | fontFamily
5 | fonts {
6 | id
7 | filename
8 | fontWeight
9 | fontStyle
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/ContextSelectors/graphql/appSettings.graphql:
--------------------------------------------------------------------------------
1 | query AppSettings($appName: String, $version: String) {
2 | appSettings(app: $appName, version: $version)
3 | @context(provider: "vtex.apps-graphql") {
4 | message
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/ContextSelectors/graphql/TenantInfo.graphql:
--------------------------------------------------------------------------------
1 | query TenantInfoQuery {
2 | tenantInfo {
3 | bindings {
4 | canonicalBaseAddress
5 | id
6 | supportedLocales
7 | targetProduct
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/mutations/DeleteManyRedirectsFromFile.graphql:
--------------------------------------------------------------------------------
1 | mutation DeleteManyRedirects($paths: [String!]!, $locators: [RouteLocator!]) {
2 | redirect @context(provider: "vtex.rewriter") {
3 | deleteMany(paths: $paths, locators: $locators)
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/components/admin/store/PWAForm/mutations/UpdateManifestIcon.graphql:
--------------------------------------------------------------------------------
1 | mutation updateManifest($icon: Upload!, $iOS: Boolean) {
2 | updateManifestIcon(icon: $icon, iOS: $iOS) {
3 | icons {
4 | src
5 | type
6 | sizes
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 2
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/react/components/admin/store/PWAForm/mutations/UpdateManifest.graphql:
--------------------------------------------------------------------------------
1 | mutation updateManifest($manifest: ManifestInput) {
2 | updateManifest(manifest: $manifest) {
3 | start_url
4 | theme_color
5 | background_color
6 | display
7 | orientation
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/react/components/admin/store/StoreForm/queries/AvailableApp.graphql:
--------------------------------------------------------------------------------
1 | query availableApp($id: String!) {
2 | availableApp(id: $id) {
3 | id
4 | title
5 | installed
6 | linked
7 | installedVersion
8 | settingsSchema
9 | settingsUiSchema
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/react/queries/RedirectWithoutBinding.graphql:
--------------------------------------------------------------------------------
1 | query RedirectWithoutBinding($path: String!) {
2 | redirect @context(provider: "vtex.rewriter") {
3 | get(path: $path) {
4 | binding
5 | endDate
6 | from
7 | to
8 | type
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/react/components/admin/institutional/Form/Loader.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Spinner } from 'vtex.styleguide'
3 |
4 | const Loader = () => (
5 |
6 |
7 |
8 | )
9 |
10 | export default Loader
11 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/getLoginToggleState.ts:
--------------------------------------------------------------------------------
1 | import { State } from '../index'
2 |
3 | export const getLoginToggleState = (prevState: State) => ({
4 | ...prevState,
5 | data: {
6 | ...prevState.data,
7 | auth: !!prevState.data && !prevState.data.auth,
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/react/queries/DeleteRedirect.graphql:
--------------------------------------------------------------------------------
1 | mutation DeleteRedirect($path: String!, $binding: String!) {
2 | redirect @context(provider: "vtex.rewriter") {
3 | delete(path: $path, locator: { from: $path, binding: $binding }) {
4 | endDate
5 | from
6 | to
7 | type
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/react/queries/Redirect.graphql:
--------------------------------------------------------------------------------
1 | query Redirect($path: String!, $binding: String!) {
2 | redirect @context(provider: "vtex.rewriter") {
3 | get(path: $path, locator: { from: $path, binding: $binding }) {
4 | binding
5 | endDate
6 | from
7 | to
8 | type
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/react/typings/json-schema-traverse.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'json-schema-traverse' {
2 | const traverse: (
3 | schema: ComponentSchema,
4 | opts: (
5 | schema: JSONSchema6 & { widget: Widget },
6 | JSONPointer: string
7 | ) => void
8 | ) => void
9 |
10 | export default traverse
11 | }
12 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/graphql/SaveFontFamily.graphql:
--------------------------------------------------------------------------------
1 | mutation SaveFontFamily($font: SaveFontFamilyInput) {
2 | saveFontFamily(font: $font) {
3 | id
4 | fontFamily
5 | fonts {
6 | filename
7 | fontWeight
8 | fontStyle
9 | id
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/__fixtures__/setupDate.ts:
--------------------------------------------------------------------------------
1 | import MockDate from 'mockdate'
2 |
3 | const setupDate = () => {
4 | beforeEach(() => {
5 | MockDate.set('2019-02-01')
6 | })
7 |
8 | afterEach(() => {
9 | MockDate.reset()
10 | })
11 | }
12 |
13 | export default setupDate
14 |
--------------------------------------------------------------------------------
/react/components/form/RichText.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import I18nInput from './I18nInput'
4 | import { CustomWidgetProps } from './typings'
5 |
6 | const RichText: React.FunctionComponent = props => (
7 |
8 | )
9 |
10 | export default RichText
11 |
--------------------------------------------------------------------------------
/react/queries/MessagesForDomain.graphql:
--------------------------------------------------------------------------------
1 | query GetMessagesForDomain(
2 | $components: [String!]!
3 | $domain: String!
4 | $renderMajor: Int!
5 | ) {
6 | messages(
7 | components: $components
8 | domain: $domain
9 | renderMajor: $renderMajor
10 | ) {
11 | key
12 | message
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "4.21.2-beta.2",
3 | "license": "UNLICENSED",
4 | "scripts": {
5 | "test": "cd react && npm run test"
6 | },
7 | "devDependencies": {
8 | "husky": "^3.0.4"
9 | },
10 | "husky": {
11 | "hooks": {
12 | "pre-commit": "cd react && npx lint-staged"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/react/EmptyExtensionPoint.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | children?: React.ReactNode
5 | }
6 |
7 | // Backwards compatibility, we can delete this file upon releasing the next render-runtime.
8 | const EmptyExtensionPoint = ({ children }: Props) => children || null
9 |
10 | export default EmptyExtensionPoint
11 |
--------------------------------------------------------------------------------
/react/EditableExtensionPoint.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | children?: React.ReactNode
5 | }
6 |
7 | // Backwards compatibility, we can delete this file upon releasing the next render-runtime.
8 | const EditableExtensionPoint = ({ children }: Props) => children || null
9 |
10 | export default EditableExtensionPoint
11 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/EditableText/styles.css:
--------------------------------------------------------------------------------
1 | .input::placeholder {
2 | color: #979899;
3 | font-style: normal;
4 | font-weight: 400;
5 | }
6 |
7 | .editableTextInputWrapper .editableTextInputWrapper__input {
8 | opacity: 0;
9 | }
10 |
11 | .editableTextInputWrapper:hover .editableTextInputWrapper__input {
12 | opacity: 1;
13 | }
14 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/graphql/DeleteContent.graphql:
--------------------------------------------------------------------------------
1 | mutation DeleteContent(
2 | $template: String
3 | $treePath: String
4 | $pageContext: PageContextInput
5 | $contentId: String
6 | ) {
7 | deleteContent(
8 | template: $template
9 | treePath: $treePath
10 | pageContext: $pageContext
11 | contentId: $contentId
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/react/components/Loader.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 |
4 | const Loader = () => (
5 |
6 | {text => (
7 |
8 | {text}
9 | …
10 |
11 | )}
12 |
13 | )
14 |
15 | export default Loader
16 |
--------------------------------------------------------------------------------
/react/components/form/I18nInput/utils.ts:
--------------------------------------------------------------------------------
1 | export const isValidData = (
2 | data: unknown
3 | ): data is {
4 | target: HTMLInputElement | HTMLTextAreaElement
5 | value: string
6 | } =>
7 | typeof data === 'object' &&
8 | data !== null &&
9 | Object.prototype.hasOwnProperty.call(data, 'target') &&
10 | Object.prototype.hasOwnProperty.call(data, 'value')
11 |
--------------------------------------------------------------------------------
/react/queries/Redirects.graphql:
--------------------------------------------------------------------------------
1 | query Redirects($limit: Int, $next: String) {
2 | redirect @context(provider: "vtex.rewriter") {
3 | listRedirects(limit: $limit, next: $next) {
4 | routes {
5 | binding
6 | endDate
7 | from
8 | to
9 | type
10 | binding
11 | }
12 | next
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/react/components/form/ArrayFieldTemplate/ArrayFieldTemplateItem/PreviewOverlay.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import styles from './ArrayFieldTemplateItem.css'
4 |
5 | const PreviewOverlay = () => (
6 |
9 | )
10 |
11 | export default React.memo(PreviewOverlay)
12 |
--------------------------------------------------------------------------------
/react/components/admin/institutional/utils.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | AvailableApp,
3 | InstalledApp,
4 | } from '../store/StoreForm/components/withStoreSettings'
5 |
6 | export const parseStoreAppId = (
7 | store: InstalledApp & AvailableApp & { settings: string }
8 | ) => {
9 | const appMajor = store.version.split('.')[0]
10 | return `${store.slug}@${appMajor}.x`
11 | }
12 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/ImportErrorModal/ImportErrorModal.css:
--------------------------------------------------------------------------------
1 | .import-error-modal-scroll::-webkit-scrollbar,
2 | .import-error-modal-scroll::-webkit-scrollbar-track {
3 | width: 2px;
4 | background: #f2f4f5;
5 | }
6 |
7 | .import-error-modal-scroll::-webkit-scrollbar-thumb {
8 | width: 2px;
9 | background: rgba(0, 0, 0, 0.2);
10 | border-radius: 2px;
11 | }
12 |
--------------------------------------------------------------------------------
/react/components/form/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { WidgetProps } from 'react-jsonschema-form'
2 |
3 | export interface CustomWidgetProps extends WidgetProps {
4 | formContext: {
5 | messages: RenderContext['messages']
6 | }
7 | onChange: (value: unknown) => void
8 | rawErrors?: string[]
9 | schema: WidgetProps['schema'] & {
10 | disabled?: boolean
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/react/components/icons/ContentActiveIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const ContentActiveIcon: React.FC = () => (
4 |
13 | )
14 |
15 | export default ContentActiveIcon
16 |
--------------------------------------------------------------------------------
/react/components/icons/ContentInactiveIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const ContentInactiveIcon: React.FC = () => (
4 |
13 | )
14 |
15 | export default ContentInactiveIcon
16 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationList/Card/utils.ts:
--------------------------------------------------------------------------------
1 | import { GetGenericContextArgs } from './typings'
2 |
3 | export const getGenericContext = ({
4 | context,
5 | isSitewide,
6 | }: GetGenericContextArgs) => {
7 | if (isSitewide) {
8 | return 'sitewide'
9 | }
10 |
11 | if (context.id === '*') {
12 | return 'template'
13 | }
14 |
15 | return 'page'
16 | }
17 |
--------------------------------------------------------------------------------
/react/typings/streamsaver.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'streamsaver' {
2 | interface Constructable {
3 | new (): T
4 | }
5 |
6 | interface StreamSaver {
7 | static WritableStream: Constructable
8 | createWriteStream: (name: string) => WritableStream
9 | }
10 |
11 | let WritableStream: WritableStream
12 | const streamsaver: StreamSaver
13 |
14 | export default streamsaver
15 | }
16 |
--------------------------------------------------------------------------------
/react/queries/SaveRedirect.graphql:
--------------------------------------------------------------------------------
1 | mutation saveRedirect(
2 | $endDate: String
3 | $from: String!
4 | $to: String!
5 | $type: RedirectTypes!
6 | $binding: String
7 | ) {
8 | redirect @context(provider: "vtex.rewriter") {
9 | save(route: { endDate: $endDate, from: $from, to: $to, type: $type, binding: $binding}) {
10 | endDate
11 | from
12 | to
13 | type
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/ContextSelectors/graphql/CopyBindings.graphql:
--------------------------------------------------------------------------------
1 | mutation CopyBindingContent($from: String!, $to: String!, $template: String, $context: PageContextInput) {
2 | copyBindingContent(from: $from, to: $to, template: $template, context: $context) {
3 | added {
4 | contentId
5 | origin
6 | }
7 | removed {
8 | contentId
9 | origin
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/List/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { RedirectsQuery } from '../typings'
2 |
3 | export interface FetchMoreOptions {
4 | updateQuery: (prevData: RedirectsQuery, newData: UpdateQueryNewData) => void
5 | variables: QueryVariables
6 | }
7 |
8 | interface QueryVariables {
9 | from: number
10 | to: number
11 | }
12 |
13 | interface UpdateQueryNewData {
14 | fetchMoreResult?: RedirectsQuery
15 | }
16 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/SectionTitle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 |
4 | interface Props {
5 | textId: string
6 | }
7 |
8 | const SectionTitle: React.FunctionComponent = ({ textId }) => (
9 |
10 | {text => {text}
}
11 |
12 | )
13 |
14 | export default SectionTitle
15 |
--------------------------------------------------------------------------------
/react/components/RichTextEditor/Link.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Link as LinkProps } from 'draft-js'
4 |
5 | const Link = (props: LinkProps) => {
6 | const { contentState, entityKey, children } = props
7 | const { url } = contentState.getEntity(entityKey).getData()
8 |
9 | return (
10 |
11 | {children}
12 |
13 | )
14 | }
15 |
16 | export default Link
17 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/ContextSelectors/graphql/SaveRoute.graphql:
--------------------------------------------------------------------------------
1 | mutation SaveRoute($route: NewRouteInput) {
2 | saveRoute(route: $route) {
3 | auth
4 | blockId
5 | binding
6 | context
7 | declarer
8 | domain
9 | interfaceId
10 | path
11 | routeId
12 | uuid
13 | metaTags {
14 | description
15 | keywords
16 | robots
17 | }
18 | title
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react'
2 |
3 | export const useHover = () => {
4 | const [hover, setHover] = useState(false)
5 |
6 | const handleMouseEnter = useCallback(() => {
7 | setHover(true)
8 | }, [])
9 |
10 | const handleMouseLeave = useCallback(() => {
11 | setHover(false)
12 | }, [])
13 |
14 | return { handleMouseEnter, handleMouseLeave, hover }
15 | }
16 |
--------------------------------------------------------------------------------
/react/components/form/ImageUploader/styles.css:
--------------------------------------------------------------------------------
1 | .emptyStateContainer {
2 | width: 100%;
3 | height: 8rem;
4 | }
5 |
6 | .emptyState {
7 | width: 100%;
8 | height: 100%;
9 | text-align: center;
10 | margin: auto;
11 | }
12 |
13 | .emptyStateContainer:hover > div {
14 | color: #134cd8;
15 | border-color: #134cd8;
16 | width: 12.93rem;
17 | height: 7rem;
18 | }
19 |
20 | .imageUploaderText {
21 | max-width: 11.3rem;
22 | }
23 |
--------------------------------------------------------------------------------
/react/components/icons/DangerIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const DangerIcon = () => (
4 |
14 | )
15 |
16 | export default React.memo(DangerIcon)
17 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationList/Card/styles.css:
--------------------------------------------------------------------------------
1 | /* This is a temporary hack to remove the hover background from the
2 | ActionMenu */
3 | #action-menu-parent > div > div > div > button:active,
4 | #action-menu-parent > div > div > div > button:hover {
5 | background-color: transparent !important;
6 | }
7 |
8 | .editor-configuration-card-tag-editing > div {
9 | background: rgba(0, 0, 0, 0.5);
10 | }
11 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockSelector/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { SidebarComponent } from '../typings'
2 |
3 | export interface NormalizedBlock extends SidebarComponent {
4 | components: NormalizedBlock[]
5 | isEditable: boolean
6 | isSortable: boolean
7 | }
8 |
9 | export interface BlocksByRole {
10 | after: NormalizedBlock[]
11 | around: NormalizedBlock[]
12 | before: NormalizedBlock[]
13 | blocks: NormalizedBlock[]
14 | }
15 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/UploadModal/UploadPrompt/UploadPrompt.css:
--------------------------------------------------------------------------------
1 | .validation-errors-container {
2 | max-height: 16rem;
3 | }
4 |
5 | .validation-errors-container::-webkit-scrollbar,
6 | .validation-errors-container::-webkit-scrollbar-track {
7 | width: 4px;
8 | background: #f2f4f5;
9 | }
10 |
11 | .validation-errors-container::-webkit-scrollbar-thumb {
12 | width: 4px;
13 | background: rgba(0, 0, 0, 0.2);
14 | border-radius: 2px;
15 | }
16 |
--------------------------------------------------------------------------------
/.vtex/deployment.yaml:
--------------------------------------------------------------------------------
1 | - name: admin-pages
2 | referenceId: 60JU5OHJ
3 | pipelines:
4 | - name: techdocs-v1
5 | parameters:
6 | entityReference: default/component/admin-pages
7 | indexFile: README.md
8 | when:
9 | - event: push
10 | source: branch
11 | regex: master
12 | path:
13 | - "docs/**"
14 | - ".vtex/deployment.yaml"
15 | - ".vtex/deployment.json"
16 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationEditor/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema6 } from 'json-schema'
2 |
3 | export interface ComponentFormState {
4 | depth: number
5 | onClose: () => void
6 | onTitleChange?: () => void
7 | title: string
8 | }
9 |
10 | export interface GetSchemasArgs {
11 | contentSchema?: JSONSchema6
12 | editTreePath: string | null
13 | iframeRuntime: RenderContext
14 | isContent?: boolean
15 | }
16 |
--------------------------------------------------------------------------------
/react/components/admin/pages/consts.ts:
--------------------------------------------------------------------------------
1 | export const NEW_ROUTE_ID = 'new'
2 |
3 | export const ROUTES_FORM = 'admin.app.cms.pages.page-details'
4 |
5 | export const ROUTES_LIST = 'admin.app.cms.pages.page-list'
6 |
7 | export const INSTITUTIONAL_ROUTES_LIST = 'admin.app.cms.content'
8 |
9 | export const INSTITUTIONAL_ROUTES_FORM = 'admin.app.cms.content-details'
10 |
11 | export const WRAPPER_PATH = 'pages'
12 |
13 | export const DATA_SOURCE = 'vtex.rewriter'
14 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/getRemoveConditionalTemplateState.ts:
--------------------------------------------------------------------------------
1 | import { State } from '../index'
2 |
3 | export const getRemoveConditionalTemplateState = (uniqueId: number) => (
4 | prevState: State
5 | ) => {
6 | const newPages = prevState.data.pages.filter(
7 | page => page.uniqueId !== uniqueId
8 | )
9 |
10 | return {
11 | ...prevState,
12 | data: { ...prevState.data, pages: newPages },
13 | formErrors: {},
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/Transitions/Enter/styles.css:
--------------------------------------------------------------------------------
1 | .transition-editor-enter-left {
2 | transform: translateX(-18em);
3 | }
4 |
5 | .transition-editor-enter-left-active {
6 | transition: transform 250ms ease-in;
7 | transform: translateX(0);
8 | }
9 |
10 | .transition-editor-enter-right {
11 | transform: translateX(18em);
12 | }
13 |
14 | .transition-editor-enter-right-active {
15 | transition: transform 250ms ease-in;
16 | transform: translateX(0);
17 | }
18 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/graphql/SendEventToAudit.graphql:
--------------------------------------------------------------------------------
1 | mutation SendEventToAudit($input: SendToAuditInput!) {
2 | sendEventToAudit(input: $input) {
3 | id
4 | mainAccountName
5 | date
6 | application
7 | accountName
8 | subjectId
9 | workspace
10 | operation
11 | meta {
12 | entityName
13 | entityBeforeAction
14 | entityAfterAction
15 | remoteIpAddress
16 | forwardFromVtexUserAgent
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { getIsDefaultContent } from './utils'
2 |
3 | describe('getIsDefaultContent', () => {
4 | it('should return true when origin is declared (comes from app)', () => {
5 | expect(getIsDefaultContent({ origin: 'comes from block' })).toBe(true)
6 | })
7 |
8 | it('should return false when origin is null (declared by user)', () => {
9 | expect(getIsDefaultContent({ origin: null })).toBe(false)
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/DeviceSwitcher/messages.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | desktop: {
3 | defaultMessage: 'Desktop view',
4 | id: 'admin/pages.editor.topbar.button.device.desktop.tooltip',
5 | },
6 | mobile: {
7 | defaultMessage: 'Mobile view',
8 | id: 'admin/pages.editor.topbar.button.device.mobile.tooltip',
9 | },
10 | tablet: {
11 | defaultMessage: 'Tablet view',
12 | id: 'admin/pages.editor.topbar.button.device.tablet.tooltip',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/List/CreateButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 | import { Button } from 'vtex.styleguide'
4 |
5 | interface Props {
6 | onClick: () => void
7 | }
8 |
9 | const CreateButton = ({ onClick }: Props) => (
10 |
13 | )
14 |
15 | export default CreateButton
16 |
--------------------------------------------------------------------------------
/react/components/admin/AdminStructure.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { PageHeader } from 'vtex.styleguide'
3 |
4 | interface Props {
5 | title: string
6 | }
7 |
8 | const AdminStructure: React.FC = ({ children, title }) => (
9 |
10 |
11 |
12 |
13 | {children}
14 |
15 |
16 | )
17 |
18 | export default AdminStructure
19 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/UploadModal/UploadModal.css:
--------------------------------------------------------------------------------
1 | .full-modal-width {
2 | width: calc(100% + 6rem);
3 | }
4 |
5 | .error-container {
6 | max-height: 30vh;
7 | margin-bottom: -3rem;
8 | }
9 |
10 | .error-container::-webkit-scrollbar,
11 | .error-container::-webkit-scrollbar-track {
12 | width: 2px;
13 | background: #f2f4f5;
14 | }
15 |
16 | .error-container::-webkit-scrollbar-thumb {
17 | width: 2px;
18 | background: rgba(0, 0, 0, 0.2);
19 | border-radius: 2px;
20 | }
21 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/mutations/DeleteManyRedirectsFromFile.d.ts:
--------------------------------------------------------------------------------
1 | export interface DeleteManyRedirectsFromFileVariables {
2 | paths: string[]
3 | }
4 |
5 | export type DeleteManyRedirectsFromFileResult = boolean
6 |
7 | export type DeleteManyRedirectsFromFileFn = MutationFn<
8 | DeleteManyRedirectsFromFileVariables,
9 | DeleteManyRedirectsFromFileResult
10 | >
11 |
12 | export interface DeleteManyRedirectsProps {
13 | deleteManyRedirects: DeleteManyRedirectsFromFileFn
14 | }
15 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationEditor/ConditionControls/ConditionTitle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 |
4 | interface Props {
5 | labelId: string
6 | }
7 |
8 | const ConditionTitle: React.FunctionComponent = ({ labelId }) => (
9 |
10 | {message => {message}
}
11 |
12 | )
13 |
14 | export default React.memo(ConditionTitle)
15 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/Transitions/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { Transition } from 'react-transition-group'
2 |
3 | type Direction = 'left' | 'right'
4 |
5 | type TransitionProps = React.ComponentPropsWithoutRef
6 |
7 | interface Props {
8 | children: React.ReactElement
9 | condition: TransitionProps['in']
10 | }
11 |
12 | export interface EnterProps extends Props {
13 | from: Direction
14 | }
15 |
16 | export interface ExitProps extends Props {
17 | to: Direction
18 | }
19 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { AlertProps } from 'vtex.styleguide'
2 |
3 | export interface RedirectsQuery {
4 | redirect: {
5 | list: Redirect[]
6 | numberOfEntries: number
7 | }
8 | }
9 |
10 | export interface AlertState {
11 | type: AlertProps['type']
12 | message: string
13 | meta?: {
14 | failedRedirects: Redirect[]
15 | mutation: (data: Redirect[]) => Promise
16 | isSave: boolean
17 | }
18 | action?: {
19 | label: string
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/react/typings/vtex.native-types.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'vtex.native-types' {
2 | import { FunctionComponent } from 'react'
3 | import { FormattedMessage, InjectedIntlProps } from 'react-intl'
4 |
5 | const formatIOMessage: (
6 | adaptedMessageDescriptor: FormattedMessage.MessageDescriptor &
7 | InjectedIntlProps,
8 | values?: Record
9 | ) => string
10 |
11 | export const IOMessage: FunctionComponent<
12 | FormattedMessage.Props & InjectedIntlProps
13 | >
14 | }
15 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleList/queries/ListStyles.ts:
--------------------------------------------------------------------------------
1 | import { Query, QueryResult } from 'react-apollo'
2 | import ListStyles from '../graphql/ListStyles.graphql'
3 |
4 | export interface ListStylesData {
5 | listStyles: Style[]
6 | }
7 |
8 | export type ListStylesQueryResult = QueryResult
9 |
10 | class ListStylesQuery extends Query {
11 | public static defaultProps = {
12 | query: ListStyles,
13 | }
14 | }
15 |
16 | export default ListStylesQuery
17 |
--------------------------------------------------------------------------------
/react/queries/ExtensionConfigurations.graphql:
--------------------------------------------------------------------------------
1 | query ExtensionConfigurations(
2 | $configurationsIds: [String]
3 | $routeId: String!
4 | $treePath: String!
5 | $url: String!
6 | ) {
7 | extensionConfigurations(
8 | configurationsIds: $configurationsIds
9 | routeId: $routeId
10 | treePath: $treePath
11 | url: $url
12 | ) {
13 | allMatches
14 | conditions
15 | configurationId
16 | device
17 | label
18 | propsJSON
19 | routeId
20 | scope
21 | url
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/react/components/icons/ArrowIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | color?: string
5 | }
6 |
7 | const ArrowIcon: React.FunctionComponent = ({ color }) => (
8 |
17 | )
18 |
19 | ArrowIcon.defaultProps = {
20 | color: 'currentColor',
21 | }
22 |
23 | export default ArrowIcon
24 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/__fixtures__/state.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | data: {
3 | auth: false,
4 | blockId: 'test.app@1.x:store.home#cool-home',
5 | context: null,
6 | declarer: 'user',
7 | domain: 'store',
8 | interfaceId: 'test.app@1.x:store.home',
9 | pages: [],
10 | path: '/',
11 | routeId: 'store.home#cool-home',
12 | title: null,
13 | uuid: undefined,
14 | },
15 | formErrors: {},
16 | isDeletable: false,
17 | isInfoEditable: true,
18 | isLoading: false,
19 | }
20 |
--------------------------------------------------------------------------------
/react/PageFormWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import PageForm, { Props } from './PageForm'
4 | import { useBinding } from './utils/bindings'
5 |
6 | const PageFormWrapper: React.FunctionComponent = (props: Props) => {
7 | const [localStorageBinding, setLocalStorageBinding] = useBinding()
8 | return (
9 |
14 | )
15 | }
16 |
17 | export default PageFormWrapper
18 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/typings/typings.d.ts:
--------------------------------------------------------------------------------
1 | interface BasicStyle {
2 | id: string
3 | app: string
4 | name: string
5 | }
6 |
7 | interface Style extends BasicStyle {
8 | editable: boolean
9 | selected: boolean
10 | path: string
11 | config: TachyonsConfig
12 | }
13 |
14 | interface ActionMenuOption {
15 | label: string
16 | onClick: (style: Style) => void
17 | }
18 |
19 | interface StyleAssetInfo {
20 | type: 'path' | 'stylesheet'
21 | keepSheet?: boolean
22 | selected?: boolean
23 | value: string
24 | }
25 |
--------------------------------------------------------------------------------
/react/components/HighlightOverlay/OverlayMask/OverlayMask.css:
--------------------------------------------------------------------------------
1 | .overlay-mask-enter {
2 | opacity: 0;
3 | }
4 |
5 | .overlay-mask-enter-active {
6 | opacity: 0.8;
7 | transition: opacity 300ms cubic-bezier(0.19, 1, 0.22, 1);
8 | }
9 |
10 | .overlay-mask-enter-done {
11 | opacity: 0.8;
12 | }
13 |
14 | .overlay-mask-exit {
15 | opacity: 0.8;
16 | }
17 |
18 | .overlay-mask-exit-active {
19 | opacity: 0;
20 | transition: opacity 150ms cubic-bezier(0.215, 0.61, 0.355, 1);
21 | }
22 |
23 | .overlay-mask-exit-done {
24 | opacity: 0;
25 | }
26 |
--------------------------------------------------------------------------------
/react/components/form/ArrayFieldTemplate/ArrayFieldTemplateItem/Handle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { SortableHandle } from 'react-sortable-hoc'
3 |
4 | import DragHandle from '../../../icons/DragHandle'
5 | import styles from '../styles.css'
6 |
7 | const Handle = SortableHandle(() => (
8 |
11 |
12 |
13 | ))
14 |
15 | export default Handle
16 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/components/Colors.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | colors: string[]
5 | }
6 |
7 | const Colors: React.FunctionComponent = ({ colors }) => (
8 |
9 | {colors.map((color, index) => (
10 |
17 | ))}
18 |
19 | )
20 |
21 | export default Colors
22 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/__fixtures__/newPage.ts:
--------------------------------------------------------------------------------
1 | import { ConditionsOperator } from 'vtex.styleguide'
2 |
3 | const newPage = {
4 | condition: {
5 | allMatches: true,
6 | id: '',
7 | statements: [
8 | {
9 | error: '',
10 | object: {
11 | date: new Date('2019-02-01'),
12 | },
13 | subject: 'date',
14 | verb: 'is',
15 | },
16 | ],
17 | },
18 | operator: 'all' as ConditionsOperator,
19 | pageId: '',
20 | template: '',
21 | }
22 |
23 | export default newPage
24 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/getLoginToggleState.test.ts:
--------------------------------------------------------------------------------
1 | import BASE_STATE from './__fixtures__/state'
2 | import { getLoginToggleState } from './getLoginToggleState'
3 |
4 | describe('getLoginToggleState', () => {
5 | it('should negate data.login value', () => {
6 | const mockState = {
7 | ...BASE_STATE,
8 | data: {
9 | ...BASE_STATE.data,
10 | auth: true,
11 | },
12 | }
13 |
14 | expect(getLoginToggleState(mockState)).toMatchObject({
15 | data: { auth: false },
16 | })
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/react/Wrapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react'
2 | import { ToastProvider } from 'vtex.styleguide'
3 | import { AdminLoadingContextProvider } from './utils/AdminLoadingContext'
4 |
5 | interface Props {
6 | children?: ReactNode
7 | }
8 |
9 | const Wrapper: React.FunctionComponent = ({ children }) => (
10 |
11 |
12 | {children}
13 |
14 |
15 | )
16 |
17 | export default React.memo(Wrapper)
18 |
--------------------------------------------------------------------------------
/react/components/HighlightOverlay/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from 'react'
2 |
3 | export interface State {
4 | editExtensionPoint: (treePath: string | null) => void
5 | editMode: boolean
6 | elementHeight?: HTMLElement['clientHeight']
7 | highlightStyle?: CSSProperties
8 | labelStyle?: CSSProperties
9 | maskStyle?: CSSProperties
10 | openBlockTreePath: string | null
11 | highlightHandler: (treePath: string | null) => void
12 | highlightTreePath: string | null
13 | sidebarBlocksMap: Record
14 | }
15 |
--------------------------------------------------------------------------------
/react/components/form/ImageUploader/ErrorAlert.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { Alert } from 'vtex.styleguide'
3 |
4 | interface Props {
5 | message: string
6 | }
7 |
8 | const ErrorAlert: React.FunctionComponent = ({ message }) => {
9 | const [isVisible, setIsVisible] = useState(true)
10 |
11 | return isVisible ? (
12 |
13 |
setIsVisible(false)} type="error">
14 | {message}
15 |
16 |
17 | ) : null
18 | }
19 |
20 | export default ErrorAlert
21 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { generateNewRouteId } from './utils'
2 |
3 | describe('#generateNewRouteId', () => {
4 | it('should remove trailing slash from path', () => {
5 | expect(
6 | generateNewRouteId('vtex.store@2.x:store.custom', '/path1/path2/')
7 | ).toBe('vtex.store@2.x:store.custom#path1-path2')
8 | })
9 |
10 | it('should generate a routeId without "/"', () => {
11 | expect(
12 | generateNewRouteId('vtex.store@2.x:store.custom', '/path1/path2')
13 | ).toEqual(expect.not.stringContaining('/'))
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/react/StoreSettings.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { injectIntl, InjectedIntlProps } from 'react-intl'
3 |
4 | import AdminStructure from './components/admin/AdminStructure'
5 | import Store from './components/admin/store'
6 |
7 | const StoreSettings: React.FC = ({ intl }) => (
8 |
14 |
15 |
16 | )
17 |
18 | export default injectIntl(StoreSettings)
19 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/EditorContainer.css:
--------------------------------------------------------------------------------
1 | .h-3em {
2 | height: 48px;
3 | }
4 |
5 | .calc--height-relative {
6 | height: calc(100% - 3em);
7 | }
8 |
9 | .calc--height-relative--dev {
10 | height: calc(100% - 8.5em);
11 | }
12 |
13 | .mobile-preview {
14 | width: 360px;
15 | height: 640px;
16 | max-height: 100%;
17 | box-shadow: 1px 4px 8px rgba(0, 0, 0, 0.2);
18 | overflow: hidden;
19 | }
20 |
21 | .tablet-preview {
22 | width: 768px;
23 | height: 1024px;
24 | max-height: 100%;
25 | box-shadow: 1px 4px 8px rgba(0, 0, 0, 0.2);
26 | overflow: hidden;
27 | }
28 |
--------------------------------------------------------------------------------
/react/components/icons/ComponentDragHandleIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const ComponentDragHandleIcon: React.FunctionComponent = () => (
4 |
12 | )
13 |
14 | export default ComponentDragHandleIcon
15 |
--------------------------------------------------------------------------------
/react/components/RichTextEditor/Media.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { Media as MediaProps } from 'draft-js'
4 |
5 | const Media = (props: MediaProps) => {
6 | const { contentState, block } = props
7 | const blockEntity = block.getEntityAt(0)
8 |
9 | if (!blockEntity) {
10 | return null
11 | }
12 |
13 | const entity = contentState.getEntity(blockEntity)
14 | const { src } = entity.getData()
15 | const type = entity.getType()
16 |
17 | if (type === 'IMAGE') {
18 | return
19 | }
20 |
21 | return null
22 | }
23 |
24 | export default Media
25 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/queries/GenerateStyleSheet.ts:
--------------------------------------------------------------------------------
1 | import { Query } from 'react-apollo'
2 | import GenerateStyleSheet from '../graphql/GenerateStyleSheet.graphql'
3 |
4 | export interface GenerateStyleSheetData {
5 | generateStyleSheet: string
6 | }
7 |
8 | interface GenerateStyleSheetVariables {
9 | config: TachyonsConfig
10 | }
11 |
12 | class GenerateStyleSheetQuery extends Query<
13 | GenerateStyleSheetData,
14 | GenerateStyleSheetVariables
15 | > {
16 | public static defaultProps = {
17 | query: GenerateStyleSheet,
18 | }
19 | }
20 |
21 | export default GenerateStyleSheetQuery
22 |
--------------------------------------------------------------------------------
/react/queries/SaveRoute.graphql:
--------------------------------------------------------------------------------
1 | mutation SaveRoute($route: NewRouteInput) {
2 | saveRoute(route: $route) {
3 | auth
4 | blockId
5 | binding
6 | context
7 | declarer
8 | domain
9 | interfaceId
10 | path
11 | routeId
12 | uuid
13 | metaTags {
14 | description
15 | keywords
16 | robots
17 | }
18 | pages {
19 | condition {
20 | allMatches
21 | id
22 | statements {
23 | subject
24 | verb
25 | objectJSON
26 | }
27 | }
28 | pageId
29 | template
30 | }
31 | title
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationEditor/styles.css:
--------------------------------------------------------------------------------
1 | .form {
2 | transition: transform 250ms ease-in-out;
3 | transform: translateX(0rem);
4 | }
5 |
6 | .form--leave {
7 | transition: transform 250ms ease-in-out;
8 | transform: translateX(-18rem);
9 | }
10 |
11 | .form--leave > :global(.rjsf) {
12 | height: 0;
13 | overflow: hidden;
14 | }
15 |
16 | @media only screen and (max-width: 40em) {
17 | .size-editor {
18 | bottom: 0;
19 | height: 100%;
20 | }
21 | }
22 |
23 | @media only screen and (min-width: 40em) {
24 | .size-editor {
25 | max-width: 450px;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/react/components/icons/CalendarIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const CalendarIcon: React.FC = () => (
4 |
10 | )
11 |
12 | export default CalendarIcon
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/questions-and-help.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Ask a question you had while building a store
4 | labels: question
5 | ---
6 |
7 | **What are you trying to accomplish? Please describe.**
8 | A clear and concise description of what is your question and what you're trying to accomplish in the end is.
9 |
10 | **What have you tried so far?**
11 | A clear and concise description of what you tried to do already.
12 |
13 | **Additional info**
14 | Add extra content here (code samples, screenshots,...)
15 |
16 | | Account | Workspace |
17 | | -------------- | ---------------- |
18 | | `your account` | `your workspace` |
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### SublimeText ###
2 | *.sublime-workspace
3 |
4 | ### OSX ###
5 | .DS_Store
6 | .AppleDouble
7 | .LSOverride
8 | Icon
9 |
10 | # Thumbnails
11 | ._*
12 |
13 | # Files that might appear on external disk
14 | .Spotlight-V100
15 | .Trashes
16 |
17 | ### Windows ###
18 | # Windows image file caches
19 | Thumbs.db
20 | ehthumbs.db
21 |
22 | # Folder config file
23 | Desktop.ini
24 |
25 | # Recycle Bin used on file shares
26 | $RECYCLE.BIN/
27 |
28 | # App specific
29 | node_modules/
30 | docs/_book/
31 | .tmp
32 | .idea
33 | .vscode
34 | npm-debug.log
35 | .build/
36 | lib
37 | *.orig
38 | package-lock.json
39 | yarn-error.log
40 | .eslintcache
41 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/typings.d.ts:
--------------------------------------------------------------------------------
1 | type DeepPartial = T extends object
2 | ? { [K in keyof T]?: DeepPartial }
3 | : T
4 |
5 | interface ButtonInfo {
6 | action?: () => void
7 | text: string
8 | }
9 |
10 | interface RouteInfo {
11 | backButton: ButtonInfo
12 | auxButton?: ButtonInfo
13 | title: string
14 | }
15 |
16 | interface NavigationUpdate {
17 | type: 'push' | 'pop' | 'update'
18 | route: EditorRoute
19 | }
20 |
21 | interface ColorRouteParams {
22 | id: string
23 | }
24 |
25 | interface CustomFontParams {
26 | id: string
27 | }
28 |
29 | interface TypeTokenParams {
30 | id: string
31 | }
32 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/getChangeTemplateConditionalTemplateState.ts:
--------------------------------------------------------------------------------
1 | import { State } from '../index'
2 |
3 | export const getChangeTemplateConditionalTemplateState = (
4 | uniqueId: number,
5 | template: string
6 | ) => (prevState: State) => {
7 | const newPages = prevState.data.pages.map(page => {
8 | if (page.uniqueId === uniqueId) {
9 | return {
10 | ...page,
11 | template,
12 | }
13 | }
14 | return page
15 | })
16 |
17 | return {
18 | ...prevState,
19 | data: {
20 | ...prevState.data,
21 | pages: newPages,
22 | },
23 | formErrors: {},
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/react/queries/Routes.graphql:
--------------------------------------------------------------------------------
1 | query Routes($domain: String, $bindingId: String) {
2 | routes(domain: $domain, bindingId: $bindingId) {
3 | auth
4 | blockId
5 | binding
6 | context
7 | declarer
8 | domain
9 | interfaceId
10 | path
11 | routeId
12 | uuid
13 | metaTags {
14 | description
15 | keywords
16 | robots
17 | }
18 | pages {
19 | condition {
20 | allMatches
21 | id
22 | statements {
23 | subject
24 | verb
25 | objectJSON
26 | }
27 | }
28 | pageId
29 | template
30 | }
31 | title
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/Transitions/Exit/styles.css:
--------------------------------------------------------------------------------
1 | .transition-editor-exit-left {
2 | transform: translateX(0);
3 | }
4 |
5 | .transition-editor-exit-left-active {
6 | transition: transform 250ms ease-in;
7 | transform: translateX(-18em);
8 | }
9 |
10 | .transition-editor-exit-left-done {
11 | transform: translateX(-18em);
12 | }
13 |
14 | .transition-editor-exit-right {
15 | transform: translateX(0);
16 | }
17 |
18 | .transition-editor-exit-right-active {
19 | transition: transform 250ms ease-in;
20 | transform: translateX(18em);
21 | }
22 |
23 | .transition-editor-exit-right-done {
24 | transform: translateX(18em);
25 | }
26 |
--------------------------------------------------------------------------------
/react/components/icons/utils.tsx:
--------------------------------------------------------------------------------
1 | export interface Dimensions {
2 | width: number
3 | height: number
4 | }
5 |
6 | export interface IconProps {
7 | color?: string
8 | size?: number
9 | block?: boolean
10 | }
11 |
12 | export const calcIconSize: (d: Dimensions, s: number) => Dimensions = (
13 | iconBase,
14 | newSize
15 | ) => {
16 | const isHorizontal = iconBase.width >= iconBase.height
17 |
18 | const width = isHorizontal
19 | ? newSize
20 | : (newSize * iconBase.width) / iconBase.height
21 |
22 | const height = !isHorizontal
23 | ? newSize
24 | : (newSize * iconBase.height) / iconBase.width
25 |
26 | return { width, height }
27 | }
28 |
--------------------------------------------------------------------------------
/react/components/form/ArrayFieldTemplate/ArrayFieldTemplateItem/NoImagePlaceholder.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 |
4 | import IconImage from './icons/IconImage'
5 |
6 | const NoImagePlaceholder = () => (
7 |
8 |
9 |
10 |
14 | {text => {text}
}
15 |
16 |
17 | )
18 |
19 | export default React.memo(NoImagePlaceholder)
20 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/queries/ListFontsQuery.ts:
--------------------------------------------------------------------------------
1 | import { Query, QueryResult } from 'react-apollo'
2 | import ListFonts from '../graphql/ListFonts.graphql'
3 | import { FontFile } from '../mutations/SaveFontFamily'
4 |
5 | export interface FontFamily {
6 | id: string
7 | fontFamily: string
8 | fonts: FontFile[]
9 | }
10 |
11 | export interface ListFontsData {
12 | listFonts: FontFamily[]
13 | }
14 |
15 | export type ListFontsQueryResult = QueryResult
16 |
17 | class ListFontsQuery extends Query {
18 | public static defaultProps = {
19 | query: ListFonts,
20 | }
21 | }
22 |
23 | export default ListFontsQuery
24 |
--------------------------------------------------------------------------------
/react/components/admin/store/PWAForm/queries/PWA.graphql:
--------------------------------------------------------------------------------
1 | query PWA {
2 | # Manifest
3 | manifest {
4 | start_url
5 | theme_color
6 | background_color
7 | display
8 | orientation
9 | icons {
10 | src
11 | type
12 | sizes
13 | }
14 | }
15 | iOSIcons {
16 | src
17 | type
18 | sizes
19 | }
20 | splashes {
21 | src
22 | type
23 | sizes
24 | }
25 | pwaSettings {
26 | disablePrompt
27 | promptOnCustomEvent
28 | }
29 |
30 | # Styles
31 | selectedStyle {
32 | config {
33 | semanticColors {
34 | background {
35 | base
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/react/queries/Route.graphql:
--------------------------------------------------------------------------------
1 | query Route($domain: String, $routeId: String!, $bindingId: String) {
2 | route(domain: $domain, routeId: $routeId, bindingId: $bindingId) {
3 | auth
4 | binding
5 | blockId
6 | context
7 | declarer
8 | domain
9 | interfaceId
10 | path
11 | routeId
12 | uuid
13 | metaTags {
14 | description
15 | keywords
16 | robots
17 | }
18 | pages {
19 | condition {
20 | allMatches
21 | id
22 | statements {
23 | subject
24 | verb
25 | objectJSON
26 | }
27 | }
28 | pageId
29 | template
30 | }
31 | title
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/react/components/icons/PersonIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const PersonIcon: React.FC = () => (
4 |
15 | )
16 |
17 | export default PersonIcon
18 |
--------------------------------------------------------------------------------
/react/components/icons/PageIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const PageIcon: React.FC = () => (
4 |
23 | )
24 |
25 | export default PageIcon
26 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/mutations/RenameStyle.ts:
--------------------------------------------------------------------------------
1 | import { Mutation, MutationFn } from 'react-apollo'
2 | import RenameStyle from '../graphql/RenameStyle.graphql'
3 |
4 | interface RenameStyleData {
5 | renameStyle: {
6 | id: string
7 | }
8 | }
9 |
10 | interface RenameStyleVariables {
11 | id: string
12 | name: string
13 | }
14 |
15 | export type RenameStyleFunction = MutationFn<
16 | RenameStyleData,
17 | RenameStyleVariables
18 | >
19 |
20 | class RenameStyleMutation extends Mutation<
21 | RenameStyleData,
22 | RenameStyleVariables
23 | > {
24 | public static defaultProps = {
25 | mutation: RenameStyle,
26 | }
27 | }
28 |
29 | export default RenameStyleMutation
30 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/components/Typography.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | typography: Font
5 | textColor?: string
6 | }
7 |
8 | const Typography: React.FunctionComponent = ({
9 | typography: {
10 | fontFamily,
11 | fontSize,
12 | fontWeight,
13 | letterSpacing,
14 | textTransform,
15 | },
16 | textColor,
17 | }) => {
18 | return (
19 |
29 | Aa
30 |
31 | )
32 | }
33 |
34 | export default Typography
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: 'New Feature'
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. For example: I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | getAddConditionalTemplateState,
3 | } from './getAddConditionalTemplateState'
4 | export {
5 | getChangeOperatorConditionalTemplateState,
6 | } from './getChangeOperatorConditionalTemplateState'
7 | export {
8 | getChangeStatementsConditionalTemplate,
9 | } from './getChangeStatementsConditionalTemplate'
10 | export {
11 | getChangeTemplateConditionalTemplateState,
12 | } from './getChangeTemplateConditionalTemplateState'
13 | export { getLoginToggleState } from './getLoginToggleState'
14 | export {
15 | getRemoveConditionalTemplateState,
16 | } from './getRemoveConditionalTemplateState'
17 | export { getValidateFormState } from './getValidateFormState'
18 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleList/mutations/DeleteStyle.ts:
--------------------------------------------------------------------------------
1 | import { Mutation, MutationFn } from 'react-apollo'
2 | import DeleteStyle from '../graphql/DeleteStyle.graphql'
3 |
4 | interface DeleteStyleData {
5 | deleteStyle: {
6 | id: string
7 | app: string
8 | name: string
9 | }
10 | }
11 |
12 | interface DeleteStyleVariables {
13 | id: string
14 | }
15 |
16 | export type DeleteStyleMutationFn = MutationFn<
17 | DeleteStyleData,
18 | DeleteStyleVariables
19 | >
20 |
21 | class DeleteStyleMutation extends Mutation<
22 | DeleteStyleData,
23 | DeleteStyleVariables
24 | > {
25 | public static defaultProps = {
26 | mutation: DeleteStyle,
27 | }
28 | }
29 |
30 | export default DeleteStyleMutation
31 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/mutations/UpdateStyle.ts:
--------------------------------------------------------------------------------
1 | import { Mutation, MutationFn } from 'react-apollo'
2 | import UpdateStyle from '../graphql/UpdateStyle.graphql'
3 |
4 | interface UpdateStyleData {
5 | updateStyle: {
6 | path: string
7 | selected: boolean
8 | }
9 | }
10 |
11 | interface UpdateStyleVariables {
12 | id: string
13 | config: TachyonsConfig
14 | }
15 |
16 | export type UpdateStyleFunction = MutationFn<
17 | UpdateStyleData,
18 | UpdateStyleVariables
19 | >
20 |
21 | class UpdateStyleMutation extends Mutation<
22 | UpdateStyleData,
23 | UpdateStyleVariables
24 | > {
25 | public static defaultProps = {
26 | mutation: UpdateStyle,
27 | }
28 | }
29 |
30 | export default UpdateStyleMutation
31 |
--------------------------------------------------------------------------------
/react/utils/auditEvents/index.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from 'uuid'
2 |
3 | export const createEventObject = (
4 | operation: string,
5 | entityName: string,
6 | account: string,
7 | workspace: string,
8 | subjectId?: any
9 | ) => {
10 | const event = {
11 | id: uuidv4(),
12 | date: new Date().toISOString(),
13 | mainAccountName: account,
14 | accountName: account,
15 | subjectId: subjectId ? subjectId : '',
16 | application: 'site-editor',
17 | workspace: workspace,
18 | operation,
19 | meta: {
20 | entityName,
21 | entityBeforeAction: '',
22 | entityAfterAction: '',
23 | remoteIpAddress: '0.0.0.0',
24 | forwardFromVtexUserAgent: 'cms',
25 | },
26 | }
27 |
28 | return event
29 | }
30 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/Transitions/Exit/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { CSSTransition } from 'react-transition-group'
3 |
4 | import { COMMON_PROPS } from '../consts'
5 | import { ExitProps } from '../typings'
6 | import styles from './styles.css'
7 |
8 | const Exit: React.FC = ({ children, condition, to }) => (
9 |
19 | {children}
20 |
21 | )
22 |
23 | export default React.memo(Exit)
24 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleList/mutations/CreateStyle.ts:
--------------------------------------------------------------------------------
1 | import { Mutation, MutationFn } from 'react-apollo'
2 | import CreateStyle from '../graphql/CreateStyle.graphql'
3 |
4 | interface CreateStyleData {
5 | createStyle: {
6 | id: string
7 | app: string
8 | name: string
9 | }
10 | }
11 |
12 | interface CreateStyleVariables {
13 | name: string
14 | config?: TachyonsConfig
15 | }
16 |
17 | export type CreateStyleMutationFn = MutationFn<
18 | CreateStyleData,
19 | CreateStyleVariables
20 | >
21 |
22 | class CreateStyleMutation extends Mutation<
23 | CreateStyleData,
24 | CreateStyleVariables
25 | > {
26 | public static defaultProps = {
27 | mutation: CreateStyle,
28 | }
29 | }
30 |
31 | export default CreateStyleMutation
32 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/getChangeStatementsConditionalTemplate.ts:
--------------------------------------------------------------------------------
1 | import { Statements } from 'pages'
2 |
3 | import { State } from '../index'
4 |
5 | export const getChangeStatementsConditionalTemplate = (
6 | uniqueId: number,
7 | statements: Statements[]
8 | ) => (prevState: State) => {
9 | const newPages = prevState.data.pages.map(page => {
10 | if (page.uniqueId === uniqueId) {
11 | return {
12 | ...page,
13 | condition: {
14 | ...page.condition,
15 | statements,
16 | },
17 | }
18 | }
19 | return page
20 | })
21 |
22 | return {
23 | ...prevState,
24 | data: {
25 | ...prevState.data,
26 | pages: newPages,
27 | },
28 | formErrors: {},
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/DeviceSwitcher/icons/IconMobile.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | color?: string
5 | }
6 |
7 | const IconPhone: React.FC = ({ color = 'currentColor' }) => (
8 |
24 | )
25 |
26 | export default IconPhone
27 |
--------------------------------------------------------------------------------
/.vtex/catalog-info.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: backstage.io/v1alpha1
2 | kind: Component
3 | metadata:
4 | name: admin-pages
5 | description: The Pages Admin is a platform to dynamically edit a VTEX Store,
6 | making it possible to select editable components and change its
7 | configurations adding or removing content in a straightforward way.
8 | tags:
9 | - typescript
10 | - react
11 | annotations:
12 | vtex.com/janus-acronym: ""
13 | vtex.com/o11y-os-index: ""
14 | grafana/dashboard-selector: ""
15 | github.com/project-slug: vtex-apps/admin-pages
16 | vtex.com/platform-flow-id: ""
17 | backstage.io/techdocs-ref: dir:../
18 | vtex.com/application-id: 60JU5OHJ
19 | spec:
20 | lifecycle: stable
21 | owner: te-0013
22 | type: frontend-ui
23 | dependsOn: []
24 |
--------------------------------------------------------------------------------
/react/components/HighlightOverlay/hooks/useStyles/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from 'react'
2 |
3 | import { ObserverReturnType } from '../useResizeObserver'
4 | import { State } from '../../typings'
5 |
6 | interface GetStylesParams {
7 | hasValidElement?: boolean
8 | highlightTreePath: State['highlightTreePath']
9 | visibleElement?: Element
10 | }
11 |
12 | export type GetStyles = (
13 | params: GetStylesParams
14 | ) => {
15 | highlightStyle: CSSProperties
16 | labelStyle: CSSProperties
17 | maskStyle: CSSProperties
18 | }
19 |
20 | interface UseStylesParams extends GetStylesParams, ObserverReturnType {
21 | isOverlayMaskActive: boolean
22 | setState: React.Dispatch>
23 | }
24 |
25 | export type UseStyles = (params: UseStylesParams) => void
26 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/DeviceSwitcher/icons/IconTablet.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | color?: string
5 | }
6 |
7 | const IconTablet: React.FC = ({ color = 'currentColor' }) => (
8 |
24 | )
25 |
26 | export default IconTablet
27 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/graphql/ListContent.graphql:
--------------------------------------------------------------------------------
1 | query ListContent(
2 | $bindingId: String
3 | $blockId: String
4 | $pageContext: PageContextInput
5 | $template: String
6 | $treePath: String
7 | ) {
8 | listContentWithSchema(
9 | bindingId: $bindingId
10 | blockId: $blockId
11 | pageContext: $pageContext
12 | template: $template
13 | treePath: $treePath
14 | ) {
15 | content {
16 | condition {
17 | allMatches
18 | id
19 | pageContext {
20 | id
21 | type
22 | }
23 | statements {
24 | objectJSON
25 | subject
26 | verb
27 | }
28 | }
29 | contentId
30 | contentJSON
31 | label
32 | origin
33 | }
34 | schemaJSON
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/react/utils/AdminLoadingContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { FunctionComponent, useContext } from 'react'
2 |
3 | const adminLoadingDefaultValue = {
4 | startLoading: () => {
5 | window.top.postMessage({ action: { type: 'START_LOADING' } }, '*')
6 | },
7 | stopLoading: () => {
8 | window.top.postMessage({ action: { type: 'STOP_LOADING' } }, '*')
9 | },
10 | }
11 |
12 | const AdminLoadingContext = React.createContext(adminLoadingDefaultValue)
13 |
14 | const useAdminLoadingContext = () => useContext(AdminLoadingContext)
15 |
16 | const AdminLoadingContextProvider: FunctionComponent = ({ children }) => (
17 |
18 | {children}
19 |
20 | )
21 |
22 | export { AdminLoadingContextProvider, useAdminLoadingContext }
23 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleList/mutations/SaveSelectedStyle.ts:
--------------------------------------------------------------------------------
1 | import { Mutation, MutationFn } from 'react-apollo'
2 | import SaveSelectedStyle from '../graphql/SaveSelectedStyle.graphql'
3 |
4 | interface SaveSelectedStyleData {
5 | saveSelectedStyle: {
6 | id: string
7 | app: string
8 | name: string
9 | }
10 | }
11 |
12 | interface SaveSelectedStyleVariables {
13 | id: string
14 | }
15 |
16 | export type SaveSelectedStyleMutationFn = MutationFn<
17 | SaveSelectedStyleData,
18 | SaveSelectedStyleVariables
19 | >
20 |
21 | class SaveSelectedStyleMutation extends Mutation<
22 | SaveSelectedStyleData,
23 | SaveSelectedStyleVariables
24 | > {
25 | public static defaultProps = {
26 | mutation: SaveSelectedStyle,
27 | }
28 | }
29 |
30 | export default SaveSelectedStyleMutation
31 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationList/CreateButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 | import { ButtonWithIcon } from 'vtex.styleguide'
4 |
5 | import AddIcon from '../../../../icons/AddIcon'
6 |
7 | interface Props {
8 | onClick: (event: Event) => void
9 | }
10 |
11 | const CreateButton = ({ onClick }: Props) => (
12 |
13 | }
16 | onClick={onClick}
17 | variation="tertiary"
18 | >
19 |
23 |
24 |
25 | )
26 |
27 | export default React.memo(CreateButton)
28 |
--------------------------------------------------------------------------------
/react/utils/conditions/index.ts:
--------------------------------------------------------------------------------
1 | import { ConditionsStatement } from 'vtex.styleguide'
2 |
3 | import { DateInfo, DateVerbOptions } from './typings'
4 |
5 | const formatDateInfo = (dateInfo: DateInfo, verb: DateVerbOptions) =>
6 | ({
7 | between: {
8 | from: dateInfo.from,
9 | to: dateInfo.to,
10 | },
11 | from: {
12 | from: dateInfo.date,
13 | },
14 | is: {
15 | from: dateInfo.date,
16 | },
17 | to: {
18 | to: dateInfo.date,
19 | },
20 | }[verb])
21 |
22 | export const formatStatements = (statements: ConditionsStatement[]) =>
23 | statements.map(({ object, subject, verb }) => ({
24 | objectJSON: JSON.stringify(
25 | formatDateInfo(object as DateInfo, verb as DateVerbOptions)
26 | ),
27 | subject: subject as ConditionSubject,
28 | verb,
29 | }))
30 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/graphql/SaveContent.graphql:
--------------------------------------------------------------------------------
1 | mutation saveContent(
2 | $bindingId: String
3 | $blockId: String
4 | $configuration: ContentConfigurationInput
5 | $template: String
6 | $treePath: String
7 | $lang: String
8 | ) {
9 | saveContent(
10 | bindingId: $bindingId
11 | blockId: $blockId
12 | configuration: $configuration
13 | template: $template
14 | treePath: $treePath
15 | lang: $lang
16 | ) {
17 | # Available fields:
18 | # condition {
19 | # allMatches
20 | # id
21 | # pageContext {
22 | # id
23 | # type
24 | # }
25 | # statements {
26 | # objectJSON
27 | # subject
28 | # verb
29 | # }
30 | # }
31 | # contentJSON
32 | # contentId
33 | # label
34 | contentId
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/react/components/form/ArrayFieldTemplate/AddButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 | import { ButtonWithIcon } from 'vtex.styleguide'
4 |
5 | import AddIcon from '../../icons/AddIcon'
6 |
7 | interface Props {
8 | onClick: (event: Event) => void
9 | }
10 |
11 | const icon =
12 |
13 | const AddButton: React.FC = ({ onClick }) => (
14 |
15 |
16 |
20 | {text => {text}
}
21 |
22 |
23 |
24 | )
25 |
26 | export default AddButton
27 |
--------------------------------------------------------------------------------
/react/components/form/ArrayFieldTemplate/ArrayFieldTemplateItem/ArrayFieldTemplateItem.css:
--------------------------------------------------------------------------------
1 | .action-menu-container {
2 | opacity: 0;
3 | transition: opacity ease-in 100ms;
4 | }
5 |
6 | .preview-container:hover .action-menu-container {
7 | opacity: 1;
8 | }
9 |
10 | .preview-container {
11 | height: 100%;
12 | }
13 |
14 | .multi-item {
15 | width: calc(100% - 3rem);
16 | }
17 |
18 | .single-item {
19 | width: calc(100% - 1rem);
20 | }
21 |
22 | .preview-text-container {
23 | width: 100%;
24 | }
25 |
26 | .preview-image {
27 | object-fit: cover;
28 | transition: filter ease-in 100ms;
29 | }
30 |
31 | .preview-overlay {
32 | background-color: black;
33 | opacity: 0;
34 | transition: opacity ease-in 100ms;
35 | will-change: opacity;
36 | }
37 |
38 | .preview-container:hover .preview-overlay {
39 | opacity: 0.15;
40 | }
41 |
--------------------------------------------------------------------------------
/react/components/form/FieldTemplate.tsx:
--------------------------------------------------------------------------------
1 | import { JSONSchema6 } from 'json-schema'
2 | import React from 'react'
3 | import { FieldTemplateProps } from 'react-jsonschema-form'
4 |
5 | interface Props {
6 | children?: React.ReactNode
7 | classNames: string
8 | hidden?: boolean
9 | schema: JSONSchema6
10 | }
11 |
12 | const FieldTemplate: React.FunctionComponent = ({
13 | children,
14 | classNames,
15 | hidden,
16 | schema,
17 | }) => {
18 | const isHidden =
19 | hidden || (schema.type !== 'object' && (schema as ComponentSchema).isLayout)
20 |
21 | if (isHidden) {
22 | return null
23 | }
24 |
25 | return {children}
26 | }
27 |
28 | FieldTemplate.defaultProps = {
29 | classNames: '',
30 | hidden: false,
31 | }
32 |
33 | export default FieldTemplate
34 |
--------------------------------------------------------------------------------
/react/components/icons/TemplateIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const TemplateIcon: React.FC = () => (
4 | // is used for making sure that the icon isn't vertically centered
5 |
6 |
25 |
26 | )
27 |
28 | export default TemplateIcon
29 |
--------------------------------------------------------------------------------
/react/components/DomainMessages/typings.d.ts:
--------------------------------------------------------------------------------
1 | import ApolloClient from 'apollo-client'
2 | import { InjectedIntl } from 'react-intl'
3 |
4 | export interface Props {
5 | runtime: RenderContext
6 | domain: string
7 | client: ApolloClient
8 | }
9 |
10 | export interface Message {
11 | key: string
12 | message: string
13 | }
14 |
15 | export interface Data {
16 | messages: Message[]
17 | }
18 |
19 | export interface Variables {
20 | components: string[]
21 | domain: string
22 | renderMajor: number
23 | }
24 |
25 | export interface AvailableCulturesProps {
26 | client: ApolloClient
27 | intl: InjectedIntl
28 | }
29 |
30 | export interface Languages {
31 | languages: {
32 | default: string
33 | supported: string[]
34 | }
35 | }
36 |
37 | export interface LabelledLocale {
38 | label: string
39 | value: string
40 | }
41 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/getChangeOperatorConditionalTemplateState.ts:
--------------------------------------------------------------------------------
1 | import { ConditionsProps } from 'vtex.styleguide'
2 |
3 | import { State } from '../index'
4 |
5 | export const getChangeOperatorConditionalTemplateState = (
6 | uniqueId: number,
7 | operator: NonNullable
8 | ) => (prevState: State) => {
9 | const newPages = prevState.data.pages.map(page => {
10 | if (page.uniqueId === uniqueId) {
11 | return {
12 | ...page,
13 | condition: {
14 | ...page.condition,
15 | allMatches: operator === 'all',
16 | },
17 | operator,
18 | }
19 | }
20 | return page
21 | })
22 |
23 | return {
24 | ...prevState,
25 | data: {
26 | ...prevState.data,
27 | pages: newPages,
28 | },
29 | formErrors: {},
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/Transitions/Enter/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { CSSTransition } from 'react-transition-group'
3 |
4 | import { COMMON_PROPS } from '../consts'
5 | import { EnterProps } from '../typings'
6 | import styles from './styles.css'
7 |
8 | const Enter: React.FC = ({ children, condition, from }) => (
9 |
21 | {children}
22 |
23 | )
24 |
25 | export default React.memo(Enter)
26 |
--------------------------------------------------------------------------------
/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "alwaysStrict": true,
4 | "esModuleInterop": true,
5 | "jsx": "preserve",
6 | "lib": [
7 | "es2019",
8 | "dom",
9 | "es2018.promise",
10 | "esnext.asynciterable"
11 | ],
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "noImplicitAny": true,
15 | "noImplicitReturns": true,
16 | "noImplicitThis": true,
17 | "noUnusedLocals": true,
18 | "noUnusedParameters": true,
19 | "skipLibCheck": true,
20 | "sourceMap": true,
21 | "strictFunctionTypes": true,
22 | "strictNullChecks": true,
23 | "strictPropertyInitialization": true,
24 | "target": "es2017"
25 | },
26 | "include": [
27 | "./typings/*.d.ts",
28 | "./**/*.tsx",
29 | "./**/*.ts"
30 | ],
31 | "typeAcquisition": {
32 | "enable": false
33 | }
34 | }
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/ContextSelectors/graphql/GetRoute.graphql:
--------------------------------------------------------------------------------
1 | query GetRoute($routeId: String!) {
2 | route(routeId: $routeId, domain: "store") {
3 | auth
4 | blockId
5 | binding
6 | context
7 | declarer
8 | domain
9 | interfaceId
10 | conflicts {
11 | binding
12 | blockId
13 | interfaceId
14 | routeId
15 | }
16 | pages {
17 | pageId
18 | condition {
19 | id
20 | pageContext {
21 | id
22 | type
23 | }
24 | allMatches
25 | statements {
26 | subject
27 | verb
28 | objectJSON
29 | }
30 | }
31 | template
32 | }
33 | path
34 | routeId
35 | title
36 | uuid
37 | metaTags {
38 | description
39 | keywords
40 | robots
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/react/components/form/ObjectFieldTemplate.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { ObjectFieldTemplateProps } from 'react-jsonschema-form'
3 |
4 | const hasFieldToBeDisplayed = (field: ComponentSchema): boolean => {
5 | if (field.type !== 'object') {
6 | return !field.isLayout
7 | }
8 |
9 | return Object.values(field.properties || {}).reduce(
10 | (acc, currValue) => acc || hasFieldToBeDisplayed(currValue),
11 | false
12 | )
13 | }
14 |
15 | const ObjectFieldTemplate: React.FunctionComponent<
16 | ObjectFieldTemplateProps
17 | > = ({ properties, schema }) =>
18 | hasFieldToBeDisplayed(schema as ComponentSchema) ? (
19 | {properties.map(property => property.content)}
20 | ) : null
21 |
22 | ObjectFieldTemplate.defaultProps = {
23 | properties: [],
24 | }
25 |
26 | export default ObjectFieldTemplate
27 |
--------------------------------------------------------------------------------
/react/typings/draft-js.d.ts:
--------------------------------------------------------------------------------
1 | import 'draft-js'
2 | declare module 'draft-js' {
3 | export interface Media {
4 | block: ContentBlock
5 | blockProps: { [key: string]: unknown } | undefined
6 | blockStyleFn?: (block: ContentBlock) => string
7 | contentState: ContentState
8 | customStyleFn?: (
9 | style: DraftInlineStyle,
10 | block: ContentBlock
11 | ) => DraftStyleMap
12 | customStyleMap: DraftStyleMap
13 | decorator: CompositeDecorator
14 | direction: string
15 | forceSelection: boolean
16 | offsetKey: string
17 | selection: SelectionState
18 | tree: Immutable.List
19 | }
20 |
21 | export interface Link {
22 | children: React.ElementType[]
23 | contentState: ContentState
24 | decoratedText: string
25 | dir: string | null
26 | entityKey: string
27 | offsetKey: string
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/react/components/ActionMenu/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | ActionMenu as StyleguideActionMenu,
4 | IconOptionsDots,
5 | } from 'vtex.styleguide'
6 |
7 | import { ActionMenuOption } from './typings'
8 |
9 | interface Props {
10 | buttonSize?: string
11 | menuWidth?: number | string
12 | options: ActionMenuOption[]
13 | variation?: string
14 | }
15 |
16 | const icon =
17 |
18 | const ActionMenu: React.FunctionComponent = ({
19 | buttonSize,
20 | menuWidth,
21 | options,
22 | variation = 'tertiary',
23 | }) => (
24 |
35 | )
36 |
37 | export default ActionMenu
38 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/DeviceSwitcher/consts.ts:
--------------------------------------------------------------------------------
1 | import IconDesktop from './icons/IconDesktop'
2 | import IconMobile from './icons/IconMobile'
3 | import IconTablet from './icons/IconTablet'
4 |
5 | const AVAILABLE_VIEWPORTS: Viewport[] = ['mobile', 'tablet', 'desktop']
6 |
7 | const AVAILABLE_MOBILE_VIEWPORTS: Viewport[] = ['mobile', 'tablet']
8 |
9 | export const BORDER_BY_POSITION = {
10 | first: 'br2 br--left b--transparent',
11 | last: 'br2 br--right b--transparent',
12 | middle: 'b--transparent',
13 | }
14 |
15 | export const ICON_BY_VIEWPORT = {
16 | desktop: IconDesktop,
17 | mobile: IconMobile,
18 | tablet: IconTablet,
19 | }
20 |
21 | export const VIEWPORTS_BY_DEVICE: Record<
22 | RenderContext['device'],
23 | Viewport[]
24 | > = {
25 | any: AVAILABLE_VIEWPORTS,
26 | desktop: AVAILABLE_VIEWPORTS,
27 | mobile: AVAILABLE_MOBILE_VIEWPORTS,
28 | }
29 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/DeviceSwitcher/icons/IconDesktop.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | color?: string
5 | }
6 |
7 | const IconDesktop: React.FC = ({ color = 'currentColor' }) => (
8 |
30 | )
31 |
32 | export default IconDesktop
33 |
--------------------------------------------------------------------------------
/react/components/icons/ContentScheduledIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const ContentScheduledIcon: React.FC = () => (
4 |
31 | )
32 |
33 | export default ContentScheduledIcon
34 |
--------------------------------------------------------------------------------
/react/components/HighlightOverlay/HighlightOverlay.css:
--------------------------------------------------------------------------------
1 | .highlight-enter {
2 | opacity: 0;
3 | border-color: rgba(19, 76, 216, 0);
4 | }
5 |
6 | .highlight-enter-active {
7 | transition: opacity 150ms cubic-bezier(0.19, 1, 0.22, 1),
8 | border-color 150ms cubic-bezier(0.19, 1, 0.22, 1);
9 | opacity: 1;
10 | border-color: rgba(19, 76, 216, 1);
11 | }
12 |
13 | .highlight-enter-done {
14 | opacity: 1;
15 | border-color: rgba(19, 76, 216, 1);
16 | }
17 |
18 | .highlight-exit {
19 | opacity: 1;
20 | border-color: rgba(19, 76, 216, 1);
21 | }
22 |
23 | .highlight-exit-active {
24 | transition: opacity 150ms cubic-bezier(0.215, 0.61, 0.355, 1),
25 | border-color 150ms cubic-bezier(0.215, 0.61, 0.355, 1);
26 | opacity: 0;
27 | border-color: rgba(19, 76, 216, 0);
28 | }
29 |
30 | .highlight-exit-done {
31 | opacity: 0;
32 | border-color: rgba(19, 76, 216, 0);
33 | }
34 |
--------------------------------------------------------------------------------
/react/RedirectListWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Query } from 'react-apollo'
3 | import { Tenant } from 'vtex.tenant-graphql'
4 |
5 | import Loader from './components/Loader'
6 | import TenantInfo from './queries/TenantInfo.graphql'
7 | import RedirectList from './RedirectList'
8 | import { getStoreBindings } from './utils/bindings'
9 |
10 | const RedirectListWrapper = () => {
11 | return (
12 | query={TenantInfo}>
13 | {({ data, loading: isLoading }) => {
14 | const tenantInfo = data?.tenantInfo
15 | if (isLoading || !tenantInfo) {
16 | return
17 | }
18 | const storeBindings = getStoreBindings(tenantInfo)
19 | return 1} />
20 | }}
21 |
22 | )
23 | }
24 |
25 | export default React.memo(RedirectListWrapper)
26 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/ContextSelectors/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { QueryResult } from 'react-apollo'
2 |
3 | export type DropdownOptions = {
4 | label: string
5 | value: string
6 | }[]
7 |
8 | export type DropdownValue = string
9 |
10 | type BaseAddress = string
11 |
12 | type Locale = string
13 |
14 | interface Binding {
15 | canonicalBaseAddress: string
16 | id: string
17 | supportedLocales: Locale[]
18 | targetProduct: string
19 | }
20 |
21 | interface TenantInfo {
22 | bindings: Binding[]
23 | }
24 |
25 | export interface TenantInfoData {
26 | tenantInfo: TenantInfo
27 | }
28 |
29 | interface QueryState extends Pick, 'data'> {
30 | hasError: boolean
31 | isLoading: QueryResult['loading']
32 | }
33 |
34 | export type QueryStateReducer = (
35 | prevState: QueryState,
36 | state: Partial
37 | ) => QueryState
38 |
--------------------------------------------------------------------------------
/docs/CONTENT_PAGE.md:
--------------------------------------------------------------------------------
1 | # VTEX Custom Content
2 |
3 | ## Description
4 |
5 | This is just an easier way to create content pages in your store. It provides an editor for you to create new routes and their content in a single form. You can edit the content using the Side Editor later as well.
6 |
7 | :loudspeaker: **Disclaimer:** Don't fork this project; use, contribute, and/or open issues with your feature requests.
8 |
9 | ## Usage
10 |
11 | You must add `store.content` to your `blocks.json` just like the example below:
12 |
13 | ```json
14 | "store.content": {
15 | "children": ["flex-layout.row#content-body"]
16 | },
17 | "flex-layout.row#content-body": {
18 | "children": ["rich-text"]
19 | }
20 | ```
21 |
22 | ## Continuous Integrations
23 |
24 | ### Travis CI
25 |
26 | ](https://travis-ci.org/vtex-apps/pages-editor)
27 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationList/Card/ConditionTags/Tag.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Tag as StyleguideTag } from 'vtex.styleguide'
3 |
4 | import CalendarIcon from '../../../../../../icons/CalendarIcon'
5 | import PersonIcon from '../../../../../../icons/PersonIcon'
6 |
7 | interface Props {
8 | kind: ConditionSubject
9 | text: string
10 | title: string
11 | }
12 |
13 | const iconByKind = {
14 | date: ,
15 | utm: ,
16 | }
17 |
18 | const Tag: React.FunctionComponent = ({ kind, text, title }) => (
19 |
20 |
21 | {iconByKind[kind]}
22 |
23 | {title}
24 |
25 | {text}
26 |
27 |
28 | )
29 |
30 | export default React.memo(Tag)
31 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/AbsoluteLoader.tsx:
--------------------------------------------------------------------------------
1 | import classnames from 'classnames'
2 | import React from 'react'
3 | import { Spinner } from 'vtex.styleguide'
4 |
5 | import { useEditorContext } from '../../EditorContext'
6 |
7 | interface Props {
8 | containerClassName?: string
9 | }
10 |
11 | const AbsoluteLoader: React.FunctionComponent = ({
12 | children,
13 | containerClassName,
14 | }) => {
15 | const editor = useEditorContext()
16 |
17 | const isLoading = editor.getIsLoading()
18 |
19 | return (
20 | <>
21 | {isLoading ? (
22 |
23 |
24 |
25 | ) : null}
26 |
27 |
28 | {children}
29 |
30 | >
31 | )
32 | }
33 |
34 | export default React.memo(AbsoluteLoader)
35 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/List/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Query } from 'react-apollo'
3 | import { Tenant } from 'vtex.tenant-graphql'
4 |
5 | import Loader from '../../../Loader'
6 | import List, { Props } from './List'
7 | import TenantInfo from '../../../../queries/TenantInfo.graphql'
8 | import { getStoreBindings } from '../../../../utils/bindings'
9 |
10 | const ListWrapper: React.FC> = props => {
11 | return (
12 | query={TenantInfo}>
13 | {({ data, loading: isLoading }) => {
14 | const tenantInfo = data?.tenantInfo
15 | if (isLoading || !tenantInfo) {
16 | return
17 | }
18 | const storeBindings = getStoreBindings(tenantInfo)
19 | return
20 | }}
21 |
22 | )
23 | }
24 |
25 | export default ListWrapper
26 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/mutations/DeleteContent.tsx:
--------------------------------------------------------------------------------
1 | import { Mutation, MutationFn, MutationResult } from 'react-apollo'
2 | import DeleteContent from '../graphql/DeleteContent.graphql'
3 |
4 | export interface DeleteContentData {
5 | deleteContent: string
6 | }
7 |
8 | interface DeleteContentVariables {
9 | pageContext: PageContext
10 | contentId: string | null
11 | template: string
12 | treePath: string
13 | }
14 |
15 | export type DeleteContentMutationFn = MutationFn<
16 | DeleteContentData,
17 | DeleteContentVariables
18 | >
19 |
20 | export interface MutationRenderProps extends MutationResult {
21 | deleteContent: DeleteContentMutationFn
22 | }
23 |
24 | class DeleteContentMutation extends Mutation<
25 | DeleteContentData,
26 | DeleteContentVariables
27 | > {
28 | public static defaultProps = {
29 | mutation: DeleteContent,
30 | }
31 | }
32 |
33 | export default DeleteContentMutation
34 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/queries/ListContent.tsx:
--------------------------------------------------------------------------------
1 | import { Query, QueryResult } from 'react-apollo'
2 | import ListContent from '../graphql/ListContent.graphql'
3 |
4 | export const ListContentGraphqlDocument = ListContent
5 |
6 | export interface ListContentVariables {
7 | bindingId?: string
8 | blockId: string
9 | pageContext: { id: string; type: string }
10 | template: string
11 | treePath: string
12 | }
13 |
14 | export interface ListContentData {
15 | listContentWithSchema?: {
16 | content?: ExtensionConfiguration[]
17 | schemaJSON: string
18 | }
19 | }
20 |
21 | export type ListContentQueryResult = QueryResult<
22 | ListContentData,
23 | ListContentVariables
24 | >
25 |
26 | class ListContentQuery extends Query {
27 | public static defaultProps = {
28 | fetchPolicy: 'network-only',
29 | query: ListContent,
30 | }
31 | }
32 |
33 | export default ListContentQuery
34 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/utils/colors.ts:
--------------------------------------------------------------------------------
1 | import { forEachObjIndexed } from 'ramda'
2 |
3 | const fromSemanticColors = (semanticColors: SemanticColors): Colors => {
4 | const getFieldPath = (name: string) => {
5 | let fieldPath = name.split('_')
6 | if (fieldPath.length === 1) {
7 | fieldPath = ['default'].concat(fieldPath)
8 | }
9 | return fieldPath.reverse().join('.')
10 | }
11 |
12 | const colors: Colors = {}
13 | forEachObjIndexed((tokens: Tokens, field: string) => {
14 | forEachObjIndexed((color: string, token: string) => {
15 | if (token.startsWith('__')) {
16 | return
17 | }
18 | const info: ColorInfo = {
19 | color,
20 | configField: field,
21 | path: getFieldPath(field),
22 | }
23 | colors[token] = (colors[token] || []).concat(info)
24 | }, tokens)
25 | }, semanticColors)
26 |
27 | return colors
28 | }
29 |
30 | export default fromSemanticColors
31 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationEditor/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react'
2 |
3 | import { ComponentFormState } from './typings'
4 |
5 | export function useComponentFormStateStack() {
6 | const [componentFormState, setComponentFormState] = useState<
7 | ComponentFormState | undefined
8 | >()
9 | const stack = useRef([])
10 |
11 | function popComponentFormState() {
12 | stack.current.pop()
13 |
14 | setComponentFormState(stack.current[stack.current.length - 1])
15 | }
16 |
17 | function pushComponentFormState(state: ComponentFormState) {
18 | const stateWithDepth = { ...state, depth: stack.current.length + 1 }
19 |
20 | stack.current.push(stateWithDepth)
21 | setComponentFormState(stateWithDepth)
22 | }
23 |
24 | return {
25 | currentDepth: stack.current.length,
26 | componentFormState,
27 | popComponentFormState,
28 | pushComponentFormState,
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/ContextSelectors/hooks/useBinding.ts:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 |
3 | import { Binding } from '../typings'
4 |
5 | const LAST_BINDING_KEY = 'vtex.site-editor.lastBinding'
6 |
7 | export const useBinding = (): [
8 | Binding | undefined,
9 | React.Dispatch
10 | ] => {
11 | const [binding, setBinding] = React.useState(() => {
12 | const lastBinding = window.localStorage.getItem(LAST_BINDING_KEY)
13 | return lastBinding ? (JSON.parse(lastBinding) as Binding) : undefined
14 | })
15 |
16 | return [
17 | binding,
18 | useMemo(() => {
19 | return (value: Binding | undefined) => {
20 | if (value) {
21 | window.localStorage.setItem(LAST_BINDING_KEY, JSON.stringify(value))
22 | } else {
23 | window.localStorage.removeItem(LAST_BINDING_KEY)
24 | }
25 | setBinding(value)
26 | }
27 | }, [setBinding]),
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/react/typings/pages.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'pages' {
2 | import { ConditionsProps } from 'vtex.styleguide'
3 |
4 | type ConditionsOperators = NonNullable
5 |
6 | interface Statements {
7 | subject: string
8 | verb: string
9 | object: Record
10 | error: unknown
11 | }
12 |
13 | interface ConditionFormsData {
14 | id?: string
15 | allMatches: boolean
16 | statements: Statements[]
17 | }
18 |
19 | type PagesFormData = Omit & {
20 | condition: ConditionFormsData
21 | uniqueId: number
22 | operator: ConditionsOperators
23 | template: string
24 | }
25 |
26 | interface KeywordsFormData {
27 | value: string
28 | label: string
29 | }
30 |
31 | type RouteFormData = Omit & {
32 | pages: PagesFormData[]
33 | metaTagDescription?: string
34 | metaTagRobots?: string
35 | metaTagKeywords?: KeywordsFormData[]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/react/components/form/ImageUploader/EmptyState.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 |
4 | import IconImage from '../../MediaGalleryWidget/icons/IconImage'
5 | import styles from './styles.css'
6 |
7 | const EmptyState = ({ style }: { style?: React.CSSProperties }) => {
8 | return (
9 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | )
25 | }
26 |
27 | export default React.memo(EmptyState)
28 |
--------------------------------------------------------------------------------
/react/components/admin/pages/utils.ts:
--------------------------------------------------------------------------------
1 | import startCase from 'lodash/startCase'
2 |
3 | type NewRouteTypeArg = Pick
4 | export const isNewRoute = (route: NewRouteTypeArg) =>
5 | !route.uuid && !route.declarer
6 |
7 | export const isUserRoute = (route: NewRouteTypeArg) => !route.declarer
8 |
9 | type GetRouteTitleArg = Pick &
10 | NewRouteTypeArg
11 |
12 | export const getRouteTitle = (route: GetRouteTitleArg) => {
13 | const { blockId, declarer, interfaceId, title } = route
14 |
15 | const nameFromInterfaceOrBlock = startCase(
16 | blockId.split('#')[1] ||
17 | interfaceId.split('.')[interfaceId.split('.').length - 1]
18 | )
19 |
20 | if (isNewRoute(route)) {
21 | return title || ''
22 | }
23 |
24 | return declarer
25 | ? nameFromInterfaceOrBlock || title
26 | : title || nameFromInterfaceOrBlock
27 | }
28 |
29 | export const isStoreRoute = (domain: Route['domain']) => domain === 'store'
30 |
--------------------------------------------------------------------------------
/react/components/form/Toggle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useIntl } from 'react-intl'
3 | import { formatIOMessage } from 'vtex.native-types'
4 | import { Toggle as StyleguideToggle } from 'vtex.styleguide'
5 |
6 | import { CustomWidgetProps } from './typings'
7 |
8 | const Toggle: React.FunctionComponent = ({
9 | disabled,
10 | id,
11 | label,
12 | onChange,
13 | readonly,
14 | schema: { disabled: disabledBySchema },
15 | value,
16 | }) => {
17 | const intl = useIntl()
18 |
19 | return (
20 | ) =>
26 | onChange(event.target.checked)
27 | }
28 | />
29 | )
30 | }
31 |
32 | Toggle.defaultProps = {
33 | disabled: false,
34 | readonly: false,
35 | }
36 |
37 | export default Toggle
38 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/icons/IconView.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | color?: string
5 | }
6 |
7 | const IconView: React.FC = ({ color = 'currentColor' }) => (
8 |
32 | )
33 |
34 | export default IconView
35 |
--------------------------------------------------------------------------------
/react/components/EditorContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from 'react'
2 |
3 | export const initialEditorContextState: EditorContextType = {
4 | activeConditions: [],
5 | addCondition: () => {},
6 | allMatches: true,
7 | availableCultures: [],
8 | blockData: {},
9 | editExtensionPoint: () => {},
10 | editMode: false,
11 | editTreePath: null,
12 | getIsLoading: () => false,
13 | iframeWindow: window.self,
14 | isSidebarVisible: true,
15 | messages: {},
16 | mode: 'content',
17 | onChangeIframeUrl: () => {},
18 | setDevice: () => {},
19 | setIsLoading: () => {},
20 | setMode: () => {},
21 | setBlockData: () => {},
22 | setViewport: () => {},
23 | toggleEditMode: () => {},
24 | toggleSidebarVisibility: () => {},
25 | viewport: 'desktop',
26 | }
27 |
28 | export const EditorContext = createContext(initialEditorContextState)
29 |
30 | EditorContext.displayName = 'EditorContext'
31 |
32 | export const useEditorContext = () => useContext(EditorContext)
33 |
--------------------------------------------------------------------------------
/react/components/icons/AddIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const AddIcon: React.FunctionComponent = () => (
4 |
30 | )
31 |
32 | export default AddIcon
33 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/utils.test.ts:
--------------------------------------------------------------------------------
1 | import EXTENSIONS from './__fixtures__/extensions'
2 | import { getIsSitewide } from './utils'
3 |
4 | describe('getIsSitewide', () => {
5 | it('should return true for AFTER', () => {
6 | const mockEditTreePath = 'store.home/$after_footer'
7 |
8 | expect(getIsSitewide(EXTENSIONS, mockEditTreePath)).toBe(true)
9 | })
10 |
11 | it('should return true for AROUND', () => {
12 | const mockEditTreePath = 'store.home/$around_homeWrapper'
13 |
14 | expect(getIsSitewide(EXTENSIONS, mockEditTreePath)).toBe(true)
15 | })
16 |
17 | it('should return true for BEFORE', () => {
18 | const mockEditTreePath = 'store.home/$before_header.full'
19 |
20 | expect(getIsSitewide(EXTENSIONS, mockEditTreePath)).toBe(true)
21 | })
22 |
23 | it('should return false for blocks without role', () => {
24 | const mockEditTreePath = 'store.home/carousel#home'
25 |
26 | expect(getIsSitewide(EXTENSIONS, mockEditTreePath)).toBe(false)
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/mutations/SaveRedirectFromFile.tsx:
--------------------------------------------------------------------------------
1 | import { Mutation, MutationFn, MutationResult } from 'react-apollo'
2 |
3 | import SaveRedirectFromFile from './SaveRedirectFromFile.graphql'
4 |
5 | interface SaveRedirectFromFileData {
6 | saveRedirectFromFile: boolean
7 | }
8 |
9 | export type UploadActionType = 'save' | 'delete'
10 |
11 | interface SaveRedirectFromFileVariables {
12 | redirects: Redirect[]
13 | }
14 |
15 | type SaveRedirectFromFileMutationFn = MutationFn<
16 | SaveRedirectFromFileData,
17 | SaveRedirectFromFileVariables
18 | >
19 |
20 | export interface MutationRenderProps
21 | extends MutationResult {
22 | saveRedirectFromFile: SaveRedirectFromFileMutationFn
23 | }
24 |
25 | class SaveRedirectFromFileMutation extends Mutation<
26 | SaveRedirectFromFileData,
27 | SaveRedirectFromFileVariables
28 | > {
29 | public static defaultProps = {
30 | mutation: SaveRedirectFromFile,
31 | }
32 | }
33 |
34 | export default SaveRedirectFromFileMutation
35 |
--------------------------------------------------------------------------------
/react/components/icons/EarthIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const EarthIcon: React.FC = () => (
4 |
15 | )
16 |
17 | export default EarthIcon
18 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/icons/IconPicker.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | color?: string
5 | }
6 |
7 | const IconEdit: React.FC = ({ color = 'currentColor' }) => (
8 |
29 | )
30 |
31 | export default IconEdit
32 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/icons/CopyContent.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | color?: string
5 | }
6 |
7 | const CopyContent = ({ color = 'currentColor' }: Props) => (
8 |
31 | )
32 |
33 | export default CopyContent
34 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/mutations/SaveContent.tsx:
--------------------------------------------------------------------------------
1 | import { Mutation, MutationFn, MutationResult } from 'react-apollo'
2 | import SaveContent from '../graphql/SaveContent.graphql'
3 |
4 | interface SaveContentData {
5 | saveContent: Pick
6 | }
7 |
8 | interface SaveContentVariables {
9 | bindingId?: string
10 | blockId?: string
11 | configuration: Omit & {
12 | contentId: string | null
13 | }
14 | lang: RenderContext['culture']['locale']
15 | template: string
16 | treePath: string
17 | }
18 |
19 | export type SaveContentMutationFn = MutationFn<
20 | SaveContentData,
21 | SaveContentVariables
22 | >
23 |
24 | export interface MutationRenderProps extends MutationResult {
25 | SaveContent: SaveContentMutationFn
26 | }
27 |
28 | class SaveContentMutation extends Mutation<
29 | SaveContentData,
30 | SaveContentVariables
31 | > {
32 | public static defaultProps = {
33 | mutation: SaveContent,
34 | }
35 | }
36 |
37 | export default SaveContentMutation
38 |
--------------------------------------------------------------------------------
/react/components/admin/institutional/Form/UnallowedWarning.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 | import { Button, EmptyState } from 'vtex.styleguide'
4 |
5 | const UnallowedWarning = () => (
6 |
12 | }
13 | >
14 |
15 | {message => {message}
}
16 |
17 |
18 |
29 |
30 |
31 | )
32 |
33 | export default UnallowedWarning
34 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/consts.ts:
--------------------------------------------------------------------------------
1 | export const BASE_URL = '/admin/app/cms/redirects'
2 | export const CSV_SEPARATOR = ';'
3 |
4 | export const getCSVHeader = (hasMultipleBindings: boolean) =>
5 | hasMultipleBindings ? 'from;to;type;binding;endDate' : 'from;to;type;endDate'
6 |
7 | const CSV_SINGLE_BINDING_TEMPLATE = `/temporary;/;TEMPORARY;
8 | /temporary-with-date;/;TEMPORARY;2025-01-01T00:00:00.000Z;
9 | /permanent;/;PERMANENT;
10 | `
11 | const CSV_MULTIPLE_BINDING_TEMPLATE = `/temporary;/;TEMPORARY;
12 | /temporary-with-date;/;TEMPORARY;1234_binding_id;2025-01-01T00:00:00.000Z;
13 | /permanent;/;PERMANENT;123_binding_id;
14 | `
15 |
16 | export const getCSVTemplate = (hasMultipleBindings: boolean) =>
17 | `${getCSVHeader(hasMultipleBindings)}
18 | ${
19 | hasMultipleBindings
20 | ? CSV_MULTIPLE_BINDING_TEMPLATE
21 | : CSV_SINGLE_BINDING_TEMPLATE
22 | }`
23 |
24 | export const NEW_REDIRECT_ID = 'new'
25 |
26 | export const PAGINATION_START = 0
27 | export const PAGINATION_STEP = 10
28 | export const REDIRECTS_LIMIT = 500
29 |
30 | export const WRAPPER_PATH = 'redirects'
31 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/typography/TypeTokensEntry.tsx:
--------------------------------------------------------------------------------
1 | import { useKeydownFromClick } from 'keydown-from-click'
2 | import startCase from 'lodash/startCase'
3 | import React from 'react'
4 | import { RouteComponentProps } from 'react-router'
5 |
6 | import { EditorPath, IdParam } from '../StyleEditorRouter'
7 |
8 | interface EntryProps {
9 | name: string
10 | history: RouteComponentProps['history']
11 | }
12 |
13 | const TypeTokenEntry: React.FunctionComponent = ({
14 | name,
15 | history,
16 | }) => {
17 | const handleClick = React.useCallback(
18 | () => history.push(EditorPath.typeToken.replace(IdParam, name)),
19 | [history, name]
20 | )
21 |
22 | const handleKeyDown = useKeydownFromClick(handleClick)
23 |
24 | return (
25 |
32 | {startCase(name)}
33 |
34 | )
35 | }
36 |
37 | export default TypeTokenEntry
38 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/typings/config.d.ts:
--------------------------------------------------------------------------------
1 | import {
2 | FontFamilyProperty,
3 | FontSizeProperty,
4 | FontWeightProperty,
5 | LetterSpacingProperty,
6 | TextTransformProperty,
7 | } from 'csstype'
8 |
9 | declare global {
10 | interface Font {
11 | fontFamily: FontFamilyProperty
12 | fontWeight: FontWeightProperty
13 | fontSize: FontSizeProperty
14 | textTransform: TextTransformProperty
15 | letterSpacing: LetterSpacingProperty
16 | }
17 |
18 | interface Tokens {
19 | [token: string]: string
20 | }
21 |
22 | interface SemanticColors {
23 | [field: string]: Tokens
24 | }
25 |
26 | interface TypographyStyles {
27 | heading_1: Font
28 | heading_2: Font
29 | heading_3: Font
30 | heading_4: Font
31 | heading_5: Font
32 | heading_6: Font
33 | body: Font
34 | small: Font
35 | mini: Font
36 | action: Font
37 | action__small: Font
38 | action__large: Font
39 | code: Font
40 | }
41 |
42 | interface TachyonsConfig {
43 | semanticColors: SemanticColors
44 | typography: {
45 | styles: TypographyStyles
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/react/components/admin/TargetPathContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { ComponentType, useContext } from 'react'
2 |
3 | export interface TargetPathContextProps {
4 | targetPath: string
5 | setTargetPath: (s: string) => void
6 | }
7 |
8 | const TargetPathContext = React.createContext({
9 | setTargetPath: () => undefined,
10 | targetPath: '',
11 | })
12 | const useTargetPathContext = () => useContext(TargetPathContext)
13 |
14 | const withTargetPath = (
15 | Component: ComponentType
16 | ): ComponentType => {
17 | const extendedComponent = (props: TOriginalProps) => (
18 |
19 | {({ setTargetPath, targetPath }) => {
20 | return (
21 |
26 | )
27 | }}
28 |
29 | )
30 | return extendedComponent
31 | }
32 |
33 | export { useTargetPathContext, withTargetPath }
34 | export default TargetPathContext
35 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/AvailableEditor.tsx:
--------------------------------------------------------------------------------
1 | import { useKeydownFromClick } from 'keydown-from-click'
2 | import React from 'react'
3 | import { FormattedMessage } from 'react-intl'
4 | import { RouteComponentProps, withRouter } from 'react-router-dom'
5 |
6 | interface Props extends RouteComponentProps {
7 | path: string
8 | titleId: string
9 | widget?: React.ReactNode
10 | }
11 |
12 | const AvailableEditor: React.FunctionComponent = ({
13 | history,
14 | path,
15 | titleId,
16 | widget,
17 | }) => {
18 | const redirect = React.useCallback(() => history.push(path), [history, path])
19 |
20 | const redirectByKeyDown = useKeydownFromClick(redirect)
21 |
22 | return (
23 |
30 |
31 |
32 |
33 | {widget}
34 |
35 | )
36 | }
37 |
38 | export default withRouter(AvailableEditor)
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | labels: bug
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 |
13 | 1. Go to '...'
14 | 2. Click on '....'
15 | 3. Scroll down to '....'
16 | 4. See error
17 |
18 | **Expected behavior**
19 | A clear and concise description of what you expected to happen.
20 |
21 | **Screenshots**
22 | If applicable, add screenshots to help explain your problem.
23 |
24 | **Desktop environment:**
25 |
26 |
33 |
34 | **Smartphone environment:**
35 |
36 | - Device: [e.g. iPhone6]
37 | - OS: [e.g. iOS8.1]
38 | - Browser [e.g. stock browser, safari]
39 | - Version [e.g. 22]
40 |
41 | **Additional context**
42 | Add any other context about the problem here.
43 |
--------------------------------------------------------------------------------
/react/components/RichTextEditor/style.css:
--------------------------------------------------------------------------------
1 | .RichEditor_root {
2 | background: #fff;
3 | border: 1px solid #ddd;
4 | font-size: 14px;
5 | padding: 15px;
6 | }
7 |
8 | .RichEditor_editor {
9 | border-top: 1px solid #ddd;
10 | cursor: text;
11 | font-size: 16px;
12 | min-height: 20rem;
13 | padding: 10px;
14 | }
15 |
16 | .RichEditor_editor div[data-contents='true'] {
17 | min-height: 20em;
18 | }
19 |
20 | .RichEditor_editor .public-DraftEditorPlaceholder-root,
21 | .RichEditor_editor .public-DraftEditor-content {
22 | margin: 0 -15px -15px;
23 | padding: 15px;
24 | }
25 |
26 | .RichEditor_hidePlaceholder .public-DraftEditorPlaceholder-root {
27 | display: none;
28 | }
29 |
30 | .RichEditor_editor .RichEditor_blockquote {
31 | border-left: 5px solid #eee;
32 | color: #666;
33 | font-family: 'Hoefler Text', 'Georgia', serif;
34 | font-style: italic;
35 | margin: 16px 0;
36 | padding: 10px 20px;
37 | }
38 |
39 | .RichEditor_editor .public-DraftStyleDefault-pre {
40 | background-color: rgba(0, 0, 0, 0.05);
41 | font-family: 'Inconsolata', 'Menlo', 'Consolas', monospace;
42 | font-size: 16px;
43 | padding: 20px;
44 | }
45 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/stateHandlers/getAddConditionalTemplateState.ts:
--------------------------------------------------------------------------------
1 | import { PagesFormData } from 'pages'
2 |
3 | import { State } from '../index'
4 |
5 | const getMaxUniqueId: (pages: PagesFormData[]) => number = pages => {
6 | return pages.reduce((acc, { uniqueId: currentValue }) => {
7 | if (acc < currentValue) {
8 | return currentValue
9 | }
10 | return acc
11 | }, -1)
12 | }
13 |
14 | export const getAddConditionalTemplateState = (prevState: State) => {
15 | const maxUniqueId = getMaxUniqueId(prevState.data.pages)
16 | const now = new Date()
17 | const newPage: PagesFormData = {
18 | condition: {
19 | allMatches: true,
20 | id: '',
21 | statements: [
22 | {
23 | error: '',
24 | object: { date: now },
25 | subject: 'date',
26 | verb: 'is',
27 | },
28 | ],
29 | },
30 | operator: 'all',
31 | pageId: '',
32 | template: '',
33 | uniqueId: maxUniqueId + 1,
34 | }
35 |
36 | return {
37 | ...prevState,
38 | data: { ...prevState.data, pages: prevState.data.pages.concat(newPage) },
39 | formErrors: {},
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/UploadModal/UploadPrompt/validateRedirect.ts:
--------------------------------------------------------------------------------
1 | import { defineMessages, IntlShape } from 'react-intl'
2 |
3 | export function isRedirectType(obj: unknown): obj is Redirect {
4 | return (
5 | typeof obj === 'object' &&
6 | obj !== null &&
7 | 'endDate' in obj &&
8 | 'from' in obj &&
9 | 'to' in obj &&
10 | 'type' in obj &&
11 | typeof (obj as Record).type === 'string'
12 | )
13 | }
14 |
15 | const messages = defineMessages({
16 | invalidTypeError: {
17 | defaultMessage:
18 | '[Line {line}] Invalid value "{type}" for redirect type. Allowed values are "PERMANENT" and "TEMPORARY".',
19 | id: 'admin/pages.admin.redirects.upload-modal.prompt.validation-error.type',
20 | },
21 | })
22 |
23 | export function validateRedirect(
24 | redirect: Redirect,
25 | line: number,
26 | intl: IntlShape
27 | ): string | undefined {
28 | const { type } = redirect
29 | return type.toUpperCase() !== 'PERMANENT' &&
30 | type.toUpperCase() !== 'TEMPORARY'
31 | ? intl.formatMessage(messages.invalidTypeError, {
32 | line,
33 | type,
34 | })
35 | : undefined
36 | }
37 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockSelector/BlockList/BlockListItem/ExpandArrow/index.tsx:
--------------------------------------------------------------------------------
1 | import classnames from 'classnames'
2 | import { useKeydownFromClick } from 'keydown-from-click'
3 | import React from 'react'
4 |
5 | import ArrowIcon from '../../../../../../icons/ArrowIcon'
6 | import styles from './styles.css'
7 |
8 | interface Props {
9 | isExpanded: boolean
10 | onClick: () => void
11 | }
12 |
13 | const ExpandArrow: React.FC = ({ isExpanded, onClick }) => {
14 | const handleKeyDown = useKeydownFromClick(onClick)
15 |
16 | return (
17 |
34 | )
35 | }
36 |
37 | export default ExpandArrow
38 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleList/icons/CreateNewIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // TODO: Use tachyons on stroke color
4 | const CreateNewIcon: React.FunctionComponent = () => (
5 |
37 | )
38 |
39 | export default CreateNewIcon
40 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/typography/TypeTokensList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 | import { RouteComponentProps } from 'react-router'
4 |
5 | import StyleEditorHeader from '../StyleEditorHeader'
6 | import TypeTokenEntry from './TypeTokensEntry'
7 |
8 | interface Props extends RouteComponentProps {
9 | style: Style
10 | }
11 |
12 | const TypeTokensList: React.FunctionComponent = ({ history, style }) => {
13 | const title = (
14 |
18 | )
19 |
20 | const { styles } = style.config.typography
21 | const tokens = Object.entries(styles)
22 | .filter(([name]) => !name.startsWith('__'))
23 | .map(([name, value]) => ({ ...value, name }))
24 |
25 | return (
26 | <>
27 |
28 |
29 | {tokens.map(({ name }) => (
30 |
31 | ))}
32 |
33 | >
34 | )
35 | }
36 |
37 | export default TypeTokensList
38 |
--------------------------------------------------------------------------------
/react/components/HighlightOverlay/OverlayMask/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { CSSTransition } from 'react-transition-group'
3 | import styles from './OverlayMask.css'
4 |
5 | interface Props {
6 | isActive: boolean
7 | style?: React.CSSProperties
8 | }
9 |
10 | const classNames = {
11 | enter: styles['overlay-mask-enter'],
12 | enterActive: styles['overlay-mask-enter-active'],
13 | enterDone: styles['overlay-mask-enter-done'],
14 | exit: styles['overlay-mask-exit'],
15 | exitActive: styles['overlay-mask-exit-active'],
16 | exitDone: styles['overlay-mask-exit-done'],
17 | }
18 |
19 | const timeout = {
20 | enter: 300,
21 | exit: 150,
22 | }
23 |
24 | export default function OverlayMask({ style, isActive }: Props) {
25 | const overlayMaskStyle: React.CSSProperties = isActive
26 | ? {}
27 | : { pointerEvents: 'none' }
28 |
29 | return (
30 |
36 |
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/react/components/admin/institutional/Form/utils.tsx:
--------------------------------------------------------------------------------
1 | import { RouteFormData } from 'pages'
2 | import { State } from './index'
3 |
4 | import { slugify } from '../../utils'
5 |
6 | const requiredMessage = 'admin/pages.admin.pages.form.templates.field.required'
7 |
8 | const validateFalsyPath = (path: keyof RouteFormData) => (
9 | data: RouteFormData
10 | ) => !data[path] && { [path]: requiredMessage }
11 |
12 | const validatePathUrl = (path: string) => {
13 | if (!path) return { path: requiredMessage }
14 | if (!path.startsWith('/'))
15 | return {
16 | path: 'admin/pages.admin.pages.form.templates.path.validation-error',
17 | }
18 | if (slugify(path) !== path.slice(1))
19 | return {
20 | path: 'admin/pages.admin.pages.form.templates.path.invalid-format',
21 | }
22 |
23 | return {}
24 | }
25 |
26 | export const getValidateFormState = (prevState: State) => {
27 | return {
28 | ...prevState,
29 | formErrors: {
30 | ...prevState.formErrors,
31 | ...(validatePathUrl(prevState.data.path) as Record),
32 | ...(prevState.isInfoEditable
33 | ? validateFalsyPath('title')(prevState.data)
34 | : {}),
35 | },
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/react/components/admin/pages/List/Entry.tsx:
--------------------------------------------------------------------------------
1 | import { useKeydownFromClick } from 'keydown-from-click'
2 | import React from 'react'
3 | import { withRuntimeContext } from 'vtex.render-runtime'
4 | import { IconEdit } from 'vtex.styleguide'
5 |
6 | import { ROUTES_FORM } from '../consts'
7 | import { getRouteTitle } from '../utils'
8 |
9 | interface Props {
10 | route: Route
11 | runtime: RenderContext
12 | }
13 |
14 | const Entry = ({ route, runtime }: Props) => {
15 | const handleClick = React.useCallback(() => {
16 | runtime.navigate({
17 | page: ROUTES_FORM,
18 | params: { id: encodeURIComponent(route.routeId) },
19 | })
20 | }, [route.routeId, runtime])
21 |
22 | const handleKeyDown = useKeydownFromClick(handleClick)
23 |
24 | return (
25 |
26 |
{getRouteTitle(route)}
27 |
34 |
35 |
36 |
37 | )
38 | }
39 |
40 | export default withRuntimeContext(Entry)
41 |
--------------------------------------------------------------------------------
/react/components/RichTextEditor/StyleButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Tooltip } from 'vtex.styleguide'
4 |
5 | interface StyleBtnProps {
6 | label: string | JSX.Element
7 | title?: string | JSX.Element
8 | active: boolean
9 | onToggle: (style: string | null) => void
10 | style: string | null
11 | className?: string
12 | }
13 |
14 | const StyleButton = ({
15 | active,
16 | onToggle,
17 | label,
18 | style,
19 | className,
20 | title,
21 | }: StyleBtnProps) => {
22 | const handleToggle = (e: React.MouseEvent) => {
23 | e.preventDefault()
24 | onToggle(style)
25 | }
26 |
27 | return (
28 |
29 |
37 | {label}
38 |
39 |
40 | )
41 | }
42 |
43 | StyleButton.defaultProps = {
44 | title: '',
45 | className: '',
46 | }
47 |
48 | export default StyleButton
49 |
--------------------------------------------------------------------------------
/react/components/form/ArrayFieldTemplate/ArrayFieldTemplateItem/icons/IconImage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Props {
4 | size?: number
5 | stroke?: string
6 | }
7 |
8 | const ImageIcon: React.FunctionComponent = ({ size, stroke }) => (
9 |
39 | )
40 |
41 | ImageIcon.defaultProps = {
42 | size: 16,
43 | stroke: '#979899',
44 | }
45 |
46 | export default React.memo(ImageIcon)
47 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/typography/TypographyEditor.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { defineMessages, FormattedMessage } from 'react-intl'
3 |
4 | import AvailableEditor from '../AvailableEditor'
5 | import StyleEditorHeader from '../StyleEditorHeader'
6 | import { EditorPath } from '../StyleEditorRouter'
7 |
8 | defineMessages({
9 | fontFamilyTitle: {
10 | defaultMessage: 'Font Family',
11 | id: 'admin/pages.editor.styles.edit.font-family.title',
12 | },
13 | })
14 |
15 | const TypographyEditor: React.FunctionComponent = () => {
16 | const title = (
17 |
21 | )
22 |
23 | return (
24 | <>
25 |
26 |
36 | >
37 | )
38 | }
39 |
40 | export default TypographyEditor
41 |
--------------------------------------------------------------------------------
/react/components/admin/institutional/List/Entry.tsx:
--------------------------------------------------------------------------------
1 | import { useKeydownFromClick } from 'keydown-from-click'
2 | import React from 'react'
3 | import { withRuntimeContext } from 'vtex.render-runtime'
4 | import { IconEdit } from 'vtex.styleguide'
5 |
6 | import { INSTITUTIONAL_ROUTES_FORM } from '../../pages/consts'
7 | import { getRouteTitle } from '../../pages/utils'
8 |
9 | interface Props {
10 | route: Route
11 | runtime: RenderContext
12 | }
13 |
14 | const Entry = ({ route, runtime }: Props) => {
15 | const handleClick = React.useCallback(() => {
16 | runtime.navigate({
17 | page: INSTITUTIONAL_ROUTES_FORM,
18 | params: { id: encodeURIComponent(route.routeId) },
19 | })
20 | }, [route.routeId, runtime])
21 |
22 | const handleKeyDown = useKeydownFromClick(handleClick)
23 |
24 | return (
25 |
26 |
{getRouteTitle(route)}
27 |
34 |
35 |
36 |
37 | )
38 | }
39 |
40 | export default withRuntimeContext(Entry)
41 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ToastConsumer } from 'vtex.styleguide'
3 |
4 | import BlockPicker from './BlockPicker'
5 | import ContextSelectors from './ContextSelectors'
6 | import DeviceSwitcher from './DeviceSwitcher'
7 | import SidebarVisibilityToggle from './SidebarVisibilityToggle'
8 | import UrlInput from './UrlInput'
9 |
10 | import styles from '../EditorContainer.css'
11 |
12 | interface Props {
13 | iframeRuntime: RenderContext
14 | }
15 |
16 | const Topbar: React.FunctionComponent = ({ iframeRuntime }) => (
17 |
20 |
21 | {({ showToast }) => (
22 |
23 | )}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | )
43 |
44 | export default Topbar
45 |
--------------------------------------------------------------------------------
/react/components/form/ArrayFieldTemplate/ItemTransitions.css:
--------------------------------------------------------------------------------
1 | .item-enter {
2 | transform: translateX(36rem);
3 | }
4 |
5 | .item-enter-active {
6 | transform: translateX(18rem);
7 | transition: transform 250ms ease-in-out;
8 | }
9 |
10 | .item-enter-done {
11 | transform: translateX(18rem);
12 | }
13 |
14 | .item-exit {
15 | transform: translateX(18rem);
16 | }
17 |
18 | .item-exit-active {
19 | transform: translateX(36rem);
20 | transition: transform 250ms ease-in-out;
21 | }
22 |
23 | .item-exit-done {
24 | transform: translateX(36rem);
25 | }
26 |
27 | .item-depth-enter {
28 | transform: translateX(18rem);
29 | }
30 |
31 | .item-depth-enter-active {
32 | transform: translateX(0rem);
33 | transition: transform 250ms ease-in-out;
34 | }
35 |
36 | .item-depth-enter-done {
37 | transform: translateX(0rem);
38 | }
39 |
40 | /* So the hidden item doesn't affect the height of the current form */
41 | .item-depth-enter-done > :global(.vtex-admin-pages-4-x .form-group) {
42 | height: 0;
43 | overflow: hidden;
44 | }
45 |
46 | .item-depth-exit {
47 | transform: translateX(0rem);
48 | }
49 |
50 | .item-depth-exit-active {
51 | transform: translateX(18rem);
52 | transition: transform 250ms ease-in-out;
53 | }
54 |
55 | .item-depth-exit-done {
56 | transform: translateX(18rem);
57 | }
58 |
--------------------------------------------------------------------------------
/react/components/icons/DragHandle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { calcIconSize } from './utils'
3 |
4 | import styles from './DragHandle.css'
5 |
6 | interface Props {
7 | className?: string
8 | size?: number
9 | }
10 |
11 | const iconBase = {
12 | height: 12,
13 | width: 6,
14 | }
15 |
16 | const DragHandle: React.FunctionComponent = ({
17 | size,
18 | className,
19 | ...props
20 | }) => {
21 | const newSize = calcIconSize(iconBase, size as number) // TS doesn't detect defaultProps
22 |
23 | return (
24 |
40 | )
41 | }
42 |
43 | DragHandle.defaultProps = {
44 | size: 20,
45 | }
46 |
47 | export default React.memo(DragHandle)
48 |
--------------------------------------------------------------------------------
/react/utils/components/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema6 } from 'json-schema'
2 | import { RenderComponent } from 'vtex.render-runtime'
3 |
4 | export type PropsOrContent = Extension['content'] | Extension['props']
5 |
6 | export interface GetActiveContentIdParams {
7 | extensions: RenderContext['extensions']
8 | treePath: EditorContextType['editTreePath']
9 | }
10 |
11 | export interface GetComponentSchemaParams {
12 | component: RenderComponent | null
13 | contentSchema?: JSONSchema6
14 | propsOrContent: PropsOrContent
15 | runtime: RenderContext
16 | isContent: boolean
17 | }
18 |
19 | export interface GetSchemaPropsOrContentParams {
20 | messages?: RenderContext['messages']
21 | schema?: JSONSchema6Definition
22 | propsOrContent?: Record
23 | }
24 |
25 | export interface GetSchemaPropsOrContentFromRuntimeParams {
26 | component: RenderComponent | null
27 | contentSchema?: JSONSchema6
28 | isContent?: boolean
29 | messages?: RenderContext['messages']
30 | propsOrContent: PropsOrContent
31 | runtime: RenderContext
32 | }
33 |
34 | export interface TranslateMessageParams {
35 | dictionary: Record
36 | id?: string
37 | }
38 |
39 | export interface UpdateExtensionFromFormParams {
40 | data: object
41 | isContent?: boolean
42 | runtime: RenderContext
43 | treePath: string | null
44 | }
45 |
--------------------------------------------------------------------------------
/react/utils/bindings/index.ts:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { Binding, Tenant } from 'vtex.tenant-graphql'
3 |
4 | const STORE_PRODUCT = 'vtex-storefront'
5 | const LAST_BINDING_KEY = 'vtex.cms-pages.lastBinding'
6 |
7 | export const getStoreBindings = (tenant: Tenant) => {
8 | return tenant.bindings.filter(
9 | binding => binding.targetProduct === STORE_PRODUCT
10 | )
11 | }
12 |
13 | export const getBindingSelectorOptions = (bindings: Binding[]) =>
14 | bindings.map(binding => ({
15 | label: binding.canonicalBaseAddress,
16 | value: binding.id,
17 | }))
18 |
19 | export const useBinding = (): [
20 | Binding | undefined,
21 | React.Dispatch
22 | ] => {
23 | const [binding, setBinding] = React.useState(() => {
24 | const lastBinding = window.localStorage.getItem(LAST_BINDING_KEY)
25 | return lastBinding ? (JSON.parse(lastBinding) as Binding) : undefined
26 | })
27 |
28 | return [
29 | binding,
30 | useMemo(() => {
31 | return (value: Binding | undefined) => {
32 | if (value) {
33 | window.localStorage.setItem(LAST_BINDING_KEY, JSON.stringify(value))
34 | } else {
35 | window.localStorage.removeItem(LAST_BINDING_KEY)
36 | }
37 | setBinding(value)
38 | }
39 | }, [setBinding]),
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/mutations/SendEventToAudit.tsx:
--------------------------------------------------------------------------------
1 | import { Mutation, MutationFn, MutationResult } from 'react-apollo'
2 |
3 | import SendEventToAudit from '../graphql/SendEventToAudit.graphql'
4 |
5 | interface SendEventToAuditData {
6 | sendEventToAudit: any
7 | }
8 |
9 | interface MetaData {
10 | entityName: string
11 | entityBeforeAction: string
12 | entityAfterAction: string
13 | remoteIpAddress: string
14 | forwardFromVtexUserAgent: string
15 | }
16 |
17 | interface SendEventToAuditInput {
18 | id: string
19 | date: string
20 | mainAccountName: string
21 | accountName: string
22 | subjectId: string
23 | application: string
24 | workspace: string
25 | operation: string
26 | meta: MetaData
27 | }
28 |
29 | interface SendEventToAuditVariables {
30 | input: SendEventToAuditInput
31 | }
32 |
33 | export type SendEventToAuditMutationFn = MutationFn<
34 | SendEventToAuditData,
35 | SendEventToAuditVariables
36 | >
37 |
38 | export interface MutationRenderProps
39 | extends MutationResult {
40 | sendEventToAudit: SendEventToAuditMutationFn
41 | }
42 |
43 | class SendEventToAuditMutation extends Mutation<
44 | SendEventToAuditData,
45 | SendEventToAuditVariables
46 | > {
47 | public static defaultProps = {
48 | mutation: SendEventToAudit,
49 | }
50 | }
51 |
52 | export default SendEventToAuditMutation
53 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/typography/FontFamilyEntry.tsx:
--------------------------------------------------------------------------------
1 | import { useKeydownFromClick } from 'keydown-from-click'
2 | import React from 'react'
3 | import { RouteComponentProps, withRouter } from 'react-router'
4 |
5 | import { FontFamily } from '../queries/ListFontsQuery'
6 | import { EditorPath, IdParam } from '../StyleEditorRouter'
7 |
8 | interface Props extends RouteComponentProps {
9 | font: FontFamily
10 | }
11 |
12 | const FontFamilyEntry: React.FunctionComponent = ({ font, history }) => {
13 | const encodedId = React.useMemo(() => encodeURIComponent(font.id), [font.id])
14 |
15 | const isActive = React.useMemo(() => document.URL.includes(encodedId), [
16 | encodedId,
17 | ])
18 |
19 | const handleClick = React.useCallback(
20 | () => history.push(EditorPath.customFontFile.replace(IdParam, encodedId)),
21 | [encodedId, history]
22 | )
23 |
24 | const handleKeyDown = useKeydownFromClick(handleClick)
25 |
26 | return (
27 |
35 | {font.fontFamily}
36 |
37 | )
38 | }
39 |
40 | export default withRouter(FontFamilyEntry)
41 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/Form/Operations.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Mutation } from 'react-apollo'
3 |
4 | import DeleteRedirect from '../../../../queries/DeleteRedirect.graphql'
5 | import SaveRedirect from '../../../../queries/SaveRedirect.graphql'
6 |
7 | import {
8 | DeleteRedirectData,
9 | DeleteRedirectMutationFn,
10 | DeleteRedirectVariables,
11 | SaveRedirectData,
12 | SaveRedirectMutationFn,
13 | SaveRedirectVariables,
14 | } from './typings'
15 | import { getStoreUpdater } from './utils'
16 |
17 | interface Props {
18 | children: (mutations: OperationsObj) => React.ReactNode
19 | }
20 |
21 | interface OperationsObj {
22 | deleteRedirect: DeleteRedirectMutationFn
23 | saveRedirect: SaveRedirectMutationFn
24 | }
25 |
26 | const Operations = (props: Props) => (
27 |
28 | mutation={DeleteRedirect}
29 | update={getStoreUpdater('delete')}
30 | >
31 | {deleteRedirect => (
32 |
33 | mutation={SaveRedirect}
34 | update={getStoreUpdater('save')}
35 | >
36 | {saveRedirect =>
37 | props.children({
38 | deleteRedirect,
39 | saveRedirect,
40 | })
41 | }
42 |
43 | )}
44 |
45 | )
46 |
47 | export default Operations
48 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/Form/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { MutationFn, MutationUpdaterFn } from 'react-apollo'
2 |
3 | import { RedirectsQuery } from '../typings'
4 |
5 | export interface MutationResult {
6 | data?: {
7 | [name: string]: Redirect
8 | }
9 | }
10 |
11 | export type QueryData = RedirectsQuery | null
12 |
13 | export interface RedirectQuery {
14 | redirect: {
15 | get: Redirect
16 | }
17 | }
18 |
19 | export interface DeleteRedirectVariables {
20 | path: string
21 | binding?: string
22 | }
23 |
24 | export interface SaveRedirectVariables {
25 | id?: string
26 | endDate: string | null
27 | from: string
28 | to: string
29 | type: RedirectTypes
30 | binding?: string
31 | }
32 |
33 | interface Mutations {
34 | redirect?: {
35 | delete?: Redirect
36 | save?: Redirect
37 | }
38 | }
39 |
40 | export type StoreUpdaterGetter = (
41 | operation: 'delete' | 'save'
42 | ) => MutationUpdaterFn
43 |
44 | export interface DeleteRedirectData {
45 | redirect: {
46 | delete: Redirect
47 | }
48 | }
49 |
50 | export type DeleteRedirectMutationFn = MutationFn<
51 | DeleteRedirectData,
52 | DeleteRedirectVariables
53 | >
54 |
55 | export interface SaveRedirectData {
56 | redirect: {
57 | save: Redirect
58 | }
59 | }
60 |
61 | export type SaveRedirectMutationFn = MutationFn<
62 | SaveRedirectData,
63 | SaveRedirectVariables
64 | >
65 |
--------------------------------------------------------------------------------
/react/components/HighlightOverlay/hooks/useAutoScroll.ts:
--------------------------------------------------------------------------------
1 | import debounce from 'lodash/debounce'
2 | import { useEffect } from 'react'
3 | import { State } from '../typings'
4 |
5 | interface UseAutoScrollArgs {
6 | highlightTreePath: State['highlightTreePath']
7 | editMode: State['editMode']
8 | visibleElement?: Element
9 | }
10 |
11 | function isElementInViewport(el: Element) {
12 | const rect = el.getBoundingClientRect()
13 |
14 | return (
15 | rect.top >= 0 &&
16 | rect.left >= 0 &&
17 | rect.bottom <=
18 | (window.innerHeight || document.documentElement.clientHeight) &&
19 | rect.right <= (window.innerWidth || document.documentElement.clientWidth)
20 | )
21 | }
22 |
23 | const scrollToElement = debounce((element: Element) => {
24 | if (!isElementInViewport(element)) {
25 | element.scrollIntoView({
26 | behavior: 'smooth',
27 | block: 'center',
28 | inline: 'center',
29 | })
30 | }
31 | }, 75)
32 |
33 | export default function useAutoScroll({
34 | highlightTreePath,
35 | editMode,
36 | visibleElement,
37 | }: UseAutoScrollArgs) {
38 | useEffect(() => {
39 | if (visibleElement && !editMode) {
40 | scrollToElement(visibleElement)
41 | }
42 | }, [visibleElement, editMode])
43 |
44 | useEffect(() => {
45 | if (highlightTreePath === null) {
46 | scrollToElement.cancel()
47 | }
48 | }, [highlightTreePath])
49 | }
50 |
--------------------------------------------------------------------------------
/react/components/admin/utils.ts:
--------------------------------------------------------------------------------
1 | export const slugify = (text: string) => {
2 | const specialChars: { [key: string]: string } = {
3 | à: 'a',
4 | ä: 'a',
5 | á: 'a',
6 | â: 'a',
7 | æ: 'a',
8 | å: 'a',
9 | ë: 'e',
10 | è: 'e',
11 | é: 'e',
12 | ê: 'e',
13 | î: 'i',
14 | ï: 'i',
15 | ì: 'i',
16 | í: 'i',
17 | ò: 'o',
18 | ó: 'o',
19 | ö: 'o',
20 | ô: 'o',
21 | ø: 'o',
22 | ù: 'o',
23 | ú: 'u',
24 | ü: 'u',
25 | û: 'u',
26 | ñ: 'n',
27 | ç: 'c',
28 | ß: 's',
29 | ÿ: 'y',
30 | œ: 'o',
31 | ŕ: 'r',
32 | ś: 's',
33 | ń: 'n',
34 | ṕ: 'p',
35 | ẃ: 'w',
36 | ǵ: 'g',
37 | ǹ: 'n',
38 | ḿ: 'm',
39 | ǘ: 'u',
40 | ẍ: 'x',
41 | ź: 'z',
42 | ḧ: 'h',
43 | '·': '-',
44 | '/': '-',
45 | _: '-',
46 | ',': '-',
47 | ':': '-',
48 | ';': '-',
49 | }
50 |
51 | return text
52 | .toString()
53 | .toLowerCase()
54 | .replace(/\s+/g, '-')
55 | .replace(/./g, target => specialChars[target] || target) // Replace special characters using the hash map
56 | .replace(/&/g, '-and-') // Replace & with 'and'
57 | .replace(/[^\w-]+/g, '') // Remove all non-word chars
58 | .replace(/--+/g, '-') // Replace multiple - with single -
59 | .replace(/^-+/, '') // Trim - from start of text
60 | .replace(/-+$/, '') // Trim - from end of text
61 | }
62 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/ContextSelectors/BindingCloning/utils/initialReducerState.ts:
--------------------------------------------------------------------------------
1 | import { Binding } from '../../typings'
2 |
3 | const formatBindings = (binding: Binding, currentBinding: Binding) => ({
4 | label: binding.canonicalBaseAddress,
5 | id: binding.id,
6 | supportedLocales: binding.supportedLocales,
7 | checked: false,
8 | overwrites: false,
9 | isCurrent: currentBinding.id === binding.id,
10 | })
11 |
12 | // Mark items as conflicting or not--that is, if saving on a
13 | // binding will overwrite a page present there or not
14 | export const createInitialCloningState = (
15 | binding: Binding,
16 | currentBinding: Binding,
17 | routeInfo: Route
18 | ) => {
19 | const formattedBinding = formatBindings(binding, currentBinding)
20 |
21 | if (formattedBinding.isCurrent) {
22 | return formattedBinding
23 | }
24 |
25 | // If the page has undefined binding, it is present on all bindings
26 | if (!routeInfo.binding) {
27 | return {
28 | ...formattedBinding,
29 | overwrites: true,
30 | }
31 | }
32 |
33 | if (!routeInfo.conflicts) {
34 | return formattedBinding
35 | }
36 |
37 | if (
38 | routeInfo.conflicts.find(
39 | conflict => formattedBinding.id === conflict.binding
40 | )
41 | ) {
42 | return {
43 | ...formattedBinding,
44 | overwrites: true,
45 | }
46 | }
47 |
48 | return formattedBinding
49 | }
50 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationList/Card/StatusLabel.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { defineMessages, useIntl } from 'react-intl'
3 |
4 | import ContentActiveIcon from '../../../../../icons/ContentActiveIcon'
5 | import ContentInactiveIcon from '../../../../../icons/ContentInactiveIcon'
6 | import ContentScheduledIcon from '../../../../../icons/ContentScheduledIcon'
7 |
8 | interface Props {
9 | type: 'active' | 'inactive' | 'scheduled'
10 | }
11 |
12 | const messages = defineMessages({
13 | active: {
14 | defaultMessage: 'Active',
15 | id: 'admin/pages.editor.component-list.status.active',
16 | },
17 | inactive: {
18 | defaultMessage: 'Inactive',
19 | id: 'admin/pages.editor.component-list.status.inactive',
20 | },
21 | scheduled: {
22 | defaultMessage: 'Scheduled',
23 | id: 'admin/pages.editor.component-list.status.scheduled',
24 | },
25 | })
26 |
27 | const ICON_BY_STATUS = {
28 | active: ,
29 | inactive: ,
30 | scheduled: ,
31 | }
32 |
33 | const StatusLabel: React.FC = ({ type }) => {
34 | const intl = useIntl()
35 |
36 | return (
37 |
38 | {ICON_BY_STATUS[type]}
39 |
{intl.formatMessage(messages[type])}
40 |
41 | )
42 | }
43 |
44 | export default StatusLabel
45 |
--------------------------------------------------------------------------------
/react/typings/vtex.render-runtime.d.ts:
--------------------------------------------------------------------------------
1 | /* Typings for `render-runtime` */
2 | declare module 'vtex.render-runtime' {
3 | import { Component, ComponentType, ReactElement } from 'react'
4 |
5 | export const ExtensionPoint: ReactElement
6 | export const Helmet: ComponentType
7 | export const Link: ReactElement
8 | export const NoSSR: ReactElement
9 | export const RenderContextConsumer: ReactElement
10 | export const canUseDOM: boolean
11 |
12 | export const withRuntimeContext: (
13 | Component: ComponentType
14 | ) => ComponentType<
15 | Pick<
16 | TOriginalProps,
17 | Exclude
18 | >
19 | >
20 |
21 | export declare const useRuntime: () => RenderContext
22 |
23 | interface RenderComponent {
24 | getCustomMessages?: (locale: string) => unknown
25 | schema?: ComponentSchema
26 | getSchema?: (props: object, otherArgs?: unknown) => ComponentSchema
27 | uiSchema?: object
28 | }
29 |
30 | export interface ComponentsRegistry {
31 | [component: string]: RenderComponent
32 | }
33 |
34 | export interface Window extends Window {
35 | __RENDER_8_COMPONENTS__: ComponentsRegistry
36 | }
37 |
38 | export const buildCacheLocator: (
39 | app: string,
40 | type: string,
41 | cacheId: string
42 | ) => string
43 |
44 | const global: Window
45 | }
46 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationList/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { MutationUpdaterFn } from 'react-apollo'
2 | import { ToastConsumerFunctions } from 'vtex.styleguide'
3 |
4 | import {
5 | DeleteContentData,
6 | DeleteContentMutationFn,
7 | } from '../../../mutations/DeleteContent'
8 | import { SendEventToAuditMutationFn } from '../../../mutations/SendEventToAudit'
9 |
10 | interface GetDeleteStoreUpdaterParams
11 | extends Pick {
12 | action: 'reset' | 'delete'
13 | blockId: EditorContextType['blockData']['id']
14 | iframeRuntime: RenderContext
15 | setBlockData: EditorContextType['setBlockData']
16 | }
17 |
18 | export type GetDeleteStoreUpdater = (
19 | params: GetDeleteStoreUpdaterParams
20 | ) => MutationUpdaterFn
21 |
22 | export interface UseListHandlersParams {
23 | activeContentId: ExtensionConfiguration['contentId']
24 | deleteContent: DeleteContentMutationFn
25 | sendEventToAudit: SendEventToAuditMutationFn
26 | iframeRuntime: RenderContext
27 | intl: ReactIntl.InjectedIntl
28 | showToast: ToastConsumerFunctions['showToast']
29 | }
30 |
31 | export type UseListHandlers = (
32 | params: UseListHandlersParams
33 | ) => {
34 | handleConfirmConfigurationDelete: (
35 | configuration: ExtensionConfiguration
36 | ) => void
37 | handleConfigurationDelete: (
38 | configuration: ExtensionConfiguration
39 | ) => Promise
40 | }
41 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/typography/TypeTokenDropdown.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from 'react'
2 |
3 | import startCase from 'lodash/startCase'
4 | import { Dropdown } from 'vtex.styleguide'
5 |
6 | import { FontFamily } from '../queries/ListFontsQuery'
7 | import { getTypeTokenDropdownOptions } from '../utils/typography'
8 |
9 | function getOnChange(key: keyof Font, dispatch: Dispatch>) {
10 | return (_: React.ChangeEvent, value: string) => {
11 | // Workaround since Dropdown does not work well with nil values
12 | dispatch({ [key]: value === '' ? null : value })
13 | }
14 | }
15 |
16 | function getValue(key: keyof Font, font: Font): string {
17 | return font[key] || ''
18 | }
19 |
20 | interface EntryProps {
21 | font: Font
22 | fontFamilies: FontFamily[]
23 | id: keyof Font
24 | dispatch: Dispatch>
25 | }
26 |
27 | const TypeTokenDropdown: React.FunctionComponent = ({
28 | font,
29 | id,
30 | dispatch,
31 | fontFamilies,
32 | }) => {
33 | return (
34 |
35 | {startCase(id)}
36 |
42 |
43 | )
44 | }
45 |
46 | export default TypeTokenDropdown
47 |
--------------------------------------------------------------------------------
/react/components/form/ArrayFieldTemplate/styles.css:
--------------------------------------------------------------------------------
1 | .accordion-item {
2 | background-color: white;
3 | }
4 |
5 | .accordion-item-settings {
6 | padding: 2rem;
7 | transform: translateX(0rem) !important;
8 | }
9 |
10 | .accordion-list-container--sorting .accordion-item {
11 | pointer-events: none;
12 | }
13 |
14 | .accordion-item--dragged {
15 | z-index: 999;
16 | }
17 |
18 | .accordion-label {
19 | display: flex;
20 | position: relative;
21 | margin: 0 -24px 0;
22 | padding: 12px 16px;
23 | cursor: pointer;
24 | user-select: none;
25 | }
26 |
27 | .accordion-item--dragged .accordion-label {
28 | background-color: #fff;
29 | }
30 |
31 | .accordion-item--handle-hidden .accordion-label {
32 | padding-left: 16px;
33 | }
34 |
35 | .accordion-item--dragged .accordion-label {
36 | box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, 0.2);
37 | }
38 |
39 | .accordion-label-title {
40 | color: #727273;
41 | font-weight: bold;
42 | cursor: pointer;
43 | }
44 |
45 | .accordion-item--dragged .accordion-handle,
46 | .accordion-label:hover .accordion-handle {
47 | display: block;
48 | }
49 |
50 | .drag-handle-container {
51 | transform: translateZ(0);
52 | transition: transform ease-in-out 100ms;
53 | }
54 |
55 | .drag-handle-container:hover,
56 | .accordion-item--dragged .drag-handle-container {
57 | transform: scale(1.1);
58 | }
59 |
60 | .drag-handle-container:hover circle,
61 | .accordion-item--dragged .drag-handle-container circle {
62 | fill: #368df7;
63 | }
64 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createMemoryHistory } from 'history'
3 | import { injectIntl, IntlShape } from 'react-intl'
4 | import { Router } from 'react-router-dom'
5 | import { ToastConsumer } from 'vtex.styleguide'
6 |
7 | import RenameStyleMutation from './mutations/RenameStyle'
8 | import UpdateStyleMutation from './mutations/UpdateStyle'
9 | import StyleEditorHooks from './StyleEditorHooks'
10 |
11 | interface Props {
12 | intl: IntlShape
13 | setStyleAsset: (asset: StyleAssetInfo) => void
14 | stopEditing: () => void
15 | style: Style
16 | }
17 |
18 | const StyleEditor: React.FunctionComponent = props => {
19 | return (
20 |
21 |
22 | {renameStyle => (
23 |
24 | {updateStyle => (
25 |
26 | {({ showToast }) => (
27 |
35 | )}
36 |
37 | )}
38 |
39 | )}
40 |
41 |
42 | )
43 | }
44 |
45 | export default injectIntl(StyleEditor)
46 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/DeviceSwitcher/DeviceItem.tsx:
--------------------------------------------------------------------------------
1 | import { createKeydownFromClick } from 'keydown-from-click'
2 | import React from 'react'
3 | import { useIntl } from 'react-intl'
4 | import { Tooltip } from 'vtex.styleguide'
5 |
6 | import { useHover } from '../hooks'
7 | import { BORDER_BY_POSITION, ICON_BY_VIEWPORT } from './consts'
8 | import messages from './messages'
9 |
10 | interface Props {
11 | isActive: boolean
12 | onClick: (e: Pick) => void
13 | position: keyof typeof BORDER_BY_POSITION
14 | type: Viewport
15 | }
16 |
17 | const DeviceItem: React.FC = ({ onClick, position, isActive, type }) => {
18 | const handleKeyDown = createKeydownFromClick(onClick)
19 |
20 | const { handleMouseEnter, handleMouseLeave, hover } = useHover()
21 | const intl = useIntl()
22 |
23 | const Icon = ICON_BY_VIEWPORT[type]
24 |
25 | return (
26 |
27 |
39 |
40 | )
41 | }
42 |
43 | export default DeviceItem
44 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/DeviceSwitcher/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { useEditorContext } from '../../../EditorContext'
4 |
5 | import { VIEWPORTS_BY_DEVICE } from './consts'
6 | import DeviceItem from './DeviceItem'
7 |
8 | import styles from './DeviceSwitcher.css'
9 |
10 | interface Props {
11 | device: RenderContext['device']
12 | }
13 |
14 | const DeviceSwitcher: React.FC = ({ device }) => {
15 | const editor = useEditorContext()
16 |
17 | const handleClick = React.useCallback(
18 | ({ currentTarget }: Pick) => {
19 | if (currentTarget && currentTarget instanceof HTMLElement) {
20 | editor.setViewport(currentTarget.dataset.type as Viewport)
21 | }
22 | },
23 | [editor]
24 | )
25 |
26 | const viewports = VIEWPORTS_BY_DEVICE[device === 'any' ? 'desktop' : device]
27 |
28 | return (
29 |
30 | {viewports.map((deviceType, index) => {
31 | const isLast = index === viewports.length - 1
32 |
33 | return (
34 |
35 |
41 |
42 | )
43 | })}
44 |
45 | )
46 | }
47 |
48 | export default DeviceSwitcher
49 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockSelector/BlockList/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { useEditorContext } from '../../../../EditorContext'
4 | import { NormalizedBlock } from '../typings'
5 | import BlockListItem from './BlockListItem'
6 |
7 | interface Props {
8 | blocks: NormalizedBlock[]
9 | highlightHandler: (treePath: string | null) => void
10 | iframeRuntime: RenderContextProps['runtime']
11 | onMouseEnterBlock: (
12 | event: React.MouseEvent
13 | ) => void
14 | onMouseLeaveBlock: () => void
15 | }
16 |
17 | const BlockList: React.FC = ({
18 | blocks,
19 | highlightHandler,
20 | onMouseEnterBlock,
21 | onMouseLeaveBlock,
22 | }) => {
23 | const editor = useEditorContext()
24 |
25 | const handleEdit = React.useCallback(
26 | (block: NormalizedBlock) => {
27 | if (block.isEditable) {
28 | editor.editExtensionPoint(block.treePath)
29 |
30 | editor.setIsLoading(true)
31 |
32 | highlightHandler(null)
33 | }
34 | },
35 | [editor, highlightHandler]
36 | )
37 |
38 | return (
39 |
40 | {blocks.map((block, index) => (
41 |
48 | ))}
49 |
50 | )
51 | }
52 |
53 | export default BlockList
54 |
--------------------------------------------------------------------------------
/react/components/form/ImageUploader/Dropzone.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import ReactDropzone, { DropzoneOptions, useDropzone } from 'react-dropzone'
3 |
4 | interface Props {
5 | children: ReactElement
6 | disabled: boolean
7 | extraClasses?: string
8 | onClick: React.MouseEventHandler
9 | onDrop: DropzoneOptions['onDrop']
10 | ref?: React.Ref
11 | }
12 |
13 | const MAX_SIZE = 4 * 1024 * 1024
14 |
15 | const Dropzone = React.forwardRef(
16 | ({ disabled, children, extraClasses, onClick, onDrop }, ref) => {
17 | const { isDragActive } = useDropzone()
18 |
19 | const stlye = isDragActive
20 | ? { borderColor: '#134cd8', width: '14.25rem', height: '7rem' }
21 | : {}
22 |
23 | return (
24 |
31 | {({ getRootProps, getInputProps }) => (
32 | onClick(e), stlye })}
34 | className={`w-100 h4 br2 ${extraClasses}`}
35 | ref={ref}
36 | >
37 |
38 | {children}
39 |
40 | )}
41 |
42 | )
43 | }
44 | )
45 |
46 | Dropzone.displayName = 'Dropzone'
47 |
48 | Dropzone.defaultProps = {
49 | extraClasses: '',
50 | }
51 |
52 | export default Dropzone
53 |
--------------------------------------------------------------------------------
/react/components/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Button, Modal as StyleguideModal } from 'vtex.styleguide'
3 |
4 | interface Props {
5 | isActionDanger?: boolean
6 | isActionLoading: boolean
7 | isOpen: boolean
8 | onClickAction?: (event: Event) => void
9 | onClickCancel?: (event: Event) => void
10 | onClose: (event?: Event) => void
11 | textButtonAction: string
12 | textButtonCancel: string
13 | textMessage: string | React.ReactNode
14 | title?: string
15 | }
16 |
17 | const Modal = ({
18 | isActionDanger = false,
19 | isActionLoading,
20 | isOpen,
21 | onClickAction,
22 | onClickCancel,
23 | onClose,
24 | textButtonAction,
25 | textButtonCancel,
26 | textMessage,
27 | title,
28 | }: Props) => (
29 |
30 | {textMessage}
31 |
32 |
33 |
34 |
42 |
43 |
44 |
52 |
53 |
54 | )
55 |
56 | export default Modal
57 |
--------------------------------------------------------------------------------
/react/components/admin/pages/Form/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { ConditionsProps } from 'vtex.styleguide'
2 |
3 | export interface DeleteMutationResult {
4 | data?: {
5 | deleteRoute: string
6 | }
7 | }
8 |
9 | export interface SaveMutationResult {
10 | data?: {
11 | saveRoute: Route
12 | }
13 | }
14 |
15 | export interface TemplateMutationResult {
16 | data?: {
17 | availableTemplates: Template[]
18 | }
19 | }
20 |
21 | export type QueryData = RoutesQuery | null
22 |
23 | export interface RoutesQuery {
24 | routes: Route[]
25 | }
26 |
27 | export interface ClientSideUniqueId {
28 | uniqueId: number
29 | operator: ConditionsProps['operator']
30 | }
31 |
32 | interface StatementsOnSaveRoute {
33 | subject: string
34 | verb: string
35 | objectJSON: string
36 | }
37 |
38 | interface ConditionsOnSaveRouteVariables {
39 | id?: string
40 | allMatches: boolean
41 | statements: StatementsOnSaveRoute[]
42 | }
43 |
44 | export interface SaveRouteVariables {
45 | route: Partial &
46 | Pick
47 | }
48 |
49 | export interface DeleteRouteVariables {
50 | uuid: string
51 | }
52 |
53 | export interface DateInfoFormat {
54 | date: string
55 | to: string
56 | from: string
57 | }
58 |
59 | export type DateStatementFormat = Record
60 |
61 | export type FormErrors = Omit, 'pages'> & {
62 | pages?: {
63 | [key: string]: {
64 | template?: string
65 | condition?: string
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/react/components/form/ImageUploader/ImagePreview.tsx:
--------------------------------------------------------------------------------
1 | import { createKeydownFromClick } from 'keydown-from-click'
2 | import React, { useMemo } from 'react'
3 |
4 | import styles from './ImagePreview.css'
5 |
6 | interface Props {
7 | imageUrl: string
8 | children: React.ReactElement
9 | }
10 |
11 | function stopPropagation(
12 | e: Pick, 'stopPropagation'>
13 | ) {
14 | e.stopPropagation()
15 | }
16 |
17 | const stopOnKeyDownPropagation = createKeydownFromClick(stopPropagation)
18 |
19 | const ImagePreview: React.FC = ({ children, imageUrl }) => {
20 | const backgroundImageStyle = useMemo(
21 | () => ({
22 | backgroundImage: `url(${imageUrl}?width=710&height=384&aspect=true)`,
23 | }),
24 | [imageUrl]
25 | )
26 |
27 | return (
28 |
32 |
35 |
43 | {children}
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | export default React.memo(ImagePreview)
51 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | #### What problem is this solving?
2 |
3 |
4 |
5 | #### How should this be manually tested?
6 |
7 | [Workspace](url)
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | #### Screenshots or example usage
18 |
19 | #### Type of changes
20 |
21 |
22 |
23 | | ✔️ | Type of Change |
24 | | --- | ----------------------------------------------------------------------------------------- |
25 | | \_ | Bug fix |
26 | | \_ | New feature |
27 | | \_ | Breaking change |
28 | | \_ | Technical improvements |
29 |
30 | #### Notes
31 |
32 |
33 |
34 | #### How does this PR make you feel? [:link:](http://giphy.com/categories/emotions/)
35 |
36 | 
37 |
--------------------------------------------------------------------------------
/react/components/icons/GearIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const GearIcon: React.FunctionComponent = () => (
4 |
17 | )
18 |
19 | export default GearIcon
20 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/utils.ts:
--------------------------------------------------------------------------------
1 | import throttle from 'lodash/throttle'
2 |
3 | import { updateExtensionFromForm } from '../../../../utils/components'
4 | import { NEW_CONFIGURATION_ID } from '../consts'
5 | import { getDefaultCondition } from '../utils'
6 | import { GetDefaultConfiguration, GetConfigurationType } from './typings'
7 |
8 | export const getDefaultConfiguration: GetDefaultConfiguration = ({
9 | iframeRuntime,
10 | isSitewide,
11 | }) => ({
12 | condition: getDefaultCondition({ iframeRuntime, isSitewide }),
13 | contentId: NEW_CONFIGURATION_ID,
14 | contentJSON: '{}',
15 | label: null,
16 | origin: null,
17 | })
18 |
19 | export const getIsDefaultContent: (
20 | configuration: Pick
21 | ) => boolean = configuration => configuration.origin !== null
22 |
23 | export const omitUndefined = (obj: Extension['content']) =>
24 | Object.entries(obj).reduce((acc, [currKey, currValue]) => {
25 | if (typeof currValue === 'undefined') {
26 | return acc
27 | }
28 |
29 | return { ...acc, [currKey]: currValue }
30 | }, {})
31 |
32 | export const getConfigurationType: GetConfigurationType = ({
33 | configuration,
34 | activeContentId,
35 | }) => {
36 | if (getIsDefaultContent(configuration)) {
37 | return 'app'
38 | }
39 |
40 | if (activeContentId === configuration.contentId) {
41 | return 'active'
42 | }
43 |
44 | return 'inactive'
45 | }
46 |
47 | export const throttledUpdateExtensionFromForm = throttle(
48 | data => updateExtensionFromForm(data),
49 | 200
50 | )
51 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/FormMetaContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component, createContext, useContext } from 'react'
2 |
3 | import { FormMetaContext as FormMetaContextT } from './typings'
4 |
5 | const defaultExternalState: FormMetaContextT = {
6 | getWasModified: () => {
7 | return false
8 | },
9 | setWasModified: () => {
10 | return
11 | },
12 | }
13 |
14 | const FormMetaContext = createContext(defaultExternalState)
15 |
16 | export const useFormMetaContext = () => useContext(FormMetaContext)
17 |
18 | export const FormMetaConsumer = FormMetaContext.Consumer
19 |
20 | interface State extends FormMetaContextT {
21 | isLoading: boolean
22 | wasModified: boolean
23 | }
24 |
25 | export class FormMetaProvider extends Component<{}, State> {
26 | public constructor(props: {}) {
27 | super(props)
28 |
29 | this.state = {
30 | ...defaultExternalState,
31 | getWasModified: this.getWasModified,
32 | isLoading: false,
33 | setWasModified: this.setWasModified,
34 | wasModified: false,
35 | }
36 | }
37 |
38 | public render() {
39 | return (
40 |
41 | {this.props.children}
42 |
43 | )
44 | }
45 |
46 | private getWasModified: State['getWasModified'] = () => this.state.wasModified
47 |
48 | private setWasModified: State['setWasModified'] = (newValue, callback) => {
49 | this.setState({ wasModified: newValue }, () => {
50 | if (callback) {
51 | callback()
52 | }
53 | })
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "vendor": "vtex",
3 | "name": "admin-pages",
4 | "version": "4.59.0",
5 | "title": "VTEX Pages Admin",
6 | "description": "The VTEX Pages CMS admin interface",
7 | "builders": {
8 | "admin": "0.x",
9 | "messages": "1.x",
10 | "react": "3.x",
11 | "docs": "0.x"
12 | },
13 | "dependencies": {
14 | "vtex.apps-graphql": "3.x",
15 | "vtex.file-manager": "0.x",
16 | "vtex.file-manager-graphql": "0.x",
17 | "vtex.native-types": "0.x",
18 | "vtex.pages-graphql": "2.x",
19 | "vtex.pwa-graphql": "1.x",
20 | "vtex.store": "2.x",
21 | "vtex.styles-graphql": "1.x",
22 | "vtex.styleguide": "9.x",
23 | "vtex.messages": "1.x",
24 | "vtex.rewriter": "1.x",
25 | "vtex.tenant-graphql": "0.x",
26 | "vtex.admin-cms": "1.x"
27 | },
28 | "mustUpdateAt": "2018-09-05",
29 | "categories": [],
30 | "registries": [
31 | "smartcheckout"
32 | ],
33 | "settingsSchema": {
34 | "title": "VTEX Pages Admin",
35 | "type": "object",
36 | "properties": {
37 | "copyContentBinding": {
38 | "type": "boolean",
39 | "title": "Copy Content Binding",
40 | "description": "Enables feature to copy pages from one binding to another",
41 | "default": false
42 | }
43 | }
44 | },
45 | "scripts": {
46 | "postreleasy": "vtex publish -r vtex --verbose"
47 | },
48 | "policies": [
49 | {
50 | "name": "vtex.file-manager:saveFileAsync"
51 | }
52 | ],
53 | "$schema": "https://raw.githubusercontent.com/vtex/node-vtex-api/master/gen/manifest.schema"
54 | }
55 |
--------------------------------------------------------------------------------
/react/components/form/MultiSelect.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import ReactSelect, { Option } from 'react-select'
3 |
4 | interface Props {
5 | disabled?: boolean
6 | id?: string
7 | label?: string
8 | onChange: (newValue: string[]) => void
9 | options: {
10 | enumOptions: Option[]
11 | }
12 | placeholder: string
13 | schema?: {
14 | disabled?: boolean
15 | title: string
16 | }
17 | value: string[]
18 | }
19 |
20 | const MultiSelect: React.FunctionComponent = ({
21 | disabled,
22 | id,
23 | label,
24 | onChange,
25 | options: { enumOptions },
26 | placeholder,
27 | schema,
28 | value,
29 | }) => (
30 |
31 | {(label || (schema && schema.title)) && (
32 |
37 | )}
38 | {
45 | const formattedValue = (optionValues as Option[]).map(
46 | (item: Option) => item.value as string
47 | )
48 | onChange(formattedValue)
49 | }}
50 | options={enumOptions}
51 | placeholder={placeholder}
52 | value={value}
53 | />
54 |
55 | )
56 |
57 | MultiSelect.defaultProps = {
58 | disabled: false,
59 | placeholder: '',
60 | value: [],
61 | }
62 |
63 | export default MultiSelect
64 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/UploadModal/Loading.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 | import { Button, Progress, Spinner } from 'vtex.styleguide'
4 |
5 | interface Props {
6 | current: number
7 | total: number
8 | onCancel: () => void
9 | }
10 |
11 | const Loading: React.FunctionComponent = ({
12 | current,
13 | onCancel,
14 | total,
15 | }) => {
16 | const percent =
17 | Math.round((current * 100) / total) < 100
18 | ? Math.round((current * 100) / total)
19 | : 100
20 |
21 | return (
22 |
23 |
24 |
25 |
26 |
27 | {`${percent}%`}
28 |
29 | {current + ' '}/{' ' + total}
30 |
31 |
32 |
33 |
34 |
38 |
44 |
45 |
46 | )
47 | }
48 |
49 | export default Loading
50 |
--------------------------------------------------------------------------------
/react/components/admin/redirects/bulkUploadRedirects.ts:
--------------------------------------------------------------------------------
1 | import { MutableRefObject } from 'react'
2 | import { splitEvery } from 'ramda'
3 |
4 | const MAX_REDIRECTS_PER_REQUEST = 200
5 | const NUMBER_OF_RETRIES = 3
6 |
7 | interface BulkUploadRedirectsArgs {
8 | data: Redirect[]
9 | mutation: (data: Redirect[]) => Promise
10 | isSave: boolean
11 | shouldUploadRef: MutableRefObject
12 | updateProgress?: (processed: number) => void
13 | }
14 |
15 | export default async function bulkUploadRedirects({
16 | data,
17 | mutation,
18 | shouldUploadRef,
19 | updateProgress = () => {
20 | return undefined
21 | },
22 | }: BulkUploadRedirectsArgs): Promise<{ failedRedirects: Redirect[] }> {
23 | let failedRedirects: Redirect[] = []
24 | let processedCount = 0
25 |
26 | const redirectBatches = splitEvery(MAX_REDIRECTS_PER_REQUEST, data)
27 |
28 | for (const payload of redirectBatches) {
29 | if (!shouldUploadRef.current) {
30 | break
31 | }
32 |
33 | for (let i = 1; i <= NUMBER_OF_RETRIES; i++) {
34 | try {
35 | await mutation(payload)
36 | processedCount += payload.length
37 | updateProgress(processedCount)
38 | break
39 | } catch (e) {
40 | await new Promise(res => {
41 | setTimeout(() => res(), i * 750)
42 | })
43 | if (i === NUMBER_OF_RETRIES) {
44 | failedRedirects = failedRedirects.concat(payload)
45 | }
46 | }
47 | }
48 |
49 | await new Promise(res => {
50 | setTimeout(() => res(), 750)
51 | })
52 | }
53 |
54 | return { failedRedirects }
55 | }
56 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/styles.css:
--------------------------------------------------------------------------------
1 | .transition-editor-enter {
2 | transform: translateX(18em);
3 | }
4 |
5 | .transition-editor-enter-active {
6 | transition: transform 250ms ease-in;
7 | transform: translateX(0);
8 | }
9 |
10 | .transition-editor-enter-done {
11 | transform: translateX(0);
12 | }
13 |
14 | .transition-editor-exit {
15 | transform: translateX(0);
16 | }
17 |
18 | .transition-editor-exit-active {
19 | transition: transform 250ms ease-in;
20 | transform: translateX(18em);
21 | }
22 |
23 | .transition-editor-exit-done {
24 | transform: translateX(18em);
25 | }
26 |
27 | .transition-selector-enter {
28 | transform: translateX(0);
29 | }
30 |
31 | .transition-selector-enter-active {
32 | transition: transform 250ms ease-in;
33 | transform: translateX(-18em);
34 | }
35 |
36 | .transition-selector-enter-done {
37 | transform: translateX(-18em);
38 | }
39 |
40 | .transition-selector-exit {
41 | transform: translateX(-18em);
42 | }
43 |
44 | .transition-selector-exit-active {
45 | transition: transform 250ms ease-in;
46 | transform: translateX(0);
47 | }
48 |
49 | .transition-selector-exit-done {
50 | transform: translateX(0);
51 | }
52 |
53 | /* Non small */
54 | @media screen and (min-width: 30em) {
55 | .admin-sidebar {
56 | border-left: 2px solid #f2f4f5;
57 | }
58 |
59 | .admin-sidebar::-webkit-scrollbar,
60 | .admin-sidebar::-webkit-scrollbar-track {
61 | width: 2px;
62 | background: #f2f4f5;
63 | }
64 |
65 | .admin-sidebar::-webkit-scrollbar-thumb {
66 | width: 2px;
67 | background: rgba(0, 0, 0, 0.2);
68 | border-radius: 2px;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/SidebarVisibilityToggle.tsx:
--------------------------------------------------------------------------------
1 | import { createKeydownFromClick } from 'keydown-from-click'
2 | import React from 'react'
3 | import { InjectedIntlProps, injectIntl } from 'react-intl'
4 | import { Tooltip } from 'vtex.styleguide'
5 |
6 | import { useEditorContext } from '../../EditorContext'
7 |
8 | import { useHover } from './hooks'
9 | import IconView from './icons/IconView'
10 |
11 | const SidebarVisibilityToggle: React.FC = ({ intl }) => {
12 | const editor = useEditorContext()
13 |
14 | const { handleMouseEnter, handleMouseLeave, hover } = useHover()
15 |
16 | const handleSidebarVisibilityToggle = editor.toggleSidebarVisibility
17 |
18 | const handleSidebarVisibilityToggleKeyDown = createKeydownFromClick(
19 | handleSidebarVisibilityToggle
20 | )
21 |
22 | return (
23 |
30 |
43 |
44 | )
45 | }
46 |
47 | export default injectIntl(SidebarVisibilityToggle)
48 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Sidebar/BlockEditor/BlockConfigurationEditor/ConditionControls/ScopeSelector/ScopeSelector.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { InjectedIntlProps, injectIntl } from 'react-intl'
3 | import { RadioGroup } from 'vtex.styleguide'
4 |
5 | import ConditionTitle from '../ConditionTitle'
6 | import { getScopeStandardOptions } from './utils'
7 |
8 | interface CustomProps {
9 | onChange: (event: React.ChangeEvent) => void
10 | pageContext: PageContext
11 | scope: ConfigurationScope
12 | isSitewide: boolean
13 | }
14 |
15 | type Props = CustomProps & InjectedIntlProps
16 |
17 | const ScopeSelector: React.FunctionComponent = ({
18 | intl,
19 | isSitewide,
20 | onChange,
21 | pageContext,
22 | scope,
23 | }) => {
24 | const standardOptions = getScopeStandardOptions(intl, pageContext)
25 |
26 | return (
27 |
28 |
29 |
30 |
50 |
51 | )
52 | }
53 |
54 | export default injectIntl(ScopeSelector)
55 |
--------------------------------------------------------------------------------
/react/components/admin/pages/List/Section.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { FormattedMessage } from 'react-intl'
3 | import { withRuntimeContext } from 'vtex.render-runtime'
4 | import { Box, Button } from 'vtex.styleguide'
5 |
6 | import { NEW_ROUTE_ID, ROUTES_FORM } from '../consts'
7 | import SeparatorWithLine from '../SeparatorWithLine'
8 |
9 | import Entry from './Entry'
10 |
11 | interface Props {
12 | hasCreateButton?: boolean
13 | routes: Route[]
14 | runtime: RenderContext
15 | titleId: string
16 | }
17 |
18 | const Section = ({ hasCreateButton, routes, runtime, titleId }: Props) => (
19 |
20 |
21 |
22 | {title => {title}
}
23 |
24 | {hasCreateButton && (
25 |
26 |
41 |
42 | )}
43 |
44 | {routes.map(route => (
45 |
46 |
47 |
48 |
49 | ))}
50 |
51 | )
52 |
53 | export default withRuntimeContext(Section)
54 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Styles/StyleEditor/mutations/DeleteFontFamily.ts:
--------------------------------------------------------------------------------
1 | import { DataProxy } from 'apollo-cache'
2 | import { Mutation, MutationFn, QueryResult } from 'react-apollo'
3 |
4 | import DeleteFontFamilyMutation from '../graphql/DeleteFontFamily.graphql'
5 | import ListFonts from '../graphql/ListFonts.graphql'
6 | import { ListFontsData } from '../queries/ListFontsQuery'
7 |
8 | interface FontFamilyVariables {
9 | id: string
10 | }
11 |
12 | export interface DeleteFontFamilyData {
13 | deleteFontFamily: {
14 | id: string
15 | }
16 | }
17 |
18 | export type DeleteFontFamilyFn = MutationFn<
19 | DeleteFontFamilyData,
20 | FontFamilyVariables
21 | >
22 |
23 | type DeleteFontFamilyResult = QueryResult
24 |
25 | const updateFontsAfterDelete = (
26 | cache: DataProxy,
27 | result: DeleteFontFamilyResult
28 | ) => {
29 | const listData = cache.readQuery({ query: ListFonts })
30 | if (
31 | result.data == null ||
32 | result.data.deleteFontFamily == null ||
33 | listData == null
34 | ) {
35 | return
36 | }
37 | const { listFonts: families } = listData
38 | const { id: removedId } = result.data.deleteFontFamily
39 | const currentFonts = families.filter(family => family.id !== removedId)
40 | cache.writeQuery({
41 | data: { listFonts: currentFonts },
42 | query: ListFonts,
43 | })
44 | }
45 |
46 | class DeleteFontFamily extends Mutation<
47 | DeleteFontFamilyData,
48 | FontFamilyVariables
49 | > {
50 | public static defaultProps = {
51 | mutation: DeleteFontFamilyMutation,
52 | update: updateFontsAfterDelete,
53 | }
54 | }
55 |
56 | export default DeleteFontFamily
57 |
--------------------------------------------------------------------------------
/react/components/EditorContainer/Topbar/BlockPicker.tsx:
--------------------------------------------------------------------------------
1 | import { useKeydownFromClick } from 'keydown-from-click'
2 | import React from 'react'
3 | import { InjectedIntlProps, injectIntl } from 'react-intl'
4 | import { Tooltip } from 'vtex.styleguide'
5 |
6 | import { useEditorContext } from '../../EditorContext'
7 | import { useHover } from './hooks'
8 | import IconPicker from './icons/IconPicker'
9 |
10 | const BlockPicker: React.FC = ({ intl }) => {
11 | const editor = useEditorContext()
12 |
13 | const { handleMouseEnter, handleMouseLeave, hover } = useHover()
14 |
15 | const handleEditModeToggle = editor.toggleEditMode
16 |
17 | const handleKeyPress = useKeydownFromClick(handleEditModeToggle)
18 |
19 | return (
20 |
27 |
42 |
43 | )
44 | }
45 |
46 | export default injectIntl(BlockPicker)
47 |
--------------------------------------------------------------------------------
/react/PageEditor.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useRuntime } from 'vtex.render-runtime'
3 |
4 | import StoreIframe from './components/EditorContainer/StoreIframe'
5 | import EditorProvider from './components/EditorProvider'
6 | import { TemporaryAlert } from './components/TemporaryAlert'
7 | import { useAdminLoadingContext } from './utils/AdminLoadingContext'
8 |
9 | interface Props extends RenderContextProps {
10 | page: string
11 | params: {
12 | targetPath: string
13 | }
14 | query?: Record[]
15 | }
16 |
17 | const PageEditor: React.FC = props => {
18 | const { page, params, query } = props
19 |
20 | const queryString =
21 | query === undefined || query === null
22 | ? ''
23 | : `?${Object.entries(query)
24 | .map(([key, value]) => `${key}=${value}`)
25 | .join('&')}`
26 |
27 | const runtime = useRuntime()
28 |
29 | if (page.includes('storefront')) {
30 | runtime.navigate({
31 | page: page.replace('storefront', 'site-editor'),
32 | params,
33 | })
34 | }
35 |
36 | const isSiteEditor = React.useMemo(() => page.includes('site-editor'), [page])
37 |
38 | const path = params?.targetPath && `${params.targetPath}${queryString ?? ''}`
39 |
40 | const { stopLoading } = useAdminLoadingContext()
41 |
42 | useEffect(() => {
43 | stopLoading()
44 | }, [stopLoading])
45 |
46 | return (
47 |
48 |
49 |
50 |
51 |
52 |
53 | )
54 | }
55 |
56 | export default PageEditor
57 |
--------------------------------------------------------------------------------
/react/components/HighlightOverlay/hooks/useStyles/index.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect } from 'react'
2 |
3 | import { UseStyles } from './typings'
4 | import { getStyles } from './utils'
5 |
6 | /*
7 | * The "data-extension-point" attribute is re-added to the element every
8 | * time it's edited. By observing it, we can consistently check if the
9 | * block's automatic height has changed and adapt the HighlightOverlay's
10 | * height accordingly.
11 | */
12 |
13 | const useStyles: UseStyles = ({
14 | hasValidElement,
15 | highlightTreePath,
16 | isOverlayMaskActive,
17 | setState,
18 | subscribeToResize,
19 | unsubscribeToResize,
20 | visibleElement,
21 | }) => {
22 | const updateStyles = useCallback(
23 | (clientHeight?) => {
24 | const elementHeight: HTMLElement['clientHeight'] = clientHeight
25 |
26 | const styles = getStyles({
27 | hasValidElement,
28 | highlightTreePath,
29 | visibleElement,
30 | })
31 |
32 | setState(state => ({
33 | ...state,
34 | ...styles,
35 | elementHeight: elementHeight || state.elementHeight,
36 | }))
37 | },
38 | [hasValidElement, highlightTreePath, setState, visibleElement]
39 | )
40 |
41 | useEffect(() => {
42 | function resizeCallback(element: Element) {
43 | updateStyles(element.clientHeight)
44 | }
45 |
46 | subscribeToResize(resizeCallback)
47 | updateStyles()
48 |
49 | return () => {
50 | unsubscribeToResize(resizeCallback)
51 | }
52 | }, [
53 | isOverlayMaskActive,
54 | subscribeToResize,
55 | unsubscribeToResize,
56 | updateStyles,
57 | visibleElement,
58 | ])
59 | }
60 |
61 | export default useStyles
62 |
--------------------------------------------------------------------------------