├── .env.example ├── .envrc ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .tool-versions ├── 404.html ├── FRONTEND_INSTRUCTIONS.md ├── docs ├── Real-World-App.svg ├── app-machine.png ├── article-machine.png ├── auth-machine.png ├── editor-machine.png ├── feed-machine.png ├── profile-machine.png └── settings-machine.png ├── index.html ├── index.tsx ├── logo.png ├── package-lock.json ├── package.json ├── readme.md ├── src ├── App.tsx ├── components │ ├── AppLayout.tsx │ ├── Article.tsx │ ├── AuthorCard.tsx │ ├── Comment.tsx │ ├── ErrorListItem.tsx │ ├── Footer.tsx │ ├── Header.tsx │ ├── Pagination.tsx │ └── Tag.tsx ├── hooks │ └── is-authenticated.ts ├── machines │ ├── app.machine.ts │ ├── article.machine.ts │ ├── auth.machine.ts │ ├── editor.machine.ts │ ├── feed.machine.ts │ ├── profile.machine.ts │ ├── settings.machine.ts │ └── tags.machine.ts ├── pages │ ├── Article.tsx │ ├── Auth.tsx │ ├── Editor.tsx │ ├── Home.tsx │ ├── Profile.tsx │ └── Settings.tsx ├── types │ └── api.ts └── utils │ ├── api-client.ts │ ├── dates.ts │ ├── env.ts │ ├── errors.ts │ └── states.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | PUBLIC_URL="" 2 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use mise 2 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy App 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | build-and-deploy-docs: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 🛎️ 12 | uses: actions/checkout@v4 13 | with: 14 | persist-credentials: false 15 | 16 | - name: Install and Build 🔧 17 | env: 18 | PUBLIC_URL: ${{ secrets.PUBLIC_URL }} 19 | run: | 20 | npm ci 21 | npm run build 22 | 23 | - name: Deploy 🚀 24 | uses: JamesIves/github-pages-deploy-action@v4 25 | with: 26 | BRANCH: gh-pages # The branch the action should deploy to. 27 | FOLDER: _site # The folder the action should deploy. 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /bower_components 6 | 7 | # IDEs and editors 8 | /.idea 9 | .project 10 | .classpath 11 | *.launch 12 | .settings/ 13 | 14 | 15 | #System Files 16 | .DS_Store 17 | Thumbs.db 18 | dist/ 19 | .cache/ 20 | .env 21 | _site 22 | .parcel-cache/ 23 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 18.14.0 2 | -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Single Page Apps for GitHub Pages 6 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /FRONTEND_INSTRUCTIONS.md: -------------------------------------------------------------------------------- 1 | > *Note: Delete this file before publishing your app!* 2 | 3 | ### Using the hosted API 4 | 5 | Simply point your [API requests](https://github.com/gothinkster/realworld/tree/master/api) to `https://conduit.productionready.io/api` and you're good to go! 6 | 7 | ### Routing Guidelines 8 | 9 | - Home page (URL: /#/ ) 10 | - List of tags 11 | - List of articles pulled from either Feed, Global, or by Tag 12 | - Pagination for list of articles 13 | - Sign in/Sign up pages (URL: /#/login, /#/register ) 14 | - Uses JWT (store the token in localStorage) 15 | - Authentication can be easily switched to session/cookie based 16 | - Settings page (URL: /#/settings ) 17 | - Editor page to create/edit articles (URL: /#/editor, /#/editor/article-slug-here ) 18 | - Article page (URL: /#/article/article-slug-here ) 19 | - Delete article button (only shown to article's author) 20 | - Render markdown from server client side 21 | - Comments section at bottom of page 22 | - Delete comment button (only shown to comment's author) 23 | - Profile page (URL: /#/profile/:username, /#/profile/:username/favorites ) 24 | - Show basic user info 25 | - List of articles populated from author's created articles or author's favorited articles 26 | 27 | # Styles 28 | 29 | Instead of having the Bootstrap theme included locally, we recommend loading the precompiled theme from our CDN (our [header template](#header) does this by default): 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | Alternatively, if you want to make modifications to the theme, check out the [theme's repo](https://github.com/gothinkster/conduit-bootstrap-template). 36 | 37 | 38 | # Templates 39 | 40 | - [Layout](#layout) 41 | - [Header](#header) 42 | - [Footer](#footer) 43 | - [Pages](#pages) 44 | - [Home](#home) 45 | - [Login/Register](#loginregister) 46 | - [Profile](#profile) 47 | - [Settings](#settings) 48 | - [Create/Edit Article](#createedit-article) 49 | - [Article](#article) 50 | 51 | 52 | ## Layout 53 | 54 | 55 | ### Header 56 | 57 | ```html 58 | 59 | 60 | 61 | 62 | Conduit 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 95 | 96 | 97 | ``` 98 | 99 | ### Footer 100 | ```html 101 | 109 | 110 | 111 | 112 | ``` 113 | 114 | ## Pages 115 | 116 | ### Home 117 | ```html 118 |
119 | 120 | 126 | 127 |
128 |
129 | 130 |
131 |
132 | 140 |
141 | 142 |
143 | 153 | 154 |

How to build webapps that scale

155 |

This is the description for the post.

156 | Read more... 157 |
158 |
159 | 160 |
161 | 171 | 172 |

The song you won't ever stop singing. No matter how hard you try.

173 |

This is the description for the post.

174 | Read more... 175 |
176 |
177 | 178 |
179 | 180 |
181 | 195 |
196 | 197 |
198 |
199 | 200 |
201 | ``` 202 | 203 | ### Login/Register 204 | 205 | ```html 206 |
207 |
208 |
209 | 210 |
211 |

Sign up

212 |

213 | Have an account? 214 |

215 | 216 |
    217 |
  • That email is already taken
  • 218 |
219 | 220 |
221 |
222 | 223 |
224 |
225 | 226 |
227 |
228 | 229 |
230 | 233 |
234 |
235 | 236 |
237 |
238 |
239 | ``` 240 | 241 | ### Profile 242 | 243 | ```html 244 |
245 | 246 |
247 |
248 |
249 | 250 |
251 | 252 |

Eric Simons

253 |

254 | Cofounder @GoThinkster, lived in Aol's HQ for a few months, kinda looks like Peeta from the Hunger Games 255 |

256 | 261 |
262 | 263 |
264 |
265 |
266 | 267 |
268 |
269 | 270 |
271 |
272 | 280 |
281 | 282 |
283 | 293 | 294 |

How to build webapps that scale

295 |

This is the description for the post.

296 | Read more... 297 |
298 |
299 | 300 |
301 | 311 | 312 |

The song you won't ever stop singing. No matter how hard you try.

313 |

This is the description for the post.

314 | Read more... 315 |
    316 |
  • Music
  • 317 |
  • Song
  • 318 |
319 |
320 |
321 | 322 | 323 |
324 | 325 |
326 |
327 | 328 |
329 | ``` 330 | 331 | ### Settings 332 | 333 | ```html 334 |
335 |
336 |
337 | 338 |
339 |

Your Settings

340 | 341 |
342 |
343 |
344 | 345 |
346 |
347 | 348 |
349 |
350 | 351 |
352 |
353 | 354 |
355 |
356 | 357 |
358 | 361 |
362 |
363 |
364 | 365 |
366 |
367 |
368 | ``` 369 | 370 | ### Create/Edit Article 371 | 372 | ```html 373 |
374 |
375 |
376 | 377 |
378 |
379 |
380 |
381 | 382 |
383 |
384 | 385 |
386 |
387 | 388 |
389 |
390 |
391 |
392 | 395 |
396 |
397 |
398 | 399 |
400 |
401 |
402 | 403 | 404 | ``` 405 | 406 | ### Article 407 | 408 | ```html 409 |
410 | 411 | 437 | 438 |
439 | 440 |
441 |
442 |

443 | Web development technologies have evolved at an incredible clip over the past few years. 444 |

445 |

Introducing RealWorld.

446 |

It's a great solution for learning how other frameworks work.

447 |
448 |
449 | 450 |
451 | 452 |
453 | 472 |
473 | 474 |
475 | 476 |
477 | 478 |
479 |
480 | 481 |
482 | 488 |
489 | 490 |
491 |
492 |

With supporting text below as a natural lead-in to additional content.

493 |
494 | 502 |
503 | 504 |
505 |
506 |

With supporting text below as a natural lead-in to additional content.

507 |
508 | 520 |
521 | 522 |
523 | 524 |
525 | 526 |
527 | 528 |
529 | ``` 530 | -------------------------------------------------------------------------------- /docs/Real-World-App.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | Authidle1submittingchoosingsignuploginSUBMITsignupRequestloginRequestfailedauthenticatedDONEERRORAppUserUserunauthenticated -------------------------------------------------------------------------------- /docs/app-machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HipsterBrown/real-world-app-xstate/db6ce7ba90d1db60fdc488e4e30f061f149184d0/docs/app-machine.png -------------------------------------------------------------------------------- /docs/article-machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HipsterBrown/real-world-app-xstate/db6ce7ba90d1db60fdc488e4e30f061f149184d0/docs/article-machine.png -------------------------------------------------------------------------------- /docs/auth-machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HipsterBrown/real-world-app-xstate/db6ce7ba90d1db60fdc488e4e30f061f149184d0/docs/auth-machine.png -------------------------------------------------------------------------------- /docs/editor-machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HipsterBrown/real-world-app-xstate/db6ce7ba90d1db60fdc488e4e30f061f149184d0/docs/editor-machine.png -------------------------------------------------------------------------------- /docs/feed-machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HipsterBrown/real-world-app-xstate/db6ce7ba90d1db60fdc488e4e30f061f149184d0/docs/feed-machine.png -------------------------------------------------------------------------------- /docs/profile-machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HipsterBrown/real-world-app-xstate/db6ce7ba90d1db60fdc488e4e30f061f149184d0/docs/profile-machine.png -------------------------------------------------------------------------------- /docs/settings-machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HipsterBrown/real-world-app-xstate/db6ce7ba90d1db60fdc488e4e30f061f149184d0/docs/settings-machine.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Conduit 7 | 8 | 11 | 12 | 13 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { App } from "./src/App"; 4 | 5 | createRoot(document.getElementById("app")!).render(); 6 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HipsterBrown/real-world-app-xstate/db6ce7ba90d1db60fdc488e4e30f061f149184d0/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "realworld-xstate-react", 3 | "version": "1.0.0", 4 | "description": "An implementation of the Real World app with XState & React", 5 | "source": "index.html", 6 | "browserslist": "> 0.5%, last 2 versions, not dead", 7 | "scripts": { 8 | "build": "NODE_ENV=production parcel build *.html --dist-dir _site --public-url ./", 9 | "start": "parcel index.html" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/HipsterBrown/realworld-starter-kit.git" 14 | }, 15 | "keywords": [ 16 | "xstate", 17 | "react" 18 | ], 19 | "author": "HipsterBrown", 20 | "license": "Apache-2.0", 21 | "bugs": { 22 | "url": "https://github.com/HipsterBrown/realworld-starter-kit/issues" 23 | }, 24 | "homepage": "https://github.com/HipsterBrown/realworld-starter-kit#readme", 25 | "dependencies": { 26 | "@xstate/react": "^4.1.1", 27 | "clsx": "^2.1.1", 28 | "dompurify": "^3.1.3", 29 | "formik": "^2.4.6", 30 | "marked": "^12.0.2", 31 | "react": "^18.3.1", 32 | "react-dom": "^18.3.1", 33 | "react-router": "^6.23.1", 34 | "react-router-dom": "^6.23.1", 35 | "xstate": "^5.13.0", 36 | "yup": "^1.4.0" 37 | }, 38 | "devDependencies": { 39 | "@statelyai/inspect": "^0.3.1", 40 | "@types/dompurify": "^3.0.5", 41 | "@types/marked": "^6.0.0", 42 | "@types/node": "^18.19.33", 43 | "@types/react": "^18.3.2", 44 | "@types/react-dom": "^18.3.0", 45 | "@types/yup": "^0.32.0", 46 | "parcel": "^2.12.0", 47 | "process": "^0.11.10", 48 | "tslib": "^2.6.2", 49 | "typescript": "^5.4.5" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ![RealWorld XState & React App](logo.png) 2 | 3 | > ### XState + React codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API. 4 | 5 | 6 | ### [Demo](https://github.com/gothinkster/realworld)    [RealWorld](https://github.com/gothinkster/realworld) 7 | 8 | 9 | This codebase was created to demonstrate a fully fledged fullstack application built with **XState & React** including CRUD operations, authentication, routing, pagination, and more. 10 | 11 | We've gone to great lengths to adhere to the **XState & React** community styleguides & best practices. 12 | 13 | For more information on how to this works with other frontends/backends, head over to the [RealWorld](https://github.com/gothinkster/realworld) repo. 14 | 15 | TODO: 16 | 17 | - [ ] tests (most likely using `@xstate/test`) 18 | - [ ] better docs about architecture 19 | 20 | # How it works 21 | 22 | > Describe the general architecture of your app here 23 | 24 | App machine: 25 | ![app state machine](./docs/app-machine.png) 26 | 27 | Auth machine: 28 | ![auth state machine](./docs/auth-machine.png) 29 | 30 | Article machine: 31 | ![article state machine](./docs/article-machine.png) 32 | 33 | Editor machine: 34 | ![editor state machine](./docs/editor-machine.png) 35 | 36 | Feed machine: 37 | ![feed state machine](./docs/feed-machine.png) 38 | 39 | Profile machine: 40 | ![profile state machine](./docs/profile-machine.png) 41 | 42 | Settings machine: 43 | ![settings state machine](./docs/settings-machine.png) 44 | 45 | 46 | # Getting started 47 | 48 | 1. `npm install` 49 | 2. `npm start` 50 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Route, Navigate, createBrowserRouter, RouterProvider, createRoutesFromElements } from "react-router-dom"; 3 | import { createActorContext } from "@xstate/react"; 4 | import { createBrowserInspector } from "@statelyai/inspect"; 5 | import { isProd } from "./utils/env"; 6 | import { appMachine } from "./machines/app.machine"; 7 | import { Home } from "./pages/Home"; 8 | import { Auth } from "./pages/Auth"; 9 | import { Editor } from "./pages/Editor"; 10 | import { Settings } from "./pages/Settings"; 11 | import { Profile } from "./pages/Profile"; 12 | import { Article } from "./pages/Article"; 13 | import { AppLayout } from "./components/AppLayout"; 14 | import { useIsAuthenticated } from "./hooks/is-authenticated"; 15 | 16 | export const { inspect } = createBrowserInspector(); 17 | 18 | export const AppMachineContext = createActorContext(appMachine, { inspect: isProd() ? inspect : undefined }); 19 | const basename = isProd() ? String(process.env.PUBLIC_URL) : '/'; 20 | 21 | const AuthenticatedRoute: React.FC< 22 | { element: React.ReactElement } 23 | > = ({ element }) => { 24 | const isAuthenticated = useIsAuthenticated(); 25 | 26 | if (isAuthenticated) { 27 | return element; 28 | } 29 | return ; 30 | }; 31 | 32 | export const appRouter = createBrowserRouter(createRoutesFromElements( 33 | } > 34 | } /> 35 | } /> 36 | } /> 37 | } 42 | /> 43 | } 44 | /> 45 | } 49 | /> 50 | } 51 | /> 52 | } 56 | /> 57 | } 58 | /> 59 | } /> 60 | } /> 61 | } /> 62 | 63 | ), { basename }) 64 | 65 | 66 | export const App: React.FC = () => { 67 | return ( 68 | 69 | 70 | 71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /src/components/AppLayout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Outlet } from 'react-router-dom'; 3 | import { AppMachineContext } from '../App'; 4 | import { UserState } from '../machines/app.machine'; 5 | import { Footer } from './Footer'; 6 | import { Header } from './Header'; 7 | import { getStateValueStrings } from '../utils/states'; 8 | 9 | export const AppLayout: React.FC = () => { 10 | const current = AppMachineContext.useSelector(s => s); 11 | const userState = 12 | (getStateValueStrings(current.value).find(state => state.includes("user.")) as UserState) || 13 | "user.unauthenticated"; 14 | return ( 15 | <> 16 |
17 | 18 |