mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Add custom provider to publish form (#707)
* Creating custom provider box selection * adding provider to Form publish validation schema * validating custom provider URL * WIP validation for custom provider * validating provider uri * adding success/ error messages * conditional rendering of custom provider input * remove console.log * fixing textFormData * Publishing custom provider URI in DDO * Adding serviceEndpoint to Debug * removing pre-checked default provider * Refactoring to reduce code duplication * removing console.log messages * update submit text * update submit text * Placing custom provider field behind Advanced Settings * removing provider field * updating placeholder provider * style for advanced setting button * refactoring customProvider * remocing template literal * fixing linting errors * restricting advanced publishing settings through env var * updating example env * adding custom provider to formAlgoPublish * refactoring to reduce duplication * Reducing duplication * updating types and initial values * Removing Custom provider input from main algorithm form * Removing concole.log * Chaning customProvider to providerUri * changing customProvider to providerUri * advanceSettings to PascalCase * Removing unused useOcean() * Using useSiteMetadata() hook * Adding allowAdvancedPublishSettings to query * Adding temporary console.log(ocean) * Removing console.log
This commit is contained in:
parent
e703f9b03d
commit
a545f723e9
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
# Enables another asset editing button holder further advanced settings
|
# Enables another asset editing button holder further advanced settings
|
||||||
#GATSBY_ALLOW_ADVANCED_SETTINGS="true"
|
#GATSBY_ALLOW_ADVANCED_SETTINGS="true"
|
||||||
|
#GATSBY_ALLOW_ADVANCED_PUBLISH_SETTINGS="true"
|
||||||
|
|
||||||
# Allow/Deny Lists
|
# Allow/Deny Lists
|
||||||
#GATSBY_CREDENTIAL_TYPE="address"
|
#GATSBY_CREDENTIAL_TYPE="address"
|
||||||
|
@ -60,5 +60,7 @@ module.exports = {
|
|||||||
|
|
||||||
// Used to show or hide advanced settings button in asset details page
|
// Used to show or hide advanced settings button in asset details page
|
||||||
allowAdvancedSettings: process.env.GATSBY_ALLOW_ADVANCED_SETTINGS || 'false',
|
allowAdvancedSettings: process.env.GATSBY_ALLOW_ADVANCED_SETTINGS || 'false',
|
||||||
|
allowAdvancedPublishSettings:
|
||||||
|
process.env.GATSBY_ALLOW_ADVANCED_PUBLISH_SETTINGS || 'false',
|
||||||
credentialType: process.env.GATSBY_CREDENTIAL_TYPE || 'address'
|
credentialType: process.env.GATSBY_CREDENTIAL_TYPE || 'address'
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,14 @@
|
|||||||
"placeholder": "e.g. logistics, ai",
|
"placeholder": "e.g. logistics, ai",
|
||||||
"help": "Separate tags with comma."
|
"help": "Separate tags with comma."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "providerUri",
|
||||||
|
"label": "Custom Provider URL",
|
||||||
|
"type": "providerUri",
|
||||||
|
"help": "Enter the URL for your custom provider or leave blank to use the default provider. [Learn more](https://github.com/oceanprotocol/provider/).",
|
||||||
|
"placeholder": "https://provider.polygon.oceanprotocol.com/",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "termsAndConditions",
|
"name": "termsAndConditions",
|
||||||
"label": "Terms & Conditions",
|
"label": "Terms & Conditions",
|
||||||
|
@ -38,6 +38,14 @@
|
|||||||
"options": ["Download", "Compute"],
|
"options": ["Download", "Compute"],
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "providerUri",
|
||||||
|
"label": "Custom Provider URL",
|
||||||
|
"type": "providerUri",
|
||||||
|
"help": "Enter the URL for your custom provider or leave blank to use the default provider. [Learn more](https://github.com/oceanprotocol/provider/).",
|
||||||
|
"placeholder": "https://provider.polygon.oceanprotocol.com/",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "timeout",
|
"name": "timeout",
|
||||||
"label": "Timeout",
|
"label": "Timeout",
|
||||||
|
1
src/@types/Form.d.ts
vendored
1
src/@types/Form.d.ts
vendored
@ -13,6 +13,7 @@ export interface FormFieldProps {
|
|||||||
placeholder?: string
|
placeholder?: string
|
||||||
pattern?: string
|
pattern?: string
|
||||||
min?: string
|
min?: string
|
||||||
|
advanced?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FormContent {
|
export interface FormContent {
|
||||||
|
2
src/@types/MetaData.d.ts
vendored
2
src/@types/MetaData.d.ts
vendored
@ -38,6 +38,7 @@ export interface MetadataPublishFormDataset {
|
|||||||
// ---- optional fields ----
|
// ---- optional fields ----
|
||||||
tags?: string
|
tags?: string
|
||||||
links?: string | EditableMetadataLinks[]
|
links?: string | EditableMetadataLinks[]
|
||||||
|
providerUri?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataPublishFormAlgorithm {
|
export interface MetadataPublishFormAlgorithm {
|
||||||
@ -56,6 +57,7 @@ export interface MetadataPublishFormAlgorithm {
|
|||||||
containerTag: string
|
containerTag: string
|
||||||
entrypoint: string
|
entrypoint: string
|
||||||
tags?: string
|
tags?: string
|
||||||
|
providerUri?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataEditForm {
|
export interface MetadataEditForm {
|
||||||
|
@ -3,6 +3,7 @@ import slugify from '@sindresorhus/slugify'
|
|||||||
import styles from './InputElement.module.css'
|
import styles from './InputElement.module.css'
|
||||||
import { InputProps } from '.'
|
import { InputProps } from '.'
|
||||||
import FilesInput from '../../molecules/FormFields/FilesInput'
|
import FilesInput from '../../molecules/FormFields/FilesInput'
|
||||||
|
import CustomProvider from '../../molecules/FormFields/CustomProvider'
|
||||||
import Terms from '../../molecules/FormFields/Terms'
|
import Terms from '../../molecules/FormFields/Terms'
|
||||||
import BoxSelection, {
|
import BoxSelection, {
|
||||||
BoxSelectionOption
|
BoxSelectionOption
|
||||||
@ -125,6 +126,8 @@ export default function InputElement({
|
|||||||
)
|
)
|
||||||
case 'files':
|
case 'files':
|
||||||
return <FilesInput name={name} {...field} {...props} />
|
return <FilesInput name={name} {...field} {...props} />
|
||||||
|
case 'providerUri':
|
||||||
|
return <CustomProvider name={name} {...field} {...props} />
|
||||||
case 'datatoken':
|
case 'datatoken':
|
||||||
return <Datatoken name={name} {...field} {...props} />
|
return <Datatoken name={name} {...field} {...props} />
|
||||||
case 'terms':
|
case 'terms':
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
.advancedBtn {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
53
src/components/molecules/FormFields/AdvancedSettings.tsx
Normal file
53
src/components/molecules/FormFields/AdvancedSettings.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import React, { ReactElement, useState, FormEvent, ChangeEvent } from 'react'
|
||||||
|
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||||
|
import Input from '../../atoms/Input'
|
||||||
|
import Button from '../../atoms/Button'
|
||||||
|
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||||
|
import { Field } from 'formik'
|
||||||
|
import styles from './AdvancedSettings.module.css'
|
||||||
|
|
||||||
|
export default function AdvancedSettings(prop: {
|
||||||
|
content: FormContent
|
||||||
|
handleFieldChange: (
|
||||||
|
e: ChangeEvent<HTMLInputElement>,
|
||||||
|
field: FormFieldProps
|
||||||
|
) => void
|
||||||
|
}): ReactElement {
|
||||||
|
const { appConfig } = useSiteMetadata()
|
||||||
|
const [advancedSettings, setAdvancedSettings] = useState<boolean>(false)
|
||||||
|
function toggleAdvancedSettings(e: FormEvent<Element>) {
|
||||||
|
e.preventDefault()
|
||||||
|
advancedSettings === true
|
||||||
|
? setAdvancedSettings(false)
|
||||||
|
: setAdvancedSettings(true)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{appConfig.allowAdvancedPublishSettings === 'true' && (
|
||||||
|
<Button
|
||||||
|
className={styles.advancedBtn}
|
||||||
|
style="text"
|
||||||
|
size="small"
|
||||||
|
onClick={toggleAdvancedSettings}
|
||||||
|
>
|
||||||
|
Advanced Settings
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{prop.content.data.map(
|
||||||
|
(field: FormFieldProps) =>
|
||||||
|
advancedSettings === true &&
|
||||||
|
field.advanced === true && (
|
||||||
|
<Field
|
||||||
|
key={field.name}
|
||||||
|
{...field}
|
||||||
|
options={field.options}
|
||||||
|
component={Input}
|
||||||
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
prop.handleFieldChange(e, field)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
60
src/components/molecules/FormFields/CustomProvider.tsx
Normal file
60
src/components/molecules/FormFields/CustomProvider.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React, { ReactElement, useState, useEffect } from 'react'
|
||||||
|
import { useField } from 'formik'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import CustomInput from './URLInput/Input'
|
||||||
|
import { useOcean } from '../../../providers/Ocean'
|
||||||
|
import { InputProps } from '../../atoms/Input'
|
||||||
|
|
||||||
|
export default function CustomProvider(props: InputProps): ReactElement {
|
||||||
|
const [field, meta, helpers] = useField(props.name)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [providerUrl, setProviderUrl] = useState<string>()
|
||||||
|
const { ocean, config } = useOcean()
|
||||||
|
|
||||||
|
function loadProvider() {
|
||||||
|
if (!providerUrl) return
|
||||||
|
async function validateProvider() {
|
||||||
|
let valid: boolean
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
valid = await ocean.provider.isValidProvider(providerUrl)
|
||||||
|
} catch (error) {
|
||||||
|
valid = false
|
||||||
|
console.error(error.message)
|
||||||
|
} finally {
|
||||||
|
valid
|
||||||
|
? toast.success('Perfect! That provider URL looks good 🐳')
|
||||||
|
: toast.error(
|
||||||
|
'Could not validate provider. Please check URL and try again'
|
||||||
|
)
|
||||||
|
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateProvider()
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadProvider()
|
||||||
|
}, [providerUrl, config.providerUri])
|
||||||
|
|
||||||
|
async function handleButtonClick(e: React.SyntheticEvent, url: string) {
|
||||||
|
helpers.setTouched(false)
|
||||||
|
e.preventDefault()
|
||||||
|
if (providerUrl === url) {
|
||||||
|
loadProvider()
|
||||||
|
}
|
||||||
|
|
||||||
|
setProviderUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CustomInput
|
||||||
|
submitText="Validate"
|
||||||
|
{...props}
|
||||||
|
{...field}
|
||||||
|
isLoading={isLoading}
|
||||||
|
handleButtonClick={handleButtonClick}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -3,7 +3,7 @@ import axios from 'axios'
|
|||||||
import { useField } from 'formik'
|
import { useField } from 'formik'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import FileInfo from './Info'
|
import FileInfo from './Info'
|
||||||
import FileInput from './Input'
|
import CustomInput from '../URLInput/Input'
|
||||||
import { InputProps } from '../../../atoms/Input'
|
import { InputProps } from '../../../atoms/Input'
|
||||||
import { fileinfo } from '../../../../utils/provider'
|
import { fileinfo } from '../../../../utils/provider'
|
||||||
import { useWeb3 } from '../../../../providers/Web3'
|
import { useWeb3 } from '../../../../providers/Web3'
|
||||||
@ -68,7 +68,8 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
{field?.value && field.value[0] && typeof field.value === 'object' ? (
|
{field?.value && field.value[0] && typeof field.value === 'object' ? (
|
||||||
<FileInfo name={props.name} file={field.value[0]} />
|
<FileInfo name={props.name} file={field.value[0]} />
|
||||||
) : (
|
) : (
|
||||||
<FileInput
|
<CustomInput
|
||||||
|
submitText="Add File"
|
||||||
{...props}
|
{...props}
|
||||||
{...field}
|
{...field}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
@ -5,11 +5,13 @@ import Loader from '../../../atoms/Loader'
|
|||||||
import styles from './Input.module.css'
|
import styles from './Input.module.css'
|
||||||
import InputGroup from '../../../atoms/Input/InputGroup'
|
import InputGroup from '../../../atoms/Input/InputGroup'
|
||||||
|
|
||||||
export default function FileInput({
|
export default function URLInput({
|
||||||
|
submitText,
|
||||||
handleButtonClick,
|
handleButtonClick,
|
||||||
isLoading,
|
isLoading,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
|
submitText: string
|
||||||
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
@ -30,7 +32,7 @@ export default function FileInput({
|
|||||||
onClick={(e: React.SyntheticEvent) => e.preventDefault()}
|
onClick={(e: React.SyntheticEvent) => e.preventDefault()}
|
||||||
disabled={!field.value}
|
disabled={!field.value}
|
||||||
>
|
>
|
||||||
{isLoading ? <Loader /> : 'Add File'}
|
{isLoading ? <Loader /> : submitText}
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
)
|
)
|
@ -23,6 +23,7 @@ export default function Debug({
|
|||||||
{
|
{
|
||||||
index: 1,
|
index: 1,
|
||||||
type: values.access,
|
type: values.access,
|
||||||
|
serviceEndpoint: values.providerUri,
|
||||||
attributes: {}
|
attributes: {}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -11,6 +11,7 @@ import Input from '../../atoms/Input'
|
|||||||
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||||
import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData'
|
import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData'
|
||||||
import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish'
|
import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish'
|
||||||
|
import AdvancedSettings from '../../molecules/FormFields/AdvancedSettings'
|
||||||
import FormTitle from './FormTitle'
|
import FormTitle from './FormTitle'
|
||||||
import FormActions from './FormActions'
|
import FormActions from './FormActions'
|
||||||
import styles from './FormPublish.module.css'
|
import styles from './FormPublish.module.css'
|
||||||
@ -33,6 +34,7 @@ const query = graphql`
|
|||||||
required
|
required
|
||||||
sortOptions
|
sortOptions
|
||||||
options
|
options
|
||||||
|
advanced
|
||||||
}
|
}
|
||||||
warning
|
warning
|
||||||
}
|
}
|
||||||
@ -145,6 +147,7 @@ export default function FormPublish(): ReactElement {
|
|||||||
|
|
||||||
{content.data.map(
|
{content.data.map(
|
||||||
(field: FormFieldProps) =>
|
(field: FormFieldProps) =>
|
||||||
|
field.advanced !== true &&
|
||||||
((field.name !== 'entrypoint' &&
|
((field.name !== 'entrypoint' &&
|
||||||
field.name !== 'image' &&
|
field.name !== 'image' &&
|
||||||
field.name !== 'containerTag') ||
|
field.name !== 'containerTag') ||
|
||||||
@ -164,7 +167,10 @@ export default function FormPublish(): ReactElement {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
<AdvancedSettings
|
||||||
|
content={content}
|
||||||
|
handleFieldChange={handleFieldChange}
|
||||||
|
/>
|
||||||
<FormActions
|
<FormActions
|
||||||
isValid={isValid}
|
isValid={isValid}
|
||||||
resetFormAndClearStorage={resetFormAndClearStorage}
|
resetFormAndClearStorage={resetFormAndClearStorage}
|
||||||
|
@ -16,6 +16,7 @@ import { ReactComponent as Compute } from '../../../images/compute.svg'
|
|||||||
import FormTitle from './FormTitle'
|
import FormTitle from './FormTitle'
|
||||||
import FormActions from './FormActions'
|
import FormActions from './FormActions'
|
||||||
import styles from './FormPublish.module.css'
|
import styles from './FormPublish.module.css'
|
||||||
|
import AdvancedSettings from '../../molecules/FormFields/AdvancedSettings'
|
||||||
|
|
||||||
const query = graphql`
|
const query = graphql`
|
||||||
query {
|
query {
|
||||||
@ -35,6 +36,7 @@ const query = graphql`
|
|||||||
required
|
required
|
||||||
sortOptions
|
sortOptions
|
||||||
options
|
options
|
||||||
|
advanced
|
||||||
}
|
}
|
||||||
warning
|
warning
|
||||||
}
|
}
|
||||||
@ -125,23 +127,30 @@ export default function FormPublish(): ReactElement {
|
|||||||
>
|
>
|
||||||
<FormTitle title={content.title} />
|
<FormTitle title={content.title} />
|
||||||
|
|
||||||
{content.data.map((field: FormFieldProps) => (
|
{content.data.map(
|
||||||
<Field
|
(field: FormFieldProps) =>
|
||||||
key={field.name}
|
field.advanced !== true && (
|
||||||
{...field}
|
<Field
|
||||||
options={
|
key={field.name}
|
||||||
field.type === 'boxSelection'
|
{...field}
|
||||||
? accessTypeOptions
|
options={
|
||||||
: field.name === 'timeout' && computeTypeSelected === true
|
field.type === 'boxSelection'
|
||||||
? computeTypeOptions
|
? accessTypeOptions
|
||||||
: field.options
|
: field.name === 'timeout' && computeTypeSelected === true
|
||||||
}
|
? computeTypeOptions
|
||||||
component={Input}
|
: field.options
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
}
|
||||||
handleFieldChange(e, field)
|
component={Input}
|
||||||
}
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
/>
|
handleFieldChange(e, field)
|
||||||
))}
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<AdvancedSettings
|
||||||
|
content={content}
|
||||||
|
handleFieldChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormActions
|
<FormActions
|
||||||
isValid={isValid}
|
isValid={isValid}
|
||||||
|
@ -132,14 +132,16 @@ export default function PublishPage({
|
|||||||
'Publish with ',
|
'Publish with ',
|
||||||
metadata,
|
metadata,
|
||||||
serviceType,
|
serviceType,
|
||||||
values.dataTokenOptions
|
values.dataTokenOptions,
|
||||||
|
values.providerUri
|
||||||
)
|
)
|
||||||
|
|
||||||
const ddo = await publish(
|
const ddo = await publish(
|
||||||
metadata as unknown as Metadata,
|
metadata as unknown as Metadata,
|
||||||
serviceType,
|
serviceType,
|
||||||
values.dataTokenOptions,
|
values.dataTokenOptions,
|
||||||
timeout
|
timeout,
|
||||||
|
values.providerUri
|
||||||
)
|
)
|
||||||
|
|
||||||
// Publish failed
|
// Publish failed
|
||||||
|
@ -32,6 +32,7 @@ interface UseSiteMetadata {
|
|||||||
allowFreePricing: string
|
allowFreePricing: string
|
||||||
allowAdvancedSettings: string
|
allowAdvancedSettings: string
|
||||||
credentialType: string
|
credentialType: string
|
||||||
|
allowAdvancedPublishSettings: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +69,7 @@ const query = graphql`
|
|||||||
allowDynamicPricing
|
allowDynamicPricing
|
||||||
allowFreePricing
|
allowFreePricing
|
||||||
allowAdvancedSettings
|
allowAdvancedSettings
|
||||||
|
allowAdvancedPublishSettings
|
||||||
credentialType
|
credentialType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,5 +51,6 @@ export const initialValues: Partial<MetadataPublishFormAlgorithm> = {
|
|||||||
algorithmPrivacy: false,
|
algorithmPrivacy: false,
|
||||||
termsAndConditions: false,
|
termsAndConditions: false,
|
||||||
tags: '',
|
tags: '',
|
||||||
timeout: 'Forever'
|
timeout: 'Forever',
|
||||||
|
providerUri: ''
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,10 @@ export const validationSchema: Yup.SchemaOf<MetadataPublishFormDataset> =
|
|||||||
.matches(/Compute|Download/g, { excludeEmptyString: true })
|
.matches(/Compute|Download/g, { excludeEmptyString: true })
|
||||||
.required('Required'),
|
.required('Required'),
|
||||||
termsAndConditions: Yup.boolean().required('Required'),
|
termsAndConditions: Yup.boolean().required('Required'),
|
||||||
|
|
||||||
// ---- optional fields ----
|
// ---- optional fields ----
|
||||||
tags: Yup.string().nullable(),
|
tags: Yup.string().nullable(),
|
||||||
links: Yup.array<FileMetadata[]>().nullable()
|
links: Yup.array<FileMetadata[]>().nullable(),
|
||||||
|
providerUri: Yup.string().url().nullable()
|
||||||
})
|
})
|
||||||
.defined()
|
.defined()
|
||||||
|
|
||||||
@ -44,5 +44,6 @@ export const initialValues: Partial<MetadataPublishFormDataset> = {
|
|||||||
timeout: 'Forever',
|
timeout: 'Forever',
|
||||||
access: '',
|
access: '',
|
||||||
termsAndConditions: false,
|
termsAndConditions: false,
|
||||||
tags: ''
|
tags: '',
|
||||||
|
providerUri: ''
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user