Skip to content

Commit 795030b

Browse files
committed
permission check troubleshooting utils in frontend and in dev
1 parent 8b52962 commit 795030b

File tree

16 files changed

+503
-236
lines changed

16 files changed

+503
-236
lines changed

Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ hypr-audio-interface = { path = "crates/audio-interface", package = "audio-inter
4343
hypr-audio-priority = { path = "crates/audio-priority", package = "audio-priority" }
4444
hypr-audio-utils = { path = "crates/audio-utils", package = "audio-utils" }
4545
hypr-buffer = { path = "crates/buffer", package = "buffer" }
46+
hypr-bundle = { path = "crates/bundle", package = "bundle" }
4647
hypr-data = { path = "crates/data", package = "data" }
4748
hypr-db-core = { path = "crates/db-core", package = "db-core" }
4849
hypr-db-user = { path = "crates/db-user", package = "db-user" }

apps/desktop/src/components/settings/calendar/configure/apple/index.tsx

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { openUrl } from "@tauri-apps/plugin-opener";
22

3-
import { commands as permissionsCommands } from "@hypr/plugin-permissions";
43
import {
54
AccordionContent,
65
AccordionItem,
76
AccordionTrigger,
87
} from "@hypr/ui/components/ui/accordion";
98

9+
import { usePermission } from "../../../../../hooks/use-permissions";
1010
import { StyledStreamdown } from "../../../ai/shared";
1111
import { PROVIDERS } from "../../shared";
1212
import { AppleCalendarSelection } from "./calendar-selection";
1313
import { SyncProvider } from "./context";
14-
import { AccessPermissionRow, useAccessPermission } from "./permission";
14+
import { AccessPermissionRow } from "./permission";
1515

1616
export function Section({
1717
title,
@@ -38,19 +38,8 @@ export function Section({
3838
export function AppleCalendarProviderCard() {
3939
const config = PROVIDERS.find((p) => p.id === "apple")!;
4040

41-
const calendar = useAccessPermission({
42-
queryKey: "appleCalendarAccess",
43-
checkPermission: () => permissionsCommands.checkPermission("calendar"),
44-
requestPermission: () => permissionsCommands.requestPermission("calendar"),
45-
openSettings: () => permissionsCommands.openPermission("calendar"),
46-
});
47-
48-
const contacts = useAccessPermission({
49-
queryKey: "appleContactsAccess",
50-
checkPermission: () => permissionsCommands.checkPermission("contacts"),
51-
requestPermission: () => permissionsCommands.requestPermission("contacts"),
52-
openSettings: () => permissionsCommands.openPermission("contacts"),
53-
});
41+
const calendar = usePermission("calendar");
42+
const contacts = usePermission("contacts");
5443

5544
return (
5645
<AccordionItem
@@ -85,25 +74,25 @@ export function AppleCalendarProviderCard() {
8574
<Section title="Permissions">
8675
<div className="space-y-1">
8776
<AccessPermissionRow
88-
title="Calendar Access"
89-
grantedDescription="Permission granted. Click to open settings."
90-
requestDescription="Grant access to sync events from your Apple Calendar"
91-
isAuthorized={calendar.isAuthorized}
77+
title="Calendar"
78+
status={calendar.status}
9279
isPending={calendar.isPending}
93-
onAction={calendar.handleAction}
80+
onOpen={calendar.open}
81+
onRequest={calendar.request}
82+
onReset={calendar.reset}
9483
/>
9584
<AccessPermissionRow
96-
title="Contacts Access"
97-
grantedDescription="Permission granted. Click to open settings."
98-
requestDescription="Grant access to match participants with your contacts"
99-
isAuthorized={contacts.isAuthorized}
85+
title="Contacts"
86+
status={contacts.status}
10087
isPending={contacts.isPending}
101-
onAction={contacts.handleAction}
88+
onOpen={contacts.open}
89+
onRequest={contacts.request}
90+
onReset={contacts.reset}
10291
/>
10392
</div>
10493
</Section>
10594

106-
{calendar.isAuthorized && (
95+
{calendar.status === "authorized" && (
10796
<SyncProvider>
10897
<AppleCalendarSelection />
10998
</SyncProvider>

apps/desktop/src/components/settings/calendar/configure/apple/permission.tsx

Lines changed: 69 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,61 @@
1-
import { useMutation, useQuery } from "@tanstack/react-query";
21
import { AlertCircleIcon, ArrowRightIcon, CheckIcon } from "lucide-react";
2+
import { useState } from "react";
33

44
import { type PermissionStatus } from "@hypr/plugin-permissions";
55
import { Button } from "@hypr/ui/components/ui/button";
66
import { cn } from "@hypr/utils";
77

8-
export function useAccessPermission(config: {
9-
queryKey: string;
10-
checkPermission: () => Promise<
11-
| { status: "ok"; data: PermissionStatus }
12-
| { status: "error"; error: string }
13-
>;
14-
requestPermission: () => Promise<unknown>;
15-
openSettings: () => Promise<unknown>;
8+
function ActionLink({
9+
onClick,
10+
disabled,
11+
children,
12+
}: {
13+
onClick: () => void;
14+
disabled?: boolean;
15+
children: React.ReactNode;
1616
}) {
17-
const status = useQuery({
18-
queryKey: [config.queryKey],
19-
queryFn: async () => {
20-
const result = await config.checkPermission();
21-
if (result.status === "ok") {
22-
return result.data;
23-
}
24-
return "denied" as PermissionStatus;
25-
},
26-
refetchInterval: 1000,
27-
});
28-
29-
const requestAccess = useMutation({
30-
mutationFn: config.requestPermission,
31-
onSuccess: () => {
32-
setTimeout(() => status.refetch(), 1000);
33-
},
34-
});
35-
36-
const isAuthorized = status.data === "authorized";
37-
const isPending = requestAccess.isPending;
38-
39-
const handleAction = async () => {
40-
if (isAuthorized) {
41-
await config.openSettings();
42-
} else if (status.data === "denied") {
43-
await config.openSettings();
44-
} else {
45-
requestAccess.mutate();
46-
}
47-
};
48-
49-
return { status: status.data, isAuthorized, isPending, handleAction };
17+
return (
18+
<button
19+
type="button"
20+
onClick={onClick}
21+
disabled={disabled}
22+
className={cn([
23+
"underline hover:text-neutral-900 transition-colors",
24+
disabled && "opacity-50 cursor-not-allowed",
25+
])}
26+
>
27+
{children}
28+
</button>
29+
);
5030
}
5131

5232
export function AccessPermissionRow({
5333
title,
54-
grantedDescription,
55-
requestDescription,
56-
isAuthorized,
34+
status,
5735
isPending,
58-
onAction,
36+
onOpen,
37+
onRequest,
38+
onReset,
5939
}: {
6040
title: string;
61-
grantedDescription: string;
62-
requestDescription: string;
63-
isAuthorized: boolean;
41+
status: PermissionStatus | undefined;
6442
isPending: boolean;
65-
onAction: () => void;
43+
onOpen: () => void;
44+
onRequest: () => void;
45+
onReset: () => void;
6646
}) {
47+
const [showActions, setShowActions] = useState(false);
48+
const isAuthorized = status === "authorized";
49+
const isDenied = status === "denied";
50+
51+
const handleButtonClick = () => {
52+
if (isAuthorized || isDenied) {
53+
onOpen();
54+
} else {
55+
onRequest();
56+
}
57+
};
58+
6759
return (
6860
<div className="flex items-center justify-between gap-4 py-2">
6961
<div className="flex-1">
@@ -76,14 +68,37 @@ export function AccessPermissionRow({
7668
{!isAuthorized && <AlertCircleIcon className="size-4" />}
7769
<h3 className="text-sm font-medium">{title}</h3>
7870
</div>
79-
<p className="text-xs text-neutral-600">
80-
{isAuthorized ? grantedDescription : requestDescription}
81-
</p>
71+
<div className="text-xs text-neutral-600">
72+
{!showActions ? (
73+
<button
74+
type="button"
75+
onClick={() => setShowActions(true)}
76+
className="underline hover:text-neutral-900 transition-colors"
77+
>
78+
Having trouble?
79+
</button>
80+
) : (
81+
<div>
82+
You can{" "}
83+
<ActionLink onClick={onRequest} disabled={isPending}>
84+
Request,
85+
</ActionLink>{" "}
86+
<ActionLink onClick={onReset} disabled={isPending}>
87+
Reset
88+
</ActionLink>{" "}
89+
or{" "}
90+
<ActionLink onClick={onOpen} disabled={isPending}>
91+
Open
92+
</ActionLink>{" "}
93+
permission panel.
94+
</div>
95+
)}
96+
</div>
8297
</div>
8398
<Button
8499
variant={isAuthorized ? "outline" : "default"}
85100
size="icon"
86-
onClick={onAction}
101+
onClick={handleButtonClick}
87102
disabled={isPending}
88103
className={cn([
89104
"size-8",

0 commit comments

Comments
 (0)