@haruhimemoe/next-kit 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -1
- package/README.md +14 -2
- package/dist/auth-react/AccountMenu.d.ts +1 -1
- package/dist/auth-react/AccountMenu.js +7 -3
- package/dist/auth-react/DeleteAccountForm.d.ts +9 -8
- package/dist/auth-react/DeleteAccountForm.js +29 -29
- package/dist/auth-react/RestoreSignedIn.d.ts +1 -1
- package/dist/auth-react/RestoreSignedIn.js +3 -2
- package/dist/auth-react/SignInWithOsu.d.ts +1 -1
- package/dist/auth-react/SignInWithOsu.js +3 -3
- package/dist/legal/blocks.d.ts +62 -0
- package/dist/legal/blocks.js +43 -0
- package/dist/legal/entries.d.ts +22 -0
- package/dist/legal/entries.js +45 -0
- package/dist/legal/index.d.ts +12 -0
- package/dist/legal/index.js +12 -0
- package/dist/legal/types.d.ts +42 -0
- package/dist/legal/types.js +16 -0
- package/package.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.0] - 2026-10-05
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `legal` entry point: the five-page legal convention every service ships (terms, privacy, your-privacy-rights, copyright, disclaimers). A `LegalSite` config (site name, operator, contact email, effective date, data stores, processors, cookies), seven plain server-safe blocks an app drops into its own legal MDX (`LegalContact`, `DataWeKeep`, `Processors`, `YourRights`, `DmcaNotice`, `NoWarranty`, `Changes`; no hooks, no `@haruhimemoe/ui`), and `legalEntries(site, pages?)` for the five standard `ContentEntry` records so apps stop hand-writing the same titles and descriptions. next-kit's first `.tsx` entry point. `LegalSite.hosting` adds an optional line to `DmcaNotice`; `Changes` takes an optional per-page `date`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- The `@haruhimemoe/ui` peer range also allows `^0.18.0`.
|
|
16
|
+
|
|
17
|
+
## [0.8.0] - 2026-10-05
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Breaking.** The `@haruhimemoe/ui` peer range is now `^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0` (0.15 to 0.17 not yet published): `DeleteAccountForm` needs ui's `ConfirmDialog`.
|
|
21
|
+
- `DeleteAccountForm` is a "Delete my account" button that opens a dialog: what goes is its description, the username is typed there, and a refusal or no answer is said in the dialog, which stays open. Once the account is deleted, focus moves to the "Your account is deleted." line.
|
|
22
|
+
- The account components use ui's text tones (`Text`, `textClasses`) and Button's own width. No change in how they look.
|
|
23
|
+
|
|
9
24
|
## [0.7.0] - 2026-10-05
|
|
10
25
|
|
|
11
26
|
### Added
|
|
@@ -89,7 +104,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
89
104
|
- `@haruhimemoe/next-kit/auth-react`: `createSignedInMarker`, `createAccountStore`, `useAccount`, `createAccount`, `RestoreSignedIn` and `osuSignIn`.
|
|
90
105
|
- `@haruhimemoe/next-kit/testing`: `startMemoryMongo`, `setupTestDb`, `setupMsw` and the fake osu! app env.
|
|
91
106
|
|
|
92
|
-
[unreleased]: https://github.com/haruhimemoe/next-kit/compare/v0.
|
|
107
|
+
[unreleased]: https://github.com/haruhimemoe/next-kit/compare/v0.8.0...HEAD
|
|
108
|
+
[0.8.0]: https://github.com/haruhimemoe/next-kit/compare/v0.7.0...v0.8.0
|
|
93
109
|
[0.6.2]: https://github.com/haruhimemoe/next-kit/compare/v0.6.1...v0.6.2
|
|
94
110
|
[0.6.1]: https://github.com/haruhimemoe/next-kit/compare/v0.6.0...v0.6.1
|
|
95
111
|
[0.6.0]: https://github.com/haruhimemoe/next-kit/compare/v0.5.0...v0.6.0
|
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ The Next.js server plumbing the haruhime.moe tools share. [packs.haruhime.moe](h
|
|
|
13
13
|
- **`/testing`:** Vitest helpers: one in-memory MongoDB per run, an msw server that refuses unhandled requests, and a fake env.
|
|
14
14
|
- **`/api-keys`:** the shared key format (an app prefix like `hpk_` plus 32 random bytes), a key store over `api_keys`, and the `/api/v1` guard with the standard limits.
|
|
15
15
|
- **`/docs`:** a content registry for an app's docs, guides and legal pages: sections, entries, app-made extra entries (like bb's tag pages), the path helpers a dynamic route needs, and `mdxToMarkdown` to turn bb-flavored MDX into plain Markdown. No runtime imports. **`/docs/files`:** reads the markdown files a registry's entries point at and reports drift between the registry and disk (node:fs).
|
|
16
|
+
- **`/legal`:** the five-page legal convention (terms, privacy, your-privacy-rights, copyright, disclaimers). A `LegalSite` config, seven plain server-safe blocks (`LegalContact`, `DataWeKeep`, `Processors`, `YourRights`, `DmcaNotice`, `NoWarranty`, `Changes`) an app drops into its own legal MDX, and `legalEntries` for the app's content registry.
|
|
16
17
|
- **`/vcs`:** document history in MongoDB on top of `@haruhimemoe/vcs`: one line of revisions per document, saves merged onto whatever landed since their base, revert, diffs, and autosave pruning.
|
|
17
18
|
|
|
18
19
|
Every name, path, limit and message comes from the caller. There is no root entry point; import a subpath.
|
|
@@ -32,12 +33,13 @@ bun add @haruhimemoe/next-kit zod
|
|
|
32
33
|
| `env` | nothing else |
|
|
33
34
|
| `mongo` | `mongodb` ^7.6.0, `mongoose` ^9.10.2 |
|
|
34
35
|
| `auth` | `better-auth` ^1.7.5, `mongodb`, `@haruhimemoe/osu` 0.2 or 0.3 |
|
|
35
|
-
| `auth-react` | `react` ^19.3.0, `next` ^16.3.6, `@haruhimemoe/ui` ^0.
|
|
36
|
+
| `auth-react` | `react` ^19.3.0, `next` ^16.3.6, `@haruhimemoe/ui` ^0.14.0 \|\| ^0.15.0 \|\| ^0.16.0 \|\| ^0.17.0 (with its theme set up) |
|
|
36
37
|
| `seo` | `next` ^16.3.6 types only (nothing loads at runtime) |
|
|
37
38
|
| `testing` | `vitest` ^5.0.1, `msw` ^2.15.0, `mongodb-memory-server` ^11.3.0 |
|
|
38
39
|
| `api-keys` | `mongodb` ^7.6.0 |
|
|
39
40
|
| `docs` | nothing else |
|
|
40
41
|
| `docs/files` | nothing else (`node:fs` is built in) |
|
|
42
|
+
| `legal` | `react` ^19.3.0 (types only, for JSX) |
|
|
41
43
|
| `vcs` | `mongodb` ^7.6.0, `@haruhimemoe/vcs` ^0.1.0 |
|
|
42
44
|
|
|
43
45
|
## Use
|
|
@@ -304,7 +306,7 @@ The command prints one `pass` or `FAIL` line per standard, names each missing fi
|
|
|
304
306
|
| `SignInWithOsu({ next, signIn, signInPath?, label?, pendingLabel?, failedMessage? })` | Since 0.2.0. The large "Sign in with osu!" button. Calls `signIn(osuSignIn(next))`; an error (better-auth's `{ message }` or `{ error: { message } }`, or a throw) shows in a `role="alert"` line and the button comes back. `signInErrorMessage(error, fallback?)` reads it. |
|
|
305
307
|
| `SignOutButton({ signOut, onSignedOut, redirectTo?, variant?, className?, label?, pendingLabel? })` | Since 0.2.0. Signs out, runs `onSignedOut` only once the session is gone, then `router.replace(redirectTo ?? "/")` and `router.refresh()`. |
|
|
306
308
|
| `AccountMenu({ account, items, signOut, onSignedOut, avatarSrc?, signInPath?, signInLabel?, signOutLabel?, signOutRedirect? })` | Since 0.2.0. A sized blank while loading, a "Sign in" link back to this page when signed out, else ui's `HeaderMenu` with the avatar and name, `items` and Sign out. The avatar is a plain `<img>` (your CSP's img-src must allow a.ppy.sh and osu.ppy.sh); `avatarSrc` defaults to `osuAvatarSrc`. |
|
|
307
|
-
| `DeleteAccountForm({ username, appName, deletes, onDeleted, endpoint?, homeHref?, homeLabel?, fetcher? })` | Since 0.2.0. ui's `
|
|
309
|
+
| `DeleteAccountForm({ username, appName, deletes, onDeleted, endpoint?, homeHref?, homeLabel?, fetcher? })` | Since 0.2.0. A "Delete my account" button (0.8.0; an in-page `TypeToConfirm` before) that opens ui's `ConfirmDialog`: `deletes` is its description, the username must be typed, then "Delete for good" sends `DELETE endpoint` (default `/api/account`) with `{ username }`. 204: `onDeleted`, "Your account is deleted." (focused) and home. Another 2xx: `onDeleted` and the answer's `notice` after that line, staying. A refusal shows `error.message` (else "Deleting failed (status).") in the dialog; no answer, "Couldn't reach <appName>. Your account is still there." The dialog stays open on both. Needs ui 0.14.0. |
|
|
308
310
|
| `osuAvatarSrc(url)`, `OSU_AVATAR_HOSTS` | Since 0.2.0. An osu! avatar URL on https when it's on a.ppy.sh or osu.ppy.sh (a bare path is osu.ppy.sh's), else null. |
|
|
309
311
|
|
|
310
312
|
### seo
|
|
@@ -393,6 +395,16 @@ No runtime imports.
|
|
|
393
395
|
| `readContentMarkdown(content, section, slug, { root?, siteUrl, transforms? })` | Reads a registered entry's markdown file and converts it with `mdxToMarkdown` (using the entry's `title`). `root` defaults to `process.cwd()`. Returns null for an unregistered slug; rejects (ENOENT) when the slug is registered but its file is missing. |
|
|
394
396
|
| `contentFileDrift(content, { root? })` | `{ missingFiles, unregistered }`: `missingFiles` lists registered entries with no file on disk (like `"guides/x.mdx"`); `unregistered` lists `.mdx` files on disk with no registry entry. `root` defaults to `process.cwd()`. |
|
|
395
397
|
|
|
398
|
+
### legal
|
|
399
|
+
|
|
400
|
+
| Export | What it does |
|
|
401
|
+
| --- | --- |
|
|
402
|
+
| `LEGAL_SLUGS`, `LegalSlug` | The five standard legal page slugs, in order: `"terms"`, `"privacy"`, `"your-privacy-rights"`, `"copyright"`, `"disclaimers"`. |
|
|
403
|
+
| `LegalSite` | The config every block and `legalEntries` renders from: `siteName`, `operator`, `contactEmail`, `effectiveDate` (`YYYY-MM-DD`), `stores` (`LegalDataStore[]`), `processors` (`LegalProcessor[]`), `cookies` (`string[]`), optional `hosting` (one sentence on what users can post, shown by `DmcaNotice`). |
|
|
404
|
+
| `LegalDataStore`, `LegalProcessor` | One kind of data kept (`what`, `why`), and one third party that processes it (`name`, `purpose`, `link?`). |
|
|
405
|
+
| `LegalContact`, `DataWeKeep`, `Processors`, `YourRights`, `DmcaNotice`, `NoWarranty`, `Changes` | The seven blocks. Each takes `{ site: LegalSite }` and renders plain semantic HTML (no `@haruhimemoe/ui`), so it inherits the app's MDX prose styling. `DataWeKeep` skips the cookies list when `site.cookies` is empty. `Processors` links a processor that has a `link`, and plain-texts one that doesn't. `DmcaNotice` prints `site.hosting` only when set. `Changes` also takes an optional `date` for a page updated on its own day. |
|
|
406
|
+
| `legalEntries(site, pages?)` | The five `ContentEntry` records (`docs`'s registry shape) for the legal convention, with the default title and description (`site.siteName` filled in) and `lastUpdated` set to `site.effectiveDate`. `pages` overrides any field per slug; everything else keeps the default. |
|
|
407
|
+
|
|
396
408
|
### vcs
|
|
397
409
|
|
|
398
410
|
| Export | What it does |
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* (src/components/layout/AccountMenu.tsx), which differed only in the links.
|
|
10
10
|
* @author David @dvhsh (https://dvh.sh)
|
|
11
11
|
* @created Mon Sep 28, 2026
|
|
12
|
-
* @modified Mon
|
|
12
|
+
* @modified Mon Oct 5, 2026
|
|
13
13
|
*/
|
|
14
14
|
import { type HeaderMenuItem } from "@haruhimemoe/ui";
|
|
15
15
|
import type { ReactNode } from "react";
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
* (src/components/layout/AccountMenu.tsx), which differed only in the links.
|
|
10
10
|
* @author David @dvhsh (https://dvh.sh)
|
|
11
11
|
* @created Mon Sep 28, 2026
|
|
12
|
-
* @modified Mon
|
|
12
|
+
* @modified Mon Oct 5, 2026
|
|
13
13
|
*/
|
|
14
14
|
"use client";
|
|
15
15
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
16
|
-
import { HeaderMenu } from "@haruhimemoe/ui";
|
|
16
|
+
import { HeaderMenu, textClasses } from "@haruhimemoe/ui";
|
|
17
17
|
import { usePathname } from "next/navigation.js";
|
|
18
18
|
import { DEFAULT_SIGN_IN_PATH, signInHref } from "../server/safe-next.js";
|
|
19
19
|
import { osuAvatarSrc } from "./avatar.js";
|
|
@@ -29,7 +29,11 @@ export function AccountMenu({ account, items, signOut, onSignedOut, avatarSrc =
|
|
|
29
29
|
if (account.status === "loading")
|
|
30
30
|
return _jsx("span", { "aria-hidden": "true", className: "block h-7 w-16" });
|
|
31
31
|
if (account.status === "signed-out") {
|
|
32
|
-
return (_jsx("a", { href: signInHref(pathname || "/", signInPath), className:
|
|
32
|
+
return (_jsx("a", { href: signInHref(pathname || "/", signInPath), className: textClasses({
|
|
33
|
+
tone: "muted",
|
|
34
|
+
bold: true,
|
|
35
|
+
className: "transition-colors hover:text-c1",
|
|
36
|
+
}), children: signInLabel }));
|
|
33
37
|
}
|
|
34
38
|
const avatar = avatarSrc(account.user.avatarUrl);
|
|
35
39
|
return (_jsx(HeaderMenu, { label: _jsxs(_Fragment, { children: [avatar ? (_jsx("img", { src: avatar, alt: "", width: 28, height: 28, className: "rounded-full" })) : null, _jsx("span", { children: account.user.username })] }), items: items, children: _jsx(SignOutButton, { signOut: signOut, onSignedOut: onSignedOut, redirectTo: signOutRedirect, label: signOutLabel, variant: "ghost", className: "h-auto justify-start rounded px-3 py-2" }) }));
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file src/auth-react/DeleteAccountForm.tsx
|
|
3
|
-
* @desc "Delete my account"
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* couldn't be reached
|
|
3
|
+
* @desc "Delete my account": a button that opens ui's ConfirmDialog (no confirm()), where what
|
|
4
|
+
* goes is the description and the osu! username is typed before "Delete for good" sends one
|
|
5
|
+
* DELETE with it to the app's endpoint. 204 means gone: signed out, the page says so and
|
|
6
|
+
* goes home. Another 2xx is gone too but stays, to show the answer's `notice` (work still
|
|
7
|
+
* waiting elsewhere). A refusal says the server's message in the dialog, no answer says the
|
|
8
|
+
* app couldn't be reached, nothing was deleted and the dialog stays open. Once it's gone
|
|
9
|
+
* the button never comes back and focus lands on the line that says so.
|
|
9
10
|
* Moved from pools and bb (src/components/account/DeleteAccountForm.tsx).
|
|
10
11
|
* @author David @dvhsh (https://dvh.sh)
|
|
11
12
|
* @created Mon Sep 28, 2026
|
|
12
|
-
* @modified Mon
|
|
13
|
+
* @modified Mon Oct 5, 2026
|
|
13
14
|
*/
|
|
14
15
|
import { type ReactNode } from "react";
|
|
15
16
|
/** DeleteAccountForm's props. */
|
|
@@ -35,6 +36,6 @@ export type DeleteAccountFormProps = {
|
|
|
35
36
|
* @function DeleteAccountForm
|
|
36
37
|
* @param props {DeleteAccountFormProps} the username, the app's name, what goes, the endpoint
|
|
37
38
|
* and what to do after
|
|
38
|
-
* @returns {ReactNode} the
|
|
39
|
+
* @returns {ReactNode} the "Delete my account" trigger and its confirm dialog, or what happened
|
|
39
40
|
*/
|
|
40
41
|
export declare function DeleteAccountForm({ username, appName, deletes, onDeleted, endpoint, homeHref, homeLabel, fetcher, }: DeleteAccountFormProps): ReactNode;
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file src/auth-react/DeleteAccountForm.tsx
|
|
3
|
-
* @desc "Delete my account"
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* couldn't be reached
|
|
3
|
+
* @desc "Delete my account": a button that opens ui's ConfirmDialog (no confirm()), where what
|
|
4
|
+
* goes is the description and the osu! username is typed before "Delete for good" sends one
|
|
5
|
+
* DELETE with it to the app's endpoint. 204 means gone: signed out, the page says so and
|
|
6
|
+
* goes home. Another 2xx is gone too but stays, to show the answer's `notice` (work still
|
|
7
|
+
* waiting elsewhere). A refusal says the server's message in the dialog, no answer says the
|
|
8
|
+
* app couldn't be reached, nothing was deleted and the dialog stays open. Once it's gone
|
|
9
|
+
* the button never comes back and focus lands on the line that says so.
|
|
9
10
|
* Moved from pools and bb (src/components/account/DeleteAccountForm.tsx).
|
|
10
11
|
* @author David @dvhsh (https://dvh.sh)
|
|
11
12
|
* @created Mon Sep 28, 2026
|
|
12
|
-
* @modified Mon
|
|
13
|
+
* @modified Mon Oct 5, 2026
|
|
13
14
|
*/
|
|
14
15
|
"use client";
|
|
15
16
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
16
|
-
import { buttonClasses,
|
|
17
|
+
import { buttonClasses, ConfirmDialog } from "@haruhimemoe/ui";
|
|
17
18
|
import { useRouter } from "next/navigation.js";
|
|
18
19
|
import { useId, useState } from "react";
|
|
19
20
|
const messageOf = async (response) => {
|
|
@@ -30,41 +31,40 @@ const messageOf = async (response) => {
|
|
|
30
31
|
* @function DeleteAccountForm
|
|
31
32
|
* @param props {DeleteAccountFormProps} the username, the app's name, what goes, the endpoint
|
|
32
33
|
* and what to do after
|
|
33
|
-
* @returns {ReactNode} the
|
|
34
|
+
* @returns {ReactNode} the "Delete my account" trigger and its confirm dialog, or what happened
|
|
34
35
|
*/
|
|
35
36
|
export function DeleteAccountForm({ username, appName, deletes, onDeleted, endpoint = "/api/account", homeHref = "/", homeLabel = "Go to the home page", fetcher = fetch, }) {
|
|
36
37
|
const router = useRouter();
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
// Once it's gone: what more to say. The form doesn't come back, so no second delete.
|
|
38
|
+
const doneId = `${useId()}-done`;
|
|
39
|
+
// Once it's gone: what more to say. The button doesn't come back, so no second delete.
|
|
40
40
|
const [done, setDone] = useState(null);
|
|
41
|
+
const unreachable = `Couldn't reach ${appName}. Your account is still there.`;
|
|
42
|
+
// Throws to keep the dialog open with what went wrong.
|
|
41
43
|
const remove = async () => {
|
|
42
|
-
|
|
44
|
+
let response;
|
|
43
45
|
try {
|
|
44
|
-
|
|
46
|
+
response = await fetcher(endpoint, {
|
|
45
47
|
method: "DELETE",
|
|
46
48
|
headers: { "Content-Type": "application/json" },
|
|
47
49
|
body: JSON.stringify({ username }),
|
|
48
50
|
});
|
|
49
|
-
if (!response.ok) {
|
|
50
|
-
setError(await messageOf(response));
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
onDeleted();
|
|
54
|
-
if (response.status === 204) {
|
|
55
|
-
setDone("");
|
|
56
|
-
router.push(homeHref);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const body = (await response.json().catch(() => ({})));
|
|
60
|
-
setDone(typeof body.notice === "string" ? body.notice : "");
|
|
61
51
|
}
|
|
62
52
|
catch {
|
|
63
|
-
|
|
53
|
+
throw new Error(unreachable);
|
|
64
54
|
}
|
|
55
|
+
if (!response.ok)
|
|
56
|
+
throw new Error(await messageOf(response));
|
|
57
|
+
onDeleted();
|
|
58
|
+
if (response.status === 204) {
|
|
59
|
+
setDone("");
|
|
60
|
+
router.push(homeHref);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const body = (await response.json().catch(() => ({})));
|
|
64
|
+
setDone(typeof body.notice === "string" ? body.notice : "");
|
|
65
65
|
};
|
|
66
66
|
if (done !== null) {
|
|
67
|
-
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("p", { role: "status", className: "text-c2 text-sm", children: `Your account is deleted. ${done}`.trim() }), _jsx("a", { href: homeHref, className: buttonClasses({ variant: "secondary"
|
|
67
|
+
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("p", { id: doneId, role: "status", tabIndex: -1, className: "text-c2 text-sm outline-none", children: `Your account is deleted. ${done}`.trim() }), _jsx("a", { href: homeHref, className: buttonClasses({ variant: "secondary" }), children: homeLabel })] }));
|
|
68
68
|
}
|
|
69
|
-
return (_jsx(
|
|
69
|
+
return (_jsx(ConfirmDialog, { trigger: "Delete my account", triggerProps: { variant: "danger" }, title: "Delete your account?", description: deletes, tone: "destructive", typeToConfirm: username, confirmLabel: "Delete for good", pendingLabel: "Deleting\u2026", failedMessage: (error) => (error instanceof Error ? error.message : unreachable), returnFocus: () => document.getElementById(doneId), onConfirm: remove }));
|
|
70
70
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* and pools (src/components/auth/RestoreSignedIn.tsx, body-identical).
|
|
8
8
|
* @author David @dvhsh (https://dvh.sh)
|
|
9
9
|
* @created Mon Sep 28, 2026
|
|
10
|
-
* @modified Mon
|
|
10
|
+
* @modified Mon Oct 5, 2026
|
|
11
11
|
*/
|
|
12
12
|
import { type ReactNode } from "react";
|
|
13
13
|
/** RestoreSignedIn's props. */
|
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
* and pools (src/components/auth/RestoreSignedIn.tsx, body-identical).
|
|
8
8
|
* @author David @dvhsh (https://dvh.sh)
|
|
9
9
|
* @created Mon Sep 28, 2026
|
|
10
|
-
* @modified Mon
|
|
10
|
+
* @modified Mon Oct 5, 2026
|
|
11
11
|
*/
|
|
12
12
|
"use client";
|
|
13
13
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
import { Text } from "@haruhimemoe/ui";
|
|
14
15
|
import { useRouter } from "next/navigation.js";
|
|
15
16
|
import { useEffect } from "react";
|
|
16
17
|
const readDocumentCookie = () => document.cookie;
|
|
17
|
-
const SIGNING_IN = _jsx(
|
|
18
|
+
const SIGNING_IN = _jsx(Text, { tone: "muted", children: "Signing you in\u2026" });
|
|
18
19
|
/**
|
|
19
20
|
* @function RestoreSignedIn
|
|
20
21
|
* @param props {RestoreSignedInProps} the store, the marker check, `next` and what to show
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* (src/components/auth/SignInWithOsu.tsx), where only the error reading differed.
|
|
8
8
|
* @author David @dvhsh (https://dvh.sh)
|
|
9
9
|
* @created Mon Sep 28, 2026
|
|
10
|
-
* @modified Mon
|
|
10
|
+
* @modified Mon Oct 5, 2026
|
|
11
11
|
*/
|
|
12
12
|
import { type ReactNode } from "react";
|
|
13
13
|
import { type OsuSignIn } from "./sign-in.js";
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* (src/components/auth/SignInWithOsu.tsx), where only the error reading differed.
|
|
8
8
|
* @author David @dvhsh (https://dvh.sh)
|
|
9
9
|
* @created Mon Sep 28, 2026
|
|
10
|
-
* @modified Mon
|
|
10
|
+
* @modified Mon Oct 5, 2026
|
|
11
11
|
*/
|
|
12
12
|
"use client";
|
|
13
13
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
14
|
-
import { Button } from "@haruhimemoe/ui";
|
|
14
|
+
import { Button, Text } from "@haruhimemoe/ui";
|
|
15
15
|
import { useState } from "react";
|
|
16
16
|
import { osuSignIn } from "./sign-in.js";
|
|
17
17
|
const FAILED = "Couldn't start osu! sign-in. Try again.";
|
|
@@ -54,5 +54,5 @@ export function SignInWithOsu({ next, signIn, signInPath, label = "Sign in with
|
|
|
54
54
|
}
|
|
55
55
|
setPending(false);
|
|
56
56
|
};
|
|
57
|
-
return (_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx(Button, { size: "lg", onClick: onClick, disabled: pending, children: pending ? pendingLabel : label }), error ? (_jsx(
|
|
57
|
+
return (_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx(Button, { size: "lg", onClick: onClick, disabled: pending, children: pending ? pendingLabel : label }), error ? (_jsx(Text, { role: "alert", tone: "error", bold: true, children: error })) : null] }));
|
|
58
58
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/blocks.tsx
|
|
3
|
+
* @desc The seven legal blocks: plain server-safe React (no hooks) an app drops into its own
|
|
4
|
+
* legal MDX pages, rendered from a `LegalSite` config instead of hand-written boilerplate.
|
|
5
|
+
* Semantic HTML only (section, h2, p, ul, a), so it inherits the app's MDX prose styling.
|
|
6
|
+
* No `@haruhimemoe/ui` dependency: these are plain text blocks, not UI components.
|
|
7
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
8
|
+
* @created Mon Oct 5, 2026
|
|
9
|
+
* @modified Mon Oct 5, 2026
|
|
10
|
+
*/
|
|
11
|
+
import type { ReactElement } from "react";
|
|
12
|
+
import type { LegalSite } from "./types.js";
|
|
13
|
+
/** Every block's props: the site config it renders from. */
|
|
14
|
+
export type LegalBlockProps = {
|
|
15
|
+
site: LegalSite;
|
|
16
|
+
};
|
|
17
|
+
/** `Changes` props: the site config, plus this page's own date when it differs from the site's. */
|
|
18
|
+
export type ChangesProps = LegalBlockProps & {
|
|
19
|
+
date?: string | undefined;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* @function LegalContact
|
|
23
|
+
* @param props {LegalBlockProps} the site config
|
|
24
|
+
* @returns {ReactElement} who runs the site and the address for legal questions
|
|
25
|
+
*/
|
|
26
|
+
export declare const LegalContact: ({ site }: LegalBlockProps) => ReactElement;
|
|
27
|
+
/**
|
|
28
|
+
* @function DataWeKeep
|
|
29
|
+
* @param props {LegalBlockProps} the site config
|
|
30
|
+
* @returns {ReactElement} what the app stores and why, plus the cookies it sets
|
|
31
|
+
*/
|
|
32
|
+
export declare const DataWeKeep: ({ site }: LegalBlockProps) => ReactElement;
|
|
33
|
+
/**
|
|
34
|
+
* @function Processors
|
|
35
|
+
* @param props {LegalBlockProps} the site config
|
|
36
|
+
* @returns {ReactElement} the third parties that process data on the app's behalf
|
|
37
|
+
*/
|
|
38
|
+
export declare const Processors: ({ site }: LegalBlockProps) => ReactElement;
|
|
39
|
+
/**
|
|
40
|
+
* @function YourRights
|
|
41
|
+
* @param props {LegalBlockProps} the site config
|
|
42
|
+
* @returns {ReactElement} the GDPR and CCPA rights every visitor has, and how to use them
|
|
43
|
+
*/
|
|
44
|
+
export declare const YourRights: ({ site }: LegalBlockProps) => ReactElement;
|
|
45
|
+
/**
|
|
46
|
+
* @function DmcaNotice
|
|
47
|
+
* @param props {LegalBlockProps} the site config
|
|
48
|
+
* @returns {ReactElement} the DMCA agent, and the notice and counter-notice steps
|
|
49
|
+
*/
|
|
50
|
+
export declare const DmcaNotice: ({ site }: LegalBlockProps) => ReactElement;
|
|
51
|
+
/**
|
|
52
|
+
* @function NoWarranty
|
|
53
|
+
* @param props {LegalBlockProps} the site config
|
|
54
|
+
* @returns {ReactElement} the "as is" warranty disclaimer and limitation of liability
|
|
55
|
+
*/
|
|
56
|
+
export declare const NoWarranty: ({ site }: LegalBlockProps) => ReactElement;
|
|
57
|
+
/**
|
|
58
|
+
* @function Changes
|
|
59
|
+
* @param props {ChangesProps} the site config and an optional per-page date
|
|
60
|
+
* @returns {ReactElement} the standard "we may update this page" line with its effective date
|
|
61
|
+
*/
|
|
62
|
+
export declare const Changes: ({ site, date }: ChangesProps) => ReactElement;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* @function LegalContact
|
|
4
|
+
* @param props {LegalBlockProps} the site config
|
|
5
|
+
* @returns {ReactElement} who runs the site and the address for legal questions
|
|
6
|
+
*/
|
|
7
|
+
export const LegalContact = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "Contact" }), _jsxs("p", { children: [site.operator, " runs ", site.siteName, ". For anything on this page, write to", " ", _jsx("a", { href: `mailto:${site.contactEmail}`, children: site.contactEmail }), "."] })] }));
|
|
8
|
+
/**
|
|
9
|
+
* @function DataWeKeep
|
|
10
|
+
* @param props {LegalBlockProps} the site config
|
|
11
|
+
* @returns {ReactElement} what the app stores and why, plus the cookies it sets
|
|
12
|
+
*/
|
|
13
|
+
export const DataWeKeep = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "What we store" }), _jsx("ul", { children: site.stores.map((store) => (_jsxs("li", { children: [_jsxs("strong", { children: [store.what, "."] }), " ", store.why] }, store.what))) }), site.cookies.length > 0 && (_jsxs(_Fragment, { children: [_jsx("h3", { children: "Cookies" }), _jsx("ul", { children: site.cookies.map((cookie) => (_jsx("li", { children: cookie }, cookie))) })] }))] }));
|
|
14
|
+
/**
|
|
15
|
+
* @function Processors
|
|
16
|
+
* @param props {LegalBlockProps} the site config
|
|
17
|
+
* @returns {ReactElement} the third parties that process data on the app's behalf
|
|
18
|
+
*/
|
|
19
|
+
export const Processors = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "Service providers" }), _jsx("ul", { children: site.processors.map((processor) => (_jsxs("li", { children: [_jsx("strong", { children: processor.link ? _jsx("a", { href: processor.link, children: processor.name }) : processor.name }), " ", processor.purpose] }, processor.name))) })] }));
|
|
20
|
+
/**
|
|
21
|
+
* @function YourRights
|
|
22
|
+
* @param props {LegalBlockProps} the site config
|
|
23
|
+
* @returns {ReactElement} the GDPR and CCPA rights every visitor has, and how to use them
|
|
24
|
+
*/
|
|
25
|
+
export const YourRights = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "Your rights under the GDPR" }), _jsxs("ul", { children: [_jsxs("li", { children: [_jsx("strong", { children: "Access and portability." }), " Get a copy of your data in a machine-readable file."] }), _jsxs("li", { children: [_jsx("strong", { children: "Rectification." }), " Have wrong data corrected."] }), _jsxs("li", { children: [_jsx("strong", { children: "Erasure." }), " Have your data deleted."] }), _jsxs("li", { children: [_jsx("strong", { children: "Restriction." }), " Ask us to pause using your data while a question about it is sorted out."] }), _jsxs("li", { children: [_jsx("strong", { children: "Objection." }), " Object to anything we do on the basis of legitimate interest."] }), _jsxs("li", { children: [_jsx("strong", { children: "Complaint." }), " Complain to your data protection supervisory authority."] })] }), _jsx("h2", { children: "Your rights under the CCPA" }), _jsxs("ul", { children: [_jsxs("li", { children: [_jsx("strong", { children: "Know, delete and correct." }), " Ask what we hold about you, ask us to delete it, and ask us to correct it."] }), _jsxs("li", { children: [_jsx("strong", { children: "Selling and sharing." }), " We don't sell or share personal information."] }), _jsxs("li", { children: [_jsx("strong", { children: "Opting out." }), " We honor Global Privacy Control signals."] })] }), _jsxs("p", { children: ["To use any of these rights, email", " ", _jsx("a", { href: `mailto:${site.contactEmail}`, children: site.contactEmail }), "."] })] }));
|
|
26
|
+
/**
|
|
27
|
+
* @function DmcaNotice
|
|
28
|
+
* @param props {LegalBlockProps} the site config
|
|
29
|
+
* @returns {ReactElement} the DMCA agent, and the notice and counter-notice steps
|
|
30
|
+
*/
|
|
31
|
+
export const DmcaNotice = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "Copyright and DMCA" }), site.hosting ? _jsx("p", { children: site.hosting }) : null, _jsxs("p", { children: ["Our designated agent for copyright notices is", " ", _jsx("a", { href: `mailto:${site.contactEmail}`, children: site.contactEmail }), "."] }), _jsx("h3", { children: "A takedown notice should include" }), _jsxs("ol", { children: [_jsx("li", { children: "your contact information;" }), _jsx("li", { children: "the copyrighted work you believe is infringed;" }), _jsx("li", { children: "the URL or page the material appears on;" }), _jsx("li", { children: "a good-faith statement that the use is not authorized;" }), _jsx("li", { children: "a sworn statement that you're the rights holder, or authorized to act for them;" }), _jsx("li", { children: "your physical or electronic signature." })] }), _jsx("h3", { children: "A counter-notice should include" }), _jsxs("ol", { children: [_jsx("li", { children: "your contact information;" }), _jsx("li", { children: "the material removed and where it appeared;" }), _jsx("li", { children: "a sworn, good-faith statement that it was removed by mistake or misidentification;" }), _jsxs("li", { children: ["your consent to the jurisdiction of your local courts, or ", site.operator, "'s;"] }), _jsx("li", { children: "your physical or electronic signature." })] })] }));
|
|
32
|
+
/**
|
|
33
|
+
* @function NoWarranty
|
|
34
|
+
* @param props {LegalBlockProps} the site config
|
|
35
|
+
* @returns {ReactElement} the "as is" warranty disclaimer and limitation of liability
|
|
36
|
+
*/
|
|
37
|
+
export const NoWarranty = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "Disclaimer of warranties" }), _jsxs("p", { children: [site.siteName, " is provided \"as is\" and \"as available\", without warranties of any kind, express or implied, including merchantability, fitness for a particular purpose and non-infringement."] }), _jsx("h2", { children: "Limitation of liability" }), _jsxs("p", { children: ["To the fullest extent the law allows, ", site.operator, " is not liable for any indirect, incidental, special, consequential or punitive damages, or for any loss of data or accounts, arising from your use of ", site.siteName, "."] })] }));
|
|
38
|
+
/**
|
|
39
|
+
* @function Changes
|
|
40
|
+
* @param props {ChangesProps} the site config and an optional per-page date
|
|
41
|
+
* @returns {ReactElement} the standard "we may update this page" line with its effective date
|
|
42
|
+
*/
|
|
43
|
+
export const Changes = ({ site, date }) => (_jsxs("section", { children: [_jsx("h2", { children: "Changes" }), _jsxs("p", { children: ["We may update this page. It was last updated on ", date ?? site.effectiveDate, "."] })] }));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/entries.ts
|
|
3
|
+
* @desc legalEntries: the five standard `ContentEntry` records (`docs`'s registry shape) for the
|
|
4
|
+
* five-page legal convention, so apps stop hand-writing the same titles and descriptions.
|
|
5
|
+
* An app overrides any field per slug through `pages`; unlisted fields keep the default.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Mon Oct 5, 2026
|
|
8
|
+
* @modified Mon Oct 5, 2026
|
|
9
|
+
*/
|
|
10
|
+
import type { ContentEntry } from "../docs/registry.js";
|
|
11
|
+
import { type LegalSite, type LegalSlug } from "./types.js";
|
|
12
|
+
/** Per-slug overrides for any `ContentEntry` field, keyed by `LegalSlug`. */
|
|
13
|
+
export type LegalPageOverrides = Partial<Record<LegalSlug, Partial<ContentEntry>>>;
|
|
14
|
+
/**
|
|
15
|
+
* @function legalEntries
|
|
16
|
+
* @param site {LegalSite} the site config; `siteName` fills the default descriptions
|
|
17
|
+
* @param pages {LegalPageOverrides} [pages] per-slug overrides (a different description, a
|
|
18
|
+
* `navTitle`, a page-specific `lastUpdated`); omitted fields keep the default
|
|
19
|
+
* @returns {ContentEntry[]} the five entries, in `LEGAL_SLUGS` order, ready for the `legal` array
|
|
20
|
+
* an app's content registry (`defineContent`) takes
|
|
21
|
+
*/
|
|
22
|
+
export declare const legalEntries: (site: LegalSite, pages?: LegalPageOverrides) => ContentEntry[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/entries.ts
|
|
3
|
+
* @desc legalEntries: the five standard `ContentEntry` records (`docs`'s registry shape) for the
|
|
4
|
+
* five-page legal convention, so apps stop hand-writing the same titles and descriptions.
|
|
5
|
+
* An app overrides any field per slug through `pages`; unlisted fields keep the default.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Mon Oct 5, 2026
|
|
8
|
+
* @modified Mon Oct 5, 2026
|
|
9
|
+
*/
|
|
10
|
+
import { LEGAL_SLUGS } from "./types.js";
|
|
11
|
+
/** The default title and description for each of the five legal slugs. */
|
|
12
|
+
const DEFAULTS = {
|
|
13
|
+
terms: { title: "Terms of Service", description: "The rules for using {site}." },
|
|
14
|
+
privacy: { title: "Privacy Policy", description: "What {site} stores and why." },
|
|
15
|
+
"your-privacy-rights": {
|
|
16
|
+
title: "GDPR & CCPA",
|
|
17
|
+
description: "Your rights over your data under the GDPR and the CCPA, and how to use them.",
|
|
18
|
+
},
|
|
19
|
+
copyright: {
|
|
20
|
+
title: "Copyright & Takedown",
|
|
21
|
+
description: "How to report a copyright concern, and where to send a DMCA notice.",
|
|
22
|
+
},
|
|
23
|
+
disclaimers: {
|
|
24
|
+
title: "Disclaimers",
|
|
25
|
+
description: "Who {site} isn't affiliated with, and what it doesn't promise.",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* @function legalEntries
|
|
30
|
+
* @param site {LegalSite} the site config; `siteName` fills the default descriptions
|
|
31
|
+
* @param pages {LegalPageOverrides} [pages] per-slug overrides (a different description, a
|
|
32
|
+
* `navTitle`, a page-specific `lastUpdated`); omitted fields keep the default
|
|
33
|
+
* @returns {ContentEntry[]} the five entries, in `LEGAL_SLUGS` order, ready for the `legal` array
|
|
34
|
+
* an app's content registry (`defineContent`) takes
|
|
35
|
+
*/
|
|
36
|
+
export const legalEntries = (site, pages) => LEGAL_SLUGS.map((slug) => {
|
|
37
|
+
const fallback = DEFAULTS[slug];
|
|
38
|
+
return {
|
|
39
|
+
slug,
|
|
40
|
+
title: fallback.title,
|
|
41
|
+
description: fallback.description.replace("{site}", site.siteName),
|
|
42
|
+
lastUpdated: site.effectiveDate,
|
|
43
|
+
...pages?.[slug],
|
|
44
|
+
};
|
|
45
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/index.ts
|
|
3
|
+
* @desc @haruhimemoe/next-kit/legal: the five-page legal convention (terms, privacy,
|
|
4
|
+
* your-privacy-rights, copyright, disclaimers). `LegalSite` config, seven MDX blocks
|
|
5
|
+
* rendered from it, and `legalEntries` for the app's content registry.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Mon Oct 5, 2026
|
|
8
|
+
* @modified Mon Oct 5, 2026
|
|
9
|
+
*/
|
|
10
|
+
export { Changes, type ChangesProps, DataWeKeep, DmcaNotice, type LegalBlockProps, LegalContact, NoWarranty, Processors, YourRights, } from "./blocks.js";
|
|
11
|
+
export { type LegalPageOverrides, legalEntries } from "./entries.js";
|
|
12
|
+
export { LEGAL_SLUGS, type LegalDataStore, type LegalProcessor, type LegalSite, type LegalSlug, } from "./types.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/index.ts
|
|
3
|
+
* @desc @haruhimemoe/next-kit/legal: the five-page legal convention (terms, privacy,
|
|
4
|
+
* your-privacy-rights, copyright, disclaimers). `LegalSite` config, seven MDX blocks
|
|
5
|
+
* rendered from it, and `legalEntries` for the app's content registry.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Mon Oct 5, 2026
|
|
8
|
+
* @modified Mon Oct 5, 2026
|
|
9
|
+
*/
|
|
10
|
+
export { Changes, DataWeKeep, DmcaNotice, LegalContact, NoWarranty, Processors, YourRights, } from "./blocks.js";
|
|
11
|
+
export { legalEntries } from "./entries.js";
|
|
12
|
+
export { LEGAL_SLUGS, } from "./types.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/types.ts
|
|
3
|
+
* @desc LegalSite: the per-app config the legal blocks and legalEntries render from. No site
|
|
4
|
+
* name, URL, email or data list is hardcoded here; every app passes its own.
|
|
5
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
6
|
+
* @created Mon Oct 5, 2026
|
|
7
|
+
* @modified Mon Oct 5, 2026
|
|
8
|
+
*/
|
|
9
|
+
/** A third party that processes data on the app's behalf, like Vercel or MongoDB Atlas. */
|
|
10
|
+
export type LegalProcessor = {
|
|
11
|
+
name: string;
|
|
12
|
+
purpose: string;
|
|
13
|
+
link?: string;
|
|
14
|
+
};
|
|
15
|
+
/** One kind of data the app stores, and why. */
|
|
16
|
+
export type LegalDataStore = {
|
|
17
|
+
what: string;
|
|
18
|
+
why: string;
|
|
19
|
+
};
|
|
20
|
+
/** The five standard legal page slugs every service ships, in the order they're listed. */
|
|
21
|
+
export declare const LEGAL_SLUGS: readonly ["terms", "privacy", "your-privacy-rights", "copyright", "disclaimers"];
|
|
22
|
+
/** One of `LEGAL_SLUGS`. */
|
|
23
|
+
export type LegalSlug = (typeof LEGAL_SLUGS)[number];
|
|
24
|
+
/** The config every legal block and `legalEntries` renders from. */
|
|
25
|
+
export type LegalSite = {
|
|
26
|
+
/** The site's display name, like "packs.haruhime.moe". */
|
|
27
|
+
siteName: string;
|
|
28
|
+
/** Who runs the site, for the contact and "who's responsible" lines. */
|
|
29
|
+
operator: string;
|
|
30
|
+
/** Where a legal request, question or notice goes. */
|
|
31
|
+
contactEmail: string;
|
|
32
|
+
/** YYYY-MM-DD. Shown as this page's "last updated" date by `Changes`. */
|
|
33
|
+
effectiveDate: string;
|
|
34
|
+
/** What the app keeps, in `DataWeKeep`'s order. */
|
|
35
|
+
stores: readonly LegalDataStore[];
|
|
36
|
+
/** Who processes data on the app's behalf, in `Processors`'s order. */
|
|
37
|
+
processors: readonly LegalProcessor[];
|
|
38
|
+
/** The cookies the app sets, one line each, in `DataWeKeep`'s cookie list. */
|
|
39
|
+
cookies: readonly string[];
|
|
40
|
+
/** One sentence on what users can post or upload, shown by `DmcaNotice`. Omitted: no line. */
|
|
41
|
+
hosting?: string | undefined;
|
|
42
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/types.ts
|
|
3
|
+
* @desc LegalSite: the per-app config the legal blocks and legalEntries render from. No site
|
|
4
|
+
* name, URL, email or data list is hardcoded here; every app passes its own.
|
|
5
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
6
|
+
* @created Mon Oct 5, 2026
|
|
7
|
+
* @modified Mon Oct 5, 2026
|
|
8
|
+
*/
|
|
9
|
+
/** The five standard legal page slugs every service ships, in the order they're listed. */
|
|
10
|
+
export const LEGAL_SLUGS = [
|
|
11
|
+
"terms",
|
|
12
|
+
"privacy",
|
|
13
|
+
"your-privacy-rights",
|
|
14
|
+
"copyright",
|
|
15
|
+
"disclaimers",
|
|
16
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haruhimemoe/next-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "The Next.js plumbing the haruhime.moe tools share: JSON route helpers, rate limits and budgets in MongoDB, bearer machine auth, zod env parsing, a connect-once MongoDB client with safe index builds, better-auth with osu! sign-in, the signed-in marker and account store for the browser, SEO: metadata, robots.txt, sitemaps, JSON-LD and llms.txt, and per-app API keys and the /api/v1 guard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
@@ -70,6 +70,10 @@
|
|
|
70
70
|
"types": "./dist/docs/files/index.d.ts",
|
|
71
71
|
"default": "./dist/docs/files/index.js"
|
|
72
72
|
},
|
|
73
|
+
"./legal": {
|
|
74
|
+
"types": "./dist/legal/index.d.ts",
|
|
75
|
+
"default": "./dist/legal/index.js"
|
|
76
|
+
},
|
|
73
77
|
"./vcs": {
|
|
74
78
|
"types": "./dist/vcs/index.d.ts",
|
|
75
79
|
"default": "./dist/vcs/index.js"
|
|
@@ -104,7 +108,7 @@
|
|
|
104
108
|
},
|
|
105
109
|
"peerDependencies": {
|
|
106
110
|
"@haruhimemoe/osu": "^0.2.0 || ^0.3.0 || ^0.4.0",
|
|
107
|
-
"@haruhimemoe/ui": "^0.
|
|
111
|
+
"@haruhimemoe/ui": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 || ^0.18.0",
|
|
108
112
|
"@haruhimemoe/vcs": "^0.1.0",
|
|
109
113
|
"better-auth": "^1.7.5",
|
|
110
114
|
"mongodb": "^7.6.0",
|
|
@@ -154,7 +158,7 @@
|
|
|
154
158
|
"devDependencies": {
|
|
155
159
|
"@biomejs/biome": "2.5.14",
|
|
156
160
|
"@haruhimemoe/osu": "0.2.0",
|
|
157
|
-
"@haruhimemoe/ui": "0.
|
|
161
|
+
"@haruhimemoe/ui": "0.14.0",
|
|
158
162
|
"@haruhimemoe/vcs": "0.1.0",
|
|
159
163
|
"@testing-library/dom": "10.4.2",
|
|
160
164
|
"@testing-library/react": "16.3.3",
|