Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve ux/#136 #159

Merged
merged 5 commits into from
Dec 3, 2024
31 changes: 18 additions & 13 deletions app/hooks/useFunnel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';

const DEFAULT_STEP_QUERY_KEY = 'funnel-step';
Expand All @@ -15,7 +15,9 @@ export default function useFunnel<Steps extends string>(

const stepQueryKey = options?.stepQueryKey ?? DEFAULT_STEP_QUERY_KEY;

const step = searchParams.get(stepQueryKey) as Steps | undefined;
const stepQueryValue = searchParams.get(stepQueryKey) as Steps | null;

const step = (stepQueryValue ?? defaultStep) as Steps;

const createUrl = useCallback(
(step: Steps) => {
Expand All @@ -36,24 +38,27 @@ export default function useFunnel<Steps extends string>(
);

useEffect(() => {
if (!step) {
if (!stepQueryValue) {
router.replace(createUrl(defaultStep));
}
}, [defaultStep, step, setStep]);
}, [defaultStep, stepQueryValue, setStep]);

const Step = ({ name, children }: React.PropsWithChildren<{ name: Steps }>) => {
const Step = useCallback(({ name, children }: React.PropsWithChildren<{ name: Steps }>) => {
return <>{children}</>;
};
}, []);

const FunnelRoot = ({ children }: React.PropsWithChildren) => {
const targetStep = React.Children.toArray(children).find((childStep) => {
return React.isValidElement(childStep) && childStep.props.name === step;
});
const FunnelRoot = useCallback(
({ children }: React.PropsWithChildren) => {
const targetStep = React.Children.toArray(children).find((childStep) => {
return React.isValidElement(childStep) && childStep.props.name === step;
});

return <>{targetStep}</>;
};
return <>{targetStep}</>;
},
[step],
);

const Funnel = Object.assign(FunnelRoot, { Step });
const Funnel = useMemo(() => Object.assign(FunnelRoot, { Step }), [FunnelRoot, Step]);

return { Funnel, setStep };
}
2 changes: 1 addition & 1 deletion app/ui/user/find-id-form/find-id-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface SignUpFormProps {
function FindIdForm({ onNext }: SignUpFormProps) {
return (
<Form onSuccess={onNext} id="์•„์ด๋””์ฐพ๊ธฐ" action={findUserToStudentNumber}>
<Form.TextInput required={true} label="ํ•™๋ฒˆ" id="studentNumber" placeholder="ex ) 60xxxxxx" />
<Form.TextInput autoFocus={true} required={true} label="ํ•™๋ฒˆ" id="studentNumber" placeholder="ex ) 60xxxxxx" />
<div className="py-6">
<Form.SubmitButton label="์•„์ด๋””์ฐพ๊ธฐ" position="center" variant="primary" />
</div>
Expand Down
1 change: 1 addition & 0 deletions app/ui/user/find-password-from/find-password-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function FindPasswordForm({ authId }: FindPasswordFormProps) {
return (
<Form id="๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •" action={resetPassword} onSuccess={onSuccess} className="flex flex-col gap-4">
<Form.PasswordInput
autoFocus={true}
required={true}
label="๋น„๋ฐ€๋ฒˆํ˜ธ"
id="newPassword"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ interface FindPasswordValidateFormProps {
function FindPasswordValidateForm({ onNext }: FindPasswordValidateFormProps) {
return (
<Form id="๊ฐ€์ž…์ž ๊ฒ€์ฆ" onSuccess={onNext} action={validateUser} className="flex flex-col gap-4">
<Form.TextInput required={true} label="์•„์ด๋””" id="authId" placeholder="์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”." />
<Form.TextInput
autoFocus={true}
required={true}
label="์•„์ด๋””"
id="authId"
placeholder="์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
/>
<Form.TextInput required={true} label="ํ•™๋ฒˆ" id="studentNumber" placeholder="ex ) 60xxxxxx" />
<div className="py-6">
<Form.SubmitButton label="๊ฒ€์‚ฌํ•˜๊ธฐ" position="center" variant="primary" />
Expand Down
2 changes: 1 addition & 1 deletion app/ui/user/sign-in-form/sign-in-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function SignInForm({ onSuccess }: SignInForm) {
return (
<>
<Form id="๋กœ๊ทธ์ธ" className="space-y-6" action={authenticate} onSuccess={onSuccess}>
<Form.TextInput required={true} label="์•„์ด๋””" id="authId" placeholder="์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" />
<Form.TextInput autoFocus={true} required={true} label="์•„์ด๋””" id="authId" placeholder="์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" />
<Form.PasswordInput required={true} label="๋น„๋ฐ€๋ฒˆํ˜ธ" id="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" />
<div className="pt-6">
<Form.SubmitButton label="๋กœ๊ทธ์ธ" position="center" variant="primary" />
Expand Down
2 changes: 1 addition & 1 deletion app/ui/user/sign-up-form/sign-up-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface SignUpFormProps {
export default function SignUpForm({ onSuccess }: SignUpFormProps) {
return (
<Form className="space-y-6" onSuccess={onSuccess} action={createUser} id="ํšŒ์›๊ฐ€์ž…">
<Form.TextInput required={true} label="์•„์ด๋””" id="authId" placeholder="6์ž ์ด์ƒ 20์ž ์ดํ•˜" />
<Form.TextInput autoFocus={true} required={true} label="์•„์ด๋””" id="authId" placeholder="6์ž ์ด์ƒ 20์ž ์ดํ•˜" />
<Form.PasswordInput
required={true}
label="๋น„๋ฐ€๋ฒˆํ˜ธ"
Expand Down
11 changes: 4 additions & 7 deletions app/ui/view/molecule/form/form-number-input.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import TextInput from '../../atom/text-input/text-input';
import { FormInputProps } from './form-root';
import { FormContext } from './form.context';
import { useContext } from 'react';
import { useFormStatus } from 'react-dom';

interface FormNumberInputProps {
label: string;
id: string;
placeholder: string;
required?: boolean;
}
interface FormNumberInputProps extends FormInputProps {}

export function FormNumberInput({ label, id, placeholder, required = false }: FormNumberInputProps) {
export function FormNumberInput({ label, id, placeholder, autoFocus, required = false }: FormNumberInputProps) {
const { errors } = useContext(FormContext);
const { pending } = useFormStatus();

Expand All @@ -23,6 +19,7 @@ export function FormNumberInput({ label, id, placeholder, required = false }: Fo
{label}
</label>
<TextInput
autoFocus={autoFocus}
required={required}
disabled={pending}
error={errors[id] ? true : false}
Expand Down
11 changes: 4 additions & 7 deletions app/ui/view/molecule/form/form-password-input.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import TextInput from '../../atom/text-input/text-input';
import { FormInputProps } from './form-root';
import { FormContext } from './form.context';
import { useContext } from 'react';
import { useFormStatus } from 'react-dom';

interface FormPasswordInputProps {
label: string;
id: string;
placeholder: string;
required?: boolean;
}
interface FormPasswordInputProps extends FormInputProps {}

export function FormPasswordInput({ label, id, placeholder, required = false }: FormPasswordInputProps) {
export function FormPasswordInput({ label, id, placeholder, autoFocus, required = false }: FormPasswordInputProps) {
const { errors } = useContext(FormContext);
const { pending } = useFormStatus();

Expand All @@ -23,6 +19,7 @@ export function FormPasswordInput({ label, id, placeholder, required = false }:
{label}
</label>
<TextInput
autoFocus={autoFocus}
required={required}
disabled={pending}
error={errors[id] ? true : false}
Expand Down
9 changes: 9 additions & 0 deletions app/ui/view/molecule/form/form-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import { filterChildrenByType } from '@/app/utils/component.util';
import AlertDestructive from '../alert-destructive/alert-destructive';
import { useToast } from '../toast/use-toast';

export interface FormInputProps {
label: string;
id: string;
placeholder: string;
required?: boolean;
autoFocus?: boolean;
}

export interface FormState {
isSuccess: boolean;
isFailure: boolean;
Expand Down Expand Up @@ -57,6 +65,7 @@ export function FormRoot({
return React.Children.map(children, (child, index) => {
if (!React.isValidElement(child) || child.type === FormSubmitButton) return null;
if (child.type === FormSubmitButton) return child;

return <div key={index}>{child}</div>;
});
};
Expand Down
10 changes: 4 additions & 6 deletions app/ui/view/molecule/form/form-select.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import Select from '../select';
import { FormInputProps } from './form-root';
import { FormContext } from './form.context';
import { useContext } from 'react';
import { useFormStatus } from 'react-dom';

interface FormSelectProps {
label: string;
id: string;
interface FormSelectProps extends FormInputProps {
options: { value: string; placeholder: string }[];
placeholder: string;
required?: boolean;
}

export const FormSelect = ({ label, id, options, placeholder, required = true }: FormSelectProps) => {
export const FormSelect = ({ label, id, options, placeholder, autoFocus, required = true }: FormSelectProps) => {
const { errors } = useContext(FormContext);
const { pending } = useFormStatus();

Expand All @@ -24,6 +21,7 @@ export const FormSelect = ({ label, id, options, placeholder, required = true }:
{label}
</label>
<Select
autoFocus={autoFocus}
required={required}
disabled={pending}
error={errors[id] ? true : false}
Expand Down
10 changes: 4 additions & 6 deletions app/ui/view/molecule/form/form-text-input.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import TextInput from '../../atom/text-input/text-input';
import { FormInputProps } from './form-root';
import { FormContext } from './form.context';
import { useContext } from 'react';
import { useFormStatus } from 'react-dom';

interface FormTextInputProps {
label: string;
id: string;
placeholder: string;
required?: boolean;
interface FormTextInputProps extends FormInputProps {
value?: string;
}

export function FormTextInput({ label, id, value, placeholder, required = false }: FormTextInputProps) {
export function FormTextInput({ label, id, value, placeholder, autoFocus, required = false }: FormTextInputProps) {
const { errors } = useContext(FormContext);
const { pending } = useFormStatus();

Expand All @@ -24,6 +21,7 @@ export function FormTextInput({ label, id, value, placeholder, required = false
{label}
</label>
<TextInput
autoFocus={autoFocus}
required={required}
disabled={pending}
error={errors[id] ? true : false}
Expand Down