Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-04-17 18:03:26 +02:00
parent 11770b4058
commit 0e701ec6e9
5 changed files with 61 additions and 104 deletions

View File

@@ -12,14 +12,23 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE. // PERFORMANCE OF THIS SOFTWARE.
import { formatError } from "@probo/helpers";
import { usePageTitle } from "@probo/hooks";
import { useTranslate } from "@probo/i18n";
import { Button, useToast } from "@probo/ui";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { type PreloadedQuery, useMutation, usePreloadedQuery } from "react-relay"; import { type PreloadedQuery, useMutation, usePreloadedQuery } from "react-relay";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import { formatError } from "@probo/helpers";
import { usePageTitle } from "@probo/hooks";
import { useTranslate } from "@probo/i18n";
import {
Button,
IconArrowsClockwise,
IconEnvelope,
IconLockOpen,
IconUser,
IconUserCircle,
useToast,
} from "@probo/ui";
import type { ConsentPageMutation } from "#/__generated__/iam/ConsentPageMutation.graphql"; import type { ConsentPageMutation } from "#/__generated__/iam/ConsentPageMutation.graphql";
import type { ConsentPageQuery } from "#/__generated__/iam/ConsentPageQuery.graphql"; import type { ConsentPageQuery } from "#/__generated__/iam/ConsentPageQuery.graphql";
@@ -53,80 +62,12 @@ const scopeLabels: Record<string, string> = {
offline_access: "Stay signed in and access your data while you're away", offline_access: "Stay signed in and access your data while you're away",
}; };
function ScopeIcon({ scope }: { scope: string }) { const scopeIcons: Record<string, React.ReactNode> = {
switch (scope) { openid: <IconUser size={18} className="shrink-0 text-txt-tertiary" />,
case "openid": email: <IconEnvelope size={18} className="shrink-0 text-txt-tertiary" />,
return ( profile: <IconUserCircle size={18} className="shrink-0 text-txt-tertiary" />,
<svg offline_access: <IconArrowsClockwise size={18} className="shrink-0 text-txt-tertiary" />,
xmlns="http://www.w3.org/2000/svg" };
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-[18px] h-[18px] shrink-0 text-txt-tertiary"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"
/>
</svg>
);
case "email":
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-[18px] h-[18px] shrink-0 text-txt-tertiary"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"
/>
</svg>
);
case "profile":
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-[18px] h-[18px] shrink-0 text-txt-tertiary"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
/>
</svg>
);
case "offline_access":
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-[18px] h-[18px] shrink-0 text-txt-tertiary"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182M21.015 4.356v4.992"
/>
</svg>
);
default:
return null;
}
}
export default function ConsentPage(props: { export default function ConsentPage(props: {
queryRef: PreloadedQuery<ConsentPageQuery>; queryRef: PreloadedQuery<ConsentPageQuery>;
@@ -236,20 +177,7 @@ export default function ConsentPage(props: {
<div className="space-y-2 text-center"> <div className="space-y-2 text-center">
<div className="flex justify-center mb-4"> <div className="flex justify-center mb-4">
<div className="w-12 h-12 rounded-full flex items-center justify-center bg-level-1"> <div className="w-12 h-12 rounded-full flex items-center justify-center bg-level-1">
<svg <IconLockOpen size={24} />
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M13.5 10.5V6.75a4.5 4.5 0 1 1 9 0v3.75M3.75 21.75h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H3.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z"
/>
</svg>
</div> </div>
</div> </div>
<h1 className="text-2xl font-bold"> <h1 className="text-2xl font-bold">
@@ -273,7 +201,7 @@ export default function ConsentPage(props: {
key={scope} key={scope}
className="flex items-center gap-2.5 px-3 py-2.5 text-sm text-txt-secondary border border-border-mid rounded-lg" className="flex items-center gap-2.5 px-3 py-2.5 text-sm text-txt-secondary border border-border-mid rounded-lg"
> >
<ScopeIcon scope={scope} /> {scopeIcons[scope]}
{__(label)} {__(label)}
</li> </li>
); );

View File

@@ -22,11 +22,10 @@ import type { ConsentPageQuery } from "#/__generated__/iam/ConsentPageQuery.grap
import ConsentPage, { consentPageQuery } from "./ConsentPage"; import ConsentPage, { consentPageQuery } from "./ConsentPage";
function ConsentPageQueryLoader() { function ConsentPageQueryLoader() {
const [searchParams] = useSearchParams();
const consentId = searchParams.get("consent_id") ?? "";
const [queryRef, loadQuery] const [queryRef, loadQuery]
= useQueryLoader<ConsentPageQuery>(consentPageQuery); = useQueryLoader<ConsentPageQuery>(consentPageQuery);
const [searchParams] = useSearchParams();
const consentId = searchParams.get("consent_id") ?? "";
useEffect(() => { useEffect(() => {
loadQuery({ consentId }); loadQuery({ consentId });
@@ -69,8 +68,11 @@ function ConsentErrorFallback() {
} }
export default function ConsentPageLoader() { export default function ConsentPageLoader() {
const [searchParams] = useSearchParams();
const consentId = searchParams.get("consent_id") ?? "";
return ( return (
<ConsentErrorBoundary fallback={<ConsentErrorFallback />}> <ConsentErrorBoundary key={consentId} fallback={<ConsentErrorFallback />}>
<ConsentPageQueryLoader /> <ConsentPageQueryLoader />
</ConsentErrorBoundary> </ConsentErrorBoundary>
); );

View File

@@ -44,6 +44,25 @@ type TestEnv struct {
cmd *exec.Cmd cmd *exec.Cmd
done chan error done chan error
outputBuf *bytes.Buffer outputBuf *bytes.Buffer
outputWriter *switchableWriter
}
type switchableWriter struct {
mu sync.Mutex
w io.Writer
}
func (s *switchableWriter) Write(p []byte) (int, error) {
s.mu.Lock()
w := s.w
s.mu.Unlock()
return w.Write(p)
}
func (s *switchableWriter) switchTo(w io.Writer) {
s.mu.Lock()
s.w = w
s.mu.Unlock()
} }
func Setup() { func Setup() {
@@ -93,8 +112,10 @@ func Setup() {
} else { } else {
var buf bytes.Buffer var buf bytes.Buffer
testEnv.outputBuf = &buf testEnv.outputBuf = &buf
cmd.Stdout = &buf sw := &switchableWriter{w: &buf}
cmd.Stderr = &buf testEnv.outputWriter = sw
cmd.Stdout = sw
cmd.Stderr = sw
} }
testEnv.cmd = cmd testEnv.cmd = cmd
@@ -126,8 +147,7 @@ func Setup() {
} }
if !verbose { if !verbose {
cmd.Stdout = io.Discard testEnv.outputWriter.switchTo(io.Discard)
cmd.Stderr = io.Discard
} }
}) })
} }

View File

@@ -104,5 +104,12 @@ export { IconBrandFacebook } from "./IconBrandFacebook";
export { IconBrandLinkedin } from "./IconBrandLinkedin"; export { IconBrandLinkedin } from "./IconBrandLinkedin";
export { IconBrandX } from "./IconBrandX"; export { IconBrandX } from "./IconBrandX";
export { IconGlobe } from "./IconGlobe"; export { IconGlobe } from "./IconGlobe";
export {
ArrowsClockwise as IconArrowsClockwise,
Envelope as IconEnvelope,
LockOpen as IconLockOpen,
User as IconUser,
UserCircle as IconUserCircle,
} from "@phosphor-icons/react";
export { SocialIcon } from "./SocialIcon"; export { SocialIcon } from "./SocialIcon";
export type { SocialIconProps } from "./SocialIcon"; export type { SocialIconProps } from "./SocialIcon";

View File

@@ -1,7 +1,7 @@
extend type Mutation { extend type Mutation {
authorizeDevice( authorizeDevice(
input: AuthorizeDeviceInput! input: AuthorizeDeviceInput!
): AuthorizeDevicePayload @session(required: PRESENT) ): AuthorizeDevicePayload @session(required: PRESENT)
approveConsent( approveConsent(
input: ApproveConsentInput! input: ApproveConsentInput!