Skip to content

Commit d018ad1

Browse files
feat(redux-storage-middleware): upgrade gmail-clone example and add CI
- Upgrade npm packages to latest versions: - Next.js 14 → 16 (Turbopack) - React 18 → 19 - Tailwind CSS 3 → 4 (new CSS-first config) - ESLint 8 → 9 (flat config) - Add browser close/reopen E2E tests for localStorage persistence - Add GitHub Actions workflow for redux-storage-middleware CI - Rename middleware.ts → storageMiddleware.ts (avoid Next.js convention conflict) - Add proxy.ts to prevent Turbopack parent directory scanning - Fix React 19 lint rules (useMemo instead of useState+useEffect) All 151 unit tests and 15 E2E tests pass.
1 parent 74c15d1 commit d018ad1

File tree

16 files changed

+709
-181
lines changed

16 files changed

+709
-181
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
name: Redux Storage Middleware
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'packages/redux-storage-middleware/**'
8+
- '.github/workflows/redux-storage-middleware.yml'
9+
pull_request:
10+
branches: [main]
11+
paths:
12+
- 'packages/redux-storage-middleware/**'
13+
- '.github/workflows/redux-storage-middleware.yml'
14+
15+
concurrency:
16+
group: redux-storage-middleware-${{ github.ref }}
17+
cancel-in-progress: true
18+
19+
defaults:
20+
run:
21+
working-directory: packages/redux-storage-middleware
22+
23+
jobs:
24+
typecheck:
25+
name: TypeScript Check
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- name: Prepare environment
31+
uses: ./.github/actions/prepare
32+
33+
- name: TypeScript Check
34+
run: pnpm typecheck
35+
36+
lint:
37+
name: Lint
38+
runs-on: ubuntu-latest
39+
steps:
40+
- uses: actions/checkout@v4
41+
42+
- name: Prepare environment
43+
uses: ./.github/actions/prepare
44+
45+
- name: Lint (ESLint)
46+
run: pnpm lint
47+
working-directory: .
48+
49+
test:
50+
name: Unit Tests
51+
runs-on: ubuntu-latest
52+
steps:
53+
- uses: actions/checkout@v4
54+
55+
- name: Prepare environment
56+
uses: ./.github/actions/prepare
57+
58+
- name: Install Playwright browsers
59+
run: pnpm exec playwright install --with-deps chromium
60+
61+
- name: Run Unit Tests
62+
run: pnpm test:run
63+
64+
build:
65+
name: Build
66+
runs-on: ubuntu-latest
67+
steps:
68+
- uses: actions/checkout@v4
69+
70+
- name: Prepare environment
71+
uses: ./.github/actions/prepare
72+
73+
- name: Build Package
74+
run: pnpm build
75+
76+
e2e:
77+
name: E2E Tests
78+
runs-on: ubuntu-latest
79+
steps:
80+
- uses: actions/checkout@v4
81+
82+
- name: Prepare environment
83+
uses: ./.github/actions/prepare
84+
85+
- name: Install Playwright browsers
86+
run: pnpm exec playwright install --with-deps chromium
87+
88+
- name: Build gmail-clone example
89+
run: pnpm build
90+
working-directory: packages/redux-storage-middleware/examples/gmail-clone
91+
92+
- name: Run E2E Tests
93+
run: pnpm test:e2e
94+
working-directory: packages/redux-storage-middleware/examples/gmail-clone
95+
96+
- name: Upload Playwright Report
97+
uses: actions/upload-artifact@v4
98+
if: ${{ !cancelled() }}
99+
with:
100+
name: playwright-report
101+
path: packages/redux-storage-middleware/examples/gmail-clone/playwright-report/
102+
retention-days: 7

packages/redux-storage-middleware/examples/gmail-clone/.eslintrc.json

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { fixupConfigRules } from '@eslint/compat'
2+
import nextVitals from 'eslint-config-next/core-web-vitals'
3+
import nextTs from 'eslint-config-next/typescript'
4+
5+
/**
6+
* Merge ESLint configs and deduplicate plugins to avoid conflicts.
7+
* @param {...Array} configArrays - ESLint config arrays to merge
8+
* @returns {Array} - Merged config array with deduplicated plugins
9+
*/
10+
function mergeConfigs(...configArrays) {
11+
const merged = []
12+
const allPlugins = {}
13+
14+
for (const configArray of configArrays) {
15+
const fixed = fixupConfigRules(configArray)
16+
for (const config of fixed) {
17+
if (config.plugins) {
18+
Object.assign(allPlugins, config.plugins)
19+
const { plugins: _plugins, ...rest } = config
20+
if (Object.keys(rest).length > 0) {
21+
merged.push(rest)
22+
}
23+
} else {
24+
merged.push(config)
25+
}
26+
}
27+
}
28+
29+
if (Object.keys(allPlugins).length > 0) {
30+
merged.push({ plugins: allPlugins })
31+
}
32+
33+
return merged
34+
}
35+
36+
const eslintConfig = [
37+
...mergeConfigs(nextVitals, nextTs),
38+
{
39+
ignores: ['.next/**', 'node_modules/**'],
40+
},
41+
]
42+
43+
export default eslintConfig
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/** @type {import('next').NextConfig} */
2-
const nextConfig = {}
2+
const nextConfig = {
3+
transpilePackages: ['@gitbox/redux-storage-middleware'],
4+
}
35

46
export default nextConfig

packages/redux-storage-middleware/examples/gmail-clone/package.json

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,26 @@
2525
"class-variance-authority": "^0.7.1",
2626
"clsx": "^2.1.1",
2727
"date-fns": "^4.1.0",
28-
"lucide-react": "^0.561.0",
29-
"next": "14.2.35",
30-
"react": "^18",
31-
"react-dom": "^18",
28+
"lucide-react": "^0.562.0",
29+
"next": "16.1.1",
30+
"react": "^19",
31+
"react-dom": "^19",
3232
"react-redux": "^9.2.0",
3333
"tailwind-merge": "^3.4.0",
3434
"tailwindcss-animate": "^1.0.7"
3535
},
3636
"devDependencies": {
37+
"@eslint/compat": "^2.0.0",
38+
"@eslint/eslintrc": "^3.3.1",
3739
"@playwright/test": "^1.57.0",
38-
"@types/node": "^20",
40+
"@tailwindcss/postcss": "^4.1.18",
41+
"@types/node": "^25",
3942
"@types/react": "^19.2.7",
4043
"@types/react-dom": "^19.2.3",
41-
"eslint": "^8",
42-
"eslint-config-next": "14.2.35",
44+
"eslint": "^9",
45+
"eslint-config-next": "16.1.1",
4346
"postcss": "^8",
44-
"tailwindcss": "^3.4.1",
47+
"tailwindcss": "^4.1.18",
4548
"typescript": "^5"
4649
}
4750
}

packages/redux-storage-middleware/examples/gmail-clone/postcss.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @type {import('postcss-load-config').Config} */
22
const config = {
33
plugins: {
4-
tailwindcss: {},
4+
'@tailwindcss/postcss': {},
55
},
66
}
77

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Next.js 16 Proxy Configuration
3+
*
4+
* This file exists to prevent Turbopack from scanning parent directories
5+
* and finding the main GitBox proxy.ts which uses project-specific imports.
6+
*
7+
* Gmail-clone example app doesn't need authentication proxying.
8+
*/
9+
10+
import { NextResponse, type NextRequest } from 'next/server'
11+
12+
/**
13+
* Proxy function that allows all requests through without authentication.
14+
* @param request - The incoming request
15+
* @returns Next response allowing the request to proceed
16+
*/
17+
export async function proxy(request: NextRequest) {
18+
return NextResponse.next({
19+
request: {
20+
headers: request.headers,
21+
},
22+
})
23+
}
24+
25+
export const config = {
26+
matcher: [],
27+
}

packages/redux-storage-middleware/examples/gmail-clone/src/app/globals.css

Lines changed: 90 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,96 @@
1-
@tailwind base;
2-
@tailwind components;
3-
@tailwind utilities;
1+
/**
2+
* Global Styles for Gmail Clone Example
3+
* Tailwind CSS v4 format
4+
*/
5+
@plugin "tailwindcss-animate";
6+
@import 'tailwindcss';
47

5-
@layer utilities {
6-
.text-balance {
7-
text-wrap: balance;
8-
}
8+
@custom-variant dark (&:is(.dark *));
9+
10+
@theme inline {
11+
--radius-sm: calc(var(--radius) - 4px);
12+
--radius-md: calc(var(--radius) - 2px);
13+
--radius-lg: var(--radius);
14+
--radius-xl: calc(var(--radius) + 4px);
15+
--color-background: var(--background);
16+
--color-foreground: var(--foreground);
17+
--color-card: var(--card);
18+
--color-card-foreground: var(--card-foreground);
19+
--color-popover: var(--popover);
20+
--color-popover-foreground: var(--popover-foreground);
21+
--color-primary: var(--primary);
22+
--color-primary-foreground: var(--primary-foreground);
23+
--color-secondary: var(--secondary);
24+
--color-secondary-foreground: var(--secondary-foreground);
25+
--color-muted: var(--muted);
26+
--color-muted-foreground: var(--muted-foreground);
27+
--color-accent: var(--accent);
28+
--color-accent-foreground: var(--accent-foreground);
29+
--color-destructive: var(--destructive);
30+
--color-destructive-foreground: var(--destructive-foreground);
31+
--color-border: var(--border);
32+
--color-input: var(--input);
33+
--color-ring: var(--ring);
34+
--color-chart-1: var(--chart-1);
35+
--color-chart-2: var(--chart-2);
36+
--color-chart-3: var(--chart-3);
37+
--color-chart-4: var(--chart-4);
38+
--color-chart-5: var(--chart-5);
939
}
1040

11-
@layer base {
12-
:root {
13-
--background: 0 0% 100%;
14-
--foreground: 0 0% 3.9%;
15-
--card: 0 0% 100%;
16-
--card-foreground: 0 0% 3.9%;
17-
--popover: 0 0% 100%;
18-
--popover-foreground: 0 0% 3.9%;
19-
--primary: 0 0% 9%;
20-
--primary-foreground: 0 0% 98%;
21-
--secondary: 0 0% 96.1%;
22-
--secondary-foreground: 0 0% 9%;
23-
--muted: 0 0% 96.1%;
24-
--muted-foreground: 0 0% 45.1%;
25-
--accent: 0 0% 96.1%;
26-
--accent-foreground: 0 0% 9%;
27-
--destructive: 0 84.2% 60.2%;
28-
--destructive-foreground: 0 0% 98%;
29-
--border: 0 0% 89.8%;
30-
--input: 0 0% 89.8%;
31-
--ring: 0 0% 3.9%;
32-
--chart-1: 12 76% 61%;
33-
--chart-2: 173 58% 39%;
34-
--chart-3: 197 37% 24%;
35-
--chart-4: 43 74% 66%;
36-
--chart-5: 27 87% 67%;
37-
--radius: 0.5rem;
38-
}
39-
.dark {
40-
--background: 0 0% 3.9%;
41-
--foreground: 0 0% 98%;
42-
--card: 0 0% 3.9%;
43-
--card-foreground: 0 0% 98%;
44-
--popover: 0 0% 3.9%;
45-
--popover-foreground: 0 0% 98%;
46-
--primary: 0 0% 98%;
47-
--primary-foreground: 0 0% 9%;
48-
--secondary: 0 0% 14.9%;
49-
--secondary-foreground: 0 0% 98%;
50-
--muted: 0 0% 14.9%;
51-
--muted-foreground: 0 0% 63.9%;
52-
--accent: 0 0% 14.9%;
53-
--accent-foreground: 0 0% 98%;
54-
--destructive: 0 62.8% 30.6%;
55-
--destructive-foreground: 0 0% 98%;
56-
--border: 0 0% 14.9%;
57-
--input: 0 0% 14.9%;
58-
--ring: 0 0% 83.1%;
59-
--chart-1: 220 70% 50%;
60-
--chart-2: 160 60% 45%;
61-
--chart-3: 30 80% 55%;
62-
--chart-4: 280 65% 60%;
63-
--chart-5: 340 75% 55%;
64-
}
41+
:root {
42+
--radius: 0.5rem;
43+
--background: oklch(1 0 0);
44+
--foreground: oklch(0.145 0 0);
45+
--card: oklch(1 0 0);
46+
--card-foreground: oklch(0.145 0 0);
47+
--popover: oklch(1 0 0);
48+
--popover-foreground: oklch(0.145 0 0);
49+
--primary: oklch(0.205 0 0);
50+
--primary-foreground: oklch(0.985 0 0);
51+
--secondary: oklch(0.97 0 0);
52+
--secondary-foreground: oklch(0.205 0 0);
53+
--muted: oklch(0.97 0 0);
54+
--muted-foreground: oklch(0.556 0 0);
55+
--accent: oklch(0.97 0 0);
56+
--accent-foreground: oklch(0.205 0 0);
57+
--destructive: oklch(0.577 0.245 27.325);
58+
--destructive-foreground: oklch(0.985 0 0);
59+
--border: oklch(0.922 0 0);
60+
--input: oklch(0.922 0 0);
61+
--ring: oklch(0.708 0 0);
62+
--chart-1: oklch(0.646 0.222 41.116);
63+
--chart-2: oklch(0.6 0.118 184.704);
64+
--chart-3: oklch(0.398 0.07 227.392);
65+
--chart-4: oklch(0.828 0.189 84.429);
66+
--chart-5: oklch(0.769 0.188 70.08);
67+
}
68+
69+
.dark {
70+
--background: oklch(0.145 0 0);
71+
--foreground: oklch(0.985 0 0);
72+
--card: oklch(0.145 0 0);
73+
--card-foreground: oklch(0.985 0 0);
74+
--popover: oklch(0.145 0 0);
75+
--popover-foreground: oklch(0.985 0 0);
76+
--primary: oklch(0.985 0 0);
77+
--primary-foreground: oklch(0.205 0 0);
78+
--secondary: oklch(0.269 0 0);
79+
--secondary-foreground: oklch(0.985 0 0);
80+
--muted: oklch(0.269 0 0);
81+
--muted-foreground: oklch(0.708 0 0);
82+
--accent: oklch(0.269 0 0);
83+
--accent-foreground: oklch(0.985 0 0);
84+
--destructive: oklch(0.396 0.141 25.723);
85+
--destructive-foreground: oklch(0.985 0 0);
86+
--border: oklch(0.269 0 0);
87+
--input: oklch(0.269 0 0);
88+
--ring: oklch(0.439 0 0);
89+
--chart-1: oklch(0.488 0.243 264.376);
90+
--chart-2: oklch(0.696 0.17 162.48);
91+
--chart-3: oklch(0.769 0.188 70.08);
92+
--chart-4: oklch(0.627 0.265 303.9);
93+
--chart-5: oklch(0.645 0.246 16.439);
6594
}
6695

6796
@layer base {

0 commit comments

Comments
 (0)