mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-02 16:23:48 +03:00
Merge pull request #1204 from hydralauncher/feat/adding-intercom
Feat/adding intercom
This commit is contained in:
commit
df0e124c3a
@ -36,6 +36,7 @@
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@fontsource/noto-sans": "^5.0.22",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@intercom/messenger-js-sdk": "^0.0.14",
|
||||
"@primer/octicons-react": "^19.9.0",
|
||||
"@reduxjs/toolkit": "^2.2.3",
|
||||
"@vanilla-extract/css": "^1.14.2",
|
||||
|
@ -25,7 +25,8 @@
|
||||
"queued": "{{title}} (Queued)",
|
||||
"game_has_no_executable": "Game has no executable selected",
|
||||
"sign_in": "Sign in",
|
||||
"friends": "Friends"
|
||||
"friends": "Friends",
|
||||
"need_help": "Need help?"
|
||||
},
|
||||
"header": {
|
||||
"search": "Search games",
|
||||
|
@ -25,7 +25,8 @@
|
||||
"queued": "{{title}} (En cola)",
|
||||
"game_has_no_executable": "El juego no tiene un ejecutable seleccionado",
|
||||
"sign_in": "Iniciar sesión",
|
||||
"friends": "Amigos"
|
||||
"friends": "Amigos",
|
||||
"need_help": "¿Necesitas ayuda?"
|
||||
},
|
||||
"header": {
|
||||
"search": "Buscar juegos",
|
||||
|
@ -25,7 +25,8 @@
|
||||
"queued": "{{title}} (Na fila)",
|
||||
"game_has_no_executable": "Jogo não possui executável selecionado",
|
||||
"sign_in": "Login",
|
||||
"friends": "Amigos"
|
||||
"friends": "Amigos",
|
||||
"need_help": "Precisa de ajuda?"
|
||||
},
|
||||
"header": {
|
||||
"search": "Buscar jogos",
|
||||
|
@ -24,7 +24,8 @@
|
||||
"queued": "{{title}} (В очереди)",
|
||||
"game_has_no_executable": "Файл запуска игры не выбран",
|
||||
"sign_in": "Войти",
|
||||
"friends": "Друзья"
|
||||
"friends": "Друзья",
|
||||
"need_help": "Нужна помощь?"
|
||||
},
|
||||
"header": {
|
||||
"search": "Поиск",
|
||||
|
@ -56,6 +56,7 @@ export const getUserData = () => {
|
||||
id: loggedUser.userId,
|
||||
username: "",
|
||||
bio: "",
|
||||
email: null,
|
||||
profileVisibility: "PUBLIC" as ProfileVisibility,
|
||||
subscription: loggedUser.subscription
|
||||
? {
|
||||
|
@ -85,6 +85,10 @@ export class WindowManager {
|
||||
return callback(details);
|
||||
}
|
||||
|
||||
if (details.url.includes("intercom.io")) {
|
||||
return callback(details);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
"access-control-allow-origin": ["*"],
|
||||
"access-control-allow-methods": ["GET, POST, PUT, DELETE, OPTIONS"],
|
||||
|
@ -6,7 +6,7 @@
|
||||
<title>Hydra</title>
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: local: *; media-src 'self' local: data: *;"
|
||||
content="default-src 'self' *.intercom.io wss://nexus-websocket-a.intercom.io; script-src 'self' *.intercom.io *.intercomcdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: *; media-src 'self' local: data: *; font-src 'self' https://fonts.intercomcdn.com"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -126,3 +126,9 @@ export const titleBar = style({
|
||||
zIndex: "4",
|
||||
borderBottom: `1px solid ${vars.color.border}`,
|
||||
} as ComplexStyleRule);
|
||||
|
||||
export const cloudText = style({
|
||||
background: "linear-gradient(270deg, #16B195 50%, #3E62C0 100%)",
|
||||
backgroundClip: "text",
|
||||
color: "transparent",
|
||||
});
|
||||
|
@ -2,6 +2,8 @@ import { useCallback, useContext, useEffect, useRef } from "react";
|
||||
|
||||
import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components";
|
||||
|
||||
import Intercom from "@intercom/messenger-js-sdk";
|
||||
|
||||
import {
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
@ -34,6 +36,10 @@ export interface AppProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
Intercom({
|
||||
app_id: "pq96v8fh",
|
||||
});
|
||||
|
||||
export function App() {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const { updateLibrary, library } = useLibrary();
|
||||
@ -54,8 +60,13 @@ export function App() {
|
||||
hideFriendsModal,
|
||||
} = useUserDetails();
|
||||
|
||||
const { userDetails, fetchUserDetails, updateUserDetails, clearUserDetails } =
|
||||
useUserDetails();
|
||||
const {
|
||||
userDetails,
|
||||
hasActiveSubscription,
|
||||
fetchUserDetails,
|
||||
updateUserDetails,
|
||||
clearUserDetails,
|
||||
} = useUserDetails();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -204,7 +215,9 @@ export function App() {
|
||||
|
||||
useEffect(() => {
|
||||
new MutationObserver(() => {
|
||||
const modal = document.body.querySelector("[role=dialog]");
|
||||
const modal = document.body.querySelector(
|
||||
"[role=dialog]:not([data-intercom-frame='true'])"
|
||||
);
|
||||
|
||||
dispatch(toggleDraggingDisabled(Boolean(modal)));
|
||||
}).observe(document.body, {
|
||||
@ -270,7 +283,12 @@ export function App() {
|
||||
<>
|
||||
{window.electron.platform === "win32" && (
|
||||
<div className={styles.titleBar}>
|
||||
<h4>Hydra</h4>
|
||||
<h4>
|
||||
Hydra
|
||||
{hasActiveSubscription && (
|
||||
<span className={styles.cloudText}> Cloud</span>
|
||||
)}
|
||||
</h4>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
color: globals.$muted-color;
|
||||
font-size: 10px;
|
||||
padding: calc(globals.$spacing-unit / 2) globals.$spacing-unit;
|
||||
border: solid 1px globals.$border-color;
|
||||
border: solid 1px globals.$muted-color;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -124,3 +124,29 @@ export const section = style({
|
||||
flexDirection: "column",
|
||||
paddingBottom: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
||||
export const helpButton = style({
|
||||
color: vars.color.muted,
|
||||
padding: `${SPACING_UNIT}px ${SPACING_UNIT * 2}px`,
|
||||
gap: "9px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
boxShadow: "0px 0px 15px 0px rgba(0, 0, 0, 0.6)",
|
||||
borderTop: `solid 1px ${vars.color.border}`,
|
||||
transition: "background-color ease 0.1s",
|
||||
":hover": {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||
},
|
||||
});
|
||||
|
||||
export const helpButtonIcon = style({
|
||||
background: "linear-gradient(0deg, #16B195 50%, #3E62C0 100%)",
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "#fff",
|
||||
borderRadius: "50%",
|
||||
});
|
||||
|
@ -5,7 +5,12 @@ import { useLocation, useNavigate } from "react-router-dom";
|
||||
import type { LibraryGame } from "@types";
|
||||
|
||||
import { TextField } from "@renderer/components";
|
||||
import { useDownload, useLibrary, useToast } from "@renderer/hooks";
|
||||
import {
|
||||
useDownload,
|
||||
useLibrary,
|
||||
useToast,
|
||||
useUserDetails,
|
||||
} from "@renderer/hooks";
|
||||
|
||||
import { routes } from "./routes";
|
||||
|
||||
@ -15,6 +20,9 @@ import { buildGameDetailsPath } from "@renderer/helpers";
|
||||
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
||||
import { SidebarProfile } from "./sidebar-profile";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { CommentDiscussionIcon } from "@primer/octicons-react";
|
||||
|
||||
import { show, update } from "@intercom/messenger-js-sdk";
|
||||
|
||||
const SIDEBAR_MIN_WIDTH = 200;
|
||||
const SIDEBAR_INITIAL_WIDTH = 250;
|
||||
@ -42,6 +50,20 @@ export function Sidebar() {
|
||||
return sortBy(library, (game) => game.title);
|
||||
}, [library]);
|
||||
|
||||
const { userDetails, hasActiveSubscription } = useUserDetails();
|
||||
|
||||
useEffect(() => {
|
||||
if (userDetails) {
|
||||
update({
|
||||
name: userDetails.displayName,
|
||||
Username: userDetails.username,
|
||||
Email: userDetails.email,
|
||||
"Subscription expiration date": userDetails?.subscription?.expiresAt,
|
||||
"Payment status": userDetails?.subscription?.status,
|
||||
});
|
||||
}
|
||||
}, [userDetails, hasActiveSubscription]);
|
||||
|
||||
const { lastPacket, progress } = useDownload();
|
||||
|
||||
const { showWarningToast } = useToast();
|
||||
@ -237,6 +259,15 @@ export function Sidebar() {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{hasActiveSubscription && (
|
||||
<button type="button" className={styles.helpButton} onClick={show}>
|
||||
<div className={styles.helpButtonIcon}>
|
||||
<CommentDiscussionIcon size={14} />
|
||||
</div>
|
||||
<span>{t("need_help")}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={styles.handle}
|
||||
|
@ -10,12 +10,22 @@ export interface HowLongToBeatEntry {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CatalogueCache {
|
||||
id?: number;
|
||||
category: string;
|
||||
games: { objectId: string; shop: GameShop }[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
expiresAt: Date;
|
||||
}
|
||||
|
||||
export const db = new Dexie("Hydra");
|
||||
|
||||
db.version(4).stores({
|
||||
db.version(5).stores({
|
||||
repacks: `++id, title, uris, fileSize, uploadDate, downloadSourceId, repacker, createdAt, updatedAt`,
|
||||
downloadSources: `++id, url, name, etag, downloadCount, status, createdAt, updatedAt`,
|
||||
howLongToBeatEntries: `++id, categories, [shop+objectId], createdAt, updatedAt`,
|
||||
catalogueCache: `++id, category, games, createdAt, updatedAt, expiresAt`,
|
||||
});
|
||||
|
||||
export const downloadSourcesTable = db.table("downloadSources");
|
||||
@ -24,4 +34,6 @@ export const howLongToBeatEntriesTable = db.table<HowLongToBeatEntry>(
|
||||
"howLongToBeatEntries"
|
||||
);
|
||||
|
||||
export const catalogueCacheTable = db.table<CatalogueCache>("catalogueCache");
|
||||
|
||||
db.open();
|
||||
|
@ -15,6 +15,14 @@ import * as styles from "./home.css";
|
||||
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||
import { buildGameDetailsPath } from "@renderer/helpers";
|
||||
import { CatalogueCategory } from "@shared";
|
||||
import { catalogueCacheTable, db } from "@renderer/dexie";
|
||||
import { add } from "date-fns";
|
||||
|
||||
const categoryCacheDurationInSeconds = {
|
||||
[CatalogueCategory.Hot]: 60 * 60 * 2,
|
||||
[CatalogueCategory.Weekly]: 60 * 60 * 24,
|
||||
[CatalogueCategory.Achievements]: 60 * 60 * 24,
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
const { t } = useTranslation("home");
|
||||
@ -36,19 +44,43 @@ export default function Home() {
|
||||
[CatalogueCategory.Achievements]: [],
|
||||
});
|
||||
|
||||
const getCatalogue = useCallback((category: CatalogueCategory) => {
|
||||
setCurrentCatalogueCategory(category);
|
||||
setIsLoading(true);
|
||||
const getCatalogue = useCallback(async (category: CatalogueCategory) => {
|
||||
try {
|
||||
const catalogueCache = await catalogueCacheTable
|
||||
.where("expiresAt")
|
||||
.above(new Date())
|
||||
.and((cache) => cache.category === category)
|
||||
.first();
|
||||
|
||||
window.electron
|
||||
.getCatalogue(category)
|
||||
.then((catalogue) => {
|
||||
setCatalogue((prev) => ({ ...prev, [category]: catalogue }));
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
setCurrentCatalogueCategory(category);
|
||||
setIsLoading(true);
|
||||
|
||||
if (catalogueCache)
|
||||
return setCatalogue((prev) => ({
|
||||
...prev,
|
||||
[category]: catalogueCache.games,
|
||||
}));
|
||||
|
||||
const catalogue = await window.electron.getCatalogue(category);
|
||||
|
||||
db.transaction("rw", catalogueCacheTable, async () => {
|
||||
await catalogueCacheTable.where("category").equals(category).delete();
|
||||
|
||||
await catalogueCacheTable.add({
|
||||
category,
|
||||
games: catalogue,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
expiresAt: add(new Date(), {
|
||||
seconds: categoryCacheDurationInSeconds[category],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
setCatalogue((prev) => ({ ...prev, [category]: catalogue }));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getRandomGame = useCallback(() => {
|
||||
|
@ -245,6 +245,7 @@ export interface Subscription {
|
||||
export interface UserDetails {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string | null;
|
||||
displayName: string;
|
||||
profileImageUrl: string | null;
|
||||
backgroundImageUrl: string | null;
|
||||
@ -257,6 +258,7 @@ export interface UserProfile {
|
||||
id: string;
|
||||
displayName: string;
|
||||
profileImageUrl: string | null;
|
||||
email: string | null;
|
||||
backgroundImageUrl: string | null;
|
||||
profileVisibility: ProfileVisibility;
|
||||
libraryGames: UserGame[];
|
||||
@ -373,4 +375,4 @@ export interface ComparedAchievements {
|
||||
export * from "./steam.types";
|
||||
export * from "./real-debrid.types";
|
||||
export * from "./ludusavi.types";
|
||||
export * from "./howlongtobeat.types";
|
||||
export * from "./how-long-to-beat.types";
|
||||
|
@ -1066,6 +1066,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
|
||||
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
|
||||
|
||||
"@intercom/messenger-js-sdk@^0.0.14":
|
||||
version "0.0.14"
|
||||
resolved "https://registry.yarnpkg.com/@intercom/messenger-js-sdk/-/messenger-js-sdk-0.0.14.tgz#a27999370cc0a82a2a57a779426df25a57891863"
|
||||
integrity sha512-2dH4BDAh9EI90K7hUkAdZ76W79LM45Sd1OBX7t6Vzy8twpNiQ5X+7sH9G5hlJlkSGnf+vFWlFcy9TOYAyEs1hA==
|
||||
|
||||
"@isaacs/cliui@^8.0.2":
|
||||
version "8.0.2"
|
||||
resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user