mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-13 15:19:15 +00:00
feat: remove the donation reminder and its column
This commit is contained in:
1 parent
0b41672a3c
commit
abdbb34ce2
18 files changed
+14010
-105
No files matched your search
@@ -0,0 +1 @@
|
||||
ALTER TABLE `users` DROP COLUMN `donation_modal_dismissed`;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -491,6 +491,13 @@
|
||||
"when": 1791388888997,
|
||||
"tag": "0069_plugin_channel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 70,
|
||||
"version": "5",
|
||||
"when": 1791550497926,
|
||||
"tag": "0070_drop_donation_modal",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "users" DROP COLUMN "donation_modal_dismissed";
|
||||
File diff suppressed because it is too large.
Load diff
@@ -491,6 +491,13 @@
|
||||
"when": 1791388888397,
|
||||
"tag": "0069_plugin_channel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 70,
|
||||
"version": "7",
|
||||
"when": 1791550496661,
|
||||
"tag": "0070_drop_donation_modal",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `users` DROP COLUMN `donation_modal_dismissed`;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -456,6 +456,13 @@
|
||||
"when": 1791388887785,
|
||||
"tag": "0064_plugin_channel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 65,
|
||||
"version": "6",
|
||||
"when": 1791550495357,
|
||||
"tag": "0065_drop_donation_modal",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -242,8 +242,7 @@ async function initializeCompleteDatabase(): Promise<void> {
|
||||
username TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
is_admin INTEGER NOT NULL DEFAULT 0,
|
||||
registered_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
donation_modal_dismissed INTEGER NOT NULL DEFAULT 0
|
||||
registered_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
@@ -970,9 +969,7 @@ const migrateSchema = () => {
|
||||
// CURRENT_TIMESTAMP, so the column is added empty and backfilled below.
|
||||
addColumnIfNotExists("users", "registered_at", "TEXT");
|
||||
if (!hadRegisteredAtColumn) {
|
||||
// Pre-existing users are backdated past the 30 day mark so they see the
|
||||
// donation modal immediately on upgrade instead of waiting a fresh
|
||||
// 30 days as if they had just registered.
|
||||
// Pre-existing users get a date in the past, since their real one is unknown.
|
||||
try {
|
||||
sqlite.exec(
|
||||
`UPDATE users SET registered_at = datetime('now', '-31 days') WHERE registered_at IS NULL`,
|
||||
@@ -1000,12 +997,6 @@ const migrateSchema = () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
addColumnIfNotExists(
|
||||
"users",
|
||||
"donation_modal_dismissed",
|
||||
"INTEGER NOT NULL DEFAULT 0",
|
||||
);
|
||||
|
||||
addColumnIfNotExists("sessions", "external_session_ref", "TEXT");
|
||||
|
||||
addColumnIfNotExists("ssh_data", "name", "TEXT");
|
||||
|
||||
@@ -77,6 +77,7 @@ export const RETIRED_COLUMNS: Record<string, string[]> = {
|
||||
"identifier_path",
|
||||
"name_path",
|
||||
"scopes",
|
||||
"donation_modal_dismissed",
|
||||
],
|
||||
sessions: ["oidc_sub", "oidc_sid", "sso_provider_id"],
|
||||
user_preferences: [
|
||||
|
||||
@@ -27,9 +27,6 @@ export const users = mysqlTable("users", {
|
||||
passwordHash: text("password_hash").notNull(),
|
||||
isAdmin: boolean("is_admin").notNull().default(false),
|
||||
registeredAt: text("registered_at").notNull().default(sql`(CURRENT_TIMESTAMP)`),
|
||||
donationModalDismissed: boolean("donation_modal_dismissed")
|
||||
.notNull()
|
||||
.default(false),
|
||||
});
|
||||
|
||||
export const settings = mysqlTable("settings", {
|
||||
|
||||
@@ -27,9 +27,6 @@ export const users = pgTable("users", {
|
||||
passwordHash: text("password_hash").notNull(),
|
||||
isAdmin: boolean("is_admin").notNull().default(false),
|
||||
registeredAt: text("registered_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
donationModalDismissed: boolean("donation_modal_dismissed")
|
||||
.notNull()
|
||||
.default(false),
|
||||
});
|
||||
|
||||
export const settings = pgTable("settings", {
|
||||
|
||||
@@ -15,11 +15,6 @@ export const users = sqliteTable("users", {
|
||||
passwordHash: text("password_hash").notNull(),
|
||||
isAdmin: integer("is_admin", { mode: "boolean" }).notNull().default(false),
|
||||
registeredAt: text("registered_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
donationModalDismissed: integer("donation_modal_dismissed", {
|
||||
mode: "boolean",
|
||||
})
|
||||
.notNull()
|
||||
.default(false),
|
||||
});
|
||||
|
||||
export const settings = sqliteTable("settings", {
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
isNativeTokenExportRequest,
|
||||
resolveDesktopAutoSessionUser,
|
||||
} from "./desktop-auto-session.js";
|
||||
import { shouldShowDonationModal } from "./donation-modal-utils.js";
|
||||
import { PermissionManager } from "../../utils/permission-manager.js";
|
||||
import { registerUserApiKeyRoutes } from "./user-api-key-routes.js";
|
||||
import { registerBrandingRoutes } from "./branding-routes.js";
|
||||
@@ -595,11 +594,6 @@ router.get("/me", authenticateJWT, async (req: Request, res: Response) => {
|
||||
const hasPassword = user.passwordHash && user.passwordHash.trim() !== "";
|
||||
const isDualAuth = hasPassword && isExternalAccount(user);
|
||||
|
||||
const showDonationModal = shouldShowDonationModal(
|
||||
user.registeredAt,
|
||||
!!user.donationModalDismissed,
|
||||
);
|
||||
|
||||
res.json({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
@@ -610,7 +604,6 @@ router.get("/me", authenticateJWT, async (req: Request, res: Response) => {
|
||||
totp_enabled: await createCurrentUserAuthRepository().hasSecondFactor(
|
||||
user.id,
|
||||
),
|
||||
show_donation_modal: showDonationModal,
|
||||
linked: await describeDesktopLink(user.id),
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -619,48 +612,6 @@ router.get("/me", authenticateJWT, async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /users/me/dismiss-donation-modal:
|
||||
* post:
|
||||
* summary: Permanently dismiss the donation reminder modal
|
||||
* description: Marks the donation reminder modal as dismissed for the currently authenticated user so it is never shown to them again.
|
||||
* tags:
|
||||
* - Users
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Donation modal dismissed.
|
||||
* 401:
|
||||
* description: Invalid userId or user not found.
|
||||
* 500:
|
||||
* description: Failed to dismiss donation modal.
|
||||
*/
|
||||
router.post(
|
||||
"/me/dismiss-donation-modal",
|
||||
authenticateJWT,
|
||||
async (req: Request, res: Response) => {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
|
||||
if (!isNonEmptyString(userId)) {
|
||||
return res.status(401).json({ error: "Invalid userId" });
|
||||
}
|
||||
try {
|
||||
const updated = await createCurrentUserRepository().update(userId, {
|
||||
donationModalDismissed: true,
|
||||
});
|
||||
if (!updated) {
|
||||
return res.status(401).json({ error: "User not found" });
|
||||
}
|
||||
return res.json({ success: true });
|
||||
} catch (err) {
|
||||
authLogger.error("Failed to dismiss donation modal", err);
|
||||
return res
|
||||
.status(500)
|
||||
.json({ error: "Failed to dismiss donation modal" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /users/me/token:
|
||||
|
||||
+2
-18
@@ -137,7 +137,6 @@ import {
|
||||
getActiveSessions,
|
||||
getUserPreferences,
|
||||
saveUserPreferences,
|
||||
dismissDonationModal,
|
||||
type UserPreferences,
|
||||
type OpenTabRecord,
|
||||
} from "@/main-axios";
|
||||
@@ -147,7 +146,6 @@ import {
|
||||
resolveLayoutTabTarget,
|
||||
snapshotData,
|
||||
} from "@/shell/shell-layout";
|
||||
import { DonationReminderModal } from "@/user/DonationReminderModal.tsx";
|
||||
import { useSyncStatus } from "@/hooks/use-sync-status";
|
||||
import { remScale } from "@/lib/rem";
|
||||
import { dbHealthMonitor } from "@/lib/db-health-monitor";
|
||||
@@ -304,8 +302,6 @@ export function AppShell({
|
||||
// The standalone desktop backend still owns system settings such as the
|
||||
// Tailscale API key, even though it has only one implicit user.
|
||||
const showAdminUI = isAdmin;
|
||||
const [showDonationModal, setShowDonationModal] = useState(false);
|
||||
const [showOnboarding, setShowOnboarding] = useState(false);
|
||||
const [backgroundTabRecords, setBackgroundTabRecords] = useState<
|
||||
OpenTabRecord[]
|
||||
>([]);
|
||||
@@ -444,18 +440,10 @@ export function AppShell({
|
||||
|
||||
useEffect(() => {
|
||||
getUserInfo()
|
||||
.then((info) => {
|
||||
setIsAdmin(info.is_admin);
|
||||
setShowDonationModal(!!info.show_donation_modal);
|
||||
})
|
||||
.then((info) => setIsAdmin(info.is_admin))
|
||||
.catch(() => setIsAdmin(false));
|
||||
}, []);
|
||||
|
||||
const handleDismissDonationModal = useCallback(() => {
|
||||
setShowDonationModal(false);
|
||||
dismissDonationModal().catch(() => {});
|
||||
}, []);
|
||||
|
||||
const toggleAppFullscreen = useCallback(async () => {
|
||||
try {
|
||||
if (document.fullscreenElement) {
|
||||
@@ -3192,11 +3180,7 @@ export function AppShell({
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
<OnboardingHost onOpenChange={setShowOnboarding} />
|
||||
<DonationReminderModal
|
||||
open={showDonationModal && !showOnboarding}
|
||||
onDismiss={handleDismissDonationModal}
|
||||
/>
|
||||
<OnboardingHost />
|
||||
</ServerStatusProvider>
|
||||
);
|
||||
}
|
||||
@@ -1130,13 +1130,6 @@
|
||||
"customizeLayout": "Customize Dashboard",
|
||||
"justNow": "just now"
|
||||
},
|
||||
"donation": {
|
||||
"title": "Enjoying Termix?",
|
||||
"body": "Termix is free and open source, built and maintained by a two-person team in our spare time. If it's replaced a commercial tool you'd otherwise be paying for, a donation helps cover hosting costs and keeps development going. As of now, donations are crypto only.",
|
||||
"milestones": "Donations also help fund the time to research and learn what's needed to build features like SAML and Kubernetes support. See the progress on the donate page.",
|
||||
"cta": "Donate",
|
||||
"dismiss": "Maybe later"
|
||||
},
|
||||
"dashboardTab": {
|
||||
"stable": "STABLE",
|
||||
"registerNewServer": "Register a new server",
|
||||
|
||||
@@ -97,7 +97,6 @@ export interface UserInfo {
|
||||
is_dual_auth?: boolean;
|
||||
password_hash?: string;
|
||||
data_unlocked?: boolean;
|
||||
show_donation_modal?: boolean;
|
||||
/** On a desktop linked to a server, the account it is signed in to there. */
|
||||
linked?: LinkedAccountInfo | null;
|
||||
}
|
||||
@@ -1018,14 +1017,6 @@ export async function getUserInfo(): Promise<UserInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function dismissDonationModal(): Promise<void> {
|
||||
try {
|
||||
await authApi.post("/users/me/dismiss-donation-modal");
|
||||
} catch (error) {
|
||||
handleApiError(error, "dismiss donation modal");
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentToken(): Promise<string | null> {
|
||||
try {
|
||||
const response = await authApi.get("/users/me/token");
|
||||
|
||||
Reference in new issue
Block a user