diff --git a/audiences-react/docs/CHANGELOG.md b/audiences-react/docs/CHANGELOG.md index 72c5397b..a5a3fd92 100644 --- a/audiences-react/docs/CHANGELOG.md +++ b/audiences-react/docs/CHANGELOG.md @@ -1,5 +1,19 @@ # Unreleased +# Version 1.2.1 (2024-08-06) + +- Add error handling to audiences form [#372](https://github.com/powerhome/audiences/pull/372) + +# Version 1.2.0 (2024-07-18) + +- Fix involuntary form reset by [#355](https://github.com/powerhome/audiences/pull/355) +- Update dependency @types/node to v20.14.5 [#343](https://github.com/powerhome/audiences/pull/343) +- Update dependency vite to v5.3.1 by [#340](https://github.com/powerhome/audiences/pull/340) +- Update typescript-eslint monorepo to v7.13.1 [#339](https://github.com/powerhome/audiences/pull/339) +- Update dependency prettier to v3.3.2 by [#334](https://github.com/powerhome/audiences/pull/334) +- Update dependency @types/lodash to v4.17.5 [#335](https://github.com/powerhome/audiences/pull/335) +- Update dependency @vitejs/plugin-react to v4.3 [#337](https://github.com/powerhome/audiences/pull/337) + # Version 1.1.0 (2024-06-10) - Update UX [#329](https://github.com/powerhome/audiences/pull/329) diff --git a/audiences-react/package.json b/audiences-react/package.json index 92ec45b9..20b24e6e 100644 --- a/audiences-react/package.json +++ b/audiences-react/package.json @@ -1,6 +1,6 @@ { "name": "audiences", - "version": "1.2.0", + "version": "1.2.1", "description": "Audiences SCIM client", "files": [ "dist/*.*", diff --git a/audiences-react/src/AudienceForm/index.tsx b/audiences-react/src/AudienceForm/index.tsx index 3663c6b8..bd1740fe 100644 --- a/audiences-react/src/AudienceForm/index.tsx +++ b/audiences-react/src/AudienceForm/index.tsx @@ -1,5 +1,5 @@ import { useState } from "react" -import { Button, Flex } from "playbook-ui" +import { FixedConfirmationToast, Button, Flex } from "playbook-ui" import { GroupCriterion, ScimObject } from "../types" import { toSentence } from "./toSentence" @@ -29,6 +29,7 @@ export const AudienceForm = ({ saving, fetchUsers, save, + error, value: context, isDirty, change, @@ -75,6 +76,9 @@ export const AudienceForm = ({ isDirty={isDirty()} onToggle={(all: boolean) => change("match_all", all)} > + {error && ( + + )} {allowIndividuals && !context.match_all && ( (data, { @@ -74,7 +75,11 @@ export function useAudiences(uri: string): UseAudienceContext { async function save() { const updatedContext = await put(criteriaForm.value) - criteriaForm.reset(updatedContext) + if (response.ok) { + criteriaForm.reset(updatedContext) + } else { + criteriaForm.setError("Unhandled server error") + } } return { diff --git a/audiences-react/src/useFormReducer.ts b/audiences-react/src/useFormReducer.ts index 9099eb7b..c1f9500c 100644 --- a/audiences-react/src/useFormReducer.ts +++ b/audiences-react/src/useFormReducer.ts @@ -5,17 +5,30 @@ import { useState, useReducer, useCallback } from "react" export interface RegistryAction { type: string } -type ResetAction = RegistryAction & { value: T } +type ErrorAction = RegistryAction & { message: string } +type ResetAction = RegistryAction & { state: FormState } type ChangeAction = RegistryAction & { name: string; value: any } // eslint-disable-line @typescript-eslint/no-explicit-any -type ReducerAction = (value: T, action: RegistryAction) => T -type ReducerRegistry = Record> +type ReducerAction = ( + value: FormState, + action: RegistryAction, +) => FormState +type NestedReducerAction = (value: T, action: RegistryAction) => T +type NestedReducerRegistry = Record> +export type FormState = { + error?: string | undefined + value: T +} const DefaultFormReducers = { - change(value: T, action: ChangeAction): T { - return set(action.name, action.value, value as object) as T + change(state: FormState, action: ChangeAction): FormState { + const value = set(action.name, action.value, state.value as object) as T + return { ...state, value } }, - reset(_: T, action: ResetAction) { - return action.value + reset(_: FormState, action: ResetAction): FormState { + return action.state + }, + error(state: FormState, action: ErrorAction): FormState { + return { ...state, error: action.message } }, } @@ -24,63 +37,70 @@ const form = { change(name: string, value: any): ChangeAction { return { type: "change", name, value } }, - reset(value: T): ResetAction { - return { type: "reset", value } + reset(state: FormState): ResetAction { + return { type: "reset", state } }, - reducer(nestedReducers?: ReducerRegistry): ReducerAction { - return (value: T, action: RegistryAction) => { - const reducer = - get(DefaultFormReducers, action.type) || - get(nestedReducers, action.type) - - if (reducer) { - return reducer(value, action) + error(message: string): ErrorAction { + return { type: "error", message } + }, + reducer(nestedReducers?: NestedReducerRegistry): ReducerAction { + return (state: FormState, action: RegistryAction) => { + if (action.type in DefaultFormReducers) { + return get(DefaultFormReducers, action.type)(state, action) + } else if (nestedReducers && action.type in nestedReducers) { + const value = get(nestedReducers, action.type)(state.value, action) + return { ...state, value } } } }, } -export type UseFormReducer = { +export type UseFormReducer = FormState & { isDirty: (attribute?: string) => boolean - value: T dispatch: ReturnType[1] reset: (newInitial?: T) => void + setError: (message: string) => void change: (name: string, value: any) => void // eslint-disable-line @typescript-eslint/no-explicit-any } export default function useFormReducer( initial: T, - nestedReducer?: ReducerRegistry, + nestedReducer?: NestedReducerRegistry, ): UseFormReducer { const [initialValue, setInitialValue] = useState(initial) - const [value, dispatch] = useReducer( - form.reducer(nestedReducer), - initialValue, - ) + const [state, dispatch] = useReducer(form.reducer(nestedReducer), { + value: initialValue, + }) const isDirty = useCallback( (attribute?: string) => { if (attribute) { - return !isEqual(get(initialValue, attribute), get(value, attribute)) + return !isEqual( + get(initialValue, attribute), + get(state.value, attribute), + ) } else { - return !isEqual(initialValue, value) + return !isEqual(initialValue, state.value) } }, - [initialValue, value], + [initialValue, state.value], ) const reset = (newInitial?: T) => { if (newInitial) { setInitialValue(newInitial) - dispatch(form.reset(newInitial)) + dispatch(form.reset({ value: newInitial })) } else { - dispatch(form.reset(initialValue)) + dispatch(form.reset({ value: initialValue })) } } return { + ...state, isDirty, - value, dispatch, reset, + setError(message: string) { + dispatch(form.error(message)) + }, // eslint-disable-next-line @typescript-eslint/no-explicit-any change(name: string, value: any) { dispatch(form.change(name, value)) diff --git a/audiences/Gemfile.lock b/audiences/Gemfile.lock index 963005d1..8c22269c 100644 --- a/audiences/Gemfile.lock +++ b/audiences/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - audiences (1.2.0) + audiences (1.2.1) rails (>= 6.0) GEM diff --git a/audiences/config/routes.rb b/audiences/config/routes.rb index 42b7a5e9..8c19daa6 100644 --- a/audiences/config/routes.rb +++ b/audiences/config/routes.rb @@ -10,10 +10,10 @@ Rails.application.routes.draw do direct :audience_context do |owner, relation = nil| context = Audiences::Context.for(owner, relation: relation) - Audiences::Engine.routes.url_helpers.route_for(:signed_context, key: context.signed_key, **url_options) + audiences.route_for(:signed_context, key: context.signed_key, **url_options) end direct :audience_scim_proxy do |options| - Audiences::Engine.routes.url_helpers.route_for(:scim_proxy, **url_options, **options) + audiences.route_for(:scim_proxy, **url_options, **options) end end diff --git a/audiences/docs/CHANGELOG.md b/audiences/docs/CHANGELOG.md index 549f2481..13561c60 100644 --- a/audiences/docs/CHANGELOG.md +++ b/audiences/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Unreleased +# Version 1.2.1 (2024-08-06) + +- Fix audiences URL helpers [#372](https://github.com/powerhome/audiences/pull/372) + # Version 1.2.0 (2024-07-24) - Add `has_audience` and the ability to attach multiple audiences to the same owner [#363](https://github.com/powerhome/audiences/pull/363) diff --git a/audiences/gemfiles/rails_6_1.gemfile.lock b/audiences/gemfiles/rails_6_1.gemfile.lock index 6c39b9ac..88d7b4c4 100644 --- a/audiences/gemfiles/rails_6_1.gemfile.lock +++ b/audiences/gemfiles/rails_6_1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - audiences (1.2.0) + audiences (1.2.1) rails (>= 6.0) GEM diff --git a/audiences/gemfiles/rails_7_0.gemfile.lock b/audiences/gemfiles/rails_7_0.gemfile.lock index a5921c1a..0aa4a8f0 100644 --- a/audiences/gemfiles/rails_7_0.gemfile.lock +++ b/audiences/gemfiles/rails_7_0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - audiences (1.2.0) + audiences (1.2.1) rails (>= 6.0) GEM diff --git a/audiences/gemfiles/rails_7_1.gemfile.lock b/audiences/gemfiles/rails_7_1.gemfile.lock index 5fc1e116..c9a49706 100644 --- a/audiences/gemfiles/rails_7_1.gemfile.lock +++ b/audiences/gemfiles/rails_7_1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - audiences (1.2.0) + audiences (1.2.1) rails (>= 6.0) GEM diff --git a/audiences/lib/audiences/version.rb b/audiences/lib/audiences/version.rb index 969a6403..c31dbd16 100644 --- a/audiences/lib/audiences/version.rb +++ b/audiences/lib/audiences/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Audiences - VERSION = "1.2.0" + VERSION = "1.2.1" end