Skip to content

Commit

Permalink
Support localize scope desc for user app consent screen (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
byn9826 authored Aug 11, 2024
1 parent a932a8d commit d447c70
Show file tree
Hide file tree
Showing 23 changed files with 523 additions and 44 deletions.
51 changes: 51 additions & 0 deletions admin-panel/app/[lang]/scopes/LocaleEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { TextInput } from 'flowbite-react'
import { useTranslations } from 'next-intl'

const LocaleEditor = ({
supportedLocales,
values,
onChange,
}: {
supportedLocales: string[];
}) => {
const t = useTranslations()

const handleSetLocale = (
targetLocale: string, val: string,
) => {
const isUpdate = values.find((value) => value.locale === targetLocale)
const newLocales = isUpdate
? values.map((value) => value.locale === targetLocale
? ({
locale: targetLocale, value: val,
})
: value)
: [...values, {
locale: targetLocale, value: val,
}]
onChange(newLocales)
}

return (
<section className='flex flex-col gap-4'>
<p>* {t('scopes.localeNote')}</p>
{supportedLocales.map((locale) => (
<section
key={locale}
className='flex items-center gap-4'>
<p>{locale.toUpperCase()}</p>
<TextInput
className='w-full'
onChange={(e) => handleSetLocale(
locale,
e.target.value,
)}
value={values.find((value) => value.locale === locale)?.value ?? ''}
/>
</section>
))}
</section>
)
}

export default LocaleEditor
83 changes: 74 additions & 9 deletions admin-panel/app/[lang]/scopes/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { useTranslations } from 'next-intl'
import { useParams } from 'next/navigation'
import {
useCallback,
useEffect, useState,
useEffect, useMemo, useState,
} from 'react'
import useEditScope from '../useEditScope'
import LocaleEditor from '../LocaleEditor'
import {
dataTool,
proxyTool, routeTool,
} from 'tools'
import SaveButton from 'components/SaveButton'
Expand All @@ -22,6 +24,8 @@ import ClientTypeLabel from 'components/ClientTypeLabel'
import PageTitle from 'components/PageTitle'
import DeleteButton from 'components/DeleteButton'
import useLocaleRouter from 'hooks/useLocaleRoute'
import useSignalValue from 'app/useSignalValue'
import { configSignal } from 'signals'

const Page = () => {
const { id } = useParams()
Expand All @@ -31,13 +35,49 @@ const Page = () => {

const [scope, setScope] = useState()
const { acquireToken } = useAuth()
const configs = useSignalValue(configSignal)

const isSystem = useMemo(
() => dataTool.isSystem(scope?.name),
[scope],
)

const {
values, errors, onChange,
} = useEditScope(scope)
const [isLoading, setIsLoading] = useState(false)
const [showErrors, setShowErrors] = useState(false)

const hasDifferentLocales = useMemo(
() => {
if (values.locales !== undefined && scope?.locales === undefined) return true
if (Array.isArray(values.locales) && Array.isArray(scope?.locales)) {
if (values.locales.length !== scope.locales.length) return true
if (values.locales.find((valueLocale) => {
return scope.locales.every((scopeLocale) => {
return scopeLocale.locale !== valueLocale.locale || scopeLocale.value !== valueLocale.value
})
})) return true
}
return false
},
[values, scope],
)

const hasDifferentName = useMemo(
() => values.name && values.name !== scope?.name,
[values, scope],
)
const hasDifferentNote = useMemo(
() => values.note !== scope?.note,
[values, scope],
)

const canUpdate = useMemo(
() => hasDifferentName || hasDifferentNote || hasDifferentLocales,
[hasDifferentName, hasDifferentNote, hasDifferentLocales],
)

const handleSave = async () => {
if (Object.values(errors).some((val) => !!val)) {
setShowErrors(true)
Expand All @@ -50,7 +90,13 @@ const Page = () => {
endpoint: `/api/scopes/${id}`,
method: 'PUT',
token,
body: { data: values },
body: {
data: {
name: hasDifferentName ? values.name : undefined,
note: hasDifferentNote ? values.note : undefined,
locales: hasDifferentLocales ? values.locales : undefined,
},
},
})
setIsLoading(false)
if (res?.scope) {
Expand Down Expand Up @@ -108,13 +154,17 @@ const Page = () => {
<Table.Row>
<Table.Cell>{t('scopes.name')}</Table.Cell>
<Table.Cell>
<TextInput
onChange={(e) => onChange(
'name',
e.target.value,
{isSystem
? values.name
: (
<TextInput
onChange={(e) => onChange(
'name',
e.target.value,
)}
value={values.name}
/>
)}
value={values.name}
/>
{showErrors && <FieldError error={errors.name} />}
</Table.Cell>
</Table.Row>
Expand All @@ -136,6 +186,21 @@ const Page = () => {
<ClientTypeLabel type={scope.type} />
</Table.Cell>
</Table.Row>
{configs.ENABLE_USER_APP_CONSENT && scope.type === 'spa' && (
<Table.Row>
<Table.Cell>{t('scopes.locales')}</Table.Cell>
<Table.Cell>
<LocaleEditor
supportedLocales={configs.SUPPORTED_LOCALES}
values={values.locales ?? []}
onChange={(locales) => onChange(
'locales',
locales,
)}
/>
</Table.Cell>
</Table.Row>
)}
<Table.Row>
<Table.Cell>{t('common.createdAt')}</Table.Cell>
<Table.Cell>{scope.createdAt} UTC</Table.Cell>
Expand All @@ -151,7 +216,7 @@ const Page = () => {
<section className='flex items-center gap-4 mt-8'>
<SaveButton
isLoading={isLoading}
disabled={!values.name || (values.name === scope.name && values.note === scope.note)}
disabled={!canUpdate}
onClick={handleSave}
/>
<DeleteButton
Expand Down
35 changes: 31 additions & 4 deletions admin-panel/app/[lang]/scopes/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useTranslations } from 'next-intl'
import { useState } from 'react'
import { useAuth } from '@melody-auth/react'
import useEditScope from '../useEditScope'
import LocaleEditor from '../LocaleEditor'
import {
proxyTool, routeTool,
} from 'tools'
Expand All @@ -17,6 +18,8 @@ import useLocaleRouter from 'hooks/useLocaleRoute'
import FieldError from 'components/FieldError'
import ClientTypeSelector from 'components/ClientTypeSelector'
import SubmitError from 'components/SubmitError'
import useSignalValue from 'app/useSignalValue'
import { configSignal } from 'signals'

const Page = () => {
const t = useTranslations()
Expand All @@ -28,6 +31,18 @@ const Page = () => {
} = useEditScope()
const [showErrors, setShowErrors] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const configs = useSignalValue(configSignal)

const handleUpdateType = (val: string) => {
onChange(
'type',
val,
)
onChange(
'locales',
undefined,
)
}

const handleSubmit = async () => {
if (Object.values(errors).some((val) => !!val)) {
Expand Down Expand Up @@ -91,14 +106,26 @@ const Page = () => {
<Table.Cell>
<ClientTypeSelector
value={values.type}
onChange={(val: string) => onChange(
'type',
val,
)}
onChange={handleUpdateType}
/>
{showErrors && <FieldError error={errors.type} />}
</Table.Cell>
</Table.Row>
{configs.ENABLE_USER_APP_CONSENT && values.type === 'spa' && (
<Table.Row>
<Table.Cell>{t('scopes.locales')}</Table.Cell>
<Table.Cell>
<LocaleEditor
supportedLocales={configs.SUPPORTED_LOCALES}
values={values.locales ?? []}
onChange={(locales) => onChange(
'locales',
locales,
)}
/>
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
</section>
Expand Down
15 changes: 6 additions & 9 deletions admin-panel/app/[lang]/scopes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ import {
} from 'react'
import useCurrentLocale from 'hooks/useCurrentLocale'
import {
proxyTool, routeTool, typeTool,
dataTool,
proxyTool, routeTool,
} from 'tools'
import EditLink from 'components/EditLink'
import SystemLabel from 'components/SystemLabel'
import PageTitle from 'components/PageTitle'
import CreateButton from 'components/CreateButton'
import ClientTypeLabel from 'components/ClientTypeLabel'

const isSystem = (name: string) => Object.values(typeTool.Scope).some((scope) => scope === name)

const Page = () => {
const t = useTranslations()
const locale = useCurrentLocale()
Expand Down Expand Up @@ -59,7 +58,7 @@ const Page = () => {
<Table.Cell>
<div className='flex items-center gap-2'>
{scope.name}
{isSystem(scope.name) && <SystemLabel />}
{dataTool.isSystem(scope.name) && <SystemLabel />}
</div>
</Table.Cell>
<Table.Cell>
Expand All @@ -69,11 +68,9 @@ const Page = () => {
<ClientTypeLabel type={scope.type} />
</Table.Cell>
<Table.Cell>
{!isSystem(scope.name) && (
<EditLink
href={`/${locale}/scopes/${scope.id}`}
/>
)}
<EditLink
href={`/${locale}/scopes/${scope.id}`}
/>
</Table.Cell>
</Table.Row>
))}
Expand Down
9 changes: 7 additions & 2 deletions admin-panel/app/[lang]/scopes/useEditScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,23 @@ const useEditScope = (scope) => {
const [name, setName] = useState('')
const [type, setType] = useState('')
const [note, setNote] = useState('')
const [locales, setLocales] = useState(undefined)

useEffect(
() => {
setName(scope?.name ?? '')
setType(scope?.type ?? '')
setNote(scope?.note ?? '')
setLocales(scope?.locales ?? undefined)
},
[scope],
)

const values = useMemo(
() => ({
name, type, note,
name, type, note, locales,
}),
[name, type, note],
[name, type, note, locales],
)

const errors = useMemo(
Expand All @@ -48,6 +50,9 @@ const useEditScope = (scope) => {
case 'note':
setNote(value as string)
break
case 'locales':
setLocales(value)
break
}
}

Expand Down
3 changes: 3 additions & 0 deletions admin-panel/tools/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { typeTool } from 'tools'

export const isSystem = (name: string) => Object.values(typeTool.Scope).some((scope) => scope === name)
1 change: 1 addition & 0 deletions admin-panel/tools/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * as proxyTool from 'tools/proxy'
export * as routeTool from 'tools/route'
export * as typeTool from 'tools/type'
export * as dataTool from 'tools/data'
4 changes: 3 additions & 1 deletion admin-panel/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@
"name": "Name",
"status": "Status",
"type": "Client Type",
"new": "Create a scope"
"new": "Create a scope",
"locales": "Locales",
"localeNote": "This will be displayed on the user app consent page."
}
}
13 changes: 13 additions & 0 deletions server/migrations/0010_create_scope_locale_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE TABLE [scope_locale] (
"id" integer PRIMARY KEY,
"scopeId" integer NOT NULL,
"locale" text NOT NULL,
"value" text NOT NULL DEFAULT "",
"createdAt" text DEFAULT CURRENT_TIMESTAMP,
"updatedAt" text DEFAULT CURRENT_TIMESTAMP,
"deletedAt" text DEFAULT null,
FOREIGN KEY(scopeId) REFERENCES scope(id)
);
CREATE UNIQUE INDEX idx_unique_scope_locale ON scope_locale (scopeId, locale) WHERE deletedAt IS NULL;
INSERT INTO scope_locale ("scopeId", "locale", "value") values (2, "en", "Access your basic profile information");
INSERT INTO scope_locale ("scopeId", "locale", "value") values (2, "fr", "Accéder à vos informations de profil de base");
1 change: 1 addition & 0 deletions server/src/configs/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum TableName {
UserRole = 'user_role',
Scope = 'scope',
AppScope = 'app_scope',
ScopeLocale = 'scope_locale',
}

export enum BaseKVKey {
Expand Down
Loading

0 comments on commit d447c70

Please sign in to comment.