Skip to content

Commit ab51bc3

Browse files
committed
Tweak proposal modal, and LocaleSelect-or (#8204)
1 parent 3192305 commit ab51bc3

5 files changed

Lines changed: 122 additions & 63 deletions

File tree

app/helpers/react_components/localization/glossary_entries_list.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ def to_s
1010
glossary_entries:,
1111
links: {
1212
localization_glossary_entries_path: Exercism::Routes.localization_glossary_entries_path,
13-
endpoint: Exercism::Routes.api_localization_glossary_entries_path
13+
endpoint: Exercism::Routes.api_localization_glossary_entries_path,
14+
create_glossary_entry: Exercism::Routes.api_localization_glossary_entries_path
1415
},
1516
request: glossary_entries_list_request,
1617
translation_locales: current_user.data.translator_locales

app/javascript/components/localization/glossary-entries/list/GlossaryEntriesTableListElement.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function TranslationsWithStatus({
4747
status,
4848
}: {
4949
locale: string
50-
status: 'unchecked' | 'approved' | 'rejected'
50+
status: 'unchecked' | 'approved' | 'rejected' | 'proposed'
5151
}) {
5252
return (
5353
<div className="translations-statuses">

app/javascript/components/localization/glossary-entries/list/LocaleSelect.tsx

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,34 @@ const LocaleOption = ({
3030
)
3131
}
3232

33-
export function LocaleSelect() {
33+
export function LocaleSelect({
34+
locales,
35+
value,
36+
onChange,
37+
showAll = true,
38+
label = 'Open the locale filter',
39+
}: {
40+
locales?: string[]
41+
value?: string
42+
onChange?: (locale: string) => void
43+
showAll?: boolean
44+
label?: string
45+
} = {}) {
3446
// ["hu", "de"] etc
35-
const { translationLocales } = useContext(GlossaryEntriesListContext)
36-
const [selectedLocale, setSelectedLocale] = useState<string>('')
37-
const { request, setQuery } = React.useContext(GlossaryEntriesListContext)
47+
const context = useContext(GlossaryEntriesListContext)
48+
const { translationLocales } = context || {}
49+
const [internalSelectedLocale, setInternalSelectedLocale] =
50+
useState<string>('')
51+
const { request, setQuery } = context || {}
52+
53+
// Use external props or fallback to internal state and context
54+
const availableLocales = locales || translationLocales || []
55+
const selectedLocale = value !== undefined ? value : internalSelectedLocale
56+
const handleLocaleChange = onChange || setInternalSelectedLocale
57+
58+
const dropdownLength = showAll
59+
? availableLocales.length + 1
60+
: availableLocales.length
3861

3962
const {
4063
buttonAttributes,
@@ -43,7 +66,7 @@ export function LocaleSelect() {
4366
itemAttributes,
4467
setOpen,
4568
open,
46-
} = useDropdown(translationLocales.length + 1, (i) => handleItemSelect(i), {
69+
} = useDropdown(dropdownLength, (i) => handleItemSelect(i), {
4770
placement: 'bottom',
4871
modifiers: [
4972
{
@@ -56,25 +79,29 @@ export function LocaleSelect() {
5679
})
5780

5881
useEffect(() => {
59-
setQuery({ ...request.query, locale: selectedLocale || undefined })
60-
}, [selectedLocale])
82+
// Only update query when used in context mode (not modal mode)
83+
if (setQuery && request && onChange === undefined) {
84+
setQuery({ ...request.query, locale: selectedLocale || undefined })
85+
}
86+
}, [selectedLocale, setQuery, request, onChange])
6187

6288
const handleItemSelect = useCallback(
6389
(index: number) => {
64-
if (index === 0) {
65-
setSelectedLocale('')
90+
if (showAll && index === 0) {
91+
handleLocaleChange('')
6692
} else {
67-
const locale = translationLocales[index - 1]
93+
const localeIndex = showAll ? index - 1 : index
94+
const locale = availableLocales[localeIndex]
6895
if (locale) {
69-
setSelectedLocale(locale)
96+
handleLocaleChange(locale)
7097
}
7198
}
7299
setOpen(false)
73100
},
74-
[translationLocales, setOpen]
101+
[availableLocales, setOpen, handleLocaleChange, showAll]
75102
)
76103

77-
if (!translationLocales || translationLocales.length === 0) {
104+
if (!availableLocales || availableLocales.length === 0) {
78105
return null
79106
}
80107

@@ -83,11 +110,15 @@ export function LocaleSelect() {
83110
<button
84111
className="current-track gap-8"
85112
style={{ minWidth: '200px' }}
86-
aria-label="Open the locale filter"
113+
aria-label={label}
87114
{...buttonAttributes}
88115
>
89116
<div className="track-title">
90-
{selectedLocale ? nameForLocale(selectedLocale) : 'All'}
117+
{selectedLocale
118+
? nameForLocale(selectedLocale)
119+
: showAll
120+
? 'All'
121+
: 'Select locale'}
91122
</div>
92123
{selectedLocale && (
93124
<span className="flag">{flagForLocale(selectedLocale)}</span>
@@ -101,29 +132,32 @@ export function LocaleSelect() {
101132
{open ? (
102133
<div {...panelAttributes} className="--options">
103134
<ul {...listAttributes}>
104-
<li key="all" {...itemAttributes(0)}>
105-
<label className="c-radio-wrapper">
106-
<input
107-
type="radio"
108-
onChange={() => {
109-
setSelectedLocale('')
110-
setOpen(false)
111-
}}
112-
checked={selectedLocale === ''}
113-
name="locale_filter"
114-
/>
115-
<div className="row gap-8">
116-
<div className="title">All</div>
117-
</div>
118-
</label>
119-
</li>
120-
{translationLocales.map((locale, i) => {
135+
{showAll && (
136+
<li key="all" {...itemAttributes(0)}>
137+
<label className="c-radio-wrapper">
138+
<input
139+
type="radio"
140+
onChange={() => {
141+
handleLocaleChange('')
142+
setOpen(false)
143+
}}
144+
checked={selectedLocale === ''}
145+
name="locale_filter"
146+
/>
147+
<div className="row gap-8">
148+
<div className="title">All</div>
149+
</div>
150+
</label>
151+
</li>
152+
)}
153+
{availableLocales.map((locale, i) => {
154+
const itemIndex = showAll ? i + 1 : i
121155
return (
122-
<li key={locale} {...itemAttributes(i + 1)}>
156+
<li key={locale} {...itemAttributes(itemIndex)}>
123157
<LocaleOption
124158
locale={locale}
125159
onChange={() => {
126-
setSelectedLocale(locale)
160+
handleLocaleChange(locale)
127161
setOpen(false)
128162
}}
129163
checked={selectedLocale === locale}

app/javascript/components/localization/glossary-entries/list/Table.tsx

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { SearchInput } from '@/components/common'
22
import { useDebounce } from '@uidotdev/usehooks'
3-
import React, { useCallback, useEffect, useState } from 'react'
3+
import React, { useCallback, useContext, useEffect, useState } from 'react'
44
import { GlossaryEntriesListContext } from '.'
55
import { GlossaryEntriesTableList } from './GlossaryEntriesTableList'
66
import { Tabs } from './Tabs'
@@ -72,14 +72,24 @@ function ProposeTermModal({
7272
isOpen: boolean
7373
onClose: () => void
7474
}) {
75+
const { links, translationLocales } = useContext(GlossaryEntriesListContext)
7576
const [term, setTerm] = useState<string>('')
7677
const [description, setDescription] = useState<string>('')
78+
const [locale, setLocale] = useState<string>(translationLocales[0])
79+
const [translation, setTranslation] = useState<string>('')
7780

7881
const onSave = useCallback(() => {
7982
const fetch = sendRequest({
80-
endpoint: '',
83+
endpoint: links.createGlossaryEntry,
8184
method: 'POST',
82-
body: JSON.stringify({ term, description }),
85+
body: JSON.stringify({
86+
glossary_entry: {
87+
term,
88+
llm_instructions: description,
89+
locale,
90+
translation,
91+
},
92+
}),
8393
})
8494

8595
fetch.fetch
@@ -91,7 +101,7 @@ function ProposeTermModal({
91101
toast.error('Failed to propose a new term.', e)
92102
console.error(e)
93103
})
94-
}, [term, description])
104+
}, [term, description, locale, translation])
95105

96106
return (
97107
<Modal open={isOpen} onClose={onClose}>
@@ -111,7 +121,6 @@ function ProposeTermModal({
111121
placeholder="Enter term"
112122
/>
113123

114-
{/* GENERATE NOTE ABOUT WHAT A GLOSSARY TERM IS*/}
115124
<p className="text-p text-textColor6 leading-130">
116125
A glossary term is a word or phrase that has a specific meaning
117126
within a particular context or field.
@@ -132,13 +141,44 @@ function ProposeTermModal({
132141
className="w-full border border-gray-300 rounded px-12 py-8 mb-8"
133142
placeholder="Enter description"
134143
></textarea>
135-
{/* GENERATE NOTE ABOUT WHAT A GLOSSARY TERM DESCRIPTION IS*/}
136144
<p className="text-p text-textColor6 leading-130">
137145
A glossary term description provides additional context or
138146
explanation about the term, helping users understand its usage and
139147
significance.
140148
</p>
141149
</div>
150+
<div>
151+
<label className="block font-semibold mb-4 text-h6">Locale</label>
152+
<LocaleSelect
153+
locales={[...(translationLocales || [])]}
154+
value={locale}
155+
onChange={setLocale}
156+
showAll={false}
157+
label="Select locale"
158+
/>
159+
<p className="text-p text-textColor6 leading-130 mt-8">
160+
Select the language locale for this glossary term.
161+
</p>
162+
</div>
163+
<div>
164+
<label
165+
className="block font-semibold mb-4 text-h6"
166+
htmlFor="translation"
167+
>
168+
Translation
169+
</label>
170+
<input
171+
type="text"
172+
id="translation"
173+
value={translation}
174+
onChange={(e) => setTranslation(e.target.value)}
175+
className="w-full border border-gray-300 rounded px-12 py-8 mb-8"
176+
placeholder="Enter translation"
177+
/>
178+
<p className="text-p text-textColor6 leading-130">
179+
Provide the translated term or phrase in the selected locale.
180+
</p>
181+
</div>
142182
<div className="flex items-center gap-8">
143183
<button
144184
onClick={onSave}

app/javascript/components/localization/glossary-entries/types.d.ts

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,29 +55,13 @@ type GlossaryEntriesListProps = {
5555
initialData: GlossaryEntriesListData
5656
}
5757
}
58-
links?: { localizationGlossaryEntriesPath: string; endpoint: string }
58+
links: {
59+
localizationGlossaryEntriesPath: string
60+
endpoint: string
61+
createGlossaryEntry: string
62+
}
5963
}
6064

61-
// {
62-
// "glossaryEntry": {
63-
// "uuid": "f1ebf3cd-c7f0-4c89-bb67-aa90b0cc50c1",
64-
// "locale": "de",
65-
// "term": "subscription",
66-
// "translation": "Abonnement",
67-
// "status": "unchecked",
68-
// "llmInstructions": "A recurring monthly donation to Exercism. Use the term for subscription or recurring payment",
69-
// "proposals": []
70-
// },
71-
// "currentUserId": 1530,
72-
// "links": {
73-
// "glossaryEntriesListPage": "http://local.exercism.io:3020/localization/glossary_entries",
74-
// "approveLlmTranslation": "http://local.exercism.io:3020/api/v2/localization/translations/f1ebf3cd-c7f0-4c89-bb67-aa90b0cc50c1/approve_llm_version",
75-
// "createProposal": "http://local.exercism.io:3020/api/v2/localization/glossary_entries/GLOSSARY_ENTRY_ID/proposals?id=ID",
76-
// "approveProposal": "http://local.exercism.io:3020/api/v2/localization/glossary_entries/GLOSSARY_ENTRY_ID/proposals/ID/approve",
77-
// "rejectProposal": "http://local.exercism.io:3020/api/v2/localization/glossary_entries/GLOSSARY_ENTRY_ID/proposals/ID/reject",
78-
// "updateProposal": "http://local.exercism.io:3020/api/v2/localization/glossary_entries/GLOSSARY_ENTRY_ID/proposals/ID"
79-
// }
80-
// }
8165
type GlossaryEntriesShowProps = {
8266
glossaryEntry: GlossaryEntry
8367
currentUserId: number

0 commit comments

Comments
 (0)