Accessibility fixes.
This commit is contained in:
parent
689548e389
commit
dae983734a
7 changed files with 201 additions and 140 deletions
|
|
@ -30,6 +30,30 @@ export default defineNuxtConfig({
|
||||||
hmr: {
|
hmr: {
|
||||||
port: 24678,
|
port: 24678,
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
ignored: [
|
||||||
|
"**/.git/**",
|
||||||
|
"**/.nuxt/**",
|
||||||
|
"**/.output/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/e2e/**",
|
||||||
|
"**/coverage/**",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nitro: {
|
||||||
|
watchOptions: {
|
||||||
|
ignored: [
|
||||||
|
"**/.git/**",
|
||||||
|
"**/.nuxt/**",
|
||||||
|
"**/.output/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/e2e/**",
|
||||||
|
"**/coverage/**",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,32 @@
|
||||||
import { defineConfig } from '@playwright/test'
|
import { defineConfig } from "@playwright/test";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: './e2e',
|
testDir: "./e2e",
|
||||||
outputDir: 'e2e/test-results',
|
outputDir: "e2e/test-results",
|
||||||
snapshotDir: 'e2e/__screenshots__',
|
snapshotDir: "e2e/__screenshots__",
|
||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
retries: process.env.CI ? 1 : 0,
|
retries: process.env.CI ? 1 : 0,
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
reporter: 'html',
|
reporter: "html",
|
||||||
use: {
|
use: {
|
||||||
baseURL: 'http://localhost:3000',
|
baseURL: "http://localhost:3000",
|
||||||
trace: 'on-first-retry',
|
trace: "on-first-retry",
|
||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: "chromium",
|
||||||
use: { browserName: 'chromium' },
|
use: { browserName: "chromium" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run dev',
|
command: "npm run build && NODE_ENV=development npm run preview",
|
||||||
url: 'http://localhost:3000',
|
url: "http://localhost:3000",
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: false,
|
||||||
env: {
|
env: {
|
||||||
NUXT_PUBLIC_COMING_SOON: 'false',
|
NUXT_PUBLIC_COMING_SOON: "false",
|
||||||
NODE_ENV: 'development',
|
NODE_ENV: "development",
|
||||||
|
ALLOW_DEV_TEST_ENDPOINTS: "true",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,109 +5,128 @@
|
||||||
* Safe to run multiple times.
|
* Safe to run multiple times.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dotenv/config'
|
import "dotenv/config";
|
||||||
import mongoose from 'mongoose'
|
import mongoose from "mongoose";
|
||||||
import Tag from '../server/models/tag.js'
|
import Tag from "../server/models/tag.js";
|
||||||
import { connectDB } from '../server/utils/mongoose.js'
|
import { connectDB } from "../server/utils/mongoose.js";
|
||||||
|
|
||||||
// Convert a slug like "qa-and-testing" to "QA and Testing"
|
// Convert a slug like "qa-and-testing" to "QA and Testing"
|
||||||
// Special-cases common abbreviations.
|
// Special-cases common abbreviations.
|
||||||
const ABBREVIATIONS = new Map([
|
const ABBREVIATIONS = new Map([
|
||||||
['qa', 'QA'],
|
["qa", "QA"],
|
||||||
['ux', 'UX'],
|
["ux", "UX"],
|
||||||
['ui', 'UI'],
|
["ui", "UI"],
|
||||||
['devops', 'DevOps'],
|
["devops", "DevOps"],
|
||||||
])
|
]);
|
||||||
|
|
||||||
function slugToLabel(slug) {
|
function slugToLabel(slug) {
|
||||||
return slug
|
return slug
|
||||||
.split('-')
|
.split("-")
|
||||||
.map((word) => ABBREVIATIONS.get(word) ?? word.charAt(0).toUpperCase() + word.slice(1))
|
.map(
|
||||||
.join(' ')
|
(word) =>
|
||||||
|
ABBREVIATIONS.get(word) ?? word.charAt(0).toUpperCase() + word.slice(1),
|
||||||
|
)
|
||||||
|
.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
const CRAFT_SLUGS = [
|
const CRAFT_SLUGS = [
|
||||||
'game-design',
|
"game-design",
|
||||||
'programming',
|
"programming",
|
||||||
'narrative-design',
|
"narrative-design",
|
||||||
'art-and-animation',
|
"art-and-animation",
|
||||||
'audio-and-music',
|
"audio-and-music",
|
||||||
'production-management',
|
"production-management",
|
||||||
'qa-and-testing',
|
"qa-and-testing",
|
||||||
'community-management',
|
"community-management",
|
||||||
'marketing-and-comms',
|
"marketing-and-comms",
|
||||||
'ux-and-ui-design',
|
"ux-and-ui-design",
|
||||||
'business-development',
|
"business-development",
|
||||||
'devops-and-tools',
|
"devops-and-tools",
|
||||||
'localization',
|
"localization",
|
||||||
'accessibility',
|
"accessibility",
|
||||||
'analytics-and-data',
|
"analytics-and-data",
|
||||||
'education-and-mentoring',
|
"education-and-mentoring",
|
||||||
]
|
];
|
||||||
|
|
||||||
const COOPERATIVE_SLUGS = [
|
const COOPERATIVE_SLUGS = [
|
||||||
'governance',
|
"governance",
|
||||||
'finance-and-budgeting',
|
"finance-and-budgeting",
|
||||||
'legal-structures',
|
"legal-structures",
|
||||||
'conflict-resolution',
|
"conflict-resolution",
|
||||||
'consensus-decision-making',
|
"consensus-decision-making",
|
||||||
'revenue-sharing',
|
"revenue-sharing",
|
||||||
'cooperative-bylaws',
|
"cooperative-bylaws",
|
||||||
'member-onboarding',
|
"member-onboarding",
|
||||||
'democratic-management',
|
"democratic-management",
|
||||||
'worker-ownership',
|
"worker-ownership",
|
||||||
'platform-cooperativism',
|
"platform-cooperativism",
|
||||||
'cooperative-marketing',
|
"cooperative-marketing",
|
||||||
'shared-resources',
|
"shared-resources",
|
||||||
'cooperative-funding',
|
"cooperative-funding",
|
||||||
'community-building',
|
"community-building",
|
||||||
'equity-and-inclusion',
|
"equity-and-inclusion",
|
||||||
'cooperative-tech',
|
"cooperative-tech",
|
||||||
'sustainability',
|
"sustainability",
|
||||||
'collective-bargaining',
|
"collective-bargaining",
|
||||||
'inter-coop-collaboration',
|
"inter-coop-collaboration",
|
||||||
]
|
];
|
||||||
|
|
||||||
async function seedTags() {
|
async function seedTags() {
|
||||||
await connectDB()
|
await connectDB();
|
||||||
|
|
||||||
const tagDefs = [
|
const tagDefs = [
|
||||||
...CRAFT_SLUGS.map((slug) => ({ slug, pool: 'craft', label: slugToLabel(slug) })),
|
...CRAFT_SLUGS.map((slug) => ({
|
||||||
...COOPERATIVE_SLUGS.map((slug) => ({ slug, pool: 'cooperative', label: slugToLabel(slug) })),
|
slug,
|
||||||
]
|
pool: "craft",
|
||||||
|
label: slugToLabel(slug),
|
||||||
|
})),
|
||||||
|
...COOPERATIVE_SLUGS.map((slug) => ({
|
||||||
|
slug,
|
||||||
|
pool: "cooperative",
|
||||||
|
label: slugToLabel(slug),
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
let upserted = 0
|
let upserted = 0;
|
||||||
let unchanged = 0
|
let unchanged = 0;
|
||||||
|
|
||||||
for (const { slug, pool, label } of tagDefs) {
|
for (const { slug, pool, label } of tagDefs) {
|
||||||
const result = await Tag.updateOne(
|
const result = await Tag.updateOne(
|
||||||
{ slug },
|
{ 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) {
|
if (result.upsertedCount > 0) {
|
||||||
console.log(` + Created [${pool}] ${label} (${slug})`)
|
console.log(` + Created [${pool}] ${label} (${slug})`);
|
||||||
upserted++
|
upserted++;
|
||||||
} else {
|
} else {
|
||||||
unchanged++
|
unchanged++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('\n=== Seed Complete ===')
|
console.log("\n=== Seed Complete ===");
|
||||||
console.log(` Total tags defined: ${tagDefs.length}`)
|
console.log(` Total tags defined: ${tagDefs.length}`);
|
||||||
console.log(` Newly created: ${upserted}`)
|
console.log(` Newly created: ${upserted}`);
|
||||||
console.log(` Already existed: ${unchanged}`)
|
console.log(` Already existed: ${unchanged}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
seedTags()
|
seedTags()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('\nTag seed completed successfully')
|
console.log("\nTag seed completed successfully");
|
||||||
process.exit(0)
|
process.exit(0);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('\nTag seed failed:', err)
|
console.error("\nTag seed failed:", err);
|
||||||
process.exit(1)
|
process.exit(1);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
mongoose.connection.close()
|
mongoose.connection.close();
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,50 @@
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from "jsonwebtoken";
|
||||||
import Member from '../../models/member.js'
|
import Member from "../../models/member.js";
|
||||||
import { connectDB } from '../../utils/mongoose.js'
|
import { connectDB } from "../../utils/mongoose.js";
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
// Only allow in development
|
// Only allow in development, unless explicitly enabled for Playwright preview runs
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (
|
||||||
throw createError({ statusCode: 404, statusMessage: 'Not found' })
|
process.env.NODE_ENV === "production" &&
|
||||||
|
process.env.ALLOW_DEV_TEST_ENDPOINTS !== "true"
|
||||||
|
) {
|
||||||
|
throw createError({ statusCode: 404, statusMessage: "Not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = getQuery(event)
|
const query = getQuery(event);
|
||||||
const email = query.email
|
const email = query.email;
|
||||||
|
|
||||||
if (!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) {
|
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(
|
const token = jwt.sign(
|
||||||
{ memberId: member._id, email: member.email, tv: member.tokenVersion },
|
{ memberId: member._id, email: member.email, tv: member.tokenVersion },
|
||||||
config.jwtSecret,
|
config.jwtSecret,
|
||||||
{ expiresIn: '7d' }
|
{ expiresIn: "7d" },
|
||||||
)
|
);
|
||||||
|
|
||||||
setCookie(event, 'auth-token', token, {
|
setCookie(event, "auth-token", token, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
sameSite: 'lax',
|
sameSite: "lax",
|
||||||
maxAge: 60 * 60 * 24 * 7,
|
maxAge: 60 * 60 * 24 * 7,
|
||||||
})
|
});
|
||||||
|
|
||||||
await sendRedirect(event, '/member/account', 302)
|
await sendRedirect(event, "/member/account", 302);
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,24 @@
|
||||||
import Member from '../../models/member.js'
|
import Member from "../../models/member.js";
|
||||||
import { connectDB } from '../../utils/mongoose.js'
|
import { connectDB } from "../../utils/mongoose.js";
|
||||||
|
|
||||||
export default defineEventHandler(async () => {
|
export default defineEventHandler(async () => {
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (
|
||||||
throw createError({ statusCode: 404, statusMessage: 'Not found' })
|
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) => ({
|
return members.map((m) => ({
|
||||||
label: `${m.name} (${m.email})`,
|
label: `${m.name} (${m.email})`,
|
||||||
value: m.email,
|
value: m.email,
|
||||||
circle: m.circle,
|
circle: m.circle,
|
||||||
role: m.role
|
role: m.role,
|
||||||
}))
|
}));
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,45 @@
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from "jsonwebtoken";
|
||||||
import Member from '../../models/member.js'
|
import Member from "../../models/member.js";
|
||||||
import { connectDB } from '../../utils/mongoose.js'
|
import { connectDB } from "../../utils/mongoose.js";
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
// Only allow in development
|
// Only allow in development, unless explicitly enabled for Playwright preview runs
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (
|
||||||
throw createError({ statusCode: 404, statusMessage: 'Not found' })
|
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
|
// 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) {
|
if (!member) {
|
||||||
member = await Member.create({
|
member = await Member.create({
|
||||||
email: 'test-admin@ghostguild.dev',
|
email: "test-admin@ghostguild.dev",
|
||||||
name: 'Test Admin',
|
name: "Test Admin",
|
||||||
circle: 'founder',
|
circle: "founder",
|
||||||
contributionTier: '0',
|
contributionTier: "0",
|
||||||
role: 'admin',
|
role: "admin",
|
||||||
status: 'active',
|
status: "active",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = useRuntimeConfig(event)
|
const config = useRuntimeConfig(event);
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{ memberId: member._id, email: member.email, tv: member.tokenVersion },
|
{ memberId: member._id, email: member.email, tv: member.tokenVersion },
|
||||||
config.jwtSecret,
|
config.jwtSecret,
|
||||||
{ expiresIn: '7d' }
|
{ expiresIn: "7d" },
|
||||||
)
|
);
|
||||||
|
|
||||||
setCookie(event, 'auth-token', token, {
|
setCookie(event, "auth-token", token, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
sameSite: 'lax',
|
sameSite: "lax",
|
||||||
maxAge: 60 * 60 * 24 * 7,
|
maxAge: 60 * 60 * 24 * 7,
|
||||||
})
|
});
|
||||||
|
|
||||||
await sendRedirect(event, '/admin', 302)
|
await sendRedirect(event, "/admin", 302);
|
||||||
})
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue