├── packages ├── demo │ ├── .babelrc │ ├── .gitignore │ ├── src │ │ ├── polyfills.ts │ │ ├── i18n │ │ │ ├── index.ts │ │ │ ├── en.ts │ │ │ └── fr.ts │ │ ├── validators.tsx │ │ ├── tags │ │ │ ├── index.tsx │ │ │ ├── TagCreate.tsx │ │ │ ├── TagShow.tsx │ │ │ ├── TagEdit.tsx │ │ │ └── TagList.tsx │ │ ├── Layout.tsx │ │ ├── posts │ │ │ ├── index.tsx │ │ │ ├── PostTitle.tsx │ │ │ ├── ResetViewsButton.tsx │ │ │ ├── TagReferenceInput.tsx │ │ │ ├── PostShow.tsx │ │ │ ├── PostList.tsx │ │ │ ├── PostCreate.tsx │ │ │ └── PostEdit.tsx │ │ ├── comments │ │ │ ├── index.tsx │ │ │ ├── CommentShow.tsx │ │ │ ├── PostQuickCreateCancelButton.tsx │ │ │ ├── PostPreview.tsx │ │ │ ├── CommentCreate.tsx │ │ │ ├── PostQuickCreate.tsx │ │ │ ├── PostReferenceInput.tsx │ │ │ ├── CommentList.tsx │ │ │ └── CommentEdit.tsx │ │ ├── users │ │ │ ├── index.tsx │ │ │ ├── UserEditEmbedded.tsx │ │ │ ├── UserShow.tsx │ │ │ ├── Aside.tsx │ │ │ ├── UserList.tsx │ │ │ ├── UserEdit.tsx │ │ │ └── UserCreate.tsx │ │ ├── i18nProvider.tsx │ │ ├── index.tsx │ │ ├── customRouteNoLayout.tsx │ │ ├── customRouteLayout.tsx │ │ ├── addUploadFeature.tsx │ │ ├── dataProvider.tsx │ │ ├── App.tsx │ │ └── data.tsx │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── tsconfig.app.json │ ├── vite.config.js │ ├── package.json │ ├── docker-compose.yml │ └── index.html └── ra-keycloak │ ├── src │ ├── index.ts │ ├── LoginPage.tsx │ ├── httpClient.spec.ts │ ├── httpClient.ts │ └── authProvider.ts │ ├── tsconfig.json │ ├── package.json │ └── Readme.md ├── .prettierrc.js ├── .gitignore ├── LICENSE.md ├── .eslintrc ├── tsconfig.json ├── Makefile ├── package.json ├── README.md └── CHANGELOG.md /packages/demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /packages/ra-keycloak/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './httpClient'; 2 | export * from './authProvider'; 3 | export * from './LoginPage'; 4 | -------------------------------------------------------------------------------- /packages/demo/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie11'; 2 | import 'react-app-polyfill/stable'; 3 | import 'proxy-polyfill/proxy.min.js'; 4 | -------------------------------------------------------------------------------- /packages/demo/src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import enMessages from './en'; 2 | import frMessages from './fr'; 3 | 4 | export const en = enMessages; 5 | export const fr = frMessages; 6 | -------------------------------------------------------------------------------- /packages/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/demo/src/validators.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | required as createRequiredValidator, 3 | number as createNumberValidator, 4 | } from 'react-admin'; 5 | 6 | export const required = createRequiredValidator(); 7 | export const number = createNumberValidator(); 8 | -------------------------------------------------------------------------------- /packages/ra-keycloak/src/LoginPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useLogin } from 'ra-core'; 3 | 4 | export const LoginPage = () => { 5 | const login = useLogin(); 6 | React.useEffect(() => { 7 | login({}); 8 | }, [login]); 9 | return null; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/demo/src/tags/index.tsx: -------------------------------------------------------------------------------- 1 | import TagCreate from './TagCreate'; 2 | import TagEdit from './TagEdit'; 3 | import TagList from './TagList'; 4 | import TagShow from './TagShow'; 5 | 6 | export default { 7 | create: TagCreate, 8 | edit: TagEdit, 9 | list: TagList, 10 | show: TagShow, 11 | recordRepresentation: 'name.en', 12 | }; 13 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSpacing: true, 4 | jsxBracketSameLine: false, 5 | jsxSingleQuote: false, 6 | printWidth: 80, 7 | quoteProps: 'as-needed', 8 | rangeStart: 0, 9 | rangeEnd: Infinity, 10 | semi: true, 11 | singleQuote: true, 12 | tabWidth: 4, 13 | trailingComma: 'es5', 14 | useTabs: false, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/demo/src/Layout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; 4 | import { Layout } from 'react-admin'; 5 | import { CssBaseline } from '@mui/material'; 6 | 7 | export default props => ( 8 | <> 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /packages/ra-keycloak/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "allowJs": false 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "esm", 12 | "lib", 13 | "**/*.spec.ts", 14 | "**/*.spec.tsx", 15 | "**/*.spec.js" 16 | ], 17 | "include": ["src", "stories"] 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | lib 12 | esm 13 | es6 14 | dist 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | .eslintcache 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /packages/demo/src/posts/index.tsx: -------------------------------------------------------------------------------- 1 | import BookIcon from '@mui/icons-material/Book'; 2 | import PostCreate from './PostCreate'; 3 | import PostEdit from './PostEdit'; 4 | import PostList from './PostList'; 5 | import PostShow from './PostShow'; 6 | 7 | export default { 8 | list: PostList, 9 | create: PostCreate, 10 | edit: PostEdit, 11 | show: PostShow, 12 | icon: BookIcon, 13 | recordRepresentation: 'title', 14 | }; 15 | -------------------------------------------------------------------------------- /packages/demo/src/posts/PostTitle.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useTranslate, useRecordContext } from 'react-admin'; 3 | 4 | export default () => { 5 | const translate = useTranslate(); 6 | const record = useRecordContext(); 7 | return ( 8 | <> 9 | {record 10 | ? translate('post.edit.title', { title: record.title }) 11 | : ''} 12 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/demo/src/comments/index.tsx: -------------------------------------------------------------------------------- 1 | import ChatBubbleIcon from '@mui/icons-material/ChatBubble'; 2 | import CommentCreate from './CommentCreate'; 3 | import CommentEdit from './CommentEdit'; 4 | import CommentList from './CommentList'; 5 | import CommentShow from './CommentShow'; 6 | 7 | export default { 8 | list: CommentList, 9 | create: CommentCreate, 10 | edit: CommentEdit, 11 | show: CommentShow, 12 | icon: ChatBubbleIcon, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/demo/src/users/index.tsx: -------------------------------------------------------------------------------- 1 | import PeopleIcon from '@mui/icons-material/People'; 2 | import UserCreate from './UserCreate'; 3 | import UserEdit from './UserEdit'; 4 | import UserList from './UserList'; 5 | import UserShow from './UserShow'; 6 | 7 | export default { 8 | list: UserList, 9 | create: UserCreate, 10 | edit: UserEdit, 11 | show: UserShow, 12 | icon: PeopleIcon, 13 | recordRepresentation: record => `${record.name} (${record.role})`, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/demo/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "isolatedModules": true, 12 | "moduleDetection": "force", 13 | "noEmit": true, 14 | 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": ["vite.config.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/demo/src/i18nProvider.tsx: -------------------------------------------------------------------------------- 1 | import polyglotI18nProvider from 'ra-i18n-polyglot'; 2 | import englishMessages from './i18n/en'; 3 | 4 | const messages = { 5 | fr: () => import('./i18n/fr').then(messages => messages.default), 6 | }; 7 | 8 | export default polyglotI18nProvider( 9 | locale => { 10 | if (locale === 'fr') { 11 | return messages[locale](); 12 | } 13 | 14 | // Always fallback on english 15 | return englishMessages; 16 | }, 17 | 'en', 18 | [ 19 | { locale: 'en', name: 'English' }, 20 | { locale: 'fr', name: 'Français' }, 21 | ] 22 | ); 23 | -------------------------------------------------------------------------------- /packages/demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import { createBrowserRouter, RouterProvider } from 'react-router-dom'; 5 | 6 | const container = document.getElementById('root'); 7 | const root = ReactDOM.createRoot(container); 8 | const router = createBrowserRouter([{ path: '*', element: }]); 9 | 10 | root.render( 11 | 12 | {/* Comment to test HashRouter */} 13 | 14 | {/* Uncomment to test HashRouter */} 15 | {/* */} 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /packages/demo/src/tags/TagCreate.tsx: -------------------------------------------------------------------------------- 1 | /* eslint react/jsx-key: off */ 2 | import * as React from 'react'; 3 | import { 4 | Create, 5 | SimpleForm, 6 | TextField, 7 | TextInput, 8 | required, 9 | TranslatableInputs, 10 | } from 'react-admin'; 11 | 12 | const TagCreate = () => ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | 23 | export default TagCreate; 24 | -------------------------------------------------------------------------------- /packages/demo/src/tags/TagShow.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { 3 | Show, 4 | SimpleShowLayout, 5 | TextField, 6 | TranslatableFields, 7 | BooleanField, 8 | } from 'react-admin'; // eslint-disable-line import/no-unresolved 9 | 10 | const TagShow = () => ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | export default TagShow; 23 | -------------------------------------------------------------------------------- /packages/demo/src/users/UserEditEmbedded.tsx: -------------------------------------------------------------------------------- 1 | /* eslint react/jsx-key: off */ 2 | import * as React from 'react'; 3 | import { Edit, Identifier, SimpleForm, TextInput, required } from 'react-admin'; 4 | 5 | const UserEditEmbedded = ({ id }: { id?: Identifier }) => ( 6 | /* Passing " " as title disables the custom title */ 7 | 8 | 9 | 14 | 15 | 16 | ); 17 | 18 | export default UserEditEmbedded; 19 | -------------------------------------------------------------------------------- /packages/demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/demo/vite.config.js: -------------------------------------------------------------------------------- 1 | import reactRefresh from '@vitejs/plugin-react-refresh'; 2 | import path from 'path'; 3 | 4 | /** 5 | * https://vitejs.dev/config/ 6 | * @type { import('vite').UserConfig } 7 | */ 8 | export default { 9 | plugins: [reactRefresh()], 10 | resolve: { 11 | alias: [ 12 | { 13 | find: /^ra-keycloak$/, 14 | replacement: path.resolve(__dirname, '../ra-keycloak/src'), 15 | }, 16 | { 17 | find: /^@mui\/icons-material\/(.*)/, 18 | replacement: '@mui/icons-material/esm/$1', 19 | }, 20 | ], 21 | }, 22 | server: { 23 | port: 8081, 24 | }, 25 | define: { 'process.env': {} }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/demo/src/customRouteNoLayout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useGetList } from 'react-admin'; 3 | 4 | const CustomRouteNoLayout = ({ title = 'Posts' }) => { 5 | const { isLoading, total } = useGetList('posts', { 6 | pagination: { page: 0, perPage: 10 }, 7 | sort: { field: 'id', order: 'ASC' }, 8 | }); 9 | 10 | return ( 11 |
12 |

{title}

13 | {isLoading ? ( 14 |

Loading...

15 | ) : ( 16 |

17 | Found {total} posts ! 18 |

19 | )} 20 |
21 | ); 22 | }; 23 | 24 | export default CustomRouteNoLayout; 25 | -------------------------------------------------------------------------------- /packages/demo/src/comments/CommentShow.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { 3 | DateField, 4 | ReferenceField, 5 | Show, 6 | SimpleShowLayout, 7 | TextField, 8 | } from 'react-admin'; // eslint-disable-line import/no-unresolved 9 | 10 | const CommentShow = () => ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | 24 | export default CommentShow; 25 | -------------------------------------------------------------------------------- /packages/demo/src/comments/PostQuickCreateCancelButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Button } from '@mui/material'; 4 | import IconCancel from '@mui/icons-material/Cancel'; 5 | 6 | import { useTranslate } from 'react-admin'; 7 | 8 | const PostQuickCreateCancelButton = ({ 9 | onClick, 10 | label = 'ra.action.cancel', 11 | }) => { 12 | const translate = useTranslate(); 13 | 14 | return ( 15 | 22 | ); 23 | }; 24 | 25 | export default PostQuickCreateCancelButton; 26 | -------------------------------------------------------------------------------- /packages/ra-keycloak/src/httpClient.spec.ts: -------------------------------------------------------------------------------- 1 | import { getKeycloakHeaders } from './httpClient'; 2 | 3 | describe('getKeycloakHeaders', () => { 4 | it('should return the headers needed to authenticate keycloak user', () => { 5 | const expected = new Headers({ 6 | Accept: 'application/json', 7 | Authorization: 'Bearer KeycloakToken', 8 | }); 9 | 10 | const extraHeaders = getKeycloakHeaders('KeycloakToken', {}); 11 | 12 | expect(extraHeaders).toEqual(expected); 13 | }); 14 | 15 | it('should not add specific header when token is not defined', () => { 16 | const expected = new Headers({ 17 | Accept: 'application/json', 18 | }); 19 | 20 | const extraHeaders = getKeycloakHeaders(null, {}); 21 | 22 | expect(extraHeaders).toEqual(expected); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/demo/src/users/UserShow.tsx: -------------------------------------------------------------------------------- 1 | /* eslint react/jsx-key: off */ 2 | import * as React from 'react'; 3 | import { 4 | Show, 5 | Tab, 6 | TabbedShowLayout, 7 | TextField, 8 | usePermissions, 9 | } from 'react-admin'; 10 | 11 | import Aside from './Aside'; 12 | 13 | const UserShow = () => { 14 | const { permissions } = usePermissions(); 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | {permissions === 'admin' && ( 23 | 24 | 25 | 26 | )} 27 | 28 |