- {items.map(({ to, label, Icon, exact }) => (
+ {items.map(({ to, label, Icon, exact, shortcut }) => (
;
@@ -22,16 +21,19 @@ function DropdownMenuContent({
side = "bottom",
sideOffset = 4,
className,
+ positionerClassName,
...props
}: MenuPrimitive.Popup.Props &
Pick<
MenuPrimitive.Positioner.Props,
"align" | "alignOffset" | "side" | "sideOffset"
- >) {
+ > & {
+ positionerClassName?: string;
+ }) {
return (
(
+ "[data-shortcut-target='page-search']",
+ );
+
+ if (!searchInput || searchInput.disabled) return false;
+ searchInput.focus();
+ searchInput.select();
+ return true;
+}
+
+export function clickShortcutAction(action: "new") {
+ const element = document.querySelector(
+ `[data-shortcut-action='${action}']`,
+ );
+
+ if (!element) return false;
+ if (element instanceof HTMLButtonElement && element.disabled) return false;
+ element.click();
+ return true;
+}
+
+export function focusMainContent() {
+ const main = document.querySelector(
+ "[data-shortcut-target='main-content']",
+ );
+ if (!(main instanceof HTMLElement)) return false;
+ main.focus();
+ return true;
+}
diff --git a/src/lib/spotify.ts b/src/lib/spotify.ts
index be89887..e823346 100644
--- a/src/lib/spotify.ts
+++ b/src/lib/spotify.ts
@@ -4,6 +4,7 @@
const SPOTIFY_CLIENT_ID = import.meta.env.VITE_SPOTIFY_CLIENT_ID || "";
const CONFIGURED_REDIRECT_URI = import.meta.env.VITE_SPOTIFY_REDIRECT_URI || "";
+const SPOTIFY_REDIRECT_URI_STORAGE_KEY = "spotify_redirect_uri";
function getSpotifyRedirectUri() {
if (typeof window === "undefined") {
@@ -11,15 +12,15 @@ function getSpotifyRedirectUri() {
}
const currentOriginRedirect = `${window.location.origin}/spotify-callback`;
- if (CONFIGURED_REDIRECT_URI) {
- return CONFIGURED_REDIRECT_URI;
- }
-
const isLocalDev =
window.location.hostname === "localhost" ||
window.location.hostname === "127.0.0.1";
- return isLocalDev ? currentOriginRedirect : "/spotify-callback";
+ if (isLocalDev) {
+ return currentOriginRedirect;
+ }
+
+ return CONFIGURED_REDIRECT_URI || currentOriginRedirect;
}
/**
@@ -73,6 +74,8 @@ export async function redirectToSpotifyAuth() {
const authUrl = new URL("https://accounts.spotify.com/authorize");
const redirectUri = getSpotifyRedirectUri();
+ window.localStorage.setItem(SPOTIFY_REDIRECT_URI_STORAGE_KEY, redirectUri);
+
const params = {
response_type: "code",
client_id: SPOTIFY_CLIENT_ID,
@@ -92,7 +95,9 @@ export async function redirectToSpotifyAuth() {
*/
export async function getSpotifyToken(code: string): Promise {
const verifier = window.localStorage.getItem("spotify_code_verifier");
- const redirectUri = getSpotifyRedirectUri();
+ const redirectUri =
+ window.localStorage.getItem(SPOTIFY_REDIRECT_URI_STORAGE_KEY) ||
+ getSpotifyRedirectUri();
const params = new URLSearchParams({
client_id: SPOTIFY_CLIENT_ID,
@@ -141,9 +146,11 @@ export async function getUserProfile(accessToken: string) {
} catch (e) {
errorDetails = "Não foi possível ler o corpo do erro.";
}
-
+
console.error(`Erro Spotify (${response.status}):`, errorDetails);
- throw new Error(`Erro ao buscar perfil (${response.status}): ${errorDetails}`);
+ throw new Error(
+ `Erro ao buscar perfil (${response.status}): ${errorDetails}`,
+ );
}
return response.json();
diff --git a/src/lib/use-shortcut-viewport.ts b/src/lib/use-shortcut-viewport.ts
new file mode 100644
index 0000000..bfa31c7
--- /dev/null
+++ b/src/lib/use-shortcut-viewport.ts
@@ -0,0 +1,24 @@
+import { useEffect, useState } from "react";
+
+const SHORTCUT_VIEWPORT_QUERY = "(min-width: 768px)";
+
+export function useShortcutViewport() {
+ const [supportsShortcuts, setSupportsShortcuts] = useState(() =>
+ typeof window === "undefined"
+ ? true
+ : window.matchMedia(SHORTCUT_VIEWPORT_QUERY).matches,
+ );
+
+ useEffect(() => {
+ const mediaQuery = window.matchMedia(SHORTCUT_VIEWPORT_QUERY);
+ const updateSupportsShortcuts = () =>
+ setSupportsShortcuts(mediaQuery.matches);
+
+ updateSupportsShortcuts();
+ mediaQuery.addEventListener("change", updateSupportsShortcuts);
+ return () =>
+ mediaQuery.removeEventListener("change", updateSupportsShortcuts);
+ }, []);
+
+ return supportsShortcuts;
+}
diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx
index 80f8616..6b36c62 100644
--- a/src/routes/__root.tsx
+++ b/src/routes/__root.tsx
@@ -58,6 +58,7 @@ function RootContent() {
function ProtectedLayout() {
const navigate = useNavigate();
+ const mainContentId = useId();
const { loading, session, plan } = useAuth();
const loadRemoteData = useRPGStore((s) => s.loadRemoteData);
const setupRealtime = useRPGStore((s) => s.setupRealtime);
@@ -139,6 +140,12 @@ function ProtectedLayout() {
return (
+
+ Pular para o conteúdo
+
{/* Mobile Sidebar Overlay */}
{isSidebarOpen && (