routes.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * Frontend route helpers for internal navigation.
  3. *
  4. * Why this file exists:
  5. * - Keep route building consistent across the UI (no stringly-typed URLs scattered everywhere).
  6. * - Central place to adjust URL structure if we ever need it.
  7. * - Easy to test in pure Node (no DOM / no Next runtime required).
  8. *
  9. * Notes:
  10. * - We encode dynamic segments defensively via encodeURIComponent.
  11. * - We validate inputs early to catch mistakes during development.
  12. */
  13. /**
  14. * Ensure a dynamic route segment is a non-empty string.
  15. *
  16. * @param {string} name - Human readable segment name for error messages.
  17. * @param {unknown} value - The segment value.
  18. * @returns {string} Trimmed segment.
  19. */
  20. function requireSegment(name, value) {
  21. if (typeof value !== "string") {
  22. throw new Error(`Route segment "${name}" must be a string`);
  23. }
  24. const trimmed = value.trim();
  25. if (!trimmed) {
  26. throw new Error(`Route segment "${name}" must not be empty`);
  27. }
  28. return trimmed;
  29. }
  30. /**
  31. * Encode a segment for safe usage in URLs.
  32. *
  33. * @param {string} name
  34. * @param {unknown} value
  35. * @returns {string}
  36. */
  37. function encodeSegment(name, value) {
  38. return encodeURIComponent(requireSegment(name, value));
  39. }
  40. /**
  41. * Build an absolute path from named segments.
  42. *
  43. * @param {Array<{ name: string, value: unknown }>} parts
  44. * @returns {string}
  45. */
  46. function buildPath(parts) {
  47. const encoded = parts.map((p) => encodeSegment(p.name, p.value));
  48. return `/${encoded.join("/")}`;
  49. }
  50. /**
  51. * /
  52. */
  53. export function homePath() {
  54. return "/";
  55. }
  56. /**
  57. * /login
  58. */
  59. export function loginPath() {
  60. return "/login";
  61. }
  62. /**
  63. * /:branch
  64. *
  65. * @param {string} branch
  66. */
  67. export function branchPath(branch) {
  68. return buildPath([{ name: "branch", value: branch }]);
  69. }
  70. /**
  71. * /:branch/:year
  72. *
  73. * @param {string} branch
  74. * @param {string} year
  75. */
  76. export function yearPath(branch, year) {
  77. return buildPath([
  78. { name: "branch", value: branch },
  79. { name: "year", value: year },
  80. ]);
  81. }
  82. /**
  83. * /:branch/:year/:month
  84. *
  85. * @param {string} branch
  86. * @param {string} year
  87. * @param {string} month
  88. */
  89. export function monthPath(branch, year, month) {
  90. return buildPath([
  91. { name: "branch", value: branch },
  92. { name: "year", value: year },
  93. { name: "month", value: month },
  94. ]);
  95. }
  96. /**
  97. * /:branch/:year/:month/:day
  98. *
  99. * @param {string} branch
  100. * @param {string} year
  101. * @param {string} month
  102. * @param {string} day
  103. */
  104. export function dayPath(branch, year, month, day) {
  105. return buildPath([
  106. { name: "branch", value: branch },
  107. { name: "year", value: year },
  108. { name: "month", value: month },
  109. { name: "day", value: day },
  110. ]);
  111. }
  112. /**
  113. * /:branch/search
  114. *
  115. * @param {string} branch
  116. */
  117. export function searchPath(branch) {
  118. return buildPath([
  119. { name: "branch", value: branch },
  120. { name: "search", value: "search" },
  121. ]);
  122. }