Skip to content

Commit 71a392c

Browse files
committed
Merge branch 'release/1.2.0'
2 parents 4ce847b + fb8a9b6 commit 71a392c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2100
-213
lines changed

.env

Lines changed: 0 additions & 2 deletions
This file was deleted.

.env.development

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
API_URL=http://localhost:3000/api
2+
PORT=30402
3+
BASE_URL=http://localhost:30402
4+
TEST_UTILS_TOKEN=foo.bar.baz

.env.production

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
API_URL=http://localhost:30400/api
2+
PORT=80
3+
BASE_URL=http://localhost:80
4+
TEST_UTILS_TOKEN=foo.bar.baz

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ module.exports = {
4444
},
4545
overrides: [
4646
{
47-
files: ['src/shared/lib/test/**/*.{js,ts,jsx,tsx}'],
47+
files: ['src/shared/lib/test/**/*.{js,ts,jsx,tsx}', 'cypress.config.ts', 'cypress/**'],
4848
rules: {
4949
'import/no-extraneous-dependencies': ['off'],
5050
},

.github/workflows/release-pipeline.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
with:
4646
registry: ghcr.io
4747
username: ${{ github.actor }}
48-
password: ${{ secrets.GITHUB_TOKEN }}
48+
password: ${{ secrets.GHCR_PAT }}
4949

5050
- name: Build and Push Docker image
5151
uses: docker/build-push-action@v5

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
5050
# Runtime data
5151
pids
5252
*.pid
53-
*.seed
5453
*.pid.lock
5554

5655
# Directory for instrumented libs generated by jscoverage/JSCover

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Versions
22

3+
## 1.2.0 (2025-06-29)
4+
5+
### 🚀 Features
6+
7+
- Added Cypress end-to-end testing framework with comprehensive user flow tests
8+
- Introduced data-test attributes for improved testability
9+
- Enabled strict null checks in TypeScript configuration for better type safety
10+
- Refactored environment variable management for clarity and multi-env support
11+
- Enhanced test support and coverage across UI components
12+
13+
### 🐛 Bug Fixes
14+
15+
- Fixed Docker login action to use GHCR_PAT instead of GITHUB_TOKEN
16+
- Minor CI/CD and workflow improvements
17+
318
## 1.1.1 (2025-06-15)
419

520
### 🐛 Bug Fixes

README.md

Lines changed: 83 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,62 @@
11
# 🙌 RealWorld example app 🍰 Feature-Sliced Design
22

3-
This codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API. Powered by [FSD (Feature-Sliced Design)](https://feature-sliced.github.io/documentation) architectural methodology.
3+
A modern implementation of the [RealWorld](https://github.com/gothinkster/realworld) app using [FSD (Feature-Sliced Design)](https://feature-sliced.github.io/documentation), React, TypeScript, and a contemporary frontend stack.
44

55
![Realworld example app](./logo.gif)
66

7-
---
7+
## About the Project
88

9-
[![TypeScript][shields-typescript-domain]](https://www.typescriptlang.org/)
10-
[![Webpack][shields-webpack-domain]](https://webpack.js.org/)
11-
[![Jest][shields-jest-domain]](https://jestjs.io/)
12-
[![React][shields-react-domain]](https://react.dev/)
13-
[![React Router][shields-react-router-domain]](https://reactrouter.com/)
14-
[![React Query][shields-react-query-domain]](https://tanstack.com/query/v4/)
15-
[![Redux][shields-redux-domain]](https://redux.js.org/)
16-
[![Zod][shields-zod-domain]](https://zod.dev/)
17-
[![Feature-Sliced Design][shields-fsd-domain]](https://feature-sliced.github.io/documentation/)
18-
19-
## Features
9+
This project is an educational and demonstration Medium-clone built with the Feature-Sliced Design (FSD) architectural approach and modern frontend tools. It is suitable for learning, experimentation, and as a template for large-scale applications.
2010

2111
![Preview][preview-domain]
2212

23-
The example application is a social blogging site (i.e. a Medium.com clone) called "Conduit". It uses a custom API for all requests, including authentication.
24-
25-
### Advanced Features
26-
27-
- **Lazy Loading for React Components and React Router** – Components and routes are dynamically loaded only when needed, improving initial load times and optimizing performance.
28-
- **React Suspense** – Used for handling asynchronous component loading, providing a smooth user experience while waiting for data to load.
29-
- **Error Boundaries** – Ensures the application remains stable by catching JavaScript errors in component trees and displaying fallback UIs instead of crashing the app.
30-
- **Optimistic Updates with React Query** – Provides an enhanced user experience by updating the UI immediately before waiting for the server response, making interactions feel faster.
31-
- **Lazy Loading for Redux Slices** – Dynamically loads Redux slices when required, reducing the initial bundle size and improving app efficiency.
32-
- **Zod Validation for Backend Responses** – Ensures API responses adhere to expected structures using Zod contracts, improving reliability and preventing unexpected runtime errors.
13+
## Tech Stack
14+
15+
- **React** 18+
16+
- **TypeScript**
17+
- **Feature-Sliced Design (FSD)**
18+
- **React Router**
19+
- **React Query**
20+
- **Redux Toolkit**
21+
- **React Hook Form**
22+
- **Webpack**
23+
- **Jest** (unit tests)
24+
- **Cypress** (e2e tests)
25+
- **Zod** (schema validation)
26+
- **CSS Modules**
27+
28+
## Project Structure
29+
30+
- `src/` — application source code
31+
- `app/` — app initialization, routing, providers
32+
- `pages/` — application pages (home, article, login, register, settings, 404, etc.)
33+
- `widgets/` — large widgets (article feed, comments, etc.)
34+
- `features/` — business features (authentication, articles, comments, etc.)
35+
- `entities/` — business entities (article, comment, profile, etc.)
36+
- `shared/` — reusable modules, utilities, UI components, API
37+
- `public/` — static files
38+
- `build/` — production build output
39+
- `coverage/` — test coverage reports
40+
- `cypress/` — e2e tests
41+
42+
## Advanced Features
43+
44+
- **Code Splitting & Lazy Loading** - React components and routes are loaded on demand, reducing initial bundle size and improving performance.
45+
- **React Suspense & Concurrent Features** - Handles asynchronous loading and leverages modern React features for a smoother user experience.
46+
- **Error Boundaries & Centralized Error Logging** - Prevents app crashes by catching JavaScript errors in component trees and displaying fallback UIs, with centralized error reporting.
47+
- **Optimistic UI Updates & Data Prefetching (React Query)** - Instantly updates the UI before server confirmation, prefetches and caches data for fast, responsive interactions.
48+
- **Dynamic Redux Slices** - Loads Redux slices only when needed, optimizing bundle size and state management.
49+
- **Type-Safe API Layer with Axios** - Centralized, strongly-typed API requests and error handling.
50+
- **Zod-based API Validation** - Validates backend responses with Zod schemas, ensuring data consistency and type safety.
51+
- **React Hook Form Integration** - Efficient, scalable form state management with validation and error display.
52+
- **Role-based Access & Permissions** - Permission checks for UI and API actions.
53+
- **Comprehensive Testing** - Includes unit (Jest) and e2e (Cypress) tests, with tag-based selection, custom commands, and coverage reports.
54+
- **CI/CD Integration** - Automated testing, linting, and formatting in the pipeline for reliable delivery.
55+
- **Environment-based Configuration** - Supports multiple environments via `.env` files and runtime variables.
56+
- **Feature-Sliced Design Architecture** - Strict adherence to FSD for scalable, maintainable code.
57+
- **Custom ESLint & Prettier Rules** - Enforced code style, import order, and formatting for consistency.
58+
- **Bundle Analysis & Dependency Graphs** - Visual tools for analyzing bundle size and module dependencies.
59+
- **Hot Module Replacement & Fast Refresh** - Instant feedback during development for a seamless DX.
3360

3461
### Dependency Graph
3562

@@ -39,105 +66,57 @@ The example application is a social blogging site (i.e. a Medium.com clone) call
3966

4067
![Bundle Analyze][bundle-analyze-domain]
4168

42-
**General functionality:**
43-
44-
- Authenticate users via JWT (login/signup pages + logout button on settings page)
45-
- CRU- users (sign up & settings page - no deleting required)
46-
- CRUD Articles
47-
- CR-D Comments on articles (no updating required)
48-
- GET and display paginated lists of articles
49-
- Favorite articles
50-
- Follow other users
51-
5269
## Scripts
5370

54-
- **`yarn start`** - Runs the Webpack development server with `webpack serve`, using development mode.
55-
- **`yarn build:dev`** - Compiles the project in development mode using Webpack.
56-
- **`yarn build:prod`** - Compiles the project in production mode using Webpack for optimized output.
57-
- **`yarn analyze`** - Builds the project in development mode and enables Webpack Bundle Analyzer for visualizing bundle contents.
58-
- **`yarn test`** - Runs Jest to execute unit tests.
59-
- **`yarn eslint`** - Runs ESLint to lint the `src` directory, automatically fixing issues and ensuring no unused disable directives remain.
60-
- **`yarn prettier`** - Formats the entire project using Prettier, respecting `.gitignore` rules.
61-
- **`yarn prepare`** - Initializes Husky and sets up pre-commit and pre-push Git hooks.
71+
- **`yarn start`** - Launches the Webpack development server with hot module replacement.
72+
- **`yarn build:dev`** - Builds the project in development mode.
73+
- **`yarn build:prod`** - Builds the project in production mode with optimizations.
74+
- **`yarn analyze`** - Builds and opens the Webpack Bundle Analyzer for bundle inspection.
75+
- **`yarn test`** - Runs all unit tests with Jest.
76+
- **`yarn eslint`** - Lints and auto-fixes code in the `src` directory.
77+
- **`yarn prettier`** - Formats the codebase using Prettier.
6278
- **`yarn graph`** - Generates a dependency graph of the `src` directory using `dependency-cruiser`.[^1]
79+
- **`yarn cy:open`** - Opens the Cypress UI for interactive e2e testing.
80+
- **`yarn cy:run`** - Runs all Cypress e2e tests in headless mode.
81+
- **`yarn prepare`** - Sets up Husky git hooks for pre-commit and pre-push.
82+
- **`yarn db:seed:dev`** - Seeds the development database via backend API.
83+
- **`yarn db:seed:prod`** - Seeds the production database via backend API.
6384

6485
[^1]:
6586
This assumes the GraphViz `dot` command is available - on most linux and
6687
comparable systems this will be. In case it's not, see
6788
[GraphViz' download page](https://www.graphviz.org/download/) for instructions
6889
on how to get it on your machine.
6990

70-
## Git Hooks and Formatting
71-
72-
This project uses **Husky** and **lint-staged** to enforce code quality before commits and pushes.
73-
74-
### Git hooks configured:
75-
76-
- **pre-commit** – Runs ESLint and Prettier on staged files
77-
- **pre-push** – Runs `yarn test` to ensure tests pass before pushing
78-
79-
### Formatting Commit
80-
81-
The entire codebase has been formatted using ESLint and Prettier.
82-
To avoid noisy blame history caused by formatting-only changes, the formatting commit hash is listed in `.git-blame-ignore-revs`.
83-
84-
To configure your local Git to ignore formatting-only commits in blame:
85-
86-
```bash
87-
git config blame.ignoreRevsFile .git-blame-ignore-revs
88-
```
89-
90-
## Getting started
91-
92-
To get the frontend running locally:
93-
94-
1. Clone this repo
95-
2. `yarn install` to install all the dependencies defined in a `package.json` file.
96-
3. `yarn start` to start webpack dev server.
97-
98-
## 🧪 Demo Environment
99-
100-
You can run both the frontend (this repo) and the backend ([node-express-realworld-example-app](https://github.com/yurisldk/node-express-realworld-example-app)) together using Docker Compose.
101-
102-
A demo setup is available in [`ops/deploy/demo`](./ops/deploy/demo), which includes preconfigured services:
103-
104-
- Frontend (React app)
105-
- Backend API (Node.js + Express + Prisma)
106-
- PostgreSQL database
107-
- PgAdmin for DB inspection
108-
109-
### Run the fullstack demo
110-
111-
Make sure Docker is installed, then from the project root run:
91+
## Demo Environment
11292

113-
```bash
114-
docker-compose -f ops/deploy/demo/docker-compose.yml --env-file ops/deploy/demo/.env up --build -d
115-
```
93+
A ready-to-use demo environment is provided for running both the frontend (this repo) and the backend ([node-express-realworld-example-app](https://github.com/yurisldk/node-express-realworld-example-app)) together using Docker Compose.
11694

117-
Once started, you can access:
95+
The setup in [`ops/deploy/demo`](./ops/deploy/demo) includes preconfigured services:
11896

119-
- Frontend: http://localhost:30401
120-
- API: http://localhost:30400
121-
- PgAdmin: http://localhost:30433
97+
- **frontend** — React-based frontend client
98+
- **api** — Node.js/Express backend API with Prisma and PostgreSQL
99+
- **db** — PostgreSQL database for persistent storage
100+
- **pgadmin** — Admin UI for managing PostgreSQL
122101

123-
## Backend Setup
102+
### Usage
124103

125-
This project is fully compatible with my **[RealWorld Express + Prisma](https://github.com/yurisldk/node-express-realworld-example-app)** backend implementation.
104+
1. A `.env` file is already provided in the demo directory. No extra setup is needed.
105+
2. Start the environment:
106+
```bash
107+
docker-compose -f ops/deploy/demo/docker-compose.yml --env-file ops/deploy/demo/.env up --build -d
108+
```
109+
3. Access the services:
110+
- Frontend: <http://localhost:30401>
111+
- API: <http://localhost:30400>
112+
- PgAdmin: <http://localhost:30433>
126113

127-
To set up the backend:
114+
**Notes:**
128115

129-
1. Follow the installation instructions in the [RealWorld Express + Prisma repository](https://github.com/yurisldk/node-express-realworld-example-app).
130-
2. Ensure the backend is running locally or deployed.
116+
- PostgreSQL data is persisted via named volumes.
117+
- Images are pulled from GitHub Container Registry (GHCR).
118+
- On ARM-based systems (e.g., Apple Silicon), ensure Docker supports `linux/amd64` platform.
131119

132-
[shields-react-router-domain]: https://img.shields.io/badge/React_Router-CA4245?style=for-the-badge&logo=react-router&logoColor=white
133-
[shields-react-query-domain]: https://img.shields.io/badge/-React%20Query-FF4154?style=for-the-badge&logo=react%20query&logoColor=white
134-
[shields-typescript-domain]: https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white
135-
[shields-fsd-domain]: https://img.shields.io/badge/Feature--Sliced-Design?style=for-the-badge&color=F2F2F2&labelColor=262224&logoWidth=10&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAALFAAACxQGJ1n/vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABISURBVHgB7dKxCQAgDETR0w2cws0cys2cwhEUBbsggikCuVekDHwSQFlYo7Q+8KnmtHdFWMdk2cl5wSsbxGSZw8dm8pX9ZHUTMBUgGU2F718AAAAASUVORK5CYII=
136-
[shields-react-domain]: https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB
137120
[dependency-graph-domain]: ./dependency-graph-preview.svg
138121
[preview-domain]: ./preview.gif
139122
[bundle-analyze-domain]: ./bundle-analyze.png
140-
[shields-webpack-domain]: https://img.shields.io/badge/Webpack-8DD6F9?style=for-the-badge&logo=Webpack&logoColor=white
141-
[shields-jest-domain]: https://img.shields.io/badge/Jest-C21325?style=for-the-badge&logo=jest&logoColor=white
142-
[shields-redux-domain]: https://img.shields.io/badge/Redux-593D88?style=for-the-badge&logo=redux&logoColor=white
143-
[shields-zod-domain]: https://img.shields.io/badge/Zod-000000?style=for-the-badge&logo=zod&logoColor=3068B7

cypress.config.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import cypressGrepPlugin from '@cypress/grep/src/plugin';
2+
import axios from 'axios';
3+
import { defineConfig } from 'cypress';
4+
import * as dotenv from 'dotenv';
5+
6+
dotenv.config({ path: `.env.${process.env.NODE_ENV}` });
7+
8+
export default defineConfig({
9+
e2e: {
10+
baseUrl: process.env.BASE_URL,
11+
async setupNodeEvents(_on, config) {
12+
cypressGrepPlugin(config);
13+
14+
const isRunMode = config.isTextTerminal;
15+
if (isRunMode) {
16+
const tags = getTagsForEnv();
17+
if (tags) {
18+
config.env.grepTags = tags.join(' ');
19+
config.env.grepFilterSpecs = true;
20+
}
21+
}
22+
23+
try {
24+
const user = await createE2ETestUser();
25+
config.env.testUser = user;
26+
return config;
27+
} catch (error) {
28+
console.error('[setupNodeEvents] Failed to create test user:', error);
29+
throw error;
30+
}
31+
},
32+
env: {
33+
apiUrl: process.env.API_URL,
34+
},
35+
},
36+
});
37+
38+
async function createE2ETestUser() {
39+
const id = Date.now();
40+
const userPayload = {
41+
username: `e2e_user_${id}`,
42+
email: `e2e_user_${id}@example.com`,
43+
password: 'testpassword123',
44+
};
45+
46+
try {
47+
const response = await axios.post(
48+
`${process.env.API_URL}/users`,
49+
{ user: userPayload },
50+
{
51+
headers: {
52+
'Content-Type': 'application/json',
53+
},
54+
},
55+
);
56+
57+
return {
58+
...response.data.user,
59+
password: userPayload.password,
60+
};
61+
} catch (error) {
62+
console.error('[createE2ETestUser] error:', error.response?.data || error.message);
63+
throw error;
64+
}
65+
}
66+
67+
const TAGS_BY_ENV = {
68+
preview: ['@smoke', '@access'],
69+
develop: ['@functional', '@destructive', '@access'],
70+
stage: ['@functional', '@destructive', '@access', '@prod-safe'],
71+
prod: ['@smoke', '@prod-safe', '@access'],
72+
} as const;
73+
74+
function getTagsForEnv() {
75+
const env = process.env.CYPRESS_ENV;
76+
if (!env) {
77+
throw new Error(
78+
'[cypress.config] CYPRESS_ENV is not defined. Set it to one of: preview, develop, stage, prod, debug',
79+
);
80+
}
81+
82+
if (env === 'debug') {
83+
return null;
84+
}
85+
86+
const tags = TAGS_BY_ENV?.[env as keyof typeof TAGS_BY_ENV];
87+
if (!tags) {
88+
throw new Error(
89+
`[cypress.config] Unknown CYPRESS_ENV "${env}". Set it to one of: preview, develop, stage, prod, debug`,
90+
);
91+
}
92+
return tags;
93+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
describe('Access Control Flow', { tags: ['@access'] }, () => {
2+
const protectedRoutes = ['/editor', '/settings'];
3+
4+
protectedRoutes.forEach((route) => {
5+
it(`should redirect unauthenticated user from ${route} to /login`, () => {
6+
cy.logout();
7+
cy.visit(route);
8+
cy.url().should('include', '/login');
9+
});
10+
});
11+
12+
it('should allow access to protected routes after login', () => {
13+
cy.loginByApi();
14+
cy.visit('/editor');
15+
cy.getByTest('article-title-input').should('be.visible');
16+
17+
cy.visit('/settings');
18+
cy.getByTest('settings-form').should('be.visible');
19+
});
20+
});

0 commit comments

Comments
 (0)