פורטל סדנאות לחברים · AfifAI
מקרה בוחן · פרונטאנד בתחום הבריאות · 2025

פורטל סדנאות בשירות עצמי לחברי ארגון בריאות

חברי ארגון בריאות ישראלי גדול יכולים לעיין בסדנאות בריאות ואיכות חיים לפי תחום, לסנן לפי מקום, שעה, יום ושפה, לראות את המחיר לחברי התוכנית ולמי שאינו חבר בה, ולנהל לבד את ההרשמות שלהם. לקחנו את זה ממאגר קוד ריק לפרונטאנד מלא מבחינת UX, בעברית ומימין לשמאל, תוך 13 יום. ההתחברות עוברת מפורטל החברים הקיים של הארגון.

React 18TypeScriptViteRedux ToolkitMUIreact-hook-formi18next (RTL)SSO token hand-off
לקוח
ארגון בריאות ישראלי גדול (חסוי)
התפקיד שלי
ראש צוות, ארכיטקט ומפתח בפועל
שלב · לוח זמנים
פרונטאנד מלא מבחינת UX, על נתוני דמה בתבנית החוזה · נובמבר–דצמבר 2025
סטטוס
ממשק נמסר
במבט מהיר
4 עד 1בילדים לסביבות local, dev, test ו־⁠production. הקונפיגורציה נקראת בזמן ריצה.
N עד 1קריאות רענון טוקן כשכמה בקשות מקבלות 401 בבת אחת
0התחברויות נוספות. החברים מגיעים מחוברים מהפורטל הקיים.
13 יוםממאגר ריק ל־⁠5 מסכים מלאים מבחינת UX
6מסנני חיפוש, בצד הלקוח, בלי פנייה לשרת בכל לחיצה
39קומפוננטות React בכ־⁠6,000 שורות TypeScript ו־⁠SCSS
80מחרוזות ממשק בעברית. הפריסה בנויה מימין לשמאל מההתחלה.
⁦100%⁩מהקומיטים עוברים lint ועיצוב אוטומטי לפני הקומיט
הבעיה

קטלוג שלם של סדנאות, ואין לחברים דרך עצמאית להגיע אליו

הארגון מפעיל סדנאות בריאות ואיכות חיים בהרבה מרפאות: הכנה ללידה והדרכת הנקה, תזונה, כושר, הורות וניהול מתחים. לכל סדנה יש כמה מועדים, מנחים ושפות, ושני מסלולי מחיר לפי התוכנית של החבר. החברים היו צריכים מקום אחד שבו אפשר למצוא סדנה שמתאימה לשבוע שלהם, לראות את המחיר שרלוונטי להם, ואחר כך לשנות מועד או לבטל בלי להתקשר למוקד. כל זה היה צריך לעבוד בטלפון, בעברית, ובלי לבקש מהם להתחבר שוב.

מה היה באחריותי

הארכיטקטורה והפרונטאנד, עם הידיים על הקוד

  • קבעתי את ארכיטקטורת הפרונטאנד: ניתוב עם layouts ציבורי, מסונן ופרטי, ניהול state, לקוח API, קונפיגורציה בזמן ריצה ו־⁠i18n.
  • תכננתי את העברת ההתחברות מפורטל החברים: החלפת טוקן, claims מוקלדים של המשתמש וכתובת התחברות לכל סביבה.
  • בניתי את מסלול העיון (בית, תחום, סוג סדנה, סדנה בודדת) ואת אזור ”הסדנאות שלי” עם שינוי מועד וביטול.
  • מימשתי את מודל הסינון המשותף ואת הפריסה הרספונסיבית מימין לשמאל לפי עיצובי ה־⁠Figma.
  • הובלתי את תהליך ה־⁠Pull Requests ואת סבב המינוח שיישר את הקוד והממשק עם השפה של הלקוח.
מה בניתי

חמישה מסכים סביב מסע אחד

דף הבית

באנר ראשי, קרוסלת ”כדאי להכיר” שמציגה תמיד כרטיסים שלמים, ורשת של תחומי סדנאות.

דף תחום

צ׳יפים של נושאים וסרגל צד עם 6 מסננים: מיקום, פרונטלי או מקוון, שפה, מנחה, שעות וימים מועדפים. כפתור ”ניקוי” אחד מאפס את כולם.

סוג סדנה

כל המחזורים המתוכננים של הסדנה, עם המפגשים, המיקום, המנחה, המחיר לחברים ולמי שאינם חברים, ומספר המקומות שנותרו.

סדנה בודדת

פרטי המפגשים, התאריכים, השפה ושני מסלולי המחיר, עם פעולת אישור אחת.

הסדנאות שלי

לשוניות לסדנאות עתידיות ולסדנאות שהסתיימו. בכל הרשמה מופיע מועד הביטול האחרון, ויש בה ביטול, שינוי מועד (חלון עם המחזורים האחרים) והוספה ליומן.

ארכיטקטורה

בילד סטטי אחד, קונפיגורציה בזמן ריצה, התחברות בהעברה

התרשים רחב מהמסך: החליקו הצידה לצפייה בכולו →

הפרונטאנד נפרס כקבצים סטטיים. ההבדל בין הסביבות הוא רק קובץ JSON קטן שהאפליקציה קוראת לפני הרינדור, כך שאותו בילד בדיוק עובר מ־⁠test ל־⁠production בלי שינוי. ה־⁠API של הסדנאות נבנה בנפרד, והממשק כבר רץ על נתוני דמה שבנויים בדיוק לפי החוזה המוקלד שלו.

החלטות תכנון

להחליט מוקדם איפה התפרים, כדי שהאינטגרציה תהיה רק החלפה

להתחיל מתוך ערכת הפתיחה הפנימית של React

Vite, Redux Toolkit, עזר למחזור החיים של קריאות axios, i18n ו־⁠hooks של lint היו מוכנים מהיום הראשון, והקומיט הראשון של פיצ׳ר נכנס יום אחרי הקומיט ההתחלתי. המחיר: ערכת הפתיחה מביאה תלויות שהאפליקציה עוד לא צריכה (מפות, צ׳אט, לוח שנה), והשארנו אותן לניקוי בהמשך.

קונפיגורציה בזמן ריצה, לא בזמן בילד

כתובת ה־⁠API, המצב ושפת ברירת המחדל מגיעים מקובץ JSON שנטען לפני הרינדור הראשון. ארטיפקט אחד משרת את כל הסביבות. המחיר: בקשה נוספת בעלייה ורגע קצר של מסך ריק עד שהיא חוזרת.

המסננים חיים בטופס אחד, ברמת ה־⁠layout

Provider אחד של react-hook-form עוטף את ה־⁠layout המסונן, כך שסרגל הצד וכל דף מתחתיו קוראים את אותו state. הסינון עצמו הוא hook עם memoization. המחיר: המסננים עוד לא נשמרים ב־⁠URL, ולכן אי אפשר לשתף תצוגה מסוננת כקישור.

נתוני דמה בתבנית של החוזה

סדנאות, סוגים, נושאים ומפגשים מוגדרים כמודלים מוקלדים, ונתוני הדמה משתמשים בדיוק באותם טיפוסים. כשה־⁠API יהיה מוכן, מחליפים את מקור הנתונים והקומפוננטות לא משתנות. המחיר: 15 סימוני TODO שצריך לעקוב אחריהם עד האינטגרציה.

מימין לשמאל מההתחלה

שורש האפליקציה מוגדר RTL וכל מחרוזת עוברת דרך i18n מהיום הראשון, בלי טקסט קשיח בקומפוננטות. כך גרסה בערבית או באנגלית תהיה רק קובץ תרגום. המחיר: יותר תשומת לב לאייקונים, לחצים ולכיוון הקרוסלה.

יומן הנדסי

הבעיות ששווה לתעד

גם לבנייה של שבועיים יש מלכודות. אלה הבעיות שעיצבו את הקוד.

26 בנובמבר

החברים כבר מחוברים במקום אחר

החברים מתחברים בפורטל הראשי של הארגון. לבקש מהם להתחבר שוב בשביל הסדנאות לא היה אופציה, ולכל סביבה יש כתובת פורטל אחרת.

הפורטל מפנה לנתיב התחברות ייעודי עם טוקן חתום. האפליקציה מחליפה אותו בטוקן API משלה, מפענחת את ה־⁠claims למשתמש מוקלד (זכאות לתוכנית, מסלול מחיר, אמצעי תשלום) ופותחת את ”הסדנאות שלי”. כתובת ההתחברות נקבעת לפי שם המארח, לכל אחת מ־⁠4 הסביבות.

26 בנובמבר

שמות בעברית, ו־⁠payload בתוך טוקן

פרטי המשתמש מגיעים כמחרוזת JSON בתוך ה־⁠payload של ה־⁠JWT, ו־⁠atob מחזיר בתים גולמיים ולא טקסט UTF-8, כך ששמות בעברית יוצאים משובשים.

מנרמלים base64url, מקודדים כל בית ב־⁠percent-encoding ומפענחים כ־⁠UTF-8, ואז מפרסרים פעמיים: פעם ל־⁠payload ופעם ל־⁠claims המקוננים. שאר האפליקציה רואה רק IUser מוקלד.

אימות

בקשות במקביל, רענונים במקביל

דף אחד שולח כמה קריאות API בבת אחת. כשטוקן הגישה פג, כל אחת מהן מקבלת 401, וכל אחת הייתה מתחילה רענון משלה.

Promise אחד של רענון משותף לכל הבקשות שנכשלו. כל בקשה מקורית מנסה שוב פעם אחת עם הטוקן החדש. אם הרענון נכשל, הסשן מתנקה.

תשתית

ארבע סביבות, בילד אחד

הטמעת ערכי הסביבה בזמן הבילד הייתה מחייבת בילד נפרד לכל סביבה, ואז הבילד שנבדק הוא לא הבילד שעולה לאוויר.

קומפוננטת שער טוענת את appSettings.json ולא מרנדרת כלום עד שהוא מגיע. כל כתובות ה־⁠API מחושבות ממנו רק בזמן השימוש.

30 בנובמבר – 4 בדצמבר

מה צריך להיות ”בוקר + יום א׳ + עברית”?

עם שישה מסננים, המימושים המתבקשים סותרים זה את זה: בחירה של שני ימים מצמצמת את הרשימה או מרחיבה אותה? ומה עם סדנה שלא הוגדרה לה שפה?

ערכים בתוך אותו מסנן מחוברים ב־⁠OR, מסננים שונים ב־⁠AND, מסנן ריק לא מגביל, וסדנה בלי ערך מוחרגת רק כשהמסנן הזה פעיל. כל זה הוא hook אחד עם memoization שמונע מהטופס המשותף. אפשר לנסות למטה.

1 בדצמבר

כרטיסים חתוכים בקרוסלה

בהרבה רוחבי מסך קרוסלת ”כדאי להכיר” הציגה חצי כרטיס בקצה, וזה נראה שבור בטלפון.

מספר הכרטיסים הגלויים מחושב מרוחב המכל, מרוחב הכרטיס הקבוע ומהרווח, כך שמוצגים רק כרטיסים שלמים, והחישוב מתעדכן בשינוי גודל.

⁦2–3⁩ בדצמבר

השמות שלנו לא היו השמות של הלקוח

בקוד היה כתוב ”workspace”, ”category” ו־⁠”topic”. אצל הלקוח מדברים על תחום, סוג סדנה ומחזור. הפער הזה התחיל לזלוג לממשק.

עשיתי סבב מינוח אחד על הקומפוננטות, הנתיבים וקובץ התרגום בעברית, ומיזגנו אותו ב־⁠Pull Request אחד שעבר review לפני תחילת האינטגרציה.

נסו בעצמכם

חוקי הסינון, על סדנאות סינתטיות

זה מימוש קטן מחדש של סמנטיקת הסינון, עם נתונים מומצאים ולא עם קוד של הלקוח. בחרו ערכים: הרשימה מצטמצמת בין מסננים ומתרחבת בתוך מסנן.

תוצאה

איפה זה עומד

תחום
לפני
אחרי
מציאת סדנה
אין קטלוג לחברים
עיון לפי תחום, 6 מסננים, שני מסלולי המחיר מוצגים
שינוי מועד או ביטול
דרך מוקד שירות (כ־⁠3 דקות לשיחה, הערכה)
שירות עצמי ב”הסדנאות שלי”, כמה לחיצות
התחברות
היה נדרש login נפרד
0 התחברויות נוספות: העברת טוקן מהפורטל
סביבות
בילד לכל סביבה (4)
בילד אחד, קונפיגורציה בזמן ריצה
טוקן שפג, N קריאות
N בקשות רענון
רענון משותף אחד, וכל קריאה מנסה שוב פעם אחת
איכות קוד
הסגנון תלוי בכל מפתח
כל קומיט עובר lint ועיצוב אוטומטית

הפרונטאנד מלא מבחינת UX ב־⁠5 מסכים ו־⁠7 נתיבים, ומחכה ל־⁠API של הסדנאות. הצעדים הבאים: להחליף את מקורות הדמה בקריאות API, לשמור את מצב הסינון ב־⁠URL, ולהוסיף טסטים סביב hook הסינון והעברת הטוקן.

טכנולוגיות

טכנולוגיות

פרונטאנד
React 18, TypeScript 5, Vite 5, SCSS
State ונתונים
Redux Toolkit thunks, axios עם interceptors
ממשק
MUI 7, react-hook-form, react-slick, אייקוני SVG דרך svgr
אימות
העברת JWT מפורטל החברים, access token ו־⁠refresh token
i18n
i18next עם HTTP backend, עברית מימין לשמאל
איכות
ESLint, Prettier, Husky ו־⁠lint-staged, review על כל PR

שיחת אבחון חינם · 30 דקות · ללא התחייבות

הביאו תהליך אחד. קבלו תוכנית ומספר לשפר.

כתבו בכמה שורות: איזה תהליך או מערכת, מה כואב היום (זמן, טעויות, עלויות או פיילוט AI שנתקע) ומי עובד עם זה. אחרי שיחה קצרה מקבלים תוך כ־3 ימים תוכנית כתובה והצעת מחיר סגורה.

לשיחת אבחון חינםafif.mansour@gmail.com

נפתח מייל מוכן מראש · מענה תוך יום עסקים

ה־AI קורא. הקוד סופר.

כל הפרויקטים