Accessibility fixes.

This commit is contained in:
Jennie Robinson Faber 2026-04-05 19:27:25 +01:00
parent 689548e389
commit dae983734a
7 changed files with 201 additions and 140 deletions

View file

@ -30,6 +30,30 @@ export default defineNuxtConfig({
hmr: {
port: 24678,
},
watch: {
ignored: [
"**/.git/**",
"**/.nuxt/**",
"**/.output/**",
"**/node_modules/**",
"**/dist/**",
"**/e2e/**",
"**/coverage/**",
],
},
},
},
nitro: {
watchOptions: {
ignored: [
"**/.git/**",
"**/.nuxt/**",
"**/.output/**",
"**/node_modules/**",
"**/dist/**",
"**/e2e/**",
"**/coverage/**",
],
},
},
runtimeConfig: {

View file

@ -1,31 +1,32 @@
import { defineConfig } from '@playwright/test'
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: './e2e',
outputDir: 'e2e/test-results',
snapshotDir: 'e2e/__screenshots__',
testDir: "./e2e",
outputDir: "e2e/test-results",
snapshotDir: "e2e/__screenshots__",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
reporter: "html",
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
baseURL: "http://localhost:3000",
trace: "on-first-retry",
},
projects: [
{
name: 'chromium',
use: { browserName: 'chromium' },
name: "chromium",
use: { browserName: "chromium" },
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
command: "npm run build && NODE_ENV=development npm run preview",
url: "http://localhost:3000",
reuseExistingServer: false,
env: {
NUXT_PUBLIC_COMING_SOON: 'false',
NODE_ENV: 'development',
NUXT_PUBLIC_COMING_SOON: "false",
NODE_ENV: "development",
ALLOW_DEV_TEST_ENDPOINTS: "true",
},
},
})
});

View file

@ -5,109 +5,128 @@
* Safe to run multiple times.
*/
import 'dotenv/config'
import mongoose from 'mongoose'
import Tag from '../server/models/tag.js'
import { connectDB } from '../server/utils/mongoose.js'
import "dotenv/config";
import mongoose from "mongoose";
import Tag from "../server/models/tag.js";
import { connectDB } from "../server/utils/mongoose.js";
// Convert a slug like "qa-and-testing" to "QA and Testing"
// Special-cases common abbreviations.
const ABBREVIATIONS = new Map([
['qa', 'QA'],
['ux', 'UX'],
['ui', 'UI'],
['devops', 'DevOps'],
])
["qa", "QA"],
["ux", "UX"],
["ui", "UI"],
["devops", "DevOps"],
]);
function slugToLabel(slug) {
return slug
.split('-')
.map((word) => ABBREVIATIONS.get(word) ?? word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
.split("-")
.map(
(word) =>
ABBREVIATIONS.get(word) ?? word.charAt(0).toUpperCase() + word.slice(1),
)
.join(" ");
}
const CRAFT_SLUGS = [
'game-design',
'programming',
'narrative-design',
'art-and-animation',
'audio-and-music',
'production-management',
'qa-and-testing',
'community-management',
'marketing-and-comms',
'ux-and-ui-design',
'business-development',
'devops-and-tools',
'localization',
'accessibility',
'analytics-and-data',
'education-and-mentoring',
]
"game-design",
"programming",
"narrative-design",
"art-and-animation",
"audio-and-music",
"production-management",
"qa-and-testing",
"community-management",
"marketing-and-comms",
"ux-and-ui-design",
"business-development",
"devops-and-tools",
"localization",
"accessibility",
"analytics-and-data",
"education-and-mentoring",
];
const COOPERATIVE_SLUGS = [
'governance',
'finance-and-budgeting',
'legal-structures',
'conflict-resolution',
'consensus-decision-making',
'revenue-sharing',
'cooperative-bylaws',
'member-onboarding',
'democratic-management',
'worker-ownership',
'platform-cooperativism',
'cooperative-marketing',
'shared-resources',
'cooperative-funding',
'community-building',
'equity-and-inclusion',
'cooperative-tech',
'sustainability',
'collective-bargaining',
'inter-coop-collaboration',
]
"governance",
"finance-and-budgeting",
"legal-structures",
"conflict-resolution",
"consensus-decision-making",
"revenue-sharing",
"cooperative-bylaws",
"member-onboarding",
"democratic-management",
"worker-ownership",
"platform-cooperativism",
"cooperative-marketing",
"shared-resources",
"cooperative-funding",
"community-building",
"equity-and-inclusion",
"cooperative-tech",
"sustainability",
"collective-bargaining",
"inter-coop-collaboration",
];
async function seedTags() {
await connectDB()
await connectDB();
const tagDefs = [
...CRAFT_SLUGS.map((slug) => ({ slug, pool: 'craft', label: slugToLabel(slug) })),
...COOPERATIVE_SLUGS.map((slug) => ({ slug, pool: 'cooperative', label: slugToLabel(slug) })),
]
...CRAFT_SLUGS.map((slug) => ({
slug,
pool: "craft",
label: slugToLabel(slug),
})),
...COOPERATIVE_SLUGS.map((slug) => ({
slug,
pool: "cooperative",
label: slugToLabel(slug),
})),
];
let upserted = 0
let unchanged = 0
let upserted = 0;
let unchanged = 0;
for (const { slug, pool, label } of tagDefs) {
const result = await Tag.updateOne(
{ slug },
{ $setOnInsert: { slug, pool, label, active: true, createdAt: new Date() } },
{ upsert: true }
)
{
$setOnInsert: {
slug,
pool,
label,
active: true,
createdAt: new Date(),
},
},
{ upsert: true },
);
if (result.upsertedCount > 0) {
console.log(` + Created [${pool}] ${label} (${slug})`)
upserted++
console.log(` + Created [${pool}] ${label} (${slug})`);
upserted++;
} else {
unchanged++
unchanged++;
}
}
console.log('\n=== Seed Complete ===')
console.log(` Total tags defined: ${tagDefs.length}`)
console.log(` Newly created: ${upserted}`)
console.log(` Already existed: ${unchanged}`)
console.log("\n=== Seed Complete ===");
console.log(` Total tags defined: ${tagDefs.length}`);
console.log(` Newly created: ${upserted}`);
console.log(` Already existed: ${unchanged}`);
}
seedTags()
.then(() => {
console.log('\nTag seed completed successfully')
process.exit(0)
console.log("\nTag seed completed successfully");
process.exit(0);
})
.catch((err) => {
console.error('\nTag seed failed:', err)
process.exit(1)
console.error("\nTag seed failed:", err);
process.exit(1);
})
.finally(() => {
mongoose.connection.close()
})
mongoose.connection.close();
});

View file

@ -1,41 +1,50 @@
import jwt from 'jsonwebtoken'
import Member from '../../models/member.js'
import { connectDB } from '../../utils/mongoose.js'
import jwt from "jsonwebtoken";
import Member from "../../models/member.js";
import { connectDB } from "../../utils/mongoose.js";
export default defineEventHandler(async (event) => {
// Only allow in development
if (process.env.NODE_ENV === 'production') {
throw createError({ statusCode: 404, statusMessage: 'Not found' })
// Only allow in development, unless explicitly enabled for Playwright preview runs
if (
process.env.NODE_ENV === "production" &&
process.env.ALLOW_DEV_TEST_ENDPOINTS !== "true"
) {
throw createError({ statusCode: 404, statusMessage: "Not found" });
}
const query = getQuery(event)
const email = query.email
const query = getQuery(event);
const email = query.email;
if (!email) {
throw createError({ statusCode: 400, statusMessage: 'email query param required' })
throw createError({
statusCode: 400,
statusMessage: "email query param required",
});
}
await connectDB()
await connectDB();
const member = await Member.findOne({ email: email.toLowerCase() })
const member = await Member.findOne({ email: email.toLowerCase() });
if (!member) {
throw createError({ statusCode: 404, statusMessage: `No member found with email: ${email}` })
throw createError({
statusCode: 404,
statusMessage: `No member found with email: ${email}`,
});
}
const config = useRuntimeConfig(event)
const config = useRuntimeConfig(event);
const token = jwt.sign(
{ memberId: member._id, email: member.email, tv: member.tokenVersion },
config.jwtSecret,
{ expiresIn: '7d' }
)
{ expiresIn: "7d" },
);
setCookie(event, 'auth-token', token, {
setCookie(event, "auth-token", token, {
httpOnly: true,
secure: false,
sameSite: 'lax',
sameSite: "lax",
maxAge: 60 * 60 * 24 * 7,
})
});
await sendRedirect(event, '/member/account', 302)
})
await sendRedirect(event, "/member/account", 302);
});

View file

@ -1,19 +1,24 @@
import Member from '../../models/member.js'
import { connectDB } from '../../utils/mongoose.js'
import Member from "../../models/member.js";
import { connectDB } from "../../utils/mongoose.js";
export default defineEventHandler(async () => {
if (process.env.NODE_ENV === 'production') {
throw createError({ statusCode: 404, statusMessage: 'Not found' })
if (
process.env.NODE_ENV === "production" &&
process.env.ALLOW_DEV_TEST_ENDPOINTS !== "true"
) {
throw createError({ statusCode: 404, statusMessage: "Not found" });
}
await connectDB()
await connectDB();
const members = await Member.find({}, 'name email circle role status').sort({ name: 1 }).lean()
const members = await Member.find({}, "name email circle role status")
.sort({ name: 1 })
.lean();
return members.map((m) => ({
label: `${m.name} (${m.email})`,
value: m.email,
circle: m.circle,
role: m.role
}))
})
role: m.role,
}));
});

View file

@ -1,42 +1,45 @@
import jwt from 'jsonwebtoken'
import Member from '../../models/member.js'
import { connectDB } from '../../utils/mongoose.js'
import jwt from "jsonwebtoken";
import Member from "../../models/member.js";
import { connectDB } from "../../utils/mongoose.js";
export default defineEventHandler(async (event) => {
// Only allow in development
if (process.env.NODE_ENV === 'production') {
throw createError({ statusCode: 404, statusMessage: 'Not found' })
// Only allow in development, unless explicitly enabled for Playwright preview runs
if (
process.env.NODE_ENV === "production" &&
process.env.ALLOW_DEV_TEST_ENDPOINTS !== "true"
) {
throw createError({ statusCode: 404, statusMessage: "Not found" });
}
await connectDB()
await connectDB();
// Find or create a test admin user
let member = await Member.findOne({ email: 'test-admin@ghostguild.dev' })
let member = await Member.findOne({ email: "test-admin@ghostguild.dev" });
if (!member) {
member = await Member.create({
email: 'test-admin@ghostguild.dev',
name: 'Test Admin',
circle: 'founder',
contributionTier: '0',
role: 'admin',
status: 'active',
})
email: "test-admin@ghostguild.dev",
name: "Test Admin",
circle: "founder",
contributionTier: "0",
role: "admin",
status: "active",
});
}
const config = useRuntimeConfig(event)
const config = useRuntimeConfig(event);
const token = jwt.sign(
{ memberId: member._id, email: member.email, tv: member.tokenVersion },
config.jwtSecret,
{ expiresIn: '7d' }
)
{ expiresIn: "7d" },
);
setCookie(event, 'auth-token', token, {
setCookie(event, "auth-token", token, {
httpOnly: true,
secure: false,
sameSite: 'lax',
sameSite: "lax",
maxAge: 60 * 60 * 24 * 7,
})
});
await sendRedirect(event, '/admin', 302)
})
await sendRedirect(event, "/admin", 302);
});