|
|
|
@@ -0,0 +1,418 @@
|
|
|
|
|
diff --git a/index.html b/index.html
|
|
|
|
|
index 6894507..a917f5d 100644
|
|
|
|
|
--- a/index.html
|
|
|
|
|
+++ b/index.html
|
|
|
|
|
@@ -14,7 +14,7 @@
|
|
|
|
|
property="og:description"
|
|
|
|
|
content="Decentralized GitHub alternative over Nostr"
|
|
|
|
|
/>
|
|
|
|
|
- <meta property="og:image" content="/og-image.png" />
|
|
|
|
|
+ <meta property="og:image" content="%BASE_URL%og-image.png" />
|
|
|
|
|
<meta property="og:image:width" content="1200" />
|
|
|
|
|
<meta property="og:image:height" content="630" />
|
|
|
|
|
<meta
|
|
|
|
|
@@ -22,15 +22,19 @@
|
|
|
|
|
content="%APP_NAME% — git collaboration without the platform"
|
|
|
|
|
/>
|
|
|
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
|
|
|
- <meta name="twitter:image" content="/og-image.png" />
|
|
|
|
|
+ <meta name="twitter:image" content="%BASE_URL%og-image.png" />
|
|
|
|
|
<meta
|
|
|
|
|
http-equiv="content-security-policy"
|
|
|
|
|
- content="default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; frame-src 'self' https:; font-src 'self'; base-uri 'self'; manifest-src 'self'; connect-src 'self' blob: https: wss:; img-src 'self' data: blob: https:; media-src 'self' https:"
|
|
|
|
|
+ content="default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; frame-src 'self' http: https:; font-src 'self'; base-uri 'self'; manifest-src 'self'; connect-src 'self' blob: https: wss:; img-src 'self' data: blob: https:; media-src 'self' https:"
|
|
|
|
|
/>
|
|
|
|
|
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
|
|
|
- <link rel="icon" type="image/png" href="/favicon.png" />
|
|
|
|
|
- <link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
|
|
|
|
|
- <link rel="manifest" href="/manifest.webmanifest" />
|
|
|
|
|
+ <link rel="icon" type="image/svg+xml" href="%BASE_URL%favicon.svg" />
|
|
|
|
|
+ <link rel="icon" type="image/png" href="%BASE_URL%favicon.png" />
|
|
|
|
|
+ <link rel="apple-touch-icon" href="%BASE_URL%icons/apple-touch-icon.png" />
|
|
|
|
|
+ <link
|
|
|
|
|
+ rel="manifest"
|
|
|
|
|
+ href="/manifest.webmanifest"
|
|
|
|
|
+ crossorigin="use-credentials"
|
|
|
|
|
+ />
|
|
|
|
|
<style>
|
|
|
|
|
body {
|
|
|
|
|
margin: 0;
|
|
|
|
|
@@ -41,7 +45,9 @@
|
|
|
|
|
background: #16171e;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
- <script src="/theme-init.js"></script>
|
|
|
|
|
+ <script src="%BASE_URL%theme-init.js"></script>
|
|
|
|
|
+ <script src="%BASE_URL%archipelago-nostrdb-config.js"></script>
|
|
|
|
|
+ <script data-no-nip98 src="/nostr-provider.js?v=tab-signer-v4"></script>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<div id="root">
|
|
|
|
|
@@ -119,7 +125,7 @@
|
|
|
|
|
<div id="splash-content">
|
|
|
|
|
<img
|
|
|
|
|
class="splash-logo"
|
|
|
|
|
- src="/icons/icon-192x192.png"
|
|
|
|
|
+ src="%BASE_URL%icons/icon-192x192.png"
|
|
|
|
|
width="64"
|
|
|
|
|
height="64"
|
|
|
|
|
alt=""
|
|
|
|
|
diff --git a/public/archipelago-nostrdb-config.js b/public/archipelago-nostrdb-config.js
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000..8f598c4
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/public/archipelago-nostrdb-config.js
|
|
|
|
|
@@ -0,0 +1,7 @@
|
|
|
|
|
+// window.nostrdb.js probes a developer-only relay at localhost:4869 unless
|
|
|
|
|
+// configured before the app module graph loads. On an installed node that
|
|
|
|
|
+// address means the user's own device, can never be the app's cache relay,
|
|
|
|
|
+// and is correctly blocked by GitWorkshop's production CSP.
|
|
|
|
|
+window.nostrdbConfig = Object.assign({}, window.nostrdbConfig || {}, {
|
|
|
|
|
+ localRelays: [],
|
|
|
|
|
+});
|
|
|
|
|
diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx
|
|
|
|
|
index 0f681e7..16dff02 100644
|
|
|
|
|
--- a/src/AppRouter.tsx
|
|
|
|
|
+++ b/src/AppRouter.tsx
|
|
|
|
|
@@ -20,6 +20,8 @@ import { MaintainerAcceptanceMonitor } from "./components/MaintainerAcceptanceMo
|
|
|
|
|
import { useRepoPath } from "./hooks/useRepoPath";
|
|
|
|
|
import { REPO_KIND } from "./lib/nip34";
|
|
|
|
|
import { getGitWorkshopPath } from "./lib/gitworkshopUrl";
|
|
|
|
|
+import { useLoginActions } from "./hooks/useLoginActions";
|
|
|
|
|
+import { accounts } from "./services/accounts";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handles public GitWorkshop links in native builds. This stays inside the
|
|
|
|
|
@@ -365,9 +367,46 @@ function LegacyRedirect() {
|
|
|
|
|
return <RepoLayout />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** Turn an eager node identity choice into GitWorkshop's extension login. */
|
|
|
|
|
+function ArchipelagoIdentityLogin() {
|
|
|
|
|
+ const login = useLoginActions();
|
|
|
|
|
+ const loginRef = useRef(login.extension);
|
|
|
|
|
+ const loginRunning = useRef(false);
|
|
|
|
|
+ loginRef.current = login.extension;
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ const bridge = (
|
|
|
|
|
+ window as Window & {
|
|
|
|
|
+ archipelagoNostr?: {
|
|
|
|
|
+ onIdentitySelected?: (
|
|
|
|
|
+ callback: (identity: { nostr_pubkey: string }) => void,
|
|
|
|
|
+ ) => () => void;
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ ).archipelagoNostr;
|
|
|
|
|
+ if (!bridge?.onIdentitySelected) return;
|
|
|
|
|
+
|
|
|
|
|
+ return bridge.onIdentitySelected(() => {
|
|
|
|
|
+ if (accounts.getActive() || loginRunning.current) return;
|
|
|
|
|
+ loginRunning.current = true;
|
|
|
|
|
+ void loginRef
|
|
|
|
|
+ .current()
|
|
|
|
|
+ .catch((error) => {
|
|
|
|
|
+ console.error("Archipelago automatic login failed:", error);
|
|
|
|
|
+ })
|
|
|
|
|
+ .finally(() => {
|
|
|
|
|
+ loginRunning.current = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
function AppRouter() {
|
|
|
|
|
return (
|
|
|
|
|
- <BrowserRouter>
|
|
|
|
|
+ <BrowserRouter basename={import.meta.env.BASE_URL}>
|
|
|
|
|
+ <ArchipelagoIdentityLogin />
|
|
|
|
|
<NativeGitWorkshopLinks />
|
|
|
|
|
<NativeAndroidBackButton />
|
|
|
|
|
<ScrollToTop />
|
|
|
|
|
diff --git a/src/components/AppFooter.tsx b/src/components/AppFooter.tsx
|
|
|
|
|
index 3eb76d2..22b079b 100644
|
|
|
|
|
--- a/src/components/AppFooter.tsx
|
|
|
|
|
+++ b/src/components/AppFooter.tsx
|
|
|
|
|
@@ -62,7 +62,7 @@ export function AppFooter() {
|
|
|
|
|
className="flex items-center gap-2 hover:opacity-80 transition-opacity w-fit"
|
|
|
|
|
>
|
|
|
|
|
<img
|
|
|
|
|
- src="/icons/icon.svg"
|
|
|
|
|
+ src={`${import.meta.env.BASE_URL}icons/icon.svg`}
|
|
|
|
|
alt="GitWorkshop"
|
|
|
|
|
className="h-6 w-6"
|
|
|
|
|
/>
|
|
|
|
|
diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx
|
|
|
|
|
index b31aa79..cec9d39 100644
|
|
|
|
|
--- a/src/components/AppHeader.tsx
|
|
|
|
|
+++ b/src/components/AppHeader.tsx
|
|
|
|
|
@@ -155,7 +155,11 @@ export function AppHeader() {
|
|
|
|
|
to="/"
|
|
|
|
|
className="group transition-opacity hover:opacity-80 shrink-0"
|
|
|
|
|
>
|
|
|
|
|
- <img src="/icons/icon.svg" alt="GitWorkshop" className="h-8 w-8" />
|
|
|
|
|
+ <img
|
|
|
|
|
+ src={`${import.meta.env.BASE_URL}icons/icon.svg`}
|
|
|
|
|
+ alt="GitWorkshop"
|
|
|
|
|
+ className="h-8 w-8"
|
|
|
|
|
+ />
|
|
|
|
|
</Link>
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-2 ml-auto">
|
|
|
|
|
diff --git a/src/main.tsx b/src/main.tsx
|
|
|
|
|
index 1e4fead..ae3a045 100644
|
|
|
|
|
--- a/src/main.tsx
|
|
|
|
|
+++ b/src/main.tsx
|
|
|
|
|
@@ -10,9 +10,11 @@ import "@fontsource-variable/inter";
|
|
|
|
|
// itself, so subsequent loads are fully uncontrolled. Capacitor packages local
|
|
|
|
|
// assets and does not use this web-deployment cleanup worker.
|
|
|
|
|
if (!Capacitor.isNativePlatform() && "serviceWorker" in navigator) {
|
|
|
|
|
- navigator.serviceWorker.register("/sw.js").catch(() => {
|
|
|
|
|
- /* ignore — browser may block in certain envs */
|
|
|
|
|
- });
|
|
|
|
|
+ navigator.serviceWorker
|
|
|
|
|
+ .register(`${import.meta.env.BASE_URL}sw.js`)
|
|
|
|
|
+ .catch(() => {
|
|
|
|
|
+ /* ignore — browser may block in certain envs */
|
|
|
|
|
+ });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createRoot(document.getElementById("root")!).render(
|
|
|
|
|
diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx
|
|
|
|
|
index 18e3593..685c422 100644
|
|
|
|
|
--- a/src/pages/NotFound.tsx
|
|
|
|
|
+++ b/src/pages/NotFound.tsx
|
|
|
|
|
@@ -28,7 +28,7 @@ const NotFound = () => {
|
|
|
|
|
Oops! Page not found
|
|
|
|
|
</p>
|
|
|
|
|
<a
|
|
|
|
|
- href="/"
|
|
|
|
|
+ href={import.meta.env.BASE_URL}
|
|
|
|
|
className="text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 underline"
|
|
|
|
|
>
|
|
|
|
|
Return to Home
|
|
|
|
|
diff --git a/src/services/settings.ts b/src/services/settings.ts
|
|
|
|
|
index 8f9a1a7..5438a72 100644
|
|
|
|
|
--- a/src/services/settings.ts
|
|
|
|
|
+++ b/src/services/settings.ts
|
|
|
|
|
@@ -124,8 +124,6 @@ export const fallbackRelaysCustomised$ = isCustomised$(
|
|
|
|
|
* These are used by the event loaders to find events more efficiently.
|
|
|
|
|
*/
|
|
|
|
|
export const DEFAULT_LOOKUP_RELAYS = normalizeRelayList([
|
|
|
|
|
- "wss://purplepag.es",
|
|
|
|
|
- "wss://index.hzrd149.com",
|
|
|
|
|
"wss://indexer.coracle.social",
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
diff --git a/vite.config.ts b/vite.config.ts
|
|
|
|
|
index 0534fb9..d94fc70 100644
|
|
|
|
|
--- a/vite.config.ts
|
|
|
|
|
+++ b/vite.config.ts
|
|
|
|
|
@@ -39,37 +39,38 @@ function htmlAppNamePlugin(): Plugin {
|
|
|
|
|
*/
|
|
|
|
|
function manifestPlugin(): Plugin {
|
|
|
|
|
const virtualId = "/manifest.webmanifest";
|
|
|
|
|
+ const appBase = process.env.APP_BASE_PATH ?? "/";
|
|
|
|
|
const manifest = JSON.stringify(
|
|
|
|
|
{
|
|
|
|
|
name: "GitWorkshop.dev",
|
|
|
|
|
short_name: "GitWorkshop",
|
|
|
|
|
description: "Decentralized GitHub alternative over Nostr",
|
|
|
|
|
- start_url: "/",
|
|
|
|
|
+ start_url: appBase,
|
|
|
|
|
display: "standalone",
|
|
|
|
|
background_color: "#16171e",
|
|
|
|
|
theme_color: "#16171e",
|
|
|
|
|
categories: ["development", "productivity", "utilities"],
|
|
|
|
|
icons: [
|
|
|
|
|
{
|
|
|
|
|
- src: "/icons/icon-192x192.png",
|
|
|
|
|
+ src: `${appBase}icons/icon-192x192.png`,
|
|
|
|
|
sizes: "192x192",
|
|
|
|
|
type: "image/png",
|
|
|
|
|
purpose: "any",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
- src: "/icons/icon-512x512.png",
|
|
|
|
|
+ src: `${appBase}icons/icon-512x512.png`,
|
|
|
|
|
sizes: "512x512",
|
|
|
|
|
type: "image/png",
|
|
|
|
|
purpose: "any",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
- src: "/icons/pwa-maskable-192x192.png",
|
|
|
|
|
+ src: `${appBase}icons/pwa-maskable-192x192.png`,
|
|
|
|
|
sizes: "192x192",
|
|
|
|
|
type: "image/png",
|
|
|
|
|
purpose: "maskable",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
- src: "/icons/pwa-maskable-512x512.png",
|
|
|
|
|
+ src: `${appBase}icons/pwa-maskable-512x512.png`,
|
|
|
|
|
sizes: "512x512",
|
|
|
|
|
type: "image/png",
|
|
|
|
|
purpose: "maskable",
|
|
|
|
|
@@ -82,6 +83,12 @@ function manifestPlugin(): Plugin {
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
name: "manifest",
|
|
|
|
|
+ transformIndexHtml(html) {
|
|
|
|
|
+ return html.replace(
|
|
|
|
|
+ 'href="/manifest.webmanifest"',
|
|
|
|
|
+ `href="${appBase}manifest.webmanifest"`,
|
|
|
|
|
+ );
|
|
|
|
|
+ },
|
|
|
|
|
configureServer(server) {
|
|
|
|
|
server.middlewares.use((req, res, next) => {
|
|
|
|
|
if (req.url === virtualId) {
|
|
|
|
|
@@ -104,6 +111,10 @@ function manifestPlugin(): Plugin {
|
|
|
|
|
|
|
|
|
|
// https://vitejs.dev/config/
|
|
|
|
|
export default defineConfig(() => ({
|
|
|
|
|
+ // Archipelago serves GitWorkshop behind the dashboard origin. Vite's base
|
|
|
|
|
+ // controls emitted asset URLs while BrowserRouter consumes the same value
|
|
|
|
|
+ // below, so repository routes remain valid below that mount point.
|
|
|
|
|
+ base: process.env.APP_BASE_PATH ?? "/",
|
|
|
|
|
define: {
|
|
|
|
|
__APP_NAME__: JSON.stringify(name),
|
|
|
|
|
__APP_RELEASE_VERSION__: JSON.stringify(
|
|
|
|
|
diff --git a/src/components/auth/AccountSwitcher.tsx b/src/components/auth/AccountSwitcher.tsx
|
|
|
|
|
index f59a7d2..3168910 100644
|
|
|
|
|
--- a/src/components/auth/AccountSwitcher.tsx
|
|
|
|
|
+++ b/src/components/auth/AccountSwitcher.tsx
|
|
|
|
|
@@ -46,7 +46,7 @@ function SignerTypeBadge({ account }: { account: IAccount }) {
|
|
|
|
|
return (
|
|
|
|
|
<span className="flex items-center gap-1 text-[10px] text-muted-foreground">
|
|
|
|
|
<Puzzle className="w-3 h-3" />
|
|
|
|
|
- Extension
|
|
|
|
|
+ Extension / Archipelago
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
if (account instanceof NostrConnectAccount)
|
|
|
|
|
diff --git a/src/components/auth/LoginDialog.tsx b/src/components/auth/LoginDialog.tsx
|
|
|
|
|
index 11f6716..0bba6a2 100644
|
|
|
|
|
--- a/src/components/auth/LoginDialog.tsx
|
|
|
|
|
+++ b/src/components/auth/LoginDialog.tsx
|
|
|
|
|
@@ -239,9 +239,15 @@ const LoginDialog: React.FC<LoginDialogProps> = ({
|
|
|
|
|
try {
|
|
|
|
|
if (!("nostr" in window)) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
- "Nostr extension not found. Please install a NIP-07 extension.",
|
|
|
|
|
+ "No NIP-07 signer found. Open GitWorkshop through Archipelago or install a browser extension.",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
+ const archipelago = (
|
|
|
|
|
+ window as Window & {
|
|
|
|
|
+ archipelagoNostr?: { selectIdentity?: () => Promise<unknown> };
|
|
|
|
|
+ }
|
|
|
|
|
+ ).archipelagoNostr;
|
|
|
|
|
+ if (archipelago?.selectIdentity) await archipelago.selectIdentity();
|
|
|
|
|
await login.extension();
|
|
|
|
|
onLogin();
|
|
|
|
|
onClose();
|
|
|
|
|
@@ -437,7 +443,9 @@ const LoginDialog: React.FC<LoginDialogProps> = ({
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
>
|
|
|
|
|
<Puzzle className="w-4 h-4" />
|
|
|
|
|
- {isLoading ? "Logging in..." : "Log in with Extension"}
|
|
|
|
|
+ {isLoading
|
|
|
|
|
+ ? "Logging in..."
|
|
|
|
|
+ : "Log in with Extension / Archipelago"}
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
|
|
|
|
|
index b8de377..7f72f31 100644
|
|
|
|
|
--- a/src/pages/Dashboard.tsx
|
|
|
|
|
+++ b/src/pages/Dashboard.tsx
|
|
|
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
|
|
ChevronUp,
|
|
|
|
|
Pin,
|
|
|
|
|
Search,
|
|
|
|
|
+ Globe2,
|
|
|
|
|
} from "lucide-react";
|
|
|
|
|
import { CreateRepoDialog } from "@/components/CreateRepoDialog";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
@@ -36,6 +37,7 @@ import { useUserActivity } from "@/hooks/useUserActivity";
|
|
|
|
|
import { useUserRepositories } from "@/hooks/useUserRepositories";
|
|
|
|
|
import { useUserFollowedRepos } from "@/hooks/useUserFollowedRepos";
|
|
|
|
|
import { useUserPinnedCoords } from "@/hooks/useUserPinnedRepos";
|
|
|
|
|
+import { useRepositorySearch } from "@/hooks/useRepositorySearch";
|
|
|
|
|
import { useNotifications } from "@/hooks/useNotifications";
|
|
|
|
|
import { useUserProfileSubscription } from "@/hooks/useUserProfileSubscription";
|
|
|
|
|
import { useUserPath } from "@/hooks/useUserPath";
|
|
|
|
|
@@ -409,6 +411,63 @@ function FollowedReposPanel({ pubkey }: { pubkey: string }) {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// Recent repositories from the wider Nostr network
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+function NetworkRepositoriesPanel() {
|
|
|
|
|
+ const { repos, isLoading } = useRepositorySearch("");
|
|
|
|
|
+ const recent = repos?.slice(0, 6);
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="h-fit">
|
|
|
|
|
+ <div className="pb-3 flex items-center justify-between gap-3">
|
|
|
|
|
+ <h3 className="text-base font-semibold flex items-center gap-2">
|
|
|
|
|
+ <Globe2 className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
+ Nostr network
|
|
|
|
|
+ </h3>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ className="h-7 px-2 text-xs text-muted-foreground hover:text-foreground"
|
|
|
|
|
+ asChild
|
|
|
|
|
+ >
|
|
|
|
|
+ <Link to="/search">
|
|
|
|
|
+ Browse all
|
|
|
|
|
+ <ArrowRight className="h-3 w-3 ml-1" />
|
|
|
|
|
+ </Link>
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {recent === undefined || (isLoading && recent.length === 0) ? (
|
|
|
|
|
+ <div className="space-y-1">
|
|
|
|
|
+ {Array.from({ length: 5 }).map((_, i) => (
|
|
|
|
|
+ <RepoRowSkeleton key={i} />
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : recent.length > 0 ? (
|
|
|
|
|
+ <div className="space-y-0.5">
|
|
|
|
|
+ {recent.map((repo) => (
|
|
|
|
|
+ <RepoListItem
|
|
|
|
|
+ key={`${repo.selectedMaintainer}:${repo.dTag}`}
|
|
|
|
|
+ repo={repo}
|
|
|
|
|
+ />
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <div className="py-6 text-center">
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">
|
|
|
|
|
+ No network repositories available
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p className="text-xs text-muted-foreground/60 mt-1">
|
|
|
|
|
+ Check the git index relay in Settings
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Embedded notifications panel (compact, inbox only, max 5)
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
@@ -591,6 +650,8 @@ export function Dashboard() {
|
|
|
|
|
<MyRepositoriesPanel pubkey={pubkey} />
|
|
|
|
|
<Separator className="opacity-40" />
|
|
|
|
|
<FollowedReposPanel pubkey={pubkey} />
|
|
|
|
|
+ <Separator className="opacity-40" />
|
|
|
|
|
+ <NetworkRepositoriesPanel />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|