diff --git a/backend/app/core/logging.py b/backend/app/core/logging.py index e50132368..1c9124392 100644 --- a/backend/app/core/logging.py +++ b/backend/app/core/logging.py @@ -37,8 +37,10 @@ def emit(self, record): ) logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True) - - if settings.HUU_ENVIRONMENT.lower() in ["development", "qa", "local"]: + ''' + A logging file can replace logging to standard out by changing sys.stdout to filename.log + ''' + if settings.HUU_ENVIRONMENT.lower() in ["development", "local"]: logger.add( sys.stdout, format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function} - {message}", diff --git a/backend/app/modules/deps.py b/backend/app/modules/deps.py index 8e2fe0355..9ef7f5c03 100644 --- a/backend/app/modules/deps.py +++ b/backend/app/modules/deps.py @@ -26,6 +26,7 @@ def get_form_1(): import json with open("form_data/form1.json", "r") as f: FORM_1 = json.load(f) + FORM_1['id'] = 'guest' return FORM_1 @@ -35,6 +36,7 @@ def get_form_2(): import json with open("form_data/form2.json", "r") as f: FORM_2 = json.load(f) + FORM_2['id'] = 'host' return FORM_2 ################################################################################ diff --git a/backend/app/modules/intake_profile/controller.py b/backend/app/modules/intake_profile/controller.py index cfe79cba3..0498828b1 100644 --- a/backend/app/modules/intake_profile/controller.py +++ b/backend/app/modules/intake_profile/controller.py @@ -6,58 +6,77 @@ router = APIRouter() -@router.put("/responses/{user_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED) -def update_intake_profile_responses(user_id, body, db_session: DbSessionDep): - pass - # TODO: Implement update intake profile responses - # with db_session.begin() as session: - # user_repo = UserRepository(session) - # forms_repo = FormsRepository(session) - # user = user_repo.get_user(token_info['Username']) - - # form = forms_repo.get_form(form_id) - # if not form: - # return f"Form with id {form_id} does not exist.", 404 - - # valid_field_ids = form.get_field_ids() - # for response in body: - # response["user_id"] = user.id - # if response["field_id"] not in valid_field_ids: - # return f"Form {form_id} does not contain field id {response['field_id']}", 400 - - # forms_repo.add_user_responses(user.id, body) - - # return {}, 204 - - -@router.get("/responses/{user_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED) -def get_intake_profile_responses(user_id, db_session: DbSessionDep): - pass - # TODO: Implement get Intake Profile responses - # with db_session.begin() as session: - # user_repo = UserRepository(session) - # forms_repo = FormsRepository(session) - - # form = forms_repo.get_form_json(form_id) - # if not form: - # return f"Form with id {form_id} does not exist.", 404 - - # user = user_repo.get_user(token_info['Username']) - # responses = forms_repo.get_user_responses(user.id, form_id) - # if responses: - # return responses, 200 - # return [], 202 - - -@router.get("/form/{profile_id}", status_code=status.HTTP_200_OK) -def get_intake_profile_form(profile_id: int, - profile_form_1: Annotated[str, Depends(get_form_1)], - profile_form_2: Annotated[str, Depends(get_form_2)]): - """Get the Intake Profile form definition.""" - if profile_id == 1: - return profile_form_1 - if profile_id == 2: - return profile_form_2 +# @router.put("/responses/{user_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED) +# def update_intake_profile_responses(user_id, body, db_session: DbSessionDep): +# pass + # TODO: Implement the functionality to update intake profile responses. + # This should: + # 1. Use the `db_session` to initiate a transaction. + # 2. Retrieve the user data using the `user_id`. + # 3. Get the specific form by `form_id`. + # 4. Check if the form exists. If not, return an error (404). + # 5. Validate the field IDs in the response body against the form's field IDs. + # 6. If any field ID is invalid, return an error (400). + # 7. Save the valid responses in the database. + # 8. Return a success response with status 204 (no content) if everything goes well. + +# @router.get("/responses/{user_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED) +# def get_intake_profile_responses(user_id, db_session: DbSessionDep): +# pass + # TODO: Implement the functionality to retrieve intake profile responses for a user. + # This should: + # 1. Use the `db_session` to begin a transaction. + # 2. Retrieve the user using the `user_id`. + # 3. Fetch the form associated with the user's responses. + # 4. If the form does not exist, return a 404 error. + # 5. Get the user responses for the form from the database. + # 6. If responses are found, return them with status 200. + # 7. If no responses are found, return an empty list with status 202. + +# @router.get("/responses/{profile_type}", status_code=status.HTTP_200_OK) +# def get_intake_profile_form(profile_type: str, +# profile_form_1: Annotated[dict, Depends(get_form_1)], +# profile_form_2: Annotated[dict, Depends(get_form_2)]): +# """Get the Intake Profile form definition for host or guest.""" +# if profile_type == "guest": +# return profile_form_1 +# if profile_type == "host": +# return profile_form_2 + # TODO: Return the appropriate form based on the `profile_type` (either "guest" or "host"). + # 1. Use `profile_form_1` for the "guest" type and return it. + # 2. Use `profile_form_2` for the "host" type and return it. + # 3. If the `profile_type` is not recognized, raise a 404 error. + +# @router.get("/form/{profile_id}", status_code=status.HTTP_200_OK) +# def get_intake_profile_form(profile_id: int, +# profile_form_1: Annotated[str, Depends(get_form_1)], +# profile_form_2: Annotated[str, Depends(get_form_2)]): +# """Get the Intake Profile form definition.""" +# if profile_id == 1: +# return profile_form_1 +# if profile_id == 2: +# return profile_form_2 + # TODO: Return the form based on the `profile_id`. + # 1. If the `profile_id` is 1, return `profile_form_1`. + # 2. If the `profile_id` is 2, return `profile_form_2`. + # 3. If the `profile_id` is not valid, raise a 404 error. + +# This is your current working function: +@router.get("/form/{profile_type}", status_code=status.HTTP_200_OK) +def get_intake_profile_form(profile_type: str, + profile_form_1: Annotated[dict, Depends(get_form_1)], + profile_form_2: Annotated[dict, Depends(get_form_2)]): + """Get the Intake Profile form definition and responses for host or guest.""" + if profile_type == "guest": + return { + "form": profile_form_1, + "responses": profile_form_1.get("responses", []) + } + if profile_type == "host": + return { + "form": profile_form_2, + "responses": profile_form_2.get("responses", []) + } raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, - detail=f"Form with id {profile_id} does not exist.") + detail=f"Form type {profile_type} does not exist.") diff --git a/backend/form_data/form1.json b/backend/form_data/form1.json index db8ced8b7..8d79b7dbc 100644 --- a/backend/form_data/form1.json +++ b/backend/form_data/form1.json @@ -1 +1,344 @@ -{"id":"1","fieldGroups":[{"id":"1293","title":"Basic Information","fields":[{"id":"3922","title":"First Name","type":"short_text","properties":{},"validations":{"required":true}},{"id":"5572","title":"Last Name","type":"short_text","properties":{},"validations":{"required":true}},{"id":"2188","title":"Date of Birth","type":"date","properties":{},"validations":{"required":true}},{"id":"3924","title":"Gender Identity","type":"dropdown","properties":{"choices":[{"id":"1359","label":"Woman"},{"id":"5342","label":"Man"},{"id":"3151","label":"Questioning"},{"id":"2178","label":"Transgender"},{"id":"3667","label":"Non-binary"},{"id":"3708","label":"Doesn’t know or prefers not to answer\\t"}]},"validations":{"required":true}}]},{"id":"1348","title":"Contact Information","fields":[{"id":"7813","title":"Email","type":"email","properties":{},"validations":{"required":true}},{"id":"2044","title":"Phone Number","type":"number","properties":{},"validations":{"required":true}},{"id":"7504","title":"What is the best way to contact you?","type":"contact_method","properties":{},"validations":{"required":true},"linkedFields":{"emailFieldId":"3158","phoneFieldId":"9365"}}]},{"id":"6577","title":"Other Guests/Pets","fields":[{"id":"7709","title":"Additional Guests","type":"additional_guests","properties":{},"validations":{}},{"id":"7740","title":"Do you have any pets in your care?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"5056","title":"Pet Type","type":"pets","properties":{},"validations":{"required":false}}]},{"id":"1676","title":"Employment Information","fields":[{"id":"5478","title":"Are you currently employed?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"8533","title":"If yes, please describe your employment.","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"5478","value":"yes"}}},{"id":"8487","title":"If no, are you currently looking for work? If so, what type?","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"5478","value":"no"}}}]},{"id":"5996","title":"Education","fields":[{"id":"6478","title":"Are you enrolled in an Educational Program?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"7222","title":"If yes, please describe the program.","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"6478","value":"yes"}}},{"id":"7661","title":"If no, are you hoping to enroll in an Educational Program? If so, what type?","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"6478","value":"no"}}}]},{"id":"4480","title":"Language Proficiency","fields":[{"id":"5479","title":"Are you bilingual or multilingual?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"2359","title":"If yes, what languages do you speak?","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"5479","value":"yes"}}}]},{"id":"5282","title":"Substance Use","fields":[{"id":"8229","title":"Do you smoke cigarettes?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"7844","title":"Do you drink alcohol?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"7494","title":"Do you use any other substances?","type":"yes_no","properties":{},"validations":{"required":true}}]},{"id":"3691","title":"Mental Health","fields":[{"id":"3831","title":"Do you suffer mental illness?","type":"yes_no","properties":{},"validations":{"required":true}}]},{"id":"7816","title":"Interest in Being a Guest","fields":[{"id":"1885","title":"Please share how you think participating in the Host Homes Program will help you obtain long-term housing and meet your educational and/or employment goals:","type":"long_text","properties":{},"validations":{"required":true}},{"id":"1185","title":"What kind of relationship do you hope to have with your host home?","type":"long_text","properties":{},"validations":{"required":true}},{"id":"3749","title":"Please describe any challenges you foresee encountering in participating in the Host Homes Program.","type":"long_text","properties":{},"validations":{"required":true}}]},{"id":"8837","title":"About You","fields":[{"id":"3411","title":"Please take some time to write an introduction of yourself that you would feel comfortable with the Host Homes Coordinator sharing with a potential host. Feel free to talk about your interests, your story or anything else that you think would be important to share:","type":"long_text","properties":{},"validations":{"required":true}}]}]} +{ + "id": "1", + "fieldGroups": [ + { + "id": "1293", + "title": "Basic Information", + "fields": [ + { + "id": "3922", + "title": "First Name", + "type": "short_text", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "5572", + "title": "Last Name", + "type": "short_text", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "2188", + "title": "Date of Birth", + "type": "date", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "3924", + "title": "Gender Identity", + "type": "dropdown", + "properties": { + "choices": [ + { + "id": "1359", + "label": "Woman" + }, + { + "id": "5342", + "label": "Man" + }, + { + "id": "3151", + "label": "Questioning" + }, + { + "id": "2178", + "label": "Transgender" + }, + { + "id": "3667", + "label": "Non-binary" + }, + { + "id": "3708", + "label": "Doesn’t know or prefers not to answer" + } + ] + }, + "validations": { + "required": true + } + } + ] + }, + { + "id": "1348", + "title": "Contact Information", + "fields": [ + { + "id": "7813", + "title": "Email", + "type": "email", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "2044", + "title": "Phone Number", + "type": "number", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "7504", + "title": "What is the best way to contact you?", + "type": "contact_method", + "properties": {}, + "validations": { + "required": true + }, + "linkedFields": { + "emailFieldId": "7813", + "phoneFieldId": "2044" + } + } + ] + }, + { + "id": "6577", + "title": "Other Guests/Pets", + "fields": [ + { + "id": "7709", + "title": "Additional Guests", + "type": "additional_guests", + "properties": {}, + "validations": {} + }, + { + "id": "7740", + "title": "Do you have any pets in your care?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "5056", + "title": "Pet Type", + "type": "pets", + "properties": {}, + "validations": { + "required": false + } + } + ] + }, + { + "id": "1676", + "title": "Employment Information", + "fields": [ + { + "id": "5478", + "title": "Are you currently employed?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "8533", + "title": "If yes, please describe your employment.", + "type": "long_text", + "properties": {}, + "validations": { + "required_if": { + "field_id": "5478", + "value": "yes" + } + } + }, + { + "id": "8487", + "title": "If no, are you currently looking for work? If so, what type?", + "type": "long_text", + "properties": {}, + "validations": { + "required_if": { + "field_id": "5478", + "value": "no" + } + } + } + ] + }, + { + "id": "5996", + "title": "Education", + "fields": [ + { + "id": "6478", + "title": "Are you enrolled in an Educational Program?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "7222", + "title": "If yes, please describe the program.", + "type": "long_text", + "properties": {}, + "validations": { + "required_if": { + "field_id": "6478", + "value": "yes" + } + } + }, + { + "id": "7661", + "title": "If no, are you hoping to enroll in an Educational Program? If so, what type?", + "type": "long_text", + "properties": {}, + "validations": { + "required_if": { + "field_id": "6478", + "value": "no" + } + } + } + ] + }, + { + "id": "4480", + "title": "Language Proficiency", + "fields": [ + { + "id": "5479", + "title": "Are you bilingual or multilingual?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "2359", + "title": "If yes, what languages do you speak?", + "type": "long_text", + "properties": {}, + "validations": { + "required_if": { + "field_id": "5479", + "value": "yes" + } + } + } + ] + }, + { + "id": "5282", + "title": "Substance Use", + "fields": [ + { + "id": "8229", + "title": "Do you smoke cigarettes?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "7844", + "title": "Do you drink alcohol?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "7494", + "title": "Do you use any other substances?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + } + ] + }, + { + "id": "3691", + "title": "Mental Health", + "fields": [ + { + "id": "3831", + "title": "Do you suffer mental illness?", + "type": "yes_no", + "properties": {}, + "validations": { + "required": true + } + } + ] + }, + { + "id": "7816", + "title": "Interest in Being a Guest", + "fields": [ + { + "id": "1885", + "title": "Please share how you think participating in the Host Homes Program will help you obtain long-term housing and meet your educational and/or employment goals:", + "type": "long_text", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "1185", + "title": "What kind of relationship do you hope to have with your host home?", + "type": "long_text", + "properties": {}, + "validations": { + "required": true + } + }, + { + "id": "3749", + "title": "Please describe any challenges you foresee encountering in participating in the Host Homes Program.", + "type": "long_text", + "properties": {}, + "validations": { + "required": true + } + } + ] + }, + { + "id": "8837", + "title": "About You", + "fields": [ + { + "id": "3411", + "title": "Please take some time to write an introduction of yourself that you would feel comfortable with the Host Homes Coordinator sharing with a potential host. Feel free to talk about your interests, your story or anything else that you think would be important to share:", + "type": "long_text", + "properties": {}, + "validations": { + "required": true + } + } + ] + } + ] + } + \ No newline at end of file diff --git a/frontend/src/features/authentication/SignInForm.tsx b/frontend/src/features/authentication/SignInForm.tsx index 0d968a245..30d8d08e4 100644 --- a/frontend/src/features/authentication/SignInForm.tsx +++ b/frontend/src/features/authentication/SignInForm.tsx @@ -26,9 +26,9 @@ const validationSchema = object({ }); export const SignInForm = ({ - onSubmit, + onSubmit, isLoading, - getTokenIsLoading + getTokenIsLoading, }: SignInFormProps) => { const { handleSubmit, diff --git a/frontend/src/features/authentication/SignUpForm.tsx b/frontend/src/features/authentication/SignUpForm.tsx index cf435e5a1..3e8a18642 100644 --- a/frontend/src/features/authentication/SignUpForm.tsx +++ b/frontend/src/features/authentication/SignUpForm.tsx @@ -34,7 +34,7 @@ export const SignUpForm = ({ type, getTokenIsLoading = false, signUpHostIsLoading = false, - signUpCoordinatorIsLoading = false + signUpCoordinatorIsLoading = false, }: SignUpFormProps) => { const isAnyLoading = isLoading || diff --git a/frontend/src/features/intake-profile/IntakeProfileGroups.tsx b/frontend/src/features/intake-profile/IntakeProfileGroups.tsx index 698385aef..9f10a71f4 100644 --- a/frontend/src/features/intake-profile/IntakeProfileGroups.tsx +++ b/frontend/src/features/intake-profile/IntakeProfileGroups.tsx @@ -49,3 +49,217 @@ export const FieldGroupList = () => { ); }; + +interface RenderFieldProps { + groupId: string; + field: Fields; +} + +export const RenderFields = ({groupId, field}: RenderFieldProps) => { + const {errors, handleChange, setFieldValue, values} = + useFormikContext(); + const groupValues = values[groupId]; + + const props = { + name: `${groupId}.${field.id}`, + value: groupValues[field.id], + onChange: handleChange, + }; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const error = Boolean(errors[groupId]?.[field.id]); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const helperText = errors[groupId]?.[field.id]; + + switch (field.type) { + case 'additional_guests': + return ( + + ); + case 'date': + return ( + { + setFieldValue(name, value); + }} + /> + ); + case 'dropdown': + if (field.properties.choices === undefined) + throw new Error('Invalid field type'); + + return ( + + + Select a choice + + + {helperText} + + ); + case 'email': + return ( + + ); + case 'long_text': + return ( + + ); + case 'number': + return ( + + ); + case 'pets': + return ( + + ); + case 'short_text': + return ( + + ); + case 'contact_method': + // eslint-disable-next-line no-case-declarations + const {emailFieldId, phoneFieldId} = field.linkedFields || {}; + + // eslint-disable-next-line no-case-declarations + let isEmailFilled = false; + // eslint-disable-next-line no-case-declarations + let isPhoneFilled = false; + if (emailFieldId) { + const emailValue = values[groupId][emailFieldId]; + isEmailFilled = + typeof emailValue === 'string' && /\S+@\S+\.\S+/.test(emailValue); + } + // eslint-disable-next-line no-case-declarations + if (phoneFieldId) { + const phoneValue = values[groupId][phoneFieldId]; + isPhoneFilled = + typeof phoneValue === 'string' && phoneRegExp.test(phoneValue); + } + + return ( + + {field.title} + + } + label="Phone" + disabled={!isPhoneFilled} + /> + } + label="Email" + disabled={!isEmailFilled} + /> + + {helperText} + + ); + + case 'yes_no': + return ( + + {field.title} + + } label="Yes" /> + } label="No" /> + + {helperText} + + ); + default: + throw new Error('Invalid field type'); + } +}; diff --git a/frontend/src/features/intake-profile/helpers/createInitialValues.ts b/frontend/src/features/intake-profile/helpers/createInitialValues.ts index f15c55997..01589cb04 100644 --- a/frontend/src/features/intake-profile/helpers/createInitialValues.ts +++ b/frontend/src/features/intake-profile/helpers/createInitialValues.ts @@ -2,7 +2,7 @@ import {InitialValues} from '../../../pages/intake-profile/IntakeProfile'; import {FieldGroup, FieldTypes, Response} from '../../../services/profile'; /** - * Creates an object used for the initial Formik valiues + * Creates an object used for the initial Formik values * It takes the form of: * { * fieldGroupId: { @@ -26,6 +26,10 @@ const fieldDefaultValue = (fieldType: FieldTypes) => { return ''; case 'yes_no': return ''; + case 'multiple_choice': + return []; + case 'contact_method': + return ''; case 'pets': return []; default: @@ -34,16 +38,29 @@ const fieldDefaultValue = (fieldType: FieldTypes) => { }; export const createInitialValues = ( - fieldGroups: FieldGroup[], - responses: Response[], + fieldGroups: FieldGroup[] | undefined, + responses: Response[] | undefined, ): InitialValues => { + // Early return if fieldGroups is undefined or empty + if (!fieldGroups || fieldGroups.length === 0) { + return {}; + } + + // Use empty array if responses is undefined + const safeResponses = responses || []; + return fieldGroups.reduce((acc: InitialValues, fieldGroup) => { - const fields = fieldGroup.fields.reduce((acc, field) => { + // Skip groups without fields + if (!fieldGroup.fields || fieldGroup.fields.length === 0) { + return acc; + } + + const fields = fieldGroup.fields.reduce((fieldAcc, field) => { return { - ...acc, + ...fieldAcc, [field.id]: - responses.find(response => response.fieldId === field.id)?.value || - fieldDefaultValue(field.type), + safeResponses.find(response => response.fieldId === field.id) + ?.value || fieldDefaultValue(field.type), }; }, {}); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 6f0eb0882..10f77c50d 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -46,7 +46,7 @@ import { IntakeProfileSection, } from './pages'; import {SystemAdminDashboard} from './pages/SystemAdminDashboard'; -import {enableMocking} from './utils/testing/browser'; +// import {enableMocking} from './utils/testing/browser'; import {useAppDispatch} from './redux/hooks/store'; import {setCredentials} from './redux/authSlice'; import NotFound from './pages/NotFound'; @@ -170,21 +170,21 @@ function HuuApp() { const appRoot = document.getElementById('root') as HTMLElement; -enableMocking().then(() => { - ReactDOM.createRoot(appRoot).render( - - - - - - - - - - - - - - , - ); -}); +// enableMocking().then(() => { +ReactDOM.createRoot(appRoot).render( + + + + + + + + + + + + + + , +); +// }); diff --git a/frontend/src/pages/authentication/SignUp.tsx b/frontend/src/pages/authentication/SignUp.tsx index 260825d3b..ad58b8c20 100644 --- a/frontend/src/pages/authentication/SignUp.tsx +++ b/frontend/src/pages/authentication/SignUp.tsx @@ -18,25 +18,33 @@ import { import {isErrorWithMessage, isFetchBaseQueryError} from '../../redux/helpers'; import {useAuthenticateWithOAuth} from '../../features/authentication/hooks/useAuthenticateWithOAuth'; import {FormContainer, SignUpForm} from '../../features/authentication'; -import { LocationState } from './SignIn'; +// Uncomment this import if it's in a separate file +// import { LocationState } from './SignIn'; + +// You might need to define LocationState if it's not imported +interface LocationState { + from?: { + pathname: string; + }; +} export const SignUp = () => { const [errorMessage, setErrorMessage] = React.useState(''); const {type} = useParams(); const navigate = useNavigate(); + const location = useLocation(); + + // Get location state information + const locationState = location.state as LocationState; + + // Save location from which user was redirected to login page + const from = locationState?.from?.pathname || '/'; const [signUp, {isLoading: signUpIsLoading}] = useSignUpMutation(); const [googleSignUp, {isLoading: getTokenIsLoading}] = useGoogleSignUpMutation(); - // const location = useLocation() - // // get type from params - // const locationState = location.state as LocationState; - - // // Save location from which user was redirected to login page - // const from = locationState?.from?.pathname || '/'; - const callbackUri = `/signup/${type}`; useAuthenticateWithOAuth({ query: googleSignUp, diff --git a/frontend/src/pages/guest-dashboard/GuestDashboard.tsx b/frontend/src/pages/guest-dashboard/GuestDashboard.tsx index f7ae20d50..275a981b1 100644 --- a/frontend/src/pages/guest-dashboard/GuestDashboard.tsx +++ b/frontend/src/pages/guest-dashboard/GuestDashboard.tsx @@ -36,7 +36,8 @@ const tasks: Task[] = [ description: 'Start your guest application to move on to the next step.', linkText: 'Start Application', - url: 'profile/1', + // url: 'profile/1/group/1', + url: 'profile/guest', }, { id: 2, diff --git a/frontend/src/pages/intake-profile/IntakeProfile.tsx b/frontend/src/pages/intake-profile/IntakeProfile.tsx index e981abe8d..f91532861 100644 --- a/frontend/src/pages/intake-profile/IntakeProfile.tsx +++ b/frontend/src/pages/intake-profile/IntakeProfile.tsx @@ -1,6 +1,19 @@ +import React, {useState, useEffect} from 'react'; +import { + Button, + Stack, + useMediaQuery, + useTheme, + CircularProgress, + Typography, +} from '@mui/material'; import {Container, Stack, useMediaQuery, useTheme} from '@mui/material'; import {Outlet, useLocation, useNavigate, useParams} from 'react-router-dom'; import {Formik} from 'formik'; +import {buildValidationSchema, createInitialValues} from './helpers'; +import {useGetProfileQuery, Response} from '../../services/profile'; +import {ProfileSidebar} from '../../features/intake-profile'; + import {useState} from 'react'; import {createInitialValues} from '../../features/intake-profile/helpers'; import { @@ -18,14 +31,93 @@ export type InitialValues = Record; export const IntakeProfile = () => { const theme = useTheme(); const navigate = useNavigate(); + const toolbarHeight = Number(theme.mixins.toolbar.minHeight); + const params = useParams(); const {profileId, groupId} = useParams(); const location = useLocation(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [showSections, setShowSections] = useState(isMobile); const [selectedItem, setSelectedItem] = useState( - groupId || null, + params.groupId || null, ); + // Debug logging for all parameters + useEffect(() => { + console.log('FULL ROUTE PARAMETERS:', params); + console.log('CURRENT LOCATION:', location); + }, [params, location]); + + // Determine profile type with extensive fallback + const profileType = + params.profileType || params.profileId?.split('-')[0] || 'guest'; + + // const userId = + // params.userId || + // (params.profileId?.split('-')[1]) || + // 'anonymous'; + + // Fetch profile data + const { + data: profileData, + isLoading: isProfileLoading, + error: profileError, + } = useGetProfileQuery({ + profileId: profileType, + }); + + // Extensive error logging + useEffect(() => { + if (profileError) { + console.error('PROFILE FETCH ERROR:', profileError); + } + }, [profileError]); + + // Loading state + if (isProfileLoading) { + return ( + + + + ); + } + + // Error handling + if (profileError) { + return ( + + + Error loading profile. Please try again. + + {JSON.stringify(profileError)} + + ); + } + + // If no data after loading, return null or show error + if (!profileData) { + return ( + + No profile data found + + ); + } + + const {form, responses = []} = profileData; + const fieldGroups = form?.fieldGroups || []; + + // create validation schema for current group + const validationSchema = + params.groupId === undefined + ? {} + : buildValidationSchema(fieldGroups, params.groupId); const {data: profileData} = useGetProfileQuery( {profileId: profileId}, {skip: !profileId}, @@ -48,12 +140,15 @@ export const IntakeProfile = () => { // groupId === undefined ? {} : buildValidationSchema(fieldGroups, groupId); // create initial values from responses and fieldGroups + const initialValues = createInitialValues(fieldGroups, responses); + const initalValues = createInitialValues(fieldGroups, responses); const currentIndex = fieldGroups.findIndex( group => group.id === selectedItem, ); + // Navigation and section management functions function toggleShowSections() { setShowSections(!showSections); } @@ -63,7 +158,6 @@ export const IntakeProfile = () => { const nextGroupId = fieldGroups[currentIndex + 1].id; setSelectedItem(nextGroupId); navigate(`group/${nextGroupId}`); - //need to add autosave feature } } @@ -72,33 +166,37 @@ export const IntakeProfile = () => { const prevGroupId = fieldGroups[currentIndex - 1].id; setSelectedItem(prevGroupId); navigate(`group/${prevGroupId}`); - //need to add autosave feature } } const handleSubmitApplication = () => { - // submit the application after review + // TODO: Implement application submission logic + console.log('Submitting application'); }; return ( { - if (!groupId) { + if (!params.groupId) { console.error('groupId is not defined in on submit'); return; } - const updateResponses = Object.entries(values[groupId]).map( + const updateResponses = Object.entries(values[params.groupId]).map( ([fieldId, value]) => { const response = responses.find( response => response.fieldId === fieldId, ); if (response) { - response.value = value; - return response; + return { + ...response, + value, + }; } else { return { fieldId, @@ -122,12 +220,77 @@ export const IntakeProfile = () => { > + + + + + + + {location.pathname.includes('review') ? ( + + ) : ( + + )} + + { +// const id = req.params.profileId; +// const profile = intakeProfiles.find(p => p.id === id); + +// if (profile) { +// return HttpResponse.json(profile); +// } + +// return new HttpResponse(null, {status: 404}); +// }), + +// http.get( +// '/api/intake-profile/responses/:userId', +// () => { +// const fields = intakeProfiles[0].fieldGroups +// .map(fieldGroup => fieldGroup.fields) +// .flat(); + +// const responses = fields.map(field => { +// const value = getResponseValue(field); +// return { +// fieldId: field.id, +// value, +// }; +// }); + +// // return a list of filled in responses +// return HttpResponse.json({responses}); +// // return an empty list of responses +// // return HttpResponse.json({responses: []}); +// }, +// ), +// ]; + export const handlers = [ http.post('/api/profile/responses', async ({request}) => { const responses = await request.json();