Merge pull request #2299 from Moamal-2000/improve-accessibility

fix(homepage): resolve missing button labels and incorrect heading order
This commit is contained in:
Amruth Pillai
2025-10-01 11:14:16 +02:00
committed by GitHub
9 changed files with 55 additions and 140 deletions
-81
View File
@@ -1,81 +0,0 @@
# Environment
NODE_ENV=development
# Ports
PORT=3000
# URLs
# These URLs must reference a publicly accessible domain or IP address, not a docker container ID (depending on your compose setup)
PUBLIC_URL=http://localhost:3000
STORAGE_URL=http://localhost:9000/default # default is the bucket name specified in the STORAGE_BUCKET variable
# Database (Prisma/PostgreSQL)
# This can be swapped out to use any other database, like MySQL
# Note: This is used only in the compose.yml file
POSTGRES_PORT=5432
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
# Database (Prisma/PostgreSQL)
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres?schema=public
# Authentication Secrets
# generated with `openssl rand -base64 64`
ACCESS_TOKEN_SECRET=access_token_secret
REFRESH_TOKEN_SECRET=refresh_token_secret
# Chrome Browser (for printing)
# generated with `openssl rand -hex 32`
CHROME_PORT=8080
CHROME_TOKEN=chrome_token
CHROME_URL=ws://localhost:8080
# Launch puppeteer with flag to ignore https errors
# CHROME_IGNORE_HTTPS_ERRORS=true
# Mail Server (for e-mails)
# For testing, you can use https://ethereal.email/create
MAIL_FROM=noreply@localhost
# SMTP_URL=smtp://username:password@smtp.ethereal.email:587
# Storage
STORAGE_ENDPOINT=localhost
STORAGE_PORT=9000
STORAGE_REGION=us-east-1
STORAGE_BUCKET=default
STORAGE_ACCESS_KEY=minioadmin
STORAGE_SECRET_KEY=minioadmin
STORAGE_USE_SSL=false
STORAGE_SKIP_BUCKET_CHECK=false
# Nx Cloud (Optional)
# NX_CLOUD_ACCESS_TOKEN=
# Crowdin (Optional)
# CROWDIN_PROJECT_ID=
# CROWDIN_PERSONAL_TOKEN=
# Feature Flags (Optional)
# DISABLE_SIGNUPS=false
# DISABLE_EMAIL_AUTH=false
# GitHub (OAuth, Optional)
# GITHUB_CLIENT_ID=
# GITHUB_CLIENT_SECRET=
# GITHUB_CALLBACK_URL=http://localhost:5173/api/auth/github/callback
# Google (OAuth, Optional)
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
# GOOGLE_CALLBACK_URL=http://localhost:5173/api/auth/google/callback
# OpenID (Optional)
# VITE_OPENID_NAME=
# OPENID_AUTHORIZATION_URL=
# OPENID_CALLBACK_URL=http://localhost:5173/api/auth/openid/callback
# OPENID_CLIENT_ID=
# OPENID_CLIENT_SECRET=
# OPENID_ISSUER=
# OPENID_SCOPE=openid profile email
# OPENID_TOKEN_URL=
# OPENID_USER_INFO_URL=
+2 -1
View File
@@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { useLingui } from "@lingui/react"; import { useLingui } from "@lingui/react";
import { TranslateIcon } from "@phosphor-icons/react"; import { TranslateIcon } from "@phosphor-icons/react";
import { Button, Popover, PopoverContent, PopoverTrigger } from "@reactive-resume/ui"; import { Button, Popover, PopoverContent, PopoverTrigger } from "@reactive-resume/ui";
@@ -13,7 +14,7 @@ export const LocaleSwitch = () => {
return ( return (
<Popover open={open} onOpenChange={setOpen}> <Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button size="icon" variant="ghost"> <Button size="icon" variant="ghost" aria-label={t`Change Language`}>
<TranslateIcon size={20} /> <TranslateIcon size={20} />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
+4 -3
View File
@@ -1,3 +1,4 @@
import { t } from "@lingui/macro";
import { CloudSunIcon, MoonIcon, SunIcon } from "@phosphor-icons/react"; import { CloudSunIcon, MoonIcon, SunIcon } from "@phosphor-icons/react";
import { useTheme } from "@reactive-resume/hooks"; import { useTheme } from "@reactive-resume/hooks";
import { Button } from "@reactive-resume/ui"; import { Button } from "@reactive-resume/ui";
@@ -25,9 +26,9 @@ export const ThemeSwitch = ({ size = 20, className }: Props) => {
<Button size="icon" variant="ghost" className={className} onClick={toggleTheme}> <Button size="icon" variant="ghost" className={className} onClick={toggleTheme}>
<div className="cursor-pointer overflow-hidden" style={{ width: size, height: size }}> <div className="cursor-pointer overflow-hidden" style={{ width: size, height: size }}>
<motion.div animate={theme} variants={variants} className="flex"> <motion.div animate={theme} variants={variants} className="flex">
<SunIcon size={size} className="shrink-0" /> <SunIcon size={size} className="shrink-0" aria-label={t`Switch to Light Mode`} />
<CloudSunIcon size={size} className="shrink-0" /> <CloudSunIcon size={size} className="shrink-0" aria-label={t`Use System Theme`} />
<MoonIcon size={size} className="shrink-0" /> <MoonIcon size={size} className="shrink-0" aria-label={t`Switch to Dark Mode`} />
</motion.div> </motion.div>
</div> </div>
</Button> </Button>
@@ -4,6 +4,7 @@ import { FloppyDiskIcon, TrashSimpleIcon } from "@phosphor-icons/react";
import { import {
Alert, Alert,
Button, Button,
Checkbox,
Form, Form,
FormControl, FormControl,
FormField, FormField,
@@ -11,12 +12,15 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
Input, Input,
Checkbox,
} from "@reactive-resume/ui"; } from "@reactive-resume/ui";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { DEFAULT_MAX_TOKENS, DEFAULT_MODEL, DEFAULT_AZURE_API_VERSION } from "@/client/constants/llm"; import {
DEFAULT_AZURE_API_VERSION,
DEFAULT_MAX_TOKENS,
DEFAULT_MODEL,
} from "@/client/constants/llm";
import { useOpenAiStore } from "@/client/stores/openai"; import { useOpenAiStore } from "@/client/stores/openai";
const formSchema = z.object({ const formSchema = z.object({
@@ -41,12 +45,18 @@ type FormValues = z.infer<typeof formSchema>;
export const OpenAISettings = () => { export const OpenAISettings = () => {
const { const {
apiKey, setApiKey, apiKey,
baseURL, setBaseURL, setApiKey,
model, setModel, baseURL,
maxTokens, setMaxTokens, setBaseURL,
isAzure, setIsAzure, model,
azureApiVersion, setAzureApiVersion setModel,
maxTokens,
setMaxTokens,
isAzure,
setIsAzure,
azureApiVersion,
setAzureApiVersion,
} = useOpenAiStore(); } = useOpenAiStore();
const isEnabled = !!apiKey; const isEnabled = !!apiKey;
@@ -58,12 +68,19 @@ export const OpenAISettings = () => {
baseURL: baseURL ?? "", baseURL: baseURL ?? "",
model: model ?? DEFAULT_MODEL, model: model ?? DEFAULT_MODEL,
maxTokens: maxTokens ?? DEFAULT_MAX_TOKENS, maxTokens: maxTokens ?? DEFAULT_MAX_TOKENS,
isAzure: isAzure ?? false, isAzure,
azureApiVersion: azureApiVersion ?? DEFAULT_AZURE_API_VERSION, azureApiVersion: azureApiVersion ?? DEFAULT_AZURE_API_VERSION,
}, },
}); });
const onSubmit = ({ apiKey, baseURL, model, maxTokens, isAzure, azureApiVersion }: FormValues) => { const onSubmit = ({
apiKey,
baseURL,
model,
maxTokens,
isAzure,
azureApiVersion,
}: FormValues) => {
setApiKey(apiKey); setApiKey(apiKey);
setIsAzure(isAzure); setIsAzure(isAzure);
if (baseURL) { if (baseURL) {
@@ -93,7 +110,7 @@ export const OpenAISettings = () => {
model: DEFAULT_MODEL, model: DEFAULT_MODEL,
maxTokens: DEFAULT_MAX_TOKENS, maxTokens: DEFAULT_MAX_TOKENS,
isAzure: false, isAzure: false,
azureApiVersion: DEFAULT_AZURE_API_VERSION azureApiVersion: DEFAULT_AZURE_API_VERSION,
}); });
}; };
@@ -125,10 +142,10 @@ export const OpenAISettings = () => {
<p> <p>
<Trans> <Trans>
You can also integrate with Azure OpenAI by enabling the "Use Azure OpenAI" checkbox You can also integrate with Azure OpenAI by enabling the <code>Use Azure OpenAI</code>{" "}
and setting the Resource URL to your Azure OpenAI resource (e.g., checkbox and setting the Resource URL to your Azure OpenAI resource:{" "}
<code>https://your-resource.openai.azure.com</code>). Set the deployment name in the Model field <code>https://your-resource.openai.azure.com</code>. Set the deployment name in the
and specify the appropriate API version for your Azure deployment. Model field and specify the appropriate API version for your Azure deployment.
</Trans> </Trans>
</p> </p>
@@ -136,8 +153,8 @@ export const OpenAISettings = () => {
<Trans> <Trans>
You can also integrate with Ollama simply by setting the API key to You can also integrate with Ollama simply by setting the API key to
<code>sk-1234567890abcdef</code> and the Base URL to your Ollama URL, i.e. <code>sk-1234567890abcdef</code> and the Base URL to your Ollama URL, i.e.
<code>http://localhost:11434/v1</code>. You can also pick and choose models and set the max tokens <code>http://localhost:11434/v1</code>. You can also pick and choose models and set the
as per your preference. max tokens as per your preference.
</Trans> </Trans>
</p> </p>
</div> </div>
@@ -163,10 +180,7 @@ export const OpenAISettings = () => {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
{form.watch("isAzure") {form.watch("isAzure") ? t`Azure OpenAI Resource URL` : t`Base URL`}
? t`Azure OpenAI Resource URL`
: t`Base URL`
}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
@@ -188,12 +202,7 @@ export const OpenAISettings = () => {
control={form.control} control={form.control}
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>{form.watch("isAzure") ? t`Deployment Name` : t`Model`}</FormLabel>
{form.watch("isAzure")
? t`Deployment Name`
: t`Model`
}
</FormLabel>
<FormControl> <FormControl>
<Input type="text" placeholder={DEFAULT_MODEL} {...field} /> <Input type="text" placeholder={DEFAULT_MODEL} {...field} />
</FormControl> </FormControl>
@@ -228,7 +237,7 @@ export const OpenAISettings = () => {
<FormItem className="flex flex-row items-center space-x-3 space-y-0"> <FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl> <FormControl>
<Checkbox <Checkbox
checked={!!field.value} checked={field.value}
onCheckedChange={(value) => { onCheckedChange={(value) => {
field.onChange(Boolean(value)); field.onChange(Boolean(value));
}} }}
@@ -118,7 +118,7 @@ export const FeaturesSection = () => {
whileInView={{ opacity: 1, x: 0, transition: { delay: index * 0.1 } }} whileInView={{ opacity: 1, x: 0, transition: { delay: index * 0.1 } }}
> >
{feature.icon} {feature.icon}
<h4>{feature.title}</h4> <h3>{feature.title}</h3>
</motion.div> </motion.div>
))} ))}
+8 -23
View File
@@ -13,40 +13,25 @@ export const openai = () => {
} }
if (isAzure) { if (isAzure) {
if (!baseURL) { if (!baseURL || !model || !azureApiVersion) {
throw new Error(t`Azure OpenAI Base URL is required when using Azure OpenAI.`); throw new Error(
t`Azure OpenAI Base URL, deployment name (model), and API version are required when using Azure OpenAI.`,
);
} }
if (!model) { const azureBaseURL = baseURL.replace(/\/$/, "");
throw new Error(t`Azure OpenAI deployment name (model) is required when using Azure OpenAI.`);
}
if (!azureApiVersion) {
throw new Error(t`Azure OpenAI API version is required when using Azure OpenAI.`);
}
// Construct Azure OpenAI URL: https://your-resource.openai.azure.com/openai/deployments/your-deployment
const azureBaseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
const constructedURL = `${azureBaseURL}/openai/deployments/${model}`;
return new OpenAI({ return new OpenAI({
apiKey, apiKey,
baseURL: constructedURL, baseURL: `${azureBaseURL}/openai/deployments/${model}`,
defaultQuery: { "api-version": azureApiVersion ?? undefined }, defaultQuery: { "api-version": azureApiVersion },
dangerouslyAllowBrowser: true,
});
}
if (baseURL) {
return new OpenAI({
apiKey,
baseURL,
dangerouslyAllowBrowser: true, dangerouslyAllowBrowser: true,
}); });
} }
return new OpenAI({ return new OpenAI({
apiKey, apiKey,
baseURL,
dangerouslyAllowBrowser: true, dangerouslyAllowBrowser: true,
}); });
}; };
+1 -1
View File
@@ -1,7 +1,7 @@
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
import { DEFAULT_MAX_TOKENS, DEFAULT_MODEL, DEFAULT_AZURE_API_VERSION } from "../constants/llm"; import { DEFAULT_AZURE_API_VERSION, DEFAULT_MAX_TOKENS, DEFAULT_MODEL } from "../constants/llm";
type OpenAIStore = { type OpenAIStore = {
baseURL: string | null; baseURL: string | null;
+1 -1
View File
@@ -145,7 +145,7 @@ export class PrinterService {
Promise.all( Promise.all(
// eslint-disable-next-line unicorn/prefer-spread // eslint-disable-next-line unicorn/prefer-spread
Array.from(document.images).map((img) => { Array.from(document.images).map((img) => {
if (img.complete) return; if (img.complete) return Promise.resolve();
return new Promise((resolve) => { return new Promise((resolve) => {
// eslint-disable-next-line unicorn/prefer-add-event-listener // eslint-disable-next-line unicorn/prefer-add-event-listener
img.onload = img.onerror = resolve; img.onload = img.onerror = resolve;
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@reactive-resume/source", "name": "@reactive-resume/source",
"description": "A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.", "description": "A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.",
"version": "4.4.7", "version": "4.4.8",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"packageManager": "pnpm@10.17.1+sha512.17c560fca4867ae9473a3899ad84a88334914f379be46d455cbf92e5cf4b39d34985d452d2583baf19967fa76cb5c17bc9e245529d0b98745721aa7200ecaf7a", "packageManager": "pnpm@10.17.1+sha512.17c560fca4867ae9473a3899ad84a88334914f379be46d455cbf92e5cf4b39d34985d452d2583baf19967fa76cb5c17bc9e245529d0b98745721aa7200ecaf7a",