Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ https://wiki.kinic.xyz

The official Kinic Wiki database is:

https://wiki.kinic.xyz/db_kva4v2twg6jv/Wiki?read=anonymous
https://wiki.kinic.xyz/db_kva4v2twg6jv/Wiki

Database ID:

Expand Down
9 changes: 2 additions & 7 deletions extensions/wiki-clipper/scripts/check-candid-drift.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,6 @@ const expectedTypes = {
WriteNodeResult: { kind: "record", fields: { created: "bool", node: "NodeMutationAck" } },
WriteSourceForGenerationResult: { kind: "record", fields: { write: "WriteNodeResult", session_nonce: "text" } }
};
const actorExpectedTypes = {
...expectedTypes,
DatabaseStatus: { kind: "variant", fields: { Hot: "null", Pending: "null", Active: "null", Restoring: "null", Archiving: "null", Archived: "null" } }
};

const expectedMethods = {
authorize_url_ingest_trigger_session: { input: ["UrlIngestTriggerSessionRequest"], output: "ResultUnit", mode: "update" },
get_cycles_billing_config: { input: [], output: "ResultCyclesBillingConfig", mode: "query" },
Expand All @@ -99,7 +94,7 @@ const actorMethods = parseActorMethods(actor);

for (const [name, shape] of Object.entries(expectedTypes)) {
assert.deepEqual(canonicalTypeShape(didTypes[name]), shape, `vfs.did type drift: ${name}`);
assert.deepEqual(actorTypes[name], actorExpectedTypes[name], `extension IDL type drift: ${name}`);
assert.deepEqual(actorTypes[name], shape, `extension IDL type drift: ${name}`);
}

for (const [name, shape] of Object.entries(expectedMethods)) {
Expand Down Expand Up @@ -148,7 +143,7 @@ function parseDidMethods(source) {

function parseActorTypes(source) {
const result = {};
for (const [name, shape] of Object.entries(actorExpectedTypes)) {
for (const [name, shape] of Object.entries(expectedTypes)) {
const initializer = source.match(new RegExp(`const\\s+${name}\\s*=\\s*idl\\.(Record|Variant)\\(\\{([^]*?)\\}\\);`, "m"));
assert.ok(initializer, `extension IDL type missing: ${name}`);
const kind = initializer[1] === "Record" ? "record" : "variant";
Expand Down
4 changes: 1 addition & 3 deletions extensions/wiki-clipper/src/vfs-actor.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export async function createVfsActor({ canisterId, host, identity }) {
function idlFactory({ IDL: idl }) {
const DatabaseRole = idl.Variant({ Reader: idl.Null, Writer: idl.Null, Owner: idl.Null });
const DatabaseStatus = idl.Variant({
Hot: idl.Null,
Pending: idl.Null,
Active: idl.Null,
Restoring: idl.Null,
Expand Down Expand Up @@ -170,8 +169,7 @@ function normalizeDatabaseSummary(raw) {
}

function normalizeDatabaseStatus(status) {
const key = variantKey(status);
return key === "Hot" ? "Active" : key;
return variantKey(status);
}

export async function getCyclesBillingConfigOrNull(actor) {
Expand Down
14 changes: 1 addition & 13 deletions wikibrowser/app/app-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// Where: root wikibrowser layout.
// What: renders the shared dashboard/cycles header with wallet and II controls.
// Why: funding pages should keep the same wallet session and management shell.
import Link from "next/link";
import { usePathname } from "next/navigation";
import { AdminHeader } from "@/components/admin-header";
import { formatTokenAmountFromE8s } from "@/lib/kinic-amount";
Expand All @@ -21,15 +20,14 @@ export function AppHeader() {
login,
logout,
principal,
refreshAuth,
wallet,
walletBalance,
walletBalanceLoading,
walletBusyProvider,
walletControlsLocked
} = useAppSession();

if (pathname !== "/" && pathname !== "/cycles") return null;
if (pathname !== "/dashboard" && pathname !== "/cycles") return null;

const title = pathname === "/cycles" ? "Database cycles purchase" : "Database dashboard";
const connectedWalletLabel = wallet ? `${walletLabel(wallet.provider)} ${shortPrincipal(connectedWalletPrincipal(wallet))}` : null;
Expand All @@ -40,13 +38,6 @@ export function AppHeader() {
<section className="mx-auto max-w-6xl">
<AdminHeader
title={title}
nav={
pathname === "/cycles" ? (
<Link className="text-accent no-underline hover:underline" href="/">
Database dashboard
</Link>
) : null
}
actions={
<>
<WalletControls
Expand All @@ -71,9 +62,6 @@ export function AppHeader() {
onLogout={() => {
void logout();
}}
onRefresh={() => {
void refreshAuth();
}}
/>
</>
}
Expand Down
20 changes: 0 additions & 20 deletions wikibrowser/app/app-session-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ type AppSessionContext = {
authError: string | null;
authLoading: boolean;
authReady: boolean;
authRefreshSeq: number;
principal: string | null;
wallet: ConnectedKinicWallet | null;
walletBalance: string | null;
Expand All @@ -26,7 +25,6 @@ type AppSessionContext = {
disconnectWallet: (provider: HeaderWalletProvider) => void;
logout: () => Promise<void>;
login: () => Promise<void>;
refreshAuth: () => Promise<void>;
refreshWalletBalance: (wallet: ConnectedKinicWallet) => Promise<void>;
setWalletControlsLocked: (locked: boolean) => void;
};
Expand All @@ -41,7 +39,6 @@ export function AppSessionProvider({ children }: { children: ReactNode }) {
const [authError, setAuthError] = useState<string | null>(null);
const [authLoading, setAuthLoading] = useState(true);
const [authReady, setAuthReady] = useState(false);
const [authRefreshSeq, setAuthRefreshSeq] = useState(0);
const [principal, setPrincipal] = useState<string | null>(null);
const [wallet, setWallet] = useState<ConnectedKinicWallet | null>(() => readStoredWallet());
const [walletBalance, setWalletBalance] = useState<string | null>(null);
Expand Down Expand Up @@ -134,22 +131,8 @@ export function AppSessionProvider({ children }: { children: ReactNode }) {
const syncAuth = useCallback(async (client: AuthClient) => {
const authenticated = await client.isAuthenticated();
setPrincipal(authenticated ? client.getIdentity().getPrincipal().toText() : null);
setAuthRefreshSeq((current) => current + 1);
}, []);

const refreshAuth = useCallback(async () => {
if (!authClient) return;
setAuthLoading(true);
setAuthError(null);
try {
await syncAuth(authClient);
} catch (cause) {
setAuthError(errorMessage(cause));
} finally {
setAuthLoading(false);
}
}, [authClient, syncAuth]);

const login = useCallback(async () => {
if (!authClient) return;
setAuthLoading(true);
Expand All @@ -173,7 +156,6 @@ export function AppSessionProvider({ children }: { children: ReactNode }) {
try {
await authClient.logout();
setPrincipal(null);
setAuthRefreshSeq((current) => current + 1);
clearWallet();
} catch (cause) {
setAuthError(errorMessage(cause));
Expand Down Expand Up @@ -225,7 +207,6 @@ export function AppSessionProvider({ children }: { children: ReactNode }) {
authError,
authLoading,
authReady,
authRefreshSeq,
principal,
wallet,
walletBalance,
Expand All @@ -237,7 +218,6 @@ export function AppSessionProvider({ children }: { children: ReactNode }) {
disconnectWallet,
login,
logout,
refreshAuth,
refreshWalletBalance,
setWalletControlsLocked
}}
Expand Down
9 changes: 2 additions & 7 deletions wikibrowser/app/cli/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { ArrowLeft, CheckCircle2, Database, Search, ShieldCheck, TerminalSquare, Wrench } from "lucide-react";
import { CheckCircle2, Database, Search, ShieldCheck, TerminalSquare, Wrench } from "lucide-react";
import { CliGuideBlock } from "./cli-guide-block";

export const metadata: Metadata = {
Expand Down Expand Up @@ -56,11 +55,7 @@ export default function CliPage() {
<main className="min-h-screen px-6 py-8">
<section className="mx-auto flex max-w-5xl flex-col gap-8">
<header className="border-b border-line pb-6">
<Link className="inline-flex items-center gap-2 text-sm font-medium text-accent no-underline hover:underline" href="/">
<ArrowLeft aria-hidden size={16} />
<span>Database dashboard</span>
</Link>
<div className="mt-6 flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between">
<div className="flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between">
<div className="flex min-w-0 items-center gap-4">
<Image className="h-12 w-12 rounded-xl shadow-sm" src="/icon.png" alt="" width={48} height={48} unoptimized />
<div className="min-w-0">
Expand Down
2 changes: 1 addition & 1 deletion wikibrowser/app/cycles/cycles-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ function cyclesPurchaseSuccessHref({
params.set("provider", provider);
params.set("kinic", kinic);
params.set("cycles", cycles);
return `/?${params.toString()}`;
return `/dashboard?${params.toString()}`;
}

function Field({ label, value }: { label: string; value: string }) {
Expand Down
21 changes: 0 additions & 21 deletions wikibrowser/app/dashboard/dashboard-client.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { AuthClient } from "@icp-sdk/auth/client";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Pencil } from "lucide-react";
Expand Down Expand Up @@ -382,18 +381,6 @@ export function DashboardDatabaseClient({ databaseId }: { databaseId: string })
</button>
) : null
}
nav={
<>
<Link className="text-accent no-underline hover:underline" href="/">
Database dashboard
</Link>
{databaseId && isActiveDatabase ? (
<Link className="text-accent no-underline hover:underline" href={`/skills/${encodeURIComponent(databaseId)}`}>
Skill Registry
</Link>
) : null}
</>
}
actions={
<>
{canisterId ? <CycleBattery canisterId={canisterId} /> : null}
Expand Down Expand Up @@ -456,14 +443,6 @@ export function DashboardDatabaseClient({ databaseId }: { databaseId: string })
) : (
<StatusPanel tone="info" message="Login with Internet Identity to manage database access." />
)
) : !databaseId ? (
<section className="rounded-lg border border-line bg-paper p-8 shadow-sm">
<h2 className="text-lg font-semibold text-ink">Select a database to manage</h2>
<p className="mt-2 text-sm leading-6 text-muted">Open the Database dashboard, then choose Manage on a database row.</p>
<Link className="mt-5 inline-flex rounded-2xl border border-action bg-action px-4 py-2 text-sm font-bold text-white no-underline hover:-translate-y-[3px] hover:border-accent hover:bg-accent" href="/">
Open Database dashboard
</Link>
</section>
) : principal ? (
<StatusPanel tone="info" message={memberError ?? "Select Cycles History to inspect cycle visibility for this database."} />
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,29 @@ import type { AuthClient } from "@icp-sdk/auth/client";
import { useCallback, useEffect, useRef, useState } from "react";
import { Plus } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { useAppSession } from "./app-session-provider";
import { CreateDatabaseDialog } from "./create-database-dialog";
import { DatabaseBody, OfficialKinicWikiPanel, StatusPanel } from "./home-ui";
import { useAppSession } from "../app-session-provider";
import { CreateDatabaseDialog } from "../create-database-dialog";
import { DatabaseBody, OfficialKinicWikiPanel, StatusPanel } from "../home-ui";
import { KINIC_LEDGER_FEE_E8S } from "@/lib/cycles";
import { parseKinicAmountE8sInput } from "@/lib/cycles-url";
import { purchaseCyclesWithOisy, purchaseCyclesWithPlug } from "@/lib/cycles-wallet";
import { formatTokenAmountFromE8s } from "@/lib/kinic-amount";
import type { CyclesBillingConfig, DatabaseSummary } from "@/lib/types";
import { createDatabaseAuthenticated, getCyclesBillingConfig, listDatabasesAuthenticated, listDatabasesPublic } from "@/lib/vfs-client";
import type { DatabaseRow } from "./home-ui";
import type { DatabaseRow } from "../home-ui";

type LoadState = "idle" | "loading" | "ready" | "error";

const CREATE_DATABASE_PURCHASE_KINIC = "1";

export function HomePageClient() {
export function DashboardHomeClient() {
const canisterId = process.env.NEXT_PUBLIC_KINIC_WIKI_CANISTER_ID ?? "";
const searchParams = useSearchParams();
const refreshSeqRef = useRef(0);
const {
authClient,
authError,
authReady,
authRefreshSeq,
principal,
refreshWalletBalance,
setWalletControlsLocked,
Expand Down Expand Up @@ -94,12 +93,13 @@ export function HomePageClient() {
let cancelled = false;
queueMicrotask(() => {
if (cancelled) return;
void refreshDatabases(authClient);
const databaseRefreshClient = principal && authClient ? authClient : null;
void refreshDatabases(databaseRefreshClient);
});
return () => {
cancelled = true;
};
}, [authClient, authReady, authRefreshSeq, refreshDatabases]);
}, [authClient, authReady, principal, refreshDatabases]);

useEffect(() => {
setWalletControlsLocked(creating);
Expand Down Expand Up @@ -179,10 +179,27 @@ export function HomePageClient() {
const trimmedDatabaseName = newDatabaseName.trim();
const databaseNameValidationError = databaseNameError(trimmedDatabaseName);
const walletReadyToFundCreate = walletCanFundCreate(walletBalance);
const createUnavailable = loadState === "loading" || walletBusyProvider !== null || walletBalanceLoading || !walletReadyToFundCreate;
const createUnavailable = !principal || loadState === "loading" || walletBusyProvider !== null || walletBalanceLoading || !walletReadyToFundCreate;
const createDisabled = creating || createUnavailable || databaseNameValidationError !== null;
const createButtonLabel = databaseCreateButtonLabel({ creating, walletConnected: Boolean(wallet), walletBalanceLoading, walletReadyToFundCreate });
const createButtonLabel = databaseCreateButtonLabel({
creating,
iiConnected: Boolean(principal),
walletConnected: Boolean(wallet),
walletBalanceLoading,
walletReadyToFundCreate
});
const fundingSuccessMessage = dashboardFundingSuccessMessage(searchParams);
const createDatabaseAction = (
<button
className="inline-flex items-center justify-center gap-2 rounded-lg border border-action bg-action px-3 py-2 text-sm font-bold text-white hover:border-accent hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60"
disabled={creating || createUnavailable}
type="button"
onClick={() => setCreateDialogOpen(true)}
>
<Plus aria-hidden size={15} />
<span>{createButtonLabel}</span>
</button>
);

return (
<main className="min-h-screen px-6 pb-8 pt-6">
Expand Down Expand Up @@ -213,17 +230,7 @@ export function HomePageClient() {

{principal ? (
<DatabaseBody
createDatabaseAction={
<button
className="inline-flex items-center justify-center gap-2 rounded-lg border border-action bg-action px-3 py-2 text-sm font-bold text-white hover:border-accent hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60"
disabled={creating || createUnavailable}
type="button"
onClick={() => setCreateDialogOpen(true)}
>
<Plus aria-hidden size={15} />
<span>{createButtonLabel}</span>
</button>
}
createDatabaseAction={createDatabaseAction}
cyclesConfig={cyclesConfig}
loading={loadState === "loading"}
myDatabases={myDatabases}
Expand All @@ -238,8 +245,9 @@ export function HomePageClient() {
<h2 className="text-lg font-semibold text-ink">Public databases</h2>
<p className="mt-1 text-sm leading-6 text-muted">Public databases open without login. Login with Internet Identity to show My databases linked to your principal.</p>
</div>
{createDatabaseAction}
</div>
<DatabaseBody cyclesConfig={cyclesConfig} loading={loadState === "loading"} myDatabases={myDatabases} principal={principal} publicDatabases={publicDatabases} publicError={publicError} />
<DatabaseBody createDatabaseAction={createDatabaseAction} cyclesConfig={cyclesConfig} loading={loadState === "loading"} myDatabases={myDatabases} principal={principal} publicDatabases={publicDatabases} publicError={publicError} />
</section>
)}
</section>
Expand Down Expand Up @@ -285,17 +293,20 @@ function walletCanFundCreate(balanceE8s: string | null): boolean {

function databaseCreateButtonLabel({
creating,
iiConnected,
walletConnected,
walletBalanceLoading,
walletReadyToFundCreate
}: {
creating: boolean;
iiConnected: boolean;
walletConnected: boolean;
walletBalanceLoading: boolean;
walletReadyToFundCreate: boolean;
}): string {
if (creating) return "Creating...";
if (!walletConnected) return "Connect wallet first";
if (!iiConnected) return "Connect Internet Identity";
if (!walletConnected) return "Connect OISY or Plug";
if (walletBalanceLoading) return "Checking balance...";
if (!walletReadyToFundCreate) return "Insufficient KINIC";
return "Create and fund database";
Expand Down
Loading