|
@@ -0,0 +1,134 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * Frontend route helpers for internal navigation.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Why this file exists:
|
|
|
|
|
+ * - Keep route building consistent across the UI (no stringly-typed URLs scattered everywhere).
|
|
|
|
|
+ * - Central place to adjust URL structure if we ever need it.
|
|
|
|
|
+ * - Easy to test in pure Node (no DOM / no Next runtime required).
|
|
|
|
|
+ *
|
|
|
|
|
+ * Notes:
|
|
|
|
|
+ * - We encode dynamic segments defensively via encodeURIComponent.
|
|
|
|
|
+ * - We validate inputs early to catch mistakes during development.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Ensure a dynamic route segment is a non-empty string.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} name - Human readable segment name for error messages.
|
|
|
|
|
+ * @param {unknown} value - The segment value.
|
|
|
|
|
+ * @returns {string} Trimmed segment.
|
|
|
|
|
+ */
|
|
|
|
|
+function requireSegment(name, value) {
|
|
|
|
|
+ if (typeof value !== "string") {
|
|
|
|
|
+ throw new Error(`Route segment "${name}" must be a string`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const trimmed = value.trim();
|
|
|
|
|
+ if (!trimmed) {
|
|
|
|
|
+ throw new Error(`Route segment "${name}" must not be empty`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return trimmed;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Encode a segment for safe usage in URLs.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} name
|
|
|
|
|
+ * @param {unknown} value
|
|
|
|
|
+ * @returns {string}
|
|
|
|
|
+ */
|
|
|
|
|
+function encodeSegment(name, value) {
|
|
|
|
|
+ return encodeURIComponent(requireSegment(name, value));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Build an absolute path from named segments.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {Array<{ name: string, value: unknown }>} parts
|
|
|
|
|
+ * @returns {string}
|
|
|
|
|
+ */
|
|
|
|
|
+function buildPath(parts) {
|
|
|
|
|
+ const encoded = parts.map((p) => encodeSegment(p.name, p.value));
|
|
|
|
|
+ return `/${encoded.join("/")}`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * /
|
|
|
|
|
+ */
|
|
|
|
|
+export function homePath() {
|
|
|
|
|
+ return "/";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * /login
|
|
|
|
|
+ */
|
|
|
|
|
+export function loginPath() {
|
|
|
|
|
+ return "/login";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * /:branch
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} branch
|
|
|
|
|
+ */
|
|
|
|
|
+export function branchPath(branch) {
|
|
|
|
|
+ return buildPath([{ name: "branch", value: branch }]);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * /:branch/:year
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} branch
|
|
|
|
|
+ * @param {string} year
|
|
|
|
|
+ */
|
|
|
|
|
+export function yearPath(branch, year) {
|
|
|
|
|
+ return buildPath([
|
|
|
|
|
+ { name: "branch", value: branch },
|
|
|
|
|
+ { name: "year", value: year },
|
|
|
|
|
+ ]);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * /:branch/:year/:month
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} branch
|
|
|
|
|
+ * @param {string} year
|
|
|
|
|
+ * @param {string} month
|
|
|
|
|
+ */
|
|
|
|
|
+export function monthPath(branch, year, month) {
|
|
|
|
|
+ return buildPath([
|
|
|
|
|
+ { name: "branch", value: branch },
|
|
|
|
|
+ { name: "year", value: year },
|
|
|
|
|
+ { name: "month", value: month },
|
|
|
|
|
+ ]);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * /:branch/:year/:month/:day
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} branch
|
|
|
|
|
+ * @param {string} year
|
|
|
|
|
+ * @param {string} month
|
|
|
|
|
+ * @param {string} day
|
|
|
|
|
+ */
|
|
|
|
|
+export function dayPath(branch, year, month, day) {
|
|
|
|
|
+ return buildPath([
|
|
|
|
|
+ { name: "branch", value: branch },
|
|
|
|
|
+ { name: "year", value: year },
|
|
|
|
|
+ { name: "month", value: month },
|
|
|
|
|
+ { name: "day", value: day },
|
|
|
|
|
+ ]);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * /:branch/search
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string} branch
|
|
|
|
|
+ */
|
|
|
|
|
+export function searchPath(branch) {
|
|
|
|
|
+ return buildPath([
|
|
|
|
|
+ { name: "branch", value: branch },
|
|
|
|
|
+ { name: "search", value: "search" },
|
|
|
|
|
+ ]);
|
|
|
|
|
+}
|