cautpartener / client / src / app / router.ts
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
/**
 * Hash router. Routes:
 *   #/              → lobby (app shell)
 *   #/u/<pub>       → user profile (placeholder until M4)
 *   #/add/<invite>  → accept a contact invite (M2 rendezvous flow)
 *   #/c/<id>        → conversation (M3; pair id = peer pub, group id = chatreg id)
 * Anything else falls back to the lobby.
 */

import { renderAbout } from "./views/about";
import { renderActivityView } from "./views/activity-view";
import { renderAddContact } from "./views/add-contact";
import { renderChatView } from "./views/chat-view";
import { renderLobby } from "./views/lobby";
import { renderPostView } from "./views/post-view";
import { renderUserProfile } from "./views/user-profile";

export type Route =
  | { name: "lobby" }
  | { name: "profile"; pub: string }
  | { name: "add"; invite: string }
  | { name: "chat"; id: string }
  | { name: "post"; shard: string; cid: string }
  | { name: "activity"; shard: string; org: string; aid: string }
  | { name: "about" };

export function parseRoute(hash: string): Route {
  const path = hash.replace(/^#/, "");
  // #/despre — the About + honest-limits page (M6)
  if (path === "/despre" || path === "/despre/") return { name: "about" };
  // #/p/<shard>/<cid> — post permalink (shard is URL-encoded: "cat%7Ccounty")
  const p = /^\/p\/([^/]+)\/([^/]+)$/.exec(path);
  if (p) {
    try {
      return { name: "post", shard: decodeURIComponent(p[1]), cid: decodeURIComponent(p[2]) };
    } catch {
      return { name: "lobby" };
    }
  }
  // #/a/<shard>/<org>/<aid> — activity permalink (P1; shard is URL-encoded)
  const a = /^\/a\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(path);
  if (a) {
    try {
      return {
        name: "activity",
        shard: decodeURIComponent(a[1]),
        org: decodeURIComponent(a[2]),
        aid: decodeURIComponent(a[3]),
      };
    } catch {
      return { name: "lobby" };
    }
  }
  const m = /^\/(u|add|c)\/(.+)$/.exec(path);
  if (m) {
    let arg: string;
    try {
      arg = decodeURIComponent(m[2]);
    } catch {
      // malformed percent-sequence (e.g. "#/add/abc%2") — never blank the app
      return { name: "lobby" };
    }
    if (m[1] === "u") return { name: "profile", pub: arg };
    if (m[1] === "c") return { name: "chat", id: arg };
    return { name: "add", invite: arg };
  }
  return { name: "lobby" }; // "#/", "" and any unknown path → lobby
}

function render(root: HTMLElement): void {
  const route = parseRoute(location.hash);
  switch (route.name) {
    case "profile":
      renderUserProfile(root, route.pub);
      break;
    case "add":
      renderAddContact(root, route.invite);
      break;
    case "chat":
      renderChatView(root, route.id);
      break;
    case "post":
      renderPostView(root, route.shard, route.cid);
      break;
    case "activity":
      renderActivityView(root, route.shard, route.org, route.aid);
      break;
    case "about":
      renderAbout(root);
      break;
    default:
      renderLobby(root);
  }
}

let routerRoot: HTMLElement | null = null;

export function startRouter(root: HTMLElement): void {
  routerRoot = root;
  if (!location.hash) history.replaceState(null, "", "#/");
  window.addEventListener("hashchange", () => render(root));
  render(root);
}

/** Re-render the current route (e.g. after a synced language change). */
export function rerenderRoute(): void {
  if (routerRoot) render(routerRoot);
}

static mirror of HEAD · about · clone: git clone https://cod-sursa.cautpartener.ro/cautpartener.git