├── .gitignore ├── .npmrc ├── README.MD ├── package.json ├── pnpm-lock.yaml ├── public ├── favicon.ico ├── images │ ├── flags │ │ ├── de.svg │ │ └── en.svg │ └── screenshots │ │ ├── client_elk.png │ │ ├── client_leaf.png │ │ ├── client_pinafore.png │ │ ├── dashboard_entries.png │ │ ├── dashboard_favorites.png │ │ ├── dashboard_feeds.png │ │ └── dashboard_home.png ├── index.html └── locales │ ├── de │ └── common.json │ └── en │ └── common.json ├── src ├── App.tsx ├── authProvider.ts ├── components │ ├── atoms │ │ └── index.tsx │ ├── header │ │ └── index.tsx │ └── index.ts ├── i18n.ts ├── index.tsx ├── meta.json ├── pages │ ├── categories │ │ ├── create.tsx │ │ ├── edit.tsx │ │ ├── index.ts │ │ ├── list.tsx │ │ └── show.tsx │ ├── dashboard.tsx │ ├── feeds │ │ ├── create.tsx │ │ ├── edit.tsx │ │ ├── index.ts │ │ ├── list.tsx │ │ └── show.tsx │ └── products │ │ ├── create.tsx │ │ ├── edit.tsx │ │ ├── index.ts │ │ ├── list.tsx │ │ └── show.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── rest-data-provider │ ├── index.ts │ └── utils │ │ ├── axios.ts │ │ ├── generateFilter.ts │ │ ├── generateSort.ts │ │ ├── index.ts │ │ └── mapOperator.ts └── setupTests.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | strict-peer-dependencies=false -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | #

📱 PocketRSS

2 | 3 |

PocketRSS - A powerfull RSS aggregate server in 1 file compatible with Mastodon client and RSS client.

4 | 5 | ## Features 6 | 7 | PocketRSS is a powerfull RSS aggregate server in 1 file with **XML** and 8 | **json** APIs. It compatible RSS clients and mastodon clients. 9 | 10 | - ⚡️ 1 file and lower resource needed 11 | 12 | - 🌀 1 click running 13 | 14 | - ⚛️ Easy to use 15 | 16 | - 💨 Elegant dashboard 17 | 18 | - 🐘 Compatible with mastodon clients 19 | 20 | - 💎 Compatible with RSS clients 21 | 22 | - 🔨 Saving data, favorites etc in your own server 23 | 24 | ## Getting Started 25 | 26 | ### Docker 27 | 28 | Create config file as below 29 | 30 | ```toml 31 | [listen] 32 | ip = "0.0.0.0" 33 | port = 5000 34 | pprof = false 35 | 36 | [instance] 37 | uri = "pocketrss.com" 38 | websocket_endpoint = "wss://pocketrss.com" 39 | enable_sensitive = false 40 | 41 | [db] 42 | name = "./pocketrss.db" 43 | 44 | [sync] 45 | enabled = true 46 | interval = 10 # number as minute; 数字,单位分钟 47 | 48 | [logger] 49 | level = "error" 50 | ``` 51 | 52 | ``` 53 | docker run -itd --name pocketrss -p 5000:5000 -v /some/where/pocketrss.toml:/app/pocketrss.toml -v /some/where/pocketrss.db:/app/pocketrss.db leopku/pocketrss 54 | ``` 55 | 56 | > menthion: remember to change `/some/where` to your actual path where you want 57 | > to save config and database files. 58 | 59 | ### Binary install 60 | 61 | 1. Download 62 | [newest release](https://github.com/pocketrss/pocketrss-dasnboard/releases) 63 | from github 64 | 65 | 2. Uncompress downloaded file and open uncompressed directory 66 | 67 | 3. Copy `pocketrss.example.toml` as `pocketrss.toml` 68 | 69 | 4. Run pocketrss server in terminal 70 | 71 | ```bash 72 | ./pocketrss_linux_amd64 serve 73 | ``` 74 | 75 | 5. Look at [http://localhost:5000](http://localhost:5000) to see the dashboard. 76 | 77 | ## Screenshot 78 | 79 | ### Dashboard 80 | 81 | - Home 82 | 83 | ![](/public/images/screenshots/dashboard_home.png?raw=true) 84 | 85 | - Feeds 86 | 87 | ![](/public/images/screenshots/dashboard_feeds.png?raw=true) 88 | 89 | - Entries 90 | 91 | ![](/public/images/screenshots/dashboard_entries.png?raw=true) 92 | 93 | - Favorites 94 | 95 | ![](/public/images/screenshots/dashboard_favorites.png?raw=true) 96 | 97 | ### Clients 98 | 99 | #### Android 100 | 101 | - Tusky 102 | 103 | ![](/public/images/screenshots/client_tusky.jpg?raw=true) 104 | 105 | #### Desktop 106 | 107 | - Leaf 108 | 109 | ![](/public/images/screenshots/client_leaf.png?raw=true) 110 | 111 | ### Web 112 | - [elk](https://github.com/elk-zone/elk) (**Recommend**) 113 | 114 | ![](/public/images/screenshots/client_elk.png?raw=true) 115 | 116 | - [Pinafore](https://pinafore.social) 117 | 118 | ![](/public/images/screenshots/client_pinafore.png?raw=true) 119 | 120 | ## FAQ 121 | 122 | - Where is the RSS endpoint 123 | 124 | `http[s]://your.server/rss` 125 | 126 | - How can I visit my own server without SSL using Tusky. 127 | 128 | Using my modified version of Tusky. I can be found in 129 | [release page](https://github.com/pocketrss/pocketrss-dashboard/releases/tag/Tusky-debug-20220713) 130 | 131 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pocketrss-dashboard", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@chakra-ui/react": "^2.5.1", 7 | "@refinedev/chakra-ui": "^2.0.0", 8 | "@refinedev/cli": "^2.0.0", 9 | "@refinedev/core": "^4.0.0", 10 | "@refinedev/inferencer": "^3.0.0", 11 | "@refinedev/kbar": "^1.0.0", 12 | "@refinedev/react-hook-form": "^4.0.0", 13 | "@refinedev/react-router-v6": "^4.0.0", 14 | "@refinedev/react-table": "^5.0.0", 15 | "@refinedev/simple-rest": "^4.0.0", 16 | "@tabler/icons": "^1.1.0", 17 | "axios": "^1.3.4", 18 | "consola": "^2.15.3", 19 | "i18next": "^20.1.0", 20 | "i18next-browser-languagedetector": "^6.1.1", 21 | "i18next-xhr-backend": "^3.2.2", 22 | "lucide-react": "^0.125.0", 23 | "luxon": "^3.3.0", 24 | "rambdax": "^9.0.0", 25 | "react": "^18.0.0", 26 | "react-dom": "^18.0.0", 27 | "react-hook-form": "^7.30.0", 28 | "react-html-renderer": "^0.3.3", 29 | "react-i18next": "^11.8.11", 30 | "react-router-dom": "^6.8.1", 31 | "react-scripts": "^5.0.0" 32 | }, 33 | "devDependencies": { 34 | "@testing-library/jest-dom": "^5.16.4", 35 | "@testing-library/react": "^13.1.1", 36 | "@testing-library/user-event": "^14.1.1", 37 | "@types/jest": "^29.2.4", 38 | "@types/node": "^12.20.11", 39 | "@types/react": "^18.0.0", 40 | "@types/react-dom": "^18.0.0", 41 | "typescript": "^4.7.4", 42 | "web-vitals": "^1.1.1" 43 | }, 44 | "scripts": { 45 | "dev": "refine start", 46 | "build": "refine build", 47 | "test": "react-scripts test", 48 | "eject": "react-scripts eject", 49 | "refine": "refine" 50 | }, 51 | "eslintConfig": { 52 | "extends": [ 53 | "react-app", 54 | "react-app/jest" 55 | ] 56 | }, 57 | "browserslist": { 58 | "production": [ 59 | ">0.2%", 60 | "not dead", 61 | "not op_mini all" 62 | ], 63 | "development": [ 64 | "last 1 chrome version", 65 | "last 1 firefox version", 66 | "last 1 safari version" 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pocketrss/pocketrss-dashboard/71eeabc3c227ac8fcfcc4037abe9b3bbbe657519/public/favicon.ico -------------------------------------------------------------------------------- /public/images/flags/de.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/en.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/screenshots/client_elk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pocketrss/pocketrss-dashboard/71eeabc3c227ac8fcfcc4037abe9b3bbbe657519/public/images/screenshots/client_elk.png -------------------------------------------------------------------------------- /public/images/screenshots/client_leaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pocketrss/pocketrss-dashboard/71eeabc3c227ac8fcfcc4037abe9b3bbbe657519/public/images/screenshots/client_leaf.png -------------------------------------------------------------------------------- /public/images/screenshots/client_pinafore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pocketrss/pocketrss-dashboard/71eeabc3c227ac8fcfcc4037abe9b3bbbe657519/public/images/screenshots/client_pinafore.png -------------------------------------------------------------------------------- /public/images/screenshots/dashboard_entries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pocketrss/pocketrss-dashboard/71eeabc3c227ac8fcfcc4037abe9b3bbbe657519/public/images/screenshots/dashboard_entries.png -------------------------------------------------------------------------------- /public/images/screenshots/dashboard_favorites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pocketrss/pocketrss-dashboard/71eeabc3c227ac8fcfcc4037abe9b3bbbe657519/public/images/screenshots/dashboard_favorites.png -------------------------------------------------------------------------------- /public/images/screenshots/dashboard_feeds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pocketrss/pocketrss-dashboard/71eeabc3c227ac8fcfcc4037abe9b3bbbe657519/public/images/screenshots/dashboard_feeds.png -------------------------------------------------------------------------------- /public/images/screenshots/dashboard_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pocketrss/pocketrss-dashboard/71eeabc3c227ac8fcfcc4037abe9b3bbbe657519/public/images/screenshots/dashboard_home.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 25 | 26 | PocketRSS - A powerful RSS aggregate server in 1 file. 27 | 28 | 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /public/locales/de/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": { 3 | "login": { 4 | "title": "Melden Sie sich bei Ihrem Konto an", 5 | "signin": "Einloggen", 6 | "signup": "Anmelden", 7 | "divider": "oder", 8 | "fields": { 9 | "email": "Email", 10 | "password": "Passwort" 11 | }, 12 | "errors": { 13 | "validEmail": "Ungültige E-Mail-Adresse" 14 | }, 15 | "buttons": { 16 | "submit": "Anmeldung", 17 | "forgotPassword": "Passwort vergessen?", 18 | "noAccount": "Sie haben kein Konto?", 19 | "rememberMe": "Erinnere dich an mich" 20 | } 21 | }, 22 | "forgotPassword": { 23 | "title": "Haben Sie Ihr Passwort vergessen?", 24 | "fields": { 25 | "email": "Email" 26 | }, 27 | "errors": { 28 | "validEmail": "Ungültige E-Mail-Adresse" 29 | }, 30 | "buttons": { 31 | "submit": "Anweisungen zum Zurücksetzen senden" 32 | } 33 | }, 34 | "register": { 35 | "title": "Registrieren Sie sich für Ihr Konto", 36 | "fields": { 37 | "email": "Email", 38 | "password": "Passwort" 39 | }, 40 | "errors": { 41 | "validEmail": "Ungültige E-Mail-Adresse" 42 | }, 43 | "buttons": { 44 | "submit": "Registrieren", 45 | "haveAccount": "Ein Konto haben?" 46 | } 47 | }, 48 | "updatePassword": { 49 | "title": "Kennwort aktualisieren", 50 | "fields": { 51 | "password": "Neues Passwort", 52 | "confirmPassword": "Bestätige neues Passwort" 53 | }, 54 | "errors": { 55 | "confirmPasswordNotMatch": "Passwörter stimmen nicht überein" 56 | }, 57 | "buttons": { 58 | "submit": "Aktualisieren" 59 | } 60 | }, 61 | "error": { 62 | "info": "Sie haben vergessen, {{action}} component zu {{resource}} hinzufügen.", 63 | "404": "Leider existiert diese Seite nicht.", 64 | "resource404": "Haben Sie die {{resource}} resource erstellt?", 65 | "backHome": "Zurück" 66 | } 67 | }, 68 | "actions": { 69 | "list": "Aufführen", 70 | "create": "Erstellen", 71 | "edit": "Bearbeiten", 72 | "show": "Zeigen" 73 | }, 74 | "buttons": { 75 | "create": "Erstellen", 76 | "save": "Speichern", 77 | "logout": "Abmelden", 78 | "delete": "Löschen", 79 | "edit": "Bearbeiten", 80 | "cancel": "Abbrechen", 81 | "confirm": "Sicher?", 82 | "filter": "Filter", 83 | "clear": "Löschen", 84 | "refresh": "Erneuern", 85 | "show": "Zeigen", 86 | "undo": "Undo", 87 | "import": "Importieren", 88 | "clone": "Klon", 89 | "notAccessTitle": "Sie haben keine zugriffsberechtigung" 90 | }, 91 | "warnWhenUnsavedChanges": "Nicht gespeicherte Änderungen werden nicht übernommen.", 92 | "notifications": { 93 | "success": "Erfolg", 94 | "error": "Fehler (status code: {{statusCode}})", 95 | "undoable": "Sie haben {{seconds}} Sekunden Zeit für Undo.", 96 | "createSuccess": "{{resource}} erfolgreich erstellt.", 97 | "createError": "Fehler beim Erstellen {{resource}} (status code: {{statusCode}})", 98 | "deleteSuccess": "{{resource}} erfolgreich gelöscht.", 99 | "deleteError": "Fehler beim Löschen {{resource}} (status code: {{statusCode}})", 100 | "editSuccess": "{{resource}} erfolgreich bearbeitet.", 101 | "editError": "Fehler beim Bearbeiten {{resource}} (status code: {{statusCode}})", 102 | "importProgress": "{{processed}}/{{total}} importiert" 103 | }, 104 | "loading": "Wird geladen", 105 | "tags": { 106 | "clone": "Klon" 107 | }, 108 | "dashboard": { 109 | "title": "Dashboard" 110 | }, 111 | "products": { 112 | "products": "Produkte", 113 | "fields": { 114 | "id": "Id", 115 | "title": "Titel", 116 | "createdAt": "Erstellt am" 117 | }, 118 | "titles": { 119 | "create": "Erstellen", 120 | "edit": "Bearbeiten", 121 | "list": "Einträge", 122 | "show": "Eintrag zeigen" 123 | } 124 | }, 125 | "categories": { 126 | "categories": "Kategorien", 127 | "fields": { 128 | "id": "Id", 129 | "title": "Titel", 130 | "createdAt": "Erstellt am" 131 | }, 132 | "titles": { 133 | "create": "Erstellen", 134 | "edit": "Bearbeiten", 135 | "list": "Einträge", 136 | "show": "Eintrag zeigen" 137 | } 138 | }, 139 | "table": { 140 | "actions": "Aktionen" 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /public/locales/en/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": { 3 | "login": { 4 | "title": "Sign in to your account", 5 | "signin": "Sign in", 6 | "signup": "Sign up", 7 | "divider": "or", 8 | "fields": { 9 | "email": "Email", 10 | "password": "Password" 11 | }, 12 | "errors": { 13 | "validEmail": "Invalid email address" 14 | }, 15 | "buttons": { 16 | "submit": "Login", 17 | "forgotPassword": "Forgot password?", 18 | "noAccount": "Don’t have an account?", 19 | "rememberMe": "Remember me" 20 | } 21 | }, 22 | "forgotPassword": { 23 | "title": "Forgot your password?", 24 | "fields": { 25 | "email": "Email" 26 | }, 27 | "errors": { 28 | "validEmail": "Invalid email address" 29 | }, 30 | "buttons": { 31 | "submit": "Send reset instructions" 32 | } 33 | }, 34 | "register": { 35 | "title": "Sign up for your account", 36 | "fields": { 37 | "email": "Email", 38 | "password": "Password" 39 | }, 40 | "errors": { 41 | "validEmail": "Invalid email address" 42 | }, 43 | "buttons": { 44 | "submit": "Register", 45 | "haveAccount": "Have an account?" 46 | } 47 | }, 48 | "updatePassword": { 49 | "title": "Update password", 50 | "fields": { 51 | "password": "New Password", 52 | "confirmPassword": "Confirm new password" 53 | }, 54 | "errors": { 55 | "confirmPasswordNotMatch": "Passwords do not match" 56 | }, 57 | "buttons": { 58 | "submit": "Update" 59 | } 60 | }, 61 | "error": { 62 | "info": "You may have forgotten to add the {{action}} component to {{resource}} resource.", 63 | "404": "Sorry, the page you visited does not exist.", 64 | "resource404": "Are you sure you have created the {{resource}} resource.", 65 | "backHome": "Back Home" 66 | } 67 | }, 68 | "actions": { 69 | "list": "List", 70 | "create": "Create", 71 | "edit": "Edit", 72 | "show": "Show" 73 | }, 74 | "buttons": { 75 | "create": "Create", 76 | "save": "Save", 77 | "logout": "Logout", 78 | "delete": "Delete", 79 | "edit": "Edit", 80 | "cancel": "Cancel", 81 | "confirm": "Are you sure?", 82 | "filter": "Filter", 83 | "clear": "Clear", 84 | "refresh": "Refresh", 85 | "show": "Show", 86 | "undo": "Undo", 87 | "import": "Import", 88 | "clone": "Clone", 89 | "notAccessTitle": "You don't have permission to access" 90 | }, 91 | "warnWhenUnsavedChanges": "Are you sure you want to leave? You have unsaved changes.", 92 | "notifications": { 93 | "success": "Successful", 94 | "error": "Error (status code: {{statusCode}})", 95 | "undoable": "You have {{seconds}} seconds to undo", 96 | "createSuccess": "Successfully created {{resource}}", 97 | "createError": "There was an error creating {{resource}} (status code: {{statusCode}})", 98 | "deleteSuccess": "Successfully deleted {{resource}}", 99 | "deleteError": "Error when deleting {{resource}} (status code: {{statusCode}})", 100 | "editSuccess": "Successfully edited {{resource}}", 101 | "editError": "Error when editing {{resource}} (status code: {{statusCode}})", 102 | "importProgress": "Importing: {{processed}}/{{total}}" 103 | }, 104 | "loading": "Loading", 105 | "tags": { 106 | "clone": "Clone" 107 | }, 108 | "dashboard": { 109 | "title": "Dashboard" 110 | }, 111 | "products": { 112 | "products": "Products", 113 | "fields": { 114 | "id": "Id", 115 | "title": "Title", 116 | "createdAt": "Created At" 117 | }, 118 | "titles": { 119 | "create": "Create Product", 120 | "edit": "Edit Product", 121 | "list": "Products", 122 | "show": "Show Product" 123 | } 124 | }, 125 | "categories": { 126 | "categories": "Categories", 127 | "fields": { 128 | "id": "Id", 129 | "title": "Title", 130 | "createdAt": "Created At" 131 | }, 132 | "titles": { 133 | "create": "Create Category", 134 | "edit": "Edit Category", 135 | "list": "Categories", 136 | "show": "Show Category" 137 | } 138 | }, 139 | "table": { 140 | "actions": "Actions" 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Authenticated, Refine } from "@refinedev/core"; 2 | import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar"; 3 | 4 | import { 5 | AuthPage, 6 | ErrorComponent, 7 | Layout, 8 | notificationProvider, 9 | } from "@refinedev/chakra-ui"; 10 | 11 | import { 12 | ChakraProvider, 13 | extendTheme, 14 | withDefaultColorScheme, 15 | } from "@chakra-ui/react"; 16 | import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom"; 17 | import routerBindings, { 18 | CatchAllNavigate, 19 | NavigateToResource, 20 | UnsavedChangesNotifier, 21 | } from "@refinedev/react-router-v6"; 22 | import { useTranslation } from "react-i18next"; 23 | import { IconHeart, IconHome, IconNews, IconRss } from "@tabler/icons"; 24 | import { ChakraUIInferencer } from "@refinedev/inferencer/chakra-ui"; 25 | 26 | import { Header } from "./components/header"; 27 | import { authProvider } from "./authProvider"; 28 | import { dataProvider } from "./rest-data-provider"; 29 | import DashboardPage from "pages/dashboard"; 30 | 31 | function App() { 32 | const { t, i18n } = useTranslation(); 33 | 34 | const i18nProvider = { 35 | translate: (key: string, params: object) => t(key, params), 36 | changeLocale: (lang: string) => i18n.changeLanguage(lang), 37 | getLocale: () => i18n.language, 38 | }; 39 | 40 | const baseTheme = extendTheme( 41 | withDefaultColorScheme({ colorScheme: "telegram" }), 42 | ); 43 | const theme = extendTheme({ 44 | ...baseTheme, 45 | colors: { 46 | sider: { 47 | background: "#2A4365", 48 | }, 49 | }, 50 | }); 51 | 52 | return ( 53 | 54 | 55 | 56 | , 63 | list: "/", 64 | meta: { label: "Dashboard" }, 65 | }, 66 | { 67 | name: "feeds", 68 | icon: , 69 | list: "/feeds", 70 | create: "/feeds/create", 71 | edit: "/feeds/edit/:id", 72 | show: "/feeds/show/:id", 73 | canDelete: true, 74 | }, 75 | { 76 | name: "entries", 77 | icon: , 78 | list: "/entries", 79 | create: "/entries/create", 80 | edit: "/entries/edit/:id", 81 | show: "/entries/show/:id", 82 | canDelete: true, 83 | }, 84 | { 85 | name: "favorites", 86 | icon: , 87 | list: "/favorites", 88 | // create: "//create", 89 | // edit: "//edit/:id", 90 | show: "/favorites/show/:id", 91 | canDelete: true, 92 | }, 93 | ]} 94 | routerProvider={routerBindings} 95 | // authProvider={authProvider} 96 | i18nProvider={i18nProvider} 97 | options={{ 98 | syncWithLocation: true, 99 | warnWhenUnsavedChanges: true, 100 | }} 101 | > 102 | 103 | }> 106 | 107 | 108 | 109 | 110 | } 111 | > 112 | } 116 | /> 117 | 118 | } /> 119 | } /> 120 | } /> 121 | } /> 122 | 123 | 124 | } /> 125 | } /> 126 | } /> 127 | } /> 128 | 129 | 130 | } /> 131 | } /> 132 | 133 | 134 | }> 137 | 138 | 139 | } 140 | > 141 | 153 | } 154 | /> 155 | 156 | 159 | 160 | 161 | 162 | 163 | } 164 | > 165 | } /> 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | ); 176 | } 177 | 178 | export default App; 179 | -------------------------------------------------------------------------------- /src/authProvider.ts: -------------------------------------------------------------------------------- 1 | import { AuthBindings } from "@refinedev/core"; 2 | 3 | export const TOKEN_KEY = "refine-auth"; 4 | 5 | export const authProvider: AuthBindings = { 6 | login: async ({ username, email, password }) => { 7 | if ((username || email) && password) { 8 | localStorage.setItem(TOKEN_KEY, username); 9 | return { 10 | success: true, 11 | redirectTo: "/", 12 | }; 13 | } 14 | 15 | return { 16 | success: false, 17 | error: { 18 | name: "LoginError", 19 | message: "Invalid username or password", 20 | }, 21 | }; 22 | }, 23 | logout: async () => { 24 | localStorage.removeItem(TOKEN_KEY); 25 | return { 26 | success: true, 27 | redirectTo: "/login", 28 | }; 29 | }, 30 | check: async () => { 31 | const token = localStorage.getItem(TOKEN_KEY); 32 | if (token) { 33 | return { 34 | authenticated: true, 35 | }; 36 | } 37 | 38 | return { 39 | authenticated: false, 40 | redirectTo: "/login", 41 | }; 42 | }, 43 | getPermissions: async () => null, 44 | getIdentity: async () => { 45 | const token = localStorage.getItem(TOKEN_KEY); 46 | if (token) { 47 | return { 48 | id: 1, 49 | name: "John Doe", 50 | avatar: "https://i.pravatar.cc/300", 51 | }; 52 | } 53 | return null; 54 | }, 55 | onError: async (error) => { 56 | console.error(error); 57 | return { error }; 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/components/atoms/index.tsx: -------------------------------------------------------------------------------- 1 | import HTMLRenderer from "react-html-renderer"; 2 | import { 3 | Box, 4 | Button, 5 | Code, 6 | Collapse, 7 | IconButton, 8 | Image, 9 | Link, 10 | ListItem, 11 | OrderedList, 12 | Spinner, 13 | UnorderedList, 14 | useBoolean, 15 | } from "@chakra-ui/react"; 16 | import { 17 | ChevronDownIcon, 18 | ChevronsUpDownIcon, 19 | ExternalLinkIcon, 20 | } from "lucide-react"; 21 | import { useEffect, useRef, useState } from "react"; 22 | import consola from "consola"; 23 | 24 | import { ColumnButtonProps } from "types"; 25 | 26 | export const ContentRender = ({ html }: { html: string }) => { 27 | const ref = useRef(null); 28 | const [isShowMore, toggleIsShowMore] = useBoolean(false); 29 | const [isCollapsed, toggleIsCollapsed] = useBoolean(false); 30 | const [startingHeight, setStartingHeight] = useState(34); 31 | const hasMedia = html.indexOf(" 0 || html.indexOf(" 0; 32 | useEffect(() => { 33 | const { clientHeight, scrollHeight } = ref?.current || 34 | { clientHeight: 0, scrollHeight: 0 }; 35 | consola.info( 36 | "scroll height: ", 37 | scrollHeight, 38 | " client height: ", 39 | clientHeight, 40 | ); 41 | let height = 300; 42 | /*if (scrollHeight > clientHeight) { 43 | setStartingHeight(scrollHeight); 44 | toggleIsShowMore.on(); 45 | } 46 | if (hasMedia) { 47 | let height = 300; 48 | if (scrollHeight > 300) { 49 | height = scrollHeight > 800 ? 800 : scrollHeight; 50 | toggleIsShowMore.off(); 51 | } 52 | setStartingHeight(height); 53 | toggleIsShowMore.on(); 54 | }*/ 55 | if (scrollHeight > clientHeight) { 56 | if (scrollHeight <= height) { 57 | height = scrollHeight; 58 | toggleIsShowMore.off(); 59 | } else { 60 | toggleIsShowMore.on(); 61 | } 62 | setStartingHeight(height); 63 | } else { 64 | height = clientHeight; 65 | } 66 | }, [ref?.current]); 67 | 68 | return ( 69 | 70 |
{isShowMore}
71 | 76 | ( 80 | 81 | {props.children} 82 | 83 | ), 84 | code: (props: any) => ( 85 | 86 | ), 87 | // img: (props: any) => , 88 | li: ListItem, 89 | ol: OrderedList, 90 | ul: UnorderedList, 91 | }} 92 | /> 93 | 94 |
{isShowMore}
95 | 104 |
105 | ); 106 | }; 107 | 108 | export const LoadingComponent = () => { 109 | return ( 110 | 111 | 117 | 118 | ); 119 | }; 120 | 121 | export const ColumnSorter: React.FC = ({ column }) => { 122 | if (!column.getCanSort()) { 123 | return null; 124 | } 125 | const sorted = column.getIsSorted(); 126 | return ( 127 | 138 | {sorted 139 | ? 140 | : } 141 | 142 | ); 143 | }; 144 | -------------------------------------------------------------------------------- /src/components/header/index.tsx: -------------------------------------------------------------------------------- 1 | import { useGetIdentity, useGetLocale, useSetLocale } from "@refinedev/core"; 2 | import { 3 | Box, 4 | IconButton, 5 | HStack, 6 | Text, 7 | Avatar, 8 | Icon, 9 | Menu, 10 | MenuButton, 11 | MenuItem, 12 | MenuList, 13 | useColorMode, 14 | } from "@chakra-ui/react"; 15 | import { IconMoon, IconSun, IconLanguage } from "@tabler/icons"; 16 | 17 | import i18n from "i18n"; 18 | 19 | type IUser = { 20 | id: number; 21 | name: string; 22 | avatar: string; 23 | }; 24 | 25 | export const Header: React.FC = () => { 26 | const { data: user } = useGetIdentity(); 27 | const showUserInfo = user && (user.name || user.avatar); 28 | 29 | const { colorMode, toggleColorMode } = useColorMode(); 30 | 31 | const changeLanguage = useSetLocale(); 32 | const locale = useGetLocale(); 33 | const currentLocale = locale(); 34 | 35 | return ( 36 | 45 | {showUserInfo && ( 46 | 47 | 48 | {user?.name} 49 | 50 | 51 | 52 | )} 53 | 54 | } 58 | variant="ghost" 59 | /> 60 | 61 | {[...(i18n.languages ?? [])].sort().map((lang: string) => ( 62 | { 65 | changeLanguage(lang); 66 | }} 67 | value={lang} 68 | color={lang === currentLocale ? "green" : undefined} 69 | icon={} 70 | > 71 | {lang === "en" ? "English" : "German"} 72 | 73 | ))} 74 | 75 | 76 | 81 | 86 | 87 | 88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { Header } from "./header"; 2 | -------------------------------------------------------------------------------- /src/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from "i18next"; 2 | import { initReactI18next } from "react-i18next"; 3 | import Backend from "i18next-xhr-backend"; 4 | 5 | i18n 6 | .use(Backend) 7 | .use(initReactI18next) 8 | .init({ 9 | supportedLngs: ["en", "de"], 10 | backend: { 11 | loadPath: "/locales/{{lng}}/{{ns}}.json", 12 | }, 13 | defaultNS: "common", 14 | fallbackLng: ["en", "de"], 15 | }); 16 | 17 | export default i18n; 18 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | 4 | import reportWebVitals from "./reportWebVitals"; 5 | import App from "./App"; 6 | import "./i18n"; 7 | 8 | const container = document.getElementById("root") as HTMLElement; 9 | const root = createRoot(container); 10 | 11 | root.render( 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | 19 | // If you want to start measuring performance in your app, pass a function 20 | // to log results (for example: reportWebVitals(console.log)) 21 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 22 | reportWebVitals(); 23 | -------------------------------------------------------------------------------- /src/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pocketrss-dashboard", 3 | "plugins": [] 4 | } 5 | -------------------------------------------------------------------------------- /src/pages/categories/create.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUICreateInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const CategoryCreate: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/categories/edit.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUIEditInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const CategoryEdit: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/categories/index.ts: -------------------------------------------------------------------------------- 1 | export { CategoryList } from "./list"; 2 | export { CategoryCreate } from "./create"; 3 | export { CategoryEdit } from "./edit"; 4 | export { CategoryShow } from "./show"; 5 | -------------------------------------------------------------------------------- /src/pages/categories/list.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUIListInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const CategoryList: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/categories/show.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUIShowInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const CategoryShow: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/dashboard.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Avatar, 3 | Box, 4 | Button, 5 | Center, 6 | Heading, 7 | HStack, 8 | Icon, 9 | Link, 10 | Text, 11 | useColorModeValue, 12 | VStack, 13 | } from "@chakra-ui/react"; 14 | import { useInfiniteList } from "@refinedev/core"; 15 | import { ExternalLinkIcon } from "lucide-react"; 16 | import { DateTime } from "luxon"; 17 | import { length } from "rambdax"; 18 | 19 | import { ContentRender, LoadingComponent } from "components/atoms"; 20 | 21 | function DashboardPage() { 22 | const cardColor = useColorModeValue("whiteAlpha.800", "blue.800"); 23 | const contentBgColor = useColorModeValue("gray.100", "chakra-body-bg"); 24 | const contentColor = useColorModeValue("black", "#8ca8be"); 25 | // const titleColor = useColorModeValue("black", "#8ca8be"); 26 | const { data, isLoading, hasNextPage, fetchNextPage, isFetchingNextPage } = 27 | useInfiniteList({ 28 | resource: "entries", 29 | config: { 30 | pagination: { pageSize: 10 }, 31 | }, 32 | }); 33 | console.log(data); 34 | 35 | // @ts-ignore:next-line 36 | const entries = [].concat(...(data?.pages ?? []).map((page) => page.data)); 37 | 38 | if (isLoading) { 39 | return ; 40 | } 41 | 42 | return ( 43 | 44 | {entries && 45 | entries.map((entry: any) => ( 46 | 54 | 55 | 56 | 57 | 58 | {entry.edges.feed.title} 59 | 60 | {DateTime.fromISO(entry.published_at).toRelative()} 61 | 62 | 63 | 64 | 65 | {entry.title} 66 | 67 | 68 | 76 | {(length(entry.content) > 0 || length(entry.description) > 0) && ( 77 | 78 | )} 79 | 80 | 81 | ))} 82 | {hasNextPage && ( 83 |
84 | 87 |
88 | )} 89 |
90 | ); 91 | } 92 | 93 | export default DashboardPage; 94 | -------------------------------------------------------------------------------- /src/pages/feeds/create.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUICreateInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const ProductCreate: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/feeds/edit.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUIEditInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const ProductEdit: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/feeds/index.ts: -------------------------------------------------------------------------------- 1 | export { FeedList } from "./list"; 2 | export { ProductCreate } from "./create"; 3 | export { ProductEdit } from "./edit"; 4 | export { ProductShow } from "./show"; 5 | -------------------------------------------------------------------------------- /src/pages/feeds/list.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUIListInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const FeedList: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/feeds/show.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUIShowInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const ProductShow: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/products/create.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUICreateInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const ProductCreate: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/products/edit.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUIEditInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const ProductEdit: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/products/index.ts: -------------------------------------------------------------------------------- 1 | export { ProductList } from "./list"; 2 | export { ProductCreate } from "./create"; 3 | export { ProductEdit } from "./edit"; 4 | export { ProductShow } from "./show"; 5 | -------------------------------------------------------------------------------- /src/pages/products/list.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUIListInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const ProductList: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/products/show.tsx: -------------------------------------------------------------------------------- 1 | import { IResourceComponentsProps } from "@refinedev/core"; 2 | import { ChakraUIShowInferencer } from "@refinedev/inferencer/chakra-ui"; 3 | 4 | export const ProductShow: React.FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from "web-vitals"; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/rest-data-provider/index.ts: -------------------------------------------------------------------------------- 1 | // "axios" package needs to be installed 2 | import { AxiosInstance } from "axios"; 3 | import { stringify } from "@refinedev/simple-rest"; 4 | import { DataProvider } from "@refinedev/core"; 5 | import { axiosInstance, generateFilter, generateSort } from "./utils"; 6 | 7 | export const dataProvider = ( 8 | apiUrl: string, 9 | httpClient: AxiosInstance = axiosInstance, 10 | ): Omit< 11 | Required, 12 | "createMany" | "updateMany" | "deleteMany" 13 | > => ({ 14 | getList: async ({ 15 | resource, 16 | hasPagination = true, 17 | pagination = { current: 1, pageSize: 10 }, 18 | filters, 19 | sort, 20 | }) => { 21 | const url = `${apiUrl}/${resource}`; 22 | 23 | const { current = 1, pageSize = 10 } = pagination ?? {}; 24 | 25 | const queryFilters = generateFilter(filters); 26 | 27 | const query: { 28 | _start?: number; 29 | _end?: number; 30 | _sort?: string; 31 | _order?: string; 32 | } = hasPagination 33 | ? { 34 | _start: (current - 1) * pageSize, 35 | _end: current * pageSize, 36 | } 37 | : {}; 38 | 39 | const generatedSort = generateSort(sort); 40 | if (generatedSort) { 41 | const { _sort, _order } = generatedSort; 42 | query._sort = _sort.join(","); 43 | query._order = _order.join(","); 44 | } 45 | 46 | const { data } = await httpClient.get( 47 | `${url}?${stringify(query)}&${stringify(queryFilters)}`, 48 | ); 49 | 50 | // const total = +headers["x-total-count"]; 51 | const { resources } = data?.data || []; 52 | const { total } = data?.pagination || 0; 53 | 54 | return { 55 | data: resources, 56 | total, 57 | }; 58 | }, 59 | 60 | getMany: async ({ resource, ids }) => { 61 | const { data } = await httpClient.get( 62 | `${apiUrl}/${resource}?${stringify({ id: ids })}`, 63 | ); 64 | 65 | const resources = data?.data?.resources || []; 66 | return { 67 | data: resources, 68 | }; 69 | }, 70 | 71 | create: async ({ resource, variables }) => { 72 | const url = `${apiUrl}/${resource}`; 73 | 74 | const { data } = await httpClient.post(url, variables); 75 | 76 | return { 77 | data, 78 | }; 79 | }, 80 | 81 | update: async ({ resource, id, variables }) => { 82 | const url = `${apiUrl}/${resource}/${id}`; 83 | 84 | const { data } = await httpClient.patch(url, variables); 85 | 86 | return { 87 | data, 88 | }; 89 | }, 90 | 91 | getOne: async ({ resource, id }) => { 92 | const url = `${apiUrl}/${resource}/${id}`; 93 | 94 | const { data } = await httpClient.get(url); 95 | 96 | return { 97 | data, 98 | }; 99 | }, 100 | 101 | deleteOne: async ({ resource, id, variables }) => { 102 | const url = `${apiUrl}/${resource}/${id}`; 103 | 104 | const { data } = await httpClient.delete(url, { 105 | data: variables, 106 | }); 107 | 108 | return { 109 | data, 110 | }; 111 | }, 112 | 113 | getApiUrl: () => { 114 | return apiUrl; 115 | }, 116 | 117 | custom: async ({ url, method, filters, sort, payload, query, headers }) => { 118 | let requestUrl = `${url}?`; 119 | 120 | if (sort) { 121 | const generatedSort = generateSort(sort); 122 | if (generatedSort) { 123 | const { _sort, _order } = generatedSort; 124 | const sortQuery = { 125 | _sort: _sort.join(","), 126 | _order: _order.join(","), 127 | }; 128 | requestUrl = `${requestUrl}&${stringify(sortQuery)}`; 129 | } 130 | } 131 | 132 | if (filters) { 133 | const filterQuery = generateFilter(filters); 134 | requestUrl = `${requestUrl}&${stringify(filterQuery)}`; 135 | } 136 | 137 | if (query) { 138 | requestUrl = `${requestUrl}&${stringify(query)}`; 139 | } 140 | 141 | if (headers) { 142 | // @ts-ignore:next-line 143 | httpClient.defaults.headers = { 144 | ...httpClient.defaults.headers, 145 | ...headers, 146 | }; 147 | } 148 | 149 | let axiosResponse; 150 | switch (method) { 151 | case "put": 152 | case "post": 153 | case "patch": 154 | axiosResponse = await httpClient[method](url, payload); 155 | break; 156 | case "delete": 157 | axiosResponse = await httpClient.delete(url, { 158 | data: payload, 159 | }); 160 | break; 161 | default: 162 | axiosResponse = await httpClient.get(requestUrl); 163 | break; 164 | } 165 | 166 | const { data } = axiosResponse; 167 | 168 | return Promise.resolve({ data }); 169 | }, 170 | }); 171 | -------------------------------------------------------------------------------- /src/rest-data-provider/utils/axios.ts: -------------------------------------------------------------------------------- 1 | import { HttpError } from "@refinedev/core"; 2 | 3 | // "axios" package should be installed to customize the http client 4 | import axios from "axios"; 5 | 6 | const axiosInstance = axios.create(); 7 | 8 | axiosInstance.interceptors.response.use( 9 | (response) => { 10 | return response; 11 | }, 12 | (error) => { 13 | const customError: HttpError = { 14 | ...error, 15 | message: error.response?.data?.message, 16 | statusCode: error.response?.status, 17 | }; 18 | 19 | return Promise.reject(customError); 20 | } 21 | ); 22 | 23 | export { axiosInstance }; 24 | -------------------------------------------------------------------------------- /src/rest-data-provider/utils/generateFilter.ts: -------------------------------------------------------------------------------- 1 | import { CrudFilters } from "@refinedev/core"; 2 | import { mapOperator } from "./mapOperator"; 3 | 4 | export const generateFilter = (filters?: CrudFilters) => { 5 | const queryFilters: { [key: string]: string } = {}; 6 | 7 | if (filters) { 8 | filters.map((filter) => { 9 | if (filter.operator === "or" || filter.operator === "and") { 10 | throw new Error( 11 | `[@pankod/refine-simple-rest]: \`operator: ${filter.operator}\` is not supported. You can create custom data provider. https://refine.dev/docs/api-reference/core/providers/data-provider/#creating-a-data-provider` 12 | ); 13 | } 14 | 15 | if ("field" in filter) { 16 | const { field, operator, value } = filter; 17 | 18 | if (field === "q") { 19 | queryFilters[field] = value; 20 | return; 21 | } 22 | 23 | const mappedOperator = mapOperator(operator); 24 | queryFilters[`${field}${mappedOperator}`] = value; 25 | } 26 | }); 27 | } 28 | 29 | return queryFilters; 30 | }; 31 | -------------------------------------------------------------------------------- /src/rest-data-provider/utils/generateSort.ts: -------------------------------------------------------------------------------- 1 | import { CrudSorting } from "@refinedev/core"; 2 | 3 | export const generateSort = (sort?: CrudSorting) => { 4 | if (sort && sort.length > 0) { 5 | const _sort: string[] = []; 6 | const _order: string[] = []; 7 | 8 | sort.map((item) => { 9 | _sort.push(item.field); 10 | _order.push(item.order); 11 | }); 12 | 13 | return { 14 | _sort, 15 | _order, 16 | }; 17 | } 18 | 19 | return; 20 | }; 21 | -------------------------------------------------------------------------------- /src/rest-data-provider/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { mapOperator } from "./mapOperator"; 2 | export { generateSort } from "./generateSort"; 3 | export { generateFilter } from "./generateFilter"; 4 | export { axiosInstance } from "./axios"; 5 | -------------------------------------------------------------------------------- /src/rest-data-provider/utils/mapOperator.ts: -------------------------------------------------------------------------------- 1 | import { CrudOperators } from "@refinedev/core"; 2 | 3 | export const mapOperator = (operator: CrudOperators): string => { 4 | switch (operator) { 5 | case "ne": 6 | case "gte": 7 | case "lte": 8 | return `_${operator}`; 9 | case "contains": 10 | return "_like"; 11 | case "eq": 12 | default: 13 | return ""; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom"; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "baseUrl": "src", 19 | "paths": { 20 | "*": ["./*"] 21 | } 22 | }, 23 | "include": ["src"] 24 | } 25 | --------------------------------------------------------------------------------