diff --git a/src/generators.js b/src/generators.js index 91247c1d..6e6cbdf4 100644 --- a/src/generators.js +++ b/src/generators.js @@ -3,6 +3,7 @@ import NextGenerator from "./generators/NextGenerator.js"; import NuxtGenerator from "./generators/NuxtGenerator.js"; import ReactGenerator from "./generators/ReactGenerator.js"; import ReactNativeGenerator from "./generators/ReactNativeGenerator.js"; +import ReactNativeGeneratorV2 from "./generators/ReactNativeGeneratorV2.js"; import TypescriptInterfaceGenerator from "./generators/TypescriptInterfaceGenerator.js"; import VueGenerator from "./generators/VueGenerator.js"; import VuetifyGenerator from "./generators/VuetifyGenerator.js"; @@ -28,6 +29,8 @@ export default async function generators(generator = "react") { return wrap(ReactGenerator); case "react-native": return wrap(ReactNativeGenerator); + case "react-native-v2": + return wrap(ReactNativeGeneratorV2); case "typescript": return wrap(TypescriptInterfaceGenerator); case "vue": diff --git a/src/generators/ReactNativeGeneratorV2.js b/src/generators/ReactNativeGeneratorV2.js new file mode 100644 index 00000000..fdfd9d7c --- /dev/null +++ b/src/generators/ReactNativeGeneratorV2.js @@ -0,0 +1,188 @@ +import chalk from "chalk"; +import handlebars from "handlebars"; +import BaseGenerator from "./BaseGenerator.js"; +import hbhComparison from "handlebars-helpers/lib/comparison.js"; + +export default class extends BaseGenerator { + constructor(params) { + super(params); + + handlebars.registerHelper("ifNotResource", function (item, options) { + if (item === null) { + return options.fn(this); + } + return options.inverse(this); + }); + + this.registerTemplates(`react-native-v2/`, [ + "app/(tabs)/foos.tsx", + "app/_layout.tsx.dist", + "lib/hooks.ts", + "lib/store.ts", + "lib/types/ApiResource.ts", + "lib/types/HydraView.ts", + "lib/types/Logs.ts", + "lib/types/foo.ts", + "lib/factory/logFactory.ts", + "lib/slices/fooSlice.ts", + "lib/api/fooApi.ts", + "components/Main.tsx", + "components/Navigation.tsx", + "components/StoreProvider.tsx", + "components/foo/CreateEditModal.tsx", + "components/foo/Form.tsx", + "components/foo/LogsRenderer.tsx", + ]); + + handlebars.registerHelper("compare", hbhComparison.compare); + } + + help(resource) { + const titleLc = resource.title.toLowerCase(); + + console.log( + 'Code for the "%s" resource type has been generated!', + resource.title + ); + + console.log("You must now configure the lib/store.ts"); + console.log( + chalk.green(` + // imports for ${titleLc} + import ${titleLc}Slice from './slices/${titleLc}Slice'; + import { ${titleLc}Api } from './api/${titleLc}Api'; + + // reducer for ${titleLc} + reducer: { + ... + ${titleLc}: ${titleLc}Slice, + [${titleLc}Api.reducerPath]: ${titleLc}Api.reducer, + } + + // middleware for ${titleLc} + getDefaultMiddleware().concat(..., ${titleLc}Api.middleware) + `) + ); + + console.log( + "You should replace app/_layout.tsx by the generated one and add the following route:" + ); + console.log( + chalk.green(` + + + tabs: { + ... + ${titleLc}: { + title: '${titleLc}', + headerShown: false, + tabBarIcon: ({ color }) => , + }, + } + `) + ); + } + + generate(api, resource, dir) { + const lc = resource.title.toLowerCase(); + const titleUcFirst = + resource.title.charAt(0).toUpperCase() + resource.title.slice(1); + const fields = this.parseFields(resource); + + const context = { + title: resource.title, + name: resource.name, + lc, + uc: resource.title.toUpperCase(), + fields, + formFields: this.buildFields(fields), + hydraPrefix: this.hydraPrefix, + ucf: titleUcFirst, + }; + + // Create directories + // These directories may already exist + [ + `${dir}/app/(tabs)`, + `${dir}/config`, + `${dir}/components`, + `${dir}/components/${lc}`, + `${dir}/lib`, + `${dir}/lib/api`, + `${dir}/lib/factory`, + `${dir}/lib/slices`, + `${dir}/lib/types`, + ].forEach((dir) => this.createDir(dir, false)); + + // static files + [ + "lib/hooks.ts", + "lib/store.ts", + "lib/types/ApiResource.ts", + "lib/types/HydraView.ts", + "lib/types/Logs.ts", + "lib/factory/logFactory.ts", + "components/Main.tsx", + "components/Navigation.tsx", + "components/StoreProvider.tsx", + ].forEach((file) => this.createFile(file, `${dir}/${file}`)); + + // templated files ucFirst + ["lib/types/%s.ts"].forEach((pattern) => + this.createFileFromPattern(pattern, dir, [titleUcFirst], context) + ); + + // templated files lc + [ + "app/(tabs)/%ss.tsx", + "app/_layout.tsx.dist", + "lib/slices/%sSlice.ts", + "lib/api/%sApi.ts", + "components/%s/CreateEditModal.tsx", + "components/%s/Form.tsx", + "components/%s/LogsRenderer.tsx", + ].forEach((pattern) => + this.createFileFromPattern(pattern, dir, [lc], context) + ); + + this.createEntrypoint(api.entrypoint, `${dir}/config/entrypoint.js`); + } + + getDescription(field) { + return field.description ? field.description.replace(/"/g, "'") : ""; + } + + parseFields(resource) { + const fields = [ + ...resource.writableFields, + ...resource.readableFields, + ].reduce((list, field) => { + if (list[field.name]) { + return list; + } + + const isReferences = Boolean( + field.reference && field.maxCardinality !== 1 + ); + const isEmbeddeds = Boolean(field.embedded && field.maxCardinality !== 1); + + return { + ...list, + [field.name]: { + ...field, + type: this.getType(field), + description: this.getDescription(field), + readonly: false, + isReferences, + isEmbeddeds, + isRelations: isEmbeddeds || isReferences, + }, + }; + }, {}); + + return Object.values(fields); + } +} diff --git a/src/generators/ReactNativeGeneratorV2.test.js b/src/generators/ReactNativeGeneratorV2.test.js new file mode 100644 index 00000000..2981cf64 --- /dev/null +++ b/src/generators/ReactNativeGeneratorV2.test.js @@ -0,0 +1,60 @@ +import { Api, Resource, Field } from "@api-platform/api-doc-parser"; +import path from "path"; +import { fileURLToPath } from "url"; +import fs from "fs"; +import tmp from "tmp"; +import ReactNativeGeneratorV2 from "./ReactNativeGeneratorV2.js"; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); + +test("Generate a React Native V2 app", () => { + const generator = new ReactNativeGeneratorV2({ + hydraPrefix: "hydra:", + templateDirectory: `${dirname}/../../templates`, + }); + const tmpobj = tmp.dirSync({ unsafeCleanup: true }); + + const fields = [ + new Field("bar", { + id: "http://schema.org/url", + range: "http://www.w3.org/2001/XMLSchema#string", + reference: null, + required: true, + description: "An URL", + }), + ]; + const resource = new Resource("abc", "http://example.com/foos", { + id: "abc", + title: "abc", + readableFields: fields, + writableFields: fields, + }); + const api = new Api("http://example.com", { + entrypoint: "http://example.com:8080", + title: "My API", + resources: [resource], + }); + generator.generate(api, resource, tmpobj.name); + + [ + "/lib/hooks.ts", + "/lib/store.ts", + "/lib/types/ApiResource.ts", + "/lib/types/HydraView.ts", + "/lib/types/Logs.ts", + "/lib/factory/logFactory.ts", + "/components/Main.tsx", + "/components/Navigation.tsx", + "/components/StoreProvider.tsx", + "/app/_layout.tsx.dist", + + "/app/(tabs)/abcs.tsx", + "/lib/slices/abcSlice.ts", + "/lib/api/abcApi.ts", + "/components/abc/CreateEditModal.tsx", + "/components/abc/Form.tsx", + "/components/abc/LogsRenderer.tsx", + ].forEach((file) => expect(fs.existsSync(tmpobj.name + file)).toBe(true)); + + tmpobj.removeCallback(); +}); diff --git a/templates/react-native-v2/app/(tabs)/foos.tsx b/templates/react-native-v2/app/(tabs)/foos.tsx new file mode 100644 index 00000000..a65beceb --- /dev/null +++ b/templates/react-native-v2/app/(tabs)/foos.tsx @@ -0,0 +1,73 @@ +import Main from "@/components/Main"; +import Navigation from "@/components/Navigation"; +import CreateEditModal from "@/components/{{{lc}}}/CreateEditModal"; +import LogsRenderer from "@/components/{{{lc}}}/LogsRenderer"; +import { useLazyGetAllQuery } from "@/lib/api/{{{lc}}}Api"; +import { useAppDispatch, useAppSelector } from "@/lib/hooks"; +import { setCurrentData, setData, setModalIsEdit, setModalIsVisible, setPage, setView } from "@/lib/slices/{{{lc}}}Slice"; +import {{{ucf}}} from "@/lib/types/{{{ucf}}}"; +import { useLocalSearchParams } from "expo-router"; +import { useEffect } from "react"; +import { Pressable, ScrollView, Text, View } from "react-native"; + +export default function {{{ucf}}}s() { + const datas = useAppSelector(state => state.{{lc}}.data); + const view = useAppSelector(state => state.{{{lc}}}.view); + const { page = '1' } = useLocalSearchParams<{ page: string }>(); + + const dispatch = useAppDispatch(); + const [getAll] = useLazyGetAllQuery(); + + const toggleEditModal = (data: {{{ucf}}}) => { + dispatch(setCurrentData(data)); + dispatch(setModalIsVisible(true)); + dispatch(setModalIsEdit(true)); + }; + + const toggleCreateModal = () => { + dispatch(setModalIsVisible(true)); + dispatch(setModalIsEdit(false)); + } + + useEffect(() => { + const intPage = parseInt(page); + if (intPage < 0) return; + dispatch(setPage(intPage)); + getAll(intPage) + .unwrap() + .then(fulfilled => { + dispatch(setView(fulfilled["hydra:view"])); + dispatch(setData(fulfilled["hydra:member"])); + }) + }, [page]); + + return ( +
+ + {{{ucf}}}s List + toggleCreateModal()}> + Create + + + + + + { + datas.map(data => ( + toggleEditModal(data)} key={data["@id"]}> + + ID: {data['@id']} + {{#each fields}} + {{{name}}}: {data["{{{name}}}"]} + {{/each}} + + + )) + } + + + + +
+ ); +} \ No newline at end of file diff --git a/templates/react-native-v2/app/_layout.tsx.dist b/templates/react-native-v2/app/_layout.tsx.dist new file mode 100644 index 00000000..aa787aec --- /dev/null +++ b/templates/react-native-v2/app/_layout.tsx.dist @@ -0,0 +1,39 @@ +import React from 'react'; +import { FontAwesome } from "@expo/vector-icons"; +import "../global.css"; +import { Tabs } from "expo-router"; +import StoreProvider from '@/components/StoreProvider'; + +function TabBarIcon(props: { + name: React.ComponentProps['name']; + color: string; +}) { + return ; +} +const iconMargin = { marginBottom: -3 } + +export default function Layout() { + return ( + + + + + + ) +} + +const options = { + tabsContainer: { + headerShown: false, + tabBarShowLabel: false, + }, + tabs: { + home: { + title: 'Accueil', + tabBarIcon: ({ color }) => , + }, + } +}; diff --git a/templates/react-native-v2/components/Main.tsx b/templates/react-native-v2/components/Main.tsx new file mode 100644 index 00000000..fa5fcb14 --- /dev/null +++ b/templates/react-native-v2/components/Main.tsx @@ -0,0 +1,16 @@ +import { View } from "react-native"; + +export default function Main({ children }) { + return ( + + {children} + + ) +} + +const styles = { + container: { + position: 'relative', + marginHorizontal: '3%', + } +} \ No newline at end of file diff --git a/templates/react-native-v2/components/Navigation.tsx b/templates/react-native-v2/components/Navigation.tsx new file mode 100644 index 00000000..0332f5cb --- /dev/null +++ b/templates/react-native-v2/components/Navigation.tsx @@ -0,0 +1,64 @@ +import { HydraView } from "@/lib/types/HydraView"; +import { FontAwesome } from "@expo/vector-icons"; +import { useRouter } from "expo-router"; +import { Pressable, View } from "react-native"; + +function NavigationIcon(props: { + name: React.ComponentProps['name']; + color: string; +}) { + return ; +} +const iconMargin = { marginBottom: -3 } + +export default function Navigation(props: { view: HydraView }) { + const view = props.view; + if (!view) { + return null; + } + + const router = useRouter(); + + const { + "hydra:first": first, + "hydra:previous": previous, + "hydra:next": next, + "hydra:last": last, + } = view; + + return ( + + { + if (first) router.navigate(first) + }}> + + + + { + if (previous) router.navigate(previous) + }}> + + + + { + if (next) router.navigate(next) + }}> + + + + { + if (last) router.navigate(last) + }}> + + + + ); +} + +const styles = { + container: { + position: 'absolute', + bottom: 5, + minWidth: '100%' + } +} \ No newline at end of file diff --git a/templates/react-native-v2/components/StoreProvider.tsx b/templates/react-native-v2/components/StoreProvider.tsx new file mode 100644 index 00000000..92936376 --- /dev/null +++ b/templates/react-native-v2/components/StoreProvider.tsx @@ -0,0 +1,17 @@ +import { useRef } from 'react' +import { Provider } from 'react-redux' +import { makeStore, AppStore } from '@/lib/store' + +export default function StoreProvider({ + children +}: { + children: React.ReactNode +}) { + const storeRef = useRef() + + if (!storeRef.current) { + storeRef.current = makeStore() + } + + return {children} +} diff --git a/templates/react-native-v2/components/foo/CreateEditModal.tsx b/templates/react-native-v2/components/foo/CreateEditModal.tsx new file mode 100644 index 00000000..b5fed4bb --- /dev/null +++ b/templates/react-native-v2/components/foo/CreateEditModal.tsx @@ -0,0 +1,67 @@ +import { useAppSelector } from "@/lib/hooks"; +import { Modal, Pressable, Text, View } from "react-native"; +import { useDispatch } from "react-redux"; +import Form from "./Form"; +import { addLog, setData, setModalIsVisible, setView } from "@/lib/slices/{{{lc}}}Slice"; +import { useDeleteMutation, useLazyGetAllQuery } from "@/lib/api/{{{lc}}}Api"; +import { createErrorLog, createSuccessLog } from "@/lib/factory/logFactory"; + +export default function CreateEditModal() { + const {{{lc}}}State = useAppSelector(state => state.{{{lc}}}); + const { modalState, currentData, page } = {{{lc}}}State; + const dispatch = useDispatch(); + const [deleteMutation] = useDeleteMutation(); + const [getAll] = useLazyGetAllQuery(); + + function handleDelete() { + deleteMutation(currentData['@id']) + .unwrap() + .then(() => { + dispatch(addLog(createSuccessLog(`{{{ucf}}} ${currentData['@id']} has been deleted successfully.`))) + getAll(page) + .unwrap() + .then(fulfilled => { + dispatch(setModalIsVisible(false)); + dispatch(setView(fulfilled["hydra:view"])); + dispatch(setData(fulfilled["hydra:member"])); + }); + }) + .catch(error => { + if (error.data) { + dispatch(addLog(createErrorLog(`Error: ${error.data["hydra:description"]}`))) + } + }); + } + + return ( + + + + {modalState.edit ? `Edit {{{ucf}}}` : 'Create a new {{{ucf}}}'} +
+ { + modalState.edit && + handleDelete()}> + Delete + + } + dispatch(setModalIsVisible(false))}> + Close + + + + + ) +} + +const styles = { + container: { height: '80%', width: '100%', backgroundColor: '#e3e9e5' }, + closeButton: { position: 'absolute', right: 5, top: 5 } +} \ No newline at end of file diff --git a/templates/react-native-v2/components/foo/Form.tsx b/templates/react-native-v2/components/foo/Form.tsx new file mode 100644 index 00000000..424b0dea --- /dev/null +++ b/templates/react-native-v2/components/foo/Form.tsx @@ -0,0 +1,120 @@ +import { useCreateMutation, useLazyGetAllQuery, useUpdateMutation } from "@/lib/api/{{{lc}}}Api"; +import { createErrorLog, createSuccessLog } from "@/lib/factory/logFactory"; +import { useAppSelector } from "@/lib/hooks"; +import { addLog, setData, setModalIsVisible, setView } from "@/lib/slices/{{{lc}}}Slice"; +import {{{ucf}}} from "@/lib/types/{{{ucf}}}"; +import { useState } from "react"; +import { Controller, SubmitErrorHandler, useForm } from "react-hook-form"; +import { Pressable, Text, TextInput, View } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { useDispatch } from "react-redux"; + +export default function Form() { + const [update] = useUpdateMutation(); + const [create] = useCreateMutation(); + const [getAll] = useLazyGetAllQuery(); + const {{{lc}}}Data = useAppSelector(state => state.{{{lc}}}); + const { page, currentData, modalState } = {{{lc}}}Data; + const dispatch = useDispatch(); + const [errors, setErrors] = useState([]); + const submitQuery = modalState.edit ? update : create; + + const initValues: {{{ucf}}} = modalState.edit ? currentData : { + '@id': '', + {{#each formFields}} + {{{name}}}: {{#if (compare type "==" "number")}}0{{else}}''{{/if}}, + {{/each}} + } + + const { control, handleSubmit, reset } = useForm<{{{ucf}}}>({ + defaultValues: initValues + }); + + + const onSubmit = (data: {{{ucf}}}) => { + intParser(data); + submitQuery(data) + .unwrap() + .then(() => { + dispatch(addLog(createSuccessLog(`The {{{lc}}} has been ${modalState.edit ? 'edited' : 'created'} successfully.`))) + getAll(page) + .unwrap() + .then(fulfilled => { + reset(); + dispatch(setView(fulfilled["hydra:view"])); + dispatch(setData(fulfilled["hydra:member"])); + dispatch(setModalIsVisible(false)); + }); + }) + .catch(error => { + if (error.data) { + dispatch( + addLog(createErrorLog(`Error : ${error.data["hydra:description"]}`)) + ) + } + }); + return; + }; + + const intParser = (data: {{{ucf}}}) => { + Object.keys(data).forEach(key => { + if ((typeof initValues[key] == "number") && !isNaN(parseInt(data[key]))) { + data[key] = parseInt(data[key]); + } + }); + return; + } + + const onError: SubmitErrorHandler<{{{ucf}}}> = (errors, e) => { + setErrors(Object.keys(errors)); + return; + } + + + return ( + + + {errors.length > 0 && + + + Field{errors.length > 1 ? "s" : ""} "{errors.join(', ')}" {errors.length > 1 ? "are" : "is"} empty + + + } + {{#each formFields}} + ( + + {{{name}}} : + + + )} + name="{{{name}}}" + {{#if required}}rules={fieldRequired}{{/if}} + /> + {{/each}} + + + {modalState.edit ? 'Edit' : 'Create'} + + + + ); +} + +const fieldRequired = { + required: true +} + +const styles = { + textInput: { minWidth: 200 } +} \ No newline at end of file diff --git a/templates/react-native-v2/components/foo/LogsRenderer.tsx b/templates/react-native-v2/components/foo/LogsRenderer.tsx new file mode 100644 index 00000000..4419732c --- /dev/null +++ b/templates/react-native-v2/components/foo/LogsRenderer.tsx @@ -0,0 +1,47 @@ +import { useAppSelector } from "@/lib/hooks"; +import { cleanLogs } from "@/lib/slices/{{{lc}}}Slice"; +import { Log } from "@/lib/types/Logs"; +import { Pressable, Text, View } from "react-native"; +import { useDispatch } from "react-redux"; + +export default function LogsRenderer() { + const logs = useAppSelector(state => state.{{{lc}}}.logs); + const { errors, successes } = logs; + + return ( + + { + errors.length > 0 && + + + {errors.map((error: string, index: number) => ( + - {error} + ))} + + + + } + { + successes.length > 0 && + + + {successes.map((success: string, index: number) => ( + - {success} + ))} + + + + } + + ) +} + +const CloseButton = (props: { type: keyof Log }) => { + const dispatch = useDispatch(); + + return ( + dispatch(cleanLogs(props.type))}> + X + + ) +} \ No newline at end of file diff --git a/templates/react-native-v2/lib/api/fooApi.ts b/templates/react-native-v2/lib/api/fooApi.ts new file mode 100644 index 00000000..6fceb81e --- /dev/null +++ b/templates/react-native-v2/lib/api/fooApi.ts @@ -0,0 +1,43 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import {{{ucf}}} from '../types/{{{ucf}}}'; +import { ENTRYPOINT } from '@/config/entrypoint'; + +export const {{{lc}}}Api = createApi({ + reducerPath: '{{{lc}}}Api', + baseQuery: fetchBaseQuery({ baseUrl: ENTRYPOINT }), + endpoints: builder => ({ + getAll: builder.query({ + query: (page) => { + return `/{{{lc}}}s?page=${page}` + } + }), + delete: builder.mutation({ + query: (id) => { + return { + url: `${id}`, + method: 'DELETE' + } + } + }), + create: builder.mutation({ + query: ({{{lc}}}) => { + return { + url: `/{{{lc}}}s`, + method: 'POST', + body: {{{lc}}}, + } + } + }), + update: builder.mutation({ + query: ({{{lc}}}) => { + return { + url: {{{lc}}}['@id'], + method: 'PUT', + body: {{{lc}}}, + } + } + }), + }) +}); + +export const { useLazyGetAllQuery, useDeleteMutation, useCreateMutation, useUpdateMutation } = {{{lc}}}Api; \ No newline at end of file diff --git a/templates/react-native-v2/lib/factory/logFactory.ts b/templates/react-native-v2/lib/factory/logFactory.ts new file mode 100644 index 00000000..69af26ee --- /dev/null +++ b/templates/react-native-v2/lib/factory/logFactory.ts @@ -0,0 +1,16 @@ +import { Log, NewLog } from "../types/Logs"; + +function createLog(type: keyof Log, message: string): NewLog { + return { + type, + message + } +} + +export function createSuccessLog(message: string): NewLog { + return createLog("successes", message); +} + +export function createErrorLog(message: string): NewLog { + return createLog("errors", message); +} \ No newline at end of file diff --git a/templates/react-native-v2/lib/hooks.ts b/templates/react-native-v2/lib/hooks.ts new file mode 100644 index 00000000..5c8ff39c --- /dev/null +++ b/templates/react-native-v2/lib/hooks.ts @@ -0,0 +1,7 @@ +import { useDispatch, useSelector, useStore } from 'react-redux' +import type { TypedUseSelectorHook } from 'react-redux' +import type { RootState, AppDispatch, AppStore } from './store' + +export const useAppDispatch: () => AppDispatch = useDispatch +export const useAppSelector: TypedUseSelectorHook = useSelector +export const useAppStore: () => AppStore = useStore \ No newline at end of file diff --git a/templates/react-native-v2/lib/slices/fooSlice.ts b/templates/react-native-v2/lib/slices/fooSlice.ts new file mode 100644 index 00000000..068480da --- /dev/null +++ b/templates/react-native-v2/lib/slices/fooSlice.ts @@ -0,0 +1,69 @@ +import { createSlice } from '@reduxjs/toolkit' +import type { PayloadAction } from '@reduxjs/toolkit' +import {{{ucf}}} from '../types/{{{ucf}}}'; +import { HydraView } from '../types/HydraView'; +import { Log, NewLog } from '../types/Logs'; + +interface {{{ucf}}}SliceState { + page: number; + data: {{{ucf}}}[]; + currentData?: {{{ucf}}}; + modalState: ModalState; + view: HydraView; + logs: Log; +} + +interface ModalState { + open: boolean; + edit: boolean; +} + +const initialState: {{{ucf}}}SliceState = { + page: 1, + data: [], + currentData: undefined, + modalState: { + open: false, + edit: false, + }, + view: {}, + logs: { + errors: [], + successes: [], + } +} + +export const {{{lc}}}Slice = createSlice({ + name: '{{{lc}}}Slice', + initialState, + reducers: { + setPage: (state, action: PayloadAction) => { + state.page = action.payload; + }, + setData: (state, action: PayloadAction<{{{ucf}}}[]>) => { + state.data = action.payload; + }, + setView: (state, action: PayloadAction) => { + state.view = action.payload; + }, + setCurrentData: (state, action: PayloadAction<{{{ucf}}}>) => { + state.currentData = action.payload; + }, + setModalIsVisible: (state, action: PayloadAction) => { + state.modalState.open = action.payload; + }, + setModalIsEdit: (state, action: PayloadAction) => { + state.modalState.edit = action.payload; + }, + addLog: (state, action: PayloadAction) => { + state.logs[action.payload.type] = [...state.logs[action.payload.type], action.payload.message]; + }, + cleanLogs: (state, action: PayloadAction) => { + state.logs[action.payload] = []; + }, + } +}) + +export const { setPage, setData, setView, setCurrentData, setModalIsEdit, setModalIsVisible, addLog, cleanLogs } = {{{lc}}}Slice.actions; + +export default {{{lc}}}Slice.reducer; \ No newline at end of file diff --git a/templates/react-native-v2/lib/store.ts b/templates/react-native-v2/lib/store.ts new file mode 100644 index 00000000..7d829bc0 --- /dev/null +++ b/templates/react-native-v2/lib/store.ts @@ -0,0 +1,16 @@ +import { configureStore } from '@reduxjs/toolkit'; + +export const makeStore = () => { + return configureStore({ + reducer: { + }, + middleware: getDefaultMiddleware => + getDefaultMiddleware() + .concat(), + }); +}; + +export type AppStore = ReturnType; +export type RootState = ReturnType; +export type AppDispatch = AppStore['dispatch']; + diff --git a/templates/react-native-v2/lib/types/ApiResource.ts b/templates/react-native-v2/lib/types/ApiResource.ts new file mode 100644 index 00000000..217fe654 --- /dev/null +++ b/templates/react-native-v2/lib/types/ApiResource.ts @@ -0,0 +1,3 @@ +export default interface ApiResource { + "@id": string; +} \ No newline at end of file diff --git a/templates/react-native-v2/lib/types/HydraView.ts b/templates/react-native-v2/lib/types/HydraView.ts new file mode 100644 index 00000000..c4b9e82d --- /dev/null +++ b/templates/react-native-v2/lib/types/HydraView.ts @@ -0,0 +1,6 @@ +export interface HydraView { + 'hydra:first'?: string; + 'hydra:last'?: string; + 'hydra:previous'?: string; + 'hydra:next'?: string; +} \ No newline at end of file diff --git a/templates/react-native-v2/lib/types/Logs.ts b/templates/react-native-v2/lib/types/Logs.ts new file mode 100644 index 00000000..46a49352 --- /dev/null +++ b/templates/react-native-v2/lib/types/Logs.ts @@ -0,0 +1,9 @@ +export interface Log { + errors: Array; + successes: Array; +} + +export interface NewLog { + type: keyof Log; + message: string; +} \ No newline at end of file diff --git a/templates/react-native-v2/lib/types/foo.ts b/templates/react-native-v2/lib/types/foo.ts new file mode 100644 index 00000000..d5f67698 --- /dev/null +++ b/templates/react-native-v2/lib/types/foo.ts @@ -0,0 +1,7 @@ +import ApiResource from "./ApiResource"; + +export default interface {{{ ucf }}} extends ApiResource { + {{#each fields}} + {{#if readonly}}readonly{{/if}} {{{name}}}?: {{#if (compare type "==" "Date")}}string{{else}}{{{type}}}{{/if}}; + {{/each}} +} \ No newline at end of file