v0.7.0 — Suggestions module via @otm/account-panel
Wire the shared Gitea-backed suggestions panel: page, list/create + comments + upload API routes, sidebar link. Suggestions file as issues on bonna61/Moonbase; screenshot uploads persist via a mounted volume. Mirrors the FMB wiring. Secrets (NEXTAUTH_SECRET, GITEA_TOKEN) moved out of docker-compose.yml to an interpolated gitignored .env on the host, per CLAUDE.md (never commit the token). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,3 +4,9 @@ DATABASE_URL="postgresql://tonym@localhost:5432/moonbase_dev"
|
|||||||
|
|
||||||
NEXTAUTH_SECRET="change-me-generate-with-openssl-rand-base64-32"
|
NEXTAUTH_SECRET="change-me-generate-with-openssl-rand-base64-32"
|
||||||
NEXTAUTH_URL="http://localhost:3000"
|
NEXTAUTH_URL="http://localhost:3000"
|
||||||
|
|
||||||
|
# Suggestions module — files feedback as issues on a Gitea repo.
|
||||||
|
# Leave blank to disable (the Suggestions page shows a "not set up" state).
|
||||||
|
GITEA_URL="http://192.168.0.5:3022"
|
||||||
|
GITEA_TOKEN=""
|
||||||
|
GITEA_REPO="bonna61/Moonbase"
|
||||||
|
|||||||
1
.npmrc
Normal file
1
.npmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@otm:registry=http://192.168.0.5:3022/api/packages/tonym/npm/
|
||||||
@@ -21,8 +21,17 @@ services:
|
|||||||
- "8053:3000"
|
- "8053:3000"
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://moonbase:moonbase@postgres:5432/moonbase
|
DATABASE_URL: postgresql://moonbase:moonbase@postgres:5432/moonbase
|
||||||
NEXTAUTH_SECRET: 81Uds/ZnQNHZWMsHIIuqNVuBSzPqcVxdPyw1AewbjZA=
|
# Secrets are interpolated from a gitignored .env next to this file on the
|
||||||
|
# host (never committed — see CLAUDE.md). docker compose auto-loads ./.env.
|
||||||
|
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
|
||||||
NEXTAUTH_URL: http://192.168.0.5:8053
|
NEXTAUTH_URL: http://192.168.0.5:8053
|
||||||
|
# Suggestions module — files feedback as Gitea issues on bonna61/Moonbase.
|
||||||
|
GITEA_URL: http://192.168.0.5:3022
|
||||||
|
GITEA_TOKEN: ${GITEA_TOKEN}
|
||||||
|
GITEA_REPO: bonna61/Moonbase
|
||||||
|
volumes:
|
||||||
|
# Persist suggestion screenshot uploads across rebuilds.
|
||||||
|
- /mnt/user/appdata/moonbase/uploads:/app/public/uploads
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ const nextConfig = {
|
|||||||
remotePatterns: [{ protocol: "https", hostname: "**" }],
|
remotePatterns: [{ protocol: "https", hostname: "**" }],
|
||||||
},
|
},
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
|
// Shared TSX package pulled from the Gitea npm registry — transpile its source.
|
||||||
|
transpilePackages: ["@otm/account-panel"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
83
package-lock.json
generated
83
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
|
"@otm/account-panel": "0.8.2",
|
||||||
"@paralleldrive/cuid2": "^3.3.0",
|
"@paralleldrive/cuid2": "^3.3.0",
|
||||||
"@prisma/client": "^5.22.0",
|
"@prisma/client": "^5.22.0",
|
||||||
"@radix-ui/react-avatar": "^1.1.1",
|
"@radix-ui/react-avatar": "^1.1.1",
|
||||||
@@ -858,9 +859,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -877,9 +875,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -896,9 +891,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -915,9 +907,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1035,6 +1024,16 @@
|
|||||||
"node": ">=12.4.0"
|
"node": ">=12.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@otm/account-panel": {
|
||||||
|
"version": "0.8.2",
|
||||||
|
"resolved": "http://192.168.0.5:3022/api/packages/tonym/npm/%40otm%2Faccount-panel/-/0.8.2/account-panel-0.8.2.tgz",
|
||||||
|
"integrity": "sha512-E7uJ3Xxm8/xz6gcUAS4TUI+eCjaVZLFs7CjF7RpwSLUn3DpIszGSE4BN0519BQOThpQ69p3m15h/b64HLMgHgw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": ">=14",
|
||||||
|
"next-auth": ">=4",
|
||||||
|
"react": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@oxc-project/types": {
|
"node_modules/@oxc-project/types": {
|
||||||
"version": "0.133.0",
|
"version": "0.133.0",
|
||||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
||||||
@@ -2048,9 +2047,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2068,9 +2064,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2088,9 +2081,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2108,9 +2098,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2128,9 +2115,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2148,9 +2132,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2757,9 +2738,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2774,9 +2752,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2791,9 +2766,6 @@
|
|||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2808,9 +2780,6 @@
|
|||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2825,9 +2794,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2842,9 +2808,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2859,9 +2822,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2876,9 +2836,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2893,9 +2850,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2910,9 +2864,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6200,9 +6151,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6224,9 +6172,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6248,9 +6193,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6272,9 +6214,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
|
"@otm/account-panel": "0.8.2",
|
||||||
"@paralleldrive/cuid2": "^3.3.0",
|
"@paralleldrive/cuid2": "^3.3.0",
|
||||||
"@prisma/client": "^5.22.0",
|
"@prisma/client": "^5.22.0",
|
||||||
"@radix-ui/react-avatar": "^1.1.1",
|
"@radix-ui/react-avatar": "^1.1.1",
|
||||||
|
|||||||
13
src/app/(dashboard)/suggestions/page.tsx
Normal file
13
src/app/(dashboard)/suggestions/page.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Metadata } from "next";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { SuggestionsPanel } from "@otm/account-panel";
|
||||||
|
import { getSession } from "@/lib/auth";
|
||||||
|
|
||||||
|
export const metadata: Metadata = { title: "Suggestions" };
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
export default async function SuggestionsPage() {
|
||||||
|
const session = await getSession();
|
||||||
|
if (!session) redirect("/login");
|
||||||
|
return <SuggestionsPanel />;
|
||||||
|
}
|
||||||
9
src/app/api/suggestions/[number]/comments/route.ts
Normal file
9
src/app/api/suggestions/[number]/comments/route.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createSuggestionCommentsRoute } from "@otm/account-panel/server";
|
||||||
|
import { giteaConfig, suggestionGuard } from "@/lib/suggestions";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
export const { GET, POST } = createSuggestionCommentsRoute({
|
||||||
|
gitea: giteaConfig(),
|
||||||
|
guard: suggestionGuard,
|
||||||
|
});
|
||||||
9
src/app/api/suggestions/route.ts
Normal file
9
src/app/api/suggestions/route.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createSuggestionsRoute } from "@otm/account-panel/server";
|
||||||
|
import { giteaConfig, suggestionGuard } from "@/lib/suggestions";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
export const { GET, POST } = createSuggestionsRoute({
|
||||||
|
gitea: giteaConfig(),
|
||||||
|
guard: suggestionGuard,
|
||||||
|
});
|
||||||
21
src/app/api/suggestions/upload/route.ts
Normal file
21
src/app/api/suggestions/upload/route.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { mkdir, writeFile } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { createSuggestionUploadRoute } from "@otm/account-panel/server";
|
||||||
|
import { suggestionGuard } from "@/lib/suggestions";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
// Screenshots are saved under public/uploads/suggestions and referenced by
|
||||||
|
// absolute URL so Gitea's web UI can render them. In Docker this dir is mounted
|
||||||
|
// to a persistent volume (see docker-compose.yml) so images survive rebuilds.
|
||||||
|
export const { POST } = createSuggestionUploadRoute({
|
||||||
|
guard: suggestionGuard,
|
||||||
|
save: async ({ name, bytes }) => {
|
||||||
|
const safe = `${Date.now()}-${name.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
||||||
|
const dir = path.join(process.cwd(), "public", "uploads", "suggestions");
|
||||||
|
await mkdir(dir, { recursive: true });
|
||||||
|
await writeFile(path.join(dir, safe), Buffer.from(bytes));
|
||||||
|
const base = (process.env.NEXT_PUBLIC_BASE_URL || process.env.NEXTAUTH_URL || "").replace(/\/+$/, "");
|
||||||
|
return { url: base ? `${base}/uploads/suggestions/${safe}` : `/uploads/suggestions/${safe}` };
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { Leaf, Home, Wrench, LayoutDashboard, Bell, HardDrive } from "lucide-react";
|
import { Leaf, Home, Wrench, LayoutDashboard, Bell, HardDrive, Lightbulb } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
@@ -15,6 +15,7 @@ const navItems = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const settingsItems = [
|
const settingsItems = [
|
||||||
|
{ label: "Suggestions", href: "/suggestions", icon: Lightbulb },
|
||||||
{ label: "Backup & Restore", href: "/settings/backup", icon: HardDrive },
|
{ label: "Backup & Restore", href: "/settings/backup", icon: HardDrive },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ export type ChangelogEntry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: ChangelogEntry[] = [
|
export const CHANGELOG: ChangelogEntry[] = [
|
||||||
|
{
|
||||||
|
version: "0.7.0",
|
||||||
|
date: "2026-06-15",
|
||||||
|
changes: [
|
||||||
|
"New Suggestions page — share ideas, requests, or problems right from the app. You can attach screenshots, see what's still open vs. done, and reply in a thread. Everything you send goes straight to Tony.",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: "0.6.0",
|
version: "0.6.0",
|
||||||
date: "2026-06-15",
|
date: "2026-06-15",
|
||||||
|
|||||||
20
src/lib/suggestions.ts
Normal file
20
src/lib/suggestions.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { NextRequest } from "next/server";
|
||||||
|
import type { GiteaConfig } from "@otm/account-panel";
|
||||||
|
import { getSession } from "./auth";
|
||||||
|
|
||||||
|
// Suggestions are filed as issues on the Gitea repo — no DB model.
|
||||||
|
// Returns null (→ routes answer 503, panel shows a tidy empty state) when any
|
||||||
|
// piece is missing, so the feature degrades gracefully if env isn't wired.
|
||||||
|
export function giteaConfig(): GiteaConfig | null {
|
||||||
|
const baseUrl = process.env.GITEA_URL;
|
||||||
|
const token = process.env.GITEA_TOKEN;
|
||||||
|
const repo = process.env.GITEA_REPO;
|
||||||
|
if (!baseUrl || !token || !repo) return null;
|
||||||
|
return { baseUrl, token, repo };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Only signed-in users may read/write suggestions. */
|
||||||
|
export async function suggestionGuard(_req: NextRequest): Promise<boolean> {
|
||||||
|
const session = await getSession();
|
||||||
|
return !!session?.user;
|
||||||
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
// Bump on every user-visible release. Changelog entries live in `src/lib/changelog.ts`.
|
// Bump on every user-visible release. Changelog entries live in `src/lib/changelog.ts`.
|
||||||
export const APP_VERSION = "0.6.0";
|
export const APP_VERSION = "0.7.0";
|
||||||
|
|||||||
Reference in New Issue
Block a user