|
| 1 | +import { generatePath } from 'react-router'; |
| 2 | + |
| 3 | +export const ROUTES = { |
| 4 | + // Public routes |
| 5 | + PUBLIC_LANDING: '/', |
| 6 | + PUBLIC_ENGAGEMENT_BY_SLUG: '/:slug/:language', |
| 7 | + PUBLIC_DASHBOARD_BY_SLUG: '/:slug/dashboard/:dashboardType/:language', |
| 8 | + PUBLIC_COMMENTS_BY_SLUG: '/:slug/comments/:dashboardType/:language', |
| 9 | + PUBLIC_SURVEY_EDIT_BY_SLUG: '/:slug/edit/:token/:language', |
| 10 | + PUBLIC_ENGAGEMENT_CREATE_FORM: '/engagements/create/form/:language', |
| 11 | + PUBLIC_ENGAGEMENT_VIEW: '/engagements/:engagementId/view/:language', |
| 12 | + PUBLIC_ENGAGEMENT_VIEW_NO_LANG: '/engagements/:engagementId/view', |
| 13 | + PUBLIC_ENGAGEMENT_COMMENTS: '/engagements/:engagementId/comments/:dashboardType/:language', |
| 14 | + PUBLIC_ENGAGEMENT_COMMENTS_NO_LANG: '/engagements/:engagementId/comments/:dashboardType', |
| 15 | + PUBLIC_ENGAGEMENT_DASHBOARD: '/engagements/:engagementId/dashboard/:dashboardType/:language', |
| 16 | + PUBLIC_ENGAGEMENT_DASHBOARD_NO_LANG: '/engagements/:engagementId/dashboard/:dashboardType', |
| 17 | + PUBLIC_ENGAGEMENT_EDIT: '/engagements/:engagementId/edit/:token/:language', |
| 18 | + PUBLIC_MANAGE_SUBSCRIPTION: '/engagements/:engagementId/:subscriptionStatus/:scriptionKey/:language', |
| 19 | + PUBLIC_ENGAGEMENT_FORM: '/engagements/:engagementId/form/:language', |
| 20 | + PUBLIC_SURVEY_SUBMIT: '/surveys/submit/:surveyId/:token/:language', |
| 21 | + PUBLIC_NOT_AVAILABLE: '/not-available', |
| 22 | + PUBLIC_NOT_FOUND: '/not-found', |
| 23 | + |
| 24 | + // Admin routes |
| 25 | + ADMIN_ENGAGEMENT_PREVIEW: '/engagements/:engagementId/preview', |
| 26 | + HOME: '/home', |
| 27 | + SURVEYS: '/surveys', |
| 28 | + SURVEY_CREATE: '/surveys/create', |
| 29 | + SURVEY_BUILD: '/surveys/:surveyId/build', |
| 30 | + SURVEY_REPORT: '/surveys/:surveyId/report', |
| 31 | + SURVEY_ADMIN_SUBMIT: '/surveys/:surveyId/submit', |
| 32 | + SURVEY_COMMENTS: '/surveys/:surveyId/comments', |
| 33 | + SURVEY_COMMENTS_ALL: '/surveys/:surveyId/comments/all', |
| 34 | + SURVEY_SUBMISSION_REVIEW: '/surveys/:surveyId/submissions/:submissionId/review', |
| 35 | + ENGAGEMENTS: '/engagements', |
| 36 | + ENGAGEMENT: '/engagements/:engagementId', |
| 37 | + ENGAGEMENT_SEARCH: '/engagements/search', |
| 38 | + ENGAGEMENT_FORM: '/engagements/:engagementId/form', |
| 39 | + ENGAGEMENT_CREATE: '/engagements/create', |
| 40 | + ENGAGEMENT_CREATE_WIZARD: '/engagements/create/wizard', |
| 41 | + ENGAGEMENT_DETAILS: '/engagements/:engagementId/details', |
| 42 | + ENGAGEMENT_DETAILS_CONFIG_EDIT: '/engagements/:engagementId/details/config/edit', |
| 43 | + ENGAGEMENT_DETAILS_CONFIG: '/engagements/:engagementId/details/config', |
| 44 | + ENGAGEMENT_DETAILS_AUTHORING: '/engagements/:engagementId/details/authoring', |
| 45 | + ENGAGEMENT_DETAILS_ACTIVITY: '/engagements/:engagementId/details/activity', |
| 46 | + ENGAGEMENT_DETAILS_RESULTS: '/engagements/:engagementId/details/results', |
| 47 | + ENGAGEMENT_DETAILS_PUBLISH: '/engagements/:engagementId/details/publish', |
| 48 | + AUTHORING_BANNER: '/engagements/:engagementId/details/authoring/banner', |
| 49 | + AUTHORING_SUMMARY: '/engagements/:engagementId/details/authoring/summary', |
| 50 | + AUTHORING_DETAILS: '/engagements/:engagementId/details/authoring/details', |
| 51 | + AUTHORING_FEEDBACK: '/engagements/:engagementId/details/authoring/feedback', |
| 52 | + AUTHORING_RESULTS: '/engagements/:engagementId/details/authoring/results', |
| 53 | + AUTHORING_SUBSCRIBE: '/engagements/:engagementId/details/authoring/subscribe', |
| 54 | + AUTHORING_MORE: '/engagements/:engagementId/details/authoring/more', |
| 55 | + AUTHORING_PAGE: '/engagements/:engagementId/details/authoring/:page', |
| 56 | + ENGAGEMENT_COMMENTS_DASHBOARD: '/engagements/comments/:dashboardType', |
| 57 | + ENGAGEMENT_DASHBOARD: '/engagements/dashboard/:dashboardType', |
| 58 | + SLUG_COMMENTS_DASHBOARD: '/:slug/comments/:dashboardType', |
| 59 | + SLUG_DASHBOARD: '/:slug/dashboard/:dashboardType', |
| 60 | + METADATA_MANAGEMENT: '/metadatamanagement', |
| 61 | + LANGUAGES: '/languages', |
| 62 | + TENANT_ADMIN: '/tenantadmin', |
| 63 | + TENANT_ADMIN_CREATE: '/tenantadmin/create', |
| 64 | + TENANT_ADMIN_DETAIL: '/tenantadmin/:tenantShortName/detail', |
| 65 | + TENANT_ADMIN_EDIT: '/tenantadmin/:tenantShortName/edit', |
| 66 | + FEEDBACK: '/feedback', |
| 67 | + CALENDAR: '/calendar', |
| 68 | + REPORTING: '/reporting', |
| 69 | + USER_MANAGEMENT: '/usermanagement', |
| 70 | + USER_MANAGEMENT_SEARCH: '/usermanagement/search', |
| 71 | + USER_DETAILS: '/usermanagement/:userId/details', |
| 72 | + UNAUTHORIZED: '/unauthorized', |
| 73 | + ADMIN_NOT_FOUND: '/not-found', |
| 74 | +} as const; |
| 75 | + |
| 76 | +// A key of ROUTES, e.g. 'HOME' or 'SURVEY_BUILD'. |
| 77 | +type RouteKey = keyof typeof ROUTES; |
| 78 | + |
| 79 | +/** |
| 80 | + * Extract param names from a route pattern. |
| 81 | + * '/manage/surveys/:surveyId/build' => 'surveyId' |
| 82 | + * '/manage/engagements/:engagementId/details/:section' => 'engagementId' | 'section' |
| 83 | + */ |
| 84 | +type ExtractRouteParams<T extends string> = T extends `${string}:${infer Param}/${infer Rest}` |
| 85 | + ? |
| 86 | + | (Param extends `${infer CleanParam}?` |
| 87 | + ? CleanParam |
| 88 | + : Param extends `${infer CleanParam}*` |
| 89 | + ? CleanParam |
| 90 | + : Param) |
| 91 | + | ExtractRouteParams<`/${Rest}`> |
| 92 | + : T extends `${string}:${infer Param}` |
| 93 | + ? Param extends `${infer CleanParam}?` |
| 94 | + ? CleanParam |
| 95 | + : Param extends `${infer CleanParam}*` |
| 96 | + ? CleanParam |
| 97 | + : Param |
| 98 | + : never; |
| 99 | + |
| 100 | +/** |
| 101 | + * Get the params object type for a route. |
| 102 | + * If no params, returns {}; otherwise returns record of required params. |
| 103 | + */ |
| 104 | +type RouteParams<Path extends string> = |
| 105 | + ExtractRouteParams<Path> extends never |
| 106 | + ? Record<string, never> |
| 107 | + : { [P in ExtractRouteParams<Path>]: string | number }; |
| 108 | + |
| 109 | +/** |
| 110 | + * Type-safe route path generator. (wraps react-router's generatePath) |
| 111 | + * Automatically extracts required params from the route pattern. |
| 112 | + * |
| 113 | + * @example |
| 114 | + * getPath(ROUTES.HOME) // '/home' |
| 115 | + * getPath(ROUTES.ENGAGEMENT_DETAILS, {'enagementId': '123' }) // '/engagements/123/details/authoring' |
| 116 | + * getPath(ROUTES.HOME, { surveyId: '123' }) // ✗ TS error: HOME has no surveyId param |
| 117 | + * @see https://reactrouter.com/api/utils/generatePath |
| 118 | + */ |
| 119 | +export function getPath<Path extends (typeof ROUTES)[RouteKey]>( |
| 120 | + route: Path, |
| 121 | + ...params: ExtractRouteParams<Path> extends never ? [] : [params: RouteParams<Path>] |
| 122 | +): string { |
| 123 | + return generatePath(route as string, params[0] as { [key: string]: string | null } | undefined); |
| 124 | +} |
| 125 | + |
| 126 | +export type { RouteKey, RouteParams }; |
0 commit comments