diff --git a/.github/workflows/update-refs-in-docs.yml b/.github/workflows/update-refs-in-docs.yml new file mode 100644 index 0000000..da94315 --- /dev/null +++ b/.github/workflows/update-refs-in-docs.yml @@ -0,0 +1,87 @@ +name: Update references in Docsite + +on: + push: + branches: + - DEX-424/ref-test + +jobs: + generate-react-native-sdk-refs: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: DEX-424/ref-test + fetch-depth: 0 + persist-credentials: false + + - uses: actions/setup-node@v1 + with: + node-version: 14.17.6 + - run: npm install + + - name: Generate React Native SDK references + run: | + mkdir docs + npm run docs-gen + cd ../.. + mkdir refs + cp -r skyflow-react-native/skyflow-react-native/docs/markdown/* refs/ + cd skyflow-react-native/skyflow-react-native/ + rm -r lib/ + echo "SHORT_SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV + git clean -df + + - name: Create a branch in skyflow-docs + env: + TOKEN: ${{ secrets.PAT_ACTIONS }} + REPO_OWNER: skyflowapi + REPO_NAME: skyflow-docs + ACTOR: ${{ github.actor }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Create a new branch in skyflow-docs + cd $GITHUB_WORKSPACE + git remote add skyflow-docs https://${TOKEN}@github.com/${REPO_OWNER}/${REPO_NAME}.git + git fetch skyflow-docs main + BRANCH_NAME="SDK/react-native/${{ env.SHORT_SHA }}" + git remote set-url --push skyflow-docs https://${ACTOR}:${TOKEN}@github.com/${REPO_OWNER}/${REPO_NAME}.git + git checkout -b $BRANCH_NAME skyflow-docs/main + cp -r ../../refs/* src/pages/content/docs/sdks/skyflow-react-native/ + + - name: Push files and raise a PR + env: + TOKEN: ${{ secrets.PAT_ACTIONS }} + REPO_OWNER: skyflowapi + REPO_NAME: skyflow-docs + ACTOR: ${{ github.actor }} + run: | + git config user.name ${{ github.actor }} + git config user.email ${{ github.actor }}@users.noreply.github.com + BRANCH_NAME="SDK/react-native/${{ env.SHORT_SHA }}" + git add . + + # Check if there are changes to commit + if [[ -n "$(git status --porcelain)" ]]; then + git commit -m "SDK-${{ env.SHORT_SHA }} Added React Native SDK references" + git push skyflow-docs $BRANCH_NAME + # Raise a pull request + BASE_BRANCH="main" + BRANCH_NAME="SDK/react-native/${{ env.SHORT_SHA }}" + TITLE="SDK-${{ env.SHORT_SHA }}: Updated React Native SDK references" + BODY="This pull request adds the latest React Native SDK references. Commit id for reference: $GITHUB_SHA" + API_URL="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/pulls" + echo "API URL: $API_URL" + RESPONSE=$(curl -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token $TOKEN" \ + -d "{\"title\":\"$TITLE\",\"body\":\"$BODY\",\"head\":\"${BRANCH_NAME}\",\"base\":\"$BASE_BRANCH\"}" \ + "$API_URL") + echo "Response Body: $RESPONSE" + PR_URL=$(echo "$RESPONSE" | jq -r '.html_url') + echo "Pull Request URL: $PR_URL" + else + echo "No changes to commit. Skipping push files and raise a PR." + exit 0 + fi diff --git a/package.json b/package.json index a56bbbe..5f7e8d3 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "prepare": "bob build", "example": "yarn --cwd example", "bootstrap": "yarn example && yarn && yarn example pods", - "build": "bob build" + "build": "bob build", + "docs-gen": "node scripts/doc-gen/components-json-gen.js && node scripts/doc-gen/markdown-gen.js" }, "keywords": [ "react-native", @@ -129,6 +130,7 @@ "dependencies": { "jwt-decode": "^3.1.2", "lodash": "^4.17.21", + "react-docgen-typescript": "^2.2.2", "react-native-uuid": "^2.0.1", "set-value": "^4.1.0" } diff --git a/scripts/doc-gen/components-json-gen.js b/scripts/doc-gen/components-json-gen.js new file mode 100644 index 0000000..c8fcf64 --- /dev/null +++ b/scripts/doc-gen/components-json-gen.js @@ -0,0 +1,68 @@ +/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const fs = require('fs'); +const path = require('path'); +const docgen = require('react-docgen-typescript'); + +const tsConfigParser = docgen.withCustomConfig('./tsconfig.json', { + savePropValueAsString: true, + skipChildrenPropWithoutDoc: false, + propFilter: { + skipPropsWithoutDoc: false, + skipPropsWithName: 'children', + }, + shouldExtractLiteralValuesFromEnum: true, +}); + +const paths = ['components', 'hooks']; +const excludeDirs = []; + +function getFiles(dir, files = []) { + const fileNames = fs.readdirSync(dir); + + fileNames.forEach((fileName) => { + const filePath = path.join(dir, fileName); + const stat = fs.statSync(filePath); + if (stat.isDirectory() && !excludeDirs.includes(fileName)) { + getFiles(filePath, files); + } else if (fileName === 'index.tsx' || fileName === 'index.ts') { + files.push(filePath); + } + }); + + return files; +} + +function generateDocumentationJson() { + const docJson = {}; + const rootPath = path.join(__dirname, '../../'); + + paths.forEach((item) => { + const pathJsonArray = []; + const folderPath = path.join(rootPath, 'src', item); + const files = getFiles(folderPath); + files.forEach((file) => { + const relativePath = path.relative(rootPath, file); + const docs = tsConfigParser.parse(relativePath); + pathJsonArray.push(docs[0]); + }); + docJson[item] = pathJsonArray; + }); + return docJson; +} + +function createDocJsonFile(json) { + const dirPath = path.join(__dirname, '../../', 'docs', 'json'); + const filePath = path.join(dirPath, 'components.json'); + + // Create docs/json folder if it doesn't exist + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath); + } + + // Write to components.json file + fs.writeFileSync(filePath, JSON.stringify(json, null, 2)); + console.log('Json documentation is generated at docs/json'); +} + +createDocJsonFile(generateDocumentationJson()); diff --git a/scripts/doc-gen/markdown-gen.js b/scripts/doc-gen/markdown-gen.js new file mode 100644 index 0000000..92309e7 --- /dev/null +++ b/scripts/doc-gen/markdown-gen.js @@ -0,0 +1,137 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const components = require('../../docs/json/components.json'); +const fs = require('fs'); +const path = require('path'); + +function containsInternalAnnotation(description) { + return /\@internal/i.test(description); +} + +function formatTypeColumn(str, propType) { + if (!str) { + return ''; + } + const parts = str.split('|').map((s) => s.trim()); + if (parts.length === 1) { + return `\`${str}\``; + } + if (!propType) { + return `\`${parts[0]}\``; + } + return `\`${parts[0]} / ${parts[1]}\``; +} + +// Output directory for Markdown files +const outputDir = './docs/markdown'; + +// Overview page to list elements +let overviewContent = ` +{% env enable="reactNativeSdkRef" %} + +# React-Native +`; + +// Loop through each component in the JSON object +Object.keys(components).forEach((key) => { + overviewContent += `\n## ${ + key == 'core' + ? 'Skyflow Provider' + : key == 'elements' + ? 'Components' + : key.charAt(0).toUpperCase() + key.slice(1) + }\n\n`; + + components[key] + .filter((component) => component) + .forEach((component) => { + // Skip generating the Markdown file if the component description contains '@internal' + if ( + !component.description.trim() && + Object.keys(component.props).every((propName) => !component.props[propName].description.trim()) + ) { + return; + } + + // Create the Markdown file path based on the component name + const componentPath = path.join( + outputDir, + key, + `${component.displayName}.md` + ); + + const name = `${component.displayName}`; + overviewContent += `- [${name}](/sdks/skyflow-react-native/${key}/${name})\n`; + + const sortedProps = Object.entries(component.props) + .sort(([_, propA], [__, propB]) => { + if (propA.required && !propB.required) { + return -1; // propA comes before propB + } else if (!propA.required && propB.required) { + return 1; // propB comes before propA + } + return 0; // no change in order + }) + .reduce((sorted, [key, value]) => { + sorted[key] = value; + return sorted; + }, {}); + // Generate the Markdown content for the component + let markdownContent = `--- +id: ${component.displayName} +title: ${component.displayName} +sidebar_label: ${component.displayName} +--- + +{% env enable="reactNativeSdkRef" %} + +# ${component.displayName} + +${component.description} + +## Import + +\`\`\` +import {${component.displayName}} from 'skyflow-react-native'; +\`\`\` +`; + const propsDetails = ` +## Props + +| Name | Type | Description | Required | +|-------------------------|----------------------|---------------------------------------------------------|------------------| +${Object.keys(sortedProps) + .map((propName) => { + const prop = sortedProps[propName]; + const isInternal = prop.description && prop.description.includes('@internal'); + const description = isInternal ? '' : prop.description; + return description ? `| ${prop.name} | ${formatTypeColumn( + prop.type.name, + prop.required + )} | ${description} | ${prop.required} |` : null; + }) + .filter(Boolean) + .join('\n')} + +`; + if (Object.keys(component.props).length) { + markdownContent += propsDetails; + } + + if (Object.keys(component.tags).length > 0 && component.tags['returns']) { + markdownContent += `\n## Returns\n${component.tags['returns']}\n\n`; + } + + markdownContent += '{% /env %}'; + const folderPath = path.dirname(componentPath); + + // Create the folder if it doesn't exist + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath, { recursive: true }); + } + // Write the Markdown content to the file + fs.writeFileSync(componentPath, markdownContent); + }); +}); +overviewContent += '\n{% /env %}'; +fs.writeFileSync(path.join(outputDir, 'Overview.md'), overviewContent); +console.log('markdown files generated at docs/markdown'); diff --git a/src/components/CardHolderNameElement/index.tsx b/src/components/CardHolderNameElement/index.tsx index e9ec317..97ded3a 100644 --- a/src/components/CardHolderNameElement/index.tsx +++ b/src/components/CardHolderNameElement/index.tsx @@ -9,6 +9,9 @@ import SkyflowError from "../../utils/skyflow-error"; import SKYFLOW_ERROR_CODE from "../../utils/skyflow-error-code"; import uuid from 'react-native-uuid'; +/** + * Element to collect cardholder names. + */ const CardHolderNameElement: React.FC = ({ container, options = { required: false }, ...rest }) => { const [element, setElement] = React.useState(); const [elementValue, setElementValue] = React.useState(''); diff --git a/src/components/CardNumberElement/index.tsx b/src/components/CardNumberElement/index.tsx index 28faa37..1aa9c81 100644 --- a/src/components/CardNumberElement/index.tsx +++ b/src/components/CardNumberElement/index.tsx @@ -10,6 +10,9 @@ import SkyflowError from "../../utils/skyflow-error"; import SKYFLOW_ERROR_CODE from "../../utils/skyflow-error-code"; import uuid from 'react-native-uuid'; +/** + * Element to collect card numbers. + */ const CardNumberElement: React.FC = ({ container, options = { required: false }, ...rest }) => { const [element, setElement] = React.useState(undefined); const [elementValue, setElementValue] = React.useState(''); diff --git a/src/components/ComposableContainer/index.tsx b/src/components/ComposableContainer/index.tsx index 81a3813..2bfe569 100644 --- a/src/components/ComposableContainer/index.tsx +++ b/src/components/ComposableContainer/index.tsx @@ -6,11 +6,15 @@ import SkyflowError from "../../utils/skyflow-error"; import SKYFLOW_ERROR_CODE from "../../utils/skyflow-error-code"; export interface IComposableContainer { + /** Type of the container. */ container: CoreComposableContainer, + /** Function to call when the onSubmit event triggers. */ onSubmit?: () => void } - +/** + * Container Component for all composable elements. + */ const ComposableContainer: React.FC = (props) => { const containerRef = useRef([]); const { container, children } = props; diff --git a/src/components/CvvElement/index.tsx b/src/components/CvvElement/index.tsx index dddbe8d..9a29d21 100644 --- a/src/components/CvvElement/index.tsx +++ b/src/components/CvvElement/index.tsx @@ -9,6 +9,9 @@ import SkyflowError from "../../utils/skyflow-error"; import SKYFLOW_ERROR_CODE from "../../utils/skyflow-error-code"; import uuid from 'react-native-uuid'; +/** + * Element to collect CVVs. + */ const CvvElement: React.FC = ({ container, options = { requried: false }, ...rest }) => { const [element, setElement] = React.useState(); const [elementValue, setElementValue] = React.useState(''); diff --git a/src/components/ExpirationDateElement/index.tsx b/src/components/ExpirationDateElement/index.tsx index 04cd240..c828d3d 100644 --- a/src/components/ExpirationDateElement/index.tsx +++ b/src/components/ExpirationDateElement/index.tsx @@ -11,6 +11,9 @@ import SkyflowError from "../../utils/skyflow-error"; import SKYFLOW_ERROR_CODE from "../../utils/skyflow-error-code"; import uuid from 'react-native-uuid'; +/** + * Element to collect expiration dates. + */ const ExpirationDateElement: React.FC = ({ container, options, ...rest }) => { const [element, setElement] = React.useState(); const [elementValue, setElementValue] = React.useState(''); diff --git a/src/components/ExpirationMonthElement/index.tsx b/src/components/ExpirationMonthElement/index.tsx index 8092770..01f9be8 100644 --- a/src/components/ExpirationMonthElement/index.tsx +++ b/src/components/ExpirationMonthElement/index.tsx @@ -9,6 +9,9 @@ import SkyflowError from "../../utils/skyflow-error"; import SKYFLOW_ERROR_CODE from "../../utils/skyflow-error-code"; import uuid from 'react-native-uuid'; +/** + * Element to collect expiration month values. + */ const ExpirationMonthElement: React.FC = ({ container, options = { required: false }, ...rest }) => { const [element, setElement] = React.useState(); const [elementValue, setElementValue] = React.useState(''); diff --git a/src/components/ExpirationYearElement/index.tsx b/src/components/ExpirationYearElement/index.tsx index c21439e..badc79f 100644 --- a/src/components/ExpirationYearElement/index.tsx +++ b/src/components/ExpirationYearElement/index.tsx @@ -12,6 +12,9 @@ import SKYFLOW_ERROR_CODE from "../../utils/skyflow-error-code"; import uuid from 'react-native-uuid'; +/** + * Element to collect expiration year values. + */ const ExpirationYearElement: React.FC = ({ container, options, ...rest }) => { const [element, setElement] = React.useState(); const [elementValue, setElementValue] = React.useState(''); diff --git a/src/components/InputFieldElement/index.tsx b/src/components/InputFieldElement/index.tsx index 0825173..58837d0 100644 --- a/src/components/InputFieldElement/index.tsx +++ b/src/components/InputFieldElement/index.tsx @@ -9,6 +9,9 @@ import SkyflowError from "../../utils/skyflow-error"; import SKYFLOW_ERROR_CODE from "../../utils/skyflow-error-code"; import uuid from 'react-native-uuid'; +/** + * Element to collect arbitrary values. + */ const InputFieldElement: React.FC = ({ container, options = { required: false }, ...rest }) => { const [element, setElement] = React.useState(undefined); const [elementValue, setElementValue] = React.useState(''); diff --git a/src/components/PinElement/index.tsx b/src/components/PinElement/index.tsx index 6bab3dc..1292721 100644 --- a/src/components/PinElement/index.tsx +++ b/src/components/PinElement/index.tsx @@ -9,6 +9,9 @@ import SkyflowError from "../../utils/skyflow-error"; import SKYFLOW_ERROR_CODE from "../../utils/skyflow-error-code"; import uuid from 'react-native-uuid'; +/** + * Element to collect PIN values. + */ const PinElement: React.FC = ({ container, options = { required: false }, ...rest }) => { const [element, setElement] = React.useState(); const [elementValue, setElementValue] = React.useState(''); diff --git a/src/components/RevealElement/index.tsx b/src/components/RevealElement/index.tsx index d08872a..6487390 100644 --- a/src/components/RevealElement/index.tsx +++ b/src/components/RevealElement/index.tsx @@ -8,7 +8,9 @@ import { RevealElementProps } from "../../utils/constants" import SkyflowError from "../../utils/skyflow-error"; import SKYFLOW_ERROR_CODE from "../../utils/skyflow-error-code"; - +/** + * Configuration for Reveal Elements. + */ const RevealElement: React.FC = ({ container, label, ...rest }) => { const [element, setElement] = React.useState(undefined); const [errorText, setErrorText] = React.useState(''); diff --git a/src/components/SkyflowProvider/index.tsx b/src/components/SkyflowProvider/index.tsx index fb50ab3..9515871 100644 --- a/src/components/SkyflowProvider/index.tsx +++ b/src/components/SkyflowProvider/index.tsx @@ -6,11 +6,15 @@ import Skyflow from '../../core/Skyflow'; import { IConfig, SkyflowConfigIntialState } from '../../utils/constants'; export interface ISkyflowProvider { + /** Configuration object for SkyflowProvider. */ config: IConfig, } export const skyflowContext = React.createContext(null); +/** + * Sets up the Skyflow context using the provided configuration. + */ const SkyflowProvider: React.FC> = ({children,config}): JSX.Element => { const skyflow = new Skyflow(config); return {children} diff --git a/src/components/composableContainerRow/index.tsx b/src/components/composableContainerRow/index.tsx index 377fb26..2a47370 100644 --- a/src/components/composableContainerRow/index.tsx +++ b/src/components/composableContainerRow/index.tsx @@ -11,6 +11,9 @@ interface IComposableRowProps{ rowIndex?:any; } +/** + * @internal + */ const ComposableRow:React.FC = (props) => { const [errorTextMap,setErrorTextMap] = React.useState({}) const [rowErrorText,setRowErrorText] = React.useState(''); diff --git a/src/hooks/useCollectContainer/index.ts b/src/hooks/useCollectContainer/index.ts index 62e994b..26b46b6 100644 --- a/src/hooks/useCollectContainer/index.ts +++ b/src/hooks/useCollectContainer/index.ts @@ -4,6 +4,10 @@ import useSkyflowContext from '../../components/SkyflowProvider/hook'; import CollectContainer from '../../core/CollectContainer'; +/** + * Container for Collect Elements. + * @returns Returns the CollectContainer instance. + */ const useCollectContainer = () => { const skyflowClient = useSkyflowContext(); return new CollectContainer(skyflowClient); diff --git a/src/hooks/useComposableContainer/index.ts b/src/hooks/useComposableContainer/index.ts index a4591a0..e05b2da 100644 --- a/src/hooks/useComposableContainer/index.ts +++ b/src/hooks/useComposableContainer/index.ts @@ -5,6 +5,10 @@ import useSkyflowContext from '../../components/SkyflowProvider/hook'; import { validateComposableContainerOptions } from '../../core-utils/element-validations'; import CoreComposableContainer from '../../core/ComposableContainer'; +/** + * Hook for Composable Container. + * @returns Returns the ComposableContainer instance. + */ const useComposableContainer = (options: any) => { const skyflowClient = useSkyflowContext(); validateComposableContainerOptions(options); diff --git a/src/hooks/useRevealContainer/index.ts b/src/hooks/useRevealContainer/index.ts index e5c8c50..92c9d5a 100644 --- a/src/hooks/useRevealContainer/index.ts +++ b/src/hooks/useRevealContainer/index.ts @@ -4,6 +4,10 @@ import useSkyflowContext from '../../components/SkyflowProvider/hook'; import RevealContainer from '../../core/RevealContainer'; +/** + * Hook for Reveal Elements Container. + * @returns Returns the RevealContainer instance. + */ const useRevealContainer = () => { const skyflowClient = useSkyflowContext(); return new RevealContainer(skyflowClient); diff --git a/src/utils/constants/index.ts b/src/utils/constants/index.ts index 7749519..948f827 100644 --- a/src/utils/constants/index.ts +++ b/src/utils/constants/index.ts @@ -4,10 +4,17 @@ import type CollectContainer from '../../core/CollectContainer'; import RevealContainer from '../../core/RevealContainer'; +/** + * Configuration for connecting to the Skyflow vault. + */ export interface IConfig { + /** ID of the vault to connect to. */ vaultID: string; + /** URL of the vault to connect to. */ vaultURL: string; + /** Function that retrieves a Skyflow bearer token from your backend. */ getBearerToken: () => Promise; + /** Additional configuration options. */ options?: Record; } @@ -37,23 +44,41 @@ export interface CollectElementInput { } export interface CollectElementProps { + /** Table that the data belongs to. */ table: string; + /** Column that the data belongs to. */ column: string; + /** Type of the container. */ container: CollectContainer; + /** Label for the element. */ label?: string; + /** Placeholder text for the element. */ placeholder?: string; + /** Input validation rules for the element. */ validations?: IValidationRule[]; + /** Function to call when the onChange event triggers. */ onChange?: Function; + /** Function to call when the onReady event triggers. */ onReady?: Function; + /** Function to call when the onBlur event triggers. */ onBlur?: Function; + /** Function to call when the onFocus event triggers. */ onFocus?: Function; + /** Additional configuration options. */ options?: Record; + /** Styles for the element.*/ inputStyles?: CollectInputStylesVariant; + /** Styles for the element's label. */ labelStyles?: CollectLabelStylesVariant; + /** Styles for the element's error text. */ errorTextStyles?: StylesBaseVariant; + /** @internal */ containerMethods?: Record; } +/** + * Supported element types. + */ export enum ElementType { CVV = 'CVV', EXPIRATION_DATE = 'EXPIRATION_DATE', @@ -81,6 +106,9 @@ export enum ContentType { FORMDATA = 'multipart/form-data', } +/** + * Supported log levels. + */ export enum LogLevel { WARN = 'WARN', INFO = 'INFO', @@ -88,6 +116,9 @@ export enum LogLevel { ERROR = 'ERROR', } +/** + * Supported environments. + */ export enum Env { DEV = 'DEV', PROD = 'PROD', @@ -102,13 +133,21 @@ export interface RevealElementInput { } export interface RevealElementProps { + /** The actual data token. */ token: string; + /** The reveal container. */ container: RevealContainer; + /** Label for the form element. */ label?: string; + /** Alternative text for the Reveal Element. */ altText?: string; + /** Styles for the element. */ inputStyles?: StylesBaseVariant; + /** Styles for the element's label. */ labelStyles?: StylesBaseVariant; + /** Styles for the element's error text. */ errorTextStyles?: StylesBaseVariant; + /** Redaction type of the revealed data. */ redaction?: RedactionType; } @@ -129,6 +168,9 @@ export interface IRevealResponseType { errors?: Record[]; } +/** + * Supported validation rule types. + */ export enum ValidationRuleType { REGEX_MATCH_RULE = 'REGEX_MATCH_RULE', LENGTH_MATCH_RULE = 'LENGTH_MATCH_RULE', @@ -201,6 +243,9 @@ export const ELEMENT_REQUIRED_ASTERISK = ' *'; export const SKY_METADATA_HEADER = 'sky-metadata'; +/** + * Supported redaction types. + */ export enum RedactionType { DEFAULT = 'DEFAULT', PLAIN_TEXT = 'PLAIN_TEXT',