Improve auth method selection (#442)

* move brand icons

* add icons

* modify the auth page

* change how dialog works

* preview

* improve the auth methods page

* better predicate types

* convert to table

* fix more stuff

* refactor

* add default case

* edit table

* add config

* add brand colors

* icon refresh

* fix a bug with shared tooltips

* Refactor auth methods page and preview with UI improvements

* Simplify provider update confirmation with async/await

* Update packages/stack-ui/src/components/brand-icons.tsx

Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>

* update deps

* more fixes

---------

Co-authored-by: Zai Shi <zaishi00@outlook.com>
Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>
This commit is contained in:
CactusBlue 2025-02-18 20:54:24 -08:00 committed by GitHub
parent 018b04e825
commit 6692194250
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 501 additions and 250 deletions

View File

@ -124,23 +124,20 @@ export default function PageClient () {
<Separator orientation="vertical" />
<div className="w-1/2 self-stretch py-4 px-4 lg:px-20 bg-zinc-300 dark:bg-zinc-800 hidden md:flex items-center">
{
(
<div className="w-full">
<BrowserFrame url="your-website.com/signin">
<div className="flex flex-col items-center justify-center min-h-[400px]">
<div className='w-full sm:max-w-xs m-auto scale-90 pointer-events-none' inert=''>
{/* a transparent cover that prevents the card from being clicked, even when pointer-events is overridden */}
<div className="absolute inset-0 bg-transparent z-10"></div>
<AuthPage
type="sign-in"
mockProject={mockProject}
/>
</div>
</div>
</BrowserFrame>
<div className="w-full">
<BrowserFrame url="your-website.com/signin">
<div className="flex flex-col items-center justify-center min-h-[400px]">
<div className='w-full sm:max-w-xs m-auto scale-90 pointer-events-none' inert=''>
{/* a transparent cover that prevents the card from being clicked, even when pointer-events is overridden */}
<div className="absolute inset-0 bg-transparent z-10"></div>
<AuthPage
type="sign-in"
mockProject={mockProject}
/>
</div>
</div>
)}
</BrowserFrame>
</div>
</div>
</div>
);

View File

@ -1,13 +1,15 @@
"use client";
import { SettingCard, SettingSwitch } from "@/components/settings";
import { AdminOAuthProviderConfig, AuthPage, OAuthProviderConfig } from "@stackframe/stack";
import { allProviders } from "@stackframe/stack-shared/dist/utils/oauth";
import { ActionDialog, Typography } from "@stackframe/stack-ui";
import { ActionDialog, Badge, BrandIcons, BrowserFrame, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Input, SimpleTooltip, Typography } from "@stackframe/stack-ui";
import { AsteriskSquare, CirclePlus, Key, Link2, MoreHorizontal } from "lucide-react";
import { useState } from "react";
import { CardSubtitle } from "../../../../../../../../../packages/stack-ui/dist/components/ui/card";
import { PageLayout } from "../page-layout";
import { useAdminApp } from "../use-admin-app";
import { ProviderSettingSwitch } from "./providers";
import { ProviderSettingDialog, ProviderSettingSwitch, TurnOffProviderDialog } from "./providers";
function ConfirmSignUpEnabledDialog(props: {
open?: boolean,
@ -73,57 +75,33 @@ function ConfirmSignUpDisabledDialog(props: {
);
}
export default function PageClient() {
function DisabledProvidersDialog({ open, onOpenChange }: { open?: boolean, onOpenChange?: (open: boolean) => void }) {
const stackAdminApp = useAdminApp();
const project = stackAdminApp.useProject();
const oauthProviders = project.config.oauthProviders;
const [confirmSignUpEnabled, setConfirmSignUpEnabled] = useState(false);
const [confirmSignUpDisabled, setConfirmSignUpDisabled] = useState(false);
const [providerSearch, setProviderSearch] = useState("");
const filteredProviders = allProviders
.filter((id) => id.toLowerCase().includes(providerSearch.toLowerCase()))
.map((id) => [id, oauthProviders.find((provider) => provider.id === id)] as const)
.filter(([, provider]) => {
return !provider?.enabled;
});
return (
<PageLayout title="Auth Methods" description="Configure how users can sign in to your app">
<SettingCard>
<CardSubtitle>
Email-based
</CardSubtitle>
<SettingSwitch
label="Email password authentication"
checked={project.config.credentialEnabled}
onCheckedChange={async (checked) => {
await project.update({
config: {
credentialEnabled: checked,
},
});
}}
/>
<SettingSwitch
label="Magic link/OTP"
checked={project.config.magicLinkEnabled}
onCheckedChange={async (checked) => {
await project.update({
config: {
magicLinkEnabled: checked,
},
});
}}
/>
<SettingSwitch
label="Passkey"
checked={project.config.passkeyEnabled}
onCheckedChange={async (checked) => {
await project.update({
config: {
passkeyEnabled: checked,
},
});
}}
/>
<CardSubtitle className="mt-2">
SSO (OAuth)
</CardSubtitle>
{allProviders.map((id) => {
const provider = oauthProviders.find((provider) => provider.id === id);
return <ActionDialog
title="Add New Auth Method"
open={open}
onOpenChange={onOpenChange}
cancelButton
>
<Input
className="mb-4"
placeholder="Search for a provider..."
value={providerSearch}
onChange={(e) => setProviderSearch(e.target.value)}
/>
<div className="flex gap-2 flex-wrap justify-center">
{filteredProviders
.map(([id, provider]) => {
return <ProviderSettingSwitch
key={id}
id={id}
@ -140,7 +118,207 @@ export default function PageClient() {
}}
/>;
})}
</SettingCard>
{ filteredProviders.length === 0 && <Typography variant="secondary">No providers found.</Typography> }
</div>
</ActionDialog>;
}
function OAuthActionCell({ config }: { config: AdminOAuthProviderConfig }) {
const stackAdminApp = useAdminApp();
const project = stackAdminApp.useProject();
const oauthProviders = project.config.oauthProviders;
const [turnOffProviderDialogOpen, setTurnOffProviderDialogOpen] = useState(false);
const [providerSettingDialogOpen, setProviderSettingDialogOpen] = useState(false);
const updateProvider = async (provider: AdminOAuthProviderConfig & OAuthProviderConfig) => {
const alreadyExist = oauthProviders.some((p) => p.id === config.id);
const newOAuthProviders = oauthProviders.map((p) => p.id === config.id ? provider : p);
if (!alreadyExist) {
newOAuthProviders.push(provider);
}
await project.update({
config: { oauthProviders: newOAuthProviders },
});
};
return (
<DropdownMenu>
<TurnOffProviderDialog
open={turnOffProviderDialogOpen}
onClose={() => setTurnOffProviderDialogOpen(false)}
providerId={config.id}
onConfirm={async () => {
await updateProvider({
...config,
id: config.id,
enabled: false
});
}}
/>
<ProviderSettingDialog
id={config.id}
open={providerSettingDialogOpen}
onClose={() => setProviderSettingDialogOpen(false)}
updateProvider={updateProvider}
/>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => { setProviderSettingDialogOpen(true); }}>
Configure
</DropdownMenuItem>
<DropdownMenuItem
className="text-red-400"
onClick={() => { setTurnOffProviderDialogOpen(true); }}
>
Disable Provider
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
const SHARED_TOOLTIP = "Shared keys are automatically created by Stack, but show Stack's logo on the OAuth sign-in page.\n\nYou should replace these before you go into production.";
export default function PageClient() {
const stackAdminApp = useAdminApp();
const project = stackAdminApp.useProject();
const oauthProviders = project.config.oauthProviders;
const [confirmSignUpEnabled, setConfirmSignUpEnabled] = useState(false);
const [confirmSignUpDisabled, setConfirmSignUpDisabled] = useState(false);
const [disabledProvidersDialogOpen, setDisabledProvidersDialogOpen] = useState(false);
const enabledProviders = allProviders
.map((id) => [id, oauthProviders.find((provider) => provider.id === id)] as const)
.filter(([, provider]) => provider?.enabled);
return (
<PageLayout title="Auth Methods" description="Configure how users can sign in to your app">
<div className="flex gap-4">
<SettingCard className="flex-grow">
<SettingSwitch
label={
<div className="flex items-center gap-2">
<AsteriskSquare size={20} aria-hidden="true" />
<span>Email/password authentication</span>
</div>
}
checked={project.config.credentialEnabled}
onCheckedChange={async (checked) => {
await project.update({
config: {
credentialEnabled: checked,
},
});
}}
/>
<SettingSwitch
label={
<div className="flex items-center gap-2">
<Link2 size={20} />
<span>Magic link (Email OTP)</span>
</div>
}
checked={project.config.magicLinkEnabled}
onCheckedChange={async (checked) => {
await project.update({
config: {
magicLinkEnabled: checked,
},
});
}}
/>
<SettingSwitch
label={
<div className="flex items-center gap-2">
<Key size={20} />
<span>Passkey</span>
</div>
}
checked={project.config.passkeyEnabled}
onCheckedChange={async (checked) => {
await project.update({
config: {
passkeyEnabled: checked,
},
});
}}
/>
<CardSubtitle className="mt-2">
SSO Providers
</CardSubtitle>
{ enabledProviders.map(([, provider]) => provider)
.filter((provider): provider is AdminOAuthProviderConfig => !!provider).map(provider => {
return <div key={provider.id} className="flex h-10 items-center justify-between">
<div className="flex items-center gap-2">
<div
className="flex items-center justify-center w-12 h-12 rounded-md border border-gray-800"
style={{ backgroundColor: BrandIcons.BRAND_COLORS[provider.id] ?? undefined }}
>
<BrandIcons.Mapping iconSize={24} provider={provider.id} />
</div>
<span className="text-sm font-semibold">{BrandIcons.toTitle(provider.id)}</span>
{provider.type === 'shared' && <SimpleTooltip tooltip={SHARED_TOOLTIP}>
<Badge variant="secondary">Shared keys</Badge>
</SimpleTooltip>}
</div>
<OAuthActionCell config={provider} />
</div>;
}) }
<Button
className="mt-4"
onClick={() => {
setDisabledProvidersDialogOpen(true);
}}
variant="secondary"
>
<CirclePlus size={16}/>
<span className="ml-2">Add SSO providers</span>
</Button>
<DisabledProvidersDialog
open={disabledProvidersDialogOpen}
onOpenChange={(x) => {
setDisabledProvidersDialogOpen(x);
}}
/>
</SettingCard>
<SettingCard className="hidden lg:flex">
<div className="self-stretch py-4 px-4 min-w-[400px] items-center">
<div className="w-full">
<BrowserFrame url="your-website.com/signin">
<div className="flex flex-col items-center justify-center min-h-[400px]">
<div className='w-full sm:max-w-xs m-auto scale-90 pointer-events-none' inert=''>
{/* a transparent cover that prevents the card from being clicked, even when pointer-events is overridden */}
<div className="absolute inset-0 bg-transparent z-10"></div>
<AuthPage
type="sign-in"
mockProject={{
config: {
...project.config,
oauthProviders: enabledProviders
.map(([, provider]) => provider)
.filter((provider): provider is AdminOAuthProviderConfig => !!provider),
},
}}
/>
</div>
</div>
</BrowserFrame>
</div>
</div>
</SettingCard>
</div>
<SettingCard title="Sign-up">
<SettingSwitch
label="Allow new user sign-ups"

View File

@ -1,13 +1,12 @@
"use client";
import { FormDialog } from "@/components/form-dialog";
import { InputField, SwitchField } from "@/components/form-fields";
import { SettingIconButton, SettingSwitch } from "@/components/settings";
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
import { AdminProject } from "@stackframe/stack";
import type { AdminProject } from "@stackframe/stack";
import { yupBoolean, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
import { sharedProviders } from "@stackframe/stack-shared/dist/utils/oauth";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { ActionDialog, Badge, InlineCode, Label, SimpleTooltip, Typography } from "@stackframe/stack-ui";
import { ActionDialog, Badge, BrandIcons, InlineCode, Label, SimpleTooltip, Typography } from "@stackframe/stack-ui";
import clsx from "clsx";
import { useState } from "react";
import * as yup from "yup";
@ -160,7 +159,7 @@ export function ProviderSettingDialog(props: Props & { open: boolean, onClose: (
export function TurnOffProviderDialog(props: {
open: boolean,
onClose: () => void,
onConfirm: () => void,
onConfirm: () => Promise<void>,
providerId: string,
}) {
return (
@ -172,7 +171,7 @@ export function TurnOffProviderDialog(props: {
okButton={{
label: `Disable ${toTitle(props.providerId)}`,
onClick: async () => {
props.onConfirm();
await props.onConfirm();
},
}}
cancelButton
@ -202,35 +201,33 @@ export function ProviderSettingSwitch(props: Props) {
return (
<>
<SettingSwitch
label={
<div className="flex items-center gap-2">
{toTitle(props.id)}
{isShared && enabled &&
<SimpleTooltip tooltip={"Shared keys are automatically created by Stack, but show Stack's logo on the OAuth sign-in page.\n\nYou should replace these before you go into production."}>
<Badge variant="secondary">Shared keys</Badge>
</SimpleTooltip>
}
</div>
<div className={clsx("flex flex-col items-center justify-center gap-2 py-2 border border-1 rounded-lg p-2 w-[120px] h-[120px] cursor-pointer transition-all",
enabled ? "border-white" : "border-gray-800 hover:border-gray-400"
)}
onClick={() => {
if (enabled) {
setTurnOffProviderDialogOpen(true);
} else {
setProviderSettingDialogOpen(true);
}
checked={enabled}
onCheckedChange={async (checked) => {
if (!checked) {
setTurnOffProviderDialogOpen(true);
return;
} else {
setProviderSettingDialogOpen(true);
}
}}
actions={<SettingIconButton onClick={() => setProviderSettingDialogOpen(true)} />}
onlyShowActionsWhenChecked
/>
}}
>
<BrandIcons.Mapping iconSize={28} provider={props.id} />
<span className="text-sm">{toTitle(props.id)}</span>
{isShared && enabled &&
<SimpleTooltip tooltip={"Shared keys are automatically created by Stack, but show Stack's logo on the OAuth sign-in page.\n\nYou should replace these before you go into production."}>
<Badge variant="secondary">Shared keys</Badge>
</SimpleTooltip>
}
</div>
<TurnOffProviderDialog
open={TurnOffProviderDialogOpen}
onClose={() => setTurnOffProviderDialogOpen(false)}
providerId={props.id}
onConfirm={() => runAsynchronously(updateProvider(false))}
onConfirm={async () => {
await updateProvider(false);
}}
/>
<ProviderSettingDialog {...props} open={ProviderSettingDialogOpen} onClose={() => setProviderSettingDialogOpen(false)} />

View File

@ -173,7 +173,7 @@ export const getCommonUserColumns = <T extends ExtendedServerUser>() => [
},
] satisfies ColumnDef<T>[];
const columns: ColumnDef<ExtendedServerUser>[] = [
const columns: ColumnDef<ExtendedServerUser>[] = [
...getCommonUserColumns<ExtendedServerUser>(),
{
accessorKey: "authTypes",

View File

@ -14,9 +14,10 @@ export function SettingCard(props: {
actions?: React.ReactNode,
children?: React.ReactNode,
accordion?: string,
className?: string,
}) {
return (
<Card>
<Card className={props.className}>
{(props.title || props.description) && (
<CardHeader>
{props.title && <CardTitle>{props.title}</CardTitle>}

View File

@ -0,0 +1,217 @@
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
export function Google({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
<path fill="none" d="M1 1h22v22H1z" />
</svg>
);
}
export function Facebook({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 512 512">
<path fill='#FFFFFF' d="M512 256C512 114.6 397.4 0 256 0S0 114.6 0 256C0 376 82.7 476.8 194.2 504.5V334.2H141.4V256h52.8V222.3c0-87.1 39.4-127.5 125-127.5c16.2 0 44.2 3.2 55.7 6.4V172c-6-.6-16.5-1-29.6-1c-42 0-58.2 15.9-58.2 57.2V256h83.6l-14.4 78.2H287V510.1C413.8 494.8 512 386.9 512 256h0z"/>
</svg>
);
}
export function GitHub({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 496 512">
<path fill='#FFFFFF' d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/>
</svg>
);
}
export function Microsoft({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 21 21">
<title>{"MS-SymbolLockup"}</title>
<path fill="#f25022" d="M1 1h9v9H1z" />
<path fill="#00a4ef" d="M1 11h9v9H1z" />
<path fill="#7fba00" d="M11 1h9v9h-9z" />
<path fill="#ffb900" d="M11 11h9v9h-9z" />
</svg>
);
}
export function Spotify({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 496 512">
<path fill='#ffffff' d="M248 8C111.1 8 0 119.1 0 256s111.1 248 248 248 248-111.1 248-248S384.9 8 248 8zm100.7 364.9c-4.2 0-6.8-1.3-10.7-3.6-62.4-37.6-135-39.2-206.7-24.5-3.9 1-9 2.6-11.9 2.6-9.7 0-15.8-7.7-15.8-15.8 0-10.3 6.1-15.2 13.6-16.8 81.9-18.1 165.6-16.5 237 26.2 6.1 3.9 9.7 7.4 9.7 16.5s-7.1 15.4-15.2 15.4zm26.9-65.6c-5.2 0-8.7-2.3-12.3-4.2-62.5-37-155.7-51.9-238.6-29.4-4.8 1.3-7.4 2.6-11.9 2.6-10.7 0-19.4-8.7-19.4-19.4s5.2-17.8 15.5-20.7c27.8-7.8 56.2-13.6 97.8-13.6 64.9 0 127.6 16.1 177 45.5 8.1 4.8 11.3 11 11.3 19.7-.1 10.8-8.5 19.5-19.4 19.5zm31-76.2c-5.2 0-8.4-1.3-12.9-3.9-71.2-42.5-198.5-52.7-280.9-29.7-3.6 1-8.1 2.6-12.9 2.6-13.2 0-23.3-10.3-23.3-23.6 0-13.6 8.4-21.3 17.4-23.9 35.2-10.3 74.6-15.2 117.5-15.2 73 0 149.5 15.2 205.4 47.8 7.8 4.5 12.9 10.7 12.9 22.6 0 13.6-11 23.3-23.2 23.3z"/>
</svg>
);
}
export function Discord({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 127.14 96.36">
<path fill="#fff" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/>
</svg>
);
}
export function Gitlab({ iconSize } : { iconSize: number} ) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={iconSize}
height={iconSize}
viewBox="0 -10 256 256"
version="1.1"
preserveAspectRatio="xMidYMid"
>
<g>
<path d="M128.07485,236.074667 L128.07485,236.074667 L175.17885,91.1043048 L80.9708495,91.1043048 L128.07485,236.074667 L128.07485,236.074667 Z" fill="#E24329"></path>
<path d="M128.07485,236.074423 L80.9708495,91.104061 L14.9557638,91.104061 L128.07485,236.074423 L128.07485,236.074423 Z" fill="#FC6D26"></path>
<path d="M14.9558857,91.1044267 L14.9558857,91.1044267 L0.641828571,135.159589 C-0.663771429,139.17757 0.766171429,143.57955 4.18438095,146.06275 L128.074971,236.074789 L14.9558857,91.1044267 L14.9558857,91.1044267 Z" fill="#FCA326"></path>
<path d="M14.9558857,91.1045486 L80.9709714,91.1045486 L52.6000762,3.79026286 C51.1408762,-0.703146667 44.7847619,-0.701927619 43.3255619,3.79026286 L14.9558857,91.1045486 L14.9558857,91.1045486 Z" fill="#E24329"></path>
<path d="M128.07485,236.074423 L175.17885,91.104061 L241.193935,91.104061 L128.07485,236.074423 L128.07485,236.074423 Z" fill="#FC6D26"></path>
<path d="M241.193935,91.1044267 L241.193935,91.1044267 L255.507992,135.159589 C256.813592,139.17757 255.38365,143.57955 251.96544,146.06275 L128.07485,236.074789 L241.193935,91.1044267 L241.193935,91.1044267 Z" fill="#FCA326"></path>
<path d="M241.193935,91.1045486 L175.17885,91.1045486 L203.549745,3.79026286 C205.008945,-0.703146667 211.365059,-0.701927619 212.824259,3.79026286 L241.193935,91.1045486 L241.193935,91.1045486 Z" fill="#E24329"></path>
</g>
</svg>
);
}
export function Bitbucket({ iconSize }: { iconSize: number }) {
return (
<svg
preserveAspectRatio="xMidYMid"
xmlns="http://www.w3.org/2000/svg"
viewBox="-0.9662264221278978 -0.5824607696358868 257.93281329857973 230.8324730411935"
width={iconSize}
height={iconSize}
>
<linearGradient
id="a"
x1="108.633%"
x2="46.927%"
y1="13.818%"
y2="78.776%"
>
<stop offset=".18" stopColor="#0052cc" />
<stop offset="1" stopColor="#2684ff" />
</linearGradient>
<g fill="none">
<path d="M101.272 152.561h53.449l12.901-75.32H87.06z" />
<path d="M8.308 0A8.202 8.202 0 0 0 .106 9.516l34.819 211.373a11.155 11.155 0 0 0 10.909 9.31h167.04a8.202 8.202 0 0 0 8.201-6.89l34.82-213.752a8.202 8.202 0 0 0-8.203-9.514zm146.616 152.768h-53.315l-14.436-75.42h80.67z" fill="#2684ff"/>
<path d="M244.61 77.242h-76.916l-12.909 75.36h-53.272l-62.902 74.663a11.105 11.105 0 0 0 7.171 2.704H212.73a8.196 8.196 0 0 0 8.196-6.884z" fill="url(#a)"/>
</g>
</svg>
);
}
export function LinkedIn({ iconSize } : { iconSize: number} ) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="#fff"
height={iconSize}
width={iconSize}
viewBox="0 0 310 310"
>
<g id="XMLID_801_">
<path id="XMLID_802_" d="M72.16,99.73H9.927c-2.762,0-5,2.239-5,5v199.928c0,2.762,2.238,5,5,5H72.16c2.762,0,5-2.238,5-5V104.73 C77.16,101.969,74.922,99.73,72.16,99.73z" />
<path id="XMLID_803_" d="M41.066,0.341C18.422,0.341,0,18.743,0,41.362C0,63.991,18.422,82.4,41.066,82.4 c22.626,0,41.033-18.41,41.033-41.038C82.1,18.743,63.692,0.341,41.066,0.341z" />
<path id="XMLID_804_" d="M230.454,94.761c-24.995,0-43.472,10.745-54.679,22.954V104.73c0-2.761-2.238-5-5-5h-59.599 c-2.762,0-5,2.239-5,5v199.928c0,2.762,2.238,5,5,5h62.097c2.762,0,5-2.238,5-5v-98.918c0-33.333,9.054-46.319,32.29-46.319 c25.306,0,27.317,20.818,27.317,48.034v97.204c0,2.762,2.238,5,5,5H305c2.762,0,5-2.238,5-5V194.995 C310,145.43,300.549,94.761,230.454,94.761z" />
</g>
</svg>
);
}
export function Apple({ iconSize } : { iconSize: number} ) {
return (
<svg fill="#fff" height={iconSize} width={iconSize} version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22.773 22.773">
<path d="M15.769,0c0.053,0,0.106,0,0.162,0c0.13,1.606-0.483,2.806-1.228,3.675c-0.731,0.863-1.732,1.7-3.351,1.573c-0.108-1.583,0.506-2.694,1.25-3.561C13.292,0.879,14.557,0.16,15.769,0z"/>
<path d="M20.67,16.716c0,0.016,0,0.03,0,0.045c-0.455,1.378-1.104,2.559-1.896,3.655c-0.723,0.995-1.609,2.334-3.191,2.334c-1.367,0-2.275-0.879-3.676-0.903c-1.482-0.024-2.297,0.735-3.652,0.926c-0.155,0-0.31,0-0.462,0c-0.995-0.144-1.798-0.932-2.383-1.642c-1.725-2.098-3.058-4.808-3.306-8.276c0-0.34,0-0.679,0-1.019c0.105-2.482,1.311-4.5,2.914-5.478c0.846-0.52,2.009-0.963,3.304-0.765c0.555,0.086,1.122,0.276,1.619,0.464c0.471,0.181,1.06,0.502,1.618,0.485c0.378-0.011,0.754-0.208,1.135-0.347c1.116-0.403,2.21-0.865,3.652-0.648c1.733,0.262,2.963,1.032,3.723,2.22c-1.466,0.933-2.625,2.339-2.427,4.74C17.818,14.688,19.086,15.964,20.67,16.716z"/>
</svg>
);
}
export function X({ iconSize } : { iconSize: number} ) {
return (
<svg aria-label="X" viewBox="0 0 1200 1227" width={iconSize} height={iconSize} xmlns="http://www.w3.org/2000/svg">
<path fill="#FFFFFF" d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z"/>
</svg>
);
}
export function Mapping({
provider,
iconSize,
}: {
provider: string,
iconSize: number,
}) {
switch (provider) {
case "google": {
return <Google iconSize={iconSize} />;
}
case "github": {
return <GitHub iconSize={iconSize} />;
}
case "facebook": {
return <Facebook iconSize={iconSize} />;
}
case "microsoft": {
return <Microsoft iconSize={iconSize} />;
}
case "spotify": {
return <Spotify iconSize={iconSize} />;
}
case "discord": {
return <Discord iconSize={iconSize} />;
}
case "gitlab": {
return <Gitlab iconSize={iconSize} />;
}
case "bitbucket": {
return <Bitbucket iconSize={iconSize} />;
}
case "linkedin": {
return <LinkedIn iconSize={iconSize} />;
}
case "apple": {
return <Apple iconSize={iconSize} />;
}
case "x": {
return <X iconSize={iconSize} />;
}
default: {
throw new StackAssertionError(`Icon not found for provider: ${provider}`);;
}
}
}
export function toTitle(id: string) {
return {
github: "GitHub",
google: "Google",
facebook: "Facebook",
microsoft: "Microsoft",
spotify: "Spotify",
discord: "Discord",
gitlab: "GitLab",
apple: "Apple",
bitbucket: "Bitbucket",
linkedin: "LinkedIn",
x: "X",
}[id] || throwErr(`Unknown provider: ${id}`);
}
export const BRAND_COLORS: Record<string, string> = {
github: '#24292e',
google: '#ffffff',
facebook: '#0866FF',
microsoft: '#2F2F2F',
spotify: '#1DD65F',
discord: '#5661F5',
linkedin: '#0A66C2',
x: '#000000',
};

View File

@ -1,5 +1,6 @@
export * from "./components/action-dialog";
export * as BrandIcons from "./components/brand-icons";
export * from "./components/browser-frame";
export * from "./components/copy-button";
export * from "./components/copy-field";

View File

@ -1,6 +1,6 @@
'use client';
import { Button } from '@stackframe/stack-ui';
import { BrandIcons, Button } from '@stackframe/stack-ui';
import Color from 'color';
import { useId } from 'react';
import { useStackApp } from '..';
@ -8,146 +8,6 @@ import { useTranslation } from '../lib/translations';
const iconSize = 22;
function GoogleIcon({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
<path fill="none" d="M1 1h22v22H1z" />
</svg>
);
}
function FacebookIcon({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 512 512">
<path fill='#FFFFFF' d="M512 256C512 114.6 397.4 0 256 0S0 114.6 0 256C0 376 82.7 476.8 194.2 504.5V334.2H141.4V256h52.8V222.3c0-87.1 39.4-127.5 125-127.5c16.2 0 44.2 3.2 55.7 6.4V172c-6-.6-16.5-1-29.6-1c-42 0-58.2 15.9-58.2 57.2V256h83.6l-14.4 78.2H287V510.1C413.8 494.8 512 386.9 512 256h0z"/>
</svg>
);
}
function GitHubIcon({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 496 512">
<path fill='#FFFFFF' d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/>
</svg>
);
}
function MicrosoftIcon({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 21 21">
<title>{"MS-SymbolLockup"}</title>
<path fill="#f25022" d="M1 1h9v9H1z" />
<path fill="#00a4ef" d="M1 11h9v9H1z" />
<path fill="#7fba00" d="M11 1h9v9h-9z" />
<path fill="#ffb900" d="M11 11h9v9h-9z" />
</svg>
);
}
function SpotifyIcon({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 496 512">
<path fill='#ffffff' d="M248 8C111.1 8 0 119.1 0 256s111.1 248 248 248 248-111.1 248-248S384.9 8 248 8zm100.7 364.9c-4.2 0-6.8-1.3-10.7-3.6-62.4-37.6-135-39.2-206.7-24.5-3.9 1-9 2.6-11.9 2.6-9.7 0-15.8-7.7-15.8-15.8 0-10.3 6.1-15.2 13.6-16.8 81.9-18.1 165.6-16.5 237 26.2 6.1 3.9 9.7 7.4 9.7 16.5s-7.1 15.4-15.2 15.4zm26.9-65.6c-5.2 0-8.7-2.3-12.3-4.2-62.5-37-155.7-51.9-238.6-29.4-4.8 1.3-7.4 2.6-11.9 2.6-10.7 0-19.4-8.7-19.4-19.4s5.2-17.8 15.5-20.7c27.8-7.8 56.2-13.6 97.8-13.6 64.9 0 127.6 16.1 177 45.5 8.1 4.8 11.3 11 11.3 19.7-.1 10.8-8.5 19.5-19.4 19.5zm31-76.2c-5.2 0-8.4-1.3-12.9-3.9-71.2-42.5-198.5-52.7-280.9-29.7-3.6 1-8.1 2.6-12.9 2.6-13.2 0-23.3-10.3-23.3-23.6 0-13.6 8.4-21.3 17.4-23.9 35.2-10.3 74.6-15.2 117.5-15.2 73 0 149.5 15.2 205.4 47.8 7.8 4.5 12.9 10.7 12.9 22.6 0 13.6-11 23.3-23.2 23.3z"/>
</svg>
);
}
function DiscordIcon({ iconSize } : { iconSize: number} ) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={iconSize} height={iconSize} viewBox="0 0 127.14 96.36">
<path fill="#fff" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/>
</svg>
);
}
function GitlabIcon({ iconSize } : { iconSize: number} ) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={iconSize}
height={iconSize}
viewBox="0 -10 256 256"
version="1.1"
preserveAspectRatio="xMidYMid"
>
<g>
<path d="M128.07485,236.074667 L128.07485,236.074667 L175.17885,91.1043048 L80.9708495,91.1043048 L128.07485,236.074667 L128.07485,236.074667 Z" fill="#E24329"></path>
<path d="M128.07485,236.074423 L80.9708495,91.104061 L14.9557638,91.104061 L128.07485,236.074423 L128.07485,236.074423 Z" fill="#FC6D26"></path>
<path d="M14.9558857,91.1044267 L14.9558857,91.1044267 L0.641828571,135.159589 C-0.663771429,139.17757 0.766171429,143.57955 4.18438095,146.06275 L128.074971,236.074789 L14.9558857,91.1044267 L14.9558857,91.1044267 Z" fill="#FCA326"></path>
<path d="M14.9558857,91.1045486 L80.9709714,91.1045486 L52.6000762,3.79026286 C51.1408762,-0.703146667 44.7847619,-0.701927619 43.3255619,3.79026286 L14.9558857,91.1045486 L14.9558857,91.1045486 Z" fill="#E24329"></path>
<path d="M128.07485,236.074423 L175.17885,91.104061 L241.193935,91.104061 L128.07485,236.074423 L128.07485,236.074423 Z" fill="#FC6D26"></path>
<path d="M241.193935,91.1044267 L241.193935,91.1044267 L255.507992,135.159589 C256.813592,139.17757 255.38365,143.57955 251.96544,146.06275 L128.07485,236.074789 L241.193935,91.1044267 L241.193935,91.1044267 Z" fill="#FCA326"></path>
<path d="M241.193935,91.1045486 L175.17885,91.1045486 L203.549745,3.79026286 C205.008945,-0.703146667 211.365059,-0.701927619 212.824259,3.79026286 L241.193935,91.1045486 L241.193935,91.1045486 Z" fill="#E24329"></path>
</g>
</svg>
);
}
function BitbucketIcon({ iconSize }: { iconSize: number }) {
return (
<svg
preserveAspectRatio="xMidYMid"
xmlns="http://www.w3.org/2000/svg"
viewBox="-0.9662264221278978 -0.5824607696358868 257.93281329857973 230.8324730411935"
width={iconSize}
height={iconSize}
>
<linearGradient
id="a"
x1="108.633%"
x2="46.927%"
y1="13.818%"
y2="78.776%"
>
<stop offset=".18" stopColor="#0052cc" />
<stop offset="1" stopColor="#2684ff" />
</linearGradient>
<g fill="none">
<path d="M101.272 152.561h53.449l12.901-75.32H87.06z" />
<path d="M8.308 0A8.202 8.202 0 0 0 .106 9.516l34.819 211.373a11.155 11.155 0 0 0 10.909 9.31h167.04a8.202 8.202 0 0 0 8.201-6.89l34.82-213.752a8.202 8.202 0 0 0-8.203-9.514zm146.616 152.768h-53.315l-14.436-75.42h80.67z" fill="#2684ff"/>
<path d="M244.61 77.242h-76.916l-12.909 75.36h-53.272l-62.902 74.663a11.105 11.105 0 0 0 7.171 2.704H212.73a8.196 8.196 0 0 0 8.196-6.884z" fill="url(#a)"/>
</g>
</svg>
);
}
function LinkedInIcon({ iconSize } : { iconSize: number} ) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="#fff"
height={iconSize}
width={iconSize}
viewBox="0 0 310 310"
>
<g id="XMLID_801_">
<path id="XMLID_802_" d="M72.16,99.73H9.927c-2.762,0-5,2.239-5,5v199.928c0,2.762,2.238,5,5,5H72.16c2.762,0,5-2.238,5-5V104.73 C77.16,101.969,74.922,99.73,72.16,99.73z" />
<path id="XMLID_803_" d="M41.066,0.341C18.422,0.341,0,18.743,0,41.362C0,63.991,18.422,82.4,41.066,82.4 c22.626,0,41.033-18.41,41.033-41.038C82.1,18.743,63.692,0.341,41.066,0.341z" />
<path id="XMLID_804_" d="M230.454,94.761c-24.995,0-43.472,10.745-54.679,22.954V104.73c0-2.761-2.238-5-5-5h-59.599 c-2.762,0-5,2.239-5,5v199.928c0,2.762,2.238,5,5,5h62.097c2.762,0,5-2.238,5-5v-98.918c0-33.333,9.054-46.319,32.29-46.319 c25.306,0,27.317,20.818,27.317,48.034v97.204c0,2.762,2.238,5,5,5H305c2.762,0,5-2.238,5-5V194.995 C310,145.43,300.549,94.761,230.454,94.761z" />
</g>
</svg>
);
}
function AppleIcon({ iconSize } : { iconSize: number} ) {
return (
<svg fill="#fff" height={iconSize} width={iconSize} version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22.773 22.773">
<path d="M15.769,0c0.053,0,0.106,0,0.162,0c0.13,1.606-0.483,2.806-1.228,3.675c-0.731,0.863-1.732,1.7-3.351,1.573c-0.108-1.583,0.506-2.694,1.25-3.561C13.292,0.879,14.557,0.16,15.769,0z"/>
<path d="M20.67,16.716c0,0.016,0,0.03,0,0.045c-0.455,1.378-1.104,2.559-1.896,3.655c-0.723,0.995-1.609,2.334-3.191,2.334c-1.367,0-2.275-0.879-3.676-0.903c-1.482-0.024-2.297,0.735-3.652,0.926c-0.155,0-0.31,0-0.462,0c-0.995-0.144-1.798-0.932-2.383-1.642c-1.725-2.098-3.058-4.808-3.306-8.276c0-0.34,0-0.679,0-1.019c0.105-2.482,1.311-4.5,2.914-5.478c0.846-0.52,2.009-0.963,3.304-0.765c0.555,0.086,1.122,0.276,1.619,0.464c0.471,0.181,1.06,0.502,1.618,0.485c0.378-0.011,0.754-0.208,1.135-0.347c1.116-0.403,2.21-0.865,3.652-0.648c1.733,0.262,2.963,1.032,3.723,2.22c-1.466,0.933-2.625,2.339-2.427,4.74C17.818,14.688,19.086,15.964,20.67,16.716z"/>
</svg>
);
}
function XIcon({ iconSize } : { iconSize: number} ) {
return (
<svg aria-label="X" viewBox="0 0 1200 1227" width={iconSize} height={iconSize} xmlns="http://www.w3.org/2000/svg">
<path fill="#FFFFFF" d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z"/>
</svg>
);
}
const changeColor = (c: Color, value: number) => {
if (c.isLight()) {
value = -value;
@ -180,7 +40,7 @@ export function OAuthButton({
textColor: '#000',
name: 'Google',
border: '1px solid #ddd',
icon: <GoogleIcon iconSize={iconSize} />,
icon: <BrandIcons.Google iconSize={iconSize} />,
};
break;
}
@ -190,7 +50,7 @@ export function OAuthButton({
textColor: '#fff',
border: '1px solid #333',
name: 'GitHub',
icon: <GitHubIcon iconSize={iconSize} />,
icon: <BrandIcons.GitHub iconSize={iconSize} />,
};
break;
}
@ -199,7 +59,7 @@ export function OAuthButton({
backgroundColor: '#1877F2',
textColor: '#fff',
name: 'Facebook',
icon: <FacebookIcon iconSize={iconSize} />,
icon: <BrandIcons.Facebook iconSize={iconSize} />,
};
break;
}
@ -208,7 +68,7 @@ export function OAuthButton({
backgroundColor: '#2f2f2f',
textColor: '#fff',
name: 'Microsoft',
icon: <MicrosoftIcon iconSize={iconSize} />,
icon: <BrandIcons.Microsoft iconSize={iconSize} />,
};
break;
}
@ -217,7 +77,7 @@ export function OAuthButton({
backgroundColor: '#1DB954',
textColor: '#fff',
name: 'Spotify',
icon: <SpotifyIcon iconSize={iconSize} />,
icon: <BrandIcons.Spotify iconSize={iconSize} />,
};
break;
}
@ -226,7 +86,7 @@ export function OAuthButton({
backgroundColor: '#5865F2',
textColor: '#fff',
name: 'Discord',
icon: <DiscordIcon iconSize={iconSize} />,
icon: <BrandIcons.Discord iconSize={iconSize} />,
};
break;
}
@ -236,7 +96,7 @@ export function OAuthButton({
textColor: "#fff",
border: "1px solid #333",
name: "Gitlab",
icon: <GitlabIcon iconSize={iconSize} />,
icon: <BrandIcons.Gitlab iconSize={iconSize} />,
};
break;
}
@ -246,7 +106,7 @@ export function OAuthButton({
textColor: "#fff",
border: "1px solid #333",
name: "Apple",
icon: <AppleIcon iconSize={iconSize} />,
icon: <BrandIcons.Apple iconSize={iconSize} />,
};
break;
}
@ -256,7 +116,7 @@ export function OAuthButton({
textColor: "#000",
border: "1px solid #ddd",
name: "Bitbucket",
icon: <BitbucketIcon iconSize={iconSize} />,
icon: <BrandIcons.Bitbucket iconSize={iconSize} />,
};
break;
}
@ -265,7 +125,7 @@ export function OAuthButton({
backgroundColor: "#0073b1",
textColor: "#fff",
name: "LinkedIn",
icon: <LinkedInIcon iconSize={iconSize} />,
icon: <BrandIcons.LinkedIn iconSize={iconSize} />,
};
break;
}
@ -274,7 +134,7 @@ export function OAuthButton({
backgroundColor: "#000",
textColor: "#fff",
name: "X",
icon: <XIcon iconSize={iconSize} />,
icon: <BrandIcons.X iconSize={iconSize} />,
};
break;
}