├── .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 | 
84 |
85 | - Feeds
86 |
87 | 
88 |
89 | - Entries
90 |
91 | 
92 |
93 | - Favorites
94 |
95 | 
96 |
97 | ### Clients
98 |
99 | #### Android
100 |
101 | - Tusky
102 |
103 | 
104 |
105 | #### Desktop
106 |
107 | - Leaf
108 |
109 | 
110 |
111 | ### Web
112 | - [elk](https://github.com/elk-zone/elk) (**Recommend**)
113 |
114 | 
115 |
116 | - [Pinafore](https://pinafore.social)
117 |
118 | 
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("